uql-orm 0.65.1 → 0.67.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 (193) hide show
  1. package/dist/browser/querier/httpQuerier.js +1 -8
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/bunSql/bunSql.util.d.ts +2 -6
  4. package/dist/bunSql/bunSql.util.js +2 -6
  5. package/dist/bunSql/bunSqlQuerier.d.ts +2 -5
  6. package/dist/bunSql/bunSqlQuerier.js +2 -5
  7. package/dist/cockroachdb/cockroachDialect.d.ts +4 -13
  8. package/dist/cockroachdb/cockroachDialect.js +4 -13
  9. package/dist/context/context.browser.js +2 -10
  10. package/dist/context/context.d.ts +4 -17
  11. package/dist/context/context.js +4 -17
  12. package/dist/dialect/abstractDialect.d.ts +4 -19
  13. package/dist/dialect/abstractDialect.js +2 -20
  14. package/dist/dialect/abstractSqlDialect.d.ts +47 -212
  15. package/dist/dialect/abstractSqlDialect.js +68 -222
  16. package/dist/dialect/aliases.d.ts +2 -12
  17. package/dist/dialect/aliases.js +4 -12
  18. package/dist/dialect/hydrateColumn.d.ts +2 -6
  19. package/dist/dialect/hydrateColumn.js +3 -13
  20. package/dist/dialect/jsonArrayElemMatchUtils.d.ts +1 -7
  21. package/dist/dialect/jsonArrayElemMatchUtils.js +1 -7
  22. package/dist/dialect/jsonSql.d.ts +6 -27
  23. package/dist/dialect/jsonSql.js +6 -27
  24. package/dist/dialect/mergeSqlDialect.d.ts +4 -22
  25. package/dist/dialect/mergeSqlDialect.js +4 -22
  26. package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -37
  27. package/dist/dialect/mysqlLikeSqlDialect.js +35 -51
  28. package/dist/dialect/pgLikeSqlDialect.d.ts +8 -22
  29. package/dist/dialect/pgLikeSqlDialect.js +36 -39
  30. package/dist/dialect/queryContext.d.ts +4 -22
  31. package/dist/dialect/queryContext.js +4 -22
  32. package/dist/dialect/queryJoins.d.ts +3 -12
  33. package/dist/dialect/queryJoins.js +3 -12
  34. package/dist/dialect/vectorCast.d.ts +2 -12
  35. package/dist/dialect/vectorCast.js +3 -19
  36. package/dist/dialect/vectorSqlDialect.d.ts +8 -38
  37. package/dist/dialect/vectorSqlDialect.js +7 -38
  38. package/dist/entity/decorator/bag.d.ts +6 -19
  39. package/dist/entity/decorator/bag.js +6 -22
  40. package/dist/entity/decorator/entity.d.ts +5 -10
  41. package/dist/entity/decorator/entity.js +2 -7
  42. package/dist/entity/decorator/members.d.ts +10 -31
  43. package/dist/entity/decorator/members.js +3 -12
  44. package/dist/entity/metadata/definition.d.ts +5 -21
  45. package/dist/entity/metadata/definition.js +69 -91
  46. package/dist/http/handler.d.ts +2 -14
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +3 -1
  49. package/dist/libsql/libsqlDialect.d.ts +1 -8
  50. package/dist/libsql/libsqlDialect.js +1 -8
  51. package/dist/maria/mariaDialect.d.ts +3 -5
  52. package/dist/maria/mariaDialect.js +5 -5
  53. package/dist/maria/mariadbQuerier.js +2 -2
  54. package/dist/maria/mariadbQuerierPool.js +1 -6
  55. package/dist/migrate/builder/migrationBuilder.js +3 -19
  56. package/dist/migrate/builder/splitSqlStatements.d.ts +1 -14
  57. package/dist/migrate/builder/splitSqlStatements.js +2 -22
  58. package/dist/migrate/builder/types.d.ts +2 -15
  59. package/dist/migrate/cli-config.js +2 -11
  60. package/dist/migrate/cli.js +2 -7
  61. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -15
  62. package/dist/migrate/codegen/entityCodeGenerator.js +15 -44
  63. package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -8
  64. package/dist/migrate/codegen/fieldOptionsSource.js +3 -22
  65. package/dist/migrate/ddl/indexDdl.d.ts +2 -5
  66. package/dist/migrate/ddl/indexDdl.js +2 -5
  67. package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -13
  68. package/dist/migrate/ddl/pgIndexDdl.js +3 -13
  69. package/dist/migrate/generator/definitionToNode.d.ts +2 -9
  70. package/dist/migrate/generator/definitionToNode.js +3 -17
  71. package/dist/migrate/generator/indexNodeToSchema.d.ts +2 -3
  72. package/dist/migrate/generator/indexNodeToSchema.js +2 -3
  73. package/dist/migrate/generator/mongoCommand.d.ts +1 -8
  74. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -8
  75. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -8
  76. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +6 -26
  77. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +9 -41
  78. package/dist/migrate/introspection/baseSqlIntrospector.js +0 -1
  79. package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
  80. package/dist/migrate/introspection/mongoIntrospector.js +48 -46
  81. package/dist/migrate/introspection/mssqlIntrospector.d.ts +4 -4
  82. package/dist/migrate/introspection/mssqlIntrospector.js +18 -27
  83. package/dist/migrate/introspection/mysqlIntrospector.d.ts +7 -2
  84. package/dist/migrate/introspection/mysqlIntrospector.js +16 -14
  85. package/dist/migrate/introspection/postgresIntrospector.d.ts +24 -9
  86. package/dist/migrate/introspection/postgresIntrospector.js +68 -59
  87. package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -1
  88. package/dist/migrate/introspection/sqliteIntrospector.js +8 -10
  89. package/dist/migrate/migrator.d.ts +9 -53
  90. package/dist/migrate/migrator.js +32 -65
  91. package/dist/migrate/schemaGenerator.d.ts +20 -66
  92. package/dist/migrate/schemaGenerator.js +32 -93
  93. package/dist/mongo/mongoDialect.d.ts +21 -53
  94. package/dist/mongo/mongoDialect.js +25 -70
  95. package/dist/mongo/mongodbQuerier.d.ts +5 -8
  96. package/dist/mongo/mongodbQuerier.js +31 -65
  97. package/dist/mssql/mssqlDialect.d.ts +8 -34
  98. package/dist/mssql/mssqlDialect.js +37 -51
  99. package/dist/mssql/mssqlQuerier.d.ts +37 -4
  100. package/dist/mssql/mssqlQuerier.js +2 -2
  101. package/dist/mssql/mssqlWireTypes.d.ts +2 -14
  102. package/dist/mssql/mssqlWireTypes.js +2 -14
  103. package/dist/nestjs/uqlModule.js +2 -7
  104. package/dist/pglite/pgliteQuerier.d.ts +1 -9
  105. package/dist/pglite/pgliteQuerierPool.d.ts +4 -26
  106. package/dist/pglite/pgliteQuerierPool.js +3 -18
  107. package/dist/postgres/abstractPgQuerierPool.d.ts +1 -8
  108. package/dist/postgres/abstractPgQuerierPool.js +1 -8
  109. package/dist/postgres/pgNumericTypes.d.ts +3 -26
  110. package/dist/postgres/pgNumericTypes.js +3 -26
  111. package/dist/postgres/postgresDialect.d.ts +4 -10
  112. package/dist/postgres/postgresDialect.js +4 -10
  113. package/dist/querier/abstractQuerier.d.ts +35 -103
  114. package/dist/querier/abstractQuerier.js +105 -201
  115. package/dist/querier/abstractSharedHandleQuerierPool.d.ts +3 -17
  116. package/dist/querier/abstractSharedHandleQuerierPool.js +3 -17
  117. package/dist/querier/abstractSqlQuerier.d.ts +15 -36
  118. package/dist/querier/abstractSqlQuerier.js +49 -131
  119. package/dist/schema/canonicalType.d.ts +3 -21
  120. package/dist/schema/canonicalType.js +22 -67
  121. package/dist/schema/dependencyGraph.d.ts +2 -8
  122. package/dist/schema/dependencyGraph.js +2 -32
  123. package/dist/schema/index.d.ts +1 -25
  124. package/dist/schema/index.js +0 -26
  125. package/dist/schema/indexColumns.d.ts +1 -8
  126. package/dist/schema/indexColumns.js +1 -8
  127. package/dist/schema/indexDifferences.d.ts +7 -40
  128. package/dist/schema/indexDifferences.js +6 -31
  129. package/dist/schema/schemaAST.d.ts +8 -175
  130. package/dist/schema/schemaAST.js +13 -365
  131. package/dist/schema/schemaASTBuilder.d.ts +2 -24
  132. package/dist/schema/schemaASTBuilder.js +6 -41
  133. package/dist/schema/schemaASTDiffer.d.ts +6 -46
  134. package/dist/schema/schemaASTDiffer.js +8 -56
  135. package/dist/schema/types.d.ts +5 -61
  136. package/dist/schema/types.js +3 -6
  137. package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -8
  138. package/dist/sqlite/localSqliteQuerierPool.d.ts +1 -7
  139. package/dist/sqlite/localSqliteQuerierPool.js +1 -7
  140. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +2 -7
  141. package/dist/sqlite/nodeSqliteQuerierPool.js +2 -7
  142. package/dist/sqlite/sqliteDialect.d.ts +5 -20
  143. package/dist/sqlite/sqliteDialect.js +29 -35
  144. package/dist/turso/tursoDialect.d.ts +4 -6
  145. package/dist/turso/tursoDialect.js +4 -6
  146. package/dist/turso/tursoLocalQuerierPool.d.ts +1 -7
  147. package/dist/turso/tursoLocalQuerierPool.js +1 -7
  148. package/dist/turso/tursoQuerierPool.d.ts +2 -6
  149. package/dist/turso/tursoQuerierPool.js +2 -6
  150. package/dist/turso/tursoSessionQuerier.d.ts +1 -7
  151. package/dist/turso/tursoSessionQuerier.js +1 -7
  152. package/dist/type/dialect.d.ts +42 -94
  153. package/dist/type/dialect.js +3 -13
  154. package/dist/type/entity.d.ts +189 -551
  155. package/dist/type/entity.js +26 -9
  156. package/dist/type/logger.d.ts +2 -14
  157. package/dist/type/migration.d.ts +9 -38
  158. package/dist/type/querier.d.ts +9 -28
  159. package/dist/type/querierPool.d.ts +4 -26
  160. package/dist/type/query.d.ts +28 -78
  161. package/dist/type/query.js +2 -7
  162. package/dist/type/queryAggregate.d.ts +18 -98
  163. package/dist/type/queryRaw.d.ts +1 -8
  164. package/dist/type/queryRaw.js +1 -8
  165. package/dist/type/queryWhere.d.ts +13 -61
  166. package/dist/type/universalQuerier.d.ts +18 -105
  167. package/dist/type/utility.d.ts +12 -24
  168. package/dist/type/vector.d.ts +8 -38
  169. package/dist/type/vector.js +1 -1
  170. package/dist/type/wire.d.ts +2 -5
  171. package/dist/util/dialect.util.d.ts +9 -27
  172. package/dist/util/dialect.util.js +10 -27
  173. package/dist/util/field.util.d.ts +5 -37
  174. package/dist/util/field.util.js +7 -50
  175. package/dist/util/fieldOption.util.d.ts +7 -15
  176. package/dist/util/fieldOption.util.js +1 -1
  177. package/dist/util/filters.util.d.ts +2 -5
  178. package/dist/util/filters.util.js +2 -5
  179. package/dist/util/logger.d.ts +2 -6
  180. package/dist/util/logger.js +2 -6
  181. package/dist/util/object.util.d.ts +2 -6
  182. package/dist/util/object.util.js +1 -5
  183. package/dist/util/raw.d.ts +3 -23
  184. package/dist/util/relationQuery.util.d.ts +3 -14
  185. package/dist/util/relationQuery.util.js +3 -14
  186. package/dist/util/rowKey.util.d.ts +2 -10
  187. package/dist/util/rowKey.util.js +2 -10
  188. package/dist/util/sql.util.d.ts +6 -37
  189. package/dist/util/sql.util.js +13 -73
  190. package/dist/util/sqlLiteral.d.ts +2 -13
  191. package/dist/util/sqlLiteral.js +8 -13
  192. package/dist/util/string.util.js +0 -2
  193. package/package.json +4 -4
