@travetto/model-mysql 8.0.0-alpha.30 → 8.0.0-alpha.32

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 (2) hide show
  1. package/package.json +6 -6
  2. package/src/dialect.ts +37 -37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mysql",
3
- "version": "8.0.0-alpha.30",
3
+ "version": "8.0.0-alpha.32",
4
4
  "type": "module",
5
5
  "description": "MySQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
6
6
  "keywords": [
@@ -28,15 +28,15 @@
28
28
  "directory": "module/model-mysql"
29
29
  },
30
30
  "dependencies": {
31
- "@travetto/config": "^8.0.0-alpha.24",
31
+ "@travetto/config": "^8.0.0-alpha.25",
32
32
  "@travetto/context": "^8.0.0-alpha.22",
33
- "@travetto/model": "^8.0.0-alpha.25",
34
- "@travetto/model-query": "^8.0.0-alpha.27",
35
- "@travetto/model-sql": "^8.0.0-alpha.29",
33
+ "@travetto/model": "^8.0.0-alpha.26",
34
+ "@travetto/model-query": "^8.0.0-alpha.28",
35
+ "@travetto/model-sql": "^8.0.0-alpha.31",
36
36
  "mysql2": "^3.23.1"
37
37
  },
38
38
  "peerDependencies": {
39
- "@travetto/cli": "^8.0.0-alpha.30"
39
+ "@travetto/cli": "^8.0.0-alpha.31"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@travetto/cli": {
package/src/dialect.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AbstractANSI99Dialect, type JSONSqlPathMode, type ResolvedPathContext, type TableContext } from '@travetto/model-sql';
2
2
  import { type Class, castTo, JSONUtil } from '@travetto/runtime';
3
- import { type SchemaFieldConfig, SchemaRegistryIndex } from '@travetto/schema';
3
+ import type { SchemaFieldConfig } from '@travetto/schema';
4
4
 
5
5
  export class MysqlDialect extends AbstractANSI99Dialect {
6
6
  override returningSupport = false;
@@ -54,36 +54,15 @@ export class MysqlDialect extends AbstractANSI99Dialect {
54
54
  }
55
55
 
56
56
  compileJsonIndexPath(columnName: string, jsonPath: string[], mode: JSONSqlPathMode): string {
57
- const result = `${columnName}->>'$.${jsonPath.join('.')}'`;
58
- switch (mode) {
59
- case 'createIndex':
60
- return `(CAST(${result} as CHAR(255)) COLLATE utf8mb4_bin)`;
61
- case 'orderBy':
62
- case 'read':
63
- return result;
64
- }
57
+ return `${columnName}->>'$.${this.formatJsonPath(jsonPath)}'`;
65
58
  }
66
59
 
67
60
  #formatSubPath(context: ResolvedPathContext): string {
68
61
  if (!context.subPath || context.subPath.length === 0) {
69
62
  return '';
70
63
  }
71
- let currentClass: Class | undefined = context.arrayField?.type;
72
- const parts: string[] = [];
73
- for (let index = 0; index < context.subPath.length; index++) {
74
- const segment = context.subPath[index];
75
- if (currentClass) {
76
- const classConfig = SchemaRegistryIndex.getOptional(currentClass)?.get();
77
- const fieldConfig = classConfig?.fields[segment];
78
- if (fieldConfig) {
79
- parts.push(fieldConfig.array ? `${segment}[*]` : segment);
80
- currentClass = fieldConfig.type;
81
- continue;
82
- }
83
- }
84
- parts.push(segment);
85
- }
86
- return parts.join('.');
64
+ const subPathMetadata = this.getSchemaSubPathMetadata(context.arrayField?.type, context.subPath);
65
+ return subPathMetadata.map(({ segment, isArray }) => (isArray ? `${segment}[*]` : segment)).join('.');
87
66
  }
88
67
 
89
68
  #getArraySqlPath(context: ResolvedPathContext): string {
@@ -150,6 +129,8 @@ export class MysqlDialect extends AbstractANSI99Dialect {
150
129
  return `CAST(${sqlPath} AS SIGNED)`;
151
130
  } else if (type === Date) {
152
131
  return `CAST(${sqlPath} AS DATETIME(6))`;
132
+ } else if (type === String) {
133
+ return `(CAST(${sqlPath} AS CHAR(255)) COLLATE utf8mb4_bin)`;
153
134
  }
154
135
  return sqlPath;
155
136
  }
