-
Notifications
You must be signed in to change notification settings - Fork 49
Feat: FTS for react native supabase demo #447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { StatusBar } from 'expo-status-bar'; | ||
import { StyleSheet, View } from 'react-native'; | ||
import { SearchBarWidget } from '../library/widgets/SearchBarWidget'; | ||
|
||
export default function Modal() { | ||
return ( | ||
<View style={styles.container}> | ||
<SearchBarWidget /> | ||
<StatusBar style={'light'} /> | ||
</View> | ||
); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
flexGrow: 1, | ||
alignItems: 'center', | ||
justifyContent: 'center' | ||
} | ||
}); |
39 changes: 39 additions & 0 deletions
39
demos/react-native-supabase-todolist/library/fts/fts_helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { system } from '../powersync/system'; | ||
|
||
/** | ||
* adding * to the end of the search term will match any word that starts with the search term | ||
* e.g. searching bl will match blue, black, etc. | ||
* consult FTS5 Full-text Query Syntax documentation for more options | ||
* @param searchTerm | ||
* @returns a modified search term with options. | ||
*/ | ||
function createSearchTermWithOptions(searchTerm: string): string { | ||
const searchTermWithOptions: string = `${searchTerm}*`; | ||
return searchTermWithOptions; | ||
} | ||
|
||
/** | ||
* Search the FTS table for the given searchTerm | ||
* @param searchTerm | ||
* @param tableName | ||
* @returns results from the FTS table | ||
*/ | ||
export async function searchTable(searchTerm: string, tableName: string): Promise<any[]> { | ||
const searchTermWithOptions = createSearchTermWithOptions(searchTerm); | ||
return await system.powersync.getAll(`SELECT * FROM fts_${tableName} WHERE fts_${tableName} MATCH ? ORDER BY rank`, [ | ||
searchTermWithOptions | ||
]); | ||
} | ||
|
||
//Used to display the search results in the autocomplete text field | ||
export class SearchResult { | ||
id: string; | ||
todoName: string | null; | ||
listName: string; | ||
|
||
constructor(id: string, listName: string, todoName: string | null = null) { | ||
this.id = id; | ||
this.listName = listName; | ||
this.todoName = todoName; | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
demos/react-native-supabase-todolist/library/fts/fts_setup.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { AppSchema } from '../powersync/AppSchema'; | ||
import { ExtractType, generateJsonExtracts } from './helpers'; | ||
import { system } from '../powersync/system'; | ||
|
||
/** | ||
* Create a Full Text Search table for the given table and columns | ||
* with an option to use a different tokenizer otherwise it defaults | ||
* to unicode61. It also creates the triggers that keep the FTS table | ||
* and the PowerSync table in sync. | ||
* @param tableName | ||
* @param columns | ||
* @param tokenizationMethod | ||
*/ | ||
async function createFtsTable(tableName: string, columns: string[], tokenizationMethod = 'unicode61'): Promise<void> { | ||
const internalName = AppSchema.tables.find((table) => table.name === tableName)?.internalName; | ||
const stringColumns = columns.join(', '); | ||
|
||
return await system.powersync.writeTransaction(async (tx) => { | ||
// Add FTS table | ||
await tx.execute(` | ||
CREATE VIRTUAL TABLE IF NOT EXISTS fts_${tableName} | ||
USING fts5(id UNINDEXED, ${stringColumns}, tokenize='${tokenizationMethod}'); | ||
`); | ||
// Copy over records already in table | ||
await tx.execute(` | ||
INSERT OR REPLACE INTO fts_${tableName}(rowid, id, ${stringColumns}) | ||
SELECT rowid, id, ${generateJsonExtracts(ExtractType.columnOnly, 'data', columns)} FROM ${internalName}; | ||
`); | ||
// Add INSERT, UPDATE and DELETE and triggers to keep fts table in sync with table | ||
await tx.execute(` | ||
CREATE TRIGGER IF NOT EXISTS fts_insert_trigger_${tableName} AFTER INSERT ON ${internalName} | ||
BEGIN | ||
INSERT INTO fts_${tableName}(rowid, id, ${stringColumns}) | ||
VALUES ( | ||
NEW.rowid, | ||
NEW.id, | ||
${generateJsonExtracts(ExtractType.columnOnly, 'NEW.data', columns)} | ||
); | ||
END; | ||
`); | ||
await tx.execute(` | ||
CREATE TRIGGER IF NOT EXISTS fts_update_trigger_${tableName} AFTER UPDATE ON ${internalName} BEGIN | ||
UPDATE fts_${tableName} | ||
SET ${generateJsonExtracts(ExtractType.columnInOperation, 'NEW.data', columns)} | ||
WHERE rowid = NEW.rowid; | ||
END; | ||
`); | ||
await tx.execute(` | ||
CREATE TRIGGER IF NOT EXISTS fts_delete_trigger_${tableName} AFTER DELETE ON ${internalName} BEGIN | ||
DELETE FROM fts_${tableName} WHERE rowid = OLD.rowid; | ||
END; | ||
`); | ||
}); | ||
} | ||
|
||
/** | ||
* This is where you can add more methods to generate FTS tables in this demo | ||
* that correspond to the tables in your schema and populate them | ||
* with the data you would like to search on | ||
*/ | ||
export async function configureFts(): Promise<void> { | ||
await createFtsTable('lists', ['name'], 'porter unicode61'); | ||
await createFtsTable('todos', ['description', 'list_id']); | ||
} |
36 changes: 36 additions & 0 deletions
36
demos/react-native-supabase-todolist/library/fts/helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
type ExtractGenerator = (jsonColumnName: string, columnName: string) => string; | ||
|
||
export enum ExtractType { | ||
columnOnly, | ||
columnInOperation | ||
} | ||
|
||
type ExtractGeneratorMap = Map<ExtractType, ExtractGenerator>; | ||
|
||
function _createExtract(jsonColumnName: string, columnName: string): string { | ||
return `json_extract(${jsonColumnName}, '$.${columnName}')`; | ||
} | ||
|
||
const extractGeneratorsMap: ExtractGeneratorMap = new Map<ExtractType, ExtractGenerator>([ | ||
[ExtractType.columnOnly, (jsonColumnName: string, columnName: string) => _createExtract(jsonColumnName, columnName)], | ||
[ | ||
ExtractType.columnInOperation, | ||
(jsonColumnName: string, columnName: string) => { | ||
const extract = _createExtract(jsonColumnName, columnName); | ||
return `${columnName} = ${extract}`; | ||
} | ||
] | ||
]); | ||
|
||
export const generateJsonExtracts = (type: ExtractType, jsonColumnName: string, columns: string[]): string => { | ||
const generator = extractGeneratorsMap.get(type); | ||
if (generator == null) { | ||
throw new Error('Unexpected null generator for key: $type'); | ||
} | ||
|
||
if (columns.length == 1) { | ||
return generator(jsonColumnName, columns[0]); | ||
} | ||
|
||
return columns.map((column) => generator(jsonColumnName, column)).join(', '); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
demos/react-native-supabase-todolist/library/widgets/AutoCompleteWidget.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { View, StyleSheet } from 'react-native'; | ||
import { Input, ListItem } from '@rneui/themed'; | ||
import React, { useState } from 'react'; | ||
import { IconNode } from '@rneui/base'; | ||
|
||
export interface AutocompleteWidgetProps { | ||
data: any[]; | ||
onChange: (value: string) => void; | ||
placeholder?: string; | ||
onPress: (id: string) => void; | ||
leftIcon?: IconNode; | ||
} | ||
|
||
export const Autocomplete: React.FC<AutocompleteWidgetProps> = ({ data, onChange, placeholder, onPress, leftIcon }) => { | ||
const [value, setValue] = useState(''); | ||
const [menuVisible, setMenuVisible] = useState(false); | ||
|
||
return ( | ||
<View style={styles.container}> | ||
<View style={styles.inputContainer}> | ||
<Input | ||
onFocus={() => { | ||
if (value?.length === 0) { | ||
setMenuVisible(true); | ||
} | ||
}} | ||
leftIcon={leftIcon} | ||
placeholder={placeholder} | ||
onBlur={() => setMenuVisible(false)} | ||
underlineColorAndroid={'transparent'} | ||
inputContainerStyle={{ borderBottomWidth: 0 }} | ||
onChangeText={(text) => { | ||
onChange(text); | ||
setMenuVisible(true); | ||
setValue(text); | ||
}} | ||
containerStyle={{ | ||
borderColor: 'black', | ||
borderWidth: 1, | ||
borderRadius: 4, | ||
height: 48, | ||
backgroundColor: 'white' | ||
}} | ||
/> | ||
</View> | ||
{menuVisible && ( | ||
<View style={styles.menuContainer}> | ||
{data.map((val, index) => ( | ||
<ListItem | ||
bottomDivider | ||
key={index} | ||
onPress={() => { | ||
setMenuVisible(false); | ||
onPress(val.id); | ||
}} | ||
style={{ paddingBottom: 8 }}> | ||
<ListItem.Content> | ||
{val.listName && ( | ||
<ListItem.Title style={{ fontSize: 18, color: 'black' }}>{val.listName}</ListItem.Title> | ||
)} | ||
{val.todoName && ( | ||
<ListItem.Subtitle style={{ fontSize: 14, color: 'grey' }}> | ||
{'\u2022'} {val.todoName} | ||
</ListItem.Subtitle> | ||
)} | ||
</ListItem.Content> | ||
<ListItem.Chevron /> | ||
</ListItem> | ||
))} | ||
</View> | ||
)} | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flexDirection: 'column', | ||
flex: 1, | ||
flexGrow: 1, | ||
marginHorizontal: 8 | ||
}, | ||
inputContainer: { | ||
flexDirection: 'row', | ||
flex: 0, | ||
marginVertical: 8 | ||
}, | ||
menuContainer: { | ||
flex: 2, | ||
flexGrow: 1, | ||
flexDirection: 'column' | ||
} | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.