uql-orm 0.21.0 → 0.23.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.
Files changed (214) hide show
  1. package/README.md +5 -4
  2. package/dist/browser/uql-browser.min.js.map +3 -3
  3. package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
  4. package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
  5. package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
  6. package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
  7. package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
  8. package/dist/cockroachdb/cockroachDialect.js +33 -18
  9. package/dist/d1/d1Querier.d.ts +4 -3
  10. package/dist/d1/d1Querier.js +0 -3
  11. package/dist/d1/d1SqliteDialect.d.ts +7 -0
  12. package/dist/d1/d1SqliteDialect.js +9 -0
  13. package/dist/dialect/abstractDialect.d.ts +8 -3
  14. package/dist/dialect/abstractDialect.js +9 -4
  15. package/dist/dialect/abstractSqlDialect.d.ts +49 -8
  16. package/dist/dialect/abstractSqlDialect.js +95 -47
  17. package/dist/dialect/index.d.ts +1 -0
  18. package/dist/dialect/index.js +1 -0
  19. package/dist/dialect/indexSqlDialect.d.ts +57 -0
  20. package/dist/dialect/indexSqlDialect.js +101 -0
  21. package/dist/dialect/mysqlLikeSqlDialect.d.ts +26 -1
  22. package/dist/dialect/mysqlLikeSqlDialect.js +57 -1
  23. package/dist/dialect/pgLikeSqlDialect.d.ts +37 -22
  24. package/dist/dialect/pgLikeSqlDialect.js +97 -68
  25. package/dist/dialect/vectorCast.d.ts +21 -0
  26. package/dist/dialect/vectorCast.js +28 -0
  27. package/dist/dialect/vectorSqlDialect.d.ts +10 -3
  28. package/dist/dialect/vectorSqlDialect.js +19 -8
  29. package/dist/entity/decorator/bag.d.ts +35 -0
  30. package/dist/entity/decorator/bag.js +54 -0
  31. package/dist/entity/decorator/entity.d.ts +28 -2
  32. package/dist/entity/decorator/entity.js +40 -2
  33. package/dist/entity/decorator/members.d.ts +51 -0
  34. package/dist/entity/decorator/members.js +51 -0
  35. package/dist/entity/index.d.ts +3 -1
  36. package/dist/entity/index.js +3 -1
  37. package/dist/entity/metadata/definition.d.ts +23 -8
  38. package/dist/entity/metadata/definition.js +56 -100
  39. package/dist/http/contract.js +1 -2
  40. package/dist/http/handler.js +5 -26
  41. package/dist/http/query.js +1 -1
  42. package/dist/libsql/libsqlDialect.d.ts +12 -2
  43. package/dist/libsql/libsqlDialect.js +14 -2
  44. package/dist/libsql/libsqlQuerier.d.ts +9 -22
  45. package/dist/libsql/libsqlQuerier.js +9 -85
  46. package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
  47. package/dist/libsql/libsqlQuerierPool.js +17 -15
  48. package/dist/maria/mariaDialect.d.ts +26 -4
  49. package/dist/maria/mariaDialect.js +54 -15
  50. package/dist/migrate/acquireQuerierForMigrations.d.ts +13 -1
  51. package/dist/migrate/acquireQuerierForMigrations.js +28 -0
  52. package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
  53. package/dist/migrate/builder/migrationBuilder.js +58 -76
  54. package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
  55. package/dist/migrate/builder/splitSqlStatements.js +0 -2
  56. package/dist/migrate/builder/tableBuilder.d.ts +12 -2
  57. package/dist/migrate/builder/tableBuilder.js +71 -119
  58. package/dist/migrate/builder/types.d.ts +30 -41
  59. package/dist/migrate/cli-config.js +24 -8
  60. package/dist/migrate/cli.d.ts +3 -2
  61. package/dist/migrate/cli.js +26 -49
  62. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
  63. package/dist/migrate/codegen/entityCodeGenerator.js +8 -67
  64. package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
  65. package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
  66. package/dist/migrate/codegen/index.d.ts +0 -3
  67. package/dist/migrate/codegen/index.js +0 -6
  68. package/dist/migrate/drift/driftDetector.d.ts +10 -1
  69. package/dist/migrate/drift/driftDetector.js +22 -5
  70. package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
  71. package/dist/migrate/generator/indexNodeToSchema.js +14 -0
  72. package/dist/migrate/generator/mongoCommand.d.ts +57 -0
  73. package/dist/migrate/generator/mongoCommand.js +26 -0
  74. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
  75. package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
  76. package/dist/migrate/index.d.ts +0 -1
  77. package/dist/migrate/index.js +0 -1
  78. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +30 -11
  79. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +61 -57
  80. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
  81. package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
  82. package/dist/migrate/introspection/mongoIntrospector.js +9 -15
  83. package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
  84. package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
  85. package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
  86. package/dist/migrate/introspection/postgresIntrospector.js +4 -10
  87. package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
  88. package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
  89. package/dist/migrate/migrator.d.ts +23 -3
  90. package/dist/migrate/migrator.js +101 -150
  91. package/dist/migrate/schemaGenerator.d.ts +26 -37
  92. package/dist/migrate/schemaGenerator.js +69 -174
  93. package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
  94. package/dist/migrate/schemaGeneratorAsync.js +3 -3
  95. package/dist/migrate/storage/databaseStorage.d.ts +2 -0
  96. package/dist/migrate/storage/databaseStorage.js +12 -27
  97. package/dist/mongo/mongoDialect.d.ts +2 -0
  98. package/dist/mongo/mongoDialect.js +14 -3
  99. package/dist/mongo/mongodbQuerier.js +171 -229
  100. package/dist/mysql/mysqlDialect.d.ts +8 -0
  101. package/dist/mysql/mysqlDialect.js +13 -0
  102. package/dist/nestjs/uqlContextInterceptor.js +58 -22
  103. package/dist/nestjs/uqlModule.d.ts +2 -11
  104. package/dist/nestjs/uqlModule.js +93 -44
  105. package/dist/postgres/postgresDialect.js +2 -2
  106. package/dist/querier/abstractQuerier.d.ts +28 -0
  107. package/dist/querier/abstractQuerier.js +47 -22
  108. package/dist/querier/abstractQuerierPool.d.ts +3 -0
  109. package/dist/querier/abstractQuerierPool.js +5 -3
  110. package/dist/querier/abstractSqlQuerier.d.ts +1 -3
  111. package/dist/querier/abstractSqlQuerier.js +48 -96
  112. package/dist/querier/index.d.ts +3 -1
  113. package/dist/querier/index.js +3 -1
  114. package/dist/querier/querierContext.browser.d.ts +12 -0
  115. package/dist/querier/querierContext.browser.js +18 -0
  116. package/dist/querier/querierContext.d.ts +22 -0
  117. package/dist/querier/querierContext.js +42 -0
  118. package/dist/querier/queryError.d.ts +22 -0
  119. package/dist/querier/queryError.js +20 -0
  120. package/dist/querier/transactional.d.ts +26 -0
  121. package/dist/querier/transactional.js +43 -0
  122. package/dist/schema/canonicalType.d.ts +14 -2
  123. package/dist/schema/canonicalType.js +41 -31
  124. package/dist/schema/index.d.ts +1 -1
  125. package/dist/schema/schemaASTBuilder.d.ts +9 -2
  126. package/dist/schema/schemaASTBuilder.js +58 -49
  127. package/dist/schema/types.d.ts +10 -58
  128. package/dist/sqlite/abstractSqliteQuerier.d.ts +46 -0
  129. package/dist/sqlite/abstractSqliteQuerier.js +44 -0
  130. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
  131. package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
  132. package/dist/sqlite/hranaQuerier.d.ts +54 -0
  133. package/dist/sqlite/hranaQuerier.js +68 -0
  134. package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
  135. package/dist/sqlite/hranaQuerierPool.js +25 -0
  136. package/dist/sqlite/index.d.ts +3 -1
  137. package/dist/sqlite/index.js +3 -1
  138. package/dist/sqlite/localSqliteQuerierPool.d.ts +31 -0
  139. package/dist/sqlite/localSqliteQuerierPool.js +34 -0
  140. package/dist/sqlite/nodeSqliteAdapter.d.ts +35 -0
  141. package/dist/sqlite/nodeSqliteAdapter.js +28 -0
  142. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +28 -0
  143. package/dist/sqlite/nodeSqliteQuerierPool.js +29 -0
  144. package/dist/sqlite/sqliteDialect.d.ts +18 -5
  145. package/dist/sqlite/sqliteDialect.js +23 -36
  146. package/dist/sqlite/sqliteQuerier.d.ts +16 -9
  147. package/dist/sqlite/sqliteQuerier.js +4 -27
  148. package/dist/sqlite/sqliteQuerierPool.d.ts +11 -14
  149. package/dist/sqlite/sqliteQuerierPool.js +16 -24
  150. package/dist/turso/index.d.ts +3 -0
  151. package/dist/turso/index.js +3 -0
  152. package/dist/turso/local.d.ts +3 -0
  153. package/dist/turso/local.js +3 -0
  154. package/dist/turso/tursoDialect.d.ts +15 -0
  155. package/dist/turso/tursoDialect.js +18 -0
  156. package/dist/turso/tursoLocalQuerier.d.ts +25 -0
  157. package/dist/turso/tursoLocalQuerier.js +20 -0
  158. package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
  159. package/dist/turso/tursoLocalQuerierPool.js +39 -0
  160. package/dist/turso/tursoQuerier.d.ts +10 -0
  161. package/dist/turso/tursoQuerier.js +10 -0
  162. package/dist/turso/tursoQuerierPool.d.ts +39 -0
  163. package/dist/turso/tursoQuerierPool.js +38 -0
  164. package/dist/type/config.d.ts +0 -6
  165. package/dist/type/dialect.d.ts +12 -4
  166. package/dist/type/dialect.js +7 -1
  167. package/dist/type/entity.d.ts +154 -26
  168. package/dist/type/migration.d.ts +32 -23
  169. package/dist/type/querier.d.ts +6 -0
  170. package/dist/type/vector.d.ts +5 -2
  171. package/dist/util/hook.util.js +1 -1
  172. package/dist/util/index.d.ts +1 -0
  173. package/dist/util/index.js +1 -0
  174. package/dist/util/indexColumn.util.d.ts +6 -0
  175. package/dist/util/indexColumn.util.js +26 -0
  176. package/package.json +21 -16
  177. package/dist/entity/decorator/field.d.ts +0 -2
  178. package/dist/entity/decorator/field.js +0 -7
  179. package/dist/entity/decorator/filter.d.ts +0 -7
  180. package/dist/entity/decorator/filter.js +0 -11
  181. package/dist/entity/decorator/hook.d.ts +0 -7
  182. package/dist/entity/decorator/hook.js +0 -14
  183. package/dist/entity/decorator/id.d.ts +0 -2
  184. package/dist/entity/decorator/id.js +0 -7
  185. package/dist/entity/decorator/index-decorator.d.ts +0 -29
  186. package/dist/entity/decorator/index-decorator.js +0 -26
  187. package/dist/entity/decorator/index.d.ts +0 -8
  188. package/dist/entity/decorator/index.js +0 -8
  189. package/dist/entity/decorator/relation.d.ts +0 -8
  190. package/dist/entity/decorator/relation.js +0 -19
  191. package/dist/migrate/codegen/entityMerger.d.ts +0 -114
  192. package/dist/migrate/codegen/entityMerger.js +0 -293
  193. package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
  194. package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
  195. package/dist/migrate/codegen/smartRelationDetector.d.ts +0 -47
  196. package/dist/migrate/codegen/smartRelationDetector.js +0 -134
  197. package/dist/migrate/generator/index.d.ts +0 -2
  198. package/dist/migrate/generator/index.js +0 -2
  199. package/dist/migrate/sync/index.d.ts +0 -6
  200. package/dist/migrate/sync/index.js +0 -6
  201. package/dist/migrate/sync/schemaSync.d.ts +0 -131
  202. package/dist/migrate/sync/schemaSync.js +0 -259
  203. package/dist/querier/decorator/index.d.ts +0 -4
  204. package/dist/querier/decorator/index.js +0 -4
  205. package/dist/querier/decorator/injectQuerier.d.ts +0 -3
  206. package/dist/querier/decorator/injectQuerier.js +0 -33
  207. package/dist/querier/decorator/log.d.ts +0 -32
  208. package/dist/querier/decorator/log.js +0 -52
  209. package/dist/querier/decorator/serialized.d.ts +0 -6
  210. package/dist/querier/decorator/serialized.js +0 -13
  211. package/dist/querier/decorator/transactional.d.ts +0 -6
  212. package/dist/querier/decorator/transactional.js +0 -48
  213. package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
  214. package/dist/sqlite/betterSqlite3Dialect.js +0 -9
