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/context.js
CHANGED
|
@@ -5,6 +5,7 @@ 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 = {
|
|
@@ -72,7 +74,7 @@ class context {
|
|
|
72
74
|
*/
|
|
73
75
|
__mysqlInit(env, sqlName){
|
|
74
76
|
try{
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
//const mysql = require(sqlName);
|
|
77
79
|
const connection = new MySQLClient(env);
|
|
78
80
|
this._SQLEngine = new MYSQLEngine();
|
|
@@ -85,6 +87,31 @@ class context {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
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
|
+
|
|
88
115
|
__clearErrorHandler(){
|
|
89
116
|
this._isModelValid = {
|
|
90
117
|
isValid: true,
|
|
@@ -212,13 +239,23 @@ class context {
|
|
|
212
239
|
}
|
|
213
240
|
|
|
214
241
|
if(type === 'mysql'){
|
|
215
|
-
this.isMySQL = true; this.isSQLite = false;
|
|
242
|
+
this.isMySQL = true; this.isSQLite = false; this.isPostgres = false;
|
|
216
243
|
this.db = this.__mysqlInit(options, 'mysql2');
|
|
217
244
|
this._SQLEngine.setDB(this.db, 'mysql');
|
|
218
245
|
return this;
|
|
219
246
|
}
|
|
220
247
|
|
|
221
|
-
|
|
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'.`);
|
|
222
259
|
}
|
|
223
260
|
catch(err){
|
|
224
261
|
console.log("error:", err);
|
|
@@ -385,9 +422,9 @@ class context {
|
|
|
385
422
|
break;
|
|
386
423
|
case "modified":
|
|
387
424
|
if(currentModel.__dirtyFields.length > 0){
|
|
388
|
-
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
389
|
-
//
|
|
390
|
-
var argu = this._SQLEngine.
|
|
425
|
+
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
426
|
+
// Use NEW SECURE parameterized version
|
|
427
|
+
var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
391
428
|
if(argu !== -1 ){
|
|
392
429
|
var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
393
430
|
var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
|
|
@@ -396,7 +433,7 @@ class context {
|
|
|
396
433
|
else{
|
|
397
434
|
console.log("Nothing has been tracked, modified, created or added");
|
|
398
435
|
}
|
|
399
|
-
|
|
436
|
+
|
|
400
437
|
}
|
|
401
438
|
else{
|
|
402
439
|
console.log("Tracked entity modified with no values being changed");
|
|
@@ -419,16 +456,16 @@ class context {
|
|
|
419
456
|
for (var model in tracked) {
|
|
420
457
|
var currentModel = tracked[model];
|
|
421
458
|
switch(currentModel.__state) {
|
|
422
|
-
case "insert":
|
|
459
|
+
case "insert":
|
|
423
460
|
var insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
424
461
|
insert.init(currentModel);
|
|
425
|
-
|
|
462
|
+
|
|
426
463
|
break;
|
|
427
464
|
case "modified":
|
|
428
465
|
if(currentModel.__dirtyFields.length > 0){
|
|
429
466
|
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
430
|
-
//
|
|
431
|
-
var argu = this._SQLEngine.
|
|
467
|
+
// Use NEW SECURE parameterized version
|
|
468
|
+
var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
432
469
|
if(argu !== -1 ){
|
|
433
470
|
var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
434
471
|
var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
|
|
@@ -437,7 +474,7 @@ class context {
|
|
|
437
474
|
else{
|
|
438
475
|
console.log("Nothing has been tracked, modified, created or added");
|
|
439
476
|
}
|
|
440
|
-
|
|
477
|
+
|
|
441
478
|
}
|
|
442
479
|
else{
|
|
443
480
|
console.log("Tracked entity modified with no values being changed");
|
|
@@ -448,13 +485,53 @@ class context {
|
|
|
448
485
|
case "delete":
|
|
449
486
|
var deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
450
487
|
deleteObject.init(currentModel);
|
|
451
|
-
|
|
488
|
+
|
|
452
489
|
break;
|
|
453
|
-
}
|
|
490
|
+
}
|
|
454
491
|
}
|
|
455
492
|
this.__clearErrorHandler();
|
|
456
493
|
//this._SQLEngine.endTransaction();
|
|
457
494
|
}
|
|
495
|
+
if(this.isPostgres){
|
|
496
|
+
// PostgreSQL async operations (no transaction control here)
|
|
497
|
+
for (var model in tracked) {
|
|
498
|
+
var currentModel = tracked[model];
|
|
499
|
+
switch(currentModel.__state) {
|
|
500
|
+
case "insert":
|
|
501
|
+
var insert = new insertManager(this._SQLEngine, this._isModelValid, this.__entities);
|
|
502
|
+
insert.init(currentModel);
|
|
503
|
+
|
|
504
|
+
break;
|
|
505
|
+
case "modified":
|
|
506
|
+
if(currentModel.__dirtyFields.length > 0){
|
|
507
|
+
var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
|
|
508
|
+
// Use NEW SECURE parameterized version
|
|
509
|
+
var argu = this._SQLEngine._buildSQLEqualToParameterized(cleanCurrentModel);
|
|
510
|
+
if(argu !== -1 ){
|
|
511
|
+
var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
|
|
512
|
+
var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
|
|
513
|
+
this._SQLEngine.update(sqlUpdate);
|
|
514
|
+
}
|
|
515
|
+
else{
|
|
516
|
+
console.log("Nothing has been tracked, modified, created or added");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
}
|
|
520
|
+
else{
|
|
521
|
+
console.log("Tracked entity modified with no values being changed");
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// code block
|
|
525
|
+
break;
|
|
526
|
+
case "delete":
|
|
527
|
+
var deleteObject = new deleteManager(this._SQLEngine, this.__entities);
|
|
528
|
+
deleteObject.init(currentModel);
|
|
529
|
+
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
this.__clearErrorHandler();
|
|
534
|
+
}
|
|
458
535
|
}
|
|
459
536
|
else{
|
|
460
537
|
console.log("save changes has no tracked entities");
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# .includes() Syntax Clarification
|
|
2
|
+
|
|
3
|
+
## Common Confusion: Two Different `.includes()` Methods
|
|
4
|
+
|
|
5
|
+
There are **two different** `.includes()` methods that developers confuse:
|
|
6
|
+
|
|
7
|
+
### 1. JavaScript's Native `.includes()` ❌ NOT SUPPORTED
|
|
8
|
+
```javascript
|
|
9
|
+
// ❌ WRONG - This is JavaScript's array.includes()
|
|
10
|
+
const ids = [1, 2, 3];
|
|
11
|
+
context.User.where(u => ids.includes(u.id)).toList();
|
|
12
|
+
// ^^^ JavaScript variable reference
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Why it doesn't work:**
|
|
16
|
+
- The lambda `u => ...` is parsed as a **string**, not executed as JavaScript
|
|
17
|
+
- Cannot access JavaScript variables (`ids`) from inside the lambda string
|
|
18
|
+
- Cannot call JavaScript methods on those variables
|
|
19
|
+
|
|
20
|
+
**Error:**
|
|
21
|
+
```
|
|
22
|
+
ReferenceError: ids is not defined
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### 2. MasterRecord's `.includes()` ✅ FULLY SUPPORTED
|
|
28
|
+
```javascript
|
|
29
|
+
// ✅ CORRECT - This is MasterRecord's special syntax
|
|
30
|
+
const ids = [1, 2, 3];
|
|
31
|
+
context.User.where(u => $$.includes(u.id), ids).toList();
|
|
32
|
+
// ^^ MasterRecord placeholder
|
|
33
|
+
// ^^^ Pass array as argument
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Why it works:**
|
|
37
|
+
- `$$` is a **placeholder** that MasterRecord recognizes
|
|
38
|
+
- The array `ids` is passed as a **separate argument**
|
|
39
|
+
- MasterRecord transforms `$$.includes(u.id)` → `u.id.any($$)` internally
|
|
40
|
+
- Generates proper SQL: `WHERE id IN (?, ?, ?)`
|
|
41
|
+
|
|
42
|
+
**Generated SQL:**
|
|
43
|
+
```sql
|
|
44
|
+
SELECT * FROM User WHERE id IN (?, ?, ?)
|
|
45
|
+
-- Parameters: [1, 2, 3]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Side-by-Side Comparison
|
|
51
|
+
|
|
52
|
+
| Feature | JavaScript `.includes()` | MasterRecord `.includes()` |
|
|
53
|
+
|---------|-------------------------|---------------------------|
|
|
54
|
+
| Syntax | `array.includes(field)` | `$$.includes(field)` |
|
|
55
|
+
| Where used | JavaScript code | MasterRecord lambda strings |
|
|
56
|
+
| Array location | Inside lambda | Separate argument |
|
|
57
|
+
| Supported | ❌ No | ✅ Yes |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Examples: Wrong vs Right
|
|
62
|
+
|
|
63
|
+
### ❌ Wrong: JavaScript Syntax
|
|
64
|
+
```javascript
|
|
65
|
+
// Trying to use JavaScript's includes() - WON'T WORK
|
|
66
|
+
const userIds = [1, 2, 3];
|
|
67
|
+
const roleIds = [10, 20];
|
|
68
|
+
|
|
69
|
+
// ❌ Wrong
|
|
70
|
+
context.User
|
|
71
|
+
.where(u => userIds.includes(u.id) && roleIds.includes(u.role_id))
|
|
72
|
+
.toList();
|
|
73
|
+
|
|
74
|
+
// Error: userIds is not defined
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### ✅ Right: MasterRecord Syntax
|
|
78
|
+
```javascript
|
|
79
|
+
// Using MasterRecord's includes() - WORKS
|
|
80
|
+
const userIds = [1, 2, 3];
|
|
81
|
+
const roleIds = [10, 20];
|
|
82
|
+
|
|
83
|
+
// ✅ Correct
|
|
84
|
+
context.User
|
|
85
|
+
.where(u => $$.includes(u.id), userIds)
|
|
86
|
+
.and(u => $$.includes(u.role_id), roleIds)
|
|
87
|
+
.toList();
|
|
88
|
+
|
|
89
|
+
// Generates: WHERE id IN (?, ?, ?) AND role_id IN (?, ?)
|
|
90
|
+
// Params: [1, 2, 3, 10, 20]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Alternative Syntax: `.any()`
|
|
96
|
+
|
|
97
|
+
You can also use `.any()` directly (it's what `.includes()` transforms to):
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
// Option 1: .includes() (modern, readable)
|
|
101
|
+
context.User.where(u => $$.includes(u.id), [1, 2, 3]).toList();
|
|
102
|
+
|
|
103
|
+
// Option 2: .any() (classic syntax)
|
|
104
|
+
context.User.where(u => u.id.any($$), [1, 2, 3]).toList();
|
|
105
|
+
|
|
106
|
+
// Option 3: .any() with comma string (also works)
|
|
107
|
+
context.User.where(u => u.id.any($$), "1,2,3").toList();
|
|
108
|
+
|
|
109
|
+
// All three generate the same SQL:
|
|
110
|
+
// WHERE id IN (?, ?, ?)
|
|
111
|
+
// Params: [1, 2, 3]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Why The Lambda is a String
|
|
117
|
+
|
|
118
|
+
MasterRecord's lambda expressions are **not executed as JavaScript**. They are:
|
|
119
|
+
|
|
120
|
+
1. **Converted to strings**: `r => r.id == $` becomes the string `"r => r.id == $"`
|
|
121
|
+
2. **Parsed**: MasterRecord extracts entity name (`r`), field name (`id`), operator (`==`), placeholder (`$`)
|
|
122
|
+
3. **Converted to SQL**: Generates `WHERE id = ?`
|
|
123
|
+
|
|
124
|
+
**This means:**
|
|
125
|
+
- ✅ Can use: MasterRecord syntax (`$$`, `$`, `.any()`, `.includes()`, `.like()`)
|
|
126
|
+
- ❌ Cannot use: JavaScript variables, JavaScript methods, JavaScript operators beyond basic comparison
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Common Mistakes and Solutions
|
|
131
|
+
|
|
132
|
+
### Mistake 1: Referencing JavaScript Variables
|
|
133
|
+
```javascript
|
|
134
|
+
// ❌ Wrong
|
|
135
|
+
const minAge = 18;
|
|
136
|
+
context.User.where(u => u.age > minAge).toList();
|
|
137
|
+
// Error: minAge is not defined
|
|
138
|
+
|
|
139
|
+
// ✅ Right
|
|
140
|
+
const minAge = 18;
|
|
141
|
+
context.User.where(u => u.age > $, minAge).toList();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Mistake 2: Using JavaScript Array Methods
|
|
145
|
+
```javascript
|
|
146
|
+
// ❌ Wrong
|
|
147
|
+
const ids = [1, 2, 3];
|
|
148
|
+
context.User.where(u => ids.includes(u.id)).toList();
|
|
149
|
+
// Error: ids is not defined
|
|
150
|
+
|
|
151
|
+
// ✅ Right
|
|
152
|
+
const ids = [1, 2, 3];
|
|
153
|
+
context.User.where(u => $$.includes(u.id), ids).toList();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Mistake 3: Calling JavaScript String Methods
|
|
157
|
+
```javascript
|
|
158
|
+
// ❌ Wrong
|
|
159
|
+
context.User.where(u => u.name.startsWith("John")).toList();
|
|
160
|
+
// Error: startsWith is not defined
|
|
161
|
+
|
|
162
|
+
// ✅ Right - Use SQL LIKE
|
|
163
|
+
context.User.where(u => u.name.like($$), "John%").toList();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Quick Reference
|
|
169
|
+
|
|
170
|
+
**When writing MasterRecord queries:**
|
|
171
|
+
|
|
172
|
+
✅ **DO use:**
|
|
173
|
+
- `$$` - Placeholder for parameters
|
|
174
|
+
- `$` - Single placeholder (backwards compatibility)
|
|
175
|
+
- `$$.includes(field)` - IN clause with arrays
|
|
176
|
+
- `field.any($$)` - Alternative IN clause syntax
|
|
177
|
+
- `field.like($$)` - LIKE clause
|
|
178
|
+
- Basic operators: `==`, `!=`, `>`, `<`, `>=`, `<=`, `&&`, `||`
|
|
179
|
+
|
|
180
|
+
❌ **DON'T use:**
|
|
181
|
+
- JavaScript variables directly (use `$$` placeholders instead)
|
|
182
|
+
- JavaScript methods (`.includes()`, `.startsWith()`, etc.)
|
|
183
|
+
- Complex JavaScript expressions
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Summary
|
|
188
|
+
|
|
189
|
+
**MasterRecord's `.includes()` is fully supported and works great!**
|
|
190
|
+
|
|
191
|
+
Just remember:
|
|
192
|
+
1. Use `$$.includes(field)` not `array.includes(field)`
|
|
193
|
+
2. Pass the array as a separate argument
|
|
194
|
+
3. The lambda is a string, not JavaScript code
|
|
195
|
+
|
|
196
|
+
**Correct Pattern:**
|
|
197
|
+
```javascript
|
|
198
|
+
const values = [1, 2, 3];
|
|
199
|
+
context.Model.where(m => $$.includes(m.field), values).toList();
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This is **not a bug** - it's working as designed. The confusion comes from developers trying to use JavaScript's native `.includes()` method inside the lambda string, which isn't possible because lambdas are parsed, not executed.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# MasterRecord Methods Reference
|
|
2
|
+
|
|
3
|
+
## Common Confusion: Dbset Methods vs Lambda Functions
|
|
4
|
+
|
|
5
|
+
MasterRecord has two types of methods that are often confused:
|
|
6
|
+
|
|
7
|
+
### 1. Dbset Methods (Called on context.EntityName)
|
|
8
|
+
|
|
9
|
+
These are methods you call **directly on the dbset**:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const user = context.User.new(); // ✅ Dbset method
|
|
13
|
+
context.User.add(user); // ✅ Dbset method
|
|
14
|
+
context.User.where(u => u.id == $, 1); // ✅ Dbset method
|
|
15
|
+
context.User.toList(); // ✅ Dbset method
|
|
16
|
+
context.User.single(); // ✅ Dbset method
|
|
17
|
+
context.User.remove(user); // ✅ Dbset method
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Location**: `QueryLanguage/queryMethods.js`
|
|
21
|
+
|
|
22
|
+
### 2. Lambda Expression Functions (Used inside WHERE/AND clauses)
|
|
23
|
+
|
|
24
|
+
These are **functions used INSIDE the lambda expression string**:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
// ✅ .any() - Used inside lambda
|
|
28
|
+
context.User.where(u => u.id.any($$), "1,2,3").toList();
|
|
29
|
+
|
|
30
|
+
// ✅ .like() - Used inside lambda
|
|
31
|
+
context.User.where(u => u.name.like($$), "John%").toList();
|
|
32
|
+
|
|
33
|
+
// ✅ .includes() - Used inside lambda (transforms to .any())
|
|
34
|
+
context.User.where(u => $$.includes(u.id), [1, 2, 3]).toList();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Location**: `QueryLanguage/queryScript.js` (parsed from lambda string)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Complete Method List
|
|
42
|
+
|
|
43
|
+
### Dbset Methods
|
|
44
|
+
|
|
45
|
+
| Method | Description | Example |
|
|
46
|
+
|--------|-------------|---------|
|
|
47
|
+
| `.new()` | Create new entity instance | `const user = context.User.new();` |
|
|
48
|
+
| `.add(entity)` | Track entity for INSERT | `context.User.add(user);` |
|
|
49
|
+
| `.remove(entity)` | Track entity for DELETE | `context.User.remove(user);` |
|
|
50
|
+
| `.where(lambda, ...args)` | Filter query | `context.User.where(u => u.id == $, 1)` |
|
|
51
|
+
| `.and(lambda, ...args)` | Add AND condition | `.where(...).and(u => u.active == true)` |
|
|
52
|
+
| `.orderBy(lambda)` | Sort ascending | `.orderBy(u => u.name)` |
|
|
53
|
+
| `.orderByDescending(lambda)` | Sort descending | `.orderByDescending(u => u.created_at)` |
|
|
54
|
+
| `.take(n)` | Limit results | `.take(10)` |
|
|
55
|
+
| `.skip(n)` | Offset results | `.skip(20)` |
|
|
56
|
+
| `.toList()` | Execute and return array | `.where(...).toList()` |
|
|
57
|
+
| `.single()` | Execute and return one | `.where(...).single()` |
|
|
58
|
+
| `.first()` | Execute and return first | `.where(...).first()` |
|
|
59
|
+
| `.include(lambda)` | Eager load relationships | `.include(u => u.Posts)` |
|
|
60
|
+
| `.raw(sql)` | Execute raw SQL | `.raw("SELECT * FROM User")` |
|
|
61
|
+
|
|
62
|
+
### Lambda Expression Functions
|
|
63
|
+
|
|
64
|
+
| Function | Used Inside | Description | Example |
|
|
65
|
+
|----------|-------------|-------------|---------|
|
|
66
|
+
| `.any($$)` | WHERE/AND | IN clause (comma-separated) | `u => u.id.any($$), "1,2,3"` |
|
|
67
|
+
| `.like($$)` | WHERE/AND | LIKE clause | `u => u.name.like($$), "John%"` |
|
|
68
|
+
| `.includes()` | WHERE/AND | IN clause (array) | `$$.includes(u.id), [1,2,3]` |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Common Patterns
|
|
73
|
+
|
|
74
|
+
### Creating and Inserting Entities
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// Create new entity
|
|
78
|
+
const user = context.User.new();
|
|
79
|
+
user.name = "John Doe";
|
|
80
|
+
user.email = "john@example.com";
|
|
81
|
+
user.age = 30;
|
|
82
|
+
|
|
83
|
+
// Track for insert
|
|
84
|
+
context.User.add(user); // Optional - .new() auto-tracks
|
|
85
|
+
|
|
86
|
+
// Save to database
|
|
87
|
+
context.saveChanges();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### IN Clause Queries
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Option 1: .any() with comma-separated string
|
|
94
|
+
context.User.where(u => u.id.any($$), "1,2,3").toList();
|
|
95
|
+
|
|
96
|
+
// Option 2: .includes() with array (Recommended)
|
|
97
|
+
const ids = [1, 2, 3];
|
|
98
|
+
context.User.where(u => $$.includes(u.id), ids).toList();
|
|
99
|
+
|
|
100
|
+
// Both produce: SELECT * FROM User WHERE id IN (?, ?, ?)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### LIKE Queries
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// Starts with
|
|
107
|
+
context.User.where(u => u.name.like($$), "John%").toList();
|
|
108
|
+
|
|
109
|
+
// Contains
|
|
110
|
+
context.User.where(u => u.email.like($$), "%@example.com%").toList();
|
|
111
|
+
|
|
112
|
+
// Produces: SELECT * FROM User WHERE name LIKE ?
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Complex Queries
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
const activeUsers = context.User
|
|
119
|
+
.where(u => $$.includes(u.role_id), [1, 2, 3])
|
|
120
|
+
.and(u => u.active == true)
|
|
121
|
+
.and(u => u.created_at > $, lastWeek)
|
|
122
|
+
.orderByDescending(u => u.created_at)
|
|
123
|
+
.take(50)
|
|
124
|
+
.toList();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Why Your LLM Might Be Confused
|
|
130
|
+
|
|
131
|
+
### ❌ Common Misunderstanding
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// ❌ WRONG - Trying to call .any() on the dbset
|
|
135
|
+
context.User.any(...) // Error: .any is not a function
|
|
136
|
+
|
|
137
|
+
// ✅ CORRECT - Use .any() inside the lambda expression
|
|
138
|
+
context.User.where(u => u.id.any($$), "1,2,3")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Key Difference
|
|
142
|
+
|
|
143
|
+
- **Dbset methods**: Called on `context.EntityName` (e.g., `.new()`, `.add()`)
|
|
144
|
+
- **Lambda functions**: Used inside the `where()` lambda string (e.g., `.any()`, `.like()`)
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Implementation Details
|
|
149
|
+
|
|
150
|
+
### Dbset Methods
|
|
151
|
+
- **File**: `QueryLanguage/queryMethods.js`
|
|
152
|
+
- **Class**: `queryMethods`
|
|
153
|
+
- **Instance**: Created via `context.dbset(Model)`
|
|
154
|
+
- **Methods**: Prototype methods on `queryMethods` class
|
|
155
|
+
|
|
156
|
+
### Lambda Functions
|
|
157
|
+
- **File**: `QueryLanguage/queryScript.js`
|
|
158
|
+
- **Parsing**: `describeExpressionPartsFunctions()` (lines 304-388)
|
|
159
|
+
- **Whitelist**: `isFunction()` method (lines 505-513)
|
|
160
|
+
- **Recognized**: `any`, `like`, `include`
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Quick Reference
|
|
165
|
+
|
|
166
|
+
| What You Want | Correct Syntax |
|
|
167
|
+
|---------------|----------------|
|
|
168
|
+
| Create entity | `context.User.new()` |
|
|
169
|
+
| Add to context | `context.User.add(entity)` |
|
|
170
|
+
| Find by ID | `context.User.where(u => u.id == $, 1).single()` |
|
|
171
|
+
| Find multiple IDs | `context.User.where(u => $$.includes(u.id), [1,2,3]).toList()` |
|
|
172
|
+
| Search text | `context.User.where(u => u.name.like($$), "John%").toList()` |
|
|
173
|
+
| Complex filter | `context.User.where(u => u.active == true).and(u => u.age > $, 18).toList()` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Summary
|
|
178
|
+
|
|
179
|
+
✅ **`.new()` is a dbset method** - Added in latest version
|
|
180
|
+
✅ **`.any()` is a lambda function** - Already existed
|
|
181
|
+
✅ **`.includes()` is a lambda function** - Transforms to `.any()`
|
|
182
|
+
✅ **Both are different types of methods used in different places**
|
|
183
|
+
|
|
184
|
+
If your LLM says `.any()` doesn't exist, clarify that it's looking for a **dbset method** when it should be looking for a **lambda expression function**.
|