masterrecord 0.2.34 → 0.3.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.
Files changed (40) hide show
  1. package/.claude/settings.local.json +25 -1
  2. package/Entity/entityModel.js +6 -0
  3. package/Entity/entityTrackerModel.js +20 -3
  4. package/Entity/fieldTransformer.js +266 -0
  5. package/Migrations/migrationMySQLQuery.js +145 -1
  6. package/Migrations/migrationPostgresQuery.js +402 -0
  7. package/Migrations/migrationSQLiteQuery.js +145 -1
  8. package/Migrations/schema.js +131 -28
  9. package/QueryLanguage/queryMethods.js +193 -15
  10. package/QueryLanguage/queryParameters.js +136 -0
  11. package/QueryLanguage/queryScript.js +14 -5
  12. package/SQLLiteEngine.js +309 -19
  13. package/context.js +57 -12
  14. package/docs/INCLUDES_CLARIFICATION.md +202 -0
  15. package/docs/METHODS_REFERENCE.md +184 -0
  16. package/docs/MIGRATIONS_GUIDE.md +699 -0
  17. package/docs/POSTGRESQL_SETUP.md +415 -0
  18. package/examples/jsonArrayTransformer.js +215 -0
  19. package/mySQLEngine.js +249 -17
  20. package/package.json +6 -6
  21. package/postgresEngine.js +434 -491
  22. package/postgresSyncConnect.js +209 -0
  23. package/readme.md +1121 -265
  24. package/test/anyCommaStringTest.js +237 -0
  25. package/test/anyMethodTest.js +176 -0
  26. package/test/findByIdTest.js +227 -0
  27. package/test/includesFeatureTest.js +183 -0
  28. package/test/includesTransformTest.js +110 -0
  29. package/test/newMethodTest.js +330 -0
  30. package/test/newMethodUnitTest.js +320 -0
  31. package/test/parameterizedPlaceholderTest.js +159 -0
  32. package/test/postgresEngineTest.js +463 -0
  33. package/test/postgresIntegrationTest.js +381 -0
  34. package/test/securityTest.js +268 -0
  35. package/test/singleDollarPlaceholderTest.js +238 -0
  36. package/test/tablePrefixTest.js +100 -0
  37. package/test/transformerTest.js +287 -0
  38. package/test/verifyFindById.js +169 -0
  39. package/test/verifyNewMethod.js +191 -0
  40. package/test/whereChainingTest.js +88 -0
@@ -1,11 +1,16 @@
1
- // version 0.0.8
1
+ // version 0.0.9
2
2
 
3
3
  const LOG_OPERATORS_REGEX = /(\|\|)|(&&)/;
4
4
  var tools = require('../Tools');
5
+ const QueryParameters = require('./queryParameters');
5
6
 