@@ -1,4 +1,5 @@
1
- import { SchemaAST } from '../../schema/schemaAST.js';
1
+ import { createTableNode, SchemaAST } from '../../schema/schemaAST.js';
2
+ import { isMongoQuerier, } from '../../type/index.js';
2
3
  /**
3
4
  * MongoDB schema introspector.
4
5
  * MongoDB doesn't have a fixed schema, so this primarily focuses on collections and indexes.
@@ -16,55 +17,22 @@ export class MongoSchemaIntrospector {
16
17
  for (const name of tableNames) {
17
18
  const schema = await this.getTableSchema(name);
18
19
  if (schema) {
19
- const columns = new Map();
20
- const table = {
21
- name,
22
- columns,
23
- primaryKey: [],
24
- indexes: [],
25
- incomingRelations: [],
26
- outgoingRelations: [],
27
- };
28
- if (schema.indexes) {
29
- for (const idx of schema.indexes) {
30
- for (const { column: colName } of idx.entries) {
31
- let column = columns.get(colName);
32
- if (!column) {
33
- column = {
34
- name: colName,
35
- type: { category: 'string' }, // MongoDB fields are flexible, but indexes usually target strings/numbers
36
- nullable: true,
37
- isPrimaryKey: false,
38
- isAutoIncrement: false,
39
- isUnique: false,
40
- table,
41
- referencedBy: [],
42
- };
43
- columns.set(colName, column);
44
- }
45
- }
46
- table.indexes.push({ name: idx.name, table, entries: idx.entries, unique: idx.unique });
47
- }
48
- }
49
- ast.addTable(table);
20
+ ast.addTable(buildTable(schema));
50
21
  }
51
22
  }
52
23
  return ast;
53
24
  }
54
25
  async getTableSchema(tableName) {
55
- return this.pool.withQuerier(async (querier) => {
56
- const { db } = querier;
57
- const collections = await db.listCollections({ name: tableName }).toArray();
58
- if (collections.length === 0) {
26
+ return this.withDb(async (db) => {
27
+ if (!(await hasCollection(db, tableName))) {
59
28
  return undefined;
60
29
  }
61
- // MongoDB doesn't have a fixed schema, but we can look at the indexes. Annotated rather than
62
- // inferred: the driver's `indexes()` is overloaded and resolves to `any` on some versions, which
63
- // silently made every field below unchecked.
30
+ // Annotated rather than inferred: the driver's `indexes()` is overloaded and resolves to `any` on
31
+ // some versions, which silently made every field below unchecked.
64
32
  const indexes = await db.collection(tableName).indexes();
65
33
  return {
66
34
  name: tableName,
67
- columns: [], // We don't have columns in Mongo
35
+ columns: [],
68
36
  indexes: indexes.map((idx) => ({
69
37
  name: idx.name ?? Object.keys(idx.key).join('_'),
70
38
  entries: Object.keys(idx.key).map((column) => ({ column })),
@@ -73,15 +41,49 @@ export class MongoSchemaIntrospector {
73
41
  };
74
42
  });
75
43
  }
44
+ /** Collections only, the way a SQL engine lists its base tables: no view, nor the `system.views` behind one. */
76
45
  async getTableNames() {
77
- return this.pool.withQuerier(async (querier) => {
78
- const { db } = querier;
79
- const collections = await db.listCollections().toArray();
80
- return collections.map((c) => c.name);
46
+ return this.withDb(async (db) => {
47
+ const filter = { type: 'collection', name: { $not: /^system\./ } };
48
+ const collections = await db.listCollections(filter, { nameOnly: true }).toArray();
49
+ return collections.map((collection) => collection.name);
81
50
  });
82
51
  }
83
52
  async tableExists(tableName) {
84
- const names = await this.getTableNames();
85
- return names.includes(tableName);
53
+ return this.withDb((db) => hasCollection(db, tableName));
86
54
  }
55
+ withDb(task) {
56
+ return this.pool.withQuerier((querier) => {
57
+ if (!isMongoQuerier(querier)) {
58
+ throw new TypeError('MongoSchemaIntrospector requires a MongoDB querier');
59
+ }
60
+ return task(querier.db);
61
+ });
62
+ }
63
+ }
64
+ async function hasCollection(db, name) {
65
+ const collections = await db.listCollections({ name, type: 'collection' }, { nameOnly: true }).toArray();
66
+ return collections.length > 0;
67
+ }
68
+ /** Mongo has no columns to read, so a table's are the fields its indexes name, one node per field. */
69
+ function buildTable({ name, indexes = [] }) {
70
+ const table = createTableNode(name);
71
+ for (const index of indexes) {
72
+ for (const { column } of index.entries) {
73
+ if (!table.columns.has(column)) {
74
+ table.columns.set(column, {
75
+ name: column,
76
+ type: { category: 'string' },
77
+ nullable: true,
78
+ isPrimaryKey: false,
79
+ isAutoIncrement: false,
80
+ isUnique: false,
81
+ table,
82
+ referencedBy: [],
83
+ });
84
+ }
85
+ }
86
+ table.indexes.push({ name: index.name, table, entries: index.entries, unique: index.unique });
87
+ }
88
+ return table;
87
89
  }
@@ -12,8 +12,8 @@ export declare class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
12
12
  protected readonly defaultSchemaExpr = "SCHEMA_NAME()";
13
13
  protected getTableNamesQuery(): string;
14
14
  protected tableExistsQuery(): string;
15
- protected parseTableExistsResult(results: {
16
- count?: number;
15
+ protected parseTableExistsResult([row]: {
16
+ count: number;
17
17
  }[]): boolean;
18
18
  /**
19
19
  * From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of
@@ -40,8 +40,8 @@ export declare class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
40
40
  update_rule: string;
41
41
  }[]): Promise<ForeignKeySchema[]>;
42
42
  /**
43
- * A default is stored wrapped in at least one layer of parentheses - `((0))` for a number,
44
- * `(N'x')` for a string - because the engine reprints it from its own parse tree.
43
+ * The engine reprints a default from its own parse tree: all of it in one pair of parentheses, and a
44
+ * number in a second, so `((0))`, `((1)+(2))`, `(N'x')` and `(getdate())`.
45
45
  */
46
46
  protected parseDefaultValue(defaultValue: string | null): unknown;
47
47
  }
@@ -26,9 +26,8 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
26
26
  AND TABLE_NAME = @p1
27
27
  `;
28
28
  }
29
- parseTableExistsResult(results) {
30
- // No row, or no count in it, reads as `NaN`, which is not above zero.
31
- return Number(results[0]?.count) > 0;
29
+ parseTableExistsResult([row]) {
30
+ return row.count > 0;
32
31
  }
33
32
  /**
34
33
  * From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of
@@ -142,46 +141,38 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
142
141
  async mapIndexesResult(_read, _tableName, results) {
143
142
  return results.map((row) => ({
144
143
  name: row.index_name,
145
- entries: (row.columns ?? '').split(',').map((column) => ({ column })),
144
+ entries: row.columns.split(',').map((column) => ({ column })),
146
145
  unique: Boolean(row.is_unique),
147
146
  }));
148
147
  }
149
148
  async mapForeignKeysResult(_read, _tableName, results) {
150
149
  return results.map((row) => ({
151
150
  name: row.constraint_name,
152
- columns: (row.columns || '').split(','),
153
- references: { table: row.referenced_table, columns: (row.referenced_columns || '').split(',') },
151
+ columns: row.columns.split(','),
152
+ references: { table: row.referenced_table, columns: row.referenced_columns.split(',') },
154
153
  // `sys` spells them with an underscore: `SET_NULL`, `NO_ACTION`.
155
- onDelete: this.normalizeReferentialAction((row.delete_rule || '').replaceAll('_', ' ')),
156
- onUpdate: this.normalizeReferentialAction((row.update_rule || '').replaceAll('_', ' ')),
154
+ onDelete: this.normalizeReferentialAction(row.delete_rule.replaceAll('_', ' ')),
155
+ onUpdate: this.normalizeReferentialAction(row.update_rule.replaceAll('_', ' ')),
157
156
  }));
158
157
  }
159
158
  /**
160
- * A default is stored wrapped in at least one layer of parentheses - `((0))` for a number,
161
- * `(N'x')` for a string - because the engine reprints it from its own parse tree.
159
+ * The engine reprints a default from its own parse tree: all of it in one pair of parentheses, and a
160
+ * number in a second, so `((0))`, `((1)+(2))`, `(N'x')` and `(getdate())`.
162
161
  */
163
162
  parseDefaultValue(defaultValue) {
164
- if (defaultValue === null || defaultValue === undefined) {
163
+ if (defaultValue === null) {
165
164
  return undefined;
166
165
  }
167
- let text = defaultValue.trim();
168
- while (text.startsWith('(') && text.endsWith(')')) {
169
- text = text.slice(1, -1).trim();
166
+ const text = defaultValue.slice(1, -1);
167
+ const number = /^\((-?\d+(?:\.\d+)?)\)$/.exec(text);
168
+ if (number) {
169
+ return Number(number[1]);
170
170
  }
171
- if (text.toUpperCase() === 'NULL') {
172
- return null;
171
+ const quoted = /^N?'(.*)'$/s.exec(text);
172
+ if (quoted) {
173
+ return quoted[1].replaceAll("''", "'");
173
174
  }
174
- const unicode = text.startsWith("N'") ? text.slice(1) : text;
175
- if (unicode.startsWith("'") && unicode.endsWith("'")) {
176
- return unicode.slice(1, -1).replaceAll("''", "'");
177
- }
178
- if (/^-?\d+$/.test(text)) {
179
- return Number.parseInt(text, 10);
180
- }
181
- if (/^-?\d+\.\d+$/.test(text)) {
182
- return Number.parseFloat(text);
183
- }
184
- return text;
175
+ return text === 'NULL' ? null : text;
185
176
  }
186
177
  }
187
178
  /** The types whose `max_length` is a width rather than a fixed storage size. */
@@ -8,8 +8,8 @@ export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
8
8
  protected readonly defaultSchemaExpr = "DATABASE()";
9
9
  protected getTableNamesQuery(): string;
10
10
  protected tableExistsQuery(): string;
11
- protected parseTableExistsResult(results: {
12
- count?: number | bigint;
11
+ protected parseTableExistsResult([row]: {
12
+ count: number | bigint;
13
13
  }[]): boolean;
14
14
  protected getColumnsQuery(_tableName: string): string;
15
15
  protected getIndexesQuery(_tableName: string): string;
@@ -29,6 +29,11 @@ export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
29
29
  delete_rule: string;
30
30
  update_rule: string;
31
31
  }[]): Promise<ForeignKeySchema[]>;
32
+ /**
33
+ * MariaDB prints a string default as the literal it is (`'it''s'`). MySQL prints one bare, save an
34
+ * expression default (`DEFAULT ('x')`, what a `TEXT` column takes), which comes with a charset
35
+ * introducer and every quote and backslash escaped once more: `_utf8mb4\'x\'`.
36
+ */
32
37
  protected parseDefaultValue(defaultValue: string | null): unknown;
33
38
  }
34
39
  type MysqlColumnRow = {
@@ -1,3 +1,4 @@
1
+ import { unescapeMysqlString } from '../../util/sqlLiteral.js';
1
2
  import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.js';
2
3
  /**
3
4
  * MySQL/MariaDB schema introspector.
@@ -23,9 +24,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
23
24
  AND TABLE_NAME = ?
24
25
  `;
25
26
  }
26
- parseTableExistsResult(results) {
27
- // No row, or no count in it, reads as `NaN`, which is not above zero.
28
- return Number(results[0]?.count) > 0;
27
+ parseTableExistsResult([row]) {
28
+ return Number(row.count) > 0;
29
29
  }
30
30
  getColumnsQuery(_tableName) {
31
31
  return /*sql*/ `
@@ -98,7 +98,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
98
98
  nullable: row.is_nullable === 'YES',
99
99
  defaultValue: this.parseDefaultValue(row.column_default),
100
100
  isPrimaryKey: row.column_key === 'PRI',
101
- isAutoIncrement: (row.extra || '').toLowerCase().includes('auto_increment'),
101
+ isAutoIncrement: row.extra.toLowerCase().includes('auto_increment'),
102
102
  isUnique: row.column_key === 'UNI',
103
103
  length: this.toNumber(row.character_maximum_length),
104
104
  precision: this.toNumber(row.numeric_precision),
@@ -112,7 +112,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
112
112
  // A functional or multi-valued key part has no `COLUMN_NAME` - the `COALESCE` above keeps its
113
113
  // place in the list, and it is reported as the expression it is, which is what stops diffing
114
114
  // from comparing an entry list the server cannot state against the entity's own.
115
- entries: (row.columns ?? '').split(',').map((column) => (column ? { column } : { column, expression: true })),
115
+ entries: row.columns.split(',').map((column) => (column ? { column } : { column, expression: true })),
116
116
  unique: Boolean(row.is_unique),
117
117
  }));
118
118
  }
@@ -125,6 +125,11 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
125
125
  onUpdate: this.normalizeReferentialAction(row.update_rule),
126
126
  }));
127
127
  }
128
+ /**
129
+ * MariaDB prints a string default as the literal it is (`'it''s'`). MySQL prints one bare, save an
130
+ * expression default (`DEFAULT ('x')`, what a `TEXT` column takes), which comes with a charset
131
+ * introducer and every quote and backslash escaped once more: `_utf8mb4\'x\'`.
132
+ */
128
133
  parseDefaultValue(defaultValue) {
129
134
  if (defaultValue === null) {
130
135
  return undefined;
@@ -136,16 +141,13 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
136
141
  if (normalized === 'CURRENT_TIMESTAMP' || normalized === 'CURRENT_TIMESTAMP()') {
137
142
  return 'CURRENT_TIMESTAMP';
138
143
  }
139
- if (/^-?\d+$/.test(defaultValue)) {
140
- return Number.parseInt(defaultValue, 10);
144
+ if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
145
+ return Number(defaultValue);
141
146
  }
142
- if (/^-?\d+\.\d+$/.test(defaultValue)) {
143
- return Number.parseFloat(defaultValue);
144
- }
145
- if (defaultValue?.startsWith("'") && defaultValue?.endsWith("'")) {
146
- return defaultValue.slice(1, -1);
147
- }
148
- return defaultValue;
147
+ const introduced = /^_\w+(\\'.*\\')$/s.exec(defaultValue);
148
+ const literal = introduced ? unescapeMysqlString(introduced[1]) : defaultValue;
149
+ const quoted = /^'(.*)'$/s.exec(literal);
150
+ return quoted ? unescapeMysqlString(quoted[1]) : literal;
149
151
  }
150
152
  }
151
153
  /**
@@ -13,7 +13,7 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
13
13
  readonly indexFacets: ReadonlySet<IndexFacet>;
14
14
  protected getTableNamesQuery(): string;
15
15
  protected tableExistsQuery(): string;
16
- protected parseTableExistsResult(results: RawRow[]): boolean;
16
+ protected parseTableExistsResult([row]: RawRow[]): boolean;
17
17
  protected getColumnsQuery(_tableName: string): string;
18
18
  /**
19
19
  * `attname` where the entry is a column, `pg_get_indexdef` for that one position where it is an
@@ -36,22 +36,29 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
36
36
  * compare. This one decides which indexes are reported at all.
37
37
  */
38
38
  protected readonly constraintIndexTypes: readonly string[];
39
+ /** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
39
40
  protected getForeignKeysQuery(_tableName: string): string;
40
41
  protected getPrimaryKeyQuery(_tableName: string): string;
41
42
  protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: PostgresColumnRow[]): Promise<ColumnSchema[]>;
42
43
  protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: PostgresIndexRow[]): Promise<IndexSchema[]>;
43
- protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: {
44
- constraint_name: string;
45
- columns: string[];
46
- referenced_table: string;
47
- referenced_columns: string[];
48
- delete_rule: string;
49
- update_rule: string;
50
- }[]): Promise<ForeignKeySchema[]>;
44
+ protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: PostgresForeignKeyRow[]): Promise<ForeignKeySchema[]>;
51
45
  protected normalizeType(dataType: string, udtName: string): string;
46
+ /**
47
+ * Postgres quotes a negative number (`'-3'::integer`); CockroachDB parenthesizes one (`(-3)`) and
48
+ * writes a quote-bearing string in escape syntax (`e'it\'s'`). Any other cast is dropped, and a
49
+ * function call (`now()`, `nextval(...)`) is returned as written.
50
+ */
52
51
  protected parseDefaultValue(defaultValue: string | null): unknown;
53
52
  protected isAutoIncrement(columnDefault: string | null, isIdentity: string): boolean;
54
53
  }
54
+ /** `pg_constraint`'s one-letter spelling of each action. */
55
+ declare const FOREIGN_KEY_ACTION_CODES: {
56
+ readonly a: 'NO ACTION';
57
+ readonly r: 'RESTRICT';
58
+ readonly c: 'CASCADE';
59
+ readonly n: 'SET NULL';
60
+ readonly d: 'SET DEFAULT';
61
+ };
55
62
  /**
56
63
  * CockroachDB answers the same catalogue queries and differs only in what it can express: v26.2.5
57
64
  * still rejects `NULLS FIRST/LAST` and operator classes as "unimplemented", and it sorts nulls first
@@ -69,6 +76,14 @@ export declare class CockroachSchemaIntrospector extends PostgresSchemaIntrospec
69
76
  */
70
77
  protected readonly constraintIndexTypes: readonly string[];
71
78
  }
