masterrecord 0.2.36 → 0.3.1
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/.claude/settings.local.json +20 -1
- package/Entity/entityModel.js +6 -0
- package/Entity/entityTrackerModel.js +20 -3
- package/Entity/fieldTransformer.js +266 -0
- package/Migrations/migrationMySQLQuery.js +145 -1
- package/Migrations/migrationPostgresQuery.js +402 -0
- package/Migrations/migrationSQLiteQuery.js +145 -1
- package/Migrations/schema.js +131 -28
- package/QueryLanguage/queryMethods.js +193 -15
- package/QueryLanguage/queryParameters.js +136 -0
- package/QueryLanguage/queryScript.js +13 -4
- package/SQLLiteEngine.js +331 -20
- package/context.js +91 -14
- package/docs/INCLUDES_CLARIFICATION.md +202 -0
- package/docs/METHODS_REFERENCE.md +184 -0
- package/docs/MIGRATIONS_GUIDE.md +699 -0
- package/docs/POSTGRESQL_SETUP.md +415 -0
- package/examples/jsonArrayTransformer.js +215 -0
- package/mySQLEngine.js +273 -17
- package/package.json +3 -3
- package/postgresEngine.js +600 -483
- package/postgresSyncConnect.js +209 -0
- package/readme.md +1046 -416
- package/test/anyCommaStringTest.js +237 -0
- package/test/anyMethodTest.js +176 -0
- package/test/findByIdTest.js +227 -0
- package/test/includesFeatureTest.js +183 -0
- package/test/includesTransformTest.js +110 -0
- package/test/newMethodTest.js +330 -0
- package/test/newMethodUnitTest.js +320 -0
- package/test/parameterizedPlaceholderTest.js +159 -0
- package/test/postgresEngineTest.js +463 -0
- package/test/postgresIntegrationTest.js +381 -0
- package/test/securityTest.js +268 -0
- package/test/singleDollarPlaceholderTest.js +238 -0
- package/test/transformerTest.js +287 -0
- package/test/verifyFindById.js +169 -0
- package/test/verifyNewMethod.js +191 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// Version 1.0.0 - PostgreSQL migration query builder
|
|
2
|
+
class migrationPostgresQuery {
|
|
3
|
+
|
|
4
|
+
#tempTableName = "_temp_alter_column_update"
|
|
5
|
+
|
|
6
|
+
#getTableColumns(table){
|
|
7
|
+
var columnList = [];
|
|
8
|
+
for (var key in table) {
|
|
9
|
+
if(typeof table[key] === "object"){
|
|
10
|
+
var col = table[key];
|
|
11
|
+
// Skip relationship-only fields
|
|
12
|
+
if(col.type === 'hasOne' || col.type === 'hasMany' || col.type === 'hasManyThrough'){
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
// Map belongsTo to its foreignKey name
|
|
16
|
+
var name = (col.relationshipType === 'belongsTo' && col.foreignKey) ? col.foreignKey : col.name;
|
|
17
|
+
columnList.push(`"${name}"`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return columnList.join(',');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#columnMapping(table){
|
|
24
|
+
/*
|
|
25
|
+
var mapping = {
|
|
26
|
+
"name": "id", // if this changes then call rename column
|
|
27
|
+
"type": "integer", // if this changes then call altercolumn
|
|
28
|
+
"primary": false, // is primary key
|
|
29
|
+
"nullable": false, // is nullable
|
|
30
|
+
"unique": true, // value has to be unique
|
|
31
|
+
"auto": true, // sets the value to SERIAL/BIGSERIAL
|
|
32
|
+
"cascadeOnDelete": true,
|
|
33
|
+
"lazyLoading": true,
|
|
34
|
+
"isNavigational": false
|
|
35
|
+
}
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// PostgreSQL uses SERIAL for auto-increment, not separate AUTO_INCREMENT keyword
|
|
39
|
+
var auto = "";
|
|
40
|
+
var primaryKey = table.primary ? " PRIMARY KEY" : "";
|
|
41
|
+
var nullName = table.nullable ? "" : " NOT NULL";
|
|
42
|
+
var unique = table.unique ? " UNIQUE" : "";
|
|
43
|
+
|
|
44
|
+
// For PostgreSQL, if auto-increment primary key, use SERIAL or BIGSERIAL
|
|
45
|
+
var type;
|
|
46
|
+
if(table.auto && table.primary && (table.type === 'integer' || table.type === 'int')){
|
|
47
|
+
type = "SERIAL"; // Auto-incrementing integer
|
|
48
|
+
auto = "";
|
|
49
|
+
primaryKey = " PRIMARY KEY";
|
|
50
|
+
} else if(table.auto && table.primary && table.type === 'bigint'){
|
|
51
|
+
type = "BIGSERIAL";
|
|
52
|
+
auto = "";
|
|
53
|
+
primaryKey = " PRIMARY KEY";
|
|
54
|
+
} else {
|
|
55
|
+
type = this.typeManager(table.type);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
var tableName = table.name;
|
|
59
|
+
if(table.relationshipType === 'belongsTo' && table.foreignKey){
|
|
60
|
+
tableName = table.foreignKey;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var defaultValue = "";
|
|
64
|
+
if(table.default !== undefined && table.default !== null){
|
|
65
|
+
let def = table.default;
|
|
66
|
+
if(table.type === 'boolean' || table.type === 'bool'){
|
|
67
|
+
// PostgreSQL uses TRUE/FALSE for booleans
|
|
68
|
+
def = (def === true || def === 'true') ? 'TRUE' : 'FALSE';
|
|
69
|
+
defaultValue = ` DEFAULT ${def}`;
|
|
70
|
+
}
|
|
71
|
+
else if(table.type === 'integer' || table.type === 'float' || table.type === 'decimal'){
|
|
72
|
+
defaultValue = ` DEFAULT ${def}`;
|
|
73
|
+
}
|
|
74
|
+
else{
|
|
75
|
+
const esc = String(def).replace(/'/g, "''");
|
|
76
|
+
defaultValue = ` DEFAULT '${esc}'`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return `"${tableName}" ${type}${nullName}${defaultValue}${unique}${primaryKey}${auto}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
typeManager(type){
|
|
84
|
+
switch(type) {
|
|
85
|
+
case "string":
|
|
86
|
+
return "VARCHAR(255)"
|
|
87
|
+
case "text":
|
|
88
|
+
return "TEXT"
|
|
89
|
+
case "float":
|
|
90
|
+
return "REAL" // PostgreSQL uses REAL for single-precision
|
|
91
|
+
case "decimal":
|
|
92
|
+
return "DECIMAL"
|
|
93
|
+
case "datetime":
|
|
94
|
+
return "TIMESTAMP"
|
|
95
|
+
case "timestamp":
|
|
96
|
+
return "TIMESTAMP"
|
|
97
|
+
case "date":
|
|
98
|
+
return "DATE"
|
|
99
|
+
case "time":
|
|
100
|
+
return "TIME"
|
|
101
|
+
case "boolean":
|
|
102
|
+
case "bool":
|
|
103
|
+
return "BOOLEAN" // PostgreSQL native boolean type
|
|
104
|
+
case "integer":
|
|
105
|
+
case "int":
|
|
106
|
+
return "INTEGER"
|
|
107
|
+
case "bigint":
|
|
108
|
+
return "BIGINT"
|
|
109
|
+
case "binary":
|
|
110
|
+
return "BYTEA" // PostgreSQL binary data type
|
|
111
|
+
case "blob":
|
|
112
|
+
return "BYTEA"
|
|
113
|
+
case "json":
|
|
114
|
+
return "JSON"
|
|
115
|
+
case "jsonb":
|
|
116
|
+
return "JSONB" // PostgreSQL binary JSON (more efficient)
|
|
117
|
+
case "uuid":
|
|
118
|
+
return "UUID" // PostgreSQL native UUID type
|
|
119
|
+
default:
|
|
120
|
+
return "TEXT"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// table is the altered field
|
|
125
|
+
alterColumn(table){
|
|
126
|
+
if(table){
|
|
127
|
+
// PostgreSQL uses different syntax for ALTER COLUMN
|
|
128
|
+
// ALTER TABLE table_name ALTER COLUMN column_name TYPE new_type;
|
|
129
|
+
const colName = table.table.name;
|
|
130
|
+
const tableName = table.tableName;
|
|
131
|
+
const type = this.typeManager(table.table.type);
|
|
132
|
+
|
|
133
|
+
// Build ALTER statements - PostgreSQL requires separate statements for different changes
|
|
134
|
+
let statements = [];
|
|
135
|
+
|
|
136
|
+
// Change type
|
|
137
|
+
statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" TYPE ${type}`);
|
|
138
|
+
|
|
139
|
+
// Change nullability
|
|
140
|
+
if(table.table.nullable === false){
|
|
141
|
+
statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET NOT NULL`);
|
|
142
|
+
} else if(table.table.nullable === true){
|
|
143
|
+
statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" DROP NOT NULL`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Change default
|
|
147
|
+
if(table.table.default !== undefined && table.table.default !== null){
|
|
148
|
+
let def = table.table.default;
|
|
149
|
+
if(table.table.type === 'boolean'){
|
|
150
|
+
def = (def === true || def === 'true') ? 'TRUE' : 'FALSE';
|
|
151
|
+
statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET DEFAULT ${def}`);
|
|
152
|
+
} else if(table.table.type === 'integer' || table.table.type === 'float'){
|
|
153
|
+
statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET DEFAULT ${def}`);
|
|
154
|
+
} else {
|
|
155
|
+
const esc = String(def).replace(/'/g, "''");
|
|
156
|
+
statements.push(`ALTER TABLE "${tableName}" ALTER COLUMN "${colName}" SET DEFAULT '${esc}'`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Return array of statements or join them with semicolons
|
|
161
|
+
return statements.join('; ');
|
|
162
|
+
}
|
|
163
|
+
else{
|
|
164
|
+
console.log("table information is null");
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
addColum(table){
|
|
170
|
+
// PostgreSQL add column syntax
|
|
171
|
+
return `ALTER TABLE "${table.tableName}"
|
|
172
|
+
ADD COLUMN "${table.name}" ${table.realDataType}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
dropColumn(table){
|
|
176
|
+
/*
|
|
177
|
+
PostgreSQL DROP COLUMN is more flexible than SQLite
|
|
178
|
+
Can drop columns with constraints if CASCADE is used
|
|
179
|
+
*/
|
|
180
|
+
return `ALTER TABLE "${table.tableName}" DROP COLUMN "${table.name}"`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
insertInto(name, table){
|
|
184
|
+
return `INSERT INTO "${name}" (${this.#getTableColumns(table)})
|
|
185
|
+
SELECT ${this.#getTableColumns(table)} FROM "${this.#tempTableName}"`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
createTable(table){
|
|
189
|
+
var queryVar = "";
|
|
190
|
+
|
|
191
|
+
for (var key in table) {
|
|
192
|
+
if(typeof table[key] === "object"){
|
|
193
|
+
if(table[key].type !== "hasOne" && table[key].type !== "hasMany" && table[key].type !== "hasManyThrough"){
|
|
194
|
+
queryVar += `${this.#columnMapping(table[key])}, `;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
var completeQuery = `CREATE TABLE IF NOT EXISTS "${table.__name}" (${queryVar.replace(/,\s*$/, "")});`;
|
|
200
|
+
return completeQuery;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
dropTable(name){
|
|
204
|
+
return `DROP TABLE IF EXISTS "${name}"`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
renameTable(table){
|
|
208
|
+
return `ALTER TABLE "${table.tableName}" RENAME TO "${table.newName}"`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
renameColumn(table){
|
|
212
|
+
return `ALTER TABLE "${table.tableName}" RENAME COLUMN "${table.name}" TO "${table.newName}"`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* SEED DATA METHODS
|
|
217
|
+
* Support for inserting seed data during migrations
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Insert seed data into a table
|
|
222
|
+
* @param {string} tableName - Name of the table
|
|
223
|
+
* @param {Object} data - Data object with column names as keys
|
|
224
|
+
* @returns {string} INSERT query
|
|
225
|
+
*/
|
|
226
|
+
insertSeedData(tableName, data){
|
|
227
|
+
const columns = Object.keys(data).filter(k => !k.startsWith('__'));
|
|
228
|
+
const values = columns.map(col => {
|
|
229
|
+
const val = data[col];
|
|
230
|
+
if(val === null || val === undefined){
|
|
231
|
+
return 'NULL';
|
|
232
|
+
}
|
|
233
|
+
if(typeof val === 'boolean'){
|
|
234
|
+
return val ? 'TRUE' : 'FALSE';
|
|
235
|
+
}
|
|
236
|
+
if(typeof val === 'number'){
|
|
237
|
+
return val;
|
|
238
|
+
}
|
|
239
|
+
// Escape strings
|
|
240
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
241
|
+
return `'${escaped}'`;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const columnList = columns.map(c => `"${c}"`).join(', ');
|
|
245
|
+
const valueList = values.join(', ');
|
|
246
|
+
|
|
247
|
+
return `INSERT INTO "${tableName}" (${columnList}) VALUES (${valueList})`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Insert multiple seed records at once
|
|
252
|
+
* @param {string} tableName - Name of the table
|
|
253
|
+
* @param {Array} dataArray - Array of data objects
|
|
254
|
+
* @returns {string} Bulk INSERT query
|
|
255
|
+
*/
|
|
256
|
+
bulkInsertSeedData(tableName, dataArray){
|
|
257
|
+
if(!dataArray || dataArray.length === 0){
|
|
258
|
+
return '';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const firstRow = dataArray[0];
|
|
262
|
+
const columns = Object.keys(firstRow).filter(k => !k.startsWith('__'));
|
|
263
|
+
const columnList = columns.map(c => `"${c}"`).join(', ');
|
|
264
|
+
|
|
265
|
+
const valueRows = dataArray.map(data => {
|
|
266
|
+
const values = columns.map(col => {
|
|
267
|
+
const val = data[col];
|
|
268
|
+
if(val === null || val === undefined){
|
|
269
|
+
return 'NULL';
|
|
270
|
+
}
|
|
271
|
+
if(typeof val === 'boolean'){
|
|
272
|
+
return val ? 'TRUE' : 'FALSE';
|
|
273
|
+
}
|
|
274
|
+
if(typeof val === 'number'){
|
|
275
|
+
return val;
|
|
276
|
+
}
|
|
277
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
278
|
+
return `'${escaped}'`;
|
|
279
|
+
});
|
|
280
|
+
return `(${values.join(', ')})`;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return `INSERT INTO "${tableName}" (${columnList}) VALUES ${valueRows.join(', ')}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Update seed data (useful for down migrations)
|
|
288
|
+
* @param {string} tableName - Name of the table
|
|
289
|
+
* @param {Object} data - Data to update
|
|
290
|
+
* @param {Object} where - WHERE conditions
|
|
291
|
+
* @returns {string} UPDATE query
|
|
292
|
+
*/
|
|
293
|
+
updateSeedData(tableName, data, where){
|
|
294
|
+
const setClause = Object.keys(data)
|
|
295
|
+
.filter(k => !k.startsWith('__'))
|
|
296
|
+
.map(col => {
|
|
297
|
+
const val = data[col];
|
|
298
|
+
if(val === null || val === undefined){
|
|
299
|
+
return `"${col}" = NULL`;
|
|
300
|
+
}
|
|
301
|
+
if(typeof val === 'boolean'){
|
|
302
|
+
return `"${col}" = ${val ? 'TRUE' : 'FALSE'}`;
|
|
303
|
+
}
|
|
304
|
+
if(typeof val === 'number'){
|
|
305
|
+
return `"${col}" = ${val}`;
|
|
306
|
+
}
|
|
307
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
308
|
+
return `"${col}" = '${escaped}'`;
|
|
309
|
+
})
|
|
310
|
+
.join(', ');
|
|
311
|
+
|
|
312
|
+
const whereClause = Object.keys(where)
|
|
313
|
+
.map(col => {
|
|
314
|
+
const val = where[col];
|
|
315
|
+
if(val === null || val === undefined){
|
|
316
|
+
return `"${col}" IS NULL`;
|
|
317
|
+
}
|
|
318
|
+
if(typeof val === 'boolean'){
|
|
319
|
+
return `"${col}" = ${val ? 'TRUE' : 'FALSE'}`;
|
|
320
|
+
}
|
|
321
|
+
if(typeof val === 'number'){
|
|
322
|
+
return `"${col}" = ${val}`;
|
|
323
|
+
}
|
|
324
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
325
|
+
return `"${col}" = '${escaped}'`;
|
|
326
|
+
})
|
|
327
|
+
.join(' AND ');
|
|
328
|
+
|
|
329
|
+
return `UPDATE "${tableName}" SET ${setClause} WHERE ${whereClause}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Delete seed data (useful for down migrations)
|
|
334
|
+
* @param {string} tableName - Name of the table
|
|
335
|
+
* @param {Object} where - WHERE conditions
|
|
336
|
+
* @returns {string} DELETE query
|
|
337
|
+
*/
|
|
338
|
+
deleteSeedData(tableName, where){
|
|
339
|
+
const whereClause = Object.keys(where)
|
|
340
|
+
.map(col => {
|
|
341
|
+
const val = where[col];
|
|
342
|
+
if(val === null || val === undefined){
|
|
343
|
+
return `"${col}" IS NULL`;
|
|
344
|
+
}
|
|
345
|
+
if(typeof val === 'boolean'){
|
|
346
|
+
return `"${col}" = ${val ? 'TRUE' : 'FALSE'}`;
|
|
347
|
+
}
|
|
348
|
+
if(typeof val === 'number'){
|
|
349
|
+
return `"${col}" = ${val}`;
|
|
350
|
+
}
|
|
351
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
352
|
+
return `"${col}" = '${escaped}'`;
|
|
353
|
+
})
|
|
354
|
+
.join(' AND ');
|
|
355
|
+
|
|
356
|
+
return `DELETE FROM "${tableName}" WHERE ${whereClause}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
module.exports = migrationPostgresQuery;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* PostgreSQL Data Types Reference:
|
|
364
|
+
*
|
|
365
|
+
* NATIVE_DATABASE_TYPES = {
|
|
366
|
+
* primary_key: "SERIAL PRIMARY KEY" or "BIGSERIAL PRIMARY KEY",
|
|
367
|
+
* string: VARCHAR(255),
|
|
368
|
+
* text: TEXT,
|
|
369
|
+
* integer: INTEGER,
|
|
370
|
+
* bigint: BIGINT,
|
|
371
|
+
* float: REAL,
|
|
372
|
+
* decimal: DECIMAL,
|
|
373
|
+
* datetime: TIMESTAMP,
|
|
374
|
+
* timestamp: TIMESTAMP,
|
|
375
|
+
* timestamptz: TIMESTAMPTZ,
|
|
376
|
+
* time: TIME,
|
|
377
|
+
* date: DATE,
|
|
378
|
+
* binary: BYTEA,
|
|
379
|
+
* boolean: BOOLEAN,
|
|
380
|
+
* json: JSON,
|
|
381
|
+
* jsonb: JSONB (recommended over JSON for performance),
|
|
382
|
+
* uuid: UUID,
|
|
383
|
+
* xml: XML
|
|
384
|
+
* }
|
|
385
|
+
*
|
|
386
|
+
* Key PostgreSQL Differences:
|
|
387
|
+
* 1. AUTO_INCREMENT → SERIAL or BIGSERIAL
|
|
388
|
+
* 2. Backticks (`) → Double quotes (") for identifiers
|
|
389
|
+
* 3. TINYINT → BOOLEAN (true/false instead of 0/1)
|
|
390
|
+
* 4. BLOB → BYTEA
|
|
391
|
+
* 5. Multiple ALTER COLUMN statements (can't combine TYPE and NOT NULL)
|
|
392
|
+
* 6. Native JSON and JSONB support
|
|
393
|
+
* 7. Native UUID support
|
|
394
|
+
* 8. IF EXISTS supported in DROP TABLE
|
|
395
|
+
* 9. More flexible DROP COLUMN (supports CASCADE)
|
|
396
|
+
*
|
|
397
|
+
* Seed Data Methods:
|
|
398
|
+
* - insertSeedData(tableName, data): Insert single record
|
|
399
|
+
* - bulkInsertSeedData(tableName, dataArray): Insert multiple records
|
|
400
|
+
* - updateSeedData(tableName, data, where): Update existing records
|
|
401
|
+
* - deleteSeedData(tableName, where): Delete records
|
|
402
|
+
*/
|
|
@@ -180,7 +180,151 @@ class migrationSQLiteQuery {
|
|
|
180
180
|
return `ALTER TABLE ${table.tableName} RENAME COLUMN ${table.name} TO ${table.newName}`
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
/**
|
|
184
|
+
* SEED DATA METHODS
|
|
185
|
+
* Support for inserting seed data during migrations
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Insert seed data into a table
|
|
190
|
+
* @param {string} tableName - Name of the table
|
|
191
|
+
* @param {Object} data - Data object with column names as keys
|
|
192
|
+
* @returns {string} INSERT query
|
|
193
|
+
*/
|
|
194
|
+
insertSeedData(tableName, data){
|
|
195
|
+
const columns = Object.keys(data).filter(k => !k.startsWith('__'));
|
|
196
|
+
const values = columns.map(col => {
|
|
197
|
+
const val = data[col];
|
|
198
|
+
if(val === null || val === undefined){
|
|
199
|
+
return 'NULL';
|
|
200
|
+
}
|
|
201
|
+
if(typeof val === 'boolean'){
|
|
202
|
+
return val ? '1' : '0'; // SQLite INTEGER for boolean
|
|
203
|
+
}
|
|
204
|
+
if(typeof val === 'number'){
|
|
205
|
+
return val;
|
|
206
|
+
}
|
|
207
|
+
// Escape strings
|
|
208
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
209
|
+
return `'${escaped}'`;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const columnList = columns.join(', ');
|
|
213
|
+
const valueList = values.join(', ');
|
|
214
|
+
|
|
215
|
+
return `INSERT INTO ${tableName} (${columnList}) VALUES (${valueList})`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Insert multiple seed records at once
|
|
220
|
+
* @param {string} tableName - Name of the table
|
|
221
|
+
* @param {Array} dataArray - Array of data objects
|
|
222
|
+
* @returns {string} Bulk INSERT query
|
|
223
|
+
*/
|
|
224
|
+
bulkInsertSeedData(tableName, dataArray){
|
|
225
|
+
if(!dataArray || dataArray.length === 0){
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const firstRow = dataArray[0];
|
|
230
|
+
const columns = Object.keys(firstRow).filter(k => !k.startsWith('__'));
|
|
231
|
+
const columnList = columns.join(', ');
|
|
232
|
+
|
|
233
|
+
const valueRows = dataArray.map(data => {
|
|
234
|
+
const values = columns.map(col => {
|
|
235
|
+
const val = data[col];
|
|
236
|
+
if(val === null || val === undefined){
|
|
237
|
+
return 'NULL';
|
|
238
|
+
}
|
|
239
|
+
if(typeof val === 'boolean'){
|
|
240
|
+
return val ? '1' : '0';
|
|
241
|
+
}
|
|
242
|
+
if(typeof val === 'number'){
|
|
243
|
+
return val;
|
|
244
|
+
}
|
|
245
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
246
|
+
return `'${escaped}'`;
|
|
247
|
+
});
|
|
248
|
+
return `(${values.join(', ')})`;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return `INSERT INTO ${tableName} (${columnList}) VALUES ${valueRows.join(', ')}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Update seed data (useful for down migrations)
|
|
256
|
+
* @param {string} tableName - Name of the table
|
|
257
|
+
* @param {Object} data - Data to update
|
|
258
|
+
* @param {Object} where - WHERE conditions
|
|
259
|
+
* @returns {string} UPDATE query
|
|
260
|
+
*/
|
|
261
|
+
updateSeedData(tableName, data, where){
|
|
262
|
+
const setClause = Object.keys(data)
|
|
263
|
+
.filter(k => !k.startsWith('__'))
|
|
264
|
+
.map(col => {
|
|
265
|
+
const val = data[col];
|
|
266
|
+
if(val === null || val === undefined){
|
|
267
|
+
return `${col} = NULL`;
|
|
268
|
+
}
|
|
269
|
+
if(typeof val === 'boolean'){
|
|
270
|
+
return `${col} = ${val ? '1' : '0'}`;
|
|
271
|
+
}
|
|
272
|
+
if(typeof val === 'number'){
|
|
273
|
+
return `${col} = ${val}`;
|
|
274
|
+
}
|
|
275
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
276
|
+
return `${col} = '${escaped}'`;
|
|
277
|
+
})
|
|
278
|
+
.join(', ');
|
|
279
|
+
|
|
280
|
+
const whereClause = Object.keys(where)
|
|
281
|
+
.map(col => {
|
|
282
|
+
const val = where[col];
|
|
283
|
+
if(val === null || val === undefined){
|
|
284
|
+
return `${col} IS NULL`;
|
|
285
|
+
}
|
|
286
|
+
if(typeof val === 'boolean'){
|
|
287
|
+
return `${col} = ${val ? '1' : '0'}`;
|
|
288
|
+
}
|
|
289
|
+
if(typeof val === 'number'){
|
|
290
|
+
return `${col} = ${val}`;
|
|
291
|
+
}
|
|
292
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
293
|
+
return `${col} = '${escaped}'`;
|
|
294
|
+
})
|
|
295
|
+
.join(' AND ');
|
|
296
|
+
|
|
297
|
+
return `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Delete seed data (useful for down migrations)
|
|
302
|
+
* @param {string} tableName - Name of the table
|
|
303
|
+
* @param {Object} where - WHERE conditions
|
|
304
|
+
* @returns {string} DELETE query
|
|
305
|
+
*/
|
|
306
|
+
deleteSeedData(tableName, where){
|
|
307
|
+
const whereClause = Object.keys(where)
|
|
308
|
+
.map(col => {
|
|
309
|
+
const val = where[col];
|
|
310
|
+
if(val === null || val === undefined){
|
|
311
|
+
return `${col} IS NULL`;
|
|
312
|
+
}
|
|
313
|
+
if(typeof val === 'boolean'){
|
|
314
|
+
return `${col} = ${val ? '1' : '0'}`;
|
|
315
|
+
}
|
|
316
|
+
if(typeof val === 'number'){
|
|
317
|
+
return `${col} = ${val}`;
|
|
318
|
+
}
|
|
319
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
320
|
+
return `${col} = '${escaped}'`;
|
|
321
|
+
})
|
|
322
|
+
.join(' AND ');
|
|
323
|
+
|
|
324
|
+
return `DELETE FROM ${tableName} WHERE ${whereClause}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
184
328
|
}
|
|
185
329
|
|
|
186
330
|
|