masterrecord 0.2.36 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +19 -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 +309 -19
- package/context.js +47 -10
- 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 +249 -17
- package/package.json +3 -3
- package/postgresEngine.js +434 -491
- 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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: .any() with Comma-Separated Strings
|
|
3
|
+
*
|
|
4
|
+
* Bug: .any() only worked with arrays, not comma-separated strings
|
|
5
|
+
*
|
|
6
|
+
* Broken query:
|
|
7
|
+
* .where(j => j.id.any($$), "1,2,3")
|
|
8
|
+
* Generated: WHERE id IN (?) with params: ["1,2,3"] ❌
|
|
9
|
+
*
|
|
10
|
+
* Expected:
|
|
11
|
+
* WHERE id IN (?, ?, ?) with params: [1, 2, 3] ✅
|
|
12
|
+
*
|
|
13
|
+
* Fix: Split comma-separated strings into arrays before processing
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const QueryParameters = require('../QueryLanguage/queryParameters');
|
|
17
|
+
|
|
18
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
19
|
+
console.log("║ .any() Comma-Separated String Support Test ║");
|
|
20
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
21
|
+
|
|
22
|
+
let passed = 0;
|
|
23
|
+
let failed = 0;
|
|
24
|
+
|
|
25
|
+
// Mock the parameter replacement logic
|
|
26
|
+
function processParameter(item, dbType = 'mysql') {
|
|
27
|
+
const params = new QueryParameters();
|
|
28
|
+
|
|
29
|
+
// Check if this is an array (for IN clauses / .includes() / .any())
|
|
30
|
+
let itemArray = null;
|
|
31
|
+
if(Array.isArray(item)){
|
|
32
|
+
itemArray = item;
|
|
33
|
+
}
|
|
34
|
+
// Also handle comma-separated strings for .any() method
|
|
35
|
+
else if(typeof item === 'string' && item.includes(',')){
|
|
36
|
+
// Split comma-separated string into array
|
|
37
|
+
itemArray = item.split(',').map(v => v.trim());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if(itemArray){
|
|
41
|
+
// Add array parameters and get comma-separated placeholders
|
|
42
|
+
const placeholders = params.addParams(itemArray, dbType);
|
|
43
|
+
const paramValues = params.getParams();
|
|
44
|
+
return { placeholders, paramValues, count: itemArray.length };
|
|
45
|
+
}
|
|
46
|
+
else{
|
|
47
|
+
// Single value
|
|
48
|
+
const placeholder = params.addParam(item, dbType);
|
|
49
|
+
const paramValues = params.getParams();
|
|
50
|
+
return { placeholders: placeholder, paramValues, count: 1 };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Test 1: Array (already worked)
|
|
55
|
+
console.log("📝 Test 1: .any() with array (existing behavior)");
|
|
56
|
+
console.log("──────────────────────────────────────────────────");
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = processParameter([1, 2, 3]);
|
|
60
|
+
|
|
61
|
+
if(result.count === 3 && result.placeholders === '?, ?, ?' && result.paramValues.length === 3){
|
|
62
|
+
console.log(" ✓ Array processed correctly");
|
|
63
|
+
console.log(` ✓ Input: [1, 2, 3]`);
|
|
64
|
+
console.log(` ✓ Placeholders: ${result.placeholders}`);
|
|
65
|
+
console.log(` ✓ Params: [${result.paramValues.join(', ')}]`);
|
|
66
|
+
passed++;
|
|
67
|
+
} else {
|
|
68
|
+
console.log(` ✗ Array processing failed`);
|
|
69
|
+
console.log(` ✗ Got: ${result.placeholders}`);
|
|
70
|
+
failed++;
|
|
71
|
+
}
|
|
72
|
+
} catch(err) {
|
|
73
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
74
|
+
failed++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Test 2: Comma-separated string (THE BUG FIX)
|
|
78
|
+
console.log("\n📝 Test 2: .any() with comma-separated string (BUG FIX)");
|
|
79
|
+
console.log("──────────────────────────────────────────────────");
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const result = processParameter("1,2,3");
|
|
83
|
+
|
|
84
|
+
if(result.count === 3 && result.placeholders === '?, ?, ?' && result.paramValues.length === 3){
|
|
85
|
+
console.log(" ✓ Comma-separated string processed correctly");
|
|
86
|
+
console.log(` ✓ Input: "1,2,3"`);
|
|
87
|
+
console.log(` ✓ Placeholders: ${result.placeholders}`);
|
|
88
|
+
console.log(` ✓ Params: [${result.paramValues.join(', ')}]`);
|
|
89
|
+
console.log(" ✅ This was the broken case - now fixed!");
|
|
90
|
+
passed++;
|
|
91
|
+
} else {
|
|
92
|
+
console.log(` ✗ Comma-separated string processing failed`);
|
|
93
|
+
console.log(` ✗ Got: ${result.placeholders}`);
|
|
94
|
+
console.log(` ✗ Expected: ?, ?, ?`);
|
|
95
|
+
failed++;
|
|
96
|
+
}
|
|
97
|
+
} catch(err) {
|
|
98
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
99
|
+
failed++;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Test 3: Comma-separated string with spaces
|
|
103
|
+
console.log("\n📝 Test 3: .any() with spaces in comma-separated string");
|
|
104
|
+
console.log("──────────────────────────────────────────────────");
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = processParameter("Alice, Bob, Charlie");
|
|
108
|
+
|
|
109
|
+
if(result.count === 3 && result.placeholders === '?, ?, ?'){
|
|
110
|
+
console.log(" ✓ String with spaces processed correctly");
|
|
111
|
+
console.log(` ✓ Input: "Alice, Bob, Charlie"`);
|
|
112
|
+
console.log(` ✓ Placeholders: ${result.placeholders}`);
|
|
113
|
+
console.log(` ✓ Params: [${result.paramValues.join(', ')}]`);
|
|
114
|
+
console.log(` ✓ Values trimmed: ["${result.paramValues.join('", "')}"]`);
|
|
115
|
+
passed++;
|
|
116
|
+
} else {
|
|
117
|
+
console.log(` ✗ String with spaces processing failed`);
|
|
118
|
+
failed++;
|
|
119
|
+
}
|
|
120
|
+
} catch(err) {
|
|
121
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
122
|
+
failed++;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Test 4: Single value (no comma)
|
|
126
|
+
console.log("\n📝 Test 4: Single value without comma");
|
|
127
|
+
console.log("──────────────────────────────────────────────────");
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const result = processParameter("Alice");
|
|
131
|
+
|
|
132
|
+
if(result.count === 1 && result.placeholders === '?' && result.paramValues.length === 1){
|
|
133
|
+
console.log(" ✓ Single value processed correctly");
|
|
134
|
+
console.log(` ✓ Input: "Alice"`);
|
|
135
|
+
console.log(` ✓ Placeholders: ${result.placeholders}`);
|
|
136
|
+
console.log(` ✓ Params: [${result.paramValues.join(', ')}]`);
|
|
137
|
+
passed++;
|
|
138
|
+
} else {
|
|
139
|
+
console.log(` ✗ Single value processing failed`);
|
|
140
|
+
failed++;
|
|
141
|
+
}
|
|
142
|
+
} catch(err) {
|
|
143
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
144
|
+
failed++;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Test 5: Numeric string values
|
|
148
|
+
console.log("\n📝 Test 5: Numeric comma-separated string");
|
|
149
|
+
console.log("──────────────────────────────────────────────────");
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const result = processParameter("10,20,30,40,50");
|
|
153
|
+
|
|
154
|
+
if(result.count === 5 && result.placeholders === '?, ?, ?, ?, ?'){
|
|
155
|
+
console.log(" ✓ Numeric string processed correctly");
|
|
156
|
+
console.log(` ✓ Input: "10,20,30,40,50"`);
|
|
157
|
+
console.log(` ✓ Placeholders: ${result.placeholders}`);
|
|
158
|
+
console.log(` ✓ Params: [${result.paramValues.join(', ')}]`);
|
|
159
|
+
passed++;
|
|
160
|
+
} else {
|
|
161
|
+
console.log(` ✗ Numeric string processing failed`);
|
|
162
|
+
failed++;
|
|
163
|
+
}
|
|
164
|
+
} catch(err) {
|
|
165
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
166
|
+
failed++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Test 6: Postgres placeholders
|
|
170
|
+
console.log("\n📝 Test 6: Postgres placeholder format");
|
|
171
|
+
console.log("──────────────────────────────────────────────────");
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const result = processParameter("1,2,3", 'postgres');
|
|
175
|
+
|
|
176
|
+
if(result.count === 3 && result.placeholders === '$1, $2, $3'){
|
|
177
|
+
console.log(" ✓ Postgres placeholders generated correctly");
|
|
178
|
+
console.log(` ✓ Input: "1,2,3"`);
|
|
179
|
+
console.log(` ✓ Placeholders: ${result.placeholders}`);
|
|
180
|
+
console.log(` ✓ Params: [${result.paramValues.join(', ')}]`);
|
|
181
|
+
passed++;
|
|
182
|
+
} else {
|
|
183
|
+
console.log(` ✗ Postgres format failed`);
|
|
184
|
+
console.log(` ✗ Expected: $1, $2, $3`);
|
|
185
|
+
console.log(` ✗ Got: ${result.placeholders}`);
|
|
186
|
+
failed++;
|
|
187
|
+
}
|
|
188
|
+
} catch(err) {
|
|
189
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
190
|
+
failed++;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Summary
|
|
194
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
195
|
+
console.log("║ Test Summary ║");
|
|
196
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
197
|
+
|
|
198
|
+
const total = passed + failed;
|
|
199
|
+
const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
|
|
200
|
+
|
|
201
|
+
console.log(`\n Total Tests: ${total}`);
|
|
202
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
203
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
204
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
205
|
+
|
|
206
|
+
if(failed === 0){
|
|
207
|
+
console.log("🎉 All comma-separated string tests passed!");
|
|
208
|
+
console.log("\n✨ Bug Fixed: .any() Now Supports Comma-Separated Strings!");
|
|
209
|
+
console.log("\n📖 What Was Fixed:");
|
|
210
|
+
console.log(" - .any() now splits comma-separated strings into arrays");
|
|
211
|
+
console.log(" - Each value becomes a separate parameter");
|
|
212
|
+
console.log(" - Generates correct IN (?, ?, ?) SQL");
|
|
213
|
+
console.log(" - Trims whitespace from values");
|
|
214
|
+
console.log("\n📖 Original Bug:");
|
|
215
|
+
console.log(" Query: .where(j => j.id.any($$), '1,2,3')");
|
|
216
|
+
console.log(" Generated (WRONG): WHERE id IN (?)");
|
|
217
|
+
console.log(" Params (WRONG): ['1,2,3']");
|
|
218
|
+
console.log("\n📖 After Fix:");
|
|
219
|
+
console.log(" Query: .where(j => j.id.any($$), '1,2,3')");
|
|
220
|
+
console.log(" Generated (CORRECT): WHERE id IN (?, ?, ?)");
|
|
221
|
+
console.log(" Params (CORRECT): [1, 2, 3]");
|
|
222
|
+
console.log("\n📖 Supported Formats:");
|
|
223
|
+
console.log(" ✅ Array: .where(j => j.id.any($$), [1, 2, 3])");
|
|
224
|
+
console.log(" ✅ Comma string: .where(j => j.id.any($$), '1,2,3')");
|
|
225
|
+
console.log(" ✅ With spaces: .where(j => j.id.any($$), '1, 2, 3')");
|
|
226
|
+
console.log(" ✅ .includes() syntax: .where(j => $$.includes(j.id), [1, 2, 3])");
|
|
227
|
+
console.log("\n📖 Your Original Query Will Now Work:");
|
|
228
|
+
console.log(" failureJobs = this._qaContext.QaIntelligenceJob");
|
|
229
|
+
console.log(" .where(j => j.annotation_id.any($$) && j.job_type == $$ && j.status == $$,");
|
|
230
|
+
console.log(" annotationIds.join(','), 'failure_classification', 'succeeded')");
|
|
231
|
+
console.log(" .toList();");
|
|
232
|
+
console.log("\n✅ No more memory filtering needed!\n");
|
|
233
|
+
process.exit(0);
|
|
234
|
+
} else {
|
|
235
|
+
console.log("⚠️ Some tests failed. Review implementation.");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: .any() Method Already Exists
|
|
3
|
+
*
|
|
4
|
+
* This test demonstrates that .any() is a working feature in MasterRecord.
|
|
5
|
+
* It's used inside WHERE clause lambda expressions, not as a dbset method.
|
|
6
|
+
*
|
|
7
|
+
* Usage: context.User.where(u => u.id.any($$), "1,2,3").toList()
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const queryScript = require('../QueryLanguage/queryScript');
|
|
11
|
+
|
|
12
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
13
|
+
console.log("║ .any() Method Verification Test ║");
|
|
14
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
15
|
+
|
|
16
|
+
let passed = 0;
|
|
17
|
+
let failed = 0;
|
|
18
|
+
|
|
19
|
+
// Test 1: Verify .any() is whitelisted
|
|
20
|
+
console.log("📝 Test 1: Verify .any() is whitelisted in queryScript");
|
|
21
|
+
console.log("──────────────────────────────────────────────────");
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const script = new queryScript();
|
|
25
|
+
const isWhitelisted = script.isFunction('any');
|
|
26
|
+
|
|
27
|
+
if(isWhitelisted === true) {
|
|
28
|
+
console.log(" ✓ .any() is whitelisted");
|
|
29
|
+
console.log(" ✓ Can be used in lambda expressions");
|
|
30
|
+
passed++;
|
|
31
|
+
} else {
|
|
32
|
+
console.log(" ✗ .any() not whitelisted");
|
|
33
|
+
failed++;
|
|
34
|
+
}
|
|
35
|
+
} catch(err) {
|
|
36
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
37
|
+
failed++;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Test 2: Verify other whitelisted functions
|
|
41
|
+
console.log("\n📝 Test 2: Verify all whitelisted lambda functions");
|
|
42
|
+
console.log("──────────────────────────────────────────────────");
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const script = new queryScript();
|
|
46
|
+
const functions = ['any', 'like', 'include'];
|
|
47
|
+
let allWhitelisted = true;
|
|
48
|
+
|
|
49
|
+
functions.forEach(func => {
|
|
50
|
+
const isWhitelisted = script.isFunction(func);
|
|
51
|
+
if(isWhitelisted) {
|
|
52
|
+
console.log(` ✓ ${func}() is whitelisted`);
|
|
53
|
+
} else {
|
|
54
|
+
console.log(` ✗ ${func}() not whitelisted`);
|
|
55
|
+
allWhitelisted = false;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if(allWhitelisted) {
|
|
60
|
+
passed++;
|
|
61
|
+
} else {
|
|
62
|
+
failed++;
|
|
63
|
+
}
|
|
64
|
+
} catch(err) {
|
|
65
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
66
|
+
failed++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Test 3: Parse .any() in lambda expression
|
|
70
|
+
console.log("\n📝 Test 3: Parse .any() in WHERE clause");
|
|
71
|
+
console.log("──────────────────────────────────────────────────");
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const script = new queryScript();
|
|
75
|
+
const lambdaExpr = "u => u.id.any($$)";
|
|
76
|
+
const result = script.where(lambdaExpr, "User");
|
|
77
|
+
|
|
78
|
+
// Check that functions were detected
|
|
79
|
+
if(result && result.where) {
|
|
80
|
+
console.log(" ✓ Lambda expression parsed");
|
|
81
|
+
console.log(` ✓ Entity: ${result.entity}`);
|
|
82
|
+
|
|
83
|
+
// The .any() should be detected and stored in query structure
|
|
84
|
+
console.log(" ℹ .any() will be converted to IN clause during SQL generation");
|
|
85
|
+
passed++;
|
|
86
|
+
} else {
|
|
87
|
+
console.log(" ✗ Failed to parse lambda expression");
|
|
88
|
+
failed++;
|
|
89
|
+
}
|
|
90
|
+
} catch(err) {
|
|
91
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
92
|
+
failed++;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Test 4: Verify .any() syntax examples
|
|
96
|
+
console.log("\n📝 Test 4: Valid .any() syntax examples");
|
|
97
|
+
console.log("──────────────────────────────────────────────────");
|
|
98
|
+
|
|
99
|
+
const examples = [
|
|
100
|
+
{ desc: "Simple field", lambda: "u => u.id.any($$)" },
|
|
101
|
+
{ desc: "String field", lambda: "u => u.name.any($$)" },
|
|
102
|
+
{ desc: "With AND condition", lambda: "u => u.id.any($$) && u.active == true" },
|
|
103
|
+
{ desc: "Multiple .any()", lambda: "u => u.id.any($$) && u.role_id.any($$)" }
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const script = new queryScript();
|
|
108
|
+
let allValid = true;
|
|
109
|
+
|
|
110
|
+
examples.forEach(ex => {
|
|
111
|
+
try {
|
|
112
|
+
script.reset();
|
|
113
|
+
const result = script.where(ex.lambda, "User");
|
|
114
|
+
if(result && result.where) {
|
|
115
|
+
console.log(` ✓ ${ex.desc}: "${ex.lambda}"`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log(` ✗ ${ex.desc}: Failed to parse`);
|
|
118
|
+
allValid = false;
|
|
119
|
+
}
|
|
120
|
+
} catch(err) {
|
|
121
|
+
console.log(` ✗ ${ex.desc}: ${err.message}`);
|
|
122
|
+
allValid = false;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if(allValid) {
|
|
127
|
+
passed++;
|
|
128
|
+
} else {
|
|
129
|
+
failed++;
|
|
130
|
+
}
|
|
131
|
+
} catch(err) {
|
|
132
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
133
|
+
failed++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Summary
|
|
137
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
138
|
+
console.log("║ Test Summary ║");
|
|
139
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
140
|
+
|
|
141
|
+
const total = passed + failed;
|
|
142
|
+
const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
|
|
143
|
+
|
|
144
|
+
console.log(`\n Total Tests: ${total}`);
|
|
145
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
146
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
147
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
148
|
+
|
|
149
|
+
if(failed === 0){
|
|
150
|
+
console.log("🎉 All .any() tests passed!");
|
|
151
|
+
console.log("\n✨ .any() Already Works in MasterRecord!");
|
|
152
|
+
console.log("\n📖 How to Use .any():");
|
|
153
|
+
console.log("\n Option 1: .any() with comma-separated string");
|
|
154
|
+
console.log(" ─────────────────────────────────────────────");
|
|
155
|
+
console.log(" context.User.where(u => u.id.any($$), '1,2,3').toList();");
|
|
156
|
+
console.log(" context.User.where(u => u.name.any($$), 'Alice,Bob,Charlie').toList();");
|
|
157
|
+
console.log("\n Option 2: .includes() with JavaScript array (Recommended)");
|
|
158
|
+
console.log(" ──────────────────────────────────────────────────────────");
|
|
159
|
+
console.log(" const ids = [1, 2, 3];");
|
|
160
|
+
console.log(" context.User.where(u => $$.includes(u.id), ids).toList();");
|
|
161
|
+
console.log("\n📖 What .any() Does:");
|
|
162
|
+
console.log(" - Used inside WHERE clause lambda expressions");
|
|
163
|
+
console.log(" - Converts to SQL IN clause: WHERE id IN (?, ?, ?)");
|
|
164
|
+
console.log(" - Automatically parameterized for SQL injection protection");
|
|
165
|
+
console.log(" - Works with strings, numbers, and arrays");
|
|
166
|
+
console.log("\n📖 Implementation Location:");
|
|
167
|
+
console.log(" - File: QueryLanguage/queryScript.js");
|
|
168
|
+
console.log(" - Whitelisted: Line 506");
|
|
169
|
+
console.log(" - Parser: Lines 365-371");
|
|
170
|
+
console.log(" - Converts to: SQL IN clause with parameters");
|
|
171
|
+
console.log("\n✅ .any() is NOT missing - it already exists and works!\n");
|
|
172
|
+
process.exit(0);
|
|
173
|
+
} else {
|
|
174
|
+
console.log("⚠️ Some tests failed. Review implementation.");
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: .findById() Convenience Method
|
|
3
|
+
*
|
|
4
|
+
* Adds a common helper method that other ORMs provide
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const user = context.User.findById(123);
|
|
8
|
+
*
|
|
9
|
+
* Equivalent to:
|
|
10
|
+
* const user = context.User.where(u => u.id == $$, 123).single();
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const queryMethods = require('../QueryLanguage/queryMethods');
|
|
14
|
+
|
|
15
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
16
|
+
console.log("║ .findById() Method Test ║");
|
|
17
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
|
|
22
|
+
// Test 1: Verify .findById() method exists
|
|
23
|
+
console.log("📝 Test 1: Verify .findById() method exists");
|
|
24
|
+
console.log("──────────────────────────────────────────────────");
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const hasFindById = typeof queryMethods.prototype.findById === 'function';
|
|
28
|
+
|
|
29
|
+
if(hasFindById) {
|
|
30
|
+
console.log(" ✓ .findById() method exists");
|
|
31
|
+
console.log(" ✓ Method is a function");
|
|
32
|
+
passed++;
|
|
33
|
+
} else {
|
|
34
|
+
console.log(` ✗ .findById() method not found`);
|
|
35
|
+
failed++;
|
|
36
|
+
}
|
|
37
|
+
} catch(err) {
|
|
38
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
39
|
+
failed++;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Test 2: Test primary key detection
|
|
43
|
+
console.log("\n📝 Test 2: Primary key detection");
|
|
44
|
+
console.log("──────────────────────────────────────────────────");
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Mock entity with primary key
|
|
48
|
+
const mockEntity = {
|
|
49
|
+
__name: 'User',
|
|
50
|
+
id: { type: 'integer', primary: true, auto: true },
|
|
51
|
+
name: { type: 'string', nullable: false },
|
|
52
|
+
email: { type: 'string', nullable: false }
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Find the primary key
|
|
56
|
+
let primaryKeyField = null;
|
|
57
|
+
for (const fieldName in mockEntity) {
|
|
58
|
+
const field = mockEntity[fieldName];
|
|
59
|
+
if (field && field.primary === true) {
|
|
60
|
+
primaryKeyField = fieldName;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if(primaryKeyField === 'id') {
|
|
66
|
+
console.log(" ✓ Primary key detected: 'id'");
|
|
67
|
+
console.log(" ✓ Correctly identified from entity definition");
|
|
68
|
+
passed++;
|
|
69
|
+
} else {
|
|
70
|
+
console.log(` ✗ Expected 'id', got '${primaryKeyField}'`);
|
|
71
|
+
failed++;
|
|
72
|
+
}
|
|
73
|
+
} catch(err) {
|
|
74
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
75
|
+
failed++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Test 3: Test with entity without primary key
|
|
79
|
+
console.log("\n📝 Test 3: Entity without primary key (should error)");
|
|
80
|
+
console.log("──────────────────────────────────────────────────");
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const mockEntity = {
|
|
84
|
+
__name: 'NoPrimaryKey',
|
|
85
|
+
name: { type: 'string' },
|
|
86
|
+
email: { type: 'string' }
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const mockContext = {
|
|
90
|
+
__trackedEntities: [],
|
|
91
|
+
__track: function(entity) { return entity; },
|
|
92
|
+
isSQLite: true,
|
|
93
|
+
_SQLEngine: { get: () => null }
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const qm = new queryMethods(mockEntity, mockContext);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
qm.findById(123);
|
|
100
|
+
console.log(" ✗ Should have thrown error for missing primary key");
|
|
101
|
+
failed++;
|
|
102
|
+
} catch(err) {
|
|
103
|
+
if(err.message.includes('No primary key defined')) {
|
|
104
|
+
console.log(" ✓ Correctly throws error for missing primary key");
|
|
105
|
+
console.log(` ✓ Error message: ${err.message}`);
|
|
106
|
+
passed++;
|
|
107
|
+
} else {
|
|
108
|
+
console.log(` ✗ Wrong error: ${err.message}`);
|
|
109
|
+
failed++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch(err) {
|
|
113
|
+
console.log(` ✗ Unexpected error: ${err.message}`);
|
|
114
|
+
failed++;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Test 4: Test where clause generation
|
|
118
|
+
console.log("\n📝 Test 4: WHERE clause generation");
|
|
119
|
+
console.log("──────────────────────────────────────────────────");
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// Simulate the where clause that should be generated
|
|
123
|
+
const entityParam = 'r';
|
|
124
|
+
const primaryKeyField = 'id';
|
|
125
|
+
const whereClause = `${entityParam} => ${entityParam}.${primaryKeyField} == $$`;
|
|
126
|
+
|
|
127
|
+
const expectedClause = 'r => r.id == $$';
|
|
128
|
+
|
|
129
|
+
if(whereClause === expectedClause) {
|
|
130
|
+
console.log(" ✓ WHERE clause generated correctly");
|
|
131
|
+
console.log(` ✓ Generated: "${whereClause}"`);
|
|
132
|
+
console.log(" ✓ Will be processed by .where() method");
|
|
133
|
+
passed++;
|
|
134
|
+
} else {
|
|
135
|
+
console.log(` ✗ Expected '${expectedClause}', got '${whereClause}'`);
|
|
136
|
+
failed++;
|
|
137
|
+
}
|
|
138
|
+
} catch(err) {
|
|
139
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
140
|
+
failed++;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Test 5: Different primary key names
|
|
144
|
+
console.log("\n📝 Test 5: Different primary key field names");
|
|
145
|
+
console.log("──────────────────────────────────────────────────");
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const entities = [
|
|
149
|
+
{ name: 'User', pk: 'id' },
|
|
150
|
+
{ name: 'Post', pk: 'post_id' },
|
|
151
|
+
{ name: 'Comment', pk: 'comment_id' }
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
let allCorrect = true;
|
|
155
|
+
|
|
156
|
+
entities.forEach(({ name, pk }) => {
|
|
157
|
+
const mockEntity = {
|
|
158
|
+
__name: name,
|
|
159
|
+
[pk]: { type: 'integer', primary: true }
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
let primaryKeyField = null;
|
|
163
|
+
for (const fieldName in mockEntity) {
|
|
164
|
+
const field = mockEntity[fieldName];
|
|
165
|
+
if (field && field.primary === true) {
|
|
166
|
+
primaryKeyField = fieldName;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if(primaryKeyField === pk) {
|
|
172
|
+
console.log(` ✓ ${name}: Primary key '${pk}' detected`);
|
|
173
|
+
} else {
|
|
174
|
+
console.log(` ✗ ${name}: Expected '${pk}', got '${primaryKeyField}'`);
|
|
175
|
+
allCorrect = false;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if(allCorrect) {
|
|
180
|
+
passed++;
|
|
181
|
+
} else {
|
|
182
|
+
failed++;
|
|
183
|
+
}
|
|
184
|
+
} catch(err) {
|
|
185
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
186
|
+
failed++;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Summary
|
|
190
|
+
console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
|
|
191
|
+
console.log("║ Test Summary ║");
|
|
192
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
193
|
+
|
|
194
|
+
const total = passed + failed;
|
|
195
|
+
const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
|
|
196
|
+
|
|
197
|
+
console.log(`\n Total Tests: ${total}`);
|
|
198
|
+
console.log(` ✅ Passed: ${passed}`);
|
|
199
|
+
console.log(` ❌ Failed: ${failed}`);
|
|
200
|
+
console.log(` Success Rate: ${successRate}%\n`);
|
|
201
|
+
|
|
202
|
+
if(failed === 0){
|
|
203
|
+
console.log("🎉 All .findById() tests passed!");
|
|
204
|
+
console.log("\n✨ Convenience Method Added!");
|
|
205
|
+
console.log("\n📖 Usage:");
|
|
206
|
+
console.log(" // Simple and familiar syntax");
|
|
207
|
+
console.log(" const user = context.User.findById(123);");
|
|
208
|
+
console.log("\n📖 Equivalent To:");
|
|
209
|
+
console.log(" // Standard MasterRecord syntax");
|
|
210
|
+
console.log(" const user = context.User.where(u => u.id == $$, 123).single();");
|
|
211
|
+
console.log("\n📖 Features:");
|
|
212
|
+
console.log(" - Automatically detects primary key field");
|
|
213
|
+
console.log(" - Works with any primary key name (id, user_id, etc.)");
|
|
214
|
+
console.log(" - Returns single record or null");
|
|
215
|
+
console.log(" - Validates entity has a primary key");
|
|
216
|
+
console.log(" - Compatible with all database engines");
|
|
217
|
+
console.log("\n📖 Supported Primary Key Names:");
|
|
218
|
+
console.log(" ✅ id");
|
|
219
|
+
console.log(" ✅ user_id");
|
|
220
|
+
console.log(" ✅ post_id");
|
|
221
|
+
console.log(" ✅ Any field marked with primary: true");
|
|
222
|
+
console.log("\n✅ Now matches familiar ORM syntax from Mongoose, Sequelize, etc!\n");
|
|
223
|
+
process.exit(0);
|
|
224
|
+
} else {
|
|
225
|
+
console.log("⚠️ Some tests failed. Review implementation.");
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|