79
+ type PostgresForeignKeyRow = {
80
+ constraint_name: string;
81
+ columns: string[];
82
+ referenced_table: string;
83
+ referenced_columns: string[];
84
+ delete_rule: keyof typeof FOREIGN_KEY_ACTION_CODES;
85
+ update_rule: keyof typeof FOREIGN_KEY_ACTION_CODES;
86
+ };
72
87
  /** One entry of one index; what the index itself is repeats across its rows. */
73
88
  type PostgresIndexRow = {
74
89
  index_name: string;
@@ -34,8 +34,8 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
34
34
  ) AS exists
35
35
  `;
36
36
  }
37
- parseTableExistsResult(results) {
38
- return results[0]?.['exists'] ?? false;
37
+ parseTableExistsResult([row]) {
38
+ return row['exists'] === true;
39
39
  }
40
40
  getColumnsQuery(_tableName) {
41
41
  return /*sql*/ `
@@ -50,28 +50,25 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
50
50
  c.numeric_scale,
51
51
  c.is_identity,
52
52
  c.identity_generation,
53
- COALESCE(
54
- (SELECT TRUE FROM information_schema.table_constraints tc
55
- JOIN information_schema.key_column_usage kcu
56
- ON tc.constraint_name = kcu.constraint_name
57
- WHERE tc.table_name = c.table_name
58
- AND tc.constraint_type = 'PRIMARY KEY'
59
- AND kcu.column_name = c.column_name
60
- LIMIT 1),
61
- FALSE
53
+ EXISTS (
54
+ SELECT 1 FROM information_schema.table_constraints tc
55
+ JOIN information_schema.key_column_usage kcu USING (constraint_schema, constraint_name)
56
+ WHERE tc.table_schema = c.table_schema
57
+ AND tc.table_name = c.table_name
58
+ AND tc.constraint_type = 'PRIMARY KEY'
59
+ AND kcu.column_name = c.column_name
62
60
  ) AS is_primary_key,
63
- COALESCE(
64
- (SELECT TRUE FROM information_schema.table_constraints tc
65
- JOIN information_schema.key_column_usage kcu
66
- ON tc.constraint_name = kcu.constraint_name
67
- WHERE tc.table_name = c.table_name
68
- AND tc.constraint_type = 'UNIQUE'
69
- AND kcu.column_name = c.column_name
70
- LIMIT 1),
71
- FALSE
61
+ EXISTS (
62
+ SELECT 1 FROM information_schema.table_constraints tc
63
+ JOIN information_schema.key_column_usage kcu USING (constraint_schema, constraint_name)
64
+ WHERE tc.table_schema = c.table_schema
65
+ AND tc.table_name = c.table_name
66
+ AND tc.constraint_type = 'UNIQUE'
67
+ GROUP BY tc.constraint_name
68
+ HAVING COUNT(*) = 1 AND MIN(kcu.column_name) = c.column_name
72
69
  ) AS is_unique,
73
70
  pg_catalog.col_description(
74
- (SELECT oid FROM pg_catalog.pg_class WHERE relname = c.table_name),
71
+ (quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))::regclass,
75
72
  c.ordinal_position
76
73
  ) AS column_comment
77
74
  FROM information_schema.columns c
@@ -113,7 +110,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
113
110
  LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum AND k.attnum > 0
114
111
  LEFT JOIN pg_opclass op ON op.oid = ix.indclass[k.n - 1]
115
112
  WHERE t.relname = $1
116
- AND n.nspname = 'public'
113
+ AND n.nspname = ${this.schemaExpr}
117
114
  AND NOT ix.indisprimary
118
115
  AND NOT EXISTS (
119
116
  SELECT 1 FROM pg_constraint con
@@ -132,30 +129,29 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
132
129
  * compare. This one decides which indexes are reported at all.
133
130
  */
134
131
  constraintIndexTypes = ['p', 'u', 'x'];
132
+ /** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
135
133
  getForeignKeysQuery(_tableName) {
134
+ const columnsOf = (keys, table) => /*sql*/ `ARRAY_TO_JSON(ARRAY(
135
+ SELECT a.attname FROM UNNEST(${keys}) WITH ORDINALITY AS k(attnum, n)
136
+ JOIN pg_attribute a ON a.attrelid = ${table} AND a.attnum = k.attnum
137
+ ORDER BY k.n
138
+ ))`;
136
139
  return /*sql*/ `
137
140
  SELECT
138
- tc.constraint_name,
139
- ARRAY_TO_JSON(ARRAY_AGG(kcu.column_name ORDER BY kcu.ordinal_position)) AS columns,
140
- ccu.table_name AS referenced_table,
141
- ARRAY_TO_JSON(ARRAY_AGG(ccu.column_name ORDER BY kcu.ordinal_position)) AS referenced_columns,
142
- rc.delete_rule,
143
- rc.update_rule
144
- FROM information_schema.table_constraints tc
145
- JOIN information_schema.key_column_usage kcu
146
- ON tc.constraint_name = kcu.constraint_name
147
- AND tc.table_schema = kcu.table_schema
148
- JOIN information_schema.constraint_column_usage ccu
149
- ON ccu.constraint_name = tc.constraint_name
150
- AND ccu.table_schema = tc.table_schema
151
- JOIN information_schema.referential_constraints rc
152
- ON rc.constraint_name = tc.constraint_name
153
- AND rc.constraint_schema = tc.table_schema
154
- WHERE tc.constraint_type = 'FOREIGN KEY'
155
- AND tc.table_name = $1
156
- AND tc.table_schema = ${this.schemaExpr}
157
- GROUP BY tc.constraint_name, ccu.table_name, rc.delete_rule, rc.update_rule
158
- ORDER BY tc.constraint_name
141
+ con.conname AS constraint_name,
142
+ ${columnsOf('con.conkey', 'con.conrelid')} AS columns,
143
+ ref.relname AS referenced_table,
144
+ ${columnsOf('con.confkey', 'con.confrelid')} AS referenced_columns,
145
+ con.confdeltype AS delete_rule,
146
+ con.confupdtype AS update_rule
147
+ FROM pg_constraint con
148
+ JOIN pg_class t ON t.oid = con.conrelid
149
+ JOIN pg_namespace n ON n.oid = t.relnamespace
150
+ JOIN pg_class ref ON ref.oid = con.confrelid
151
+ WHERE con.contype = 'f'
152
+ AND t.relname = $1
153
+ AND n.nspname = ${this.schemaExpr}
154
+ ORDER BY con.conname
159
155
  `;
160
156
  }
161
157
  getPrimaryKeyQuery(_tableName) {
@@ -205,8 +201,8 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
205
201
  name: row.constraint_name,
206
202
  columns: row.columns,
207
203
  references: { table: row.referenced_table, columns: row.referenced_columns },
208
- onDelete: this.normalizeReferentialAction(row.delete_rule),
209
- onUpdate: this.normalizeReferentialAction(row.update_rule),
204
+ onDelete: FOREIGN_KEY_ACTION_CODES[row.delete_rule],
205
+ onUpdate: FOREIGN_KEY_ACTION_CODES[row.update_rule],
210
206
  }));