6
7
  class queryScript{
7
8
 
8
- constructor(){ }
9
+ constructor(){
10
+ this.parameters = new QueryParameters();
11
+ // Initialize script.parameters reference
12
+ this.script.parameters = this.parameters;
13
+ }
9
14
 
10
15
  script = {
11
16
  select : false,
@@ -19,11 +24,13 @@ class queryScript{
19
24
  skip: 0,
20
25
  orderBy : false,
21
26
  orderByDesc : false,
22
- parentName : ""
27
+ parentName : "",
28
+ parameters: null // Will hold QueryParameters instance
23
29
  };
24
30
 
25
31
 
26
32
  reset(){
33
+ this.parameters.reset();
27
34
  this.script = {
28
35
  select : false,
29
36
  where: false,
@@ -35,7 +42,9 @@ class queryScript{
35
42
  take : 0,
36
43
  skip: 0,
37
44
  orderBy : false,
38
- orderByDesc : false
45
+ orderByDesc : false,
46
+ parentName : "",
47
+ parameters: this.parameters
39
48
  };
40
49
  }
41
50
 
@@ -140,7 +149,7 @@ class queryScript{
140
149
  else if(type === "where"){
141
150
  // If where already exists, merge new expressions into existing where so multiple
142
151
  // chained where(...) calls combine into a single WHERE clause (joined by AND).
143
- if(obj.where && obj[entityName] && cachedExpr[entityName]){
152
+ if(obj.where && obj.where[entityName] && cachedExpr[entityName]){
144
153
  const existingQuery = obj.where[entityName].query || {};
145
154
  const incomingQuery = cachedExpr[entityName].query || {};
146
155
  const existingExprs = existingQuery.expressions || [];
package/SQLLiteEngine.js CHANGED
@@ -1,28 +1,46 @@
1
1
  // Version 0.0.23
2
2
  var tools = require('masterrecord/Tools');
3
+ var FieldTransformer = require('masterrecord/Entity/fieldTransformer');
3
4
 
4
5
  class SQLLiteEngine {
5
6
 
6
7
  unsupportedWords = ["order"]
7
8
 
8
9
  update(query){
9
- var sqlQuery = ` UPDATE [${query.tableName}]
10
- SET ${query.arg}
11
- WHERE [${query.tableName}].[${query.primaryKey}] = ${query.primaryKeyValue}` // primary key for that table =
12
- return this._run(sqlQuery);
10
+ // Use parameterized query for security
11
+ // query.arg now contains {sql, params} from _buildSQLEqualToParameterized
12
+ if(query.arg && typeof query.arg === 'object' && query.arg.sql && query.arg.params){
13
+ var sqlQuery = ` UPDATE [${query.tableName}]
14
+ SET ${query.arg.sql}
15
+ 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}]
22
+ SET ${query.arg}
23
+ WHERE [${query.tableName}].[${query.primaryKey}] = ?`;
24
+ return this._runWithParams(sqlQuery, [query.primaryKeyValue]);
25
+ }
13
26
  }
14
27
 
15
28
  delete(queryObject){
16
29
  var sqlObject = this._buildDeleteObject(queryObject);
17
- var sqlQuery = `DELETE FROM [${sqlObject.tableName}] WHERE [${sqlObject.tableName}].[${sqlObject.primaryKey}] = ${sqlObject.value}`;
18
- return this._execute(sqlQuery);
30
+ // Use parameterized query to prevent SQL injection
31
+ var sqlQuery = `DELETE FROM [${sqlObject.tableName}] WHERE [${sqlObject.tableName}].[${sqlObject.primaryKey}] = ?`;
32
+ return this._executeWithParams(sqlQuery, [sqlObject.value]);
19
33
  }
20
34
 
21
35
  insert(queryObject){
22
- var sqlObject = this._buildSQLInsertObject(queryObject, queryObject.__entity);
36
+ // Use NEW SECURE parameterized version
37
+ var sqlObject = this._buildSQLInsertObjectParameterized(queryObject, queryObject.__entity);
38
+ if(sqlObject === -1){
39
+ throw new Error('INSERT failed: No columns to insert');
40
+ }
23
41
  var query = `INSERT INTO [${sqlObject.tableName}] (${sqlObject.columns})
24
- VALUES (${sqlObject.values})`;
25
- var queryObj = this._run(query);
42
+ VALUES (${sqlObject.placeholders})`;
43
+ var queryObj = this._runWithParams(query, sqlObject.params);
26
44
  var open = {
27
45
  "id": queryObj.lastInsertRowid
28
46
  };
@@ -44,8 +62,11 @@ class SQLLiteEngine {
44
62
  }
45
63
  }
46
64
  if(queryString.query){
65
+ // Get parameters from query script
66
+ const params = query.parameters ? query.parameters.getParams() : [];
47
67
  console.log("SQL:", queryString.query);
48
- var queryReturn = this.db.prepare(queryString.query).get();
68
+ console.log("Params:", params);
69
+ var queryReturn = this.db.prepare(queryString.query).get(...params);
49
70
  return queryReturn;
50
71
  }
51
72
  return null;
@@ -58,8 +79,9 @@ class SQLLiteEngine {
58
79
  // Introspection helpers
59
80
  tableExists(tableName){
60
81
  try{
61
- const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`;
62
- const row = this.db.prepare(sql).get();
82
+ // Use parameterized query to prevent SQL injection
83
+ const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
84
+ const row = this.db.prepare(sql).get(tableName);
63
85
  return !!row;
64
86
  }catch(_){ return false; }
65
87
  }
@@ -88,8 +110,11 @@ class SQLLiteEngine {
88
110
  }
89
111
  if(queryString.query){
90
112
  var queryCount = queryString.query
113
+ // Get parameters from query script
114
+ const params = query.parameters ? query.parameters.getParams() : [];
91
115
  console.log("SQL:", queryCount );
92
- var queryReturn = this.db.prepare(queryCount ).get();
116
+ console.log("Params:", params);
117
+ var queryReturn = this.db.prepare(queryCount).get(...params);
93
118
  return queryReturn;
94
119
  }
95
120
  return null;
@@ -110,8 +135,11 @@ class SQLLiteEngine {
110
135
  selectQuery = this.buildQuery(query, entity, context);
111
136
  }
112
137
  if(selectQuery.query){
138
+ // Get parameters from query script
139
+ const params = query.parameters ? query.parameters.getParams() : [];
113
140
  console.log("SQL:", selectQuery.query);
114
- var queryReturn = this.db.prepare(selectQuery.query).all();
141
+ console.log("Params:", params);
142
+ var queryReturn = this.db.prepare(selectQuery.query).all(...params);
115
143
  return queryReturn;
116
144
  }
117
145
  return null;
@@ -247,16 +275,28 @@ class SQLLiteEngine {
247
275
  if(item.expressions[exp].arg === "null"){
248
276
  strQuery = `${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
249
277
  }else{
250
- strQuery = `${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
278
+ // Check if arg is a parameterized placeholder
279
+ var isPlaceholder = (item.expressions[exp].arg === '?' || /^\$\d+$/.test(item.expressions[exp].arg));
280
+ if(isPlaceholder){
281
+ strQuery = `${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
282
+ }else{
283
+ strQuery = `${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
284
+ }
251
285
  }
252
286
  }
253
287
  else{
254
288
  if(item.expressions[exp].arg === "null"){
255
289
  strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
256
290
  }else{
257
- strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
291
+ // Check if arg is a parameterized placeholder
292
+ var isPlaceholder = (item.expressions[exp].arg === '?' || /^\$\d+$/.test(item.expressions[exp].arg));
293
+ if(isPlaceholder){
294
+ strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} ${item.expressions[exp].arg}`;
295
+ }else{
296
+ strQuery = `${strQuery} and ${entity}.${field} ${item.expressions[exp].func} '${item.expressions[exp].arg}'`;
297
+ }
258
298
  }
259
-
299
+
260
300
  }
261
301
  }
262
302
  andList.push(strQuery);
@@ -313,6 +353,12 @@ class SQLLiteEngine {
313
353
  if(func === "IN"){
314
354
  return `${ent}.${field} ${func} ${arg}`;
315
355
  }
356
+ // Check if arg is a parameterized placeholder (? for MySQL/SQLite, $1/$2/etc for Postgres)
357
+ var isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
358
+ if(isPlaceholder){
359
+ // Don't quote placeholders - they must remain as bare ? or $1
360
+ return `${ent}.${field} ${func} ${arg}`;
361
+ }
316
362
  return `${ent}.${field} ${func} '${arg}'`;
317
363
  }
318
364
 
@@ -648,10 +694,172 @@ class SQLLiteEngine {
648
694
  else{
649
695
  return -1;
650
696
  }
651
-
697
+
652
698
  }
653
699
 
654
-
700
+ /**
701
+ * NEW SECURE VERSION: Build SQL SET clause with parameterized queries
702
+ * Returns {sql: "column1 = ?, column2 = ?", params: [value1, value2]}
703
+ * This prevents SQL injection by separating SQL structure from values
704
+ */
705
+ _buildSQLEqualToParameterized(model){
706
+ var $that = this;
707
+ var sqlParts = [];
708
+ var params = [];
709
+ var dirtyFields = model.__dirtyFields;
710
+
711
+ for (var column in dirtyFields) {
712
+ // Validate non-nullable constraints on updates
713
+ var fieldName = dirtyFields[column];
714
+ var entityDef = model.__entity[fieldName];
715
+ if(entityDef && entityDef.nullable === false && entityDef.primary !== true){
716
+ // Determine the value that will actually be persisted for this field
717
+ var persistedValue;
718
+ switch(entityDef.type){
719
+ case "integer":
720
+ persistedValue = model["_" + fieldName];
721
+ break;
722
+ case "belongsTo":
723
+ persistedValue = model["_" + fieldName] !== undefined ? model["_" + fieldName] : model[fieldName];
724
+ break;
725
+ default:
726
+ persistedValue = model[fieldName];
727
+ }
728
+ var isEmptyString = (typeof persistedValue === 'string') && (persistedValue.trim() === '');
729
+ if(persistedValue === undefined || persistedValue === null || isEmptyString){
730
+ throw `Entity ${model.__entity.__name} column ${fieldName} is a required Field`;
731
+ }
732
+ }
733
+
734
+ var type = model.__entity[dirtyFields[column]].type;
735
+
736
+ if(model.__entity[dirtyFields[column]].relationshipType === "belongsTo"){
737
+ type = "belongsTo";
738
+ }
739
+
740
+ // Build parameterized SET clause
741
+ switch(type){
742
+ case "belongsTo":
743
+ const foreignKey = model.__entity[dirtyFields[column]].foreignKey;
744
+ let fkValue = model[dirtyFields[column]];
745
+
746
+ // 🔥 Apply toDatabase transformer before validation
747
+ try {
748
+ fkValue = FieldTransformer.toDatabase(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
749
+ } catch(transformError) {
750
+ throw new Error(`UPDATE failed: ${transformError.message}`);
751
+ }
752
+
753
+ try {
754
+ fkValue = $that._validateAndCoerceFieldType(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
755
+ } catch(typeError) {
756
+ throw new Error(`UPDATE failed: ${typeError.message}`);
757
+ }
758
+ var fore = `_${dirtyFields[column]}`;
759
+ sqlParts.push(`[${foreignKey}] = ?`);
760
+ params.push(model[fore]);
761
+ break;
762
+ case "integer":
763
+ var intValue = model["_" + dirtyFields[column]];
764
+
765
+ // 🔥 Apply toDatabase transformer before validation
766
+ try {
767
+ intValue = FieldTransformer.toDatabase(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
768
+ } catch(transformError) {
769
+ throw new Error(`UPDATE failed: ${transformError.message}`);
770
+ }
771
+
772
+ try {
773
+ intValue = $that._validateAndCoerceFieldType(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
774
+ } catch(typeError) {
775
+ throw new Error(`UPDATE failed: ${typeError.message}`);
776
+ }
777
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
778
+ params.push(intValue);
779
+ break;
780
+ case "string":
781
+ var strValue = model[dirtyFields[column]];
782
+
783
+ // 🔥 Apply toDatabase transformer before validation
784
+ try {
785
+ strValue = FieldTransformer.toDatabase(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
786
+ } catch(transformError) {
787
+ throw new Error(`UPDATE failed: ${transformError.message}`);
788
+ }
789
+
790
+ try {
791
+ strValue = $that._validateAndCoerceFieldType(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
792
+ } catch(typeError) {
793
+ throw new Error(`UPDATE failed: ${typeError.message}`);
794
+ }
795
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
796
+ params.push(strValue);
797
+ break;
798
+ case "boolean":
799
+ var boolValue = model[dirtyFields[column]];
800
+
801
+ // 🔥 Apply toDatabase transformer before validation
802
+ try {
803
+ boolValue = FieldTransformer.toDatabase(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
804
+ } catch(transformError) {
805
+ throw new Error(`UPDATE failed: ${transformError.message}`);
806
+ }
807
+
808
+ try {
809
+ boolValue = $that._validateAndCoerceFieldType(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
810
+ } catch(typeError) {
811
+ throw new Error(`UPDATE failed: ${typeError.message}`);
812
+ }
813
+ var bool;
814
+ if(model.__entity[dirtyFields[column]].valueConversion){
815
+ bool = tools.convertBooleanToNumber(boolValue);
816
+ }
817
+ else{
818
+ bool = boolValue;
819
+ }
820
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
821
+ params.push(bool);
822
+ break;
823
+ case "time":
824
+ var timeValue = model[dirtyFields[column]];
825
+
826
+ // 🔥 Apply toDatabase transformer before validation
827
+ try {
828
+ timeValue = FieldTransformer.toDatabase(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
829
+ } catch(transformError) {
830
+ throw new Error(`UPDATE failed: ${transformError.message}`);
831
+ }
832
+
833
+ try {
834
+ timeValue = $that._validateAndCoerceFieldType(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
835
+ } catch(typeError) {
836
+ throw new Error(`UPDATE failed: ${typeError.message}`);
837
+ }
838
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
839
+ params.push(timeValue);
840
+ break;
841
+ case "hasMany":
842
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
843
+ params.push(model[dirtyFields[column]]);
844
+ break;
845
+ default:
846
+ sqlParts.push(`[${dirtyFields[column]}] = ?`);
847
+ params.push(model[dirtyFields[column]]);
848
+ }
849
+ }
850
+
851
+ if(sqlParts.length > 0){
852
+ return {
853
+ sql: sqlParts.join(', '),
854
+ params: params
855
+ };
856
+ }
857
+ else{
858
+ return -1;
859
+ }
860
+ }
861
+
862
+
655
863
  _buildDeleteObject(currentModel){
656
864
  var primaryKey = currentModel.__Key === undefined ? tools.getPrimaryKeyObject(currentModel.__entity) : currentModel.__Key;
657
865
  var value = currentModel.__value === undefined ? currentModel[primaryKey] : currentModel.__value;
@@ -817,6 +1025,76 @@ class SQLLiteEngine {
817
1025
 
818
1026
  }
819
1027
 
1028
+ /**
1029
+ * NEW SECURE VERSION: Build SQL INSERT with parameterized queries
1030
+ * Returns {tableName, columns, placeholders, params}
1031
+ * This prevents SQL injection by separating SQL structure from values
1032
+ */
1033
+ _buildSQLInsertObjectParameterized(fields, modelEntity){
1034
+ var $that = this;
1035
+ var columnNames = [];
1036
+ var params = [];
1037
+
1038
+ for (var column in modelEntity) {
1039
+ // Skip internal properties
1040
+ if(column.indexOf("__") === -1 ){
1041
+ var fieldColumn = fields[column];
1042
+
1043
+ if((fieldColumn !== undefined && fieldColumn !== null ) && typeof(fieldColumn) !== "object"){
1044
+ // 🔥 Apply toDatabase transformer before validation
1045
+ try {
1046
+ fieldColumn = FieldTransformer.toDatabase(fieldColumn, modelEntity[column], modelEntity.__name, column);
1047
+ } catch(transformError) {
1048
+ throw new Error(`INSERT failed: ${transformError.message}`);
1049
+ }
1050
+
1051
+ // Validate and coerce field type before processing
1052
+ try {
1053
+ fieldColumn = $that._validateAndCoerceFieldType(fieldColumn, modelEntity[column], modelEntity.__name, column);
1054
+ } catch(typeError) {
1055
+ throw new Error(`INSERT failed: ${typeError.message}`);
1056
+ }
1057
+
1058
+ var relationship = modelEntity[column].relationshipType;
1059
+ var actualColumn = relationship === "belongsTo" ? modelEntity[column].foreignKey : column;
1060
+
1061
+ // Add column name and parameter
1062
+ columnNames.push(`[${actualColumn}]`);
1063
+ params.push(fieldColumn);
1064
+ }
1065
+ else{
1066
+ switch(modelEntity[column].type){
1067
+ case "belongsTo":
1068
+ var fieldObject = tools.findTrackedObject(fields.__context.__trackedEntities, column);
1069
+ if(Object.keys(fieldObject).length > 0){
1070
+ var primaryKey = tools.getPrimaryKeyObject(fieldObject.__entity);
1071
+ fieldColumn = fieldObject[primaryKey];
1072
+ var actualColumn = modelEntity[column].foreignKey;
1073
+ columnNames.push(`[${actualColumn}]`);
1074
+ params.push(fieldColumn);
1075
+ } else{
1076
+ console.log("Cannot find belongs to relationship")
1077
+ }
1078
+ break;
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+
1084
+ if(columnNames.length > 0){
1085
+ // Create placeholders: ?, ?, ?, ...
1086
+ var placeholders = params.map(() => '?').join(', ');
1087
+ return {
1088
+ tableName: modelEntity.__name,
1089
+ columns: columnNames.join(', '),
1090
+ placeholders: placeholders,
1091
+ params: params
1092
+ };
1093
+ } else {
1094
+ return -1;
1095
+ }
1096
+ }
1097
+
820
1098
  // will add double single quotes to allow string to be saved.
821
1099
  _santizeSingleQuotes(value, context){
822
1100
  if (typeof value === 'string' || value instanceof String){
@@ -854,11 +1132,23 @@ class SQLLiteEngine {
854
1132
  return this.db.exec(query);
855
1133
  }
856
1134
 
1135
+ _executeWithParams(query, params = []){
1136
+ console.log("SQL:", query);
1137
+ console.log("Params:", params);
1138
+ return this.db.prepare(query).run(...params);
1139
+ }
1140
+
857
1141
  _run(query){
858
1142
  console.log("SQL:", query);
859
1143
  return this.db.prepare(query).run();
860
1144
  }
861
1145
 
1146
+ _runWithParams(query, params = []){
1147
+ console.log("SQL:", query);
1148
+ console.log("Params:", params);
1149
+ return this.db.prepare(query).run(...params);
1150
+ }
1151
+
862
1152
  setDB(db, type){
863
1153
  this.db = db;
864
1154
  this.dbType = type; // this will let us know which type of sqlengine to use.
package/context.js CHANGED
@@ -1,10 +1,11 @@
1
- // Version 0.0.16
1
+ // Version 0.0.17
2
2
 
3
3
  var modelBuilder = require('./Entity/entityModelBuilder');
4
4
  var query = require('masterrecord/QueryLanguage/queryMethods');
5
5
  var tools = require('./Tools');
6
6
  var SQLLiteEngine = require('masterrecord/SQLLiteEngine');
7
7
  var MYSQLEngine = require('masterrecord/mySQLEngine');
8
+ var PostgresEngine = require('masterrecord/postgresEngine');
8
9
  var insertManager = require('./insertManager');
9
10
  var deleteManager = require('./deleteManager');
10
11
  var globSearch = require("glob");
@@ -12,6 +13,7 @@ var fs = require('fs');
12
13
  var path = require('path');
13
14
  const appRoot = require('app-root-path');
14
15
  const MySQLClient = require('masterrecord/mySQLSyncConnect');
16
+ const PostgresClient = require('masterrecord/postgresSyncConnect');
15
17
 
16
18
  class context {
17
19
  _isModelValid = {
@@ -24,6 +26,7 @@ class context {
24
26
  __relationshipModels = [];
25
27
  __environment = "";
26
28
  __name = "";
29
+ tablePrefix = "";
27
30
  isSQLite = false;
28
31
  isMySQL = false;
29
32
  isPostgres = false;
@@ -71,7 +74,7 @@ class context {
71
74
  */
72
75
  __mysqlInit(env, sqlName){
73
76
  try{
74
-
77
+
75
78
  //const mysql = require(sqlName);
76
79
  const connection = new MySQLClient(env);
77
80
  this._SQLEngine = new MYSQLEngine();
@@ -84,6 +87,31 @@ class context {
84
87
  }
85
88
  }
86
89
 
90
+ /*
91
+ postgres expected model
92
+ {
93
+ "type": "postgres",
94
+ host : 'localhost',
95
+ port : 5432,
96
+ user : 'me',
97
+ password : 'secret',
98
+ database : 'my_db'
99
+ }
100
+ */
101
+ async __postgresInit(env, sqlName){
102
+ try{
103
+ const connection = new PostgresClient();
104
+ await connection.connect(env);
105
+ this._SQLEngine = connection.getEngine();
106
+ this._SQLEngine.__name = sqlName;
107
+ return connection.getPool();
108
+ }
109
+ catch (e) {
110
+ console.log("error PostgreSQL", e);
111
+ throw e;
112
+ }
113
+ }
114
+
87
115
  __clearErrorHandler(){
88
116
  this._isModelValid = {
89
117
  isValid: true,
@@ -211,13 +239,23 @@ class context {
211
239
  }
212
240
 
213
241
  if(type === 'mysql'){
214
- this.isMySQL = true; this.isSQLite = false;
242
+ this.isMySQL = true; this.isSQLite = false; this.isPostgres = false;
215
243
  this.db = this.__mysqlInit(options, 'mysql2');
216
244
  this._SQLEngine.setDB(this.db, 'mysql');
217
245
  return this;
218
246
  }
219
247
 
220
- throw new Error(`Unsupported database type '${options.type}'. Expected 'sqlite' or 'mysql'.`);
248
+ if(type === 'postgres' || type === 'postgresql'){
249
+ this.isPostgres = true; this.isMySQL = false; this.isSQLite = false;
250
+ // Postgres is async, so we need to handle promises
251
+ (async () => {
252
+ this.db = await this.__postgresInit(options, 'pg');
253
+ // Note: engine is already set in __postgresInit
254
+ })();
255
+ return this;
256
+ }
257
+
258
+ throw new Error(`Unsupported database type '${options.type}'. Expected 'sqlite', 'mysql', or 'postgres'.`);
221
259
  }
222
260
  catch(err){
223
261
  console.log("error:", err);
@@ -348,7 +386,14 @@ class context {
348
386
 
349
387
  dbset(model, name){
350
388
  var validModel = modelBuilder.create(model);
351
- validModel.__name = name === undefined ? model.name : name;
389
+ var tableName = name === undefined ? model.name : name;
390
+
391
+ // Apply tablePrefix if set
392
+ if(this.tablePrefix && typeof this.tablePrefix === 'string' && this.tablePrefix.length > 0){
393
+ tableName = this.tablePrefix + tableName;
394
+ }
395
+
396
+ validModel.__name = tableName;
352
397
  this.__entities.push(validModel); // model object
353
398
  var buildMod = tools.createNewInstance(validModel, query, this);
354
399
  this.__builderEntities.push(buildMod); // query builder entites
@@ -377,9 +422,9 @@ class context {
377
422
  break;
378
423
  case "modified":
379
424
  if(currentModel.__dirtyFields.length > 0){
380
- var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
381
- // build columns equal to value string
382
- var argu = this._SQLEngine._buildSQLEqualTo(cleanCurrentModel);
425
+ var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
426
+ // Use NEW SECURE parameterized version
427
+ var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
383
428
  if(argu !== -1 ){
384
429
  var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
385
430
  var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
@@ -388,7 +433,7 @@ class context {
388
433
  else{
389
434
  console.log("Nothing has been tracked, modified, created or added");
390
435
  }
391
-
436
+
392
437
  }
393
438
  else{
394
439
  console.log("Tracked entity modified with no values being changed");
@@ -419,8 +464,8 @@ class context {
419
464
  case "modified":
420
465
  if(currentModel.__dirtyFields.length > 0){
421
466
  var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
422
- // build columns equal to value string
423
- var argu = this._SQLEngine._buildSQLEqualTo(cleanCurrentModel);
467
+ // Use NEW SECURE parameterized version
468
+ var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
424
469
  if(argu !== -1 ){
425
470
  var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
426
471
  var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
@@ -429,7 +474,7 @@ class context {
429
474
  else{
430
475
  console.log("Nothing has been tracked, modified, created or added");
431
476
  }
432
-
477
+
433
478
  }
434
479
  else{
435
480
  console.log("Tracked entity modified with no values being changed");