@tstdl/base 0.93.27 → 0.93.29
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/orm/data-types/tsvector.d.ts +3 -1
- package/orm/data-types/tsvector.js +2 -0
- package/orm/decorators.d.ts +168 -18
- package/orm/decorators.js +48 -2
- package/orm/index.d.ts +1 -1
- package/orm/index.js +1 -1
- package/orm/{query.d.ts → query/base.d.ts} +14 -166
- package/orm/{query.js → query/base.js} +3 -2
- package/orm/query/index.d.ts +2 -0
- package/orm/query/index.js +2 -0
- package/orm/query/parade.d.ts +172 -0
- package/orm/query/parade.js +4 -0
- package/orm/repository.types.d.ts +27 -6
- package/orm/schemas/index.d.ts +1 -0
- package/orm/schemas/index.js +1 -0
- package/orm/schemas/tsvector.d.ts +12 -0
- package/orm/schemas/tsvector.js +20 -0
- package/orm/server/drizzle/schema-converter.js +100 -20
- package/orm/server/query-converter.d.ts +2 -2
- package/orm/server/query-converter.js +138 -91
- package/orm/server/repository.d.ts +6 -6
- package/orm/server/repository.js +34 -23
- package/orm/sqls.d.ts +19 -16
- package/orm/sqls.js +28 -38
- package/package.json +6 -6
- package/test/drizzle/0000_natural_cannonball.sql +9 -0
- package/test/drizzle/meta/0000_snapshot.json +29 -10
- package/test/drizzle/meta/_journal.json +2 -2
- package/test/test.model.js +8 -2
- package/test1.js +9 -9
- package/utils/enum.js +1 -1
- package/utils/object/object.d.ts +5 -3
- package/utils/object/object.js +17 -7
- package/utils/string/casing.d.ts +3 -0
- package/utils/string/casing.js +15 -0
- package/utils/string/index.d.ts +1 -1
- package/utils/string/index.js +1 -1
- package/test/drizzle/0000_sturdy_patch.sql +0 -9
- package/utils/string/snake-case.d.ts +0 -1
- package/utils/string/snake-case.js +0 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { SQL } from 'drizzle-orm';
|
|
2
|
+
import type { LiteralUnion, UnionToIntersection } from 'type-fest';
|
|
3
|
+
import type { Record } from '../../types/index.js';
|
|
4
|
+
import type { BaseEntity } from '../entity.js';
|
|
5
|
+
import type { TargetColumnPath } from '../repository.types.js';
|
|
6
|
+
/** Represents a comparison query using various operators like $eq, $ne, $gt, $in, etc. */
|
|
7
|
+
export type ParadeComparisonQuery = Partial<ComparisonParadeQuery>;
|
|
8
|
+
/** Union of keys representing comparison query operators. */
|
|
9
|
+
export type ParadeComparisonQueryTypes = keyof ParadeComparisonQuery;
|
|
10
|
+
/** Array containing all valid comparison query operator keys. */
|
|
11
|
+
export declare const paradeComparisonQueryTypes: ParadeComparisonQueryTypes[];
|
|
12
|
+
/** Represents specialized query types beyond simple comparisons. */
|
|
13
|
+
export type ParadeSpecialQuery<T extends BaseEntity = BaseEntity> = ParadeSearchQuery<T> | {
|
|
14
|
+
$parade: ParadeSpecialQueryObject<T>;
|
|
15
|
+
};
|
|
16
|
+
/** Union of keys representing special query operators. */
|
|
17
|
+
export type ParadeSpecialQueryTypes = keyof UnionToIntersection<ParadeSpecialQuery>;
|
|
18
|
+
/** Array containing all valid special query operator keys. */
|
|
19
|
+
export declare const paradeSpecialQueryTypes: ParadeSpecialQueryTypes[];
|
|
20
|
+
/** Represents a ParadeDB query on a specific field. */
|
|
21
|
+
export type ComparisonParadeQuery = {
|
|
22
|
+
$parade: ParadeComparisonQueryObject;
|
|
23
|
+
};
|
|
24
|
+
/** Represents a term in a proximity query. */
|
|
25
|
+
export type ParadeProximityTerm = string | {
|
|
26
|
+
regex: string;
|
|
27
|
+
maxExpansions?: number;
|
|
28
|
+
} | {
|
|
29
|
+
array: readonly (string | {
|
|
30
|
+
regex: string;
|
|
31
|
+
maxExpansions?: number;
|
|
32
|
+
})[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Represents a bound for a ParadeDB range query.
|
|
36
|
+
*/
|
|
37
|
+
type ParadeRangeBound<V> = {
|
|
38
|
+
included: V;
|
|
39
|
+
} | {
|
|
40
|
+
excluded: V;
|
|
41
|
+
};
|
|
42
|
+
/** A recursive type representing the rich ParadeDB / Tantivy query DSL for FIELD-LEVEL queries. */
|
|
43
|
+
export type ParadeComparisonQueryObject = {
|
|
44
|
+
term?: {
|
|
45
|
+
value: any;
|
|
46
|
+
boost?: number;
|
|
47
|
+
const?: number;
|
|
48
|
+
};
|
|
49
|
+
fuzzyTerm?: {
|
|
50
|
+
value: string;
|
|
51
|
+
distance?: number;
|
|
52
|
+
transpositionCostOne?: boolean;
|
|
53
|
+
prefix?: boolean;
|
|
54
|
+
boost?: number;
|
|
55
|
+
const?: number;
|
|
56
|
+
};
|
|
57
|
+
regex?: {
|
|
58
|
+
pattern: string;
|
|
59
|
+
boost?: number;
|
|
60
|
+
const?: number;
|
|
61
|
+
};
|
|
62
|
+
phrase?: {
|
|
63
|
+
phrases: readonly string[];
|
|
64
|
+
slop?: number;
|
|
65
|
+
boost?: number;
|
|
66
|
+
const?: number;
|
|
67
|
+
};
|
|
68
|
+
match?: {
|
|
69
|
+
value: string;
|
|
70
|
+
distance?: number;
|
|
71
|
+
prefix?: boolean;
|
|
72
|
+
conjunctionMode?: boolean;
|
|
73
|
+
tokenizer?: ParadeTokenizerObject;
|
|
74
|
+
transpositionCostOne?: boolean;
|
|
75
|
+
boost?: number;
|
|
76
|
+
const?: number;
|
|
77
|
+
};
|
|
78
|
+
exists?: null;
|
|
79
|
+
range?: {
|
|
80
|
+
lowerBound?: ParadeRangeBound<any> | null;
|
|
81
|
+
upperBound?: ParadeRangeBound<any> | null;
|
|
82
|
+
isDatetime?: boolean;
|
|
83
|
+
};
|
|
84
|
+
termSet?: {
|
|
85
|
+
terms: readonly string[];
|
|
86
|
+
conjunctionMode?: boolean;
|
|
87
|
+
};
|
|
88
|
+
phrasePrefix?: {
|
|
89
|
+
phrases: readonly string[];
|
|
90
|
+
maxExpansions?: number;
|
|
91
|
+
};
|
|
92
|
+
regexPhrase?: {
|
|
93
|
+
regexes: readonly string[];
|
|
94
|
+
slop?: number;
|
|
95
|
+
maxExpansions?: number;
|
|
96
|
+
};
|
|
97
|
+
rangeTerm?: {
|
|
98
|
+
value: any;
|
|
99
|
+
operator?: 'Intersects' | 'Contains' | 'Within';
|
|
100
|
+
};
|
|
101
|
+
proximity?: {
|
|
102
|
+
ordered?: boolean;
|
|
103
|
+
distance: number;
|
|
104
|
+
terms: readonly [ParadeProximityTerm, ParadeProximityTerm, ...ParadeProximityTerm[]];
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* A type representing the rich ParadeDB / Tantivy query DSL for TOP-LEVEL queries.
|
|
109
|
+
*/
|
|
110
|
+
export type ParadeSpecialQueryObject<T extends BaseEntity = BaseEntity> = {
|
|
111
|
+
all?: null;
|
|
112
|
+
empty?: null;
|
|
113
|
+
moreLikeThis?: {
|
|
114
|
+
keyValue?: any;
|
|
115
|
+
document?: Record<TargetColumnPath<T>, any>;
|
|
116
|
+
fields?: readonly TargetColumnPath<T>[];
|
|
117
|
+
minDocFrequency?: number;
|
|
118
|
+
maxDocFrequency?: number;
|
|
119
|
+
minTermFrequency?: number;
|
|
120
|
+
maxQueryTerms?: number;
|
|
121
|
+
minWordLength?: number;
|
|
122
|
+
maxWordLength?: number;
|
|
123
|
+
stopWords?: readonly string[];
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Represents a ParadeDB search using its native JSON-based query language,
|
|
128
|
+
* or a simplified multi-field match query.
|
|
129
|
+
*/
|
|
130
|
+
export type ParadeSearchQuery<T extends BaseEntity = BaseEntity> = {
|
|
131
|
+
$parade: {
|
|
132
|
+
/** The fields to search across. */
|
|
133
|
+
fields: readonly [TargetColumnPath<T>, ...TargetColumnPath<T>[]];
|
|
134
|
+
/** The search query string. */
|
|
135
|
+
query: string | SQL<string>;
|
|
136
|
+
/**
|
|
137
|
+
* The conjunction mode for token matching within the query string.
|
|
138
|
+
* `true` for AND (all terms must match), `false` for OR (any term can match).
|
|
139
|
+
* Defaults to `false` (OR).
|
|
140
|
+
*/
|
|
141
|
+
conjunction?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* If greater than zero, enables fuzzy matching.
|
|
144
|
+
* Specifies the maximum Levenshtein distance (edit distance). Max value is 2.
|
|
145
|
+
* @default 0
|
|
146
|
+
*/
|
|
147
|
+
distance?: number;
|
|
148
|
+
/**
|
|
149
|
+
* When `true` and fuzzy matching is enabled, considers transpositions as a single edit.
|
|
150
|
+
* @default true
|
|
151
|
+
*/
|
|
152
|
+
transpositionCostOne?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* When `true` and fuzzy matching is enabled, allows prefix matching.
|
|
155
|
+
* @default false
|
|
156
|
+
*/
|
|
157
|
+
prefix?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* The alias of a field configuration to use for the search.
|
|
160
|
+
* If specified, all fields must have this alias defined.
|
|
161
|
+
*/
|
|
162
|
+
alias?: string;
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Represents a ParadeDB tokenizer configuration object for a `match` query.
|
|
167
|
+
*/
|
|
168
|
+
export type ParadeTokenizerObject = {
|
|
169
|
+
type: LiteralUnion<'default' | 'whitespace' | 'raw' | 'keyword' | 'regex' | 'ngram' | 'source_code' | 'chinese_compatible' | 'chinese_lindera' | 'korean_lindera' | 'japanese_lindera' | 'jieba' | 'icu', string>;
|
|
170
|
+
[key: string]: any;
|
|
171
|
+
};
|
|
172
|
+
export {};
|
|
@@ -8,7 +8,7 @@ import type { UntaggedDeep } from '../types/tagged.js';
|
|
|
8
8
|
import type { AnyColumn, SQL, SQLWrapper } from 'drizzle-orm';
|
|
9
9
|
import type { PartialDeep } from 'type-fest';
|
|
10
10
|
import type { BaseEntity, Entity, EntityMetadata } from './entity.js';
|
|
11
|
-
import type { FullTextSearchQuery, Query } from './query.js';
|
|
11
|
+
import type { FullTextSearchQuery, Query } from './query/index.js';
|
|
12
12
|
import type { TsHeadlineOptions } from './sqls.js';
|
|
13
13
|
type WithSql<T> = {
|
|
14
14
|
[P in keyof T]: T[P] extends Record ? WithSql<T[P]> : (T[P] | SQL);
|
|
@@ -79,13 +79,23 @@ export type HighlightOptions<T extends BaseEntity> = {
|
|
|
79
79
|
* The source to generate the highlight from. Can be one or more property paths or a raw SQL expression.
|
|
80
80
|
*/
|
|
81
81
|
source: TargetColumnPath<T> | SQL<string>;
|
|
82
|
+
/**
|
|
83
|
+
* If true, includes the byte offsets of highlight matches in the result.
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
includePositions?: boolean;
|
|
82
87
|
} & (TsHeadlineOptions | ParadeDbHighlightOptions);
|
|
83
88
|
/**
|
|
84
|
-
* Options for highlighting with ParadeDB (`pdb.snippet`).
|
|
89
|
+
* Options for highlighting with ParadeDB (`pdb.snippet` or `pdb.snippets`).
|
|
85
90
|
*/
|
|
86
91
|
export type ParadeDbHighlightOptions = {
|
|
87
92
|
/**
|
|
88
|
-
*
|
|
93
|
+
* If true, retrieves multiple snippets using `pdb.snippets`.
|
|
94
|
+
* If false (default), retrieves a single best snippet using `pdb.snippet`.
|
|
95
|
+
*/
|
|
96
|
+
multiple?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Limits the number of characters in each snippet. Defaults to 150.
|
|
89
99
|
*/
|
|
90
100
|
maxNumChars?: number;
|
|
91
101
|
/**
|
|
@@ -97,13 +107,19 @@ export type ParadeDbHighlightOptions = {
|
|
|
97
107
|
*/
|
|
98
108
|
endTag?: string;
|
|
99
109
|
/**
|
|
100
|
-
*
|
|
110
|
+
* The maximum number of snippets to return per document. Only used when `multiple` is true.
|
|
111
|
+
* @default 5
|
|
101
112
|
*/
|
|
102
113
|
limit?: number;
|
|
103
114
|
/**
|
|
104
|
-
*
|
|
115
|
+
* The number of snippets to skip before returning results. Only used when `multiple` is true.
|
|
116
|
+
* @default 0
|
|
105
117
|
*/
|
|
106
118
|
offset?: number;
|
|
119
|
+
/**
|
|
120
|
+
* The order in which to sort the snippets. Can be 'score' (default) or 'position'. Only used when `multiple` is true.
|
|
121
|
+
*/
|
|
122
|
+
sortBy?: 'score' | 'position';
|
|
107
123
|
};
|
|
108
124
|
/**
|
|
109
125
|
* Options for the `search` method.
|
|
@@ -151,7 +167,12 @@ export type SearchOptions<T extends BaseEntity> = SimplifyObject<TypedOmit<LoadM
|
|
|
151
167
|
export type SearchResult<T extends BaseEntity> = {
|
|
152
168
|
entity: T;
|
|
153
169
|
score?: number;
|
|
154
|
-
highlight?: string;
|
|
170
|
+
highlight?: string | string[];
|
|
171
|
+
/**
|
|
172
|
+
* Byte offsets of highlight matches. Format is an array of start/end pairs.
|
|
173
|
+
* e.g., `[{ start: 14, end: 19 }]`
|
|
174
|
+
*/
|
|
175
|
+
highlightPositions?: [start: number, end: number][];
|
|
155
176
|
};
|
|
156
177
|
/**
|
|
157
178
|
* Options for update operations (currently inherits from LoadOptions, primarily for ordering).
|
package/orm/schemas/index.d.ts
CHANGED
package/orm/schemas/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { JsonPath } from '../../json-path/json-path.js';
|
|
2
|
+
import { Schema, type SchemaDecoratorOptions, type SchemaPropertyDecorator, type SchemaTestOptions, type SchemaTestResult } from '../../schema/index.js';
|
|
3
|
+
export declare class TsVectorSchema extends Schema<string> {
|
|
4
|
+
readonly name = "TsVector";
|
|
5
|
+
_test(_value: any, _path: JsonPath, _options: SchemaTestOptions): SchemaTestResult<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function tsvector(): TsVectorSchema;
|
|
8
|
+
/**
|
|
9
|
+
* Defines a `tsvector` column.
|
|
10
|
+
* For automatically generated tsvector columns, use `@GeneratedTsVector()` instead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function TsVectorProperty(options?: SchemaDecoratorOptions): SchemaPropertyDecorator;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Property, Schema, SchemaError } from '../../schema/index.js';
|
|
2
|
+
export class TsVectorSchema extends Schema {
|
|
3
|
+
name = 'TsVector';
|
|
4
|
+
_test(_value, _path, _options) {
|
|
5
|
+
return {
|
|
6
|
+
valid: false,
|
|
7
|
+
error: SchemaError.expectedButGot('nothing', 'value', _path, { customMessage: `TSVectorSchema does not support validation as tsvector columns are not directly mapped to application data.` })
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function tsvector() {
|
|
12
|
+
return new TsVectorSchema();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Defines a `tsvector` column.
|
|
16
|
+
* For automatically generated tsvector columns, use `@GeneratedTsVector()` instead.
|
|
17
|
+
*/
|
|
18
|
+
export function TsVectorProperty(options) {
|
|
19
|
+
return Property(tsvector(), options);
|
|
20
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SQL } from 'drizzle-orm';
|
|
1
|
+
import { sql, 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';
|
|
@@ -13,16 +13,17 @@ import { decodeText, encodeUtf8 } from '../../../utils/encoding.js';
|
|
|
13
13
|
import { enumValues } from '../../../utils/enum.js';
|
|
14
14
|
import { memoize, memoizeSingle } from '../../../utils/function/memoize.js';
|
|
15
15
|
import { compileDereferencer } from '../../../utils/object/dereference.js';
|
|
16
|
-
import { fromEntries, objectEntries } from '../../../utils/object/object.js';
|
|
17
|
-
import { assertDefined, assertDefinedPass, isArray, isDefined, isNotNullOrUndefined, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
|
|
16
|
+
import { fromEntries, mapObjectKeysToSnakeCase, objectEntries } from '../../../utils/object/object.js';
|
|
17
|
+
import { assertDefined, assertDefinedPass, isArray, isDefined, isNotNull, isNotNullOrUndefined, isNull, isString, isUndefined } from '../../../utils/type-guards.js';
|
|
18
18
|
import { resolveValueOrProvider } from '../../../utils/value-or-provider.js';
|
|
19
|
-
import { bytea, numericDate, timestamp } from '../../data-types/index.js';
|
|
19
|
+
import { bytea, numericDate, timestamp, tsvector } from '../../data-types/index.js';
|
|
20
20
|
import { JsonSchema } from '../../schemas/json.js';
|
|
21
21
|
import { NumericDateSchema } from '../../schemas/numeric-date.js';
|
|
22
22
|
import { TimestampSchema } from '../../schemas/timestamp.js';
|
|
23
|
+
import { TsVectorSchema } from '../../schemas/tsvector.js';
|
|
23
24
|
import { UuidSchema } from '../../schemas/uuid.js';
|
|
24
25
|
import { decryptBytes, encryptBytes } from '../encryption.js';
|
|
25
|
-
import { convertQuery, resolveTargetColumn } from '../query-converter.js';
|
|
26
|
+
import { convertQuery, resolveTargetColumn, resolveTargetColumns } from '../query-converter.js';
|
|
26
27
|
const getDbSchema = memoizeSingle(pgSchema);
|
|
27
28
|
export const getDrizzleTableFromType = memoize(_getDrizzleTableFromType);
|
|
28
29
|
const columnDefinitionsSymbol = Symbol('columnDefinitions');
|
|
@@ -59,10 +60,13 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
|
|
|
59
60
|
if (columnValue instanceof SQL) {
|
|
60
61
|
return columnValue;
|
|
61
62
|
}
|
|
62
|
-
assertDefined(columnValue, 'Missing column
|
|
63
|
+
assertDefined(columnValue, 'Missing column for index.');
|
|
63
64
|
const [columnNameOrConfig, columnOrder] = isArray(columnValue) ? columnValue : [columnValue];
|
|
64
65
|
const order = columnOrder ?? data.order ?? 'asc';
|
|
65
|
-
let column =
|
|
66
|
+
let column = getColumn(table, columnNameOrConfig);
|
|
67
|
+
if (isDefined(data.options?.opclass)) {
|
|
68
|
+
return sql `${column} ${sql.raw(data.options.opclass)}`;
|
|
69
|
+
}
|
|
66
70
|
column = column[order]();
|
|
67
71
|
if (data.options?.nulls == 'first') {
|
|
68
72
|
column = column.nullsFirst();
|
|
@@ -73,7 +77,6 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
|
|
|
73
77
|
return column;
|
|
74
78
|
});
|
|
75
79
|
const indexFn = (data.options?.unique == true) ? uniqueIndex : index;
|
|
76
|
-
console.log({ name: data.options?.name ?? getIndexName(tableName, columns, { naming: data.options?.naming }) });
|
|
77
80
|
let builder = indexFn(data.options?.name ?? getIndexName(tableName, columns, { naming: data.options?.naming })).using(data.options?.using ?? 'btree', ...columns);
|
|
78
81
|
if (isDefined(data.options?.where)) {
|
|
79
82
|
const query = convertQuery(data.options.where(table), table, columnDefinitionsMap);
|
|
@@ -90,16 +93,90 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
|
|
|
90
93
|
function buildPrimaryKey(table) {
|
|
91
94
|
const columns = primaryKeyColumnDefinitions.map((columnDefinition) => table[columnDefinition.name]);
|
|
92
95
|
return primaryKey({
|
|
93
|
-
name: mergedTableReflectionData.
|
|
96
|
+
name: mergedTableReflectionData.compoundPrimaryKeyName ?? getPrimaryKeyName(tableName, columns, { naming: mergedTableReflectionData.compoundPrimaryKeyNaming }),
|
|
94
97
|
columns,
|
|
95
98
|
});
|
|
96
99
|
}
|
|
100
|
+
function buildParadeCast(columnSql, paradeOptions) {
|
|
101
|
+
const { tokenizer, alias } = paradeOptions;
|
|
102
|
+
const { type, parameters: rawParameters = [], filters = {} } = isString(tokenizer) ? { type: tokenizer, parameters: [], filters: undefined } : (tokenizer ?? {});
|
|
103
|
+
const parameters = objectEntries({ alias, ...filters })
|
|
104
|
+
.filter(([_, value]) => isDefined(value))
|
|
105
|
+
.map(([key, value]) => {
|
|
106
|
+
return `'${toSnakeCase(key)}=${String(value)}'`;
|
|
107
|
+
});
|
|
108
|
+
if (isUndefined(type)) {
|
|
109
|
+
return sql `${columnSql}`;
|
|
110
|
+
}
|
|
111
|
+
if (parameters.length == 0) {
|
|
112
|
+
return sql `((${columnSql})::pdb.${sql.raw(type)})`;
|
|
113
|
+
}
|
|
114
|
+
const allParameters = [...rawParameters.map((param) => String(param)), ...parameters];
|
|
115
|
+
const parametersSql = sql.join(allParameters.map((parameter) => sql `${sql.raw(parameter)}`), sql `, `);
|
|
116
|
+
return sql `((${columnSql})::pdb.${sql.raw(type)}(${parametersSql}))`;
|
|
117
|
+
}
|
|
118
|
+
function buildParadeIndex(table) {
|
|
119
|
+
const paradeIndexData = mergedTableReflectionData.paradeIndex;
|
|
120
|
+
const propertyLevelColumns = columnDefinitions.filter((definition) => isDefined(definition.reflectionData?.paradeField));
|
|
121
|
+
if (isUndefined(paradeIndexData) && (propertyLevelColumns.length == 0)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const { naming, where, concurrently, columns: classLevelColumns, compoundColumns, expressions, ...indexOptions } = paradeIndexData ?? {};
|
|
125
|
+
const classLevelColumnSqls = (classLevelColumns ?? [])
|
|
126
|
+
.map((configOrProvider) => resolveValueOrProvider(configOrProvider, table))
|
|
127
|
+
.map((columnConfig) => {
|
|
128
|
+
const [columnOrSql, fieldOptions] = isArray(columnConfig) ? columnConfig : [columnConfig, undefined];
|
|
129
|
+
const resolvedColumnSql = resolveTargetColumn(columnOrSql, table, columnDefinitionsMap);
|
|
130
|
+
if (isUndefined(fieldOptions)) {
|
|
131
|
+
return resolvedColumnSql.getSQL();
|
|
132
|
+
}
|
|
133
|
+
return buildParadeCast(resolvedColumnSql, fieldOptions);
|
|
134
|
+
});
|
|
135
|
+
const compoundColumnSqls = (compoundColumns ?? []).map(([columnsOrProvider, fieldOptions]) => {
|
|
136
|
+
const columns = resolveValueOrProvider(columnsOrProvider, table);
|
|
137
|
+
const resolvedColumns = resolveTargetColumns(columns, table, columnDefinitionsMap);
|
|
138
|
+
const compoundExpression = sql.join(resolvedColumns, sql ` || ' ' || `);
|
|
139
|
+
return buildParadeCast(compoundExpression, fieldOptions);
|
|
140
|
+
});
|
|
141
|
+
const expressionSqls = (expressions ?? []).map(([expressionSqlOrProvider, paradeOptions]) => {
|
|
142
|
+
const expressionSql = resolveValueOrProvider(expressionSqlOrProvider, table);
|
|
143
|
+
return buildParadeCast(expressionSql, paradeOptions);
|
|
144
|
+
});
|
|
145
|
+
const propertyLevelColumnSqls = propertyLevelColumns
|
|
146
|
+
.map((columnDef) => {
|
|
147
|
+
const columnSql = resolveTargetColumn(columnDef, table, columnDefinitionsMap).getSQL();
|
|
148
|
+
const paradeOptions = columnDef.reflectionData.paradeField;
|
|
149
|
+
return buildParadeCast(columnSql, paradeOptions);
|
|
150
|
+
});
|
|
151
|
+
const indexName = getIndexName(tableName, 'parade', { naming });
|
|
152
|
+
const indexColumns = [
|
|
153
|
+
table.id, // this orm always uses 'id' as key field as every entity has it
|
|
154
|
+
...classLevelColumnSqls,
|
|
155
|
+
...propertyLevelColumnSqls,
|
|
156
|
+
...compoundColumnSqls,
|
|
157
|
+
...expressionSqls,
|
|
158
|
+
];
|
|
159
|
+
let builder = index(indexName).using('bm25', ...indexColumns);
|
|
160
|
+
if (isDefined(where)) {
|
|
161
|
+
const query = convertQuery(where(table), table, columnDefinitionsMap);
|
|
162
|
+
builder = builder.where(query.inlineParams());
|
|
163
|
+
}
|
|
164
|
+
if (concurrently == true) {
|
|
165
|
+
builder = builder.concurrently();
|
|
166
|
+
}
|
|
167
|
+
builder = builder.with({
|
|
168
|
+
key_field: `'id'`,
|
|
169
|
+
...mapObjectKeysToSnakeCase(indexOptions),
|
|
170
|
+
});
|
|
171
|
+
return builder;
|
|
172
|
+
}
|
|
97
173
|
const primaryKeyColumnDefinitions = columnDefinitions.filter((columnDefinition) => columnDefinition.reflectionData?.primaryKey == true);
|
|
98
174
|
const skipPrimaryKey = primaryKeyColumnDefinitions.length > 1;
|
|
99
175
|
const columnEntries = columnDefinitions.map((entry) => [entry.name, entry.buildType({ skipPrimaryKey })]);
|
|
100
176
|
const drizzleSchema = dbSchema.table(tableName, fromEntries(columnEntries), (drizzleTable) => {
|
|
101
177
|
const table = drizzleTable;
|
|
102
178
|
const indexes = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.index).filter(isDefined).map((data) => buildIndex(table, data));
|
|
179
|
+
const bm25Index = buildParadeIndex(table);
|
|
103
180
|
const checks = tableReflectionDatas.flatMap((tableReflectionData) => tableReflectionData.checks).filter(isDefined).map((data) => check(data.name, data.builder(table)));
|
|
104
181
|
return [
|
|
105
182
|
...((primaryKeyColumnDefinitions.length > 1)
|
|
@@ -131,6 +208,7 @@ export function _getDrizzleTableFromType(type, fallbackSchemaName) {
|
|
|
131
208
|
return constraint;
|
|
132
209
|
}),
|
|
133
210
|
...indexes,
|
|
211
|
+
...(isNotNull(bm25Index) ? [bm25Index] : []),
|
|
134
212
|
...checks,
|
|
135
213
|
];
|
|
136
214
|
});
|
|
@@ -243,6 +321,7 @@ function getPostgresBaseColumn(columnName, dbSchema, schema, reflectionData, con
|
|
|
243
321
|
.with(P.instanceOf(EnumerationSchema), (s) => getPgEnum(dbSchema, s.enumeration, context)(columnName))
|
|
244
322
|
.with(P.instanceOf(JsonSchema), () => jsonb(columnName))
|
|
245
323
|
.with(P.instanceOf(Uint8ArraySchema), () => bytea(columnName))
|
|
324
|
+
.with(P.instanceOf(TsVectorSchema), () => tsvector(columnName))
|
|
246
325
|
.otherwise(() => {
|
|
247
326
|
throw new NotSupportedError(`Schema "${schema.constructor.name}" not supported on type "${context.type.name}" for property "${context.property}"`);
|
|
248
327
|
});
|
|
@@ -273,23 +352,24 @@ export function getPgEnum(schema, enumeration, context) {
|
|
|
273
352
|
function getDefaultTableName(type) {
|
|
274
353
|
return toSnakeCase(isString(type.entityName) ? type.entityName : type.name.replace(/\d+$/u, ''));
|
|
275
354
|
}
|
|
276
|
-
function getPrimaryKeyName(tableName,
|
|
277
|
-
return getIdentifier(tableName,
|
|
355
|
+
function getPrimaryKeyName(tableName, columnsOrBaseName, options) {
|
|
356
|
+
return getIdentifier(tableName, columnsOrBaseName, 'pk', options);
|
|
278
357
|
}
|
|
279
|
-
function getIndexName(tableName,
|
|
280
|
-
return getIdentifier(tableName,
|
|
358
|
+
function getIndexName(tableName, columnsOrBaseName, options) {
|
|
359
|
+
return getIdentifier(tableName, columnsOrBaseName, 'idx', options);
|
|
281
360
|
}
|
|
282
|
-
function getUniqueName(tableName,
|
|
283
|
-
return getIdentifier(tableName,
|
|
361
|
+
function getUniqueName(tableName, columnsOrBaseName, options) {
|
|
362
|
+
return getIdentifier(tableName, columnsOrBaseName, 'unique', options);
|
|
284
363
|
}
|
|
285
|
-
function getForeignKeyName(tableName,
|
|
286
|
-
return getIdentifier(tableName,
|
|
364
|
+
function getForeignKeyName(tableName, columnsOrBaseName, options) {
|
|
365
|
+
return getIdentifier(tableName, columnsOrBaseName, 'fkey', options);
|
|
287
366
|
}
|
|
288
|
-
function getIdentifier(tableName,
|
|
289
|
-
const
|
|
367
|
+
function getIdentifier(tableName, columnsOrBaseName, suffix, options) {
|
|
368
|
+
const middle = isString(columnsOrBaseName) ? columnsOrBaseName : getColumnNames(columnsOrBaseName).join('_');
|
|
369
|
+
const identifier = `${getTablePrefix(tableName, options?.naming)}_${middle}_${suffix}`;
|
|
290
370
|
if (identifier.length > 63) {
|
|
291
371
|
if (options?.naming != 'abbreviated-table') {
|
|
292
|
-
return getIdentifier(tableName,
|
|
372
|
+
return getIdentifier(tableName, columnsOrBaseName, suffix, { naming: 'abbreviated-table' });
|
|
293
373
|
}
|
|
294
374
|
throw new Error(`Identifier "${identifier}" for table "${tableName}" is too long. Maximum length is 63 characters.`);
|
|
295
375
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SQL, type SQLWrapper } from 'drizzle-orm';
|
|
2
2
|
import type { BaseEntity } from '../entity.js';
|
|
3
|
-
import type { Query, TsVectorParser, TsVectorWeight } from '../query.js';
|
|
3
|
+
import type { Query, TsVectorParser, TsVectorWeight } from '../query/index.js';
|
|
4
4
|
import type { TargetColumn } from '../repository.types.js';
|
|
5
5
|
import type { ColumnDefinition, ExtraConfigColumnsFromType, PgTableFromType } from './types.js';
|
|
6
6
|
/**
|
|
@@ -16,7 +16,7 @@ export declare function resolveTargetColumn<T extends BaseEntity>(target: Target
|
|
|
16
16
|
* @param table The Drizzle table object.
|
|
17
17
|
* @param columnDefinitionsMap A map from property names to column definitions.
|
|
18
18
|
*/
|
|
19
|
-
export declare function resolveTargetColumns<T extends BaseEntity>(targets: (TargetColumn<T> | ColumnDefinition)[], table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQLWrapper[];
|
|
19
|
+
export declare function resolveTargetColumns<T extends BaseEntity>(targets: readonly (TargetColumn<T> | ColumnDefinition)[], table: PgTableFromType | ExtraConfigColumnsFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQLWrapper[];
|
|
20
20
|
/**
|
|
21
21
|
* Converts a query object into a Drizzle SQL condition.
|
|
22
22
|
* Recursively handles nested logical operators and maps property names to database columns.
|