@@ -1,12 +1,13 @@
1
1
  import { getMeta } from '../entity/index.js';
2
2
  import { QueryRaw, RAW_ALIAS, RAW_VALUE, } from '../type/index.js';
3
- import { asSelectMap, buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, flatObject, getFieldKeys, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, hasMultipleKeys, isJsonType, isJsonUpdateOp, isOperatorObject, isOperatorOnlyObject, isPopulatingRelations, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationAtKey, parseRelationSize, raw, someValue, withoutSoftDeleteFilter, } from '../util/index.js';
3
+ import { asSelectMap, buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, flatObject, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, hasMultipleKeys, isJsonType, isJsonUpdateOp, isOperatorObject, isOperatorOnlyObject, isPopulatingRelations, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationAtKey, parseRelationSize, raw, someValue, withoutSoftDeleteFilter, } from '../util/index.js';
4
4
  import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
5
+ import { IndexSqlDialect } from './indexSqlDialect.js';
5
6
  import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
6
7
  import { JSON_ELEM_ALIAS_PREFIX, jsonElemExists } from './jsonSql.js';
7
8
  import { SqlQueryContext } from './queryContext.js';
8
- import { VectorSqlDialect } from './vectorSqlDialect.js';
9
- export class AbstractSqlDialect extends VectorSqlDialect {
9
+ import { isVectorFieldType } from './vectorCast.js';
10
+ export class AbstractSqlDialect extends IndexSqlDialect {
10
11
  isolationLevelStrategy = 'inline';
11
12
  alterColumnStrategy = 'single-statement';
12
13
  alterColumnSyntax = 'ALTER COLUMN';
@@ -135,7 +136,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
135
136
  const field = meta.fields[key];
136
137
  if (!field)
137
138
  return;
138
- const columnName = this.resolveColumnName(key, field);
139
139
  if (field.virtual) {
140
140
  this.getRawValue(ctx, {
141
141
  value: raw(field.virtual[RAW_VALUE], key),
@@ -143,17 +143,35 @@ export class AbstractSqlDialect extends VectorSqlDialect {
143
143
  escapedPrefix,
144
144
  autoPrefixAlias: opts.autoPrefixAlias,
145
145
  });
146
+ return;
146
147
  }
147
- else {
148
- ctx.append(escapedPrefix + this.escapeId(columnName));
149
- }
150
- if (!field.virtual && (columnName !== key || opts.autoPrefixAlias)) {
151
- const aliasStr = prefix + key;
152
- ctx.append(' ' + this.escapeId(aliasStr, true));
148
+ const columnName = this.resolveColumnName(key, field);
149
+ const column = escapedPrefix + this.escapeId(columnName);
150
+ const expr = this.selectFieldExpr(column, field);
151
+ ctx.append(expr);
152
+ // An expression needs the alias too, or the row comes back keyed by the expression text.
153
+ if (expr !== column || columnName !== key || opts.autoPrefixAlias) {
154
+ ctx.append(' ' + this.escapeId(prefix + key, true));
153
155
  }
154
156
  }
155
157
  });
156
158
  }
