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.
@@ -1,5 +1,5 @@
1
1
 
2
- // version 0.0.10
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
- if(args){
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
- if(args){
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
- if(args){
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
- if(args){
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
- if(args){
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
- if(args){
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
- if(args){
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
- if(args){
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
- if(args){
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.5
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*(>|<|(?:===)|(?:!==)|(?:==)|(?:!=)|(?:=)|(?:<=)|(?:>=)|(?:in))\\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
- part.inside = part.inside.replaceAll("&&", "and");
197
- part.query = part.query.replaceAll("&&", "and");
198
- part.inside = part.inside.replaceAll("||", "or");
199
- part.query = part.query.replaceAll("||", "or");
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 trimmedQuery = partQuery.replace(/\s/g, '');
353
- var splitByAnd = trimmedQuery.split("and");
354
- for (let splitAnds in splitByAnd) {
355
-
356
- if (match = splitByAnd[splitAnds].match(exprPartRegExp)) {
357
- fields = match[1].split(".");
358
- func = (match[2] ? fields[fields.length - 1] : (match[3] || "exists"));
359
-
360
- if (func == "==" || func == "===") {
361
- func = "=";
362
- }
363
- else if (func == "!==") {
364
- func = "!=";
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
- arg = match[2] || match[4];
368
- if (arg == "true" || arg == "false") {
369
- arg = arg == "true";
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
- else if (arg && arg.charAt(0) == arg.charAt(arg.length - 1) && (arg.charAt(0) == "'" || arg.charAt(0) == '"')) {
372
- arg = arg.slice(1, -1);
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
  }