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
|
@@ -9,7 +9,26 @@
|
|
|
9
9
|
"Bash(npm whoami:*)",
|
|
10
10
|
"Bash(npm pkg fix:*)",
|
|
11
11
|
"Bash(~/.npmrc)",
|
|
12
|
-
"Bash(cat:*)"
|
|
12
|
+
"Bash(cat:*)",
|
|
13
|
+
"Bash(node test/includesTransformTest.js:*)",
|
|
14
|
+
"Bash(node test/securityTest.js:*)",
|
|
15
|
+
"Bash(node test/transformerTest.js:*)",
|
|
16
|
+
"Bash(node:*)",
|
|
17
|
+
"Bash(npm publish)",
|
|
18
|
+
"Bash(npm owner:*)",
|
|
19
|
+
"Bash(npm profile get:*)",
|
|
20
|
+
"Bash(npm cache clean:*)",
|
|
21
|
+
"Bash(npm config:*)",
|
|
22
|
+
"Bash(npm publish:*)",
|
|
23
|
+
"Bash(npm logout)",
|
|
24
|
+
"Bash(npm access:*)",
|
|
25
|
+
"Bash(npm view:*)",
|
|
26
|
+
"Bash(git add:*)",
|
|
27
|
+
"Bash(git commit:*)",
|
|
28
|
+
"Bash(git push)",
|
|
29
|
+
"Bash(find:*)",
|
|
30
|
+
"Bash(npm install)",
|
|
31
|
+
"Bash(ls:*)"
|
|
13
32
|
],
|
|
14
33
|
"deny": [],
|
|
15
34
|
"ask": []
|
package/Entity/entityModel.js
CHANGED
|
@@ -137,6 +137,12 @@ class EntityModel {
|
|
|
137
137
|
return this;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// allows you to add custom field transformers for serialization/deserialization
|
|
141
|
+
transform(transformObj){
|
|
142
|
+
this.obj.transform = transformObj;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
140
146
|
// allows you to add a virtual object that will skipped from being used as sql objects
|
|
141
147
|
virtual(){
|
|
142
148
|
this.obj.virtual = true;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
// version : 0.0.9
|
|
3
3
|
var tools = require('../Tools');
|
|
4
|
+
var FieldTransformer = require('./fieldTransformer');
|
|
5
|
+
|
|
4
6
|
class EntityTrackerModel {
|
|
5
7
|
|
|
6
8
|
|
|
@@ -18,12 +20,27 @@ class EntityTrackerModel {
|
|
|
18
20
|
this.buildRelationshipModels(modelClass, currentEntity, dataModel);
|
|
19
21
|
|
|
20
22
|
// loop through data model fields
|
|
21
|
-
for (const [modelField, modelFieldValue] of modelFields) {
|
|
22
|
-
|
|
23
|
+
for (const [modelField, modelFieldValue] of modelFields) {
|
|
24
|
+
|
|
23
25
|
// set the value dynamiclly
|
|
24
26
|
if(!$that._isRelationship(currentEntity[modelField])){
|
|
27
|
+
// 🔥 Apply fromDatabase transformer when building entity from DB row
|
|
28
|
+
let transformedValue = modelFieldValue;
|
|
29
|
+
try {
|
|
30
|
+
transformedValue = FieldTransformer.fromDatabase(
|
|
31
|
+
modelFieldValue,
|
|
32
|
+
currentEntity[modelField],
|
|
33
|
+
currentEntity.__name,
|
|
34
|
+
modelField
|
|
35
|
+
);
|
|
36
|
+
} catch(transformError) {
|
|
37
|
+
console.error(`Warning: Failed to transform ${currentEntity.__name}.${modelField} from database: ${transformError.message}`);
|
|
38
|
+
// Use original value if transformation fails (non-fatal)
|
|
39
|
+
transformedValue = modelFieldValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
// current entity has a value then add
|
|
26
|
-
modelClass["__proto__"]["_" + modelField] =
|
|
43
|
+
modelClass["__proto__"]["_" + modelField] = transformedValue;
|
|
27
44
|
|
|
28
45
|
Object.defineProperty(modelClass,modelField, {
|
|
29
46
|
set: function(value) {
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldTransformer - Production-grade field transformation system
|
|
3
|
+
*
|
|
4
|
+
* Allows entity fields to define custom serialization/deserialization logic
|
|
5
|
+
* for transforming values between JavaScript types and database storage formats.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* class User {
|
|
9
|
+
* constructor() {
|
|
10
|
+
* this.certified_models = {
|
|
11
|
+
* type: "string",
|
|
12
|
+
* transform: {
|
|
13
|
+
* toDatabase: (value) => Array.isArray(value) ? JSON.stringify(value) : value,
|
|
14
|
+
* fromDatabase: (value) => {
|
|
15
|
+
* try { return JSON.parse(value); }
|
|
16
|
+
* catch { return []; }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* };
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* @author MasterRecord Team
|
|
24
|
+
* @version 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
class FieldTransformer {
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if a field definition has a transformer
|
|
31
|
+
* @param {Object} fieldDef - Field definition from entity
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
static hasTransformer(fieldDef) {
|
|
35
|
+
return fieldDef
|
|
36
|
+
&& typeof fieldDef === 'object'
|
|
37
|
+
&& fieldDef.transform
|
|
38
|
+
&& typeof fieldDef.transform === 'object'
|
|
39
|
+
&& (typeof fieldDef.transform.toDatabase === 'function'
|
|
40
|
+
|| typeof fieldDef.transform.fromDatabase === 'function');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate transformer definition structure
|
|
45
|
+
* @param {Object} transformer - The transform object
|
|
46
|
+
* @param {string} entityName - Entity name for error messages
|
|
47
|
+
* @param {string} fieldName - Field name for error messages
|
|
48
|
+
* @throws {Error} If transformer is invalid
|
|
49
|
+
*/
|
|
50
|
+
static validateTransformer(transformer, entityName, fieldName) {
|
|
51
|
+
if (!transformer || typeof transformer !== 'object') {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid transformer for ${entityName}.${fieldName}: ` +
|
|
54
|
+
`transform must be an object with toDatabase and/or fromDatabase functions`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { toDatabase, fromDatabase } = transformer;
|
|
59
|
+
|
|
60
|
+
// At least one direction must be provided
|
|
61
|
+
if (!toDatabase && !fromDatabase) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Invalid transformer for ${entityName}.${fieldName}: ` +
|
|
64
|
+
`must provide at least one of: toDatabase, fromDatabase`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate toDatabase if present
|
|
69
|
+
if (toDatabase !== undefined && typeof toDatabase !== 'function') {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Invalid transformer for ${entityName}.${fieldName}: ` +
|
|
72
|
+
`toDatabase must be a function, got ${typeof toDatabase}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Validate fromDatabase if present
|
|
77
|
+
if (fromDatabase !== undefined && typeof fromDatabase !== 'function') {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Invalid transformer for ${entityName}.${fieldName}: ` +
|
|
80
|
+
`fromDatabase must be a function, got ${typeof fromDatabase}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Transform a value for database storage
|
|
87
|
+
* Executes the toDatabase transformer if defined
|
|
88
|
+
*
|
|
89
|
+
* @param {*} value - The value to transform
|
|
90
|
+
* @param {Object} fieldDef - Field definition with transformer
|
|
91
|
+
* @param {string} entityName - Entity name for error messages
|
|
92
|
+
* @param {string} fieldName - Field name for error messages
|
|
93
|
+
* @returns {*} Transformed value
|
|
94
|
+
* @throws {Error} If transformation fails
|
|
95
|
+
*/
|
|
96
|
+
static toDatabase(value, fieldDef, entityName, fieldName) {
|
|
97
|
+
// No transformer - return original value
|
|
98
|
+
if (!this.hasTransformer(fieldDef)) {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const transformer = fieldDef.transform;
|
|
103
|
+
|
|
104
|
+
// No toDatabase function - return original value
|
|
105
|
+
if (!transformer.toDatabase) {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Execute transformation with comprehensive error handling
|
|
110
|
+
try {
|
|
111
|
+
const transformed = transformer.toDatabase(value);
|
|
112
|
+
|
|
113
|
+
// Validate transformation returned a value
|
|
114
|
+
if (transformed === undefined) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Transformer for ${entityName}.${fieldName} returned undefined. ` +
|
|
117
|
+
`Transform functions must return a value.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return transformed;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
// Re-throw with context if it's already our error
|
|
124
|
+
if (err.message.includes(entityName)) {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Wrap external errors with context
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Transform error for ${entityName}.${fieldName}: ${err.message}\n` +
|
|
131
|
+
`Original value: ${JSON.stringify(value)}\n` +
|
|
132
|
+
`Stack: ${err.stack}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Transform a value from database storage to application format
|
|
139
|
+
* Executes the fromDatabase transformer if defined
|
|
140
|
+
*
|
|
141
|
+
* @param {*} value - The value from database
|
|
142
|
+
* @param {Object} fieldDef - Field definition with transformer
|
|
143
|
+
* @param {string} entityName - Entity name for error messages
|
|
144
|
+
* @param {string} fieldName - Field name for error messages
|
|
145
|
+
* @returns {*} Transformed value
|
|
146
|
+
* @throws {Error} If transformation fails
|
|
147
|
+
*/
|
|
148
|
+
static fromDatabase(value, fieldDef, entityName, fieldName) {
|
|
149
|
+
// No transformer - return original value
|
|
150
|
+
if (!this.hasTransformer(fieldDef)) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const transformer = fieldDef.transform;
|
|
155
|
+
|
|
156
|
+
// No fromDatabase function - return original value
|
|
157
|
+
if (!transformer.fromDatabase) {
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Execute transformation with comprehensive error handling
|
|
162
|
+
try {
|
|
163
|
+
const transformed = transformer.fromDatabase(value);
|
|
164
|
+
|
|
165
|
+
// Allow undefined return for optional transformations
|
|
166
|
+
// (e.g., parsing may return undefined for null/empty strings)
|
|
167
|
+
return transformed;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// Re-throw with context if it's already our error
|
|
170
|
+
if (err.message.includes(entityName)) {
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Wrap external errors with context
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Transform error for ${entityName}.${fieldName}: ${err.message}\n` +
|
|
177
|
+
`Original value: ${JSON.stringify(value)}\n` +
|
|
178
|
+
`Stack: ${err.stack}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Apply toDatabase transformers to all fields in an entity
|
|
185
|
+
* Used during INSERT/UPDATE operations
|
|
186
|
+
*
|
|
187
|
+
* @param {Object} entityData - The entity data to transform
|
|
188
|
+
* @param {Object} entityDef - Entity definition with field definitions
|
|
189
|
+
* @returns {Object} New object with transformed values
|
|
190
|
+
*/
|
|
191
|
+
static applyToDatabaseTransforms(entityData, entityDef) {
|
|
192
|
+
const transformed = {};
|
|
193
|
+
const entityName = entityDef.__name || 'Entity';
|
|
194
|
+
|
|
195
|
+
for (const fieldName in entityData) {
|
|
196
|
+
// Skip internal fields
|
|
197
|
+
if (fieldName.startsWith('__')) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const value = entityData[fieldName];
|
|
202
|
+
const fieldDef = entityDef[fieldName];
|
|
203
|
+
|
|
204
|
+
// If no field definition, pass through
|
|
205
|
+
if (!fieldDef) {
|
|
206
|
+
transformed[fieldName] = value;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Apply transformer if present
|
|
211
|
+
try {
|
|
212
|
+
transformed[fieldName] = this.toDatabase(value, fieldDef, entityName, fieldName);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
// Add operation context
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Failed to transform field for database write: ${err.message}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return transformed;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Apply fromDatabase transformers to all fields in an entity
|
|
226
|
+
* Used during SELECT operations when building entities from database rows
|
|
227
|
+
*
|
|
228
|
+
* @param {Object} dbRow - Raw database row
|
|
229
|
+
* @param {Object} entityDef - Entity definition with field definitions
|
|
230
|
+
* @returns {Object} New object with transformed values
|
|
231
|
+
*/
|
|
232
|
+
static applyFromDatabaseTransforms(dbRow, entityDef) {
|
|
233
|
+
const transformed = {};
|
|
234
|
+
const entityName = entityDef.__name || 'Entity';
|
|
235
|
+
|
|
236
|
+
for (const fieldName in dbRow) {
|
|
237
|
+
// Skip internal fields
|
|
238
|
+
if (fieldName.startsWith('__')) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const value = dbRow[fieldName];
|
|
243
|
+
const fieldDef = entityDef[fieldName];
|
|
244
|
+
|
|
245
|
+
// If no field definition, pass through
|
|
246
|
+
if (!fieldDef) {
|
|
247
|
+
transformed[fieldName] = value;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Apply transformer if present
|
|
252
|
+
try {
|
|
253
|
+
transformed[fieldName] = this.fromDatabase(value, fieldDef, entityName, fieldName);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
// Add operation context
|
|
256
|
+
throw new Error(
|
|
257
|
+
`Failed to transform field from database: ${err.message}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return transformed;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
module.exports = FieldTransformer;
|
|
@@ -224,7 +224,151 @@ class migrationMySQLQuery {
|
|
|
224
224
|
return `ALTER TABLE \`${table.tableName}\` RENAME COLUMN \`${table.name}\` TO \`${table.newName}\``
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
/**
|
|
228
|
+
* SEED DATA METHODS
|
|
229
|
+
* Support for inserting seed data during migrations
|
|
230
|
+
*/
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Insert seed data into a table
|
|
234
|
+
* @param {string} tableName - Name of the table
|
|
235
|
+
* @param {Object} data - Data object with column names as keys
|
|
236
|
+
* @returns {string} INSERT query
|
|
237
|
+
*/
|
|
238
|
+
insertSeedData(tableName, data){
|
|
239
|
+
const columns = Object.keys(data).filter(k => !k.startsWith('__'));
|
|
240
|
+
const values = columns.map(col => {
|
|
241
|
+
const val = data[col];
|
|
242
|
+
if(val === null || val === undefined){
|
|
243
|
+
return 'NULL';
|
|
244
|
+
}
|
|
245
|
+
if(typeof val === 'boolean'){
|
|
246
|
+
return val ? '1' : '0'; // MySQL TINYINT for boolean
|
|
247
|
+
}
|
|
248
|
+
if(typeof val === 'number'){
|
|
249
|
+
return val;
|
|
250
|
+
}
|
|
251
|
+
// Escape strings
|
|
252
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
253
|
+
return `'${escaped}'`;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const columnList = columns.map(c => `\`${c}\``).join(', ');
|
|
257
|
+
const valueList = values.join(', ');
|
|
258
|
+
|
|
259
|
+
return `INSERT INTO \`${tableName}\` (${columnList}) VALUES (${valueList})`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Insert multiple seed records at once
|
|
264
|
+
* @param {string} tableName - Name of the table
|
|
265
|
+
* @param {Array} dataArray - Array of data objects
|
|
266
|
+
* @returns {string} Bulk INSERT query
|
|
267
|
+
*/
|
|
268
|
+
bulkInsertSeedData(tableName, dataArray){
|
|
269
|
+
if(!dataArray || dataArray.length === 0){
|
|
270
|
+
return '';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const firstRow = dataArray[0];
|
|
274
|
+
const columns = Object.keys(firstRow).filter(k => !k.startsWith('__'));
|
|
275
|
+
const columnList = columns.map(c => `\`${c}\``).join(', ');
|
|
276
|
+
|
|
277
|
+
const valueRows = dataArray.map(data => {
|
|
278
|
+
const values = columns.map(col => {
|
|
279
|
+
const val = data[col];
|
|
280
|
+
if(val === null || val === undefined){
|
|
281
|
+
return 'NULL';
|
|
282
|
+
}
|
|
283
|
+
if(typeof val === 'boolean'){
|
|
284
|
+
return val ? '1' : '0';
|
|
285
|
+
}
|
|
286
|
+
if(typeof val === 'number'){
|
|
287
|
+
return val;
|
|
288
|
+
}
|
|
289
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
290
|
+
return `'${escaped}'`;
|
|
291
|
+
});
|
|
292
|
+
return `(${values.join(', ')})`;
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return `INSERT INTO \`${tableName}\` (${columnList}) VALUES ${valueRows.join(', ')}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Update seed data (useful for down migrations)
|
|
300
|
+
* @param {string} tableName - Name of the table
|
|
301
|
+
* @param {Object} data - Data to update
|
|
302
|
+
* @param {Object} where - WHERE conditions
|
|
303
|
+
* @returns {string} UPDATE query
|
|
304
|
+
*/
|
|
305
|
+
updateSeedData(tableName, data, where){
|
|
306
|
+
const setClause = Object.keys(data)
|
|
307
|
+
.filter(k => !k.startsWith('__'))
|
|
308
|
+
.map(col => {
|
|
309
|
+
const val = data[col];
|
|
310
|
+
if(val === null || val === undefined){
|
|
311
|
+
return `\`${col}\` = 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(', ');
|
|
323
|
+
|
|
324
|
+
const whereClause = Object.keys(where)
|
|
325
|
+
.map(col => {
|
|
326
|
+
const val = where[col];
|
|
327
|
+
if(val === null || val === undefined){
|
|
328
|
+
return `\`${col}\` IS NULL`;
|
|
329
|
+
}
|
|
330
|
+
if(typeof val === 'boolean'){
|
|
331
|
+
return `\`${col}\` = ${val ? '1' : '0'}`;
|
|
332
|
+
}
|
|
333
|
+
if(typeof val === 'number'){
|
|
334
|
+
return `\`${col}\` = ${val}`;
|
|
335
|
+
}
|
|
336
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
337
|
+
return `\`${col}\` = '${escaped}'`;
|
|
338
|
+
})
|
|
339
|
+
.join(' AND ');
|
|
340
|
+
|
|
341
|
+
return `UPDATE \`${tableName}\` SET ${setClause} WHERE ${whereClause}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Delete seed data (useful for down migrations)
|
|
346
|
+
* @param {string} tableName - Name of the table
|
|
347
|
+
* @param {Object} where - WHERE conditions
|
|
348
|
+
* @returns {string} DELETE query
|
|
349
|
+
*/
|
|
350
|
+
deleteSeedData(tableName, where){
|
|
351
|
+
const whereClause = Object.keys(where)
|
|
352
|
+
.map(col => {
|
|
353
|
+
const val = where[col];
|
|
354
|
+
if(val === null || val === undefined){
|
|
355
|
+
return `\`${col}\` IS NULL`;
|
|
356
|
+
}
|
|
357
|
+
if(typeof val === 'boolean'){
|
|
358
|
+
return `\`${col}\` = ${val ? '1' : '0'}`;
|
|
359
|
+
}
|
|
360
|
+
if(typeof val === 'number'){
|
|
361
|
+
return `\`${col}\` = ${val}`;
|
|
362
|
+
}
|
|
363
|
+
const escaped = String(val).replace(/'/g, "''");
|
|
364
|
+
return `\`${col}\` = '${escaped}'`;
|
|
365
|
+
})
|
|
366
|
+
.join(' AND ');
|
|
367
|
+
|
|
368
|
+
return `DELETE FROM \`${tableName}\` WHERE ${whereClause}`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
228
372
|
}
|
|
229
373
|
|
|
230
374
|
|