159
+ /**
160
+ * The expression a scalar field is read through, the plain column by default. MariaDB reads a
161
+ * vector column back with `VEC_ToText`, since selecting it raw yields its binary form.
162
+ */
163
+ selectFieldExpr(escapedColumn, _field) {
164
+ return escapedColumn;
165
+ }
166
+ /**
167
+ * The `$text` full-text predicate, which every engine spells differently: `MATCH ... AGAINST`
168
+ * (MySQL family), `to_tsvector @@ websearch_to_tsquery` (Postgres-wire), an FTS5 `MATCH` against
169
+ * the table itself (SQLite). No portable form exists, so a dialect without one says so here rather
170
+ * than inheriting another engine's syntax.
171
+ */
172
+ appendTextSearch(_ctx, _entity, _meta, _search) {
173
+ throw new TypeError(`${this.dialectName} does not support $text full-text search`);
174
+ }
157
175
  select(ctx, entity, select, exclude, populate, opts = {}, distinct, sort) {
158
176
  const meta = getMeta(entity);
159
177
  const tableName = this.resolveTableName(entity, meta);
@@ -296,16 +314,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
296
314
  return;
297
315
  }
298
316
  if (key === '$text') {
299
- const search = val;
300
- const searchFields = search.$fields ?? getFieldKeys(meta.fields);
301
- const fields = searchFields.map((fKey) => {
302
- const field = meta.fields[fKey];
303
- const columnName = this.resolveColumnName(fKey, field);
304
- return this.escapeId(columnName);
305
- });
306
- ctx.append(`MATCH(${fields.join(', ')}) AGAINST(`);
307
- ctx.addValue(search.$value);
308
- ctx.append(')');
317
+ this.appendTextSearch(ctx, entity, meta, val);
309
318
  return;
310
319
  }
