@typed-sql/mysql 2.0.0 → 2.1.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 (89) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +12 -0
  3. package/dist/packages/mysql/src/bulk.d.ts.map +1 -1
  4. package/dist/packages/mysql/src/bulk.js +9 -3
  5. package/dist/packages/mysql/src/bulk.js.map +1 -1
  6. package/dist/packages/mysql/src/capabilities.d.ts +30 -0
  7. package/dist/packages/mysql/src/capabilities.d.ts.map +1 -0
  8. package/dist/packages/mysql/src/capabilities.js +233 -0
  9. package/dist/packages/mysql/src/capabilities.js.map +1 -0
  10. package/dist/packages/mysql/src/catalog/generated/26.7.d.ts +4 -0
  11. package/dist/packages/mysql/src/catalog/generated/26.7.d.ts.map +1 -0
  12. package/dist/packages/mysql/src/catalog/generated/26.7.js +3 -0
  13. package/dist/packages/mysql/src/catalog/generated/26.7.js.map +1 -0
  14. package/dist/packages/mysql/src/catalog/generated/8.4.d.ts +4 -0
  15. package/dist/packages/mysql/src/catalog/generated/8.4.d.ts.map +1 -0
  16. package/dist/packages/mysql/src/catalog/generated/8.4.js +3 -0
  17. package/dist/packages/mysql/src/catalog/generated/8.4.js.map +1 -0
  18. package/dist/packages/mysql/src/catalog/generated/9.7.d.ts +4 -0
  19. package/dist/packages/mysql/src/catalog/generated/9.7.d.ts.map +1 -0
  20. package/dist/packages/mysql/src/catalog/generated/9.7.js +3 -0
  21. package/dist/packages/mysql/src/catalog/generated/9.7.js.map +1 -0
  22. package/dist/packages/mysql/src/catalog/index.d.ts +15 -0
  23. package/dist/packages/mysql/src/catalog/index.d.ts.map +1 -0
  24. package/dist/packages/mysql/src/catalog/index.js +129 -0
  25. package/dist/packages/mysql/src/catalog/index.js.map +1 -0
  26. package/dist/packages/mysql/src/catalog/types.d.ts +50 -0
  27. package/dist/packages/mysql/src/catalog/types.d.ts.map +1 -0
  28. package/dist/packages/mysql/src/catalog/types.js +2 -0
  29. package/dist/packages/mysql/src/catalog/types.js.map +1 -0
  30. package/dist/packages/mysql/src/index.d.ts +9 -1
  31. package/dist/packages/mysql/src/index.d.ts.map +1 -1
  32. package/dist/packages/mysql/src/index.js +37 -15
  33. package/dist/packages/mysql/src/index.js.map +1 -1
  34. package/dist/packages/mysql/src/mysql2.d.ts +9 -1
  35. package/dist/packages/mysql/src/mysql2.d.ts.map +1 -1
  36. package/dist/packages/mysql/src/mysql2.js +86 -3
  37. package/dist/packages/mysql/src/mysql2.js.map +1 -1
  38. package/dist/packages/mysql/src/parser/index.d.ts +8 -0
  39. package/dist/packages/mysql/src/parser/index.d.ts.map +1 -0
  40. package/dist/packages/mysql/src/parser/index.js +4 -0
  41. package/dist/packages/mysql/src/parser/index.js.map +1 -0
  42. package/dist/packages/mysql/src/parser/parser.d.ts +14 -0
  43. package/dist/packages/mysql/src/parser/parser.d.ts.map +1 -0
  44. package/dist/packages/mysql/src/parser/parser.js +1276 -0
  45. package/dist/packages/mysql/src/parser/parser.js.map +1 -0
  46. package/dist/packages/mysql/src/parser/tokenizer.d.ts +18 -0
  47. package/dist/packages/mysql/src/parser/tokenizer.d.ts.map +1 -0
  48. package/dist/packages/mysql/src/parser/tokenizer.js +385 -0
  49. package/dist/packages/mysql/src/parser/tokenizer.js.map +1 -0
  50. package/dist/packages/mysql/src/parser/types.d.ts +313 -0
  51. package/dist/packages/mysql/src/parser/types.d.ts.map +1 -0
  52. package/dist/packages/mysql/src/parser/types.js +9 -0
  53. package/dist/packages/mysql/src/parser/types.js.map +1 -0
  54. package/dist/packages/mysql/src/parser/walk.d.ts +13 -0
  55. package/dist/packages/mysql/src/parser/walk.d.ts.map +1 -0
  56. package/dist/packages/mysql/src/parser/walk.js +190 -0
  57. package/dist/packages/mysql/src/parser/walk.js.map +1 -0
  58. package/dist/packages/mysql/src/prepared.d.ts +6 -3
  59. package/dist/packages/mysql/src/prepared.d.ts.map +1 -1
  60. package/dist/packages/mysql/src/prepared.js +15 -14
  61. package/dist/packages/mysql/src/prepared.js.map +1 -1
  62. package/dist/packages/mysql/src/provider.d.ts +21 -5
  63. package/dist/packages/mysql/src/provider.d.ts.map +1 -1
  64. package/dist/packages/mysql/src/provider.js +647 -44
  65. package/dist/packages/mysql/src/provider.js.map +1 -1
  66. package/dist/packages/mysql/src/resolver.d.ts +5 -1
  67. package/dist/packages/mysql/src/resolver.d.ts.map +1 -1
  68. package/dist/packages/mysql/src/resolver.js +1207 -95
  69. package/dist/packages/mysql/src/resolver.js.map +1 -1
  70. package/dist/packages/mysql/src/routing.d.ts.map +1 -1
  71. package/dist/packages/mysql/src/routing.js +3 -2
  72. package/dist/packages/mysql/src/routing.js.map +1 -1
  73. package/dist/packages/mysql/src/runtime.d.ts +33 -0
  74. package/dist/packages/mysql/src/runtime.d.ts.map +1 -1
  75. package/dist/packages/mysql/src/runtime.js +166 -12
  76. package/dist/packages/mysql/src/runtime.js.map +1 -1
  77. package/dist/packages/mysql/src/semantics.d.ts +1 -1
  78. package/dist/packages/mysql/src/semantics.d.ts.map +1 -1
  79. package/dist/packages/mysql/src/semantics.js +27 -3
  80. package/dist/packages/mysql/src/semantics.js.map +1 -1
  81. package/dist/packages/mysql/src/support.d.ts +43 -0
  82. package/dist/packages/mysql/src/support.d.ts.map +1 -0
  83. package/dist/packages/mysql/src/support.js +102 -0
  84. package/dist/packages/mysql/src/support.js.map +1 -0
  85. package/dist/packages/mysql/src/type-policy.d.ts +2 -0
  86. package/dist/packages/mysql/src/type-policy.d.ts.map +1 -1
  87. package/dist/packages/mysql/src/type-policy.js +24 -50
  88. package/dist/packages/mysql/src/type-policy.js.map +1 -1
  89. package/package.json +8 -5