211
207
  }
212
208
  normalizeType(dataType, udtName) {
@@ -219,29 +215,32 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
219
215
  }
220
216
  return dataType.toUpperCase();
221
217
  }
218
+ /**
219
+ * Postgres quotes a negative number (`'-3'::integer`); CockroachDB parenthesizes one (`(-3)`) and
220
+ * writes a quote-bearing string in escape syntax (`e'it\'s'`). Any other cast is dropped, and a
221
+ * function call (`now()`, `nextval(...)`) is returned as written.
222
+ */
222
223
  parseDefaultValue(defaultValue) {
223
224
  if (!defaultValue) {
224
225
  return undefined;
225
226
  }
226
- // Remove type casting (e.g., ::text, ::character varying, ::text[])
227
227
  const cleaned = defaultValue.replace(/::[a-z_]+(\s+[a-z_]+)?(\[\])?/gi, '').trim();
228
- if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
229
- return cleaned.slice(1, -1);
228
+ const number = NUMBER_DEFAULT.exec(cleaned) ?? QUOTED_NUMBER_DEFAULT.exec(defaultValue);
229
+ if (number) {
230
+ return Number(number[1]);
230
231
  }
231
- if (cleaned === 'true' || cleaned === 'false') {
232
- return cleaned === 'true';
232
+ const quoted = /^'(.*)'$/s.exec(cleaned);
233
+ if (quoted) {
234
+ return quoted[1].replaceAll("''", "'");
233
235
  }
234
- if (cleaned === 'NULL') {
235
- return null;
236
+ const escaped = /^e'(.*)'$/s.exec(cleaned);
237
+ if (escaped) {
238
+ return escaped[1].replace(/\\(.)/gs, '$1');
236
239
  }
237
- if (/^-?\d+$/.test(cleaned)) {
238
- return Number.parseInt(cleaned, 10);
239
- }
240
- if (/^-?\d+\.\d+$/.test(cleaned)) {
241
- return Number.parseFloat(cleaned);
240
+ if (cleaned === 'true' || cleaned === 'false') {
241
+ return cleaned === 'true';
242
242
  }
243
- // Return cleaned value for functions like CURRENT_TIMESTAMP, nextval(), etc.
244
- return cleaned;
243
+ return cleaned === 'NULL' ? null : cleaned;
245
244
  }
246
245
  isAutoIncrement(columnDefault, isIdentity) {
247
246
  // PostgreSQL identity columns (GENERATED ... AS IDENTITY)
@@ -252,6 +251,16 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
252
251
  return columnDefault?.includes('nextval(') ?? false;
253
252
  }
254
253
  }
254
+ /** `pg_constraint`'s one-letter spelling of each action. */
255
+ const FOREIGN_KEY_ACTION_CODES = {
256
+ a: 'NO ACTION',
257
+ r: 'RESTRICT',
258
+ c: 'CASCADE',
259
+ n: 'SET NULL',
260
+ d: 'SET DEFAULT',
261
+ };
262
+ const NUMBER_DEFAULT = /^\(?(-?\d+(?:\.\d+)?)\)?$/;
263
+ const QUOTED_NUMBER_DEFAULT = /^'(-?\d+(?:\.\d+)?)'::(?:smallint|integer|bigint|numeric|real|double precision)$/;
255
264
  /**
256
265
  * Postgres states every entry in full: a plain column still reports `order: 'asc'`, and only a
257
266
  * non-default operator class is named. The diff defaults the entity side to match, so an option
@@ -6,7 +6,7 @@ import { AbstractSqlSchemaIntrospector, type TableRowReader } from './abstractSq
6
6
  export declare class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
7
7
  protected getTableNamesQuery(): string;
8
8
  protected tableExistsQuery(): string;
9
- protected parseTableExistsResult(results: SqliteCountRow[]): boolean;
9
+ protected parseTableExistsResult([row]: SqliteCountRow[]): boolean;
10
10
  /**
11
11
  * `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
12
12
  * one read back without it and every sync offered to add a column that was already there - which
@@ -21,9 +21,8 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
21
21
  AND name = ?
22
22
  `;
23
23
  }
24
- parseTableExistsResult(results) {
25
- // No row, or no count in it, reads as `NaN`, which is not above zero.
26
- return Number(results[0]?.count) > 0;
24
+ parseTableExistsResult([row]) {
25
+ return Number(row.count) > 0;
27
26
  }
28
27
  /**
29
28
  * `table_xinfo`, not `table_info`: the latter omits generated columns entirely, so a table carrying
@@ -59,15 +58,16 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
59
58
  return row.name;
60
59
  }
61
60
  async mapColumnsResult(read, tableName, results) {
62
- // Get unique columns from indexes
63
61
  const uniqueColumns = await this.getUniqueColumns(read, tableName);
62
+ // Only a sole `INTEGER PRIMARY KEY` is the rowid, which is what numbers itself.
63
+ const soleKey = results.filter((row) => row.pk > 0).length === 1;
64
64
  return results.map((row) => ({
65
65
  name: row.name,
66
66
  type: this.normalizeType(row.type),
67
67
  nullable: row.notnull === 0,
68
68
  defaultValue: this.parseDefaultValue(row.dflt_value),
69
69
  isPrimaryKey: row.pk > 0,
70
- isAutoIncrement: row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
70
+ isAutoIncrement: soleKey && row.pk > 0 && row.type.toUpperCase() === 'INTEGER',
71
71
  isUnique: uniqueColumns.has(row.name),
72
72
  length: this.extractLength(row.type),
73
73
  precision: undefined,
@@ -130,8 +130,9 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
130
130
  async getUniqueColumns(read, tableName) {
131
131
  const indexes = await read(this.getIndexesQuery(tableName));
132
132
  const uniqueColumns = new Set();
133
+ // The key's own index is left out: a key column is unique already, which the entity side never states.
133
134
  for (const index of indexes) {
134
- if (index.unique) {
135
+ if (index.unique && index.origin !== 'pk') {
135
136
  const columns = await this.getIndexColumns(read, index.name);
136
137
  // Only single-column unique constraints, and only over a real column (not an expression)
137
138
  const [column] = columns;
@@ -165,14 +166,11 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
165
166
  return defaultValue;
166
167
  }
167
168
  if (/^'.*'$/.test(defaultValue)) {
168
- return defaultValue.slice(1, -1);
169
+ return defaultValue.slice(1, -1).replaceAll("''", "'");
169
170
  }
170
171
  if (/^-?\d+$/.test(defaultValue)) {
171
172
  return Number.parseInt(defaultValue, 10);
172
173
  }
173
- if (typeof defaultValue !== 'string') {
174
- return defaultValue;
175
- }
176
174
  if (/^-?\d+\.\d+$/.test(defaultValue)) {
177
175
  return Number.parseFloat(defaultValue);
178
176
  }