311
320
  if (key === '$and' || key === '$or' || key === '$not' || key === '$nor') {
@@ -911,6 +920,18 @@ export class AbstractSqlDialect extends VectorSqlDialect {
911
920
  this.search(ctx, entity, q, opts);
912
921
  }
913
922
  insert(ctx, entity, payload, opts) {
923
+ this.appendInsertValues(ctx, entity, payload, opts);
924
+ // Every engine whose ids come back from the statement itself wants the same clause, so it is
925
+ // appended once here instead of in an identical `insert` override per dialect.
926
+ if (this.insertIdSource === 'returning') {
927
+ ctx.append(` ${this.returningId(entity)}`);
928
+ }
929
+ }
930
+ /**
931
+ * `INSERT INTO ... VALUES (...)` and nothing more. The upsert builders extend this rather than
932
+ * {@link insert}: their own clause has to come before the `RETURNING`, not after it.
933
+ */
934
+ appendInsertValues(ctx, entity, payload, opts) {
914
935
  const meta = getMeta(entity);
915
936
  const payloads = fillOnFields(meta, payload, 'onInsert');
916
937
  const keys = getInsertFieldKeys(meta, payloads);
@@ -919,10 +940,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
919
940
  const width = keys.length;
920
941
  const fields = new Array(width);
921
942
  const columns = new Array(width);
943
+ const kinds = new Array(width);
922
944
  for (let i = 0; i < width; i++) {
923
945
  const key = keys[i];
924
- fields[i] = meta.fields[key];
946
+ const field = meta.fields[key];
947
+ fields[i] = field;
925
948
  columns[i] = this.escapedColumnName(meta, key);
949
+ kinds[i] = this.persistKind(field);
926
950
  }
927
951
  const tableName = this.resolveTableName(entity, meta);
928
952
  ctx.append(`INSERT INTO ${this.escapeId(tableName)} (${columns.join(', ')}) VALUES (`);
@@ -939,8 +963,12 @@ export class AbstractSqlDialect extends VectorSqlDialect {
939
963
  if (value === undefined) {
940
964
  this.appendDefaultInsertValue(ctx, fields[i]);
941
965
  }
966
+ else if (kinds[i] === 'plain' && !(value instanceof QueryRaw)) {
967
+ // The overwhelmingly common case in a bulk insert, so it binds without a dispatch.
968
+ ctx.addValue(value);
969
+ }
942
970
  else {
943
- this.formatPersistableValue(ctx, fields[i], value);
971
+ this.writePersistableValue(ctx, kinds[i], fields[i], value);
944
972
  }
945
973
  }
946
974
  }
@@ -978,22 +1006,29 @@ export class AbstractSqlDialect extends VectorSqlDialect {
978
1006
  }
979
1007
  this.search(ctx, entity, q, opts);
980
1008
  }
981
- upsert(ctx, entity, conflictPaths, payload) {
1009
+ /**
1010
+ * `INSERT ... ON CONFLICT (...) DO UPDATE/NOTHING RETURNING ...`, which SQLite adopted from Postgres
1011
+ * and which every dialect here speaks except the MySQL family (see {@link MysqlLikeSqlDialect}).
1012
+ *
1013
+ * Two orderings matter, and they pull in opposite directions. The assignments are computed *before*
1014
+ * the insert, because `appendInsertValues` fills `onInsert` fields into the payload and a column that
1015
+ * exists only there - `createdAt` - must not join the update set. Their bound values are pushed
1016
+ * *after* it, because a `?` placeholder is positional and the clause comes last in the statement.
1017
+ * {@link PgLikeSqlDialect} overrides this: `$N` placeholders make array order irrelevant, so it can
1018
+ * bind into the main context and skip the second one.
1019
+ */
1020
+ upsert(ctx, entity, conflictPaths, payload, extraReturning = '') {
982
1021
  const meta = getMeta(entity);
983
1022
  const updateCtx = this.createContext();
984
- const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `VALUES(${name})`);
985
- if (update) {
986
- this.insert(ctx, entity, payload);
987
- ctx.append(` ON DUPLICATE KEY UPDATE ${update}`);
988
- ctx.pushValue(...updateCtx.values);
989
- }
990
- else {
991
- const insertCtx = this.createContext();
992
- this.insert(insertCtx, entity, payload);
993
- ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
994
- ctx.pushValue(...insertCtx.values);
995
- }
996
- }
1023
+ const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, this.upsertExcluded);
1024
+ const keys = this.getUpsertConflictPathsStr(meta, conflictPaths);
1025
+ const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
1026
+ this.appendInsertValues(ctx, entity, payload);
1027
+ ctx.append(` ON CONFLICT (${keys}) ${onConflict} ${this.returningId(entity)}${extraReturning}`);
1028
+ ctx.pushValue(...updateCtx.values);
1029
+ }
1030
+ /** How an `ON CONFLICT` assignment reads the row that was being inserted. */
1031
+ upsertExcluded = (columnName) => `EXCLUDED.${columnName}`;
997
1032
  getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, callback) {
998
1033
  const sample = Array.isArray(payload) ? payload[0] : payload;
999
1034
  const cloned = { ...sample };
@@ -1045,22 +1080,38 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1045
1080
  return escapeSqlId(val, this.escapeIdChar, forbidQualified, addDot);
1046
1081
  }
1047
1082
  /**
1048
- * The single type dispatch for a persisted value. Dialects override {@link appendJsonValue} and
1049
- * {@link appendVectorValue} rather than this, so the chain runs once per value - overriding this
1050
- * and delegating back to `super` ran every check twice, measurably slowing every INSERT/UPDATE.
1083
+ * Bind one persisted value, classifying its column on the spot. Dialects override
1084
+ * {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
1085
+ * value - overriding this and delegating back to `super` ran every check twice, measurably slowing
1086
+ * every INSERT/UPDATE.
1051
1087
  */
