masterrecord 0.2.34 → 0.2.36
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 +7 -1
- package/QueryLanguage/queryScript.js +1 -1
- package/context.js +10 -2
- package/package.json +5 -5
- package/readme.md +227 -1
- package/test/tablePrefixTest.js +100 -0
- package/test/whereChainingTest.js +88 -0
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
"allow": [
|
|
4
4
|
"Read(//Users/alexanderrich/Documents/development/bookbaghq/bookbag-ce/components/models/app/models/**)",
|
|
5
5
|
"Read(//Users/alexanderrich/Documents/development/bookbaghq/bookbag-ce/components/models/app/controllers/api/**)",
|
|
6
|
-
"Read(//Users/alexanderrich/Documents/development/bookbaghq/bookbag-ce/config/environments/**)"
|
|
6
|
+
"Read(//Users/alexanderrich/Documents/development/bookbaghq/bookbag-ce/config/environments/**)",
|
|
7
|
+
"Bash(node test/tablePrefixTest.js:*)",
|
|
8
|
+
"Bash(node test/whereChainingTest.js:*)",
|
|
9
|
+
"Bash(npm whoami:*)",
|
|
10
|
+
"Bash(npm pkg fix:*)",
|
|
11
|
+
"Bash(~/.npmrc)",
|
|
12
|
+
"Bash(cat:*)"
|
|
7
13
|
],
|
|
8
14
|
"deny": [],
|
|
9
15
|
"ask": []
|
|
@@ -140,7 +140,7 @@ class queryScript{
|
|
|
140
140
|
else if(type === "where"){
|
|
141
141
|
// If where already exists, merge new expressions into existing where so multiple
|
|
142
142
|
// chained where(...) calls combine into a single WHERE clause (joined by AND).
|
|
143
|
-
if(obj.where && obj[entityName] && cachedExpr[entityName]){
|
|
143
|
+
if(obj.where && obj.where[entityName] && cachedExpr[entityName]){
|
|
144
144
|
const existingQuery = obj.where[entityName].query || {};
|
|
145
145
|
const incomingQuery = cachedExpr[entityName].query || {};
|
|
146
146
|
const existingExprs = existingQuery.expressions || [];
|
package/context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Version 0.0.
|
|
1
|
+
// Version 0.0.17
|
|
2
2
|
|
|
3
3
|
var modelBuilder = require('./Entity/entityModelBuilder');
|
|
4
4
|
var query = require('masterrecord/QueryLanguage/queryMethods');
|
|
@@ -24,6 +24,7 @@ class context {
|
|
|
24
24
|
__relationshipModels = [];
|
|
25
25
|
__environment = "";
|
|
26
26
|
__name = "";
|
|
27
|
+
tablePrefix = "";
|
|
27
28
|
isSQLite = false;
|
|
28
29
|
isMySQL = false;
|
|
29
30
|
isPostgres = false;
|
|
@@ -348,7 +349,14 @@ class context {
|
|
|
348
349
|
|
|
349
350
|
dbset(model, name){
|
|
350
351
|
var validModel = modelBuilder.create(model);
|
|
351
|
-
|
|
352
|
+
var tableName = name === undefined ? model.name : name;
|
|
353
|
+
|
|
354
|
+
// Apply tablePrefix if set
|
|
355
|
+
if(this.tablePrefix && typeof this.tablePrefix === 'string' && this.tablePrefix.length > 0){
|
|
356
|
+
tableName = this.tablePrefix + tableName;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
validModel.__name = tableName;
|
|
352
360
|
this.__entities.push(validModel); // model object
|
|
353
361
|
var buildMod = tools.createNewInstance(validModel, query, this);
|
|
354
362
|
this.__builderEntities.push(buildMod); // query builder entites
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.36",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"masterrecord": "
|
|
7
|
+
"masterrecord": "Migrations/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"masterrecord"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"commander": "^14.0.
|
|
31
|
-
"glob": "^
|
|
30
|
+
"commander": "^14.0.2",
|
|
31
|
+
"glob": "^13.0.0",
|
|
32
32
|
"deep-object-diff": "^1.1.9",
|
|
33
33
|
"pg": "^8.16.3",
|
|
34
34
|
"sync-mysql2": "^1.0.7",
|
|
35
35
|
"app-root-path": "^3.1.0",
|
|
36
|
-
"better-sqlite3": "^12.
|
|
36
|
+
"better-sqlite3": "^12.5.0"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/readme.md
CHANGED
|
@@ -369,6 +369,232 @@ set master=development && masterrecord update-database-all
|
|
|
369
369
|
- For SQLite contexts, the `connection` path will be created if the directory does not exist.
|
|
370
370
|
- For MySQL contexts, `ensure-database <ContextName>` can create the DB (permissions required) before migrations run.
|
|
371
371
|
- If you rename/move the project root, re-run `enable-migrations-all` or any single-context command once; snapshots use relative paths and will continue working.
|
|
372
|
-
- If `update-database-all` reports
|
|
372
|
+
- If `update-database-all` reports "no migration files found" for a context, run `get-migrations <ContextName>`. If empty, create a migration with `add-migration <Name> <ContextName>` or use `add-migration-all <Name>`.
|
|
373
|
+
|
|
374
|
+
## Table Prefixes
|
|
375
|
+
|
|
376
|
+
MasterRecord supports automatic table prefixing for both MySQL and SQLite databases. This is useful for:
|
|
377
|
+
- Multi-tenant applications sharing a single database
|
|
378
|
+
- Plugin systems where each plugin needs isolated tables
|
|
379
|
+
- Avoiding table name conflicts in shared database environments
|
|
380
|
+
|
|
381
|
+
### Using tablePrefix
|
|
382
|
+
|
|
383
|
+
Set the `tablePrefix` property in your Context constructor before calling `dbset()`:
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
var masterrecord = require('masterrecord');
|
|
387
|
+
const User = require('./models/User');
|
|
388
|
+
const Post = require('./models/Post');
|
|
389
|
+
|
|
390
|
+
class AppContext extends masterrecord.context {
|
|
391
|
+
constructor() {
|
|
392
|
+
super();
|
|
393
|
+
|
|
394
|
+
// Set table prefix
|
|
395
|
+
this.tablePrefix = 'myapp_';
|
|
396
|
+
|
|
397
|
+
// Configure environment
|
|
398
|
+
this.env('config/environments');
|
|
399
|
+
|
|
400
|
+
// Register models - prefix will be automatically applied
|
|
401
|
+
this.dbset(User); // Creates table: myapp_User
|
|
402
|
+
this.dbset(Post); // Creates table: myapp_Post
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
module.exports = AppContext;
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### How it works
|
|
410
|
+
|
|
411
|
+
When `tablePrefix` is set:
|
|
412
|
+
1. The prefix is automatically prepended to all table names during `dbset()` registration
|
|
413
|
+
2. Works with both the default table name (model class name) and custom names
|
|
414
|
+
3. Applies to all database operations: queries, inserts, updates, deletes, and migrations
|
|
415
|
+
4. Supports both MySQL and SQLite databases
|
|
416
|
+
|
|
417
|
+
### Example with custom table names
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
class AppContext extends masterrecord.context {
|
|
421
|
+
constructor() {
|
|
422
|
+
super();
|
|
423
|
+
this.tablePrefix = 'myapp_';
|
|
424
|
+
this.env('config/environments');
|
|
425
|
+
|
|
426
|
+
// Custom table name + prefix
|
|
427
|
+
this.dbset(User, 'users'); // Creates table: myapp_users
|
|
428
|
+
this.dbset(Post, 'blog_posts'); // Creates table: myapp_blog_posts
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Plugin example
|
|
434
|
+
|
|
435
|
+
Perfect for plugin systems where each plugin needs isolated tables:
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
// RAG Plugin Context
|
|
439
|
+
class RagContext extends masterrecord.context {
|
|
440
|
+
constructor() {
|
|
441
|
+
super();
|
|
442
|
+
|
|
443
|
+
// Prefix all RAG plugin tables
|
|
444
|
+
this.tablePrefix = 'rag_';
|
|
445
|
+
|
|
446
|
+
this.env(path.join(__dirname, '../../config/environments'));
|
|
447
|
+
|
|
448
|
+
this.dbset(Document); // Creates table: rag_Document
|
|
449
|
+
this.dbset(DocumentChunk); // Creates table: rag_DocumentChunk
|
|
450
|
+
this.dbset(Settings); // Creates table: rag_Settings
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Migrations with table prefixes
|
|
456
|
+
|
|
457
|
+
Table prefixes work seamlessly with migrations:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
# Enable migrations (prefix is read from your Context)
|
|
461
|
+
master=development masterrecord enable-migrations AppContext
|
|
462
|
+
|
|
463
|
+
# Create migration (tables will have prefix in migration file)
|
|
464
|
+
master=development masterrecord add-migration Init AppContext
|
|
465
|
+
|
|
466
|
+
# Apply migration (creates prefixed tables)
|
|
467
|
+
master=development masterrecord update-database AppContext
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The generated migration files will reference the prefixed table names, so you don't need to manually add prefixes in your migration code.
|
|
471
|
+
|
|
472
|
+
### Notes
|
|
473
|
+
- The prefix is applied during Context construction, so it must be set before `dbset()` calls
|
|
474
|
+
- The prefix is stored in migration snapshots, ensuring consistency across migration operations
|
|
475
|
+
- Empty strings or non-string values are ignored (no prefix applied)
|
|
476
|
+
- Both MySQL and SQLite fully support table prefixes with no special configuration needed
|
|
477
|
+
|
|
478
|
+
## Query Method Chaining
|
|
479
|
+
|
|
480
|
+
MasterRecord supports fluent query chaining for building complex queries. You can chain multiple `where()`, `orderBy()`, `skip()`, `take()`, and other methods together to build your query dynamically.
|
|
481
|
+
|
|
482
|
+
### Chaining Multiple where() Clauses
|
|
483
|
+
|
|
484
|
+
Multiple `where()` calls are automatically combined with AND logic:
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
// Build query dynamically
|
|
488
|
+
let query = context.QaTask;
|
|
489
|
+
|
|
490
|
+
// Add first condition
|
|
491
|
+
query = query.where(t => t.assigned_worker_id == $$, currentUser.id);
|
|
492
|
+
|
|
493
|
+
// Add second condition (combines with AND)
|
|
494
|
+
query = query.where(t => t.status == $$, 'pending');
|
|
495
|
+
|
|
496
|
+
// Add ordering and execute
|
|
497
|
+
let tasks = query.orderBy(t => t.created_at).toList();
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Generated SQL:**
|
|
501
|
+
```sql
|
|
502
|
+
SELECT * FROM QaTask AS t
|
|
503
|
+
WHERE t.assigned_worker_id = 123
|
|
504
|
+
AND t.status = 'pending'
|
|
505
|
+
ORDER BY t.created_at ASC
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Dynamic Query Building
|
|
509
|
+
|
|
510
|
+
This is especially useful for building queries based on conditional logic:
|
|
511
|
+
|
|
512
|
+
```javascript
|
|
513
|
+
let query = context.User;
|
|
514
|
+
|
|
515
|
+
// Always apply base filter
|
|
516
|
+
query = query.where(u => u.is_active == true);
|
|
517
|
+
|
|
518
|
+
// Conditionally add filters
|
|
519
|
+
if (searchTerm) {
|
|
520
|
+
query = query.where(u => u.name.like($$), `%${searchTerm}%`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (roleFilter) {
|
|
524
|
+
query = query.where(u => u.role == $$, roleFilter);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Add pagination
|
|
528
|
+
query = query
|
|
529
|
+
.orderBy(u => u.created_at)
|
|
530
|
+
.skip(offset)
|
|
531
|
+
.take(limit);
|
|
532
|
+
|
|
533
|
+
// Execute query
|
|
534
|
+
let users = query.toList();
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Chainable Query Methods
|
|
538
|
+
|
|
539
|
+
All of these methods return the query builder and can be chained:
|
|
540
|
+
|
|
541
|
+
- **`where(query, ...args)`** - Add WHERE condition (multiple calls combine with AND)
|
|
542
|
+
- **`and(query, ...args)`** - Explicitly add AND condition (alternative to chaining where)
|
|
543
|
+
- **`orderBy(query, ...args)`** - Sort ascending
|
|
544
|
+
- **`orderByDescending(query, ...args)`** - Sort descending
|
|
545
|
+
- **`skip(number)`** - Skip N records (pagination offset)
|
|
546
|
+
- **`take(number)`** - Limit to N records (pagination limit)
|
|
547
|
+
- **`select(query, ...args)`** - Select specific fields
|
|
548
|
+
- **`include(query, ...args)`** - Eager load relationships
|
|
549
|
+
|
|
550
|
+
### Combining with OR Logic
|
|
551
|
+
|
|
552
|
+
For OR conditions within a single where clause, use the `||` operator:
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
// Single where with OR
|
|
556
|
+
let tasks = context.Task
|
|
557
|
+
.where(t => t.status == 'pending' || t.status == 'in_progress')
|
|
558
|
+
.toList();
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Generated SQL:**
|
|
562
|
+
```sql
|
|
563
|
+
SELECT * FROM Task AS t
|
|
564
|
+
WHERE (t.status = 'pending' OR t.status = 'in_progress')
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Complex Example
|
|
568
|
+
|
|
569
|
+
```javascript
|
|
570
|
+
// Complex query with multiple conditions
|
|
571
|
+
let query = context.Order;
|
|
572
|
+
|
|
573
|
+
// Base filters
|
|
574
|
+
query = query.where(o => o.customer_id == $$, customerId);
|
|
575
|
+
query = query.where(o => o.status == $$ || o.status == $$, 'pending', 'processing');
|
|
576
|
+
|
|
577
|
+
// Date range filter
|
|
578
|
+
if (startDate) {
|
|
579
|
+
query = query.where(o => o.created_at >= $$, startDate);
|
|
580
|
+
}
|
|
581
|
+
if (endDate) {
|
|
582
|
+
query = query.where(o => o.created_at <= $$, endDate);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Sorting and pagination
|
|
586
|
+
let orders = query
|
|
587
|
+
.orderByDescending(o => o.created_at)
|
|
588
|
+
.skip(page * pageSize)
|
|
589
|
+
.take(pageSize)
|
|
590
|
+
.toList();
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Important Notes
|
|
594
|
+
|
|
595
|
+
- Each `where()` call adds an AND condition to the existing WHERE clause
|
|
596
|
+
- Conditions are combined in the order they're added
|
|
597
|
+
- The query is only executed when you call a terminal method: `toList()`, `single()`, `count()`
|
|
598
|
+
- Query builders are reusable - calling `toList()` resets the builder for the next query
|
|
373
599
|
|
|
374
600
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Test for tablePrefix functionality
|
|
2
|
+
// Run with: node test/tablePrefixTest.js
|
|
3
|
+
|
|
4
|
+
var masterrecord = require('../MasterRecord');
|
|
5
|
+
|
|
6
|
+
// Define a simple test model
|
|
7
|
+
class TestUser extends masterrecord.model {
|
|
8
|
+
id(db) {
|
|
9
|
+
db.integer().primaryKey().autoIncrement();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
name(db) {
|
|
13
|
+
db.string();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
email(db) {
|
|
17
|
+
db.string();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class TestPost extends masterrecord.model {
|
|
22
|
+
id(db) {
|
|
23
|
+
db.integer().primaryKey().autoIncrement();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
title(db) {
|
|
27
|
+
db.string();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Test 1: Context WITHOUT prefix
|
|
32
|
+
class TestContextNoPrefix extends masterrecord.context {
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
this.dbset(TestUser, 'User');
|
|
36
|
+
this.dbset(TestPost, 'Post');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Test 2: Context WITH prefix
|
|
41
|
+
class TestContextWithPrefix extends masterrecord.context {
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
this.tablePrefix = 'myapp_';
|
|
45
|
+
this.dbset(TestUser, 'User');
|
|
46
|
+
this.dbset(TestPost, 'Post');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Test 3: Context WITH prefix using default names
|
|
51
|
+
class TestContextWithPrefixDefault extends masterrecord.context {
|
|
52
|
+
constructor() {
|
|
53
|
+
super();
|
|
54
|
+
this.tablePrefix = 'test_';
|
|
55
|
+
this.dbset(TestUser);
|
|
56
|
+
this.dbset(TestPost);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Run tests
|
|
61
|
+
console.log('=== MasterRecord tablePrefix Tests ===\n');
|
|
62
|
+
|
|
63
|
+
// Test 1: No prefix
|
|
64
|
+
console.log('Test 1: Context without prefix');
|
|
65
|
+
const ctx1 = new TestContextNoPrefix();
|
|
66
|
+
const user1TableName = ctx1.__entities[0].__name;
|
|
67
|
+
const post1TableName = ctx1.__entities[1].__name;
|
|
68
|
+
console.log(` User table name: ${user1TableName}`);
|
|
69
|
+
console.log(` Post table name: ${post1TableName}`);
|
|
70
|
+
console.log(` Expected: User, Post`);
|
|
71
|
+
console.log(` Result: ${user1TableName === 'User' && post1TableName === 'Post' ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
72
|
+
|
|
73
|
+
// Test 2: With prefix and custom names
|
|
74
|
+
console.log('Test 2: Context with prefix "myapp_" and custom table names');
|
|
75
|
+
const ctx2 = new TestContextWithPrefix();
|
|
76
|
+
const user2TableName = ctx2.__entities[0].__name;
|
|
77
|
+
const post2TableName = ctx2.__entities[1].__name;
|
|
78
|
+
console.log(` User table name: ${user2TableName}`);
|
|
79
|
+
console.log(` Post table name: ${post2TableName}`);
|
|
80
|
+
console.log(` Expected: myapp_User, myapp_Post`);
|
|
81
|
+
console.log(` Result: ${user2TableName === 'myapp_User' && post2TableName === 'myapp_Post' ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
82
|
+
|
|
83
|
+
// Test 3: With prefix using default names
|
|
84
|
+
console.log('Test 3: Context with prefix "test_" and default table names');
|
|
85
|
+
const ctx3 = new TestContextWithPrefixDefault();
|
|
86
|
+
const user3TableName = ctx3.__entities[0].__name;
|
|
87
|
+
const post3TableName = ctx3.__entities[1].__name;
|
|
88
|
+
console.log(` TestUser table name: ${user3TableName}`);
|
|
89
|
+
console.log(` TestPost table name: ${post3TableName}`);
|
|
90
|
+
console.log(` Expected: test_TestUser, test_TestPost`);
|
|
91
|
+
console.log(` Result: ${user3TableName === 'test_TestUser' && post3TableName === 'test_TestPost' ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
92
|
+
|
|
93
|
+
// Test 4: Verify query builder has correct table name
|
|
94
|
+
console.log('Test 4: Query builder references');
|
|
95
|
+
console.log(` ctx2.myapp_User exists: ${ctx2.myapp_User !== undefined ? '✓ PASS' : '✗ FAIL'}`);
|
|
96
|
+
console.log(` ctx2.myapp_Post exists: ${ctx2.myapp_Post !== undefined ? '✓ PASS' : '✗ FAIL'}`);
|
|
97
|
+
console.log(` ctx3.test_TestUser exists: ${ctx3.test_TestUser !== undefined ? '✓ PASS' : '✗ FAIL'}`);
|
|
98
|
+
console.log(` ctx3.test_TestPost exists: ${ctx3.test_TestPost !== undefined ? '✓ PASS' : '✗ FAIL'}\n`);
|
|
99
|
+
|
|
100
|
+
console.log('=== Tests Complete ===');
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Test for where() chaining functionality
|
|
2
|
+
// This test verifies that multiple .where() calls combine properly with AND
|
|
3
|
+
|
|
4
|
+
var queryScript = require('../QueryLanguage/queryScript');
|
|
5
|
+
|
|
6
|
+
console.log('=== MasterRecord where() Chaining Test ===\n');
|
|
7
|
+
|
|
8
|
+
// Simulate what happens in the query builder
|
|
9
|
+
const qs = new queryScript();
|
|
10
|
+
|
|
11
|
+
// Test Case: Two chained where() calls (like the user's example)
|
|
12
|
+
// let query = this._qaContext.QaTask;
|
|
13
|
+
// query = query.where(t => t.assigned_worker_id == $$, this._currentUser.id);
|
|
14
|
+
// query = query.where(t => t.status == $$, status);
|
|
15
|
+
|
|
16
|
+
console.log('Test: Chaining two where() calls');
|
|
17
|
+
console.log(' First: where(t => t.assigned_worker_id == 123)');
|
|
18
|
+
console.log(' Second: where(t => t.status == "pending")');
|
|
19
|
+
console.log();
|
|
20
|
+
|
|
21
|
+
// First where call
|
|
22
|
+
qs.where('t => t.assigned_worker_id == 123', 'QaTask');
|
|
23
|
+
|
|
24
|
+
console.log('After first where():');
|
|
25
|
+
console.log(' script.where exists:', qs.script.where !== false);
|
|
26
|
+
console.log(' script.where.QaTask exists:', qs.script.where && qs.script.where.QaTask !== undefined);
|
|
27
|
+
if (qs.script.where && qs.script.where.QaTask && qs.script.where.QaTask.query) {
|
|
28
|
+
const exprs1 = qs.script.where.QaTask.query.expressions || [];
|
|
29
|
+
console.log(' Expressions count:', exprs1.length);
|
|
30
|
+
console.log(' Expression 1:', JSON.stringify(exprs1[0]));
|
|
31
|
+
}
|
|
32
|
+
console.log();
|
|
33
|
+
|
|
34
|
+
// Second where call (this should MERGE, not overwrite)
|
|
35
|
+
qs.where('t => t.status == "pending"', 'QaTask');
|
|
36
|
+
|
|
37
|
+
console.log('After second where():');
|
|
38
|
+
console.log(' script.where exists:', qs.script.where !== false);
|
|
39
|
+
console.log(' script.where.QaTask exists:', qs.script.where && qs.script.where.QaTask !== undefined);
|
|
40
|
+
|
|
41
|
+
if (qs.script.where && qs.script.where.QaTask && qs.script.where.QaTask.query) {
|
|
42
|
+
const exprs2 = qs.script.where.QaTask.query.expressions || [];
|
|
43
|
+
console.log(' Expressions count:', exprs2.length);
|
|
44
|
+
console.log(' Expression 1:', JSON.stringify(exprs2[0]));
|
|
45
|
+
console.log(' Expression 2:', JSON.stringify(exprs2[1]));
|
|
46
|
+
console.log();
|
|
47
|
+
|
|
48
|
+
// Verify results
|
|
49
|
+
console.log('=== Test Results ===');
|
|
50
|
+
if (exprs2.length === 2) {
|
|
51
|
+
console.log('✓ PASS: Both where conditions are present');
|
|
52
|
+
console.log(' - First condition: assigned_worker_id == 123');
|
|
53
|
+
console.log(' - Second condition: status == "pending"');
|
|
54
|
+
console.log(' - These should be combined with AND in the SQL');
|
|
55
|
+
} else {
|
|
56
|
+
console.log('✗ FAIL: Expected 2 expressions, got', exprs2.length);
|
|
57
|
+
if (exprs2.length === 1) {
|
|
58
|
+
console.log(' - Only the last where() was applied (bug not fixed)');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
console.log('✗ FAIL: script.where structure is invalid');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log();
|
|
66
|
+
console.log('=== Additional Test: Three where() calls ===');
|
|
67
|
+
|
|
68
|
+
// Test with three chained where calls
|
|
69
|
+
const qs2 = new queryScript();
|
|
70
|
+
qs2.where('t => t.user_id == 1', 'Task');
|
|
71
|
+
qs2.where('t => t.status == "active"', 'Task');
|
|
72
|
+
qs2.where('t => t.priority == "high"', 'Task');
|
|
73
|
+
|
|
74
|
+
if (qs2.script.where && qs2.script.where.Task && qs2.script.where.Task.query) {
|
|
75
|
+
const exprs3 = qs2.script.where.Task.query.expressions || [];
|
|
76
|
+
console.log('Expressions count:', exprs3.length);
|
|
77
|
+
if (exprs3.length === 3) {
|
|
78
|
+
console.log('✓ PASS: All three where conditions are present');
|
|
79
|
+
exprs3.forEach((expr, idx) => {
|
|
80
|
+
console.log(` ${idx + 1}. ${expr.field} ${expr.func} ${expr.arg}`);
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
console.log('✗ FAIL: Expected 3 expressions, got', exprs3.length);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log();
|
|
88
|
+
console.log('=== Test Complete ===');
|