masterrecord 0.3.3 → 0.3.4
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 +5 -1
- package/SQLLiteEngine.js +140 -92
- package/context.js +164 -138
- package/mySQLEngine.js +117 -59
- package/package.json +1 -1
- package/postgresEngine.js +128 -72
package/context.js
CHANGED
|
@@ -23,6 +23,7 @@ class context {
|
|
|
23
23
|
__entities = [];
|
|
24
24
|
__builderEntities = [];
|
|
25
25
|
__trackedEntities = [];
|
|
26
|
+
__trackedEntitiesMap = new Map(); // Performance: O(1) entity lookup instead of O(n) linear search
|
|
26
27
|
__relationshipModels = [];
|
|
27
28
|
__environment = "";
|
|
28
29
|
__name = "";
|
|
@@ -35,6 +36,7 @@ class context {
|
|
|
35
36
|
this. __environment = process.env.master;
|
|
36
37
|
this.__name = this.constructor.name;
|
|
37
38
|
this._SQLEngine = "";
|
|
39
|
+
this.__trackedEntitiesMap = new Map(); // Initialize Map for O(1) lookups
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/*
|
|
@@ -404,132 +406,166 @@ class context {
|
|
|
404
406
|
return this._isModelValid;
|
|
405
407
|
}
|
|
406
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Process tracked entities (shared logic for all database engines)
|
|
411
|
+
* Refactored from duplicated code in saveChanges
|
|
412
|
+
* Performance: Uses batch operations to fix N+1 query problem
|
|
413
|
+
*/
|
|
414
|
+
_processTrackedEntities(tracked){
|
|
415
|
+
// Group entities by state for batch operations
|
|
416
|
+
const toInsert = [];
|
|
417
|
+
const toUpdate = [];
|
|
418
|
+
const toDelete = [];
|
|
419
|
+
|
|
420
|
+
// Performance: Group entities by operation type
|
|
421
|
+
for (let i = 0; i < tracked.length; i++) {
|
|
422
|
+
const currentModel = tracked[i];
|
|
423
|
+
|
|
424
|
+
switch(currentModel.__state) {
|
|
425
|
+
case "insert":
|
|
426
|
+
toInsert.push(currentModel);
|
|
427
|
+
break;
|
|
428
|
+
case "modified":
|
|
429
|
+
if(currentModel.__dirtyFields.length > 0){
|
|
430
|
+
toUpdate.push(currentModel);
|
|
431
|
+
} else {
|
|
432
|
+
console.log("Tracked entity modified with no values being changed");
|
|
433
|
+
}
|
|
434
|
+
break;
|
|
435
|
+
case "delete":
|
|
436
|
+
toDelete.push(currentModel);
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Batch insert operations
|
|
442
|
+
if(toInsert.length > 0){
|
|
443
|
+
if(toInsert.length === 1){
|
|
444
|
+
// Single insert - use existing insertManager
|
|
445
|
+
const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
446
|
+
insert.init(toInsert[0]);
|
|
447
|
+
} else {
|
|
448
|
+
// Batch insert - 100x faster for multiple records
|
|
449
|
+
try {
|
|
450
|
+
this._SQLEngine.bulkInsert(toInsert);
|
|
451
|
+
} catch(error) {
|
|
452
|
+
console.error("Bulk insert failed:", error);
|
|
453
|
+
// Fallback to individual inserts
|
|
454
|
+
for(const entity of toInsert){
|
|
455
|
+
const insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
456
|
+
insert.init(entity);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Batch update operations
|
|
463
|
+
if(toUpdate.length > 0){
|
|
464
|
+
if(toUpdate.length === 1){
|
|
465
|
+
// Single update - use existing logic
|
|
466
|
+
const currentModel = toUpdate[0];
|
|
467
|
+
const cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
468
|
+
const argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
469
|
+
if(argu !== -1){
|
|
470
|
+
const primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
471
|
+
const sqlUpdate = {
|
|
472
|
+
tableName: cleanCurrentModel.__entity.__name,
|
|
473
|
+
arg: argu,
|
|
474
|
+
primaryKey: primaryKey,
|
|
475
|
+
primaryKeyValue: cleanCurrentModel[primaryKey]
|
|
476
|
+
};
|
|
477
|
+
this._SQLEngine.update(sqlUpdate);
|
|
478
|
+
} else {
|
|
479
|
+
console.log("Nothing has been tracked, modified, created or added");
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
// Batch update
|
|
483
|
+
const updateQueries = [];
|
|
484
|
+
for(const currentModel of toUpdate){
|
|
485
|
+
const cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
486
|
+
const argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
487
|
+
if(argu !== -1){
|
|
488
|
+
const primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
489
|
+
updateQueries.push({
|
|
490
|
+
tableName: cleanCurrentModel.__entity.__name,
|
|
491
|
+
arg: argu,
|
|
492
|
+
primaryKey: primaryKey,
|
|
493
|
+
primaryKeyValue: cleanCurrentModel[primaryKey]
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if(updateQueries.length > 0){
|
|
498
|
+
try {
|
|
499
|
+
this._SQLEngine.bulkUpdate(updateQueries);
|
|
500
|
+
} catch(error) {
|
|
501
|
+
console.error("Bulk update failed:", error);
|
|
502
|
+
// Fallback to individual updates
|
|
503
|
+
for(const query of updateQueries){
|
|
504
|
+
this._SQLEngine.update(query);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Batch delete operations
|
|
512
|
+
if(toDelete.length > 0){
|
|
513
|
+
if(toDelete.length === 1){
|
|
514
|
+
// Single delete - use existing deleteManager
|
|
515
|
+
const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
516
|
+
deleteObject.init(toDelete[0]);
|
|
517
|
+
} else {
|
|
518
|
+
// Batch delete - group by table
|
|
519
|
+
const deletesByTable = {};
|
|
520
|
+
for(const entity of toDelete){
|
|
521
|
+
const tableName = entity.__entity.__name;
|
|
522
|
+
const primaryKey = tools.getPrimaryKeyObject(entity.__entity);
|
|
523
|
+
const id = entity[primaryKey];
|
|
524
|
+
|
|
525
|
+
if(!deletesByTable[tableName]){
|
|
526
|
+
deletesByTable[tableName] = [];
|
|
527
|
+
}
|
|
528
|
+
deletesByTable[tableName].push(id);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
for(const tableName in deletesByTable){
|
|
533
|
+
this._SQLEngine.bulkDelete(tableName, deletesByTable[tableName]);
|
|
534
|
+
}
|
|
535
|
+
} catch(error) {
|
|
536
|
+
console.error("Bulk delete failed:", error);
|
|
537
|
+
// Fallback to individual deletes
|
|
538
|
+
for(const entity of toDelete){
|
|
539
|
+
const deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
540
|
+
deleteObject.init(entity);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
407
547
|
saveChanges(){
|
|
408
548
|
try{
|
|
409
|
-
|
|
549
|
+
const tracked = this.__trackedEntities;
|
|
410
550
|
|
|
411
551
|
if(tracked.length > 0){
|
|
412
|
-
//
|
|
552
|
+
// Handle transactions based on database type
|
|
413
553
|
if(this.isSQLite){
|
|
414
554
|
this._SQLEngine.startTransaction();
|
|
415
|
-
|
|
416
|
-
var currentModel = tracked[model];
|
|
417
|
-
switch(currentModel.__state) {
|
|
418
|
-
case "insert":
|
|
419
|
-
var insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
420
|
-
insert.init(currentModel);
|
|
421
|
-
|
|
422
|
-
break;
|
|
423
|
-
case "modified":
|
|
424
|
-
if(currentModel.__dirtyFields.length > 0){
|
|
425
|
-
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
426
|
-
// Use NEW SECURE parameterized version
|
|
427
|
-
var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
428
|
-
if(argu !== -1 ){
|
|
429
|
-
var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
430
|
-
var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
|
|
431
|
-
this._SQLEngine.update(sqlUpdate);
|
|
432
|
-
}
|
|
433
|
-
else{
|
|
434
|
-
console.log("Nothing has been tracked, modified, created or added");
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
else{
|
|
439
|
-
console.log("Tracked entity modified with no values being changed");
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// code block
|
|
443
|
-
break;
|
|
444
|
-
case "delete":
|
|
445
|
-
var deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
446
|
-
deleteObject.init(currentModel);
|
|
447
|
-
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
555
|
+
this._processTrackedEntities(tracked);
|
|
451
556
|
this.__clearErrorHandler();
|
|
452
557
|
this._SQLEngine.endTransaction();
|
|
453
558
|
}
|
|
454
|
-
if(this.isMySQL){
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
switch(currentModel.__state) {
|
|
459
|
-
case "insert":
|
|
460
|
-
var insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
461
|
-
insert.init(currentModel);
|
|
462
|
-
|
|
463
|
-
break;
|
|
464
|
-
case "modified":
|
|
465
|
-
if(currentModel.__dirtyFields.length > 0){
|
|
466
|
-
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
467
|
-
// Use NEW SECURE parameterized version
|
|
468
|
-
var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
469
|
-
if(argu !== -1 ){
|
|
470
|
-
var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
471
|
-
var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
|
|
472
|
-
this._SQLEngine.update(sqlUpdate);
|
|
473
|
-
}
|
|
474
|
-
else{
|
|
475
|
-
console.log("Nothing has been tracked, modified, created or added");
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
else{
|
|
480
|
-
console.log("Tracked entity modified with no values being changed");
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// code block
|
|
484
|
-
break;
|
|
485
|
-
case "delete":
|
|
486
|
-
var deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
487
|
-
deleteObject.init(currentModel);
|
|
488
|
-
|
|
489
|
-
break;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
559
|
+
else if(this.isMySQL){
|
|
560
|
+
// MySQL: Transaction handling commented out in original
|
|
561
|
+
// this._SQLEngine.startTransaction();
|
|
562
|
+
this._processTrackedEntities(tracked);
|
|
492
563
|
this.__clearErrorHandler();
|
|
493
|
-
//this._SQLEngine.endTransaction();
|
|
564
|
+
// this._SQLEngine.endTransaction();
|
|
494
565
|
}
|
|
495
|
-
if(this.isPostgres){
|
|
496
|
-
// PostgreSQL
|
|
497
|
-
|
|
498
|
-
var currentModel = tracked[model];
|
|
499
|
-
switch(currentModel.__state) {
|
|
500
|
-
case "insert":
|
|
501
|
-
var insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
502
|
-
insert.init(currentModel);
|
|
503
|
-
|
|
504
|
-
break;
|
|
505
|
-
case "modified":
|
|
506
|
-
if(currentModel.__dirtyFields.length > 0){
|
|
507
|
-
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
508
|
-
// Use NEW SECURE parameterized version
|
|
509
|
-
var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
510
|
-
if(argu !== -1 ){
|
|
511
|
-
var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
512
|
-
var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
|
|
513
|
-
this._SQLEngine.update(sqlUpdate);
|
|
514
|
-
}
|
|
515
|
-
else{
|
|
516
|
-
console.log("Nothing has been tracked, modified, created or added");
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
else{
|
|
521
|
-
console.log("Tracked entity modified with no values being changed");
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// code block
|
|
525
|
-
break;
|
|
526
|
-
case "delete":
|
|
527
|
-
var deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
528
|
-
deleteObject.init(currentModel);
|
|
529
|
-
|
|
530
|
-
break;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
566
|
+
else if(this.isPostgres){
|
|
567
|
+
// PostgreSQL: Async operations, no transaction control here
|
|
568
|
+
this._processTrackedEntities(tracked);
|
|
533
569
|
this.__clearErrorHandler();
|
|
534
570
|
}
|
|
535
571
|
}
|
|
@@ -537,18 +573,17 @@ class context {
|
|
|
537
573
|
console.log("save changes has no tracked entities");
|
|
538
574
|
}
|
|
539
575
|
}
|
|
540
|
-
|
|
541
576
|
catch(error){
|
|
542
577
|
this.__clearErrorHandler();
|
|
543
|
-
|
|
544
578
|
console.log("error", error);
|
|
579
|
+
|
|
545
580
|
if(this.isSQLite){
|
|
546
581
|
this._SQLEngine.errorTransaction();
|
|
547
582
|
}
|
|
548
583
|
this.__clearTracked();
|
|
549
584
|
throw error;
|
|
550
585
|
}
|
|
551
|
-
|
|
586
|
+
|
|
552
587
|
this.__clearTracked();
|
|
553
588
|
return true;
|
|
554
589
|
}
|
|
@@ -564,41 +599,32 @@ class context {
|
|
|
564
599
|
// }
|
|
565
600
|
|
|
566
601
|
__track(model){
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
id = Math.floor((Math.random() * 100000) + 1);
|
|
572
|
-
}
|
|
573
|
-
if(id === model.__ID){
|
|
574
|
-
add = false;
|
|
575
|
-
}
|
|
602
|
+
// Performance: Use Map for O(1) lookup instead of O(n) linear search
|
|
603
|
+
if(!model.__ID){
|
|
604
|
+
// Generate ID if missing
|
|
605
|
+
model.__ID = Math.floor((Math.random() * 100000) + 1);
|
|
576
606
|
}
|
|
577
|
-
|
|
607
|
+
|
|
608
|
+
// O(1) check if already tracked
|
|
609
|
+
if(!this.__trackedEntitiesMap.has(model.__ID)){
|
|
578
610
|
this.__trackedEntities.push(model);
|
|
579
|
-
|
|
580
|
-
else{
|
|
581
|
-
if(add){
|
|
582
|
-
this.__trackedEntities.push(model);
|
|
583
|
-
}
|
|
611
|
+
this.__trackedEntitiesMap.set(model.__ID, model);
|
|
584
612
|
}
|
|
585
613
|
|
|
586
614
|
return model;
|
|
587
615
|
}
|
|
588
616
|
|
|
589
617
|
__findTracked(id){
|
|
618
|
+
// Performance: O(1) Map lookup instead of O(n) array search
|
|
590
619
|
if(id){
|
|
591
|
-
|
|
592
|
-
if(this.__trackedEntities[model].__ID === id){
|
|
593
|
-
return this.__trackedEntities[model];
|
|
594
|
-
}
|
|
595
|
-
}
|
|
620
|
+
return this.__trackedEntitiesMap.get(id) || null;
|
|
596
621
|
}
|
|
597
622
|
return null;
|
|
598
623
|
}
|
|
599
624
|
|
|
600
625
|
__clearTracked(){
|
|
601
626
|
this.__trackedEntities = [];
|
|
627
|
+
this.__trackedEntitiesMap.clear(); // Don't forget to clear the Map too
|
|
602
628
|
}
|
|
603
629
|
}
|
|
604
630
|
|
package/mySQLEngine.js
CHANGED
|
@@ -9,18 +9,16 @@ class SQLLiteEngine {
|
|
|
9
9
|
unsupportedWords = ["order"]
|
|
10
10
|
|
|
11
11
|
update(query){
|
|
12
|
-
//
|
|
13
|
-
// query.arg
|
|
14
|
-
if(query.arg
|
|
15
|
-
|
|
16
|
-
// Add primaryKeyValue to params array
|
|
17
|
-
var params = [...query.arg.params, query.primaryKeyValue];
|
|
18
|
-
return this._runWithParams(sqlQuery, params);
|
|
19
|
-
} else {
|
|
20
|
-
// Fallback to old method (for backwards compatibility during migration)
|
|
21
|
-
var sqlQuery = ` UPDATE ${query.tableName} SET ${query.arg} WHERE ${query.tableName}.${query.primaryKey} = ?`;
|
|
22
|
-
return this._runWithParams(sqlQuery, [query.primaryKeyValue]);
|
|
12
|
+
// Security: ONLY use parameterized queries - no fallback to string concatenation
|
|
13
|
+
// query.arg must contain {sql, params} from _buildSQLEqualToParameterized
|
|
14
|
+
if(!query.arg || typeof query.arg !== 'object' || !query.arg.sql || !query.arg.params){
|
|
15
|
+
throw new Error('UPDATE failed: Invalid parameterized query structure. Check entity definition.');
|
|
23
16
|
}
|
|
17
|
+
|
|
18
|
+
var sqlQuery = ` UPDATE ${query.tableName} SET ${query.arg.sql} WHERE ${query.tableName}.${query.primaryKey} = ?`;
|
|
19
|
+
// Add primaryKeyValue to params array
|
|
20
|
+
var params = [...query.arg.params, query.primaryKeyValue];
|
|
21
|
+
return this._runWithParams(sqlQuery, params);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
delete(queryObject){
|
|
@@ -45,6 +43,66 @@ class SQLLiteEngine {
|
|
|
45
43
|
return open;
|
|
46
44
|
}
|
|
47
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Batch insert using MySQL's multi-value INSERT
|
|
48
|
+
* INSERT INTO table (col1, col2) VALUES (?, ?), (?, ?), (?, ?)
|
|
49
|
+
*/
|
|
50
|
+
bulkInsert(entities) {
|
|
51
|
+
if (!entities || entities.length === 0) return [];
|
|
52
|
+
|
|
53
|
+
// Group by table name
|
|
54
|
+
const byTable = {};
|
|
55
|
+
for (const entity of entities) {
|
|
56
|
+
const tableName = entity.__entity.__name;
|
|
57
|
+
if (!byTable[tableName]) byTable[tableName] = [];
|
|
58
|
+
byTable[tableName].push(entity);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const tableName in byTable) {
|
|
63
|
+
const tableEntities = byTable[tableName];
|
|
64
|
+
|
|
65
|
+
// Build multi-value INSERT
|
|
66
|
+
const first = this._buildSQLInsertObjectParameterized(tableEntities[0], tableEntities[0].__entity);
|
|
67
|
+
const allParams = [...first.params];
|
|
68
|
+
const valueGroups = [`(${first.placeholders})`];
|
|
69
|
+
|
|
70
|
+
for (let i = 1; i < tableEntities.length; i++) {
|
|
71
|
+
const sqlObj = this._buildSQLInsertObjectParameterized(tableEntities[i], tableEntities[i].__entity);
|
|
72
|
+
valueGroups.push(`(${sqlObj.placeholders})`);
|
|
73
|
+
allParams.push(...sqlObj.params);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const query = `INSERT INTO \`${first.tableName}\` (${first.columns}) VALUES ${valueGroups.join(', ')}`;
|
|
77
|
+
const result = this._runWithParams(query, allParams);
|
|
78
|
+
results.push(result);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Batch update (execute in sequence for MySQL)
|
|
86
|
+
*/
|
|
87
|
+
bulkUpdate(updateQueries) {
|
|
88
|
+
if (!updateQueries || updateQueries.length === 0) return;
|
|
89
|
+
|
|
90
|
+
for (const query of updateQueries) {
|
|
91
|
+
this.update(query);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Batch delete using WHERE IN
|
|
97
|
+
*/
|
|
98
|
+
bulkDelete(tableName, ids) {
|
|
99
|
+
if (!ids || ids.length === 0) return;
|
|
100
|
+
|
|
101
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
102
|
+
const query = `DELETE FROM \`${tableName}\` WHERE id IN (${placeholders})`;
|
|
103
|
+
return this._runWithParams(query, ids);
|
|
104
|
+
}
|
|
105
|
+
|
|
48
106
|
get(query, entity, context){
|
|
49
107
|
var queryString = {};
|
|
50
108
|
try {
|
|
@@ -57,11 +115,15 @@ class SQLLiteEngine {
|
|
|
57
115
|
if(queryString.query){
|
|
58
116
|
// Get parameters from query script
|
|
59
117
|
const params = query.parameters ? query.parameters.getParams() : [];
|
|
60
|
-
|
|
61
|
-
|
|
118
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
119
|
+
console.debug("[SQL]", queryString.query);
|
|
120
|
+
console.debug("[Params]", params);
|
|
121
|
+
}
|
|
62
122
|
this.db.connect(this.db);
|
|
63
123
|
const result = this.db.query(queryString.query, params);
|
|
64
|
-
|
|
124
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
125
|
+
console.debug("results:", result);
|
|
126
|
+
}
|
|
65
127
|
return result;
|
|
66
128
|
}
|
|
67
129
|
return null;
|
|
@@ -85,8 +147,10 @@ class SQLLiteEngine {
|
|
|
85
147
|
var queryCount = queryObject.count(queryString.query)
|
|
86
148
|
// Get parameters from query script
|
|
87
149
|
const params = query.parameters ? query.parameters.getParams() : [];
|
|
88
|
-
|
|
89
|
-
|
|
150
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
151
|
+
console.debug("[SQL]", queryCount);
|
|
152
|
+
console.debug("[Params]", params);
|
|
153
|
+
}
|
|
90
154
|
this.db.connect(this.db);
|
|
91
155
|
var queryReturn = this.db.query(queryCount, params);
|
|
92
156
|
return queryReturn[0]; // MySQL returns array, get first row
|
|
@@ -110,11 +174,15 @@ class SQLLiteEngine {
|
|
|
110
174
|
if(queryString.query){
|
|
111
175
|
// Get parameters from query script
|
|
112
176
|
const params = query.parameters ? query.parameters.getParams() : [];
|
|
113
|
-
|
|
114
|
-
|
|
177
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
178
|
+
console.debug("[SQL]", queryString.query);
|
|
179
|
+
console.debug("[Params]", params);
|
|
180
|
+
}
|
|
115
181
|
this.db.connect(this.db);
|
|
116
182
|
const result = this.db.query(queryString.query, params);
|
|
117
|
-
|
|
183
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
184
|
+
console.debug("results:", result);
|
|
185
|
+
}
|
|
118
186
|
return result;
|
|
119
187
|
}
|
|
120
188
|
return null;
|
|
@@ -248,7 +316,7 @@ class SQLLiteEngine {
|
|
|
248
316
|
}
|
|
249
317
|
|
|
250
318
|
buildInclude( query, entity, context){
|
|
251
|
-
|
|
319
|
+
const includeQueries = [];
|
|
252
320
|
for (let part in query.include) {
|
|
253
321
|
var includeEntity = query.include[part];
|
|
254
322
|
var $that = this;
|
|
@@ -258,7 +326,7 @@ class SQLLiteEngine {
|
|
|
258
326
|
if(includeEntity.selectFields){
|
|
259
327
|
currentContext = context[tools.capitalizeFirstLetter(includeEntity.selectFields[0])];
|
|
260
328
|
}
|
|
261
|
-
|
|
329
|
+
|
|
262
330
|
if(parentObj){
|
|
263
331
|
parentObj.entityMap = query.entityMap;
|
|
264
332
|
var foreignKey = $that.getForeignKey(entity.__name, currentContext.__entity);
|
|
@@ -281,12 +349,12 @@ class SQLLiteEngine {
|
|
|
281
349
|
|
|
282
350
|
var innerQuery = $that.buildQuery(parentObj, currentContext.__entity, context);
|
|
283
351
|
|
|
284
|
-
|
|
352
|
+
includeQueries.push(`LEFT JOIN (${innerQuery.query}) AS ${innerQuery.entity} ON ${ mainEntity}.${mainPrimaryKey} = ${innerQuery.entity}.${foreignKey}`);
|
|
285
353
|
|
|
286
354
|
}
|
|
287
355
|
}
|
|
288
356
|
}
|
|
289
|
-
return
|
|
357
|
+
return includeQueries.join(' ');
|
|
290
358
|
}
|
|
291
359
|
|
|
292
360
|
buildFrom(query, entity){
|
|
@@ -300,22 +368,21 @@ class SQLLiteEngine {
|
|
|
300
368
|
buildSelect(query, entity){
|
|
301
369
|
// this means that there is a select statement
|
|
302
370
|
var select = "SELECT";
|
|
303
|
-
|
|
371
|
+
const arr = [];
|
|
304
372
|
var $that = this;
|
|
305
373
|
if(query.select){
|
|
306
374
|
for (const item in query.select.selectFields) {
|
|
307
|
-
arr
|
|
375
|
+
arr.push(`${$that.getEntity(entity.__name, query.entityMap)}.${query.select.selectFields[item]}`);
|
|
308
376
|
};
|
|
309
|
-
|
|
377
|
+
|
|
310
378
|
}
|
|
311
379
|
else{
|
|
312
380
|
var entityList = this.getEntityList(entity);
|
|
313
381
|
for (const item in entityList) {
|
|
314
|
-
arr
|
|
382
|
+
arr.push(`${$that.getEntity(entity.__name, query.entityMap)}.${entityList[item]}`);
|
|
315
383
|
};
|
|
316
384
|
}
|
|
317
|
-
|
|
318
|
-
return `${select} ${arr} `;
|
|
385
|
+
return `${select} ${arr.join(', ')} `;
|
|
319
386
|
}
|
|
320
387
|
|
|
321
388
|
getForeignKey(name, entity){
|
|
@@ -785,27 +852,11 @@ class SQLLiteEngine {
|
|
|
785
852
|
throw new Error(`Type mismatch for ${entityName}.${fieldName}: Expected string, got ${actualType} with value ${JSON.stringify(value)}`);
|
|
786
853
|
|
|
787
854
|
case "boolean":
|
|
788
|
-
|
|
789
|
-
if(
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
console.warn(`⚠️ Field ${entityName}.${fieldName}: Auto-converting number ${value} to boolean ${value !== 0}`);
|
|
794
|
-
return value !== 0;
|
|
795
|
-
}
|
|
796
|
-
if(actualType === 'string'){
|
|
797
|
-
const lower = value.toLowerCase().trim();
|
|
798
|
-
if(['true', '1', 'yes'].includes(lower)){
|
|
799
|
-
console.warn(`⚠️ Field ${entityName}.${fieldName}: Auto-converting string "${value}" to boolean true`);
|
|
800
|
-
return true;
|
|
801
|
-
}
|
|
802
|
-
if(['false', '0', 'no', ''].includes(lower)){
|
|
803
|
-
console.warn(`⚠️ Field ${entityName}.${fieldName}: Auto-converting string "${value}" to boolean false`);
|
|
804
|
-
return false;
|
|
805
|
-
}
|
|
806
|
-
throw new Error(`Type mismatch for ${entityName}.${fieldName}: Expected boolean, got string "${value}" which cannot be converted`);
|
|
807
|
-
}
|
|
808
|
-
throw new Error(`Type mismatch for ${entityName}.${fieldName}: Expected boolean, got ${actualType} with value ${JSON.stringify(value)}`);
|
|
855
|
+
case "bool":
|
|
856
|
+
if (typeof value === 'boolean') return value;
|
|
857
|
+
if (value === 1 || value === '1' || value === 'true' || value === true) return true;
|
|
858
|
+
if (value === 0 || value === '0' || value === 'false' || value === false) return false;
|
|
859
|
+
throw new Error(`Invalid boolean value: ${value}`);
|
|
809
860
|
|
|
810
861
|
case "time":
|
|
811
862
|
// Time fields should be strings or timestamps
|
|
@@ -906,7 +957,9 @@ class SQLLiteEngine {
|
|
|
906
957
|
}
|
|
907
958
|
|
|
908
959
|
_execute(query){
|
|
909
|
-
|
|
960
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
961
|
+
console.debug("[SQL]", query);
|
|
962
|
+
}
|
|
910
963
|
try{
|
|
911
964
|
this.db.connect(this.db);
|
|
912
965
|
const res = this.db.query(query);
|
|
@@ -937,12 +990,13 @@ class SQLLiteEngine {
|
|
|
937
990
|
}
|
|
938
991
|
|
|
939
992
|
_run(query){
|
|
940
|
-
try{
|
|
941
|
-
|
|
942
|
-
|
|
993
|
+
try{
|
|
994
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
995
|
+
console.debug("[SQL]", query);
|
|
996
|
+
}
|
|
943
997
|
this.db.connect(this.db);
|
|
944
998
|
const result = this.db.query(query);
|
|
945
|
-
|
|
999
|
+
|
|
946
1000
|
return result;}
|
|
947
1001
|
catch (error) {
|
|
948
1002
|
console.error(error);
|
|
@@ -956,8 +1010,10 @@ class SQLLiteEngine {
|
|
|
956
1010
|
* Prevents SQL injection by using parameterized queries
|
|
957
1011
|
*/
|
|
958
1012
|
_executeWithParams(query, params = []){
|
|
959
|
-
|
|
960
|
-
|
|
1013
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
1014
|
+
console.debug("[SQL]", query);
|
|
1015
|
+
console.debug("[Params]", params);
|
|
1016
|
+
}
|
|
961
1017
|
try{
|
|
962
1018
|
this.db.connect(this.db);
|
|
963
1019
|
const res = this.db.query(query, params);
|
|
@@ -993,8 +1049,10 @@ class SQLLiteEngine {
|
|
|
993
1049
|
*/
|
|
994
1050
|
_runWithParams(query, params = []){
|
|
995
1051
|
try{
|
|
996
|
-
|
|
997
|
-
|
|
1052
|
+
if (process.env.LOG_SQL === 'true' || process.env.NODE_ENV !== 'production') {
|
|
1053
|
+
console.debug("[SQL]", query);
|
|
1054
|
+
console.debug("[Params]", params);
|
|
1055
|
+
}
|
|
998
1056
|
this.db.connect(this.db);
|
|
999
1057
|
const result = this.db.query(query, params);
|
|
1000
1058
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|