masterrecord 0.1.4 → 0.2.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.
- package/Entity/entityTrackerModel.js +7 -3
- package/MIGRATIONS.md +178 -0
- package/Migrations/cli.js +3 -3
- package/Migrations/migrationMySQLQuery.js +18 -8
- package/Migrations/migrationSQLiteQuery.js +30 -3
- package/Migrations/schema.js +201 -16
- package/QueryLanguage/queryMethods.js +45 -58
- package/QueryLanguage/queryScript.js +102 -35
- package/SQLLiteEngine.js +158 -61
- package/Tools.js +74 -29
- package/context.js +191 -60
- package/deleteManager.js +3 -3
- package/insertManager.js +128 -34
- package/masterrecord_all_files.txt +4646 -0
- package/mySQLEngine.js +159 -44
- package/package.json +5 -5
- package/readme.md +3 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
// version 0.0.
|
|
2
|
+
// version 0.0.14
|
|
3
3
|
var entityTrackerModel = require('masterrecord/Entity/entityTrackerModel');
|
|
4
4
|
var tools = require('masterrecord/Tools');
|
|
5
5
|
var queryScript = require('masterrecord/QueryLanguage/queryScript');
|
|
@@ -61,48 +61,28 @@ class queryMethods{
|
|
|
61
61
|
|
|
62
62
|
______orderByCount(query, ...args){
|
|
63
63
|
var str = query.toString();
|
|
64
|
-
|
|
65
|
-
for(let argument in args){
|
|
66
|
-
var item = args[argument];
|
|
67
|
-
str = str.replace("$$", item);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
64
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'orderByCount');
|
|
70
65
|
this.__queryObject.orderByCount(str, this.__entity.__name);
|
|
71
66
|
return this;
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
______orderByCountDescending(query, ...args){
|
|
75
70
|
var str = query.toString();
|
|
76
|
-
|
|
77
|
-
for(let argument in args){
|
|
78
|
-
var item = args[argument];
|
|
79
|
-
str = str.replace("$$", item);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
71
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'orderByCountDescending');
|
|
82
72
|
this.__queryObject.orderByCountDesc(str, this.__entity.__name);
|
|
83
73
|
return this;
|
|
84
74
|
}
|
|
85
75
|
|
|
86
76
|
orderBy(query, ...args){
|
|
87
77
|
var str = query.toString();
|
|
88
|
-
|
|
89
|
-
for(let argument in args){
|
|
90
|
-
var item = args[argument];
|
|
91
|
-
str = str.replace("$$", item);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
78
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'orderBy');
|
|
94
79
|
this.__queryObject.orderBy(str, this.__entity.__name);
|
|
95
80
|
return this;
|
|
96
81
|
}
|
|
97
82
|
|
|
98
83
|
orderByDescending(query, ...args){
|
|
99
84
|
var str = query.toString();
|
|
100
|
-
|
|
101
|
-
for(let argument in args){
|
|
102
|
-
var item = args[argument];
|
|
103
|
-
str = str.replace("$$", item);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
85
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'orderByDescending');
|
|
106
86
|
this.__queryObject.orderByDesc(str, this.__entity.__name);
|
|
107
87
|
return this;
|
|
108
88
|
}
|
|
@@ -115,25 +95,14 @@ class queryMethods{
|
|
|
115
95
|
/* WHERE and AND work together its a way to add to the WHERE CLAUSE DYNAMICALLY */
|
|
116
96
|
and(query, ...args){
|
|
117
97
|
var str = query.toString();
|
|
118
|
-
|
|
119
|
-
for(let argument in args){
|
|
120
|
-
var item = args[argument];
|
|
121
|
-
str = str.replace("$$", item);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
98
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'and');
|
|
124
99
|
this.__queryObject.and(str, this.__entity.__name);
|
|
125
100
|
return this;
|
|
126
101
|
}
|
|
127
102
|
|
|
128
103
|
where(query, ...args){
|
|
129
104
|
var str = query.toString();
|
|
130
|
-
|
|
131
|
-
for(let argument in args){
|
|
132
|
-
var item = args[argument];
|
|
133
|
-
str = str.replace("$$", item);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
105
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'where');
|
|
137
106
|
this.__queryObject.where(str, this.__entity.__name);
|
|
138
107
|
return this;
|
|
139
108
|
}
|
|
@@ -142,12 +111,7 @@ class queryMethods{
|
|
|
142
111
|
//Eagerly loading
|
|
143
112
|
include(query, ...args){
|
|
144
113
|
var str = query.toString();
|
|
145
|
-
|
|
146
|
-
for(let argument in args){
|
|
147
|
-
var item = args[argument];
|
|
148
|
-
str = str.replace("$$", item);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
114
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'include');
|
|
151
115
|
this.__queryObject.include(str, this.__entity.__name);
|
|
152
116
|
return this;
|
|
153
117
|
}
|
|
@@ -155,12 +119,7 @@ class queryMethods{
|
|
|
155
119
|
// only takes a array of selected items
|
|
156
120
|
select(query, ...args){
|
|
157
121
|
var str = query.toString();
|
|
158
|
-
|
|
159
|
-
for(let argument in args){
|
|
160
|
-
var item = args[argument];
|
|
161
|
-
str = str.replace("$$", item);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
122
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'select');
|
|
164
123
|
this.__queryObject.select(str, this.__entity.__name);
|
|
165
124
|
return this;
|
|
166
125
|
}
|
|
@@ -182,12 +141,7 @@ class queryMethods{
|
|
|
182
141
|
count(query, ...args){
|
|
183
142
|
if(query){
|
|
184
143
|
var str = query.toString();
|
|
185
|
-
|
|
186
|
-
for(let argument in args){
|
|
187
|
-
var item = args[argument];
|
|
188
|
-
str = str.replace("$$", item);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
144
|
+
str = this.__validateAndReplacePlaceholders(str, args, 'count');
|
|
191
145
|
this.__queryObject.count(str, this.__entity.__name);
|
|
192
146
|
}
|
|
193
147
|
|
|
@@ -208,7 +162,36 @@ class queryMethods{
|
|
|
208
162
|
}
|
|
209
163
|
}
|
|
210
164
|
|
|
165
|
+
__validateAndReplacePlaceholders(str, args, methodName){
|
|
166
|
+
// Count placeholders
|
|
167
|
+
const placeholderCount = (str.match(/\$\$/g) || []).length;
|
|
168
|
+
const providedCount = args ? args.length : 0;
|
|
169
|
+
if(placeholderCount !== providedCount){
|
|
170
|
+
const msg = `Query argument error in ${methodName}: expected ${placeholderCount} value(s) for '$$', but received ${providedCount}.`;
|
|
171
|
+
console.error(msg);
|
|
172
|
+
throw new Error(msg);
|
|
173
|
+
}
|
|
174
|
+
if(args){
|
|
175
|
+
for(let argument in args){
|
|
176
|
+
var item = args[argument];
|
|
177
|
+
if(typeof item === 'undefined'){
|
|
178
|
+
const msg = `Query argument error in ${methodName}: placeholder value at index ${argument} is undefined.`;
|
|
179
|
+
console.error(msg);
|
|
180
|
+
throw new Error(msg);
|
|
181
|
+
}
|
|
182
|
+
str = str.replace("$$", item);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return str;
|
|
186
|
+
}
|
|
187
|
+
|
|
211
188
|
single(){
|
|
189
|
+
// If no clauses were used before single(), seed defaults so SQL is valid
|
|
190
|
+
if(this.__queryObject.script.entityMap.length === 0){
|
|
191
|
+
this.__queryObject.skipClause(this.__entity.__name);
|
|
192
|
+
this.__queryObject.script.take = 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
212
195
|
if(this.__context.isSQLite){
|
|
213
196
|
var entityValue = this.__context._SQLEngine.get(this.__queryObject.script, this.__entity, this.__context);
|
|
214
197
|
var sing = this.__singleEntityBuilder(entityValue);
|
|
@@ -228,6 +211,9 @@ class queryMethods{
|
|
|
228
211
|
if(this.__context.isSQLite){
|
|
229
212
|
if(this.__queryObject.script.entityMap.length === 0){
|
|
230
213
|
this.__queryObject.skipClause( this.__entity.__name);
|
|
214
|
+
if(!this.__queryObject.script.take || this.__queryObject.script.take === 0){
|
|
215
|
+
this.__queryObject.script.take = 1000;
|
|
216
|
+
}
|
|
231
217
|
}
|
|
232
218
|
var entityValue = this.__context._SQLEngine.all(this.__queryObject.script, this.__entity, this.__context);
|
|
233
219
|
var toLi = this.__multipleEntityBuilder(entityValue);
|
|
@@ -238,6 +224,9 @@ class queryMethods{
|
|
|
238
224
|
if(this.__context.isMySQL){
|
|
239
225
|
if(this.__queryObject.script.entityMap.length === 0){
|
|
240
226
|
this.__queryObject.skipClause( this.__entity.__name);
|
|
227
|
+
if(!this.__queryObject.script.take || this.__queryObject.script.take === 0){
|
|
228
|
+
this.__queryObject.script.take = 1000;
|
|
229
|
+
}
|
|
241
230
|
}
|
|
242
231
|
var entityValue = this.__context._SQLEngine.all(this.__queryObject.script, this.__entity, this.__context);
|
|
243
232
|
var toLi = this.__multipleEntityBuilder(entityValue);
|
|
@@ -249,8 +238,6 @@ class queryMethods{
|
|
|
249
238
|
// ------------------------------- FUNCTIONS THAT UPDATE SQL START FROM HERE -----------------------------------------------------
|
|
250
239
|
// ---------------------------------------------------------------------------------------------------------------------------------------
|
|
251
240
|
add(entityValue){
|
|
252
|
-
// This will call context API to REMOVE entity to update list
|
|
253
|
-
tools.clearAllProto(entityValue);
|
|
254
241
|
entityValue.__state = "insert";
|
|
255
242
|
entityValue.__entity = this.__entity;
|
|
256
243
|
entityValue.__context = this.__context;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// version 0.0.
|
|
1
|
+
// version 0.0.8
|
|
2
2
|
|
|
3
3
|
const LOG_OPERATORS_REGEX = /(\|\|)|(&&)/;
|
|
4
4
|
var tools = require('../Tools');
|
|
@@ -137,6 +137,43 @@ class queryScript{
|
|
|
137
137
|
if(type === "include" || type === "and"){
|
|
138
138
|
obj[type].push(cachedExpr);
|
|
139
139
|
}
|
|
140
|
+
else if(type === "where"){
|
|
141
|
+
// If where already exists, merge new expressions into existing where so multiple
|
|
142
|
+
// chained where(...) calls combine into a single WHERE clause (joined by AND).
|
|
143
|
+
if(obj.where && obj[entityName] && cachedExpr[entityName]){
|
|
144
|
+
const existingQuery = obj.where[entityName].query || {};
|
|
145
|
+
const incomingQuery = cachedExpr[entityName].query || {};
|
|
146
|
+
const existingExprs = existingQuery.expressions || [];
|
|
147
|
+
const incomingExprs = (incomingQuery.expressions || []).map(e => ({...e}));
|
|
148
|
+
|
|
149
|
+
// Avoid OR-group id collisions across separate where calls by offsetting
|
|
150
|
+
// incoming group ids by the max existing group id.
|
|
151
|
+
let maxGroup = 0;
|
|
152
|
+
for(let i = 0; i < existingExprs.length; i++){
|
|
153
|
+
const g = existingExprs[i] && existingExprs[i].group;
|
|
154
|
+
if(typeof g === 'number' && g > maxGroup){ maxGroup = g; }
|
|
155
|
+
}
|
|
156
|
+
for(let i = 0; i < incomingExprs.length; i++){
|
|
157
|
+
if(typeof incomingExprs[i].group === 'number'){
|
|
158
|
+
incomingExprs[i].group = incomingExprs[i].group + maxGroup;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
existingQuery.expressions = existingExprs.concat(incomingExprs);
|
|
163
|
+
|
|
164
|
+
// Merge selectFields for completeness (not strictly required for WHERE)
|
|
165
|
+
const existingFields = existingQuery.selectFields || [];
|
|
166
|
+
const incomingFields = incomingQuery.selectFields || [];
|
|
167
|
+
const mergedFields = existingFields.concat(incomingFields.filter(f => existingFields.indexOf(f) === -1));
|
|
168
|
+
if(mergedFields.length > 0){ existingQuery.selectFields = mergedFields; }
|
|
169
|
+
|
|
170
|
+
// Keep original obj.where; just ensure parentName is set correctly
|
|
171
|
+
obj.parentName = entityName;
|
|
172
|
+
}
|
|
173
|
+
else{
|
|
174
|
+
obj[type] = cachedExpr;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
140
177
|
else{
|
|
141
178
|
obj[type] = cachedExpr;
|
|
142
179
|
}
|
|
@@ -174,8 +211,9 @@ class queryScript{
|
|
|
174
211
|
}
|
|
175
212
|
|
|
176
213
|
OPERATORS_REGEX(entityName){
|
|
214
|
+
// Prefer longest operators first to avoid partially matching '>' in '>=' and leaving '=' in the argument
|
|
177
215
|
return new RegExp("(?:^|[^\\w\\d])" + entityName
|
|
178
|
-
+ "\\.((?:\\.?[\\w\\d_\\$]+)+)(?:\\((.*?)\\))?(?:\\s*(
|
|
216
|
+
+ "\\.((?:\\.?[\\w\\d_\\$]+)+)(?:\\((.*?)\\))?(?:\\s*((?:===)|(?:!==)|(?:<=)|(?:>=)|(?:==)|(?:!=)|(?:in)|>|<|(?:=))\\s*(.*))?")
|
|
179
217
|
}
|
|
180
218
|
|
|
181
219
|
|
|
@@ -193,10 +231,11 @@ class queryScript{
|
|
|
193
231
|
part.inside = part.query;
|
|
194
232
|
parts[part.name] = part;
|
|
195
233
|
}
|
|
196
|
-
|
|
197
|
-
part.
|
|
198
|
-
part.
|
|
199
|
-
part.
|
|
234
|
+
// Normalize logical operators and add spacing so we can safely split by tokens
|
|
235
|
+
part.inside = part.inside.replaceAll("&&", " and ");
|
|
236
|
+
part.query = part.query.replaceAll("&&", " and ");
|
|
237
|
+
part.inside = part.inside.replaceAll("||", " or ");
|
|
238
|
+
part.query = part.query.replaceAll("||", " or ");
|
|
200
239
|
|
|
201
240
|
return parts;
|
|
202
241
|
}
|
|
@@ -349,45 +388,73 @@ class queryScript{
|
|
|
349
388
|
var entity = this.getEntity(partQuery);
|
|
350
389
|
var exprPartRegExp = this.OPERATORS_REGEX(entity);
|
|
351
390
|
// check if query contains an AND.
|
|
352
|
-
var
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
391
|
+
var normalized = partQuery.replace(/\s+/g, ' ').trim();
|
|
392
|
+
var splitByAnd = normalized.split(/\sand\s/);
|
|
393
|
+
let groupId = 0;
|
|
394
|
+
for (let splitAnds in splitByAnd) {
|
|
395
|
+
let token = splitByAnd[splitAnds];
|
|
396
|
+
// Split possible OR groups inside this AND segment
|
|
397
|
+
let orParts = token.split(/\sor\s/);
|
|
398
|
+
const hasOrGroup = orParts.length > 1;
|
|
399
|
+
let currentGroup = hasOrGroup ? (++groupId) : null;
|
|
400
|
+
for (let idx in orParts){
|
|
401
|
+
let segment = orParts[idx];
|
|
402
|
+
// strip wrapping parentheses pairs repeatedly
|
|
403
|
+
segment = segment.trim();
|
|
404
|
+
while(segment.startsWith('(') && segment.endsWith(')')){
|
|
405
|
+
segment = segment.slice(1, -1).trim();
|
|
365
406
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
407
|
+
// detect unary negation like '!r.field'
|
|
408
|
+
let isNegated = false;
|
|
409
|
+
const negationMatch = segment.match(/^\s*!+\s*/);
|
|
410
|
+
if(negationMatch){
|
|
411
|
+
isNegated = true;
|
|
412
|
+
segment = segment.replace(/^\s*!+\s*/, '');
|
|
370
413
|
}
|
|
371
|
-
|
|
372
|
-
|
|
414
|
+
if (match = segment.match(exprPartRegExp)) {
|
|
415
|
+
fields = match[1].split(".");
|
|
416
|
+
func = (match[2] ? fields[fields.length - 1] : (match[3] || "exists"));
|
|
417
|
+
if (func == "==" || func == "===") {
|
|
418
|
+
func = "=";
|
|
419
|
+
}
|
|
420
|
+
else if (func == "!==") {
|
|
421
|
+
func = "!=";
|
|
422
|
+
}
|
|
423
|
+
arg = match[2] || match[4];
|
|
424
|
+
if (arg == "true" || arg == "false") {
|
|
425
|
+
arg = arg == "true";
|
|
426
|
+
}
|
|
427
|
+
else if (arg && arg.charAt(0) == arg.charAt(arg.length - 1) && (arg.charAt(0) == "'" || arg.charAt(0) == '"')) {
|
|
428
|
+
arg = arg.slice(1, -1);
|
|
429
|
+
}
|
|
430
|
+
part.entity = entity;
|
|
431
|
+
const exprObj = {
|
|
432
|
+
field: fields[0],
|
|
433
|
+
func : func.toLowerCase(),
|
|
434
|
+
arg : arg
|
|
435
|
+
};
|
|
436
|
+
// For bare field checks (exists) without explicit arg, carry negation forward
|
|
437
|
+
if(exprObj.func === 'exists' && typeof exprObj.arg === 'undefined' && isNegated){
|
|
438
|
+
exprObj.negate = true;
|
|
439
|
+
}
|
|
440
|
+
if(currentGroup){ exprObj.group = currentGroup; }
|
|
441
|
+
part.expressions.push(exprObj);
|
|
442
|
+
parts.query = part;
|
|
373
443
|
}
|
|
374
|
-
|
|
375
|
-
part.entity = entity;
|
|
376
|
-
|
|
377
|
-
part.expressions.push({
|
|
378
|
-
field: fields[0],
|
|
379
|
-
func : func.toLowerCase(),
|
|
380
|
-
arg : arg
|
|
381
|
-
});
|
|
382
|
-
parts.query = part;
|
|
383
444
|
}
|
|
384
|
-
|
|
445
|
+
}
|
|
385
446
|
}
|
|
386
447
|
|
|
387
448
|
return parts;
|
|
388
449
|
}
|
|
389
450
|
|
|
390
451
|
getEntity(str){
|
|
452
|
+
// Prefer parsing the lambda parameter (e.g., 'uc' in 'uc => uc.user_id == $$')
|
|
453
|
+
const m = str.match(/^\s*([\w\d$_]+?)\s*=>/);
|
|
454
|
+
if(m && m[1]){
|
|
455
|
+
return m[1];
|
|
456
|
+
}
|
|
457
|
+
// Fallback to previous behavior: first non-space char
|
|
391
458
|
var clean = str.replace(/\s/g, '');
|
|
392
459
|
return clean.substring(0, 1);
|
|
393
460
|
}
|