@@ -1,7 +1,22 @@
1
- import { defaultMySqlTypePolicy, mapMySqlType } from "./type-policy.js";
1
+ import { defineSchemaSnapshotV2, fingerprintSchemaExpression, } from "@typed-sql/schema";
2
+ import { mySqlServerEvidence } from "./capabilities.js";
3
+ import { mySqlCoreCatalogForSchema } from "./catalog/index.js";
4
+ import { defaultMySqlTypePolicy, mapMySqlType, mySqlEnumValues, mySqlSetValues, } from "./type-policy.js";
5
+ import { MYSQL_DIALECT_VERSION } from "./version.js";
2
6
  export const mysqlCatalogQueries = Object.freeze({
3
- version: "SELECT VERSION() AS server_version",
7
+ version: "SELECT VERSION() AS server_version, @@version_comment AS version_comment, @@session.sql_mode AS sql_mode, @@global.character_set_server AS character_set_server, @@global.collation_server AS collation_server, @@session.character_set_connection AS character_set_connection, @@session.collation_connection AS collation_connection, @@session.time_zone AS time_zone, @@global.system_time_zone AS system_time_zone, @@global.lower_case_table_names AS lower_case_table_names",
4
8
  database: "SELECT DATABASE() AS database_name",
9
+ schemas(schemaCount) {
10
+ return `
11
+ SELECT 'def' AS catalog_name,
12
+ SCHEMA_NAME AS schema_name,
13
+ DEFAULT_CHARACTER_SET_NAME AS default_character_set_name,
14
+ DEFAULT_COLLATION_NAME AS default_collation_name
15
+ FROM information_schema.SCHEMATA
16
+ WHERE SCHEMA_NAME IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
17
+ ORDER BY SCHEMA_NAME
18
+ `;
19
+ },
5
20
  columns(schemaCount) {
6
21
  return `
7
22
  SELECT TABLE_SCHEMA AS schema_name,
@@ -9,8 +24,22 @@ export const mysqlCatalogQueries = Object.freeze({
9
24
  COLUMN_NAME AS column_name,
10
25
  COLUMN_TYPE AS database_type,
11
26
  IS_NULLABLE AS is_nullable,
12
- COLUMN_DEFAULT AS default_expression
27
+ COLUMN_DEFAULT AS default_expression,
28
+ ORDINAL_POSITION AS ordinal_position,
29
+ CHARACTER_SET_NAME AS character_set_name,
30
+ COLLATION_NAME AS collation_name,
31
+ EXTRA AS extra,
32
+ GENERATION_EXPRESSION AS generation_expression,
33
+ CHARACTER_MAXIMUM_LENGTH AS character_maximum_length,
34
+ NUMERIC_PRECISION AS numeric_precision,
35
+ NUMERIC_SCALE AS numeric_scale,
36
+ DATETIME_PRECISION AS datetime_precision,
37
+ SRS_ID AS spatial_reference_system_id,
38
+ t.TABLE_TYPE AS table_type,
39
+ v.IS_UPDATABLE AS view_updatable
13
40
  FROM information_schema.COLUMNS
41
+ JOIN information_schema.TABLES AS t USING (TABLE_SCHEMA, TABLE_NAME)
42
+ LEFT JOIN information_schema.VIEWS AS v USING (TABLE_SCHEMA, TABLE_NAME)
14
43
  WHERE TABLE_SCHEMA IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
15
44
  ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
16
45
  `;
@@ -19,16 +48,249 @@ export const mysqlCatalogQueries = Object.freeze({
19
48
  return `
20
49
  SELECT ROUTINE_SCHEMA AS schema_name,
21
50
  ROUTINE_NAME AS function_name,
51
+ ROUTINE_TYPE AS routine_type,
22
52
  DTD_IDENTIFIER AS database_return_type,
23
53
  IS_DETERMINISTIC AS is_deterministic,
24
- SQL_DATA_ACCESS AS sql_data_access
54
+ SQL_DATA_ACCESS AS sql_data_access,
55
+ CHARACTER_SET_NAME AS return_character_set,
56
+ COLLATION_NAME AS return_collation,
57
+ SECURITY_TYPE AS security_type,
58
+ SQL_MODE AS creation_sql_mode,
59
+ CHARACTER_SET_CLIENT AS creation_character_set,
60
+ COLLATION_CONNECTION AS creation_collation,
61
+ DATABASE_COLLATION AS database_collation
25
62
  FROM information_schema.ROUTINES
26
- WHERE ROUTINE_TYPE = 'FUNCTION'
27
- AND ROUTINE_SCHEMA IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
63
+ WHERE ROUTINE_SCHEMA IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
28
64
  ORDER BY ROUTINE_SCHEMA, ROUTINE_NAME
29
65
  `;
30
66
  },
67
+ parameters(schemaCount) {
68
+ return `
69
+ SELECT SPECIFIC_SCHEMA AS schema_name, SPECIFIC_NAME AS routine_name,
70
+ ORDINAL_POSITION AS ordinal_position, PARAMETER_MODE AS parameter_mode,
71
+ PARAMETER_NAME AS parameter_name, DTD_IDENTIFIER AS database_type,
72
+ CHARACTER_SET_NAME AS character_set_name, COLLATION_NAME AS collation_name
73
+ FROM information_schema.PARAMETERS
74
+ WHERE SPECIFIC_SCHEMA IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
75
+ ORDER BY SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION
76
+ `;
77
+ },
78
+ constraints(schemaCount) {
79
+ return `
80
+ SELECT tc.CONSTRAINT_SCHEMA AS schema_name, tc.TABLE_NAME AS table_name,
81
+ tc.CONSTRAINT_NAME AS constraint_name, tc.CONSTRAINT_TYPE AS constraint_type,
82
+ kcu.COLUMN_NAME AS column_name,
83
+ kcu.REFERENCED_TABLE_SCHEMA AS referenced_schema,
84
+ kcu.REFERENCED_TABLE_NAME AS referenced_table,
85
+ kcu.REFERENCED_COLUMN_NAME AS referenced_column_name,
86
+ rc.UPDATE_RULE AS update_rule, rc.DELETE_RULE AS delete_rule,
87
+ cc.CHECK_CLAUSE AS check_clause,
88
+ tc.ENFORCED AS enforced,
89
+ COALESCE(kcu.ORDINAL_POSITION, 0) AS ordinal_position
90
+ FROM information_schema.TABLE_CONSTRAINTS AS tc
91
+ LEFT JOIN information_schema.KEY_COLUMN_USAGE AS kcu
92
+ ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND kcu.TABLE_NAME = tc.TABLE_NAME
93
+ AND kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
94
+ LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc
95
+ ON rc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND rc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
96
+ LEFT JOIN information_schema.CHECK_CONSTRAINTS AS cc
97
+ ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
98
+ WHERE tc.CONSTRAINT_SCHEMA IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
99
+ ORDER BY tc.CONSTRAINT_SCHEMA, tc.TABLE_NAME, tc.CONSTRAINT_NAME, ordinal_position
100
+ `;
101
+ },
102
+ indexes(schemaCount) {
103
+ return `
104
+ SELECT s.TABLE_SCHEMA AS schema_name, s.TABLE_NAME AS table_name, s.INDEX_NAME AS index_name,
105
+ (s.NON_UNIQUE = 0) AS is_unique, s.INDEX_TYPE AS index_type,
106
+ s.COLUMN_NAME AS column_name, s.EXPRESSION AS expression,
107
+ COALESCE(s.COLLATION = 'D', 0) AS descending,
108
+ c.COLLATION_NAME AS column_collation,
109
+ s.SUB_PART AS prefix_length,
110
+ (s.IS_VISIBLE = 'YES') AS visible,
111
+ s.SEQ_IN_INDEX AS ordinal_position
112
+ FROM information_schema.STATISTICS AS s
113
+ LEFT JOIN information_schema.COLUMNS AS c
114
+ ON c.TABLE_SCHEMA = s.TABLE_SCHEMA AND c.TABLE_NAME = s.TABLE_NAME
115
+ AND c.COLUMN_NAME = s.COLUMN_NAME
116
+ WHERE s.TABLE_SCHEMA IN (${Array.from({ length: schemaCount }, () => "?").join(", ")})
117
+ ORDER BY s.TABLE_SCHEMA, s.TABLE_NAME, s.INDEX_NAME, s.SEQ_IN_INDEX
118
+ `;
119
+ },
31
120
  });
121
+ function relationKey(schemas, schema, name) {
122
+ return schemas.length === 1 ? name : `${schema}.${name}`;
123
+ }
124
+ function foreignKeyAction(value) {
125
+ const normalized = value?.toUpperCase();
126
+ if (normalized === "RESTRICT")
127
+ return "restrict";
128
+ if (normalized === "CASCADE")
129
+ return "cascade";
130
+ if (normalized === "SET NULL")
131
+ return "set-null";
132
+ if (normalized === "SET DEFAULT")
133
+ return "set-default";
134
+ if (normalized === "NO ACTION")
135
+ return "no-action";
136
+ return "unknown";
137
+ }
138
+ function catalogArray(value, path) {
139
+ if (Array.isArray(value))
140
+ return value;
141
+ if (typeof value === "string") {
142
+ try {
143
+ const parsed = JSON.parse(value);
144
+ if (Array.isArray(parsed))
145
+ return parsed;
146
+ }
147
+ catch {
148
+ // The stable, redacted error below is more useful than a driver JSON failure.
149
+ }
150
+ }
151
+ throw new TypeError(`MySQL introspection ${path} must be a JSON array`);
152
+ }
153
+ function catalogBoolean(value, path) {
154
+ if (typeof value === "boolean")
155
+ return value;
156
+ if (value === 0 || value === "0")
157
+ return false;
158
+ if (value === 1 || value === "1")
159
+ return true;
160
+ throw new TypeError(`MySQL introspection ${path} must be boolean evidence`);
161
+ }
162
+ function optionalCatalogInteger(value, path) {
163
+ if (value === undefined || value === null)
164
+ return undefined;
165
+ const normalized = typeof value === "string" && /^\d+$/u.test(value.trim()) ? Number(value.trim()) : value;
166
+ if (!Number.isSafeInteger(normalized) || normalized < 0) {
167
+ throw new TypeError(`MySQL introspection ${path} must be non-negative integer evidence`);
168
+ }
169
+ return normalized;
170
+ }
171
+ function normalizeCatalogSqlMode(value) {
172
+ return [
173
+ ...new Set(value
174
+ .split(",")
175
+ .map((mode) => mode.trim().toUpperCase())
176
+ .filter(Boolean)),
177
+ ]
178
+ .sort()
179
+ .join(",");
180
+ }
181
+ function normalizeSchemas(values) {
182
+ const schemas = values.map((value) => {
183
+ if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
184
+ throw new TypeError("MySQL introspection schema names must be non-empty strings without NUL bytes");
185
+ }
186
+ return value.trim();
187
+ });
188
+ return [...new Set(schemas)].sort();
189
+ }
190
+ function incompleteCatalogDiagnostic(catalog) {
191
+ return Object.freeze({
192
+ code: "TSQ406",
193
+ severity: "warning",
194
+ catalog,
195
+ message: `MySQL introspection could not read ${catalog} metadata; the snapshot contains incomplete evidence.`,
196
+ });
197
+ }
198
+ function compareCatalogIdentity(left, right) {
199
+ return (left.schema_name.localeCompare(right.schema_name) ||
200
+ left.table_name.localeCompare(right.table_name) ||
201
+ left.constraint_name.localeCompare(right.constraint_name));
202
+ }
203
+ function normalizeConstraintRows(rows) {
204
+ const groups = new Map();
205
+ for (const row of rows) {
206
+ const key = `${row.schema_name}\u0000${row.table_name}\u0000${row.constraint_name}`;
207
+ const group = groups.get(key);
208
+ if (group === undefined)
209
+ groups.set(key, [row]);
210
+ else
211
+ group.push(row);
212
+ }
213
+ return [...groups.values()]
214
+ .map((group) => {
215
+ const ordered = [...group].sort((left, right) => (left.ordinal_position ?? 0) - (right.ordinal_position ?? 0));
216
+ const first = ordered[0];
217
+ const referencedRow = ordered.find((row) => row.referenced_schema != null || row.referenced_table != null || row.referenced_column_name != null);
218
+ return {
219
+ ...first,
220
+ referenced_schema: first.referenced_schema ?? referencedRow?.referenced_schema ?? null,
221
+ referenced_table: first.referenced_table ?? referencedRow?.referenced_table ?? null,
222
+ update_rule: first.update_rule ?? null,
223
+ delete_rule: first.delete_rule ?? null,
224
+ check_clause: first.check_clause ?? null,
225
+ columns: first.columns === undefined
226
+ ? ordered.map(({ column_name }) => column_name).filter((column) => column != null)
227
+ : catalogArray(first.columns, "constraint columns"),
228
+ referenced_columns: first.referenced_columns === undefined
229
+ ? ordered.map(({ referenced_column_name }) => referenced_column_name).filter((column) => column != null)
230
+ : first.referenced_columns === null
231
+ ? []
232
+ : catalogArray(first.referenced_columns, "referenced constraint columns"),
233
+ };
234
+ })
235
+ .sort(compareCatalogIdentity);
236
+ }
237
+ function normalizeIndexRows(rows) {
238
+ const groups = new Map();
239
+ for (const row of rows) {
240
+ const key = `${row.schema_name}\u0000${row.table_name}\u0000${row.index_name}`;
241
+ const group = groups.get(key);
242
+ if (group === undefined)
243
+ groups.set(key, [row]);
244
+ else
245
+ group.push(row);
246
+ }
247
+ return [...groups.values()]
248
+ .map((group) => {
249
+ const ordered = [...group].sort((left, right) => (left.ordinal_position ?? 0) - (right.ordinal_position ?? 0));
250
+ const first = ordered[0];
251
+ return {
252
+ ...first,
253
+ columns: first.columns === undefined
254
+ ? ordered.map(({ column_name }) => column_name ?? null)
255
+ : catalogArray(first.columns, "index columns"),
256
+ expressions: first.expressions === undefined
257
+ ? ordered.map(({ expression }) => expression ?? null)
258
+ : catalogArray(first.expressions, "index expressions"),
259
+ descending: first.descending === undefined || Array.isArray(first.descending)
260
+ ? first.descending === undefined
261
+ ? ordered.map(() => false)
262
+ : first.descending
263
+ : typeof first.descending === "string" && first.descending.startsWith("[")
264
+ ? catalogArray(first.descending, "index ordering")
265
+ : ordered.map(({ descending }) => descending ?? false),
266
+ column_collations: first.column_collations === undefined
267
+ ? ordered.map(({ column_collation }) => column_collation ?? null)
268
+ : catalogArray(first.column_collations, "index column collations"),
269
+ prefix_lengths: first.prefix_lengths === undefined
270
+ ? ordered.map(({ prefix_length }) => prefix_length ?? null)
271
+ : catalogArray(first.prefix_lengths, "index prefix lengths"),
272
+ };
273
+ })
274
+ .sort((left, right) => left.schema_name.localeCompare(right.schema_name) ||
275
+ left.table_name.localeCompare(right.table_name) ||
276
+ left.index_name.localeCompare(right.index_name));
277
+ }
278
+ function routineDataAccess(value) {
279
+ if (value === "NO SQL")
280
+ return "none";
281
+ if (value === "CONTAINS SQL")
282
+ return "contains-sql";
283
+ if (value === "READS SQL DATA")
284
+ return "reads-sql";
285
+ return "modifies-sql";
286
+ }
287
+ function routineVolatility(row) {
288
+ if (row.sql_data_access === "MODIFIES SQL DATA" || row.is_deterministic === "NO")
289
+ return "volatile";
290
+ if (row.sql_data_access === "READS SQL DATA")
291
+ return "stable";
292
+ return "immutable";
293
+ }
32
294
  export class MySqlSchemaProvider {
33
295
  #client;
34
296
  #schemas;
@@ -38,58 +300,399 @@ export class MySqlSchemaProvider {
38
300
  this.#schemas = options.includeSchemas;
39
301
  this.#policy = options.typePolicy ?? defaultMySqlTypePolicy;
40
302
  }
41
- async introspect(_input) {
42
- if (this.#client === undefined)
303
+ async introspect(input) {
304
+ return (await this.introspectWithDiagnostics(input)).snapshot;
305
+ }
306
+ async introspectWithDiagnostics(_input) {
307
+ const client = this.#client;
308
+ if (client === undefined)
43
309
  throw new TypeError("MySQL schema provider requires an injected client; use @typed-sql/mysql/mysql2 for a connection URI");
44
- const version = (await this.#client.query(mysqlCatalogQueries.version)).rows[0]?.server_version;
45
- const currentDatabase = (await this.#client.query(mysqlCatalogQueries.database)).rows[0]
46
- ?.database_name;
47
- const schemas = this.#schemas ?? (currentDatabase === null || currentDatabase === undefined ? [] : [currentDatabase]);
310
+ const versionRow = (await client.query(mysqlCatalogQueries.version)).rows[0];
311
+ if (versionRow === undefined)
312
+ throw new TypeError("MySQL introspection did not return a server version");
313
+ const version = versionRow.server_version;
314
+ const server = mySqlServerEvidence(version, {
315
+ ...(versionRow.sql_mode === undefined ? {} : { sqlMode: versionRow.sql_mode }),
316
+ ...(versionRow.version_comment === undefined ? {} : { versionComment: versionRow.version_comment }),
317
+ ...(versionRow.character_set_server === undefined ? {} : { characterSetServer: versionRow.character_set_server }),
318
+ ...(versionRow.collation_server === undefined ? {} : { collationServer: versionRow.collation_server }),
319
+ ...(versionRow.character_set_connection === undefined
320
+ ? {}
321
+ : { characterSetConnection: versionRow.character_set_connection }),
322
+ ...(versionRow.collation_connection === undefined
323
+ ? {}
324
+ : { collationConnection: versionRow.collation_connection }),
325
+ ...(versionRow.time_zone === undefined ? {} : { timeZone: versionRow.time_zone }),
326
+ ...(versionRow.system_time_zone === undefined ? {} : { systemTimeZone: versionRow.system_time_zone }),
327
+ ...(versionRow.lower_case_table_names === undefined
328
+ ? {}
329
+ : { lowerCaseTableNames: versionRow.lower_case_table_names }),
330
+ });
331
+ if (server.product !== "mysql") {
332
+ throw new TypeError(`MySQL introspection requires an Oracle MySQL server; detected ${server.product}`);
333
+ }
334
+ const typeContext = {
335
+ formatVersion: 1,
336
+ dialect: "mysql",
337
+ tables: {},
338
+ server,
339
+ };
340
+ const mapType = (databaseType) => mapMySqlType(databaseType, this.#policy, typeContext);
341
+ const currentDatabase = (await client.query(mysqlCatalogQueries.database)).rows[0]?.database_name;
342
+ const schemas = normalizeSchemas(this.#schemas ?? (currentDatabase === null || currentDatabase === undefined ? [] : [currentDatabase]));
48
343
  if (schemas.length === 0)
49
344
  throw new TypeError("MySQL introspection requires at least one database schema");
50
- const columnRows = (await this.#client.query(mysqlCatalogQueries.columns(schemas.length), schemas)).rows;
51
- const routineRows = (await this.#client.query(mysqlCatalogQueries.routines(schemas.length), schemas))
52
- .rows;
53
- const tables = {};
54
- for (const row of columnRows) {
55
- const key = schemas.length === 1 ? row.table_name : `${row.schema_name}.${row.table_name}`;
56
- let table = tables[key];
57
- if (table === undefined) {
58
- table = { schema: row.schema_name, name: row.table_name, columns: {} };
59
- tables[key] = table;
345
+ const diagnostics = [];
346
+ const catalogEvidence = new Map();
347
+ const queryCatalog = async (catalog, sql) => {
348
+ try {
349
+ const { rows } = await client.query(sql, schemas);
350
+ catalogEvidence.set(catalog, "complete");
351
+ return rows;
352
+ }
353
+ catch {
354
+ catalogEvidence.set(catalog, "incomplete");
355
+ diagnostics.push(incompleteCatalogDiagnostic(catalog));
356
+ return [];
357
+ }
358
+ };
359
+ const [schemaRows, columnRows, routineRows, parameterRows, constraintRows, indexRows] = await Promise.all([
360
+ queryCatalog("schemas", mysqlCatalogQueries.schemas(schemas.length)),
361
+ queryCatalog("columns", mysqlCatalogQueries.columns(schemas.length)),
362
+ queryCatalog("routines", mysqlCatalogQueries.routines(schemas.length)),
363
+ queryCatalog("parameters", mysqlCatalogQueries.parameters(schemas.length)),
364
+ queryCatalog("constraints", mysqlCatalogQueries.constraints(schemas.length)),
365
+ queryCatalog("indexes", mysqlCatalogQueries.indexes(schemas.length)),
366
+ ]);
367
+ if (catalogEvidence.get("schemas") === "complete") {
368
+ const visibleSchemas = new Set(schemaRows.map(({ schema_name }) => schema_name));
369
+ if (schemas.some((schema) => !visibleSchemas.has(schema))) {
370
+ catalogEvidence.set("schemas", "incomplete");
371
+ diagnostics.push(incompleteCatalogDiagnostic("schemas"));
60
372
  }
61
- table.columns[row.column_name] = {
373
+ }
374
+ const relations = {};
375
+ const types = {};
376
+ const orderedColumnRows = [...columnRows].sort((left, right) => left.schema_name.localeCompare(right.schema_name) ||
377
+ left.table_name.localeCompare(right.table_name) ||
378
+ (left.ordinal_position ?? Number.MAX_SAFE_INTEGER) - (right.ordinal_position ?? Number.MAX_SAFE_INTEGER) ||
379
+ left.column_name.localeCompare(right.column_name));
380
+ for (const row of orderedColumnRows) {
381
+ const key = relationKey(schemas, row.schema_name, row.table_name);
382
+ const relation = relations[key];
383
+ const columns = relation === undefined ? {} : { ...relation.columns };
384
+ const generated = /(?:VIRTUAL|STORED) GENERATED/iu
385
+ .exec(row.extra ?? "")?.[0]
386
+ ?.toUpperCase()
387
+ .startsWith("STORED")
388
+ ? "stored"
389
+ : /GENERATED/iu.test(row.extra ?? "")
390
+ ? "virtual"
391
+ : "none";
392
+ const isView = row.table_type === "VIEW" || row.table_type === "SYSTEM VIEW";
393
+ const invisible = /(?:^|\s)INVISIBLE(?:\s|$)/iu.test(row.extra ?? "");
394
+ const characterMaximumLength = optionalCatalogInteger(row.character_maximum_length, `${row.schema_name}.${row.table_name}.${row.column_name} character length`);
395
+ const numericPrecision = optionalCatalogInteger(row.numeric_precision, `${row.schema_name}.${row.table_name}.${row.column_name} numeric precision`);
396
+ const numericScale = optionalCatalogInteger(row.numeric_scale, `${row.schema_name}.${row.table_name}.${row.column_name} numeric scale`);
397
+ const datetimePrecision = optionalCatalogInteger(row.datetime_precision, `${row.schema_name}.${row.table_name}.${row.column_name} datetime precision`);
398
+ const spatialReferenceSystemId = optionalCatalogInteger(row.spatial_reference_system_id, `${row.schema_name}.${row.table_name}.${row.column_name} spatial reference system`);
399
+ columns[row.column_name] = {
62
400
  name: row.column_name,
401
+ position: typeof row.ordinal_position !== "number"
402
+ ? Math.max(-1, ...Object.values(columns).map(({ position }) => position)) + 1
403
+ : Math.max(0, row.ordinal_position - 1),
63
404
  databaseType: row.database_type,
64
- tsType: mapMySqlType(row.database_type, this.#policy),
405
+ typeIdentity: `mysql:${row.database_type.toLowerCase()}`,
406
+ tsType: mapType(row.database_type),
65
407
  nullable: row.is_nullable === "YES",
66
- ...(row.default_expression === null ? {} : { defaultExpression: row.default_expression }),
408
+ nullabilitySource: "declared",
409
+ default: row.default_expression === null ? "unknown" : "present",
410
+ ...(row.default_expression === null
411
+ ? {}
412
+ : { defaultExpressionHash: fingerprintSchemaExpression(row.default_expression) }),
413
+ generated,
414
+ ...(generated === "none" || row.generation_expression === undefined || row.generation_expression === null
415
+ ? {}
416
+ : { generatedExpressionHash: fingerprintSchemaExpression(row.generation_expression) }),
417
+ identity: /auto_increment/iu.test(row.extra ?? "") ? "by-default" : "none",
418
+ ...(row.collation_name === undefined || row.collation_name === null ? {} : { collation: row.collation_name }),
419
+ ...(row.character_set_name === undefined || row.character_set_name === null
420
+ ? {}
421
+ : { characterSet: row.character_set_name }),
422
+ classification: invisible ? "hidden" : "normal",
423
+ insertable: generated === "none" && !isView,
424
+ updatable: generated === "none" && (!isView || row.view_updatable === "YES"),
425
+ extension: {
426
+ version: "1",
427
+ attributes: {
428
+ unsigned: /(?:^|\s)unsigned(?:\s|$)/iu.test(row.database_type),
429
+ zerofill: /(?:^|\s)zerofill(?:\s|$)/iu.test(row.database_type),
430
+ invisible,
431
+ ...(characterMaximumLength === undefined ? {} : { characterMaximumLength }),
432
+ ...(numericPrecision === undefined ? {} : { numericPrecision }),
433
+ ...(numericScale === undefined ? {} : { numericScale }),
434
+ ...(datetimePrecision === undefined ? {} : { datetimePrecision }),
435
+ ...(spatialReferenceSystemId === undefined ? {} : { spatialReferenceSystemId }),
436
+ },
437
+ },
438
+ };
439
+ relations[key] = {
440
+ schema: row.schema_name,
441
+ name: row.table_name,
442
+ kind: isView ? "view" : "table",
443
+ columns,
444
+ constraints: relation?.constraints ?? [],
445
+ indexes: relation?.indexes ?? [],
446
+ capabilities: {
447
+ viewUpdatable: !isView || row.view_updatable === "YES",
448
+ constraintEvidence: catalogEvidence.get("constraints") === "complete",
449
+ indexEvidence: catalogEvidence.get("indexes") === "complete",
450
+ },
451
+ };
452
+ if (types[row.database_type] === undefined) {
453
+ const labels = mySqlEnumValues(row.database_type);
454
+ const setMembers = mySqlSetValues(row.database_type);
455
+ const common = {
456
+ name: row.database_type,
457
+ identity: `mysql:${row.database_type.toLowerCase()}`,
458
+ databaseType: row.database_type,
459
+ tsType: mapType(row.database_type),
460
+ };
461
+ types[row.database_type] =
462
+ labels === undefined
463
+ ? common.tsType === this.#policy.unknown
464
+ ? { kind: "opaque", ...common, reason: "The MySQL type has no configured type-policy mapping." }
465
+ : {
466
+ kind: /^set\(/iu.test(row.database_type) ? "collection" : "scalar",
467
+ ...common,
468
+ ...(/^set\(/iu.test(row.database_type)
469
+ ? {
470
+ elementTypeIdentity: "mysql:string",
471
+ extension: {
472
+ version: "1",
473
+ attributes: { members: setMembers ?? [] },
474
+ },
475
+ }
476
+ : {}),
477
+ }
478
+ : { kind: "enum", ...common, labels };
479
+ }
480
+ }
481
+ for (const row of normalizeConstraintRows(constraintRows)) {
482
+ const key = relationKey(schemas, row.schema_name, row.table_name);
483
+ const relation = relations[key];
484
+ if (relation === undefined)
485
+ continue;
486
+ const columns = row.columns.filter((column) => typeof column === "string");
487
+ const referencedColumns = row.referenced_columns.filter((column) => typeof column === "string");
488
+ const base = {
489
+ name: row.constraint_name,
490
+ identity: `${row.schema_name}.${row.table_name}.${row.constraint_name}`,
491
+ columns,
492
+ partial: false,
493
+ expressionBased: false,
494
+ deferrable: false,
495
+ initiallyDeferred: false,
496
+ extension: {
497
+ version: "1",
498
+ attributes: { enforced: row.enforced === undefined ? "unknown" : row.enforced === "YES" },
499
+ },
67
500
  };
501
+ const constraint = row.constraint_type === "PRIMARY KEY"
502
+ ? { kind: "primary-key", ...base, nullsDistinct: false }
503
+ : row.constraint_type === "UNIQUE"
504
+ ? { kind: "unique", ...base, nullsDistinct: true }
505
+ : row.constraint_type === "FOREIGN KEY"
506
+ ? {
507
+ kind: "foreign-key",
508
+ ...base,
509
+ referencedRelation: row.referenced_table === null
510
+ ? "unknown"
511
+ : relationKey(schemas, row.referenced_schema ?? row.schema_name, row.referenced_table),
512
+ referencedColumns,
513
+ match: "simple",
514
+ onUpdate: foreignKeyAction(row.update_rule),
515
+ onDelete: foreignKeyAction(row.delete_rule),
516
+ }
517
+ : {
518
+ kind: "check",
519
+ ...base,
520
+ expressionBased: true,
521
+ predicate: row.check_clause === null ? "unknown" : "present",
522
+ ...(row.check_clause === null
523
+ ? {}
524
+ : { predicateHash: fingerprintSchemaExpression(row.check_clause) }),
525
+ };
526
+ relations[key] = { ...relation, constraints: [...relation.constraints, constraint] };
68
527
  }
69
- const functions = {};
70
- for (const row of routineRows) {
71
- functions[`${row.schema_name}.${row.function_name}()`] = {
528
+ for (const row of normalizeIndexRows(indexRows)) {
529
+ const key = relationKey(schemas, row.schema_name, row.table_name);
530
+ const relation = relations[key];
531
+ if (relation === undefined)
532
+ continue;
533
+ const { columns, expressions, descending, column_collations, prefix_lengths } = row;
534
+ const visible = catalogBoolean(row.visible, "index visibility");
535
+ relations[key] = {
536
+ ...relation,
537
+ indexes: [
538
+ ...relation.indexes,
539
+ {
540
+ name: row.index_name,
541
+ identity: `${row.schema_name}.${row.table_name}.${row.index_name}`,
542
+ unique: catalogBoolean(row.is_unique, "index uniqueness"),
543
+ method: row.index_type.toLowerCase(),
544
+ columns: columns.map((value, index) => {
545
+ const column = typeof value === "string" ? value : null;
546
+ const expression = expressions[index];
547
+ const collation = column_collations[index];
548
+ return {
549
+ ...(column === null
550
+ ? {
551
+ expressionHash: fingerprintSchemaExpression(typeof expression === "string" ? expression : `${row.index_name}:${index}`),
552
+ }
553
+ : { column }),
554
+ ...(catalogBoolean(descending[index], "index column ordering") ? { descending: true } : {}),
555
+ ...(typeof collation !== "string" ? {} : { collation }),
556
+ };
557
+ }),
558
+ predicate: "none",
559
+ valid: true,
560
+ extension: {
561
+ version: "1",
562
+ attributes: {
563
+ visible,
564
+ prefixLengths: prefix_lengths.map((value, index) => optionalCatalogInteger(value, `${row.index_name} prefix length ${index}`) ?? null),
565
+ },
566
+ },
567
+ },
568
+ ],
569
+ };
570
+ }
571
+ const parameters = new Map();
572
+ for (const row of parameterRows) {
573
+ const key = `${row.schema_name}.${row.routine_name}`;
574
+ const rows = parameters.get(key);
575
+ if (rows === undefined)
576
+ parameters.set(key, [row]);
577
+ else
578
+ rows.push(row);
579
+ }
580
+ const routines = {};
581
+ for (const row of catalogEvidence.get("parameters") === "complete" ? routineRows : []) {
582
+ const name = `${row.schema_name}.${row.function_name}`;
583
+ const allParameters = (parameters.get(name) ?? []).sort((left, right) => left.ordinal_position - right.ordinal_position);
584
+ const inputParameters = allParameters.filter(({ ordinal_position }) => ordinal_position > 0);
585
+ const argumentsList = inputParameters.map((parameter) => ({
586
+ ...(parameter.parameter_name === null ? {} : { name: parameter.parameter_name }),
587
+ mode: parameter.parameter_mode === "OUT"
588
+ ? "out"
589
+ : parameter.parameter_mode === "INOUT"
590
+ ? "inout"
591
+ : "in",
592
+ typeIdentity: `mysql:${parameter.database_type.toLowerCase()}`,
593
+ databaseType: parameter.database_type,
594
+ tsType: mapType(parameter.database_type),
595
+ default: "none",
596
+ }));
597
+ const kind = row.routine_type === "PROCEDURE" ? "procedure" : "function";
598
+ const routine = {
72
599
  schema: row.schema_name,
73
600
  name: row.function_name,
74
- argumentTypes: [],
75
- databaseReturnType: row.database_return_type,
76
- returnType: mapMySqlType(row.database_return_type, this.#policy),
77
- nullable: true,
78
- volatility: row.sql_data_access === "MODIFIES SQL DATA" || row.is_deterministic === "NO"
79
- ? "volatile"
80
- : row.sql_data_access === "READS SQL DATA"
81
- ? "stable"
82
- : "immutable",
601
+ identity: `${name}(${argumentsList
602
+ .filter(({ mode }) => mode !== "out")
603
+ .map(({ databaseType }) => databaseType)
604
+ .join(",")})`,
605
+ kind,
606
+ arguments: argumentsList,
607
+ result: kind === "procedure"
608
+ ? { kind: "command" }
609
+ : {
610
+ kind: "scalar",
611
+ typeIdentity: `mysql:${row.database_return_type.toLowerCase()}`,
612
+ databaseType: row.database_return_type,
613
+ tsType: mapType(row.database_return_type),
614
+ nullable: true,
615
+ },
616
+ volatility: routineVolatility(row),
617
+ deterministic: row.is_deterministic === "YES",
618
+ dataAccess: routineDataAccess(row.sql_data_access),
619
+ nullInput: "called",
620
+ extension: {
621
+ version: "1",
622
+ attributes: {
623
+ ...(row.security_type === undefined ? {} : { securityType: row.security_type.toLowerCase() }),
624
+ ...(row.creation_sql_mode === undefined
625
+ ? {}
626
+ : { creationSqlMode: normalizeCatalogSqlMode(row.creation_sql_mode) }),
627
+ ...(row.creation_character_set == null ? {} : { creationCharacterSet: row.creation_character_set }),
628
+ ...(row.creation_collation == null ? {} : { creationCollation: row.creation_collation }),
629
+ ...(row.database_collation == null ? {} : { databaseCollation: row.database_collation }),
630
+ ...(row.return_character_set == null ? {} : { returnCharacterSet: row.return_character_set }),
631
+ ...(row.return_collation == null ? {} : { returnCollation: row.return_collation }),
632
+ argumentCharacterSets: inputParameters.map(({ character_set_name }) => character_set_name ?? null),
633
+ argumentCollations: inputParameters.map(({ collation_name }) => collation_name ?? null),
634
+ },
635
+ },
83
636
  };
637
+ const overloads = routines[name];
638
+ if (overloads === undefined)
639
+ routines[name] = [routine];
640
+ else
641
+ overloads.push(routine);
84
642
  }
85
- return {
86
- formatVersion: 1,
643
+ for (const overloads of Object.values(routines)) {
644
+ overloads.sort((left, right) => left.identity.localeCompare(right.identity) ||
645
+ left.volatility.localeCompare(right.volatility) ||
646
+ String(left.deterministic).localeCompare(String(right.deterministic)));
647
+ }
648
+ const schemaByName = new Map(schemaRows.map((row) => [row.schema_name, row]));
649
+ const namespaces = Object.fromEntries([
650
+ ["def", { name: "def", kind: "catalog" }],
651
+ ...schemas.map((name) => {
652
+ const row = schemaByName.get(name);
653
+ return [
654
+ name,
655
+ {
656
+ name,
657
+ kind: "database",
658
+ ...(row === undefined
659
+ ? {}
660
+ : {
661
+ extension: {
662
+ version: "1",
663
+ attributes: {
664
+ catalog: row.catalog_name,
665
+ characterSet: row.default_character_set_name,
666
+ collation: row.default_collation_name,
667
+ },
668
+ },
669
+ }),
670
+ },
671
+ ];
672
+ }),
673
+ ]);
674
+ const coreCatalog = mySqlCoreCatalogForSchema(typeContext);
675
+ const snapshot = defineSchemaSnapshotV2({
676
+ formatVersion: 2,
87
677
  dialect: "mysql",
88
- dialectVersion: "1.0.0",
89
- ...(version === undefined ? {} : { version }),
90
- tables,
91
- ...(Object.keys(functions).length === 0 ? {} : { functions }),
92
- };
678
+ dialectVersion: MYSQL_DIALECT_VERSION,
679
+ server,
680
+ namespaces,
681
+ types,
682
+ relations,
683
+ routines,
684
+ extension: {
685
+ version: "1",
686
+ attributes: {
687
+ catalogRevision: coreCatalog?.revision ?? "unknown",
688
+ evidenceScope: "current-role",
689
+ catalogEvidence: Object.fromEntries([...catalogEvidence.entries()].sort(([left], [right]) => left.localeCompare(right))),
690
+ incompleteEvidence: diagnostics.length > 0,
691
+ temporaryTables: "not-captured",
692
+ },
693
+ },
694
+ });
695
+ return Object.freeze({ snapshot, diagnostics: Object.freeze([...diagnostics]) });
93
696
  }
94
697
  }
95
698
  export async function introspectMySql(options) {