@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.
- package/application/application.js +1 -1
- package/audit/auditor.js +1 -1
- package/authentication/server/authentication.service.js +1 -1
- package/authentication/server/module.d.ts +1 -1
- package/authentication/server/module.js +1 -6
- package/document-management/api/document-management.api.d.ts +0 -4
- package/document-management/server/services/singleton.js +1 -1
- package/document-management/service-models/document.service-model.d.ts +0 -2
- package/injector/injector.d.ts +1 -1
- package/injector/injector.js +25 -13
- package/injector/types.d.ts +1 -1
- package/key-value-store/postgres/key-value-store.service.js +1 -1
- package/lock/postgres/provider.js +1 -1
- package/logger/manager.js +3 -3
- package/mail/mail.service.js +1 -1
- package/orm/data-types/bytea.d.ts +4 -14
- package/orm/data-types/bytea.js +2 -2
- package/orm/data-types/common.d.ts +18 -0
- package/orm/data-types/common.js +11 -0
- package/orm/data-types/index.d.ts +1 -0
- package/orm/data-types/index.js +1 -0
- package/orm/data-types/numeric-date.d.ts +4 -15
- package/orm/data-types/numeric-date.js +2 -2
- package/orm/data-types/timestamp.d.ts +4 -15
- package/orm/data-types/timestamp.js +2 -2
- package/orm/data-types/tsvector.d.ts +3 -13
- package/orm/data-types/tsvector.js +2 -2
- package/orm/decorators.d.ts +16 -54
- package/orm/decorators.js +24 -37
- package/orm/entity.d.ts +6 -9
- package/orm/entity.js +1 -2
- package/orm/query.d.ts +199 -61
- package/orm/query.js +2 -2
- package/orm/repository.types.d.ts +38 -9
- package/orm/server/drizzle/schema-converter.js +40 -118
- package/orm/server/query-converter.d.ts +21 -7
- package/orm/server/query-converter.js +194 -38
- package/orm/server/repository.d.ts +39 -22
- package/orm/server/repository.js +141 -71
- package/orm/server/types.d.ts +10 -2
- package/orm/sqls.d.ts +14 -16
- package/orm/sqls.js +34 -17
- package/package.json +2 -2
- package/queue/postgres/queue.js +1 -1
- package/test/drizzle/0000_nervous_iron_monger.sql +9 -0
- package/test/drizzle/meta/0000_snapshot.json +27 -7
- package/test/drizzle/meta/_journal.json +2 -44
- package/test/test.model.js +2 -6
- package/test1.js +18 -5
- package/test6.js +21 -35
- package/types/types.d.ts +8 -5
- package/utils/equals.js +2 -2
- package/utils/format-error.js +2 -2
- package/utils/helpers.js +3 -2
- package/utils/object/object.d.ts +4 -4
- package/test/drizzle/0000_sudden_sphinx.sql +0 -9
- package/test/drizzle/0001_organic_rhodey.sql +0 -2
- package/test/drizzle/0002_nice_squadron_supreme.sql +0 -1
- package/test/drizzle/0003_serious_mockingbird.sql +0 -1
- package/test/drizzle/0004_complete_pixie.sql +0 -1
- package/test/drizzle/0005_bumpy_sabra.sql +0 -1
- package/test/drizzle/0006_overrated_post.sql +0 -6
- package/test/drizzle/meta/0001_snapshot.json +0 -79
- package/test/drizzle/meta/0002_snapshot.json +0 -63
- package/test/drizzle/meta/0003_snapshot.json +0 -73
- package/test/drizzle/meta/0004_snapshot.json +0 -89
- package/test/drizzle/meta/0005_snapshot.json +0 -104
- package/test/drizzle/meta/0006_snapshot.json +0 -104
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SQL
|
|
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
|
|
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
|
|
54
|
+
return resolveTargetColumn(propertyName, table, columnDefinitionsMap);
|
|
57
55
|
}
|
|
58
56
|
function buildIndex(table, data, columnName) {
|
|
59
|
-
const
|
|
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 [
|
|
63
|
+
const [columnNameOrConfig, columnOrder] = isArray(columnValue) ? columnValue : [columnValue];
|
|
62
64
|
const order = columnOrder ?? data.order ?? 'asc';
|
|
63
|
-
let column = getColumn(table,
|
|
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) =>
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
230
|
+
return match(schema)
|
|
231
|
+
.with(P.instanceOf(UuidSchema), (s) => {
|
|
293
232
|
let column = uuid(columnName);
|
|
294
|
-
if (
|
|
233
|
+
if (s.defaultRandom) {
|
|
295
234
|
column = column.defaultRandom();
|
|
296
235
|
}
|
|
297
236
|
return column;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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 {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
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:
|
|
19
|
-
export declare function getTsVector(fields: readonly
|
|
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 '$
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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, '$
|
|
171
|
-
const queryValue = value.$
|
|
172
|
-
const { query,
|
|
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
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const
|
|
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
|
-
|
|
377
|
+
const mergedTsVector = sql.join(sqlFields, sql ` || `);
|
|
378
|
+
return mergedTsVector;
|
|
223
379
|
}
|