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/postgresEngine.js
CHANGED
|
@@ -1,618 +1,735 @@
|
|
|
1
|
-
// Version 0.0.
|
|
2
|
-
|
|
1
|
+
// Version 0.1.0 - Complete PostgreSQL implementation with pg 8.16.3
|
|
2
|
+
const tools = require('./Tools');
|
|
3
|
+
const FieldTransformer = require('./Entity/fieldTransformer');
|
|
4
|
+
const { Pool } = require('pg');
|
|
3
5
|
|
|
4
6
|
class postgresEngine {
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
constructor() {
|
|
9
|
+
this.pool = null;
|
|
10
|
+
this.db = null;
|
|
11
|
+
this.dbType = 'postgres';
|
|
12
|
+
this.unsupportedWords = ["order"];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize PostgreSQL connection pool
|
|
17
|
+
* @param {Object} config - PostgreSQL connection config
|
|
18
|
+
*/
|
|
19
|
+
async initialize(config) {
|
|
20
|
+
this.pool = new Pool({
|
|
21
|
+
host: config.host || 'localhost',
|
|
22
|
+
port: config.port || 5432,
|
|
23
|
+
database: config.database,
|
|
24
|
+
user: config.user,
|
|
25
|
+
password: config.password,
|
|
26
|
+
max: config.max || 20,
|
|
27
|
+
idleTimeoutMillis: config.idleTimeoutMillis || 30000,
|
|
28
|
+
connectionTimeoutMillis: config.connectionTimeoutMillis || 2000,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Test connection
|
|
32
|
+
try {
|
|
33
|
+
const client = await this.pool.connect();
|
|
34
|
+
console.log('PostgreSQL connected successfully');
|
|
35
|
+
client.release();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('PostgreSQL connection error:', err);
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
7
41
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
42
|
+
/**
|
|
43
|
+
* UPDATE with parameterized query
|
|
44
|
+
*/
|
|
45
|
+
async update(query) {
|
|
46
|
+
// Use parameterized query for security
|
|
47
|
+
// query.arg now contains {query, params} from _buildSQLEqualToParameterized
|
|
48
|
+
if (query.arg && typeof query.arg === 'object' && query.arg.query && query.arg.params) {
|
|
49
|
+
const sqlQuery = `UPDATE ${query.tableName} SET ${query.arg.query} WHERE ${query.tableName}.${query.primaryKey} = $${query.arg.params.length + 1}`;
|
|
50
|
+
// Add primaryKeyValue to params array
|
|
51
|
+
const params = [...query.arg.params, query.primaryKeyValue];
|
|
52
|
+
return await this._runWithParams(sqlQuery, params);
|
|
53
|
+
} else {
|
|
54
|
+
// Fallback for legacy support
|
|
55
|
+
const sqlQuery = `UPDATE ${query.tableName} SET ${query.arg} WHERE ${query.tableName}.${query.primaryKey} = $1`;
|
|
56
|
+
return await this._runWithParams(sqlQuery, [query.primaryKeyValue]);
|
|
57
|
+
}
|
|
13
58
|
}
|
|
14
59
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
60
|
+
/**
|
|
61
|
+
* DELETE with parameterized query
|
|
62
|
+
*/
|
|
63
|
+
async delete(queryObject) {
|
|
64
|
+
const sqlObject = this._buildDeleteObject(queryObject);
|
|
65
|
+
const sqlQuery = `DELETE FROM ${sqlObject.tableName} WHERE ${sqlObject.tableName}.${sqlObject.primaryKey} = $1`;
|
|
66
|
+
return await this._runWithParams(sqlQuery, [sqlObject.value]);
|
|
19
67
|
}
|
|
20
68
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
69
|
+
/**
|
|
70
|
+
* INSERT with parameterized query
|
|
71
|
+
* Postgres uses RETURNING to get the inserted ID
|
|
72
|
+
*/
|
|
73
|
+
async insert(queryObject) {
|
|
74
|
+
const sqlObject = this._buildSQLInsertObjectParameterized(queryObject, queryObject.__entity);
|
|
75
|
+
if (sqlObject === -1) {
|
|
76
|
+
throw new Error('INSERT failed: No columns to insert');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Get primary key name for RETURNING clause
|
|
80
|
+
const primaryKey = tools.getPrimaryKeyObject(queryObject.__entity);
|
|
81
|
+
const query = `INSERT INTO ${sqlObject.tableName} (${sqlObject.columns}) VALUES (${sqlObject.placeholders}) RETURNING ${primaryKey}`;
|
|
82
|
+
|
|
83
|
+
const result = await this._runWithParams(query, sqlObject.params);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
id: result.rows[0][primaryKey]
|
|
28
87
|
};
|
|
29
|
-
return open;
|
|
30
88
|
}
|
|
31
89
|
|
|
32
|
-
|
|
33
|
-
|
|
90
|
+
/**
|
|
91
|
+
* SELECT single record
|
|
92
|
+
*/
|
|
93
|
+
async get(query, entity, context) {
|
|
34
94
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
95
|
+
let queryString;
|
|
96
|
+
if (query.raw) {
|
|
97
|
+
queryString = { query: query.raw, params: [] };
|
|
98
|
+
} else if (typeof query === 'string') {
|
|
99
|
+
queryString = { query: query, params: [] };
|
|
100
|
+
} else {
|
|
101
|
+
queryString = this.buildQuery(query, entity, context);
|
|
37
102
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
queryString.query = query;
|
|
41
|
-
}
|
|
42
|
-
else{
|
|
43
|
-
queryString = this.buildQuery(query, entity, context);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if(queryString.query){
|
|
103
|
+
|
|
104
|
+
if (queryString.query) {
|
|
47
105
|
console.log("SQL:", queryString.query);
|
|
48
|
-
|
|
49
|
-
|
|
106
|
+
console.log("Params:", queryString.params || []);
|
|
107
|
+
const result = await this._runWithParams(queryString.query, queryString.params || []);
|
|
108
|
+
return result.rows[0] || null;
|
|
50
109
|
}
|
|
51
110
|
return null;
|
|
52
111
|
} catch (err) {
|
|
53
|
-
console.error(err);
|
|
112
|
+
console.error('PostgreSQL get error:', err);
|
|
54
113
|
return null;
|
|
55
114
|
}
|
|
56
115
|
}
|
|
57
116
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
117
|
+
/**
|
|
118
|
+
* SELECT COUNT
|
|
119
|
+
*/
|
|
120
|
+
async getCount(queryObject, entity, context) {
|
|
121
|
+
const query = queryObject.script;
|
|
61
122
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
else{
|
|
66
|
-
if(query.count === undefined){
|
|
123
|
+
let queryString;
|
|
124
|
+
if (query.raw) {
|
|
125
|
+
queryString = { query: query.raw, params: [] };
|
|
126
|
+
} else {
|
|
127
|
+
if (query.count === undefined) {
|
|
67
128
|
query.count = "none";
|
|
68
129
|
}
|
|
69
|
-
|
|
70
|
-
queryString
|
|
130
|
+
const entityAlias = this.getEntity(entity.__name, query.entityMap);
|
|
131
|
+
queryString = {
|
|
132
|
+
query: `SELECT ${this.buildCount(query, entity)} ${this.buildFrom(query, entity)} ${this.buildWhere(query, entity)}`,
|
|
133
|
+
params: query.parameters ? query.parameters.getParams() : []
|
|
134
|
+
};
|
|
71
135
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log("SQL:",
|
|
75
|
-
|
|
76
|
-
|
|
136
|
+
|
|
137
|
+
if (queryString.query) {
|
|
138
|
+
console.log("SQL:", queryString.query);
|
|
139
|
+
console.log("Params:", queryString.params);
|
|
140
|
+
const result = await this._runWithParams(queryString.query, queryString.params);
|
|
141
|
+
return result.rows[0] || null;
|
|
77
142
|
}
|
|
78
143
|
return null;
|
|
79
144
|
} catch (err) {
|
|
80
|
-
console.error(err);
|
|
145
|
+
console.error('PostgreSQL getCount error:', err);
|
|
81
146
|
return null;
|
|
82
147
|
}
|
|
83
148
|
}
|
|
84
149
|
|
|
85
|
-
|
|
86
|
-
|
|
150
|
+
/**
|
|
151
|
+
* SELECT multiple records
|
|
152
|
+
*/
|
|
153
|
+
async all(query, entity, context) {
|
|
87
154
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else{
|
|
92
|
-
|
|
155
|
+
let selectQuery;
|
|
156
|
+
if (query.raw) {
|
|
157
|
+
selectQuery = { query: query.raw, params: [] };
|
|
158
|
+
} else {
|
|
93
159
|
selectQuery = this.buildQuery(query, entity, context);
|
|
94
160
|
}
|
|
95
|
-
|
|
161
|
+
|
|
162
|
+
if (selectQuery.query) {
|
|
96
163
|
console.log("SQL:", selectQuery.query);
|
|
97
|
-
|
|
98
|
-
|
|
164
|
+
console.log("Params:", selectQuery.params || []);
|
|
165
|
+
const result = await this._runWithParams(selectQuery.query, selectQuery.params || []);
|
|
166
|
+
return result.rows || [];
|
|
99
167
|
}
|
|
100
|
-
return
|
|
168
|
+
return [];
|
|
101
169
|
} catch (err) {
|
|
102
|
-
console.error(err);
|
|
103
|
-
return
|
|
170
|
+
console.error('PostgreSQL all error:', err);
|
|
171
|
+
return [];
|
|
104
172
|
}
|
|
105
173
|
}
|
|
106
174
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
whereClaus = query.where.expr.replace("!= null", "is not null");
|
|
113
|
-
}
|
|
114
|
-
query.where.expr = whereClaus;
|
|
115
|
-
}
|
|
116
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Execute raw SQL with parameters
|
|
177
|
+
*/
|
|
178
|
+
async exec(query, params = []) {
|
|
179
|
+
return await this._runWithParams(query, params);
|
|
117
180
|
}
|
|
118
181
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
else{
|
|
126
|
-
return `COUNT(*)`
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else{
|
|
130
|
-
return ""
|
|
131
|
-
}
|
|
132
|
-
}
|
|
182
|
+
/**
|
|
183
|
+
* Build complete SELECT query with parameters
|
|
184
|
+
*/
|
|
185
|
+
buildQuery(query, entity, context) {
|
|
186
|
+
const entityStr = this.getEntity(entity.__name, query.entityMap);
|
|
187
|
+
const params = query.parameters ? query.parameters.getParams() : [];
|
|
133
188
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
var queryObject = {};
|
|
137
|
-
queryObject.entity = this.getEntity(entity.__name, query.entityMap);
|
|
138
|
-
queryObject.select = this.buildSelect(query, entity);
|
|
139
|
-
queryObject.count = this.buildCount(query, entity);
|
|
140
|
-
queryObject.from = this.buildFrom(query, entity);
|
|
141
|
-
queryObject.include = this.buildInclude(query, entity, context, queryObject);
|
|
142
|
-
queryObject.where = this.buildWhere(query, entity);
|
|
143
|
-
queryObject.and = this.buildAnd(query, entity);
|
|
144
|
-
queryObject.take = this.buildTake(query);
|
|
145
|
-
queryObject.skip = this.buildSkip(query);
|
|
146
|
-
queryObject.orderBy = this.buildOrderBy(query);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
var queryString = `${queryObject.select} ${queryObject.count} ${queryObject.from} ${queryObject.include} ${queryObject.where} ${queryObject.and} ${queryObject.orderBy} ${queryObject.take} ${queryObject.skip}`;
|
|
150
|
-
return {
|
|
151
|
-
query : queryString,
|
|
152
|
-
entity : this.getEntity(entity.__name, query.entityMap)
|
|
153
|
-
}
|
|
189
|
+
const sql = `SELECT ${this.buildSelectString(query, entity)} ${this.buildFrom(query, entity)} ${this.buildWhere(query, entity)} ${this.buildAnd(query, entity)} ${this.buildLimit(query)} ${this.buildSkip(query)} ${this.buildOrderBy(query)}`;
|
|
154
190
|
|
|
191
|
+
return {
|
|
192
|
+
query: sql,
|
|
193
|
+
params: params
|
|
194
|
+
};
|
|
155
195
|
}
|
|
156
196
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
var orderByType = "ASC";
|
|
161
|
-
var orderByEntity = query.orderBy;
|
|
162
|
-
var strQuery = "";
|
|
163
|
-
if(orderByEntity === false){
|
|
164
|
-
orderByType = "DESC";
|
|
165
|
-
orderByEntity = query.orderByDesc;
|
|
166
|
-
}
|
|
167
|
-
if(orderByEntity){
|
|
168
|
-
var entity = this.getEntity(query.parentName, query.entityMap);
|
|
169
|
-
var fieldList = "";
|
|
170
|
-
for (const item in orderByEntity.selectFields) {
|
|
171
|
-
fieldList += `${entity}.${orderByEntity.selectFields[item]}, `;
|
|
172
|
-
};
|
|
173
|
-
fieldList = fieldList.replace(/,\s*$/, "");
|
|
174
|
-
strQuery = "ORDER BY";
|
|
175
|
-
strQuery += ` ${fieldList} ${orderByType}`;
|
|
197
|
+
buildSelectString(query, entity) {
|
|
198
|
+
if (query.select) {
|
|
199
|
+
return query.select;
|
|
176
200
|
}
|
|
177
|
-
return
|
|
201
|
+
return `${tools.convertEntityToSelectParameterString(entity)}`;
|
|
178
202
|
}
|
|
179
203
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
else{
|
|
185
|
-
return "";
|
|
204
|
+
buildCount(query, entity) {
|
|
205
|
+
const entityStr = this.getEntity(entity.__name, query.entityMap);
|
|
206
|
+
if (query.count === "none") {
|
|
207
|
+
return `COUNT(${entityStr}.*)`;
|
|
186
208
|
}
|
|
209
|
+
return `COUNT(${entityStr}.${query.count})`;
|
|
187
210
|
}
|
|
188
211
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return `OFFSET ${query.skip}`
|
|
192
|
-
}
|
|
193
|
-
else{
|
|
194
|
-
return "";
|
|
195
|
-
}
|
|
212
|
+
buildFrom(query, entity) {
|
|
213
|
+
return `FROM ${entity.__name}`;
|
|
196
214
|
}
|
|
197
215
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if(andEntity){
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Build AND clause with placeholder detection
|
|
218
|
+
*/
|
|
219
|
+
buildAnd(query, mainQuery) {
|
|
220
|
+
const andEntity = query.and;
|
|
221
|
+
let strQuery = "";
|
|
222
|
+
const $that = this;
|
|
223
|
+
|
|
224
|
+
if (andEntity) {
|
|
225
|
+
const entity = this.getEntity(query.parentName, query.entityMap);
|
|
226
|
+
const andList = [];
|
|
227
|
+
|
|
228
|
+
for (let entityPart in andEntity) {
|
|
229
|
+
const itemEntity = andEntity[entityPart];
|
|
230
|
+
for (let table in itemEntity[query.parentName]) {
|
|
231
|
+
const item = itemEntity[query.parentName][table];
|
|
213
232
|
for (let exp in item.expressions) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
233
|
+
let field = tools.capitalizeFirstLetter(item.expressions[exp].field);
|
|
234
|
+
let entityRef = entity;
|
|
235
|
+
|
|
236
|
+
if (mainQuery[field] && mainQuery[field].isNavigational) {
|
|
237
|
+
entityRef = $that.getEntity(field, query.entityMap);
|
|
238
|
+
field = item.fields[1];
|
|
220
239
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
|
|
241
|
+
let func = item.expressions[exp].func;
|
|
242
|
+
const arg = item.expressions[exp].arg;
|
|
243
|
+
|
|
244
|
+
// Handle NULL
|
|
245
|
+
if (arg === "null") {
|
|
246
|
+
if (func === "=") func = "IS";
|
|
247
|
+
if (func === "!=") func = "IS NOT";
|
|
228
248
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
249
|
+
|
|
250
|
+
if (strQuery === "") {
|
|
251
|
+
if (arg === "null") {
|
|
252
|
+
strQuery = `${entityRef}.${field} ${func} ${arg}`;
|
|
253
|
+
} else {
|
|
254
|
+
// Check if arg is a parameterized placeholder
|
|
255
|
+
const isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
|
|
256
|
+
if (isPlaceholder || func === "IN") {
|
|
257
|
+
strQuery = `${entityRef}.${field} ${func} ${arg}`;
|
|
258
|
+
} else {
|
|
259
|
+
strQuery = `${entityRef}.${field} ${func} '${arg}'`;
|
|
260
|
+
}
|
|
234
261
|
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
} else {
|
|
263
|
+
if (arg === "null") {
|
|
264
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} ${arg}`;
|
|
265
|
+
} else {
|
|
266
|
+
// Check if arg is a parameterized placeholder
|
|
267
|
+
const isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
|
|
268
|
+
if (isPlaceholder || func === "IN") {
|
|
269
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} ${arg}`;
|
|
270
|
+
} else {
|
|
271
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} '${arg}'`;
|
|
272
|
+
}
|
|
241
273
|
}
|
|
242
|
-
|
|
243
274
|
}
|
|
244
275
|
}
|
|
245
276
|
andList.push(strQuery);
|
|
246
277
|
}
|
|
247
278
|
}
|
|
279
|
+
|
|
280
|
+
if (andList.length > 0) {
|
|
281
|
+
return `AND ${andList.join(" AND ")}`;
|
|
282
|
+
}
|
|
248
283
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
str = `and ${andList.join(" and ")}`;
|
|
252
|
-
}
|
|
253
|
-
return str
|
|
284
|
+
|
|
285
|
+
return "";
|
|
254
286
|
}
|
|
255
287
|
|
|
256
|
-
|
|
257
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Build WHERE clause with placeholder detection
|
|
290
|
+
*/
|
|
291
|
+
buildWhere(query, mainQuery) {
|
|
292
|
+
const whereEntity = query.where;
|
|
293
|
+
let strQuery = "";
|
|
294
|
+
const $that = this;
|
|
258
295
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
var entity = this.getEntity(query.parentName, query.entityMap);
|
|
296
|
+
if (whereEntity) {
|
|
297
|
+
const entity = this.getEntity(query.parentName, query.entityMap);
|
|
298
|
+
const item = whereEntity[query.parentName].query;
|
|
263
299
|
|
|
264
|
-
var item = whereEntity[query.parentName].query;
|
|
265
300
|
for (let exp in item.expressions) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
301
|
+
let field = tools.capitalizeFirstLetter(item.expressions[exp].field);
|
|
302
|
+
let entityRef = entity;
|
|
303
|
+
|
|
304
|
+
if (mainQuery[field] && mainQuery[field].isNavigational) {
|
|
305
|
+
entityRef = $that.getEntity(field, query.entityMap);
|
|
306
|
+
field = item.fields[1];
|
|
272
307
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
308
|
+
|
|
309
|
+
let func = item.expressions[exp].func;
|
|
310
|
+
const arg = item.expressions[exp].arg;
|
|
311
|
+
|
|
312
|
+
// Handle NULL
|
|
313
|
+
if (arg === "null") {
|
|
314
|
+
if (func === "=") func = "IS";
|
|
315
|
+
if (func === "!=") func = "IS NOT";
|
|
280
316
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
317
|
+
|
|
318
|
+
if (strQuery === "") {
|
|
319
|
+
if (arg === "null") {
|
|
320
|
+
strQuery = `WHERE ${entityRef}.${field} ${func} ${arg}`;
|
|
321
|
+
} else if (func === "IN") {
|
|
322
|
+
strQuery = `WHERE ${entityRef}.${field} ${func} ${arg}`;
|
|
323
|
+
} else {
|
|
324
|
+
// Check if arg is a parameterized placeholder ($1, $2, etc.)
|
|
325
|
+
const isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
|
|
326
|
+
if (isPlaceholder) {
|
|
327
|
+
strQuery = `WHERE ${entityRef}.${field} ${func} ${arg}`;
|
|
328
|
+
} else {
|
|
329
|
+
strQuery = `WHERE ${entityRef}.${field} ${func} '${arg}'`;
|
|
290
330
|
}
|
|
291
331
|
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
} else {
|
|
333
|
+
if (arg === "null") {
|
|
334
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} ${arg}`;
|
|
335
|
+
} else if (func === "IN") {
|
|
336
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} ${arg}`;
|
|
337
|
+
} else {
|
|
338
|
+
// Check if arg is a parameterized placeholder
|
|
339
|
+
const isPlaceholder = (arg === '?' || /^\$\d+$/.test(arg));
|
|
340
|
+
if (isPlaceholder) {
|
|
341
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} ${arg}`;
|
|
342
|
+
} else {
|
|
343
|
+
strQuery = `${strQuery} AND ${entityRef}.${field} ${func} '${arg}'`;
|
|
344
|
+
}
|
|
298
345
|
}
|
|
299
|
-
|
|
300
346
|
}
|
|
301
347
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
348
|
}
|
|
349
|
+
|
|
306
350
|
return strQuery;
|
|
307
351
|
}
|
|
308
352
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
var includeEntity = query.include[part];
|
|
313
|
-
var $that = this;
|
|
314
|
-
if(includeEntity){
|
|
315
|
-
var parentObj = includeEntity[query.parentName];
|
|
316
|
-
var currentContext = "";
|
|
317
|
-
if(includeEntity.selectFields){
|
|
318
|
-
currentContext = context[tools.capitalizeFirstLetter(includeEntity.selectFields[0])];
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if(parentObj){
|
|
322
|
-
parentObj.entityMap = query.entityMap;
|
|
323
|
-
var foreignKey = $that.getForeignKey(entity.__name, currentContext.__entity);
|
|
324
|
-
var mainPrimaryKey = $that.getPrimarykey(entity);
|
|
325
|
-
var mainEntity = $that.getEntity(entity.__name, query.entityMap);
|
|
326
|
-
if(currentContext.__entity[entity.__name].type === "hasManyThrough"){
|
|
327
|
-
var foreignTable = tools.capitalizeFirstLetter(currentContext.__entity[entity.__name].foreignTable); //to uppercase letter
|
|
328
|
-
foreignKey = $that.getPrimarykey(currentContext.__entity);
|
|
329
|
-
mainPrimaryKey = context[foreignTable].__entity[currentContext.__entity.__name].foreignKey;
|
|
330
|
-
var mainEntity = $that.getEntity(foreignTable,query.entityMap);
|
|
331
|
-
}
|
|
332
|
-
// add foreign key to select so that it picks it up
|
|
333
|
-
if(parentObj.select){
|
|
334
|
-
parentObj.select.selectFields.push(foreignKey);
|
|
335
|
-
}else{
|
|
336
|
-
parentObj.select = {};
|
|
337
|
-
parentObj.select.selectFields = [];
|
|
338
|
-
parentObj.select.selectFields.push(foreignKey);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
var innerQuery = $that.buildQuery(parentObj, currentContext.__entity, context);
|
|
342
|
-
|
|
343
|
-
includeQuery += `LEFT JOIN (${innerQuery.query}) AS ${innerQuery.entity} ON ${ mainEntity}.${mainPrimaryKey} = ${innerQuery.entity}.${foreignKey} `;
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
}
|
|
353
|
+
buildLimit(query) {
|
|
354
|
+
if (query.take) {
|
|
355
|
+
return `LIMIT ${query.take}`;
|
|
347
356
|
}
|
|
348
|
-
return
|
|
357
|
+
return "";
|
|
349
358
|
}
|
|
350
359
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return `FROM ${entity.__name } AS ${entityName}`;
|
|
360
|
+
buildSkip(query) {
|
|
361
|
+
if (query.skip) {
|
|
362
|
+
return `OFFSET ${query.skip}`;
|
|
355
363
|
}
|
|
356
|
-
|
|
364
|
+
return "";
|
|
357
365
|
}
|
|
358
366
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
arr += `${$that.getEntity(entity.__name, query.entityMap)}.${query.select.selectFields[item]}, `;
|
|
367
|
-
};
|
|
368
|
-
|
|
367
|
+
buildOrderBy(query) {
|
|
368
|
+
if (query.orderBy) {
|
|
369
|
+
const entityStr = this.getEntity(query.parentName, query.entityMap);
|
|
370
|
+
return `ORDER BY ${entityStr}.${query.orderBy} ASC`;
|
|
371
|
+
} else if (query.orderByDescending) {
|
|
372
|
+
const entityStr = this.getEntity(query.parentName, query.entityMap);
|
|
373
|
+
return `ORDER BY ${entityStr}.${query.orderByDescending} DESC`;
|
|
369
374
|
}
|
|
370
|
-
|
|
371
|
-
var entityList = this.getEntityList(entity);
|
|
372
|
-
for (const item in entityList) {
|
|
373
|
-
arr += `${$that.getEntity(entity.__name, query.entityMap)}.${entityList[item]}, `;
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
arr = arr.replace(/,\s*$/, "");
|
|
377
|
-
return `${select} ${arr} `;
|
|
375
|
+
return "";
|
|
378
376
|
}
|
|
379
377
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
getEntity(name, list) {
|
|
379
|
+
for (let i = 0; i < list.length; i++) {
|
|
380
|
+
if (list[i].name === name) {
|
|
381
|
+
return list[i].entity;
|
|
382
|
+
}
|
|
383
383
|
}
|
|
384
|
-
|
|
384
|
+
return name;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Build SQL SET clause with parameterized queries for UPDATE (PostgreSQL)
|
|
389
|
+
* Returns {query: "column1 = $1, column2 = $2", params: [value1, value2]}
|
|
390
|
+
*/
|
|
391
|
+
_buildSQLEqualToParameterized(model) {
|
|
392
|
+
const $that = this;
|
|
393
|
+
const sqlParts = [];
|
|
394
|
+
const params = [];
|
|
395
|
+
const dirtyFields = model.__dirtyFields;
|
|
396
|
+
let paramIndex = 1;
|
|
397
|
+
|
|
398
|
+
for (let column in dirtyFields) {
|
|
399
|
+
const fieldName = dirtyFields[column];
|
|
400
|
+
const entityDef = model.__entity[fieldName];
|
|
401
|
+
|
|
402
|
+
// Check for required fields
|
|
403
|
+
if (entityDef && entityDef.nullable === false && entityDef.primary !== true) {
|
|
404
|
+
let persistedValue;
|
|
405
|
+
switch (entityDef.type) {
|
|
406
|
+
case "integer":
|
|
407
|
+
persistedValue = model["_" + fieldName];
|
|
408
|
+
break;
|
|
409
|
+
case "belongsTo":
|
|
410
|
+
persistedValue = model["_" + fieldName] !== undefined ? model["_" + fieldName] : model[fieldName];
|
|
411
|
+
break;
|
|
412
|
+
default:
|
|
413
|
+
persistedValue = model[fieldName];
|
|
414
|
+
}
|
|
415
|
+
const isEmptyString = (typeof persistedValue === 'string') && (persistedValue.trim() === '');
|
|
416
|
+
if (persistedValue === undefined || persistedValue === null || isEmptyString) {
|
|
417
|
+
throw new Error(`Entity ${model.__entity.__name} column ${fieldName} is a required Field`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
385
420
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
421
|
+
let type = model.__entity[dirtyFields[column]].type;
|
|
422
|
+
if (model.__entity[dirtyFields[column]].relationshipType === "belongsTo") {
|
|
423
|
+
type = "belongsTo";
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
switch (type) {
|
|
427
|
+
case "belongsTo":
|
|
428
|
+
const foreignKey = model.__entity[dirtyFields[column]].foreignKey;
|
|
429
|
+
let fkValue = model[dirtyFields[column]];
|
|
430
|
+
// Apply toDatabase transformer
|
|
431
|
+
try {
|
|
432
|
+
fkValue = FieldTransformer.toDatabase(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
433
|
+
} catch (transformError) {
|
|
434
|
+
throw new Error(`UPDATE failed: ${transformError.message}`);
|
|
391
435
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
436
|
+
try {
|
|
437
|
+
fkValue = $that._validateAndCoerceFieldType(fkValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
438
|
+
} catch (typeError) {
|
|
439
|
+
throw new Error(`UPDATE failed: ${typeError.message}`);
|
|
440
|
+
}
|
|
441
|
+
fkValue = $that._convertValueForDatabase(fkValue, model.__entity[dirtyFields[column]].type);
|
|
442
|
+
const fore = `_${dirtyFields[column]}`;
|
|
443
|
+
sqlParts.push(`${foreignKey} = $${paramIndex++}`);
|
|
444
|
+
params.push(model[fore]);
|
|
445
|
+
break;
|
|
446
|
+
|
|
447
|
+
case "integer":
|
|
448
|
+
let intValue = model["_" + dirtyFields[column]];
|
|
449
|
+
// Apply toDatabase transformer
|
|
450
|
+
try {
|
|
451
|
+
intValue = FieldTransformer.toDatabase(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
452
|
+
} catch (transformError) {
|
|
453
|
+
throw new Error(`UPDATE failed: ${transformError.message}`);
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
intValue = $that._validateAndCoerceFieldType(intValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
457
|
+
} catch (typeError) {
|
|
458
|
+
throw new Error(`UPDATE failed: ${typeError.message}`);
|
|
459
|
+
}
|
|
460
|
+
intValue = $that._convertValueForDatabase(intValue, model.__entity[dirtyFields[column]].type);
|
|
461
|
+
sqlParts.push(`${dirtyFields[column]} = $${paramIndex++}`);
|
|
462
|
+
params.push(intValue);
|
|
463
|
+
break;
|
|
464
|
+
|
|
465
|
+
case "string":
|
|
466
|
+
let strValue = model[dirtyFields[column]];
|
|
467
|
+
// Apply toDatabase transformer
|
|
468
|
+
try {
|
|
469
|
+
strValue = FieldTransformer.toDatabase(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
470
|
+
} catch (transformError) {
|
|
471
|
+
throw new Error(`UPDATE failed: ${transformError.message}`);
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
strValue = $that._validateAndCoerceFieldType(strValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
475
|
+
} catch (typeError) {
|
|
476
|
+
throw new Error(`UPDATE failed: ${typeError.message}`);
|
|
477
|
+
}
|
|
478
|
+
strValue = $that._convertValueForDatabase(strValue, model.__entity[dirtyFields[column]].type);
|
|
479
|
+
sqlParts.push(`${dirtyFields[column]} = $${paramIndex++}`);
|
|
480
|
+
params.push(strValue);
|
|
481
|
+
break;
|
|
482
|
+
|
|
483
|
+
case "boolean":
|
|
484
|
+
let boolValue = model[dirtyFields[column]];
|
|
485
|
+
// Apply toDatabase transformer
|
|
486
|
+
try {
|
|
487
|
+
boolValue = FieldTransformer.toDatabase(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
488
|
+
} catch (transformError) {
|
|
489
|
+
throw new Error(`UPDATE failed: ${transformError.message}`);
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
boolValue = $that._validateAndCoerceFieldType(boolValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
493
|
+
} catch (typeError) {
|
|
494
|
+
throw new Error(`UPDATE failed: ${typeError.message}`);
|
|
495
|
+
}
|
|
496
|
+
boolValue = $that._convertValueForDatabase(boolValue, model.__entity[dirtyFields[column]].type);
|
|
497
|
+
sqlParts.push(`${dirtyFields[column]} = $${paramIndex++}`);
|
|
498
|
+
params.push(boolValue);
|
|
499
|
+
break;
|
|
500
|
+
|
|
501
|
+
case "time":
|
|
502
|
+
let timeValue = model[dirtyFields[column]];
|
|
503
|
+
// Apply toDatabase transformer
|
|
504
|
+
try {
|
|
505
|
+
timeValue = FieldTransformer.toDatabase(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
506
|
+
} catch (transformError) {
|
|
507
|
+
throw new Error(`UPDATE failed: ${transformError.message}`);
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
timeValue = $that._validateAndCoerceFieldType(timeValue, model.__entity[dirtyFields[column]], model.__entity.__name, dirtyFields[column]);
|
|
511
|
+
} catch (typeError) {
|
|
512
|
+
throw new Error(`UPDATE failed: ${typeError.message}`);
|
|
513
|
+
}
|
|
514
|
+
timeValue = $that._convertValueForDatabase(timeValue, model.__entity[dirtyFields[column]].type);
|
|
515
|
+
sqlParts.push(`${dirtyFields[column]} = $${paramIndex++}`);
|
|
516
|
+
params.push(timeValue);
|
|
517
|
+
break;
|
|
395
518
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
519
|
+
case "hasMany":
|
|
520
|
+
sqlParts.push(`${dirtyFields[column]} = $${paramIndex++}`);
|
|
521
|
+
params.push(model[dirtyFields[column]]);
|
|
522
|
+
break;
|
|
401
523
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
for (let part in include) {
|
|
406
|
-
if(tools.capitalizeFirstLetter(include[part].selectFields[0]) === name){
|
|
407
|
-
return include[part];
|
|
408
|
-
}
|
|
524
|
+
default:
|
|
525
|
+
sqlParts.push(`${dirtyFields[column]} = $${paramIndex++}`);
|
|
526
|
+
params.push(model[dirtyFields[column]]);
|
|
409
527
|
}
|
|
410
528
|
}
|
|
411
|
-
else{
|
|
412
|
-
return "";
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
529
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
530
|
+
return sqlParts.length > 0 ? { query: sqlParts.join(', '), params: params } : -1;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Build parameterized INSERT object for PostgreSQL
|
|
535
|
+
* Uses $1, $2, $3... instead of ?
|
|
536
|
+
*/
|
|
537
|
+
_buildSQLInsertObjectParameterized(fields, modelEntity) {
|
|
538
|
+
const $that = this;
|
|
539
|
+
const columnNames = [];
|
|
540
|
+
const params = [];
|
|
541
|
+
let paramIndex = 1;
|
|
542
|
+
|
|
543
|
+
for (const column in modelEntity) {
|
|
544
|
+
if (column.indexOf("__") === -1) {
|
|
545
|
+
let fieldColumn = fields[column];
|
|
546
|
+
|
|
547
|
+
if ((fieldColumn !== undefined && fieldColumn !== null) && typeof(fieldColumn) !== "object") {
|
|
548
|
+
// Apply toDatabase transformer
|
|
549
|
+
try {
|
|
550
|
+
fieldColumn = FieldTransformer.toDatabase(fieldColumn, modelEntity[column], modelEntity.__name, column);
|
|
551
|
+
} catch (transformError) {
|
|
552
|
+
throw new Error(`INSERT failed: ${transformError.message}`);
|
|
553
|
+
}
|
|
425
554
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if(!ent.startsWith("_")){
|
|
432
|
-
if(!entity[ent].foreignKey){
|
|
433
|
-
if($that.chechUnsupportedWords(ent)){
|
|
434
|
-
entitiesList.push(`'${ent}'`);
|
|
435
|
-
}
|
|
436
|
-
else{
|
|
437
|
-
entitiesList.push(ent);
|
|
438
|
-
}
|
|
555
|
+
// Validate and coerce type
|
|
556
|
+
try {
|
|
557
|
+
fieldColumn = $that._validateAndCoerceFieldType(fieldColumn, modelEntity[column], modelEntity.__name, column);
|
|
558
|
+
} catch (typeError) {
|
|
559
|
+
throw new Error(`INSERT failed: ${typeError.message}`);
|
|
439
560
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
561
|
+
|
|
562
|
+
// Convert to database-specific format
|
|
563
|
+
fieldColumn = $that._convertValueForDatabase(fieldColumn, modelEntity[column].type);
|
|
564
|
+
|
|
565
|
+
// Skip auto-increment primary keys
|
|
566
|
+
if (modelEntity[column].auto !== true) {
|
|
567
|
+
columnNames.push(column);
|
|
568
|
+
params.push(fieldColumn);
|
|
444
569
|
}
|
|
445
570
|
}
|
|
446
571
|
}
|
|
447
|
-
|
|
448
|
-
}
|
|
572
|
+
}
|
|
449
573
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
var text = this.unsupportedWords[item];
|
|
453
|
-
if(text === word){
|
|
454
|
-
return true
|
|
455
|
-
}
|
|
574
|
+
if (columnNames.length === 0) {
|
|
575
|
+
return -1;
|
|
456
576
|
}
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
577
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
578
|
+
// Generate PostgreSQL placeholders: $1, $2, $3...
|
|
579
|
+
const placeholders = params.map((_, index) => `$${index + 1}`).join(', ');
|
|
463
580
|
|
|
464
|
-
|
|
465
|
-
|
|
581
|
+
return {
|
|
582
|
+
tableName: modelEntity.__name,
|
|
583
|
+
columns: columnNames.join(', '),
|
|
584
|
+
placeholders: placeholders,
|
|
585
|
+
params: params
|
|
586
|
+
};
|
|
466
587
|
}
|
|
467
588
|
|
|
468
|
-
|
|
469
|
-
|
|
589
|
+
_buildDeleteObject(queryObject) {
|
|
590
|
+
const primaryKey = tools.getPrimaryKeyObject(queryObject.__entity);
|
|
591
|
+
return {
|
|
592
|
+
tableName: queryObject.__entity.__name,
|
|
593
|
+
primaryKey: primaryKey,
|
|
594
|
+
value: queryObject[primaryKey]
|
|
595
|
+
};
|
|
470
596
|
}
|
|
471
597
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// TODO Boolean value is a string with a letter
|
|
480
|
-
switch(model.__entity[dirtyFields[column]].type){
|
|
481
|
-
case "integer" :
|
|
482
|
-
//model.__entity[dirtyFields[column]].skipGetFunction = true;
|
|
483
|
-
argument = argument === null ? `[${dirtyFields[column]}] = ${model[dirtyFields[column]]},` : `${argument} [${dirtyFields[column]}] = ${model[dirtyFields[column]]},`;
|
|
484
|
-
//model.__entity[dirtyFields[column]].skipGetFunction = false;
|
|
485
|
-
break;
|
|
486
|
-
case "string" :
|
|
487
|
-
argument = argument === null ? `[${dirtyFields[column]}] = '${$that._santizeSingleQuotes(model[dirtyFields[column]])}',` : `${argument} [${dirtyFields[column]}] = '${$that._santizeSingleQuotes(model[dirtyFields[column]])}',`;
|
|
488
|
-
break;
|
|
489
|
-
case "boolean" :
|
|
490
|
-
var bool = "";
|
|
491
|
-
if(model.__entity[dirtyFields[column]].valueConversion){
|
|
492
|
-
bool = tools.convertBooleanToNumber(model[dirtyFields[column]]);
|
|
493
|
-
}
|
|
494
|
-
else{
|
|
495
|
-
bool = model[dirtyFields[column]];
|
|
496
|
-
}
|
|
497
|
-
argument = argument === null ? `[${dirtyFields[column]}] = '${bool}',` : `${argument} [${dirtyFields[column]}] = ${bool},`;
|
|
498
|
-
break;
|
|
499
|
-
case "time" :
|
|
500
|
-
argument = argument === null ? `[${dirtyFields[column]}] = '${model[dirtyFields[column]]}',` : `${argument} [${dirtyFields[column]}] = ${model[dirtyFields[column]]},`;
|
|
501
|
-
break;
|
|
502
|
-
case "belongsTo" :
|
|
503
|
-
var fore = `_${dirtyFields[column]}`;
|
|
504
|
-
argument = argument === null ? `[${model.__entity[dirtyFields[column]].foreignKey}] = '${model[fore]}',` : `${argument} [${model.__entity[dirtyFields[column]].foreignKey}] = '${model[fore]}',`;
|
|
505
|
-
break;
|
|
506
|
-
case "hasMany" :
|
|
507
|
-
argument = argument === null ? `[${dirtyFields[column]}] = '${model[dirtyFields[column]]}',` : `${argument} [${dirtyFields[column]}] = '${model[dirtyFields[column]]}',`;
|
|
508
|
-
break;
|
|
509
|
-
default:
|
|
510
|
-
argument = argument === null ? `[${dirtyFields[column]}] = '${model[dirtyFields[column]]}',` : `${argument} [${dirtyFields[column]}] = '${model[dirtyFields[column]]}',`;
|
|
598
|
+
/**
|
|
599
|
+
* Validate and coerce field type
|
|
600
|
+
*/
|
|
601
|
+
_validateAndCoerceFieldType(value, fieldDef, entityName, fieldName) {
|
|
602
|
+
if (value === null || value === undefined) {
|
|
603
|
+
if (fieldDef.nullable === false || fieldDef.notNullable === true) {
|
|
604
|
+
throw new Error(`Field '${entityName}.${fieldName}' cannot be null`);
|
|
511
605
|
}
|
|
606
|
+
return null;
|
|
512
607
|
}
|
|
513
|
-
return argument.replace(/,\s*$/, "");
|
|
514
|
-
}
|
|
515
608
|
|
|
516
|
-
|
|
517
|
-
_buildDeleteObject(currentModel){
|
|
518
|
-
var primaryKey = currentModel.__Key === undefined ? tools.getPrimaryKeyObject(currentModel.__entity) : currentModel.__Key;
|
|
519
|
-
var value = currentModel.__value === undefined ? currentModel[primaryKey] : currentModel.__value;
|
|
520
|
-
var tableName = currentModel.__tableName === undefined ? currentModel.__entity.__name : currentModel.__tableName;
|
|
521
|
-
return {tableName: tableName, primaryKey : primaryKey, value : value};
|
|
522
|
-
}
|
|
609
|
+
const fieldType = fieldDef.type;
|
|
523
610
|
|
|
611
|
+
switch (fieldType) {
|
|
612
|
+
case 'string':
|
|
613
|
+
case 'text':
|
|
614
|
+
return String(value);
|
|
524
615
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
column = modelEntity[column].foreignKey === undefined ? column : modelEntity[column].foreignKey;
|
|
542
|
-
break;
|
|
543
|
-
case "string" :
|
|
544
|
-
fieldColumn = `'${$that._santizeSingleQuotes(fields[column])}'`;
|
|
545
|
-
break;
|
|
546
|
-
case "time" :
|
|
547
|
-
fieldColumn = fields[column];
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
616
|
+
case 'integer':
|
|
617
|
+
case 'int':
|
|
618
|
+
const intVal = parseInt(value, 10);
|
|
619
|
+
if (isNaN(intVal)) {
|
|
620
|
+
throw new Error(`Field '${entityName}.${fieldName}' must be an integer, got: ${value}`);
|
|
621
|
+
}
|
|
622
|
+
return intVal;
|
|
623
|
+
|
|
624
|
+
case 'float':
|
|
625
|
+
case 'double':
|
|
626
|
+
case 'decimal':
|
|
627
|
+
const floatVal = parseFloat(value);
|
|
628
|
+
if (isNaN(floatVal)) {
|
|
629
|
+
throw new Error(`Field '${entityName}.${fieldName}' must be a number, got: ${value}`);
|
|
630
|
+
}
|
|
631
|
+
return floatVal;
|
|
550
632
|
|
|
551
|
-
|
|
552
|
-
|
|
633
|
+
case 'boolean':
|
|
634
|
+
case 'bool':
|
|
635
|
+
return Boolean(value);
|
|
553
636
|
|
|
637
|
+
case 'date':
|
|
638
|
+
case 'datetime':
|
|
639
|
+
case 'timestamp':
|
|
640
|
+
if (value instanceof Date) {
|
|
641
|
+
return value;
|
|
554
642
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
var fieldObject = tools.findTrackedObject(fields.__context.__trackedEntities, column );
|
|
559
|
-
if( Object.keys(fieldObject).length > 0){
|
|
560
|
-
var primaryKey = tools.getPrimaryKeyObject(fieldObject.__entity);
|
|
561
|
-
fieldColumn = fieldObject[primaryKey];
|
|
562
|
-
column = modelEntity[column].foreignKey;
|
|
563
|
-
columns = columns === null ? `'${column}',` : `${columns} '${column}',`;
|
|
564
|
-
values = values === null ? `${fieldColumn},` : `${values} ${fieldColumn},`;
|
|
565
|
-
}else{
|
|
566
|
-
console.log("Cannot find belings to relationship")
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
|
|
643
|
+
const dateVal = new Date(value);
|
|
644
|
+
if (isNaN(dateVal.getTime())) {
|
|
645
|
+
throw new Error(`Field '${entityName}.${fieldName}' must be a valid date, got: ${value}`);
|
|
572
646
|
}
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
return {tableName: modelEntity.__name, columns: columns.replace(/,\s*$/, ""), values: values.replace(/,\s*$/, "")};
|
|
647
|
+
return dateVal;
|
|
576
648
|
|
|
577
|
-
|
|
649
|
+
case 'json':
|
|
650
|
+
case 'jsonb':
|
|
651
|
+
if (typeof value === 'object') {
|
|
652
|
+
return JSON.stringify(value);
|
|
653
|
+
}
|
|
654
|
+
return value;
|
|
578
655
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (typeof string === 'string' || string instanceof String){
|
|
582
|
-
return string.replace(/'/g, "''");
|
|
656
|
+
default:
|
|
657
|
+
return value;
|
|
583
658
|
}
|
|
584
|
-
else{
|
|
585
|
-
console.log("warning - Field being passed is not a string");
|
|
586
|
-
throw "warning - Field being passed is not a string";
|
|
587
|
-
}
|
|
588
659
|
}
|
|
589
660
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Convert validated value to database-specific format
|
|
663
|
+
* Modern ORM pattern: transparent database-specific conversions
|
|
664
|
+
*
|
|
665
|
+
* @param {*} value - Already validated value
|
|
666
|
+
* @param {string} fieldType - Field type from entity definition
|
|
667
|
+
* @returns {*} Database-ready value
|
|
668
|
+
*/
|
|
669
|
+
_convertValueForDatabase(value, fieldType){
|
|
670
|
+
if(value === undefined || value === null){
|
|
671
|
+
return value;
|
|
672
|
+
}
|
|
595
673
|
|
|
596
|
-
|
|
597
|
-
|
|
674
|
+
// PostgreSQL accepts native booleans, but we convert to 1/0 for consistency
|
|
675
|
+
// The pg driver will convert to PostgreSQL TRUE/FALSE
|
|
676
|
+
if(fieldType === 'boolean' && typeof value === 'boolean'){
|
|
677
|
+
return value ? 1 : 0;
|
|
598
678
|
}
|
|
599
|
-
return mainString;;
|
|
600
|
-
}
|
|
601
679
|
|
|
602
|
-
|
|
603
|
-
console.log("SQL:", query);
|
|
604
|
-
return this.db.exec(query);
|
|
680
|
+
return value;
|
|
605
681
|
}
|
|
606
682
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
683
|
+
/**
|
|
684
|
+
* Execute parameterized query with pg library
|
|
685
|
+
*/
|
|
686
|
+
async _runWithParams(query, params = []) {
|
|
687
|
+
try {
|
|
688
|
+
console.log("SQL:", query);
|
|
689
|
+
console.log("Params:", params);
|
|
690
|
+
|
|
691
|
+
const client = await this.pool.connect();
|
|
692
|
+
try {
|
|
693
|
+
const result = await client.query(query, params);
|
|
694
|
+
return result;
|
|
695
|
+
} finally {
|
|
696
|
+
client.release();
|
|
697
|
+
}
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.error('PostgreSQL query error:', error);
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
610
702
|
}
|
|
611
703
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
704
|
+
/**
|
|
705
|
+
* Sanitize single quotes (legacy, prefer parameterized queries)
|
|
706
|
+
*/
|
|
707
|
+
_santizeSingleQuotes(string, context = {}) {
|
|
708
|
+
if (typeof string === 'string' || string instanceof String) {
|
|
709
|
+
return string.replace(/'/g, "''");
|
|
710
|
+
}
|
|
711
|
+
console.warn(`Warning - Field ${context.entityName}.${context.fieldName} is not a string`);
|
|
712
|
+
throw new Error(`Field ${context.entityName}.${context.fieldName} must be a string`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Set database connection
|
|
717
|
+
*/
|
|
718
|
+
setDB(db, type) {
|
|
719
|
+
this.db = db;
|
|
720
|
+
this.pool = db;
|
|
721
|
+
this.dbType = type || 'postgres';
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Close database connection pool
|
|
726
|
+
*/
|
|
727
|
+
async close() {
|
|
728
|
+
if (this.pool) {
|
|
729
|
+
await this.pool.end();
|
|
730
|
+
console.log('PostgreSQL pool closed');
|
|
731
|
+
}
|
|
732
|
+
}
|
|
616
733
|
}
|
|
617
734
|
|
|
618
|
-
module.exports = postgresEngine;
|
|
735
|
+
module.exports = postgresEngine;
|