@tstdl/base 0.93.20 → 0.93.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/application/application.js +1 -1
  2. package/audit/auditor.js +1 -1
  3. package/authentication/server/authentication.service.js +1 -1
  4. package/authentication/server/module.d.ts +1 -1
  5. package/authentication/server/module.js +1 -6
  6. package/document-management/api/document-management.api.d.ts +0 -4
  7. package/document-management/server/services/singleton.js +1 -1
  8. package/document-management/service-models/document.service-model.d.ts +0 -2
  9. package/injector/injector.d.ts +1 -1
  10. package/injector/injector.js +25 -13
  11. package/injector/types.d.ts +1 -1
  12. package/key-value-store/postgres/key-value-store.service.js +1 -1
  13. package/lock/postgres/provider.js +1 -1
  14. package/logger/manager.js +3 -3
  15. package/mail/mail.service.js +1 -1
  16. package/orm/data-types/bytea.d.ts +4 -14
  17. package/orm/data-types/bytea.js +2 -2
  18. package/orm/data-types/common.d.ts +18 -0
  19. package/orm/data-types/common.js +11 -0
  20. package/orm/data-types/index.d.ts +1 -0
  21. package/orm/data-types/index.js +1 -0
  22. package/orm/data-types/numeric-date.d.ts +4 -15
  23. package/orm/data-types/numeric-date.js +2 -2
  24. package/orm/data-types/timestamp.d.ts +4 -15
  25. package/orm/data-types/timestamp.js +2 -2
  26. package/orm/data-types/tsvector.d.ts +3 -13
  27. package/orm/data-types/tsvector.js +2 -2
  28. package/orm/decorators.d.ts +16 -54
  29. package/orm/decorators.js +24 -37
  30. package/orm/entity.d.ts +6 -9
  31. package/orm/entity.js +1 -2
  32. package/orm/query.d.ts +199 -61
  33. package/orm/query.js +2 -2
  34. package/orm/repository.types.d.ts +38 -9
  35. package/orm/server/drizzle/schema-converter.js +40 -118
  36. package/orm/server/query-converter.d.ts +21 -7
  37. package/orm/server/query-converter.js +194 -38
  38. package/orm/server/repository.d.ts +39 -22
  39. package/orm/server/repository.js +141 -71
  40. package/orm/server/types.d.ts +10 -2
  41. package/orm/sqls.d.ts +14 -16
  42. package/orm/sqls.js +34 -17
  43. package/package.json +2 -2
  44. package/queue/postgres/queue.js +1 -1
  45. package/test/drizzle/0000_nervous_iron_monger.sql +9 -0
  46. package/test/drizzle/meta/0000_snapshot.json +27 -7
  47. package/test/drizzle/meta/_journal.json +2 -44
  48. package/test/test.model.js +2 -6
  49. package/test1.js +18 -5
  50. package/test6.js +21 -35
  51. package/types/types.d.ts +8 -5
  52. package/utils/equals.js +2 -2
  53. package/utils/format-error.js +2 -2
  54. package/utils/helpers.js +3 -2
  55. package/utils/object/object.d.ts +4 -4
  56. package/test/drizzle/0000_sudden_sphinx.sql +0 -9
  57. package/test/drizzle/0001_organic_rhodey.sql +0 -2
  58. package/test/drizzle/0002_nice_squadron_supreme.sql +0 -1
  59. package/test/drizzle/0003_serious_mockingbird.sql +0 -1
  60. package/test/drizzle/0004_complete_pixie.sql +0 -1
  61. package/test/drizzle/0005_bumpy_sabra.sql +0 -1
  62. package/test/drizzle/0006_overrated_post.sql +0 -6
  63. package/test/drizzle/meta/0001_snapshot.json +0 -79
  64. package/test/drizzle/meta/0002_snapshot.json +0 -63
  65. package/test/drizzle/meta/0003_snapshot.json +0 -73
  66. package/test/drizzle/meta/0004_snapshot.json +0 -89
  67. package/test/drizzle/meta/0005_snapshot.json +0 -104
  68. package/test/drizzle/meta/0006_snapshot.json +0 -104
@@ -1,4 +1,4 @@
1
- import { SQL, sql } from 'drizzle-orm';
1
+ import { SQL } from 'drizzle-orm';
2
2
  import { toCamelCase, toSnakeCase } from 'drizzle-orm/casing';