@@ -161,13 +142,8 @@ export class MysqlDialect extends AbstractANSI99Dialect {
161
142
  conflictTarget: string[],
162
143
  updates: string[]
163
144
  ): string {
164
- const mysqlUpdates = updates.map(val => val.replace(/EXCLUDED\.(.*)/g, 'VALUES($1)'));
165
- return `
166
- INSERT INTO
167
- ${this.escapeIdentifier(context.tableName)} (${columns.join(', ')})
168
- VALUES
169
- (${placeholders.join(', ')})
170
- ON DUPLICATE KEY UPDATE ${mysqlUpdates.join(', ')};`;
145
+ const mysqlUpdateStatements = updates.map(statement => statement.replaceAll(/EXCLUDED\.(.*)/g, 'new_row.$1'));
146
+ return `INSERT INTO ${this.escapeIdentifier(context.tableName)} (${columns.join(', ')}) VALUES (${placeholders.join(', ')}) AS new_row ON DUPLICATE KEY UPDATE ${mysqlUpdateStatements.join(', ')};`;
171
147
  }
172
148
 
173
149
  getTableExistsQuery(context: TableContext): { sql: string; parameters?: unknown[] } {
@@ -191,7 +167,7 @@ WHERE table_schema = ? AND table_name = ?;
191
167
  sql: `
192
168
  SELECT
193
169
  COLUMN_NAME as name,
194
- DATA_TYPE as type
170
+ COLUMN_TYPE as type
195
171
  FROM information_schema.columns
196
172
  WHERE table_schema = ? AND table_name = ?;
197
173
  `,
@@ -206,23 +182,47 @@ WHERE table_schema = ? AND table_name = ?;
206
182
  getExistingIndexesQuery(context: TableContext): { sql: string; parameters?: unknown[] } {
207
183
  return {
208
184
  sql: `
209
- SELECT DISTINCT
210
- INDEX_NAME as name
185
+ SELECT
186
+ INDEX_NAME as name,
187
+ TABLE_NAME as tableName,
188
+ NON_UNIQUE as nonUnique,
189
+ GROUP_CONCAT(COALESCE(EXPRESSION, COLUMN_NAME) ORDER BY SEQ_IN_INDEX SEPARATOR ', ') as indexColumns
211
190
  FROM information_schema.statistics
212
191
  WHERE
213
192
  table_schema = ?
214
193
  AND table_name = ?
215
- AND INDEX_NAME != 'PRIMARY';
194
+ AND INDEX_NAME != 'PRIMARY'
195
+ GROUP BY INDEX_NAME, TABLE_NAME, NON_UNIQUE;
216
196
  `,
217
197
  parameters: [context.database, context.tableName]
218
198
  };
219
199
  }
220
200
 
221
201
  parseExistingIndexes(records: unknown[]): Map<string, string> {
222
- return new Map(castTo<{ name: string }[]>(records).map(record => [record.name, '']));
202
+ return new Map(
203
+ castTo<{ name: string; tableName: string; nonUnique: number; indexColumns: string }[]>(records).map(record => {
204
+ const isUnique = Number(record.nonUnique) === 0;
205
+ const indexDefinition = `CREATE ${isUnique ? 'UNIQUE ' : ''}INDEX ${this.escapeIdentifier(record.name)} ON ${this.escapeIdentifier(record.tableName)} (${record.indexColumns});`;
206
+ return [record.name, indexDefinition];
207
+ })
208
+ );
223
209
  }
224
210
 
225
211
  override getDropIndexSQL(context: TableContext, indexName: string): string {
226
212
  return `DROP INDEX ${this.escapeIdentifier(indexName)} ON ${this.escapeIdentifier(context.tableName)};`;
227
213
  }
214
+
215
+ override getTruncateTableSQL(context: TableContext): string {
216
+ return `TRUNCATE TABLE ${this.escapeIdentifier(context.tableName)};`;
217
+ }
218
+
219
+ override getAlterColumnTypeSQL(context: TableContext, columnName: string, columnType: string, existingType: string): string | undefined {
220
+ const normalizedExisting = existingType.replaceAll('CHARACTER VARYING', 'VARCHAR').replaceAll('INTEGER', 'INT');
221
+ const normalizedRequested = columnType.toUpperCase().replaceAll('CHARACTER VARYING', 'VARCHAR').replaceAll('INTEGER', 'INT');
222
+
223
+ if (!normalizedExisting.startsWith(normalizedRequested) && !normalizedRequested.startsWith(normalizedExisting)) {
224
+ return `ALTER TABLE ${this.escapeIdentifier(context.tableName)} MODIFY COLUMN ${this.escapeIdentifier(columnName)} ${columnType};`;
225
+ }
226
+ return undefined;
227
+ }
228
228
  }