@travetto/model-mysql 7.0.0-rc.2 → 7.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,9 +74,9 @@ export class SQLModelConfig<T extends {} = {}> {
74
74
  */
75
75
  database = 'app';
76
76
  /**
77
- * Auto schema creation
77
+ * Allow storage modification at runtime
78
78
  */
79
- autoCreate?: boolean;
79
+ modifyStorage?: boolean;
80
80
  /**
81
81
  * Db version
82
82
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-mysql",
3
- "version": "7.0.0-rc.2",
3
+ "version": "7.0.0-rc.3",
4
4
  "description": "MySQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
5
5
  "keywords": [
6
6
  "sql",
@@ -27,12 +27,12 @@
27
27
  "directory": "module/model-mysql"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/cli": "^7.0.0-rc.2",
31
- "@travetto/config": "^7.0.0-rc.2",
32
- "@travetto/context": "^7.0.0-rc.2",
33
- "@travetto/model": "^7.0.0-rc.2",
34
- "@travetto/model-query": "^7.0.0-rc.2",
35
- "@travetto/model-sql": "^7.0.0-rc.2",
30
+ "@travetto/cli": "^7.0.0-rc.3",
31
+ "@travetto/config": "^7.0.0-rc.3",
32
+ "@travetto/context": "^7.0.0-rc.3",
33
+ "@travetto/model": "^7.0.0-rc.3",
34
+ "@travetto/model-query": "^7.0.0-rc.3",
35
+ "@travetto/model-sql": "^7.0.0-rc.3",
36
36
  "mysql2": "^3.15.3"
37
37
  },
38
38
  "travetto": {
package/src/connection.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createPool } from 'mysql2';
2
2
  import { PoolConnection, Pool, OkPacket, ResultSetHeader } from 'mysql2/promise';
3
3
 
4
- import { castTo, ShutdownManager } from '@travetto/runtime';
4
+ import { castTo, JSONUtil, ShutdownManager } from '@travetto/runtime';
5
5
  import { AsyncContext } from '@travetto/context';
6
6
  import { ExistsError } from '@travetto/model';
7
7
  import { Connection, SQLModelConfig } from '@travetto/model-sql';
@@ -52,7 +52,7 @@ export class MySQLConnection extends Connection<PoolConnection> {
52
52
  if (typeof result === 'string' && (field && typeof field === 'object' && 'type' in field) && (field.type === 'JSON' || field.type === 'BLOB')) {
53
53
  if (result.charAt(0) === '{' && result.charAt(result.length - 1) === '}') {
54
54
  try {
55
- return JSON.parse(result);
55
+ return JSONUtil.parseSafe(result);
56
56
  } catch { }
57
57
  }
58
58
  }
@@ -76,10 +76,11 @@ export class MySQLConnection extends Connection<PoolConnection> {
76
76
  }
77
77
  } catch (error) {
78
78
  console.debug('Failed query', { error, query });
79
- if (error instanceof Error && error.message.startsWith('Duplicate entry')) {
80
- throw new ExistsError('query', query);
81
- } else {
82
- throw error;
79
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
80
+ switch (code) {
81
+ case 'ER_DUP_ENTRY': throw new ExistsError('query', query);
82
+ case 'ER_DUP_KEYNAME': throw new ExistsError('index', query);
83
+ default: throw error;
83
84
  }
84
85
  } finally {
85
86
  try {
package/src/dialect.ts CHANGED
@@ -3,8 +3,8 @@ import { Injectable } from '@travetto/di';
3
3
  import { AsyncContext } from '@travetto/context';
4
4
  import { WhereClause } from '@travetto/model-query';
5
5
  import { castTo, Class } from '@travetto/runtime';
6
- import { ModelType } from '@travetto/model';
7
- import { SQLModelConfig, SQLDialect, VisitStack } from '@travetto/model-sql';
6
+ import { ModelType, type IndexConfig } from '@travetto/model';
7
+ import { SQLModelConfig, SQLDialect, VisitStack, type SQLTableDescription, SQLModelUtil } from '@travetto/model-sql';
8
8
 
9
9
  import { MySQLConnection } from './connection.ts';
10
10
 
@@ -61,6 +61,85 @@ export class MySQLDialect extends SQLDialect {
61
61
  return `SHA2('${value}', '256')`;
62
62
  }
63
63
 
64
+ /**
65
+ * Get DROP INDEX sql
66
+ */
67
+ getDropIndexSQL<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string): string {
68
+ const constraint = typeof idx === 'string' ? idx : this.getIndexName(cls, idx);
69
+ return `DROP INDEX ${this.identifier(constraint)} ON ${this.table(SQLModelUtil.classToStack(cls))};`;
70
+ }
71
+
72
+ async describeTable(table: string): Promise<SQLTableDescription | undefined> {
73
+ const IGNORE_FIELDS = [this.pathField.name, this.parentPathField.name, this.idxField.name].map(field => `'${field}'`);
74
+ const [columns, foreignKeys, indices] = await Promise.all([
75
+ // 1. Columns
76
+ this.executeSQL<{ name: string, type: string, is_notnull: boolean }>(`
77
+ SELECT
78
+ COLUMN_NAME AS name,
79
+ COLUMN_TYPE AS type,
80
+ IS_NULLABLE <> 'YES' AS is_notnull
81
+ FROM information_schema.COLUMNS
82
+ WHERE TABLE_NAME = '${table}'
83
+ AND TABLE_SCHEMA = DATABASE()
84
+ AND COLUMN_NAME NOT IN (${IGNORE_FIELDS.join(',')})
85
+ ORDER BY ORDINAL_POSITION
86
+ `),
87
+
88
+ // 2. Foreign Keys
89
+ this.executeSQL<{ name: string, from_column: string, to_column: string, to_table: string }>(`
90
+ SELECT
91
+ CONSTRAINT_NAME AS name,
92
+ COLUMN_NAME AS from_column,
93
+ REFERENCED_COLUMN_NAME AS to_column,
94
+ REFERENCED_TABLE_NAME AS to_table
95
+ FROM information_schema.KEY_COLUMN_USAGE
96
+ WHERE TABLE_NAME = '${table}'
97
+ AND TABLE_SCHEMA = DATABASE()
98
+ AND REFERENCED_TABLE_NAME IS NOT NULL
99
+ `),
100
+
101
+ // 3. Indices
102
+ this.executeSQL<{ name: string, is_unique: number, columns: string }>(`
103
+ SELECT
104
+ stat.INDEX_NAME AS name,
105
+ stat.NON_UNIQUE = 0 AS is_unique,
106
+ GROUP_CONCAT(CONCAT(stat.COLUMN_NAME, ' ', stat.COLLATION, ' ') ORDER BY stat.SEQ_IN_INDEX) AS columns
107
+ FROM information_schema.STATISTICS stat
108
+ LEFT OUTER JOIN information_schema.TABLE_CONSTRAINTS AS tc
109
+ ON tc.CONSTRAINT_NAME = stat.INDEX_NAME
110
+ AND tc.TABLE_NAME = stat.TABLE_NAME
111
+ AND tc.TABLE_SCHEMA = stat.TABLE_SCHEMA
112
+ WHERE
113
+ stat.TABLE_NAME = '${table}'
114
+ AND stat.TABLE_SCHEMA = DATABASE()
115
+ AND tc.CONSTRAINT_TYPE IS NULL
116
+ AND stat.COLUMN_NAME NOT IN (${IGNORE_FIELDS.join(',')})
117
+ GROUP BY stat.INDEX_NAME, stat.NON_UNIQUE
118
+ `)
119
+ ]);
120
+
121
+ if (!columns.count) {
122
+ return undefined;
123
+ }
124
+
125
+ return {
126
+ columns: columns.records.map(col => ({
127
+ ...col,
128
+ type: col.type.toUpperCase(),
129
+ is_notnull: !!col.is_notnull
130
+ })),
131
+ foreignKeys: foreignKeys.records,
132
+ indices: indices.records.map(idx => ({
133
+ name: idx.name,
134
+ is_unique: !!idx.is_unique,
135
+ columns: idx.columns
136
+ .split(',')
137
+ .map(column => column.split(' '))
138
+ .map(([name, desc]) => ({ name, desc: desc === 'D' }))
139
+ }))
140
+ };
141
+ }
142
+
64
143
  /**
65
144
  * Create table, adding in specific engine options
66
145
  */
@@ -8,7 +8,9 @@ export const service: ServiceDescriptor = {
8
8
  image: `mysql:${version}`,
9
9
  port: 3306,
10
10
  env: {
11
- MYSQL_ROOT_PASSWORD: 'password',
11
+ MYSQL_RANDOM_ROOT_PASSWORD: '1',
12
+ MYSQL_PASSWORD: 'travetto',
13
+ MYSQL_USER: 'travetto',
12
14
  MYSQL_DATABASE: 'app'
13
15
  },
14
16
  };