3
3
  import { boolean, check, doublePrecision, foreignKey, index, integer, jsonb, pgSchema, primaryKey, text, unique, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
4
4
  import { match, P } from 'ts-pattern';
@@ -6,25 +6,23 @@ import { MultiKeyMap } from '../../../data-structures/multi-key-map.js';
6
6
  import { tryGetEnumName } from '../../../enumeration/enumeration.js';
7
7
  import { NotSupportedError } from '../../../errors/not-supported.error.js';
8
8
  import { JsonPath } from '../../../json-path/json-path.js';
9
- import { setweight, toTsVector } from '../../../orm/sqls.js';
10
9
  import { reflectionRegistry } from '../../../reflection/registry.js';
11
10
  import { ArraySchema, BooleanSchema, DefaultSchema, EnumerationSchema, getObjectSchema, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, StringSchema, Uint8ArraySchema } from '../../../schema/index.js';
12
11
  import { compareByValueSelectionToOrder, orderRest } from '../../../utils/comparison.js';
13
12
  import { decodeText, encodeUtf8 } from '../../../utils/encoding.js';
14
13
  import { enumValues } from '../../../utils/enum.js';
15
14
  import { memoize, memoizeSingle } from '../../../utils/function/memoize.js';
16
- import { iif } from '../../../utils/helpers.js';
17
15
  import { compileDereferencer } from '../../../utils/object/dereference.js';
18
16
  import { fromEntries, objectEntries } from '../../../utils/object/object.js';
19
17
  import { assertDefined, assertDefinedPass, isArray, isDefined, isNotNullOrUndefined, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
20
18
  import { resolveValueOrProvider } from '../../../utils/value-or-provider.js';
21
- import { bytea, numericDate, timestamp, tsvector } from '../../data-types/index.js';
19
+ import { bytea, numericDate, timestamp } from '../../data-types/index.js';
22
20
  import { JsonSchema } from '../../schemas/json.js';
23
21
  import { NumericDateSchema } from '../../schemas/numeric-date.js';
24
22
  import { TimestampSchema } from '../../schemas/timestamp.js';
25
23
  import { UuidSchema } from '../../schemas/uuid.js';
26
24
  import { decryptBytes, encryptBytes } from '../encryption.js';
27
- import { convertQuery } from '../query-converter.js';
25
+ import { convertQuery, resolveTargetColumn } from '../query-converter.js';
28
26
  const getDbSchema = memoizeSingle(pgSchema);
29
27
  export const getDrizzleTableFromType = memoize(_getDrizzleTableFromType);
30
28
  const columnDefinitionsSymbol = Symbol('columnDefinitions');
@@ -53,14 +51,18 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
53
51
  const columnDefinitions = getPostgresColumnEntries(type, dbSchema, tableName);
54
52
  const columnDefinitionsMap = new Map(columnDefinitions.map((column) => [column.objectPath.path, column]));
55
53
  function getColumn(table, propertyName) {
56
- return assertDefinedPass(table[propertyName], `Property "${propertyName}" does not exist on ${type.name}`);
54
+ return resolveTargetColumn(propertyName, table, columnDefinitionsMap);
57
55
  }
58
56
  function buildIndex(table, data, columnName) {
59
- const columns = (data.columns ?? [columnName]).map((columnValue) => {
57
+ const resolvedColumns = resolveValueOrProvider(data.columns ?? [columnName], table);
58
+ const columns = resolvedColumns.map((columnValue) => {
59
+ if (columnValue instanceof SQL) {
60
+ return columnValue;
61
+ }
60
62
  assertDefined(columnValue, 'Missing column name for index.');
61
- const [columnName, columnOrder] = isString(columnValue) ? [columnValue] : columnValue;
63
+ const [columnNameOrConfig, columnOrder] = isArray(columnValue) ? columnValue : [columnValue];
62
64
  const order = columnOrder ?? data.order ?? 'asc';
63
- let column = getColumn(table, columnName);
65
+ let column = isString(columnNameOrConfig) ? getColumn(table, columnNameOrConfig) : columnNameOrConfig;
64
66
  column = column[order]();
65
67
  if (data.options?.nulls == 'first') {
66
68
  column = column.nullsFirst();
@@ -71,15 +73,22 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
71
73
  return column;
72
74
  });
73
75
  const indexFn = (data.options?.unique == true) ? uniqueIndex : index;
76
+ console.log({ name: data.options?.name ?? getIndexName(tableName, columns, { naming: data.options?.naming }) });
74
77
  let builder = indexFn(data.options?.name ?? getIndexName(tableName, columns, { naming: data.options?.naming })).using(data.options?.using ?? 'btree', ...columns);
75
78
  if (isDefined(data.options?.where)) {
76
79
  const query = convertQuery(data.options.where(table), table, columnDefinitionsMap);
77
80
  builder = builder.where(query.inlineParams());
78
81
  }
82
+ if (isDefined(data.options?.with)) {
83
+ builder = builder.with(data.options.with);
84
+ }
85
+ if (data.options?.concurrently == true) {
86
+ builder = builder.concurrently();
87
+ }
79
88
  return builder;
80
89
  }
81
90
  function buildPrimaryKey(table) {
82
- const columns = primaryKeyColumnDefinitions.map((columnDefinition) => getColumn(table, columnDefinition.name));
91
+ const columns = primaryKeyColumnDefinitions.map((columnDefinition) => table[columnDefinition.name]);
83
92
  return primaryKey({
84
93
  name: mergedTableReflectionData.compundPrimaryKeyName ?? getPrimaryKeyName(tableName, columns, { naming: mergedTableReflectionData.compundPrimaryKeyNaming }),
85
94
  columns,
@@ -88,40 +97,33 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
88
97
  const primaryKeyColumnDefinitions = columnDefinitions.filter((columnDefinition) => columnDefinition.reflectionData?.primaryKey == true);
89
98
  const skipPrimaryKey = primaryKeyColumnDefinitions.length > 1;
90
99
  const columnEntries = columnDefinitions.map((entry) => [entry.name, entry.buildType({ skipPrimaryKey })]);
91
- const fullTextSearchColumnEntries = tableReflectionDatas
92
- .flatMap((tableReflectionData) => tableReflectionData.fullTextSearch ?? [])
93
- .flatMap((ftsData) => getFullTextSearchColumns(ftsData, () => drizzleSchema, (property) => getColumn(drizzleSchema, property)));
94
- const drizzleSchema = dbSchema.table(tableName, fromEntries([
95
- ...columnEntries,
96
- ...fullTextSearchColumnEntries,
97
- ]), (drizzleTable) => {
100
+ const drizzleSchema = dbSchema.table(tableName, fromEntries(columnEntries), (drizzleTable) => {
98
101
  const table = drizzleTable;
99
- const indexes = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.index).filter(isDefined).map((data) => buildIndex(drizzleTable, data));
100
- const fullTextIndexes = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.fullTextSearch ?? []).flatMap((ftsData) => getFullTextSearchIndexes(ftsData, table, (property) => getColumn(drizzleTable, property)));
102
+ const indexes = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.index).filter(isDefined).map((data) => buildIndex(table, data));
101
103
  const checks = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.checks).filter(isDefined).map((data) => check(data.name, data.builder(table)));
102
104
  return [
103
105
  ...((primaryKeyColumnDefinitions.length > 1)
104
- ? [buildPrimaryKey(drizzleTable)]
106
+ ? [buildPrimaryKey(table)]
105
107
  : []),
106
108
  ...(columnDefinitions.map((columnDefinition) => {
107
109
  const indexData = columnDefinition.reflectionData?.index;
108
110
  if (isUndefined(indexData)) {
109
111
  return undefined;
110
112
  }
111
- return buildIndex(drizzleTable, indexData, columnDefinition.name);
113
+ return buildIndex(table, indexData, columnDefinition.name);
112
114
  }).filter(isDefined)),
113
115
  ...tableReflectionDatas.flatMap((tableReflectionData) => {
114
116
  return tableReflectionData.foreignKeys?.map((foreignKeyData) => {
115
117
  const foreignTable = getDrizzleTableFromType(foreignKeyData.target(), dbSchema.schemaName);
116
118
  return foreignKey({
117
119
  name: foreignKeyData.options?.name ?? getForeignKeyName(tableName, foreignKeyData.columns, { naming: foreignKeyData.options?.naming }),
118
- columns: foreignKeyData.columns.map((column) => getColumn(drizzleTable, column)),
120
+ columns: foreignKeyData.columns.map((column) => getColumn(table, column)),
119
121
  foreignColumns: foreignKeyData.foreignColumns.map((column) => getColumn(foreignTable, column)),
120
122
  });
121
123
  }) ?? [];
122
124
  }),
123
125
  ...tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.unique).filter(isDefined).map((data) => {
124
- const columns = data.columns?.map((column) => getColumn(drizzleTable, column));
126
+ const columns = data.columns?.map((column) => getColumn(table, column));
125
127
  let constraint = unique(data.options?.name ?? getUniqueName(tableName, columns, { naming: data.options?.naming })).on(...columns);
126
128
  if (data.options?.nulls == 'not distinct') {
127
129
  constraint = constraint.nullsNotDistinct();
@@ -129,7 +131,6 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
129
131
  return constraint;
130
132
  }),
131
133
  ...indexes,
132
- ...fullTextIndexes,
133
134
  ...checks,
134
135
  ];
135
136
  });
@@ -137,69 +138,6 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
137
138
  drizzleSchema[columnDefinitionsMapSymbol] = columnDefinitionsMap;
138
139
  return drizzleSchema;
139
140
  }
140
- function getFullTextSearchColumns(data, tableProvider, columnProvider) {
141
- if (isDefined(data.vector)) {
142
- const name = `fts_v_${data.name}`;
143
- const generationSource = () => {
144
- const table = tableProvider();
145
- const source = assertDefinedPass(resolveValueOrProvider(data.source ?? data.vector?.rawVectorSource, table), 'Either "source" or "vector.rawVectorSource" must be provided for full-text search vector generation.');
146
- const language = resolveValueOrProvider(data.vector?.language ?? 'simple', table);
147
- return match(source)
148
- .with(P.instanceOf(SQL), (sqlExpression) => sqlExpression)
149
- .otherwise((paths) => {
150
- const vectors = paths.map((property) => {
151
- const column = columnProvider(property);
152
- const tsVector = toTsVector(language, column);
153
- const weight = data.vector?.weights?.[property];
154
- if (isDefined(weight)) {
155
- return setweight(tsVector, weight);
156
- }
157
- return tsVector;
158
- });
159
- return sql `(${sql.join(vectors, sql ` || `)})`;
160
- });
161
- };
162
- const column = tsvector(name).generatedAlwaysAs(generationSource);
163
- return [[name, column]];
164
- }
165
- return [];
166
- }
167
- function getFullTextSearchIndexes(data, table, columnProvider) {
168
- return [
169
- ...iif(isDefined(data.vector), () => {
170
- const indexName = `fts_${data.name}_bm25`;
171
- const textSource = assertDefinedPass(data.source, 'Either "source" or "vector.rawVectorSource" must be provided for full-text search vector generation.');
172
- const source = resolveValueOrProvider(textSource, table);
173
- const indexExpression = match(source)
174
- .with(P.instanceOf(SQL), (sqlExpression) => sqlExpression)
175
- .otherwise((paths) => {
176
- const columns = paths.map((property) => columnProvider(property));
177
- return sql `(${sql.join(columns, sql ` || `)})`;
178
- });
179
- const bm25Index = index(indexName).using('bm25', indexExpression).with({ key_field: 'id' });
180
- return [bm25Index];
181
- }, () => []),
182
- ...iif(isDefined(data.vector), () => {
183
- const columnName = `fts_v_${data.name}`;
184
- const indexName = `${columnName}_gin`;
185
- const ginIndex = index(indexName).using('gin', columnProvider(columnName)).with({ fastupdate: 'off' });
186
- return [ginIndex];
187
- }, () => []),
188
- ...iif(isDefined(data.trigram), () => {
189
- const indexName = `fts_t_${data.name}_gist`;
190
- const textSource = assertDefinedPass(data.source, 'Either "source" or "vector.rawVectorSource" must be provided for full-text search vector generation.');
191
- const source = resolveValueOrProvider(textSource, table);
192
- const indexExpression = match(source)
193
- .with(P.instanceOf(SQL), (sqlExpression) => sqlExpression)
194
- .otherwise((paths) => {
195
- const columns = paths.map((property) => columnProvider(property));
196
- return sql `(${sql.join(columns, sql ` || `)})`;
197
- });
198
- const gistIndex = index(indexName).using('gist', indexExpression).with({ fastupdate: 'off' });
199
- return [gistIndex];
200
- }, () => []),
201
- ];
202
- }
203
141
  function getPostgresColumnEntries(type, dbSchema, tableName, path = new JsonPath({ dollar: false }), prefix = '') {
204
142
  const metadata = reflectionRegistry.getMetadata(type);
205
143
  assertDefined(metadata, `Type ${type.name} does not have reflection metadata (path: ${path.toString()}).`);
@@ -289,41 +227,25 @@ function getPostgresBaseColumn(columnName, dbSchema, schema, reflectionData, con
289
227
  if (reflectionData.encrypted) {
290
228
  return bytea(columnName);
291
229
  }
292
- if (schema instanceof UuidSchema) {
230
+ return match(schema)
231
+ .with(P.instanceOf(UuidSchema), (s) => {
293
232
  let column = uuid(columnName);
294
- if (schema.defaultRandom) {
233
+ if (s.defaultRandom) {
295
234
  column = column.defaultRandom();
296
235
  }
297
236
  return column;
298
- }
299
- if (schema instanceof TimestampSchema) {
300
- return timestamp(columnName);
301
- }
302
- if (schema instanceof NumericDateSchema) {
303
- return numericDate(columnName);
304
- }
305
- if (schema instanceof NumberSchema) {
306
- return schema.integer
307
- ? integer(columnName)
308
- : doublePrecision(columnName);
309
- }
310
- if (schema instanceof StringSchema) {
311
- return text(columnName);
312
- }
313
- if (schema instanceof BooleanSchema) {
314
- return boolean(columnName);
315
- }
316
- if (schema instanceof EnumerationSchema) {
317
- const pgEnum = getPgEnum(dbSchema, schema.enumeration, context);
318
- return pgEnum(columnName);
319
- }
320
- if (schema instanceof JsonSchema) {
321
- return jsonb(columnName);
322
- }
323
- if (schema instanceof Uint8ArraySchema) {
324
- return bytea(columnName);
325
- }
326
- throw new NotSupportedError(`Schema "${schema.constructor.name}" not supported on type "${context.type.name}" for property "${context.property}"`);
237
+ })
238
+ .with(P.instanceOf(TimestampSchema), () => timestamp(columnName))
239
+ .with(P.instanceOf(NumericDateSchema), () => numericDate(columnName))
240
+ .with(P.instanceOf(NumberSchema), (s) => (s.integer ? integer(columnName) : doublePrecision(columnName)))
241
+ .with(P.instanceOf(StringSchema), () => text(columnName))
242
+ .with(P.instanceOf(BooleanSchema), () => boolean(columnName))
243
+ .with(P.instanceOf(EnumerationSchema), (s) => getPgEnum(dbSchema, s.enumeration, context)(columnName))
244
+ .with(P.instanceOf(JsonSchema), () => jsonb(columnName))
245
+ .with(P.instanceOf(Uint8ArraySchema), () => bytea(columnName))
246
+ .otherwise(() => {
247
+ throw new NotSupportedError(`Schema "${schema.constructor.name}" not supported on type "${context.type.name}" for property "${context.property}"`);
248
+ });
327
249
  }
328
250
  const enumNames = new Map();
329
251
  const enums = new MultiKeyMap();
@@ -1,7 +1,22 @@
1
- import { SQL } from 'drizzle-orm';
2
- import type { Record } from '../../types/index.js';
3
- import type { FtsParser, Query } from '../query.js';
4
- import type { ColumnDefinition, PgTableFromType } from './types.js';
1
+ import { SQL, type SQLWrapper } from 'drizzle-orm';
2
+ import type { BaseEntity } from '../entity.js';
3
+ import type { Query, TsVectorParser, TsVectorWeight } from '../query.js';
4
+ import type { TargetColumn } from '../repository.types.js';
5
+ import type { ColumnDefinition, ExtraConfigColumnsFromType, PgTableFromType } from './types.js';
6
+ /**
7
+ * Resolves a target to a Drizzle PgColumn or SQLWrapper.
8
+ * @param target The target to resolve.
9
+ * @param table The Drizzle table object.
10
+ * @param columnDefinitionsMap A map from property names to column definitions.
11
+ */
12
+ export declare function resolveTargetColumn<T extends BaseEntity>(target: TargetColumn<T> | ColumnDefinition, table: PgTableFromType | ExtraConfigColumnsFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQLWrapper;
13
+ /**
14
+ * Resolves multiple targets to a Drizzle PgColumn or SQLWrapper.
15
+ * @param targets The targets to resolve.
16
+ * @param table The Drizzle table object.
17
+ * @param columnDefinitionsMap A map from property names to column definitions.
18
+ */
19
+ export declare function resolveTargetColumns<T extends BaseEntity>(targets: (TargetColumn<T> | ColumnDefinition)[], table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQLWrapper[];
5
20
  /**
6
21
  * Converts a query object into a Drizzle SQL condition.
7
22
  * Recursively handles nested logical operators and maps property names to database columns.
@@ -15,6 +30,5 @@ import type { ColumnDefinition, PgTableFromType } from './types.js';
15
30
  * @throws {Error} If an unsupported query type is encountered.
16
31
  */
17
32
  export declare function convertQuery(query: Query, table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQL;
18
- export declare function getTsQuery(text: string | SQL, language: string | SQL, parser: FtsParser): SQL;
19
- export declare function getTsVector(fields: readonly string[], language: string | SQL, table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>, weights?: Partial<Record<string, 'A' | 'B' | 'C' | 'D'>>): SQL;
20
- export declare function getColumnConcatenation(fields: readonly string[], table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQL;
33
+ export declare function getTsQuery(text: string | SQL, language: string | SQL, parser: TsVectorParser): SQL;
34
+ export declare function getTsVector(fields: readonly (SQLWrapper | readonly [field: SQLWrapper, weight?: TsVectorWeight])[], language: string | SQL): SQL;
@@ -1,10 +1,35 @@
1
- import { and, eq, gt, gte, inArray, isNotNull, isNull, isSQLWrapper, lt, lte, ne, not, notInArray, or, SQL, sql } from 'drizzle-orm';
1
+ import { and, Column, eq, gt, gte, inArray, isNotNull, isNull, isSQLWrapper, lt, lte, ne, not, notInArray, or, SQL, sql } from 'drizzle-orm';
2
2
  import { match } from 'ts-pattern';
3
3
  import { NotSupportedError } from '../../errors/not-supported.error.js';
4
+ import { toArray } from '../../utils/array/index.js';
4
5
  import { hasOwnProperty, objectEntries } from '../../utils/object/object.js';
5
- import { assertDefinedPass, isDefined, isPrimitive, isRegExp, isString, isUndefined } from '../../utils/type-guards.js';
6
- import { isSimilar, phraseToTsQuery, plainToTsQuery, setweight, toTsQuery, toTsVector, websearchToTsQuery } from '../sqls.js';
6
+ import { assert, assertDefinedPass, isArray, isDefined, isPrimitive, isRegExp, isString, isUndefined } from '../../utils/type-guards.js';
7
+ import { isSimilar, isStrictWordSimilar, isWordSimilar, phraseToTsQuery, plainToTsQuery, setweight, similarity, strictWordSimilarity, toTsQuery, toTsVector, websearchToTsQuery, wordSimilarity } from '../sqls.js';
7
8
  const sqlTrue = sql `true`;
9
+ /**
10
+ * Resolves a target to a Drizzle PgColumn or SQLWrapper.
11
+ * @param target The target to resolve.
12
+ * @param table The Drizzle table object.
13
+ * @param columnDefinitionsMap A map from property names to column definitions.
14
+ */
15
+ export function resolveTargetColumn(target, table, columnDefinitionsMap) {
16
+ if ((target instanceof SQL) || (target instanceof SQL.Aliased) || (target instanceof Column)) {
17
+ return target;
18
+ }
19
+ const columnDef = isString(target)
20
+ ? assertDefinedPass(columnDefinitionsMap.get(target), `Could not map property ${target} to column.`)
21
+ : target; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
22
+ return table[columnDef.name];
23
+ }
24
+ /**
25
+ * Resolves multiple targets to a Drizzle PgColumn or SQLWrapper.
26
+ * @param targets The targets to resolve.
27
+ * @param table The Drizzle table object.
28
+ * @param columnDefinitionsMap A map from property names to column definitions.
29
+ */
30
+ export function resolveTargetColumns(targets, table, columnDefinitionsMap) {
31
+ return targets.map((target) => resolveTargetColumn(target, table, columnDefinitionsMap));
32
+ }
8
33
  /**
9
34
  * Converts a query object into a Drizzle SQL condition.
10
35
  * Recursively handles nested logical operators and maps property names to database columns.
@@ -56,21 +81,41 @@ export function convertQuery(query, table, columnDefinitionsMap) {
56
81
  }
57
82
  break;
58
83
  }
59
- case '$fts': {
60
- const ftsQuery = value;
61
- const method = ftsQuery.method ?? 'vector';
62
- const sqlValue = match(method)
63
- .with('vector', () => {
64
- const tsquery = getTsQuery(ftsQuery.text, ftsQuery.vector?.language ?? 'simple', ftsQuery.vector?.parser ?? 'raw');
65
- const tsvector = getTsVector(ftsQuery.fields, ftsQuery.vector?.language ?? 'simple', table, columnDefinitionsMap, ftsQuery.vector?.weights);
66
- return sql `${tsvector} @@ ${tsquery}`;
67
- })
68
- .with('trigram', () => {
69
- const searchExpression = getColumnConcatenation(ftsQuery.fields, table, columnDefinitionsMap);
70
- return isSimilar(searchExpression, ftsQuery.text);
71
- })
72
- .exhaustive();
73
- conditions.push(sqlValue);
84
+ case '$tsvector': {
85
+ const tsvectorQuery = value.$tsvector;
86
+ const fields = assertDefinedPass(tsvectorQuery.fields, '`fields` is required for tsvector search.');
87
+ const convertedFields = fields.map((target) => {
88
+ const [field, weight] = isArray(target) ? target : [target, undefined];
89
+ const sqlField = resolveTargetColumn(field, table, columnDefinitionsMap);
90
+ if (isDefined(weight)) {
91
+ return [sqlField, weight];
92
+ }
93
+ return sqlField;
94
+ });
95
+ const tsquery = getTsQuery(tsvectorQuery.query, tsvectorQuery.language ?? 'simple', tsvectorQuery.parser ?? 'raw');
96
+ const tsvector = getTsVector(convertedFields, tsvectorQuery.language ?? 'simple');
97
+ conditions.push(sql `${tsvector} @@ ${tsquery}`);
98
+ break;
99
+ }
100
+ case '$trigram': {
101
+ const trigramQuery = value.$trigram;
102
+ const fields = isArray(trigramQuery.fields) ? trigramQuery.fields : [trigramQuery.fields];
103
+ const convertedFields = fields.map((target) => resolveTargetColumn(target, table, columnDefinitionsMap));
104
+ const searchExpression = sql `(${sql.join(convertedFields, sql ` || ' ' || `)})`;
105
+ if (isDefined(trigramQuery.threshold)) {
106
+ const similarityFn = getTrigramSimilarityFunction(trigramQuery.type);
107
+ conditions.push(sql `${similarityFn(trigramQuery.query, searchExpression)} > ${trigramQuery.threshold}`);
108
+ }
109
+ else {
110
+ const isSimilarOperator = getTrigramIsSimilarOperator(trigramQuery.type);
111
+ conditions.push(isSimilarOperator(trigramQuery.query, searchExpression));
112
+ }
113
+ break;
114
+ }
115
+ case '$parade': {
116
+ const paradeQuery = value.$parade;
117
+ const convertedParadeQuery = convertParadeDbQuery(paradeQuery, table, columnDefinitionsMap);
118
+ conditions.push(sql `${table.id} @@@ ${convertedParadeQuery}`);
74
119
  break;
75
120
  }
76
121
  default: {
@@ -167,18 +212,53 @@ function getCondition(property, value, column, table, columnDefinitionsMap) {
167
212
  const operator = (regexp.flags?.includes('i') ?? false) ? sql.raw('~*') : sql.raw('~');
168
213
  return sql `${column} ${operator} ${regexp.value}`;
169
214
  }
170
- if (hasOwnProperty(value, '$fts')) {
171
- const queryValue = value.$fts;
172
- const { query, method = 'vector', language = 'simple', parser = 'plain' } = isString(queryValue)
215
+ if (hasOwnProperty(value, '$tsvector')) {
216
+ const queryValue = value.$tsvector;
217
+ const { query, language = 'simple', parser = 'plain' } = isString(queryValue)
173
218
  ? { query: queryValue }
174
219
  : queryValue;
175
- if (method == 'similarity') {
176
- return isSimilar(column, query);
177
- }
178
220
  const tsquery = getTsQuery(query, language, parser);
179
221
  const tsvector = toTsVector(language, column);
180
222
  return sql `${tsvector} @@ ${tsquery}`;
181
223
  }
224
+ if (hasOwnProperty(value, '$trigram')) {
225
+ const queryValue = value.$trigram;
226
+ const { query, type, threshold } = isString(queryValue) ? { query: queryValue } : queryValue;
227
+ if (isDefined(threshold)) {
228
+ const similarityFn = getTrigramSimilarityFunction(type);
229
+ return sql `${similarityFn(column, query)} > ${threshold}`;
230
+ }
231
+ return isSimilar(column, query);
232
+ }
233
+ if (hasOwnProperty(value, '$match')) {
234
+ const queryValue = value.$match;
235
+ const { query, distance, prefix, conjunctionMode } = isString(queryValue) ? { query: queryValue } : queryValue;
236
+ const parts = [sql `field => ${column.name}`, sql `value => ${query}`];
237
+ if (isDefined(distance)) {
238
+ parts.push(sql `distance => ${distance}`);
239
+ }
240
+ if (isDefined(prefix)) {
241
+ parts.push(sql `prefix => ${prefix}`);
242
+ }
243
+ if (isDefined(conjunctionMode)) {
244
+ parts.push(sql `conjunction_mode => ${conjunctionMode}`);
245
+ }
246
+ const searchFn = sql `paradedb.match(${sql.join(parts, sql.raw(', '))})`;
247
+ return sql `${table.id} @@@ ${searchFn}`;
248
+ }
249
+ if (hasOwnProperty(value, '$tantivy')) {
250
+ const queryValue = value.$tantivy;
251
+ const { query, lenient, conjunctionMode } = isString(queryValue) ? { query: queryValue } : queryValue;
252
+ const parts = [sql `${query}`];
253
+ if (isDefined(lenient)) {
254
+ parts.push(sql `lenient => ${lenient}`);
255
+ }
256
+ if (isDefined(conjunctionMode)) {
257
+ parts.push(sql `conjunction_mode => ${conjunctionMode}`);
258
+ }
259
+ const searchFn = sql `paradedb.parse(${sql.join(parts, sql.raw(', '))})`;
260
+ return sql `${table.id} @@@ ${searchFn}`;
261
+ }
182
262
  if (hasOwnProperty(value, '$geoShape')) {
183
263
  throw new NotSupportedError('$geoShape is not supported.');
184
264
  }
@@ -187,6 +267,90 @@ function getCondition(property, value, column, table, columnDefinitionsMap) {
187
267
  }
188
268
  throw new Error(`Unsupported query type "${property}".`);
189
269
  }
270
+ function getTrigramSimilarityFunction(type = 'phrase') {
271
+ return match(type)
272
+ .with('phrase', () => similarity)
273
+ .with('word', () => wordSimilarity)
274
+ .with('strict-word', () => strictWordSimilarity)
275
+ .exhaustive();
276
+ }
277
+ function getTrigramIsSimilarOperator(type = 'phrase') {
278
+ return match(type)
279
+ .with('phrase', () => isSimilar)
280
+ .with('word', () => isWordSimilar)
281
+ .with('strict-word', () => isStrictWordSimilar)
282
+ .exhaustive();
283
+ }
284
+ /**
285
+ * Recursively converts a ParadeDbQueryObject into a Drizzle SQL object representing the JSONB structure.
286
+ */
287
+ function convertParadeDbQuery(query, table, columnDefinitionsMap) {
288
+ const entries = objectEntries(query);
289
+ assert(entries.length == 1, 'ParadeDB query object must have exactly one key.');
290
+ const [key, queryValue] = entries[0];
291
+ const buildObject = (properties) => {
292
+ const chunks = [];
293
+ for (const [propKey, propValue] of properties) {
294
+ chunks.push(sql `'${sql.raw(propKey)}', ${propValue}`);
295
+ }
296
+ return sql `jsonb_build_object(${sql.join(chunks, sql `, `)})`;
297
+ };
298
+ const convertRecursive = (subQuery) => {
299
+ return convertParadeDbQuery(subQuery, table, columnDefinitionsMap);
300
+ };
301
+ const innerSql = match(key)
302
+ .with('all', 'empty', () => sql `null`)
303
+ .with('term', 'fuzzy_term', 'regex', 'phrase', 'match', 'parse_with_field', 'exists', 'range', 'phrase_prefix', 'regex_phrase', 'range_term', 'range_intersects', 'range_contains', 'range_within', (_) => {
304
+ const properties = [];
305
+ for (const [propKey, propValue] of objectEntries(queryValue)) {
306
+ if (propKey == 'field') {
307
+ const column = resolveTargetColumn(propValue, table, columnDefinitionsMap);
308
+ properties.push([propKey, column.name]);
309
+ }
310
+ else {
311
+ properties.push([propKey, propValue]);
312
+ }
313
+ }
314
+ return buildObject(properties);
315
+ })
316
+ .with('parse', 'more_like_this', () => buildObject(objectEntries(queryValue)))
317
+ .with('boolean', () => {
318
+ const properties = [];
319
+ for (const [propKey, propValue] of objectEntries(queryValue)) {
320
+ const subQueries = toArray(propValue ?? []).map(convertRecursive);
321
+ properties.push([propKey, sql `jsonb_build_array(${sql.join(subQueries, sql `, `)})`]);
322
+ }
323
+ return buildObject(properties);
324
+ })
325
+ .with('boost', 'const_score', () => {
326
+ const properties = [];
327
+ for (const [propKey, propValue] of objectEntries(queryValue)) {
328
+ if (propKey == 'query') {
329
+ properties.push([propKey, convertRecursive(propValue)]);
330
+ }
331
+ else {
332
+ properties.push([propKey, propValue]);
333
+ }
334
+ }
335
+ return buildObject(properties);
336
+ })
337
+ .with('disjunction_max', 'term_set', (_k) => {
338
+ const properties = [];
339
+ const subqueryArrayKey = (key == 'disjunction_max') ? 'disjuncts' : 'terms';
340
+ for (const [propKey, propValue] of objectEntries(queryValue)) {
341
+ if (propKey == subqueryArrayKey) {
342
+ const subQueries = propValue.map(convertRecursive);
343
+ properties.push([propKey, sql `jsonb_build_array(${sql.join(subQueries, sql `, `)})`]);
344
+ }
345
+ else {
346
+ properties.push([propKey, propValue]);
347
+ }
348
+ }
349
+ return buildObject(properties);
350
+ })
351
+ .exhaustive();
352
+ return buildObject([[key, innerSql]]);
353
+ }
190
354
  export function getTsQuery(text, language, parser) {
191
355
  switch (parser) {
192
356
  case 'raw':
@@ -201,23 +365,15 @@ export function getTsQuery(text, language, parser) {
201
365
  throw new NotSupportedError(`Unsupported text search parser: ${parser}`);
202
366
  }
203
367
  }
204
- export function getTsVector(fields, language, table, columnDefinitionsMap, weights) {
205
- const tsvector = sql.join(fields.map((field) => {
206
- const columnDef = assertDefinedPass(columnDefinitionsMap.get(field), `Could not map property ${field} to column.`);
207
- const column = table[columnDef.name];
208
- const vector = toTsVector(language, column);
209
- const weight = weights?.[field];
368
+ export function getTsVector(fields, language) {
369
+ const sqlFields = fields.map((fieldWithWeight) => {
370
+ const [field, weight] = isArray(fieldWithWeight) ? fieldWithWeight : [fieldWithWeight, undefined];
371
+ const vector = toTsVector(language, field);
210
372
  if (isDefined(weight)) {
211
373
  return setweight(vector, weight);
212
374
  }
213
375
  return vector;
214
- }), sql ` || `);
215
- return tsvector;
216
- }
217
- export function getColumnConcatenation(fields, table, columnDefinitionsMap) {
218
- const columns = fields.map((field) => {
219
- const columnDef = assertDefinedPass(columnDefinitionsMap.get(field), `Could not map property ${field} to column.`);
220
- return table[columnDef.name];
221
376
  });
222
- return sql `(${sql.join(columns, sql ` || ' ' || `)})`;
377
+ const mergedTsVector = sql.join(sqlFields, sql ` || `);
378
+ return mergedTsVector;
223
379
  }