1052
1088
  formatPersistableValue(ctx, field, value) {
1089
+ this.writePersistableValue(ctx, this.persistKind(field), field, value);
1090
+ }
1091
+ /**
1092
+ * How a column's values are written. A function of the column, not of the value, so a bulk insert
1093
+ * classifies each column once instead of re-deciding per row: a 20-row, 6-column insert asked
1094
+ * `isJsonType` and `isVectorFieldType` 120 times to get the same six answers.
1095
+ */
1096
+ persistKind(field) {
1097
+ const type = field?.type;
1098
+ if (isJsonType(type)) {
1099
+ return 'json';
1100
+ }
1101
+ return isVectorFieldType(type) ? 'vector' : 'plain';
1102
+ }
1103
+ /** The one type dispatch for a persisted value, over a column kind decided by the caller. */
1104
+ writePersistableValue(ctx, kind, field, value) {
1053
1105
  if (value instanceof QueryRaw) {
1054
1106
  this.getRawValue(ctx, { value });
1055
1107
  return;
1056
1108
  }
1057
- const type = field?.type;
1058
- if (isJsonType(type)) {
1059
- this.appendJsonValue(ctx, value, type);
1109
+ if (kind === 'json') {
1110
+ this.appendJsonValue(ctx, value, field?.type);
1060
1111
  return;
1061
1112
  }
1062
- if (type === 'vector' && Array.isArray(value)) {
1063
- this.appendVectorValue(ctx, value);
1113
+ if (kind === 'vector' && Array.isArray(value)) {
1114
+ this.appendVectorValue(ctx, value, field);
1064
1115
  return;
1065
1116
  }
1066
1117
  ctx.addValue(value);
@@ -1068,9 +1119,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1068
1119
  appendJsonValue(ctx, value, _type) {
1069
1120
  ctx.addValue(value == null ? null : JSON.stringify(value));
1070
1121
  }
1071
- appendVectorValue(ctx, value) {
1072
- ctx.addValue(`[${value.join(',')}]`);
1073
- }
1074
1122
  /**
1075
1123
  * Reads `operand` as a JSON value. Passing the `?` placeholder yields the cast for a bound
1076
1124
  * parameter, and passing an expression re-reads text as JSON - the same SQL either way, which is
@@ -4,5 +4,6 @@ export { PostgresDialect } from '../postgres/postgresDialect.js';
4
4
  export { SqliteDialect } from '../sqlite/sqliteDialect.js';
5
5
  export * from './abstractDialect.js';
6
6
  export * from './abstractSqlDialect.js';
7
+ export * from './indexSqlDialect.js';
7
8
  export * from './mysqlLikeSqlDialect.js';
8
9
  export * from './queryContext.js';
@@ -4,5 +4,6 @@ export { PostgresDialect } from '../postgres/postgresDialect.js';
4
4
  export { SqliteDialect } from '../sqlite/sqliteDialect.js';
5
5
  export * from './abstractDialect.js';
6
6
  export * from './abstractSqlDialect.js';
7
+ export * from './indexSqlDialect.js';
7
8
  export * from './mysqlLikeSqlDialect.js';
8
9
  export * from './queryContext.js';
@@ -0,0 +1,57 @@
1
+ import { type IndexColumnSchema, type IndexFeature, type IndexSchema } from '../type/index.js';
2
+ import { VectorSqlDialect } from './vectorSqlDialect.js';
3
+ /**
4
+ * `CREATE INDEX` for SQL dialects: the statement and the fragments each engine spells differently.
5
+ *
6
+ * A layer of its own for the same reason as {@link VectorSqlDialect} below it - it needs only
7
+ * `escapeId` and `features.indexIfNotExists` from the SQL dialect above, so keeping it here spares
8
+ * that 2000-line class thirteen more members. What each engine can express at all is data
9
+ * ({@link indexFeatures}), validated once, rather than a throw per feature per dialect.
10
+ */
11
+ export declare abstract class IndexSqlDialect extends VectorSqlDialect {
12
+ /**
13
+ * The `CREATE INDEX` statement for this dialect. The form here is the portable one, which SQLite
14
+ * (and so libSQL, Turso and D1) takes verbatim: no access-method clause, no operator classes, no
15
+ * tuning parameters. Dialects that have those override the fragments below rather than this.
16
+ */
17
+ getCreateIndexStatement(tableName: string, index: IndexSchema, opts?: {
18
+ ifNotExists?: boolean;
19
+ }): string;
20
+ /**
21
+ * Index features this dialect can express. Everything here is supported by at least one engine and
22
+ * refused by at least one other, so an index asking for a missing one is rejected rather than
23
+ * emitted: each of them is a hard error at the server, not a slower plan.
24
+ */
25
+ protected readonly indexFeatures: ReadonlySet<IndexFeature>;
26
+ private assertIndexFeatures;
27
+ /**
28
+ * The column-level declaration for a vector index that lives inside `CREATE TABLE` rather than in
29
+ * its own statement, which the `inlineVectorIndex` feature flags. Only MariaDB has one.
30
+ */
31
+ getInlineVectorIndexDeclaration(index: IndexSchema): string;
32
+ /** Only CockroachDB's native vector index replaces the `INDEX` keyword. */
33
+ protected indexKeyword(_index: IndexSchema): string;
34
+ /** One index entry: what is indexed, its operator class if any, then its stored order. */
35
+ protected indexColumn(entry: IndexColumnSchema, index: IndexSchema): string;
36
+ /**
37
+ * A quoted column, optionally prefix-limited, or an expression in its own parentheses - the form
38
+ * `((lower("email")))` that MySQL requires and Postgres, CockroachDB and SQLite all accept, so one
39
+ * rendering serves every engine that has expression indexes.
40
+ */
41
+ protected indexColumnTarget(entry: IndexColumnSchema): string;
42
+ /** Postgres-wire dialects put a vector or user-declared operator class here. */
43
+ protected indexColumnOpsClass(_entry: IndexColumnSchema, _index: IndexSchema): string;
44
+ /** `ASC` is every engine's default, so only `DESC` is worth emitting. */
45
+ protected indexColumnOrder(entry: IndexColumnSchema): string;
46
+ /** ` INCLUDE (...)`: non-key columns stored for index-only scans. Postgres-wire only. */
47
+ protected indexInclude(_index: IndexSchema): string;
48
+ /** ` USING <method>`, which SQLite's grammar has no place for at all. */
49
+ protected indexAccessMethod(_index: IndexSchema): string;
50
+ /** pgvector's ` WITH (m = ..., ef_construction = ..., lists = ...)`. */
51
+ protected indexTuning(_index: IndexSchema): string;
52
+ /**
53
+ * The partial-index predicate. Dialects without partial indexes throw instead of dropping it:
54
+ * silently widening a partial unique index changes which rows the database accepts.
55
+ */
56
+ protected indexPredicate(index: IndexSchema): string;
57
+ }
@@ -0,0 +1,101 @@
1
+ import { INDEX_FEATURE_LABELS } from '../type/index.js';
2
+ import { VectorSqlDialect } from './vectorSqlDialect.js';
3
+ /**
4
+ * `CREATE INDEX` for SQL dialects: the statement and the fragments each engine spells differently.
5
+ *
6
+ * A layer of its own for the same reason as {@link VectorSqlDialect} below it - it needs only
7
+ * `escapeId` and `features.indexIfNotExists` from the SQL dialect above, so keeping it here spares
8
+ * that 2000-line class thirteen more members. What each engine can express at all is data
9
+ * ({@link indexFeatures}), validated once, rather than a throw per feature per dialect.
10
+ */
11
+ export class IndexSqlDialect extends VectorSqlDialect {
12
+ /**
13
+ * The `CREATE INDEX` statement for this dialect. The form here is the portable one, which SQLite
14
+ * (and so libSQL, Turso and D1) takes verbatim: no access-method clause, no operator classes, no
15
+ * tuning parameters. Dialects that have those override the fragments below rather than this.
16
+ */
17
+ getCreateIndexStatement(tableName, index, opts = {}) {
18
+ this.assertIndexFeatures(index);
19
+ const unique = index.unique ? 'UNIQUE ' : '';
20
+ const ifNotExists = (opts.ifNotExists ?? this.features.indexIfNotExists) ? 'IF NOT EXISTS ' : '';
21
+ const columns = index.columns.map((entry) => this.indexColumn(entry, index)).join(', ');
22
+ return (`CREATE ${unique}${this.indexKeyword(index)} ${ifNotExists}${this.escapeId(index.name)} ` +
23
+ `ON ${this.escapeId(tableName)}${this.indexAccessMethod(index)} (${columns})` +
24
+ `${this.indexInclude(index)}${this.indexTuning(index)}${this.indexPredicate(index)};`);
25
+ }
26
+ /**
27
+ * Index features this dialect can express. Everything here is supported by at least one engine and
28
+ * refused by at least one other, so an index asking for a missing one is rejected rather than
29
+ * emitted: each of them is a hard error at the server, not a slower plan.
30
+ */
31
+ indexFeatures = new Set(['expression']);
32
+ assertIndexFeatures(index) {
33
+ const requested = [
34
+ ['expression', index.columns.some((entry) => entry.expression)],
35
+ ['prefixLength', index.columns.some((entry) => entry.length !== undefined)],
36
+ ['nullsOrder', index.columns.some((entry) => entry.nulls !== undefined)],
37
+ ['opsClass', index.columns.some((entry) => entry.opsClass !== undefined)],
38
+ ['include', Boolean(index.include?.length)],
39
+ ];
40
+ for (const [feature, needed] of requested) {
41
+ if (needed && !this.indexFeatures.has(feature)) {
42
+ throw new TypeError(`${this.dialectName} does not support ${INDEX_FEATURE_LABELS[feature]} (index "${index.name}")`);
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * The column-level declaration for a vector index that lives inside `CREATE TABLE` rather than in
48
+ * its own statement, which the `inlineVectorIndex` feature flags. Only MariaDB has one.
49
+ */
50
+ getInlineVectorIndexDeclaration(index) {
51
+ throw new TypeError(`${this.dialectName} has no inline vector index (index "${index.name}")`);
52
+ }
53
+ /** Only CockroachDB's native vector index replaces the `INDEX` keyword. */
54
+ indexKeyword(_index) {
55
+ return 'INDEX';
56
+ }
57
+ /** One index entry: what is indexed, its operator class if any, then its stored order. */
58
+ indexColumn(entry, index) {
59
+ return `${this.indexColumnTarget(entry)}${this.indexColumnOpsClass(entry, index)}${this.indexColumnOrder(entry)}`;
60
+ }
61
+ /**
62
+ * A quoted column, optionally prefix-limited, or an expression in its own parentheses - the form
63
+ * `((lower("email")))` that MySQL requires and Postgres, CockroachDB and SQLite all accept, so one
64
+ * rendering serves every engine that has expression indexes.
65
+ */
66
+ indexColumnTarget(entry) {
67
+ if (entry.expression) {
68
+ return `(${entry.column})`;
69
+ }
70
+ const column = this.escapeId(entry.column);
71
+ return entry.length === undefined ? column : `${column}(${entry.length})`;
72
+ }
73
+ /** Postgres-wire dialects put a vector or user-declared operator class here. */
74
+ indexColumnOpsClass(_entry, _index) {
75
+ return '';
76
+ }
77
+ /** `ASC` is every engine's default, so only `DESC` is worth emitting. */
78
+ indexColumnOrder(entry) {
79
+ const order = entry.order === 'desc' ? ' DESC' : '';
80
+ return entry.nulls ? `${order} NULLS ${entry.nulls.toUpperCase()}` : order;
81
+ }
82
+ /** ` INCLUDE (...)`: non-key columns stored for index-only scans. Postgres-wire only. */
83
+ indexInclude(_index) {
84
+ return '';
85
+ }
86
+ /** ` USING <method>`, which SQLite's grammar has no place for at all. */
87
+ indexAccessMethod(_index) {
88
+ return '';
89
+ }
90
+ /** pgvector's ` WITH (m = ..., ef_construction = ..., lists = ...)`. */
91
+ indexTuning(_index) {
92
+ return '';
93
+ }
94
+ /**
95
+ * The partial-index predicate. Dialects without partial indexes throw instead of dropping it:
96
+ * silently widening a partial unique index changes which rows the database accepts.
97
+ */
98
+ indexPredicate(index) {
99
+ return index.where ? ` WHERE ${index.where}` : '';
100
+ }
101
+ }
@@ -1,4 +1,4 @@
1
- import type { DialectFeatures, FieldOptions, InsertIdSource, QueryContext, QuerySizeComparisonOps } from '../type/index.js';
1
+ import type { DialectFeatures, EntityMeta, FieldOptions, IndexFeature, IndexSchema, InsertIdSource, QueryConflictPaths, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type } from '../type/index.js';
2
2
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
3
3
  /**
4
4
  * Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
@@ -26,8 +26,33 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
26
26
  readonly alterColumnSyntax = "MODIFY COLUMN";
27
27
  readonly booleanLiteral = "integer";
28
28
  readonly insertIdSource: InsertIdSource;
29
+ /**
30
+ * `INSERT ... ON DUPLICATE KEY UPDATE`, and `INSERT IGNORE` when every non-conflict column is itself a
31
+ * conflict key so there is nothing to assign. Neither form takes a conflict target: MySQL picks the
32
+ * unique index for you.
33
+ *
34
+ * The update assignments are built into their own context and pushed afterwards, since they read
35
+ * `VALUES(col)` rather than binding, and any value they *do* bind (an `onUpdate` field absent from the
36
+ * payload) has to land after the insert's for a `?`-placeholder driver.
37
+ */
38
+ upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
29
39
  readonly maxBindValues: number;
30
40
  escape(value: unknown): string;
41
+ /**
42
+ * `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
43
+ * the server answers "Can't find FULLTEXT index matching the column list". Declare it with
44
+ * `@Index([...], { type: 'fulltext' })`.
45
+ */
46
+ protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
47
+ /**
48
+ * A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext`
49
+ * is a syntax error, so it is the keyword that changes rather than the access method.
50
+ */
51
+ protected indexKeyword(index: IndexSchema): string;
52
+ protected readonly indexFeatures: Set<IndexFeature>;
53
+ protected indexAccessMethod(index: IndexSchema): string;
54
+ /** Neither MySQL nor MariaDB has partial indexes, and quietly widening one changes which rows it rejects. */
55
+ protected indexPredicate(index: IndexSchema): string;
31
56
  protected numericCast(expr: string): string;
32
57
  protected ilikeExpr(f: string, ph: string): string;
33
58
  protected neExpr(field: string, ph: string): string;
@@ -1,3 +1,5 @@
1
+ import { getMeta } from '../entity/index.js';
2
+ import { getFieldKeys } from '../util/index.js';
1
3
  import { escapeMysqlSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
2
4
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
3
5
  import { JSON_PULL_ALIAS, jsonAssignCall, jsonPath, jsonRemoveCall, jsonSetTarget } from './jsonSql.js';
@@ -23,7 +25,7 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
23
25
  renameColumn: true,
24
26
  foreignKeyAlter: true,
25
27
  columnComment: true,
26
- vectorIndexStyle: 'inline',
28
+ inlineVectorIndex: false,
27
29
  vectorSupportsLength: false,
28
30
  supportsTimestamptz: false,
29
31
  defaultStringAsText: false,
@@ -43,10 +45,64 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
43
45
  // No `RETURNING` support, so multi-row insert IDs are inferred from the header - see the
44
46
  // `innodb_autoinc_lock_mode` caveat on `buildUpdateResult` in `util/sql.util.ts`.
45
47
  insertIdSource = 'firstId';
48
+ /**
49
+ * `INSERT ... ON DUPLICATE KEY UPDATE`, and `INSERT IGNORE` when every non-conflict column is itself a
50
+ * conflict key so there is nothing to assign. Neither form takes a conflict target: MySQL picks the
51
+ * unique index for you.
52
+ *
53
+ * The update assignments are built into their own context and pushed afterwards, since they read
54
+ * `VALUES(col)` rather than binding, and any value they *do* bind (an `onUpdate` field absent from the
55
+ * payload) has to land after the insert's for a `?`-placeholder driver.
56
+ */
57
+ upsert(ctx, entity, conflictPaths, payload) {
58
+ const meta = getMeta(entity);
59
+ const updateCtx = this.createContext();
60
+ const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `VALUES(${name})`);
61
+ if (update) {
62
+ this.appendInsertValues(ctx, entity, payload);
63
+ ctx.append(` ON DUPLICATE KEY UPDATE ${update}`);
64
+ ctx.pushValue(...updateCtx.values);
65
+ return;
66
+ }
67
+ const insertCtx = this.createContext();
68
+ this.appendInsertValues(insertCtx, entity, payload);
69
+ ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
70
+ ctx.pushValue(...insertCtx.values);
71
+ }
46
72
  maxBindValues = 65535;
47
73
  escape(value) {
48
74
  return escapeMysqlSqlLiteral(value);
49
75
  }
76
+ /**
77
+ * `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
78
+ * the server answers "Can't find FULLTEXT index matching the column list". Declare it with
79
+ * `@Index([...], { type: 'fulltext' })`.
80
+ */
81
+ appendTextSearch(ctx, _entity, meta, search) {
82
+ const searchFields = search.$fields ?? getFieldKeys(meta.fields);
83
+ const columns = searchFields.map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
84
+ ctx.append(`MATCH(${columns.join(', ')}) AGAINST(`);
85
+ ctx.addValue(search.$value);
86
+ ctx.append(')');
87
+ }
88
+ /**
89
+ * A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext`
90
+ * is a syntax error, so it is the keyword that changes rather than the access method.
91
+ */
92
+ indexKeyword(index) {
93
+ return index.type === 'fulltext' ? 'FULLTEXT INDEX' : super.indexKeyword(index);
94
+ }
95
+ indexFeatures = new Set(['expression', 'prefixLength']);
96
+ indexAccessMethod(index) {
97
+ return index.type && index.type !== 'fulltext' ? ` USING ${index.type}` : '';
98
+ }
99
+ /** Neither MySQL nor MariaDB has partial indexes, and quietly widening one changes which rows it rejects. */
100
+ indexPredicate(index) {
101
+ if (index.where) {
102
+ throw new TypeError(`${this.dialectName} does not support partial indexes (index "${index.name}" declares a "where" condition)`);
103
+ }
104
+ return '';
105
+ }
50
106
  numericCast(expr) {
51
107
  return `CAST(${expr} AS DECIMAL)`;
52
108
  }
@@ -1,4 +1,4 @@
1
- import { type DialectFeatures, type EntityMeta, type FieldOptions, type JsonColumnType, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryOptions, type QuerySizeComparisonOps, type QueryVectorSearch, type Type, type VectorDistance } from '../type/index.js';
1
+ import { type DialectFeatures, type EntityMeta, type FieldOptions, type IndexColumnSchema, type IndexFeature, type IndexSchema, type JsonColumnType, type QueryConflictPaths, type QueryContext, type QuerySizeComparisonOps, type QueryTextSearchOptions, type QueryVectorSearch, type Type, type VectorDistance } from '../type/index.js';
2
2
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
3
3
  /**
4
4
  * Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
@@ -21,25 +21,43 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
21
21
  readonly alterColumnStrategy = "separate-clauses";
22
22
  readonly insertIdSource = "returning";
23
23
  readonly maxBindValues: number;
24
- readonly vectorOpsClass: ReadonlyMap<VectorDistance, string> | undefined;
24
+ /**
25
+ * Each metric's pgvector distance operator and the operator-class suffix its index takes, in one
26
+ * place so a dialect cannot end up with the operator but not the opclass. The key set is the single
27
+ * source of truth for which metrics the dialect supports at all: CockroachDB narrows it to three.
28
+ */
29
+ readonly vectorMetrics: ReadonlyMap<VectorDistance, {
30
+ op: string;
31
+ opsSuffix: string;
32
+ }>;
25
33
  normalizeValue(value: unknown): unknown;
34
+ /** pgvector's own index types; CockroachDB's native one widens this. */
35
+ protected isVectorIndex(index: IndexSchema): boolean;
36
+ protected indexAccessMethod(index: IndexSchema): string;
37
+ protected readonly indexFeatures: Set<IndexFeature>;
38
+ /**
39
+ * A vector index's operator class is named `{type}_{metric}_ops`: an index on a `halfvec` column
40
+ * needs `halfvec_cosine_ops`, and `vector_cosine_ops` there is rejected outright. An unsupported
41
+ * distance throws rather than being omitted, since a bare `USING hnsw ("embedding")` would build
42
+ * with the dialect's default metric instead of the one requested, with nothing signalling it.
43
+ * Everything else takes the operator class the entry declares, e.g. `jsonb_path_ops` for GIN.
44
+ */
45
+ protected indexColumnOpsClass(entry: IndexColumnSchema, index: IndexSchema): string;
46
+ protected indexInclude(index: IndexSchema): string;
47
+ protected indexTuning(index: IndexSchema): string;
26
48
  placeholder(index: number): string;
27
- insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void;
28
- upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
29
49
  /**
30
- * Shared `INSERT ... ON CONFLICT (...) DO UPDATE/NOTHING RETURNING ...` builder, assembling
31
- * everything upfront rather than handing back fragments, for the `$N`-placeholder Postgres-wire
32
- * dialects. `extraReturning` lets {@link PostgresDialect} append `(xmax = 0) AS "_created"` to
33
- * detect insert-vs-update; CockroachDB has no `xmax`/`ctid` system columns, so it uses the default
34
- * (empty) and `created` stays `undefined` in the result.
50
+ * The same statement as the base, binding the assignments into the main context instead of a second
51
+ * one: `$N` placeholders carry their own index, so the values need not be in statement order, and
52
+ * computing them first is what keeps `appendInsertValues`' `onInsert` fields out of the update set.
35
53
  */
36
- protected buildUpsertOnConflict<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[], extraReturning?: string): void;
54
+ upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[], extraReturning?: string): void;
37
55
  /**
38
- * Full-text search: `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes
39
- * free-form user input (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike
40
- * `to_tsquery`, which rejects anything unparseable - including a plain two-word search.
56
+ * `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
57
+ * (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `to_tsquery`, which
58
+ * rejects anything unparseable - including a plain two-word search.
41
59
  */
42
- compare<E>(ctx: QueryContext, entity: Type<E>, key: string, val: unknown, opts?: QueryComparisonOptions): void;
60
+ protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
43
61
  protected jsonAll(ctx: QueryContext, jsonField: string, value: unknown): string;
44
62
  protected jsonSize(ctx: QueryContext, jsonField: string, value: number | QuerySizeComparisonOps): string;
45
63
  /**
@@ -54,7 +72,11 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
54
72
  protected formatIn(ctx: QueryContext, values: unknown[], negate: boolean): string;
55
73
  protected numericCast(expr: string): string;
56
74
  protected appendJsonValue(ctx: QueryContext, value: unknown, type: JsonColumnType): void;
57
- protected appendVectorValue(ctx: QueryContext, value: readonly unknown[]): void;
75
+ /**
76
+ * pgvector needs the cast to pick the right type, and `sparsevec` needs its own literal: the dense
77
+ * `[1,0,2]` every other type takes is rejected as "invalid input syntax for type sparsevec".
78
+ */
79
+ protected appendVectorValue(ctx: QueryContext, value: readonly unknown[], field?: FieldOptions): void;
58
80
  /**
59
81
  * `create_if_missing => false` keeps an absent key (and a NULL column) untouched; `WITH
60
82
  * ORDINALITY` keeps the surviving elements in their original order.
@@ -70,13 +92,6 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
70
92
  * Helper to add a JSON value to context with appropriate stringification and cast.
71
93
  */
72
94
  private jsonVal;
73
- /**
74
- * pgvector distance operators. Not every dialect supports every metric - see
75
- * {@link CockroachDialect.vectorOpsClass} for which ones each dialect actually has; the
76
- * `vectorOpsClass` key set (checked in `appendVectorSort` below via `vectorOpsClass.get`)
77
- * is the single source of truth for that, not this map.
78
- */
79
- private static readonly VECTOR_OPS;
80
95
  /** Emit a pgvector-style distance expression: `"col" <op> $N::<vectorType>`. */
81
96
  protected appendVectorSort<E>(ctx: QueryContext, meta: EntityMeta<E>, key: string, search: QueryVectorSearch): void;
82
97
  }