-
Notifications
You must be signed in to change notification settings - Fork 651
add support for SQLite Full Text Search #623
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
Changes from 6 commits
d3fc571
3730dfa
2c49cd3
aefe7b6
045cac8
5cbf5dd
a625529
6cc0d0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ export type Operator = | |
| | 'between' | ||
| | 'like' | ||
| | 'notLike' | ||
| | 'match' | ||
|
|
||
| export type ColumnDescription = $RE<{ column: ColumnName }> | ||
| export type ComparisonRight = | ||
|
|
@@ -174,6 +175,10 @@ export function sanitizeLikeString(value: string): string { | |
| return value.replace(nonLikeSafeRegexp, '_') | ||
| } | ||
|
|
||
| export function textMatches(value: string): Comparison { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one more place that takes Queries is |
||
| return { operator: 'match', right: { value } } | ||
| } | ||
|
|
||
| export function column(name: ColumnName): ColumnDescription { | ||
| return { column: name } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -481,4 +481,21 @@ describe('QueryDescription', () => { | |
| ], | ||
| }) | ||
| }) | ||
|
|
||
| it('supports textMatches as fts join', () => { | ||
| const query = Q.buildQueryDescription([ | ||
| Q.textMatches('searchable', 'hello world'), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this test doesn't seem right - did you mean BTW. This test shouldn't pass. |
||
| ]) | ||
| expect(query).toEqual({ | ||
| 'where': [ | ||
| { | ||
| 'operator': 'match', | ||
| 'right': { | ||
| 'value': 'searchable', | ||
| }, | ||
| }, | ||
| ], | ||
| 'join': [], | ||
| }) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import type { | |
| AddColumnsMigrationStep, | ||
| } from '../../../Schema/migrations' | ||
| import type { SQL } from '../index' | ||
| import { logger } from '../../../utils/common' | ||
|
|
||
| import encodeName from '../encodeName' | ||
| import encodeValue from '../encodeValue' | ||
|
|
@@ -35,8 +36,103 @@ const encodeTableIndicies: TableSchema => SQL = ({ name: tableName, columns }) = | |
| .concat([`create index ${tableName}__status on ${encodeName(tableName)} ("_status");`]) | ||
| .join('') | ||
|
|
||
| const encodeFTSTrigger: ({ | ||
| tableName: string, | ||
| ftsTableName: string, | ||
| event: 'delete' | 'insert' | 'update', | ||
| action: SQL, | ||
| }) => SQL = ({ tableName, ftsTableName, event, action }) => { | ||
| const triggerName = `${ftsTableName}_${event}` | ||
| return `create trigger ${encodeName(triggerName)} after ${event} on ${encodeName( | ||
| tableName, | ||
| )} begin ${action} end;` | ||
| } | ||
|
|
||
| const encodeFTSDeleteTrigger: ({ | ||
| tableName: string, | ||
| ftsTableName: string, | ||
| }) => SQL = ({ tableName, ftsTableName }) => | ||
| encodeFTSTrigger({ | ||
| tableName, | ||
| ftsTableName, | ||
| event: 'delete', | ||
| action: `delete from ${encodeName(ftsTableName)} where "rowid" = OLD.rowid;`, | ||
| }) | ||
|
|
||
| const encodeFTSInsertTrigger: ({ | ||
| tableName: string, | ||
| ftsTableName: string, | ||
| ftsColumns: ColumnSchema[], | ||
| }) => SQL = ({ tableName, ftsTableName, ftsColumns }) => { | ||
| const rawColumnNames = ['rowid', ...ftsColumns.map(column => column.name)] | ||
| const columns = rawColumnNames.map(encodeName) | ||
| const valueColumns = rawColumnNames.map(column => `NEW.${encodeName(column)}`) | ||
|
|
||
| const columnsSQL = columns.join(', ') | ||
| const valueColumnsSQL = valueColumns.join(', ') | ||
|
|
||
| return encodeFTSTrigger({ | ||
| tableName, | ||
| ftsTableName, | ||
| event: 'insert', | ||
| action: `insert into ${encodeName(ftsTableName)} (${columnsSQL}) values (${valueColumnsSQL});`, | ||
| }) | ||
| } | ||
|
|
||
| const encodeFTSUpdateTrigger: ({ | ||
| tableName: string, | ||
| ftsTableName: string, | ||
| ftsColumns: ColumnSchema[], | ||
| }) => SQL = ({ tableName, ftsTableName, ftsColumns }) => { | ||
| const rawColumnNames = ftsColumns.map(column => column.name) | ||
| const assignments = rawColumnNames.map( | ||
| column => `${encodeName(column)} = NEW.${encodeName(column)}`, | ||
| ) | ||
|
|
||
| const assignmentsSQL = assignments.join(', ') | ||
|
|
||
| return encodeFTSTrigger({ | ||
| tableName, | ||
| ftsTableName, | ||
| event: 'update', | ||
| action: `update ${encodeName(ftsTableName)} set ${assignmentsSQL} where "rowid" = NEW."rowid";`, | ||
| }) | ||
| } | ||
|
|
||
| const encodeFTSTriggers: ({ | ||
| tableName: string, | ||
| ftsTableName: string, | ||
| ftsColumns: ColumnSchema[], | ||
| }) => SQL = ({ tableName, ftsTableName, ftsColumns }) => { | ||
| return ( | ||
| encodeFTSDeleteTrigger({ tableName, ftsTableName }) + | ||
| encodeFTSInsertTrigger({ tableName, ftsTableName, ftsColumns }) + | ||
| encodeFTSUpdateTrigger({ tableName, ftsTableName, ftsColumns }) | ||
| ) | ||
| } | ||
|
|
||
| const encodeFTSTable: ({ | ||
| ftsTableName: string, | ||
| ftsColumns: ColumnSchema[], | ||
| }) => SQL = ({ ftsTableName, ftsColumns }) => { | ||
| const columnsSQL = ftsColumns.map(column => encodeName(column.name)).join(', ') | ||
| return `create virtual table ${encodeName(`${ftsTableName}`)} using fts4(${columnsSQL});` | ||
|
radex marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| const encodeFTSSearch: TableSchema => SQL = ({ name: tableName, columns }) => { | ||
| const ftsColumns = values(columns).filter(c => c.isSearchable) | ||
| if (ftsColumns.length === 0) { | ||
| return '' | ||
| } | ||
| const ftsTableName = `${tableName}_fts` | ||
| return ( | ||
| encodeFTSTable({ ftsTableName, ftsColumns }) + | ||
| encodeFTSTriggers({ tableName, ftsTableName, ftsColumns }) | ||
| ) | ||
| } | ||
|
|
||
| const encodeTable: TableSchema => SQL = table => | ||
| encodeCreateTable(table) + encodeTableIndicies(table) | ||
| encodeCreateTable(table) + encodeTableIndicies(table) + encodeFTSSearch(table) | ||
|
|
||
| export const encodeSchema: AppSchema => SQL = ({ tables }) => | ||
| values(tables) | ||
|
|
@@ -55,6 +151,12 @@ const encodeAddColumnsMigrationStep: AddColumnsMigrationStep => SQL = ({ table, | |
| )} = ${encodeValue(nullValue(column))};` | ||
| const addIndex = encodeIndex(column, table) | ||
|
|
||
| if (column.isSearchable) { | ||
| logger.warn( | ||
| '[DB][Worker] Support for migrations and isSearchable is still to be implemented', | ||
| ) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there's no support for schema migrations for FTS, that's fine, I can merge that - but throw an error if someone tries to do that - not just on add columns, but for create table migration as well |
||
| } | ||
|
|
||
| return addColumn + setDefaultValue + addIndex | ||
| }) | ||
| .join('') | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add to docs-master/ .... queries document a few words about Full Text Search?