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
package/mySQLEngine.js
CHANGED
|
@@ -2,27 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
var tools = require('masterrecord/Tools');
|
|
4
4
|
var util = require('util');
|
|
5
|
+
var FieldTransformer = require('masterrecord/Entity/fieldTransformer');
|
|
5
6
|
|
|
6
7
|
class SQLLiteEngine {
|
|
7
8
|
|
|
8
9
|
unsupportedWords = ["order"]
|
|
9
10
|
|
|
10
11
|
update(query){
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
// Use parameterized query for security
|
|
13
|
+
// query.arg now contains {sql, params} from _buildSQLEqualToParameterized
|
|
14
|
+
if(query.arg && typeof query.arg === 'object' && query.arg.sql && query.arg.params){
|
|
15
|
+
var sqlQuery = ` UPDATE ${query.tableName} SET ${query.arg.sql} WHERE ${query.tableName}.${query.primaryKey} = ?`;
|
|
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]);
|
|
23
|
+
}
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
delete(queryObject){
|
|
16
27
|
var sqlObject = this._buildDeleteObject(queryObject);
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
// Use parameterized query to prevent SQL injection
|
|
29
|
+
var sqlQuery = `DELETE FROM ${sqlObject.tableName} WHERE ${sqlObject.tableName}.${sqlObject.primaryKey} = ?`;
|
|
30
|
+
return this._runWithParams(sqlQuery, [sqlObject.value]);
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
insert(queryObject){
|
|
22
|
-
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
// Use NEW SECURE parameterized version
|
|
35
|
+
var sqlObject = this._buildSQLInsertObjectParameterized(queryObject, queryObject.__entity);
|
|
36
|
+
if(sqlObject === -1){
|
|
37
|
+
throw new Error('INSERT failed: No columns to insert');
|
|
38
|
+
}
|
|
39
|
+
var query = `INSERT INTO ${sqlObject.tableName} (${sqlObject.columns}) VALUES (${sqlObject.placeholders})`;
|
|
40
|
+
var queryObj = this._runWithParams(query, sqlObject.params);
|
|
41
|
+
// return
|
|
26
42
|
var open = {
|
|
27
43
|
"id": queryObj.insertId
|
|
28
44
|
};
|
|
@@ -39,9 +55,12 @@ class SQLLiteEngine {
|
|
|
39
55
|
queryString = this.buildQuery(query, entity, context);
|
|
40
56
|
}
|
|
41
57
|
if(queryString.query){
|
|
58
|
+
// Get parameters from query script
|
|
59
|
+
const params = query.parameters ? query.parameters.getParams() : [];
|
|
42
60
|
console.log("SQL:", queryString.query);
|
|
61
|
+
console.log("Params:", params);
|
|
43
62
|
this.db.connect(this.db);
|
|
44
|
-
const result = this.db.query(queryString.query);
|
|
63
|
+
const result = this.db.query(queryString.query, params);
|
|
45
64
|
console.log("results:", result);
|
|
46
65
|
return result;
|
|
47
66
|
}
|
|
@@ -64,9 +83,13 @@ class SQLLiteEngine {
|
|
|
64
83
|
}
|
|
65
84
|
if(queryString.query){
|
|
66
85
|
var queryCount = queryObject.count(queryString.query)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
// Get parameters from query script
|
|
87
|
+
const params = query.parameters ? query.parameters.getParams() : [];
|
|
88
|
+
console.log("SQL:", queryCount);
|
|
89
|
+
console.log("Params:", params);
|
|
90
|
+
this.db.connect(this.db);
|
|
91
|
+
var queryReturn = this.db.query(queryCount, params);
|
|
92
|
+
return queryReturn[0]; // MySQL returns array, get first row
|
|
70
93
|
}
|
|
71
94
|
return null;
|
|
72
95
|
} catch (err) {
|
|
@@ -85,9 +108,12 @@ class SQLLiteEngine {
|
|
|
85
108
|
queryString = this.buildQuery(query, entity, context);
|
|
86
109
|
}
|
|
87
110
|
if(queryString.query){
|
|
111
|
+
// Get parameters from query script
|
|
112
|
+
const params = query.parameters ? query.parameters.getParams() : [];
|
|
88
113
|
console.log("SQL:", queryString.query);
|
|
114
|
+
console.log("Params:", params);
|
|
89
115
|
this.db.connect(this.db);
|
|
90
|
-
const result = this.db.query(queryString.query);
|
|
116
|
+
const result = this.db.query(queryString.query, params);
|
|
91
117
|
console.log("results:", result);
|
|
92
118
|
return result;
|
|
93
119
|
}
|
|
@@ -101,18 +127,18 @@ class SQLLiteEngine {
|
|
|
101
127
|
// Introspection helpers
|
|
102
128
|
tableExists(tableName){
|
|
103
129
|
try{
|
|
104
|
-
const sql = `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME =
|
|
130
|
+
const sql = `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`;
|
|
105
131
|
this.db.connect(this.db);
|
|
106
|
-
const res = this.db.query(sql);
|
|
132
|
+
const res = this.db.query(sql, [tableName]);
|
|
107
133
|
return Array.isArray(res) ? res.length > 0 : !!res?.length;
|
|
108
134
|
}catch(_){ return false; }
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
getTableInfo(tableName){
|
|
112
138
|
try{
|
|
113
|
-
const sql = `SELECT COLUMN_NAME as name, COLUMN_DEFAULT as dflt_value, IS_NULLABLE as is_nullable, DATA_TYPE as data_type FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME =
|
|
139
|
+
const sql = `SELECT COLUMN_NAME as name, COLUMN_DEFAULT as dflt_value, IS_NULLABLE as is_nullable, DATA_TYPE as data_type FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`;
|
|
114
140
|
this.db.connect(this.db);
|
|
115
|
-
const res = this.db.query(sql);
|
|
141
|
+
const res = this.db.query(sql, [tableName]);
|
|
116
142
|
return res || [];
|
|
117
143
|
}catch(_){ return []; }
|
|
118
144
|
}
|
|
@@ -182,6 +208,12 @@ class SQLLiteEngine {
|
|
|
182
208
|
if(func === "IN"){
|
|
183
209
|
return `${ent}.${field} ${func} ${arg}`;
|
|
184
210
|
}
|
|
211
|
+
// Check if arg is a parameterized placeholder (? for MySQL/SQLite, $1/$2/etc for Postgres)
|
|
212
|
+
var isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
|
|
213
|
+
if(isPlaceholder){
|
|
214
|
+
// Don't quote placeholders - they must remain as bare ? or $1
|
|
215
|
+
return `${ent}.${field} ${func} ${arg}`;
|
|
216
|
+
}
|
|
185
217
|
var safeArg = (typeof arg === 'string' || arg instanceof String)
|
|
186
218
|
? $that._santizeSingleQuotes(arg, { entityName: ent, fieldName: field })
|
|
187
219
|
: String(arg);
|
|
@@ -528,6 +560,177 @@ class SQLLiteEngine {
|
|
|
528
560
|
return {tableName: tableName, primaryKey : primaryKey, value : value};
|
|
529
561
|
}
|
|
530
562
|
|
|
563
|
+
/**
|
|
564
|
+
* NEW SECURE VERSION: Build SQL SET clause with parameterized queries (MySQL)
|
|
565
|
+
* Returns {sql: "column1 = ?, column2 = ?", params: [value1, value2]}
|
|
566
|
+
*/
|
|
567
|
+
_buildSQLEqualToParameterized(model){
|
|
568
|
+
var $that = this;
|
|
569
|
+
var sqlParts = [];
|
|
570
|
+
var params = [];
|
|
571
|
+
var dirtyFields = model.__dirtyFields;
|
|
572
|
+
|
|
573
|
+
for (var column in dirtyFields) {
|
|
574
|
+
var fieldName = dirtyFields[column];
|
|
575
|
+
var entityDef = model.__entity[fieldName];
|
|
576
|
+
if(entityDef && entityDef.nullable === false && entityDef.primary !== true){
|
|
577
|
+
var persistedValue;
|
|
578
|
+
switch(entityDef.type){
|
|
579
|
+
case "integer": persistedValue = model["_" + fieldName]; break;
|
|
580
|
+
case "belongsTo": persistedValue = model["_" + fieldName] !== undefined ? model["_" + fieldName] : model[fieldName]; break;
|
|
581
|
+
default: persistedValue = model[fieldName];
|
|
582
|
+
}
|
|
583
|
+
var isEmptyString = (typeof persistedValue === 'string') && (persistedValue.trim() === '');
|
|
584
|
+
if(persistedValue === undefined || persistedValue === null || isEmptyString){
|
|
585
|
+
throw `Entity ${model.__entity.__name} column ${fieldName} is a required Field`;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
var type = model.__entity[dirtyFields[column]].type;
|
|
590
|
+
if(model.__entity[dirtyFields[column]].relationshipType === "belongsTo"){ type = "belongsTo"; }
|
|
591
|
+
|
|
592
|
+
switch(type){
|
|
593
|
+
case "belongsTo":
|
|
594
|
+
const foreignKey = model.__entity[dirtyFields[column]].foreignKey;
|
|
595
|
+
let fkValue = model[dirtyFields[column]];
|
|
596
|
+
// 🔥 Apply toDatabase transformer
|
|
597
|
+
try { fkValue = FieldTransformer.toDatabase(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
598
|
+
catch(transformError) { throw new Error(`UPDATE failed: ${transformError.message}`); }
|
|
599
|
+
try { fkValue = $that._validateAndCoerceFieldType(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
600
|
+
catch(typeError) { throw new Error(`UPDATE failed: ${typeError.message}`); }
|
|
601
|
+
var fore = `_${dirtyFields[column]}`;
|
|
602
|
+
sqlParts.push(`${foreignKey} = ?`);
|
|
603
|
+
params.push(model[fore]);
|
|
604
|
+
break;
|
|
605
|
+
case "integer":
|
|
606
|
+
var intValue = model["_" + dirtyFields[column]];
|
|
607
|
+
// 🔥 Apply toDatabase transformer
|
|
608
|
+
try { intValue = FieldTransformer.toDatabase(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
609
|
+
catch(transformError) { throw new Error(`UPDATE failed: ${transformError.message}`); }
|
|
610
|
+
try { intValue = $that._validateAndCoerceFieldType(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
611
|
+
catch(typeError) { throw new Error(`UPDATE failed: ${typeError.message}`); }
|
|
612
|
+
sqlParts.push(`${dirtyFields[column]} = ?`);
|
|
613
|
+
params.push(intValue);
|
|
614
|
+
break;
|
|
615
|
+
case "string":
|
|
616
|
+
var strValue = model[dirtyFields[column]];
|
|
617
|
+
// 🔥 Apply toDatabase transformer
|
|
618
|
+
try { strValue = FieldTransformer.toDatabase(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
619
|
+
catch(transformError) { throw new Error(`UPDATE failed: ${transformError.message}`); }
|
|
620
|
+
try { strValue = $that._validateAndCoerceFieldType(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
621
|
+
catch(typeError) { throw new Error(`UPDATE failed: ${typeError.message}`); }
|
|
622
|
+
sqlParts.push(`${dirtyFields[column]} = ?`);
|
|
623
|
+
params.push(strValue);
|
|
624
|
+
break;
|
|
625
|
+
case "boolean":
|
|
626
|
+
var boolValue = model[dirtyFields[column]];
|
|
627
|
+
// 🔥 Apply toDatabase transformer
|
|
628
|
+
try { boolValue = FieldTransformer.toDatabase(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
629
|
+
catch(transformError) { throw new Error(`UPDATE failed: ${transformError.message}`); }
|
|
630
|
+
try { boolValue = $that._validateAndCoerceFieldType(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
631
|
+
catch(typeError) { throw new Error(`UPDATE failed: ${typeError.message}`); }
|
|
632
|
+
boolValue = $that._convertValueForDatabase(boolValue, model.__entity[dirtyFields[column]].type);
|
|
633
|
+
var bool = model.__entity[dirtyFields[column]].valueConversion ? tools.convertBooleanToNumber(boolValue) : boolValue;
|
|
634
|
+
sqlParts.push(`${dirtyFields[column]} = ?`);
|
|
635
|
+
params.push(bool);
|
|
636
|
+
break;
|
|
637
|
+
case "time":
|
|
638
|
+
var timeValue = model[dirtyFields[column]];
|
|
639
|
+
// 🔥 Apply toDatabase transformer
|
|
640
|
+
try { timeValue = FieldTransformer.toDatabase(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
641
|
+
catch(transformError) { throw new Error(`UPDATE failed: ${transformError.message}`); }
|
|
642
|
+
try { timeValue = $that._validateAndCoerceFieldType(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]); }
|
|
643
|
+
catch(typeError) { throw new Error(`UPDATE failed: ${typeError.message}`); }
|
|
644
|
+
sqlParts.push(`${dirtyFields[column]} = ?`);
|
|
645
|
+
params.push(timeValue);
|
|
646
|
+
break;
|
|
647
|
+
case "hasMany":
|
|
648
|
+
sqlParts.push(`${dirtyFields[column]} = ?`);
|
|
649
|
+
params.push(model[dirtyFields[column]]);
|
|
650
|
+
break;
|
|
651
|
+
default:
|
|
652
|
+
sqlParts.push(`${dirtyFields[column]} = ?`);
|
|
653
|
+
params.push(model[dirtyFields[column]]);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return sqlParts.length > 0 ? { sql: sqlParts.join(', '), params: params } : -1;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* NEW SECURE VERSION: Build SQL INSERT with parameterized queries (MySQL)
|
|
662
|
+
* Returns {tableName, columns, placeholders, params}
|
|
663
|
+
*/
|
|
664
|
+
_buildSQLInsertObjectParameterized(fields, modelEntity){
|
|
665
|
+
var $that = this;
|
|
666
|
+
var columnNames = [];
|
|
667
|
+
var params = [];
|
|
668
|
+
|
|
669
|
+
for (var column in modelEntity) {
|
|
670
|
+
if(column.indexOf("__") === -1 ){
|
|
671
|
+
var fieldColumn = fields[column];
|
|
672
|
+
|
|
673
|
+
if((fieldColumn !== undefined && fieldColumn !== null ) && typeof(fieldColumn) !== "object"){
|
|
674
|
+
// 🔥 Apply toDatabase transformer before validation
|
|
675
|
+
try { fieldColumn = FieldTransformer.toDatabase(fieldColumn, modelEntity[column], modelEntity.__name, column); }
|
|
676
|
+
catch(transformError) { throw new Error(`INSERT failed: ${transformError.message}`); }
|
|
677
|
+
|
|
678
|
+
try { fieldColumn = $that._validateAndCoerceFieldType(fieldColumn, modelEntity[column], modelEntity.__name, column); }
|
|
679
|
+
catch(typeError) { throw new Error(`INSERT failed: ${typeError.message}`); }
|
|
680
|
+
|
|
681
|
+
fieldColumn = $that._convertValueForDatabase(fieldColumn, modelEntity[column].type);
|
|
682
|
+
|
|
683
|
+
var relationship = modelEntity[column].relationshipType;
|
|
684
|
+
var actualColumn = relationship === "belongsTo" ? modelEntity[column].foreignKey : column;
|
|
685
|
+
columnNames.push(actualColumn);
|
|
686
|
+
params.push(fieldColumn);
|
|
687
|
+
}
|
|
688
|
+
else{
|
|
689
|
+
switch(modelEntity[column].type){
|
|
690
|
+
case "belongsTo":
|
|
691
|
+
var fieldObject = tools.findTrackedObject(fields.__context.__trackedEntities, column);
|
|
692
|
+
if(Object.keys(fieldObject).length > 0){
|
|
693
|
+
var primaryKey = tools.getPrimaryKeyObject(fieldObject.__entity);
|
|
694
|
+
fieldColumn = fieldObject[primaryKey];
|
|
695
|
+
var actualColumn = modelEntity[column].foreignKey;
|
|
696
|
+
columnNames.push(actualColumn);
|
|
697
|
+
params.push(fieldColumn);
|
|
698
|
+
}
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if(columnNames.length > 0){
|
|
706
|
+
var placeholders = params.map(() => '?').join(', ');
|
|
707
|
+
return { tableName: modelEntity.__name, columns: columnNames.join(', '), placeholders: placeholders, params: params };
|
|
708
|
+
} else {
|
|
709
|
+
return -1;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Convert validated value to database-specific format
|
|
715
|
+
* Modern ORM pattern: transparent database-specific conversions
|
|
716
|
+
*
|
|
717
|
+
* @param {*} value - Already validated value
|
|
718
|
+
* @param {string} fieldType - Field type from entity definition
|
|
719
|
+
* @returns {*} Database-ready value
|
|
720
|
+
*/
|
|
721
|
+
_convertValueForDatabase(value, fieldType){
|
|
722
|
+
if(value === undefined || value === null){
|
|
723
|
+
return value;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// MySQL boolean conversion: JavaScript boolean → TINYINT (1/0)
|
|
727
|
+
if(fieldType === 'boolean' && typeof value === 'boolean'){
|
|
728
|
+
return value ? 1 : 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return value;
|
|
732
|
+
}
|
|
733
|
+
|
|
531
734
|
/**
|
|
532
735
|
* Validate and coerce field value to match entity type definition
|
|
533
736
|
* Throws detailed error if type cannot be coerced
|
|
@@ -748,6 +951,59 @@ class SQLLiteEngine {
|
|
|
748
951
|
}
|
|
749
952
|
}
|
|
750
953
|
|
|
954
|
+
/**
|
|
955
|
+
* NEW SECURE VERSION: Execute query with parameters
|
|
956
|
+
* Prevents SQL injection by using parameterized queries
|
|
957
|
+
*/
|
|
958
|
+
_executeWithParams(query, params = []){
|
|
959
|
+
console.log("SQL:", query);
|
|
960
|
+
console.log("Params:", params);
|
|
961
|
+
try{
|
|
962
|
+
this.db.connect(this.db);
|
|
963
|
+
const res = this.db.query(query, params);
|
|
964
|
+
if(res === null){
|
|
965
|
+
const dbName = (this.db && this.db.config && this.db.config.database) ? this.db.config.database : '(unknown)';
|
|
966
|
+
if(this.db && this.db.lastErrorCode === 'ER_BAD_DB_ERROR'){
|
|
967
|
+
console.error(`MySQL execute skipped: database '${dbName}' does not exist`);
|
|
968
|
+
}else{
|
|
969
|
+
console.error('MySQL execute skipped: connection not defined');
|
|
970
|
+
}
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
return res;
|
|
974
|
+
}catch(err){
|
|
975
|
+
const code = err && err.code ? err.code : '';
|
|
976
|
+
if(code === 'ER_BAD_DB_ERROR' || code === 'ECONNREFUSED' || code === 'PROTOCOL_CONNECTION_LOST'){
|
|
977
|
+
const dbName = (this.db && this.db.config && this.db.config.database) ? this.db.config.database : '(unknown)';
|
|
978
|
+
if(code === 'ER_BAD_DB_ERROR'){
|
|
979
|
+
console.error(`MySQL execute skipped: database '${dbName}' does not exist`);
|
|
980
|
+
} else {
|
|
981
|
+
console.error('MySQL execute skipped: connection not defined');
|
|
982
|
+
}
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
console.error(err);
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* NEW SECURE VERSION: Run query with parameters
|
|
992
|
+
* Prevents SQL injection by using parameterized queries
|
|
993
|
+
*/
|
|
994
|
+
_runWithParams(query, params = []){
|
|
995
|
+
try{
|
|
996
|
+
console.log("SQL:", query);
|
|
997
|
+
console.log("Params:", params);
|
|
998
|
+
this.db.connect(this.db);
|
|
999
|
+
const result = this.db.query(query, params);
|
|
1000
|
+
return result;
|
|
1001
|
+
}
|
|
1002
|
+
catch (error) {
|
|
1003
|
+
console.error(error);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
751
1007
|
setDB(db, type){
|
|
752
1008
|
this.db = db;
|
|
753
1009
|
this.dbType = type; // this will let us know which type of sqlengine to use.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"glob": "^13.0.0",
|
|
32
32
|
"deep-object-diff": "^1.1.9",
|
|
33
33
|
"pg": "^8.16.3",
|
|
34
|
-
"sync-mysql2": "^1.0.
|
|
34
|
+
"sync-mysql2": "^1.0.8",
|
|
35
35
|
"app-root-path": "^3.1.0",
|
|
36
|
-
"better-sqlite3": "^12.
|
|
36
|
+
"better-sqlite3": "^12.6.0"
|
|
37
37
|
}
|
|
38
38
|
}
|