uql-orm 0.69.0 → 0.71.0
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/README.md +2 -0
- package/dist/browser/querier/httpQuerier.d.ts +7 -7
- package/dist/browser/type/clientQuerier.d.ts +5 -5
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/cockroachdb/cockroachDialect.d.ts +6 -0
- package/dist/cockroachdb/cockroachDialect.js +10 -2
- package/dist/d1/d1SqliteDialect.d.ts +3 -0
- package/dist/d1/d1SqliteDialect.js +3 -1
- package/dist/dialect/abstractDialect.d.ts +8 -2
- package/dist/dialect/abstractDialect.js +17 -1
- package/dist/dialect/abstractSqlDialect.d.ts +21 -5
- package/dist/dialect/abstractSqlDialect.js +91 -46
- package/dist/dialect/aliases.d.ts +10 -7
- package/dist/dialect/aliases.js +10 -7
- package/dist/dialect/hydrateColumn.d.ts +3 -2
- package/dist/dialect/hydrateColumn.js +10 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +5 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +17 -5
- package/dist/dialect/pgLikeSqlDialect.js +34 -15
- package/dist/dialect/queryJoins.d.ts +19 -1
- package/dist/dialect/queryJoins.js +54 -11
- package/dist/dialect/vectorCast.d.ts +2 -0
- package/dist/dialect/vectorCast.js +7 -0
- package/dist/dialect/vectorSqlDialect.d.ts +7 -3
- package/dist/dialect/vectorSqlDialect.js +15 -10
- package/dist/entity/decorator/members.d.ts +25 -11
- package/dist/entity/metadata/definition.d.ts +8 -3
- package/dist/libsql/libsqlDialect.d.ts +1 -1
- package/dist/libsql/libsqlDialect.js +3 -3
- package/dist/maria/mariaDialect.d.ts +3 -9
- package/dist/maria/mariaDialect.js +4 -13
- package/dist/maria/mariadbQuerier.js +9 -3
- package/dist/migrate/codegen/entityCodeGenerator.js +4 -2
- package/dist/migrate/ddl/index.d.ts +1 -0
- package/dist/migrate/ddl/index.js +4 -2
- package/dist/migrate/ddl/indexDdl.d.ts +2 -0
- package/dist/migrate/ddl/indexDdl.js +10 -2
- package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -4
- package/dist/migrate/ddl/pgIndexDdl.js +11 -11
- package/dist/migrate/ddl/sqliteIndexDdl.d.ts +11 -0
- package/dist/migrate/ddl/sqliteIndexDdl.js +38 -0
- package/dist/migrate/generator/mongoCommand.d.ts +28 -0
- package/dist/migrate/generator/mongoCommand.js +8 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +2 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +50 -6
- package/dist/migrate/introspection/mongoIntrospector.js +33 -7
- package/dist/migrate/introspection/postgresIntrospector.js +19 -0
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +29 -3
- package/dist/migrate/migrator.d.ts +2 -1
- package/dist/migrate/migrator.js +5 -3
- package/dist/mongo/mongoDialect.d.ts +33 -27
- package/dist/mongo/mongoDialect.js +178 -114
- package/dist/mongo/mongodbQuerier.d.ts +0 -2
- package/dist/mongo/mongodbQuerier.js +14 -13
- package/dist/mssql/mssqlDialect.js +4 -2
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +16 -11
- package/dist/querier/abstractQuerier.js +28 -8
- package/dist/querier/abstractQuerierPool.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.d.ts +1 -1
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/schema/canonicalType.js +10 -4
- package/dist/schema/indexDifferences.d.ts +5 -2
- package/dist/schema/indexDifferences.js +5 -0
- package/dist/schema/schemaASTBuilder.js +3 -2
- package/dist/sqlite/localSqliteQuerierPool.d.ts +10 -11
- package/dist/sqlite/localSqliteQuerierPool.js +8 -11
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +4 -2
- package/dist/sqlite/nodeSqliteQuerierPool.js +7 -4
- package/dist/sqlite/sqliteDialect.d.ts +9 -1
- package/dist/sqlite/sqliteDialect.js +43 -3
- package/dist/sqlite/sqliteQuerierPool.d.ts +6 -5
- package/dist/sqlite/sqliteQuerierPool.js +10 -7
- package/dist/turso/tursoDialect.d.ts +1 -1
- package/dist/turso/tursoDialect.js +6 -2
- package/dist/turso/tursoLocalDialect.d.ts +1 -1
- package/dist/turso/tursoLocalDialect.js +1 -1
- package/dist/turso/tursoLocalQuerierPool.d.ts +4 -5
- package/dist/turso/tursoLocalQuerierPool.js +3 -10
- package/dist/type/dialect.d.ts +11 -0
- package/dist/type/entity.d.ts +45 -5
- package/dist/type/migration.d.ts +14 -9
- package/dist/type/query.d.ts +6 -0
- package/dist/type/queryAggregate.d.ts +73 -42
- package/dist/type/queryAggregate.js +4 -21
- package/dist/type/universalQuerier.d.ts +9 -9
- package/dist/type/vector.d.ts +17 -0
- package/dist/type/vector.js +6 -0
- package/dist/util/ddlExpression.util.d.ts +2 -0
- package/dist/util/ddlExpression.util.js +5 -0
- package/dist/util/dialect.util.d.ts +17 -3
- package/dist/util/dialect.util.js +40 -6
- package/package.json +5 -3
- package/skills/uql-orm/SKILL.md +142 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getMeta, relationOf } from '../entity/index.js';
|
|
2
|
-
import { getKeys, getRelationRequestSummary, isToManyRelation, parseRelationAtKey } from '../util/index.js';
|
|
2
|
+
import { getKeys, getRelationRequestSummary, isRecord, isToManyRelation, parseRelationAtKey, parseRelationSize, } from '../util/index.js';
|
|
3
3
|
export const NO_JOINS = new Map();
|
|
4
4
|
/**
|
|
5
5
|
* What the statement joins, from `$populate` and from a `$sort` by a to-one relation's field, so the
|
|
@@ -11,9 +11,48 @@ export function resolveQueryJoins(meta, q, claimAlias = (path) => path) {
|
|
|
11
11
|
}
|
|
12
12
|
const joins = new Map();
|
|
13
13
|
addPopulateJoins(joins, claimAlias, meta, q.$populate);
|
|
14
|
-
|
|
14
|
+
addPathJoins(joins, claimAlias, meta, q.$sort, false);
|
|
15
15
|
return joins;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* What an aggregate joins, and the `$where` left to it. Each to-one relation a `$group` path passes through
|
|
19
|
+
* is an `INNER` join, since a group of a path names a related row; a filter on one of them, keyed at the top
|
|
20
|
+
* of the `$where` where an `AND` joins it, moves into that join rather than reading its table again. Under a
|
|
21
|
+
* `$not` it could not: the join would drop the rows the negation keeps.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveGroupJoins(meta, q, claimAlias = (path) => path) {
|
|
24
|
+
const joins = new Map();
|
|
25
|
+
for (const ref of Object.values(q.$group ?? {})) {
|
|
26
|
+
if (isRecord(ref)) {
|
|
27
|
+
addPathJoins(joins, claimAlias, meta, ref, true);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!q.$where) {
|
|
31
|
+
return { joins, where: q.$where };
|
|
32
|
+
}
|
|
33
|
+
const where = { ...q.$where };
|
|
34
|
+
for (const key of getKeys(q.$where)) {
|
|
35
|
+
const join = joins.get(key);
|
|
36
|
+
const filter = q.$where[key];
|
|
37
|
+
if (join && isRecord(filter) && parseRelationSize(filter) === undefined) {
|
|
38
|
+
joins.set(key, { ...join, query: { $where: filter } });
|
|
39
|
+
delete where[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { joins, where };
|
|
43
|
+
}
|
|
44
|
+
/** The field a grouped `path` reads, and the join it reads it through: none for the entity's own. */
|
|
45
|
+
export function groupPathField(joins, path) {
|
|
46
|
+
const key = path[path.length - 1];
|
|
47
|
+
if (path.length === 1) {
|
|
48
|
+
return { key, join: undefined };
|
|
49
|
+
}
|
|
50
|
+
const join = joins.get(path.slice(0, -1).join('.'));
|
|
51
|
+
if (!join) {
|
|
52
|
+
throw new TypeError(`cannot $group by '${path.join('.')}': only a to-one relation's field groups, since a to-many multiplies the rows it joins`);
|
|
53
|
+
}
|
|
54
|
+
return { key, join };
|
|
55
|
+
}
|
|
17
56
|
/**
|
|
18
57
|
* Whether a join drops parents that have no match, which is the one thing a join does to *how many*
|
|
19
58
|
* rows a read returns rather than how wide they are. A count that skips the joins has to be told, or
|
|
@@ -71,21 +110,25 @@ function addPopulateJoins(joins, claimAlias, meta, populate, parent) {
|
|
|
71
110
|
addPopulateJoins(joins, claimAlias, join.meta, query.$populate, join);
|
|
72
111
|
}
|
|
73
112
|
}
|
|
74
|
-
|
|
75
|
-
|
|
113
|
+
/**
|
|
114
|
+
* The to-one relations a nested map of fields passes through, a `$sort` or a `$group` path, as joins adding
|
|
115
|
+
* no columns: `required` where the path names a related row, as a group's does, and a sort's does not.
|
|
116
|
+
*/
|
|
117
|
+
function addPathJoins(joins, claimAlias, meta, map, required, parent) {
|
|
118
|
+
if (!map) {
|
|
76
119
|
return;
|
|
77
120
|
}
|
|
78
|
-
for (const key of getKeys(
|
|
121
|
+
for (const key of getKeys(map)) {
|
|
79
122
|
const relation = meta.relations[key];
|
|
80
|
-
const value =
|
|
123
|
+
const value = map[key];
|
|
81
124
|
// A to-many, or a value that is not a map of the relation's own fields, cannot be joined and is
|
|
82
|
-
// reported where the
|
|
125
|
+
// reported where the statement names it - the one place that knows how to.
|
|
83
126
|
if (!relation || isToManyRelation(relation) || !isSortMap(value)) {
|
|
84
127
|
continue;
|
|
85
128
|
}
|
|
86
|
-
const join = addJoin(joins, claimAlias, parent, key, relation, {},
|
|
87
|
-
// `E` stated: inferred from
|
|
88
|
-
|
|
129
|
+
const join = addJoin(joins, claimAlias, parent, key, relation, {}, required, false);
|
|
130
|
+
// `E` stated: inferred from the nested map, it lands on the nested relation's target.
|
|
131
|
+
addPathJoins(joins, claimAlias, join.meta, value, required, join);
|
|
89
132
|
}
|
|
90
133
|
}
|
|
91
134
|
/** The join a sort may address at `path` with the relation's own sort map, or why it may not; `unjoinable` is the dialect's remedy. */
|
|
@@ -102,7 +145,7 @@ export function resolveSortableJoin(relation, path, value, joins, unjoinable) {
|
|
|
102
145
|
}
|
|
103
146
|
return { join, sort: value };
|
|
104
147
|
}
|
|
105
|
-
/** A nested
|
|
148
|
+
/** A nested map of fields, as opposed to a `$sort` direction or vector search, or a `$group` field's `true`. */
|
|
106
149
|
function isSortMap(value) {
|
|
107
150
|
return typeof value === 'object' && value !== null && !Array.isArray(value) && !('$vector' in value);
|
|
108
151
|
}
|
|
@@ -20,5 +20,7 @@ export declare function toSparsevecLiteral(values: readonly unknown[]): string;
|
|
|
20
20
|
* as it was written, `{1:1,3:2}/3` or `[1,2,3]`; `undefined` where the text matches neither.
|
|
21
21
|
*/
|
|
22
22
|
export declare function parseVectorLiteral(raw: string, cast: VectorCast): number[] | undefined;
|
|
23
|
+
/** The packed little-endian float32s a blob vector column holds, and {@link decodeFloat32s} reads back. */
|
|
24
|
+
export declare function encodeFloat32s(values: readonly unknown[]): Uint8Array;
|
|
23
25
|
/** Packed little-endian float32s, each read as {@link shortestFloat32}. */
|
|
24
26
|
export declare function decodeFloat32s(bytes: Uint8Array): number[];
|
|
@@ -62,6 +62,13 @@ function parseDense(text) {
|
|
|
62
62
|
return undefined;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
/** The packed little-endian float32s a blob vector column holds, and {@link decodeFloat32s} reads back. */
|
|
66
|
+
export function encodeFloat32s(values) {
|
|
67
|
+
const bytes = new Uint8Array(values.length * 4);
|
|
68
|
+
const view = new DataView(bytes.buffer);
|
|
69
|
+
values.forEach((value, at) => view.setFloat32(at * 4, Number(value), true));
|
|
70
|
+
return bytes;
|
|
71
|
+
}
|
|
65
72
|
/** Packed little-endian float32s, each read as {@link shortestFloat32}. */
|
|
66
73
|
export function decodeFloat32s(bytes) {
|
|
67
74
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { EntityIndexMeta, EntityMeta, FieldOptions, Query, QueryContext, QueryVectorSearch, SqlDialectFeatures, VectorDistance, VectorMetric } from '../type/index.js';
|
|
1
|
+
import type { EntityIndexMeta, EntityMeta, FieldOptions, Query, QueryContext, QueryVectorSearch, QueryWhere, SqlDialectFeatures, VectorDistance, VectorMetric } from '../type/index.js';
|
|
2
2
|
import { AbstractDialect } from './abstractDialect.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type VectorCast } from './vectorCast.js';
|
|
4
4
|
/**
|
|
5
5
|
* Vector search for the SQL dialects: the distance a `$sort` ranks by and projects, and the ANN tuning.
|
|
6
6
|
* Each dialect lists its metrics in {@link vectorMetrics}, an operator or a function; empty means no search.
|
|
@@ -20,12 +20,16 @@ export declare abstract class VectorSqlDialect extends AbstractDialect {
|
|
|
20
20
|
* spelled into a `SET` rather than bound, and `/http` input is untyped.
|
|
21
21
|
*/
|
|
22
22
|
protected tunedVectorIndex<E>(meta: EntityMeta<E>, q: Query<E>): EntityIndexMeta | undefined;
|
|
23
|
+
/** The `$where` a read runs: the query's own, which an engine reading its vector index as a table narrows. */
|
|
24
|
+
protected rankedWhere<E>(_meta: EntityMeta<E>, q: Query<E>, _prefix: string | undefined): QueryWhere<E> | undefined;
|
|
23
25
|
/**
|
|
24
26
|
* Every distance metric this dialect has, and how a search and an index spell each. Empty means no
|
|
25
27
|
* vector search at all, which is what MySQL and D1 are. The key set is the single answer to "is this
|
|
26
28
|
* metric supported here", for a query and an index alike.
|
|
27
29
|
*/
|
|
28
30
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
31
|
+
/** Whether this engine has a vector index: the one a metric's `index` names. */
|
|
32
|
+
hasVectorIndex(): boolean;
|
|
29
33
|
/** Quotes an identifier; supplied by the SQL dialect built on top of this layer. */
|
|
30
34
|
abstract escapeId(val: string | undefined, forbidQualified?: boolean, addDot?: boolean): string;
|
|
31
35
|
/**
|
|
@@ -39,7 +43,7 @@ export declare abstract class VectorSqlDialect extends AbstractDialect {
|
|
|
39
43
|
};
|
|
40
44
|
/**
|
|
41
45
|
* Binds a vector, both as a persisted value and as the query vector of a distance expression, so a
|
|
42
|
-
* dialect needing a conversion around it (`$1::vector`, `
|
|
46
|
+
* dialect needing a conversion around it (`$1::vector`, `CAST(? AS VECTOR(n))`) declares it once.
|
|
43
47
|
*/
|
|
44
48
|
protected appendVectorValue(ctx: QueryContext, value: readonly unknown[], _field?: FieldOptions): void;
|
|
45
49
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { unsupportedVectorMetric } from '../type/vector.js';
|
|
2
|
-
import { findVectorIndex, findVectorSort } from '../util/dialect.util.js';
|
|
1
|
+
import { DEFAULT_VECTOR_DISTANCE, unsupportedVectorMetric } from '../type/vector.js';
|
|
2
|
+
import { findVectorIndex, findVectorSort, vectorCandidates } from '../util/dialect.util.js';
|
|
3
3
|
import { entityName } from '../util/object.util.js';
|
|
4
4
|
import { AbstractDialect } from './abstractDialect.js';
|
|
5
|
+
import { encodeFloat32s } from './vectorCast.js';
|
|
5
6
|
/**
|
|
6
7
|
* Vector search for the SQL dialects: the distance a `$sort` ranks by and projects, and the ANN tuning.
|
|
7
8
|
* Each dialect lists its metrics in {@link vectorMetrics}, an operator or a function; empty means no search.
|
|
@@ -24,22 +25,26 @@ export class VectorSqlDialect extends AbstractDialect {
|
|
|
24
25
|
* spelled into a `SET` rather than bound, and `/http` input is untyped.
|
|
25
26
|
*/
|
|
26
27
|
tunedVectorIndex(meta, q) {
|
|
27
|
-
|
|
28
|
-
if (candidates === undefined) {
|
|
28
|
+
if (vectorCandidates(q) === undefined) {
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
|
-
if (!Number.isInteger(candidates) || candidates < 1) {
|
|
32
|
-
throw new TypeError(`$candidates must be a positive integer, got ${JSON.stringify(candidates)}`);
|
|
33
|
-
}
|
|
34
31
|
const key = this.vectorSortKey(q);
|
|
35
32
|
return key ? findVectorIndex(meta, key) : undefined;
|
|
36
33
|
}
|
|
34
|
+
/** The `$where` a read runs: the query's own, which an engine reading its vector index as a table narrows. */
|
|
35
|
+
rankedWhere(_meta, q, _prefix) {
|
|
36
|
+
return q.$where;
|
|
37
|
+
}
|
|
37
38
|
/**
|
|
38
39
|
* Every distance metric this dialect has, and how a search and an index spell each. Empty means no
|
|
39
40
|
* vector search at all, which is what MySQL and D1 are. The key set is the single answer to "is this
|
|
40
41
|
* metric supported here", for a query and an index alike.
|
|
41
42
|
*/
|
|
42
43
|
vectorMetrics = new Map();
|
|
44
|
+
/** Whether this engine has a vector index: the one a metric's `index` names. */
|
|
45
|
+
hasVectorIndex() {
|
|
46
|
+
return [...this.vectorMetrics.values()].some((metric) => metric.index);
|
|
47
|
+
}
|
|
43
48
|
/**
|
|
44
49
|
* What a distance expression reads, for a `$sort` and a `$near` alike. The metric falls back to the
|
|
45
50
|
* field's, then its index's, which serves no other, then cosine.
|
|
@@ -47,15 +52,15 @@ export class VectorSqlDialect extends AbstractDialect {
|
|
|
47
52
|
resolveVectorDistance(meta, key, search) {
|
|
48
53
|
const field = meta.fields[key];
|
|
49
54
|
const colName = this.resolveColumnName(key, field);
|
|
50
|
-
const distance = search.$distance ?? field?.distance ?? findVectorIndex(meta, key)?.distance ??
|
|
55
|
+
const distance = search.$distance ?? field?.distance ?? findVectorIndex(meta, key)?.distance ?? DEFAULT_VECTOR_DISTANCE;
|
|
51
56
|
return { colName, distance, field };
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
59
|
* Binds a vector, both as a persisted value and as the query vector of a distance expression, so a
|
|
55
|
-
* dialect needing a conversion around it (`$1::vector`, `
|
|
60
|
+
* dialect needing a conversion around it (`$1::vector`, `CAST(? AS VECTOR(n))`) declares it once.
|
|
56
61
|
*/
|
|
57
62
|
appendVectorValue(ctx, value, _field) {
|
|
58
|
-
ctx.addValue(`[${value.join(',')}]`);
|
|
63
|
+
ctx.addValue(this.features.vectorBytes ? encodeFloat32s(value) : `[${value.join(',')}]`);
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* The vector type this dialect actually has for a declared one, so the cast follows the column
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import type { AggregateValue, ComputedRefs, EntityAggregate, EntityGetter, Except, FieldOptions, FieldType, HasCompositeKey, IdValue, NamedIdKey, RejectKeys, RelationManyToManyOptions, RelationManyToOneOptions, RelationOneToManyOptions, RelationOneToOneOptions, RelationAggregate, TsTypeOf } from '../../type/index.js';
|
|
1
|
+
import type { AggregateValue, ComputedRefs, EntityAggregate, EntityGetter, Except, FieldOptions, FieldType, HasCompositeKey, IdValue, NamedIdKey, RejectKeys, RelationManyToManyOptions, RelationManyToOneOptions, RelationOneToManyOptions, RelationOneToOneOptions, RelationAggregate, TsTypeOf, Writable } from '../../type/index.js';
|
|
2
2
|
import type { RejectIncompatible } from '../../util/index.js';
|
|
3
3
|
/** A member decorator that also constrains the property it may be applied to, on a class `O`. */
|
|
4
4
|
type MemberDecorator<V, O = unknown> = (value: undefined, context: ClassFieldDecoratorContext<O, V>) => void;
|
|
5
|
+
/**
|
|
6
|
+
* A {@link MemberDecorator} whose property has to admit `M` as well as hold `V`. A decorator context is
|
|
7
|
+
* covariant in its value, so on its own it takes a property narrower than the field reads: a `string`
|
|
8
|
+
* where a nullable column reads `string | null`, a `number` where `max()` reads `number | null`.
|
|
9
|
+
*
|
|
10
|
+
* `M` is what each arm insists on rather than the whole of `V`, because a column type that names a
|
|
11
|
+
* family - `jsonb`, whose document the property shapes, or `numeric`, either number kind - is meant to
|
|
12
|
+
* be narrowed. A column insists on its `null`; an aggregate, whose value is exact, insists on all of it.
|
|
13
|
+
*/
|
|
14
|
+
type AdmittingDecorator<V, M, O> = <P extends V | undefined>(value: undefined, context: ClassFieldDecoratorContext<O, P> & ([Writable<M>] extends [Writable<P>] ? unknown : {
|
|
15
|
+
readonly __propertyMustAdmit: M;
|
|
16
|
+
})) => void;
|
|
5
17
|
/**
|
|
6
18
|
* The property type a set of field options describes: the declared `type`, narrowed by `enum` to the
|
|
7
19
|
* values that type admits (so `enum: [2]` stays off a `String`), or else the referenced key's own type,
|
|
@@ -16,6 +28,16 @@ type DeclaredValue<O> = O extends {
|
|
|
16
28
|
} ? HasCompositeKey<E> extends true ? {
|
|
17
29
|
readonly __compositeKeyNeedsAColumnPerKey: true;
|
|
18
30
|
} : IdValue<E> : never;
|
|
31
|
+
/**
|
|
32
|
+
* The `null` a column reads back: every one holds it unless `nullable: false` says otherwise, so the
|
|
33
|
+
* property admits it too. A key holds none, whether `@Id` or `@Field({ isId: true })` declares it, since
|
|
34
|
+
* it is NOT NULL on every engine.
|
|
35
|
+
*/
|
|
36
|
+
type NullOf<O> = O extends {
|
|
37
|
+
readonly nullable: false;
|
|
38
|
+
} | {
|
|
39
|
+
readonly isId: true;
|
|
40
|
+
} ? never : null;
|
|
19
41
|
/** The enum's members, or a named complaint where they widened for lack of `as const`, which would check nothing. */
|
|
20
42
|
type EnumValue<Members, Declared> = Declared extends Members ? {
|
|
21
43
|
readonly __enumNeedsAsConst: true;
|
|
@@ -29,14 +51,14 @@ export declare function Field<This, O extends FieldOptions<DeclaredValue<O>, Thi
|
|
|
29
51
|
type: FieldType;
|
|
30
52
|
} | {
|
|
31
53
|
references: EntityGetter;
|
|
32
|
-
}) & RejectKeys<Exclude<keyof O, keyof FieldOptions>> & RejectIncompatible<O>>(opts: O):
|
|
54
|
+
}) & RejectKeys<Exclude<keyof O, keyof FieldOptions>> & RejectIncompatible<O>>(opts: O): AdmittingDecorator<DeclaredValue<O> | NullOf<O>, NullOf<O>, This>;
|
|
33
55
|
/**
|
|
34
56
|
* Declares a field a relation aggregate computes, `@Field({ computed: (user) => user.resources.count() })`.
|
|
35
57
|
* The aggregate says what the field holds, so it takes no `type`, and only `count` and `sum` - the two a
|
|
36
58
|
* row change turns into a delta - may be `stored`.
|
|
37
59
|
* @example `@Field({ computed: (user) => user.resources.count() }) readonly resourceCount?: number;`
|
|
38
60
|
*/
|
|
39
|
-
export declare function Field<This, O extends AggregateOptions<This> & RejectKeys<Exclude<keyof O, keyof FieldOptions>>>(opts: O):
|
|
61
|
+
export declare function Field<This, O extends AggregateOptions<This> & RejectKeys<Exclude<keyof O, keyof FieldOptions>>>(opts: O): AdmittingDecorator<AggregateValue<O>, AggregateValue<O>, This>;
|
|
40
62
|
/**
|
|
41
63
|
* A field the aggregate itself types: `stored: true` only where a trigger could keep it, and every other
|
|
42
64
|
* option as a column takes it.
|
|
@@ -50,14 +72,6 @@ type AggregateOptions<E> = (Except<FieldOptions<never, E>, 'computed' | 'stored'
|
|
|
50
72
|
}['agg'];
|
|
51
73
|
readonly stored: true;
|
|
52
74
|
});
|
|
53
|
-
/**
|
|
54
|
-
* {@link MemberDecorator} for a field an aggregate types, which the property has to hold *exactly*: a
|
|
55
|
-
* decorator context takes a property narrower than its value, so nothing else would stop a `number`
|
|
56
|
-
* from holding what `max()` reads, which is `number | null` on a parent with no rows.
|
|
57
|
-
*/
|
|
58
|
-
type AggregateDecorator<V, O> = <P extends V | undefined>(value: undefined, context: ClassFieldDecoratorContext<O, P> & ([V] extends [P] ? unknown : {
|
|
59
|
-
readonly __propertyMustAdmit: V;
|
|
60
|
-
})) => void;
|
|
61
75
|
/**
|
|
62
76
|
* A key the type level cannot name, reported on each `@Id` that leaves it unnamed. Where no `idKey`
|
|
63
77
|
* brand and no conventional name applies, `IdKey` falls back to every field, and `IdValue`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldMeta, FieldOptions, FilterName, FilterOptions, HookEvent, IdKey, RelationKey, RelationMeta, RelationOptions, RelationRegistration, Type, WrittenId } from '../../type/index.js';
|
|
2
2
|
export declare function defineField<E>(entity: Type<E>, key: string, opts?: FieldOptions): EntityMeta<E>;
|
|
3
3
|
export declare function defineId<E>(entity: Type<E>, key: string, opts: FieldOptions): EntityMeta<E>;
|
|
4
4
|
/** `T` is the relation's target, independent of the owner `E`. */
|
|
@@ -39,10 +39,14 @@ export declare function soleIdOf<E>(meta: EntityMeta<E>, what: string): IdKey<E>
|
|
|
39
39
|
export declare function fieldOf<E>(meta: EntityMeta<E>, key: string): FieldMeta;
|
|
40
40
|
/** The relation `key` names, for a caller that took `key` from the metadata itself. */
|
|
41
41
|
export declare function relationOf<E>(meta: EntityMeta<E>, key: RelationKey<E>): RelationMeta;
|
|
42
|
+
/** A row of `E` as far as reading its key goes: a record or a write, whatever its values. */
|
|
43
|
+
type KeyedRow<E> = {
|
|
44
|
+
readonly [K in keyof E]?: unknown;
|
|
45
|
+
};
|
|
42
46
|
/** Whether the row names every column of its primary key, `0` and `''` included. */
|
|
43
|
-
export declare function namesKey<E>(meta: EntityMeta<E>, row:
|
|
47
|
+
export declare function namesKey<E>(meta: EntityMeta<E>, row: KeyedRow<E>): boolean;
|
|
44
48
|
/** A row's primary key: its value, or a map of every column on a composite, checked at run time. */
|
|
45
|
-
export declare function idOf<E>(meta: EntityMeta<E>, row:
|
|
49
|
+
export declare function idOf<E>(meta: EntityMeta<E>, row: KeyedRow<E>): WrittenId<E>;
|
|
46
50
|
/**
|
|
47
51
|
* Forgets an entity, and reports whether there was one - for a registry that grows at runtime, where a
|
|
48
52
|
* deleted content type would otherwise keep its metadata for the life of the process. Nothing rewrites
|
|
@@ -58,3 +62,4 @@ export declare function getMeta<E>(entity: Type<E>): EntityMeta<E>;
|
|
|
58
62
|
* schema build constrains and a junction joins by, settling the relations holding them first.
|
|
59
63
|
*/
|
|
60
64
|
export declare function foreignKeysOf<E>(meta: EntityMeta<E>): RelationMeta[];
|
|
65
|
+
export {};
|
|
@@ -7,6 +7,6 @@ import type { VectorDistance, VectorMetric } from '../type/index.js';
|
|
|
7
7
|
* functions, which `TursoDialect` inherits.
|
|
8
8
|
*/
|
|
9
9
|
export declare class LibsqlDialect extends SqliteDialect {
|
|
10
|
-
/** libSQL's built-in vector functions; no `inner` (only the Rust engine has it) and no `l1`. */
|
|
10
|
+
/** libSQL's built-in vector functions, and the metric its DiskANN index names; no `inner` (only the Rust engine has it) and no `l1`. */
|
|
11
11
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
12
12
|
}
|
|
@@ -6,9 +6,9 @@ import { SqliteDialect } from '../sqlite/sqliteDialect.js';
|
|
|
6
6
|
* functions, which `TursoDialect` inherits.
|
|
7
7
|
*/
|
|
8
8
|
export class LibsqlDialect extends SqliteDialect {
|
|
9
|
-
/** libSQL's built-in vector functions; no `inner` (only the Rust engine has it) and no `l1`. */
|
|
9
|
+
/** libSQL's built-in vector functions, and the metric its DiskANN index names; no `inner` (only the Rust engine has it) and no `l1`. */
|
|
10
10
|
vectorMetrics = new Map([
|
|
11
|
-
['cosine', { fn: 'vector_distance_cos' }],
|
|
12
|
-
['l2', { fn: 'vector_distance_l2' }],
|
|
11
|
+
['cosine', { fn: 'vector_distance_cos', index: 'cosine' }],
|
|
12
|
+
['l2', { fn: 'vector_distance_l2', index: 'l2' }],
|
|
13
13
|
]);
|
|
14
14
|
}
|
|
@@ -6,8 +6,9 @@ export declare class MariaDialect extends MysqlLikeSqlDialect {
|
|
|
6
6
|
readonly dialectName = "mariadb";
|
|
7
7
|
readonly insertIdSource = "returning";
|
|
8
8
|
/**
|
|
9
|
-
* Unlike MySQL: `VECTOR(n)` takes its dimension
|
|
10
|
-
* `CREATE INDEX` takes `IF NOT EXISTS`, and a lock cannot be narrowed
|
|
9
|
+
* Unlike MySQL: `VECTOR(n)` takes its dimension and binds as packed float32 bytes, every column of a
|
|
10
|
+
* vector index has to be NOT NULL, `CREATE INDEX` takes `IF NOT EXISTS`, and a lock cannot be narrowed
|
|
11
|
+
* to one table of a join.
|
|
11
12
|
*/
|
|
12
13
|
readonly features: SqlDialectFeatures;
|
|
13
14
|
/**
|
|
@@ -34,13 +35,6 @@ export declare class MariaDialect extends MysqlLikeSqlDialect {
|
|
|
34
35
|
protected jsonDiffers(elem: string, operand: string): string;
|
|
35
36
|
/** `VEC_DISTANCE_COSINE`/`VEC_DISTANCE_EUCLIDEAN`, 11.7+, which the index's `DISTANCE=` names alike. */
|
|
36
37
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
37
|
-
/**
|
|
38
|
-
* A `VECTOR` column holds a packed little-endian float32 blob, and MariaDB refuses text where one
|
|
39
|
-
* belongs: inserting `'[1,2,3]'` fails with `Incorrect vector value`, and passing it to
|
|
40
|
-
* `VEC_DISTANCE_COSINE` with `Illegal parameter data type varchar`. `VEC_FromText` is the
|
|
41
|
-
* conversion, needed on both paths.
|
|
42
|
-
*/
|
|
43
|
-
protected appendVectorValue(ctx: QueryContext, value: readonly unknown[]): void;
|
|
44
38
|
/**
|
|
45
39
|
* `mhnsw_ef_search` too, where a vector search is tuned. A setting scoped to one statement needs
|
|
46
40
|
* neither a transaction nor a restore, and cannot leak to the next query on this pooled connection,
|
|
@@ -8,12 +8,14 @@ export class MariaDialect extends MysqlLikeSqlDialect {
|
|
|
8
8
|
// MariaDB 10.5+ has `INSERT ... RETURNING`, so ids come back exact per row - the upsert's too.
|
|
9
9
|
insertIdSource = 'returning';
|
|
10
10
|
/**
|
|
11
|
-
* Unlike MySQL: `VECTOR(n)` takes its dimension
|
|
12
|
-
* `CREATE INDEX` takes `IF NOT EXISTS`, and a lock cannot be narrowed
|
|
11
|
+
* Unlike MySQL: `VECTOR(n)` takes its dimension and binds as packed float32 bytes, every column of a
|
|
12
|
+
* vector index has to be NOT NULL, `CREATE INDEX` takes `IF NOT EXISTS`, and a lock cannot be narrowed
|
|
13
|
+
* to one table of a join.
|
|
13
14
|
*/
|
|
14
15
|
features = {
|
|
15
16
|
...MYSQL_FEATURES,
|
|
16
17
|
vectorSupportsLength: true,
|
|
18
|
+
vectorBytes: true,
|
|
17
19
|
vectorIndexRequiresNotNull: true,
|
|
18
20
|
indexIfNotExists: true,
|
|
19
21
|
rowLockOf: false,
|
|
@@ -69,17 +71,6 @@ export class MariaDialect extends MysqlLikeSqlDialect {
|
|
|
69
71
|
['cosine', { fn: 'VEC_DISTANCE_COSINE', index: 'cosine' }],
|
|
70
72
|
['l2', { fn: 'VEC_DISTANCE_EUCLIDEAN', index: 'euclidean' }],
|
|
71
73
|
]);
|
|
72
|
-
/**
|
|
73
|
-
* A `VECTOR` column holds a packed little-endian float32 blob, and MariaDB refuses text where one
|
|
74
|
-
* belongs: inserting `'[1,2,3]'` fails with `Incorrect vector value`, and passing it to
|
|
75
|
-
* `VEC_DISTANCE_COSINE` with `Illegal parameter data type varchar`. `VEC_FromText` is the
|
|
76
|
-
* conversion, needed on both paths.
|
|
77
|
-
*/
|
|
78
|
-
appendVectorValue(ctx, value) {
|
|
79
|
-
ctx.append('VEC_FromText(');
|
|
80
|
-
super.appendVectorValue(ctx, value);
|
|
81
|
-
ctx.append(')');
|
|
82
|
-
}
|
|
83
74
|
/**
|
|
84
75
|
* `mhnsw_ef_search` too, where a vector search is tuned. A setting scoped to one statement needs
|
|
85
76
|
* neither a transaction nor a restore, and cannot leak to the next query on this pooled connection,
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
|
|
2
2
|
import { decodeBigInts } from '../util/wideNumber.js';
|
|
3
|
+
/** This driver binds only a `Buffer` as bytes: any other `Uint8Array` goes as the JSON of its indices. */
|
|
4
|
+
function toBindValues(values) {
|
|
5
|
+
return values?.map((value) => value instanceof Uint8Array && !Buffer.isBuffer(value)
|
|
6
|
+
? Buffer.from(value.buffer, value.byteOffset, value.byteLength)
|
|
7
|
+
: value);
|
|
8
|
+
}
|
|
3
9
|
export class MariadbQuerier extends AbstractPoolQuerier {
|
|
4
10
|
async internalAll(query, values) {
|
|
5
|
-
const rows = await this.getConn().query(query, values);
|
|
11
|
+
const rows = await this.getConn().query(query, toBindValues(values));
|
|
6
12
|
return Array.from(rows, decodeBigInts);
|
|
7
13
|
}
|
|
8
14
|
async internalRun(query, values) {
|
|
9
|
-
const res = await this.getConn().query(query, values);
|
|
15
|
+
const res = await this.getConn().query(query, toBindValues(values));
|
|
10
16
|
// An OK packet reports `affectedRows`; a `RETURNING` statement answers rows instead, and counts by them.
|
|
11
17
|
const changes = res.affectedRows ?? res.length;
|
|
12
18
|
const rows = res.length ? Array.from(res, decodeBigInts) : [];
|
|
13
19
|
return this.buildUpdateResult({ rows, changes, upsertStatus: res.affectedRows });
|
|
14
20
|
}
|
|
15
21
|
async *internalStream(query, values) {
|
|
16
|
-
const stream = this.getConn().queryStream(query, values);
|
|
22
|
+
const stream = this.getConn().queryStream(query, toBindValues(values));
|
|
17
23
|
try {
|
|
18
24
|
for await (const row of stream) {
|
|
19
25
|
yield decodeBigInts(row);
|
|
@@ -156,9 +156,11 @@ export class EntityCodeGenerator {
|
|
|
156
156
|
const fieldOptions = this.buildFieldOptions(col, propertyName);
|
|
157
157
|
lines.push(` @Field(${fieldOptions})`);
|
|
158
158
|
}
|
|
159
|
-
// Property
|
|
159
|
+
// Property. A generated column is the database's to write, so it is `readonly`: a write payload
|
|
160
|
+
// leaves those out, and one naming it would be dropped rather than persisted.
|
|
160
161
|
const nullable = col.nullable ? '?' : '';
|
|
161
|
-
|
|
162
|
+
const written = col.generatedAs === undefined ? '' : 'readonly ';
|
|
163
|
+
lines.push(` ${written}${propertyName}${nullable}: ${tsType};`);
|
|
162
164
|
return lines.join('\n');
|
|
163
165
|
}
|
|
164
166
|
/**
|
|
@@ -6,6 +6,7 @@ export { MsSqlIndexDdl } from './mssqlIndexDdl.js';
|
|
|
6
6
|
export { MsSqlTableDdl } from './mssqlTableDdl.js';
|
|
7
7
|
export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
|
|
8
8
|
export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
9
|
+
export { SqliteIndexDdl } from './sqliteIndexDdl.js';
|
|
9
10
|
export { TableDdl } from './tableDdl.js';
|
|
10
11
|
export declare function indexDdlFor(dialect: AbstractSqlDialect): IndexDdl;
|
|
11
12
|
/**
|
|
@@ -3,16 +3,18 @@ import { MsSqlIndexDdl } from './mssqlIndexDdl.js';
|
|
|
3
3
|
import { MsSqlTableDdl } from './mssqlTableDdl.js';
|
|
4
4
|
import { MariaIndexDdl, MySqlIndexDdl } from './mysqlIndexDdl.js';
|
|
5
5
|
import { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
6
|
+
import { SqliteIndexDdl } from './sqliteIndexDdl.js';
|
|
6
7
|
import { TableDdl } from './tableDdl.js';
|
|
7
8
|
export { IndexDdl } from './indexDdl.js';
|
|
8
9
|
export { MsSqlIndexDdl } from './mssqlIndexDdl.js';
|
|
9
10
|
export { MsSqlTableDdl } from './mssqlTableDdl.js';
|
|
10
11
|
export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl.js';
|
|
11
12
|
export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
|
|
13
|
+
export { SqliteIndexDdl } from './sqliteIndexDdl.js';
|
|
12
14
|
export { TableDdl } from './tableDdl.js';
|
|
13
15
|
/**
|
|
14
16
|
* Each engine's index DDL, by the `dialectName` a subclass inherits: by name, so this entry carries no
|
|
15
|
-
* dialect, and exhaustive, so a new engine has to name its own.
|
|
17
|
+
* dialect, and exhaustive, so a new engine has to name its own.
|
|
16
18
|
*/
|
|
17
19
|
const INDEX_DDL = {
|
|
18
20
|
postgres: PgIndexDdl,
|
|
@@ -20,7 +22,7 @@ const INDEX_DDL = {
|
|
|
20
22
|
mysql: MySqlIndexDdl,
|
|
21
23
|
mariadb: MariaIndexDdl,
|
|
22
24
|
mssql: MsSqlIndexDdl,
|
|
23
|
-
sqlite:
|
|
25
|
+
sqlite: SqliteIndexDdl,
|
|
24
26
|
};
|
|
25
27
|
export function indexDdlFor(dialect) {
|
|
26
28
|
return new INDEX_DDL[dialect.dialectName](dialect);
|
|
@@ -37,6 +37,8 @@ export declare class IndexDdl<D extends AbstractSqlDialect = AbstractSqlDialect>
|
|
|
37
37
|
protected readonly indexTypeKeywords: ReadonlyMap<IndexType, string>;
|
|
38
38
|
/** The keyword an index type replaces `INDEX` with, or `INDEX` for the types that do not. */
|
|
39
39
|
protected indexKeyword(index: IndexSchema): string;
|
|
40
|
+
/** What the index is over, between the parentheses: a fulltext one's columns as a search matches them, else its entries. */
|
|
41
|
+
protected indexTarget(index: IndexSchema): string;
|
|
40
42
|
/** One index entry: what is indexed, its operator class if any, then its stored order. */
|
|
41
43
|
protected indexColumn(entry: IndexColumnSchema, index: IndexSchema): string;
|
|
42
44
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsonTypeMode } from '../../dialect/jsonSql.js';
|
|
2
2
|
import { INDEX_TYPES } from '../../schema/types.js';
|
|
3
3
|
import { INDEX_FEATURE_LABELS, } from '../../type/index.js';
|
|
4
|
-
import { getKeys } from '../../util/index.js';
|
|
4
|
+
import { fulltextConfig, getKeys } from '../../util/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* What in an index asks for each feature. A `Record` over the feature union rather than a list, so a
|
|
7
7
|
* feature added to {@link INDEX_FEATURE_LABELS} cannot reach a dialect without the test that decides
|
|
@@ -45,7 +45,7 @@ export class IndexDdl {
|
|
|
45
45
|
assertIndexFeatures(index, this.indexFeatures, this.dialect.dialectName);
|
|
46
46
|
const unique = index.unique ? 'UNIQUE ' : '';
|
|
47
47
|
const ifNotExists = (opts.ifNotExists ?? this.dialect.features.indexIfNotExists) ? 'IF NOT EXISTS ' : '';
|
|
48
|
-
const columns =
|
|
48
|
+
const columns = this.indexTarget(index);
|
|
49
49
|
return (`CREATE ${unique}${this.indexKeyword(index)} ${ifNotExists}${this.dialect.escapeId(index.name)} ` +
|
|
50
50
|
`ON ${this.dialect.escapeId(tableName)}${this.indexAccessMethod(index)} (${columns})` +
|
|
51
51
|
`${this.indexInclude(index)}${this.indexTuning(index)}${this.indexPredicate(index)};`);
|
|
@@ -78,6 +78,14 @@ export class IndexDdl {
|
|
|
78
78
|
indexKeyword(index) {
|
|
79
79
|
return (index.type && this.indexTypeKeywords.get(index.type)) || 'INDEX';
|
|
80
80
|
}
|
|
81
|
+
/** What the index is over, between the parentheses: a fulltext one's columns as a search matches them, else its entries. */
|
|
82
|
+
indexTarget(index) {
|
|
83
|
+
if (index.type === 'fulltext' && !index.entries.some((entry) => entry.expression)) {
|
|
84
|
+
const columns = index.entries.map((entry) => this.dialect.escapeId(entry.column));
|
|
85
|
+
return this.dialect.textSearchTarget(columns, fulltextConfig(index));
|
|
86
|
+
}
|
|
87
|
+
return index.entries.map((entry) => this.indexColumn(entry, index)).join(', ');
|
|
88
|
+
}
|
|
81
89
|
/** One index entry: what is indexed, its operator class if any, then its stored order. */
|
|
82
90
|
indexColumn(entry, index) {
|
|
83
91
|
return `${this.indexColumnTarget(entry)}${this.indexColumnOpsClass(entry, index)}${this.indexColumnOrder(entry)}`;
|
|
@@ -2,9 +2,8 @@ import type { IndexColumnSchema, IndexSchema } from '../../type/index.js';
|
|
|
2
2
|
import { IndexDdl } from './indexDdl.js';
|
|
3
3
|
/** `CREATE INDEX ... USING hnsw ("embedding" vector_cosine_ops) WITH (m = ...)`, pgvector's form. */
|
|
4
4
|
export declare class PgIndexDdl extends IndexDdl {
|
|
5
|
-
/** Postgres 18's `pg_am`, with pgvector's two. */
|
|
5
|
+
/** Postgres 18's `pg_am`, with pgvector's two, and `fulltext`, which builds a `gin` one. */
|
|
6
6
|
protected readonly indexTypes: Set<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch">;
|
|
7
|
-
protected readonly indexTypeHints: ReadonlyMap<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch", string>;
|
|
8
7
|
protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
|
|
9
8
|
/** pgvector's own index types; CockroachDB's native one widens this. */
|
|
10
9
|
protected isVectorIndex(index: IndexSchema): boolean;
|
|
@@ -27,6 +26,6 @@ export declare class CockroachIndexDdl extends PgIndexDdl {
|
|
|
27
26
|
protected isVectorIndex(index: IndexSchema): boolean;
|
|
28
27
|
protected indexKeyword(index: IndexSchema): string;
|
|
29
28
|
protected indexAccessMethod(index: IndexSchema): string;
|
|
30
|
-
/**
|
|
31
|
-
protected indexTuning(): string;
|
|
29
|
+
/** Its build-time candidate list alone: pgvector's `WITH (m = 16)` answers "invalid storage parameter", `hnsw` included. */
|
|
30
|
+
protected indexTuning(index: IndexSchema): string;
|
|
32
31
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { unsupportedVectorMetric } from '../../type/vector.js';
|
|
2
2
|
import { IndexDdl } from './indexDdl.js';
|
|
3
|
-
/** `$text` computes its `TO_TSVECTOR` per row, which no index over the raw columns serves. */
|
|
4
|
-
const PG_INDEX_TYPE_HINTS = new Map([
|
|
5
|
-
['fulltext', '. $text needs none there; name the columns it searches with $fields.'],
|
|
6
|
-
]);
|
|
7
3
|
/** `CREATE INDEX ... USING hnsw ("embedding" vector_cosine_ops) WITH (m = ...)`, pgvector's form. */
|
|
8
4
|
export class PgIndexDdl extends IndexDdl {
|
|
9
|
-
/** Postgres 18's `pg_am`, with pgvector's two. */
|
|
5
|
+
/** Postgres 18's `pg_am`, with pgvector's two, and `fulltext`, which builds a `gin` one. */
|
|
10
6
|
indexTypes = new Set([
|
|
11
7
|
'btree',
|
|
12
8
|
'hash',
|
|
@@ -15,8 +11,8 @@ export class PgIndexDdl extends IndexDdl {
|
|
|
15
11
|
'brin',
|
|
16
12
|
'hnsw',
|
|
17
13
|
'ivfflat',
|
|
14
|
+
'fulltext',
|
|
18
15
|
]);
|
|
19
|
-
indexTypeHints = PG_INDEX_TYPE_HINTS;
|
|
20
16
|
indexFeatures = new Set([
|
|
21
17
|
'expression',
|
|
22
18
|
'partial',
|
|
@@ -30,6 +26,9 @@ export class PgIndexDdl extends IndexDdl {
|
|
|
30
26
|
return index.type === 'hnsw' || index.type === 'ivfflat';
|
|
31
27
|
}
|
|
32
28
|
indexAccessMethod(index) {
|
|
29
|
+
if (index.type === 'fulltext') {
|
|
30
|
+
return ' USING gin';
|
|
31
|
+
}
|
|
33
32
|
return index.type ? ` USING ${index.type}` : '';
|
|
34
33
|
}
|
|
35
34
|
/**
|
|
@@ -75,9 +74,8 @@ export class PgIndexDdl extends IndexDdl {
|
|
|
75
74
|
export class CockroachIndexDdl extends PgIndexDdl {
|
|
76
75
|
indexFeatures = new Set(['expression', 'partial', 'include', 'jsonPath']);
|
|
77
76
|
/** v26.3 answers `hash` and `brin` "unimplemented", `ivfflat` "unrecognized"; `hnsw` builds its vector index. */
|
|
78
|
-
indexTypes = new Set(['btree', 'gin', 'gist', 'hnsw', 'vector']);
|
|
77
|
+
indexTypes = new Set(['btree', 'gin', 'gist', 'hnsw', 'vector', 'fulltext']);
|
|
79
78
|
indexTypeHints = new Map([
|
|
80
|
-
...PG_INDEX_TYPE_HINTS,
|
|
81
79
|
['ivfflat', "; declare type: 'vector' instead"],
|
|
82
80
|
]);
|
|
83
81
|
isNativeVectorIndex(index) {
|
|
@@ -92,8 +90,10 @@ export class CockroachIndexDdl extends PgIndexDdl {
|
|
|
92
90
|
indexAccessMethod(index) {
|
|
93
91
|
return this.isNativeVectorIndex(index) ? '' : super.indexAccessMethod(index);
|
|
94
92
|
}
|
|
95
|
-
/**
|
|
96
|
-
indexTuning() {
|
|
97
|
-
return
|
|
93
|
+
/** Its build-time candidate list alone: pgvector's `WITH (m = 16)` answers "invalid storage parameter", `hnsw` included. */
|
|
94
|
+
indexTuning(index) {
|
|
95
|
+
return this.isVectorIndex(index) && index.efConstruction !== undefined
|
|
96
|
+
? ` WITH (build_beam_size = ${index.efConstruction})`
|
|
97
|
+
: '';
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IndexColumnSchema, IndexSchema } from '../../type/index.js';
|
|
2
|
+
import { IndexDdl } from './indexDdl.js';
|
|
3
|
+
/**
|
|
4
|
+
* SQLite's `CREATE INDEX`, which names no index type. A vector index is libSQL's DiskANN where the dialect
|
|
5
|
+
* can index its metric, and a plain index on an engine with none, so an entity written for Postgres migrates.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SqliteIndexDdl extends IndexDdl {
|
|
8
|
+
protected indexColumn(entry: IndexColumnSchema, index: IndexSchema): string;
|
|
9
|
+
/** The metric libSQL's index names, or `undefined` where the index is no vector index or the engine has none. */
|
|
10
|
+
private vectorIndexMetric;
|
|
11
|
+
}
|