@mlagie/sql-connector 3.2.0 → 3.4.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.
- package/index.d.ts +13 -0
- package/package.json +1 -1
- package/src/migration/MigrationRunner.js +26 -5
- package/src/migration/MigrationStore.js +6 -1
- package/src/migration/SchemaDiffer.js +34 -29
- package/src/utils/buildQuery/buildAggregationField.js +11 -0
- package/src/utils/buildQuery/buildQuery.js +4 -2
- package/src/utils/buildQuery/count.js +0 -11
package/index.d.ts
CHANGED
|
@@ -362,6 +362,19 @@ export type SelectAggregation = {
|
|
|
362
362
|
*/
|
|
363
363
|
count?: string | string[] | Record<string, any>;
|
|
364
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Calculates MAX(column).
|
|
367
|
+
*
|
|
368
|
+
* A string, list of strings or object may be supplied
|
|
369
|
+
* depending on the query builder expression.
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* { max: "created_at", as: "latest" }
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
max?: string | string[] | Record<string, any>;
|
|
377
|
+
|
|
365
378
|
/**
|
|
366
379
|
* Alias assigned to the resulting expression.
|
|
367
380
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlagie/sql-connector",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "The sql-connector module allows you to manage connections to a MySQL database, define table schemas, and interact with data in a simple and efficient way.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -61,6 +61,11 @@ class MigrationRunner {
|
|
|
61
61
|
plan.id
|
|
62
62
|
);
|
|
63
63
|
|
|
64
|
+
// The rollback files are written BEFORE the schema is touched: if the
|
|
65
|
+
// process dies or the write fails, nothing has been changed yet and the
|
|
66
|
+
// database never ends up "applied" without its rollback metadata.
|
|
67
|
+
await this.store.writeMigration(plan);
|
|
68
|
+
|
|
64
69
|
const run = async () => {
|
|
65
70
|
for (const operation of plan.operations) {
|
|
66
71
|
const sql = this.sqlForOperation(operation);
|
|
@@ -75,15 +80,31 @@ class MigrationRunner {
|
|
|
75
80
|
);
|
|
76
81
|
};
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
try {
|
|
84
|
+
await this.dialect.transaction(this.connection, run);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
await this.store.removeMigration(plan.id).catch(() => {});
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const warnings = [];
|
|
91
|
+
try {
|
|
92
|
+
await this.store.writeSnapshot(plan.snapshot);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
warnings.push(`Migration applied but schema snapshot could not be written: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await this.store.cleanup();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
warnings.push(`Migration applied but cleanup failed: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
for (const warning of warnings) console.warn(warning);
|
|
82
102
|
|
|
83
103
|
return {
|
|
84
104
|
applied: true,
|
|
85
105
|
plan,
|
|
86
|
-
backupPath
|
|
106
|
+
backupPath,
|
|
107
|
+
...(warnings.length > 0 ? { warnings } : {})
|
|
87
108
|
};
|
|
88
109
|
}
|
|
89
110
|
|
|
@@ -43,6 +43,11 @@ class MigrationStore {
|
|
|
43
43
|
return outputPath;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
async removeMigration(id) {
|
|
47
|
+
await fs.rm(path.join(this.directory, `${id}.sql.enc`), { force: true });
|
|
48
|
+
await fs.rm(path.join(this.directory, `${id}.meta.json.enc`), { force: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
async readMigration(id) {
|
|
47
52
|
await this.ensureDirectories();
|
|
48
53
|
if (!this.encryptionKey) throw new Error("An encryption key is required to read migration files.");
|
|
@@ -134,4 +139,4 @@ class MigrationStore {
|
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
module.exports = { MigrationStore };
|
|
142
|
+
module.exports = { MigrationStore };
|
|
@@ -171,37 +171,13 @@ class SchemaDiffer {
|
|
|
171
171
|
|
|
172
172
|
/*
|
|
173
173
|
* Second pass:
|
|
174
|
-
* detect additions and modifications.
|
|
175
|
-
*/
|
|
176
|
-
for (const [columnName, field] of Object.entries(desiredColumns)) {
|
|
177
|
-
const currentField = getSafe(virtualCurrentColumns, columnName);
|
|
178
|
-
|
|
179
|
-
if (!currentField) {
|
|
180
|
-
operations.push({
|
|
181
|
-
type: "addColumn",
|
|
182
|
-
table: tableName,
|
|
183
|
-
column: columnName,
|
|
184
|
-
field
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (!equivalentField(field, currentField)) {
|
|
191
|
-
operations.push({
|
|
192
|
-
type: "alterColumn",
|
|
193
|
-
table: tableName,
|
|
194
|
-
column: columnName,
|
|
195
|
-
field,
|
|
196
|
-
previous: currentField
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/*
|
|
202
|
-
* Third pass:
|
|
203
174
|
* detect removed columns.
|
|
204
175
|
*
|
|
176
|
+
* Deletions are emitted BEFORE additions/alterations so that a
|
|
177
|
+
* dropped primary key column (e.g. `uuid`) releases its PRIMARY KEY
|
|
178
|
+
* before a new one (e.g. `id`) is added; otherwise PostgreSQL fails
|
|
179
|
+
* with "multiple primary keys for table".
|
|
180
|
+
*
|
|
205
181
|
* Renamed columns are excluded because their old name
|
|
206
182
|
* intentionally disappears from the desired schema.
|
|
207
183
|
*/
|
|
@@ -228,6 +204,35 @@ class SchemaDiffer {
|
|
|
228
204
|
operations.push(operation);
|
|
229
205
|
}
|
|
230
206
|
}
|
|
207
|
+
/*
|
|
208
|
+
* Third pass:
|
|
209
|
+
* detect additions and modifications (after the deletions).
|
|
210
|
+
*/
|
|
211
|
+
for (const [columnName, field] of Object.entries(desiredColumns)) {
|
|
212
|
+
const currentField = getSafe(virtualCurrentColumns, columnName);
|
|
213
|
+
|
|
214
|
+
if (!currentField) {
|
|
215
|
+
operations.push({
|
|
216
|
+
type: "addColumn",
|
|
217
|
+
table: tableName,
|
|
218
|
+
column: columnName,
|
|
219
|
+
field
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!equivalentField(field, currentField)) {
|
|
226
|
+
operations.push({
|
|
227
|
+
type: "alterColumn",
|
|
228
|
+
table: tableName,
|
|
229
|
+
column: columnName,
|
|
230
|
+
field,
|
|
231
|
+
previous: currentField
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
231
236
|
}
|
|
232
237
|
|
|
233
238
|
/*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { getDialect } = require("../../db/dialects");
|
|
2
|
+
|
|
3
|
+
module.exports = (aggregateType, field) => {
|
|
4
|
+
if (typeof field === 'string') return `${aggregateType}(${getDialect().escape(field)})`;
|
|
5
|
+
if (field instanceof Array && field !== null) return field.map(col => `${aggregateType}(${getDialect().escape(col)})`).join(' + ');
|
|
6
|
+
if (field instanceof Object) {
|
|
7
|
+
const [key, value] = Object.entries(field)[0];
|
|
8
|
+
return `${aggregateType}(CASE WHEN ${getDialect().escape(key)} = ${getDialect().escapeValue(value)} THEN 1 END)`;
|
|
9
|
+
}
|
|
10
|
+
throw new Error(`Invalid field type for ${aggregateType}. Must be a string, array, or object.`);
|
|
11
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { getDialect } = require("../../db/dialects");
|
|
2
2
|
const { escapeOrderDirection } = require("../sql");
|
|
3
|
-
const
|
|
3
|
+
const buildAggregationField = require("./buildAggregationField");
|
|
4
4
|
|
|
5
5
|
function buildGroupByItem(group) {
|
|
6
6
|
let sql;
|
|
@@ -43,7 +43,9 @@ function buildField(field) {
|
|
|
43
43
|
else if (field.distinct)
|
|
44
44
|
sql = `DISTINCT ${getDialect().escape(field.distinct)}`;
|
|
45
45
|
else if (field.count)
|
|
46
|
-
sql =
|
|
46
|
+
sql = buildAggregationField("COUNT", field.count);
|
|
47
|
+
else if (field.max)
|
|
48
|
+
sql = buildAggregationField("MAX", field.max);
|
|
47
49
|
if (field.as)
|
|
48
50
|
sql += ` AS ${getDialect().escape(field.as)}`;
|
|
49
51
|
else if (field.sum)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const { getDialect } = require("../../db/dialects");
|
|
2
|
-
|
|
3
|
-
module.exports = (field) => {
|
|
4
|
-
if (typeof field === 'string') return `COUNT(${getDialect().escape(field)})`;
|
|
5
|
-
if (field instanceof Array && field !== null) return field.map(col => `COUNT(${getDialect().escape(col)})`).join(' + ');
|
|
6
|
-
if (field instanceof Object) {
|
|
7
|
-
const [key, value] = Object.entries(field)[0];
|
|
8
|
-
return `COUNT(CASE WHEN ${getDialect().escape(key)} = ${getDialect().escapeValue(value)} THEN 1 END)`;
|
|
9
|
-
}
|
|
10
|
-
throw new Error("Invalid field type for COUNT. Must be a string, array, or object.");
|
|
11
|
-
}
|