@expo/entity-database-adapter-knex 0.60.0 → 0.62.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/build/src/AuthorizationResultBasedKnexEntityLoader.d.ts +8 -9
- package/build/src/AuthorizationResultBasedKnexEntityLoader.js +5 -11
- package/build/src/BasePostgresEntityDatabaseAdapter.d.ts +4 -3
- package/build/src/BasePostgresEntityDatabaseAdapter.js +13 -19
- package/build/src/BaseSQLQueryBuilder.d.ts +4 -3
- package/build/src/BaseSQLQueryBuilder.js +4 -9
- package/build/src/EnforcingKnexEntityLoader.d.ts +7 -7
- package/build/src/EnforcingKnexEntityLoader.js +4 -10
- package/build/src/EntityFields.js +4 -11
- package/build/src/KnexEntityLoaderFactory.d.ts +4 -4
- package/build/src/KnexEntityLoaderFactory.js +8 -13
- package/build/src/PaginationStrategy.js +2 -6
- package/build/src/PostgresEntity.d.ts +4 -3
- package/build/src/PostgresEntity.js +5 -10
- package/build/src/PostgresEntityDatabaseAdapter.d.ts +6 -5
- package/build/src/PostgresEntityDatabaseAdapter.js +19 -24
- package/build/src/PostgresEntityDatabaseAdapterProvider.d.ts +1 -1
- package/build/src/PostgresEntityDatabaseAdapterProvider.js +3 -8
- package/build/src/PostgresEntityQueryContextProvider.d.ts +3 -2
- package/build/src/PostgresEntityQueryContextProvider.js +5 -10
- package/build/src/ReadonlyPostgresEntity.d.ts +4 -3
- package/build/src/ReadonlyPostgresEntity.js +5 -10
- package/build/src/SQLOperator.d.ts +620 -103
- package/build/src/SQLOperator.js +332 -198
- package/build/src/errors/wrapNativePostgresCallAsync.js +11 -15
- package/build/src/index.d.ts +20 -20
- package/build/src/index.js +20 -37
- package/build/src/internal/EntityKnexDataManager.d.ts +5 -5
- package/build/src/internal/EntityKnexDataManager.js +78 -86
- package/build/src/internal/getKnexDataManager.d.ts +2 -2
- package/build/src/internal/getKnexDataManager.js +7 -14
- package/build/src/internal/getKnexEntityLoaderFactory.d.ts +2 -2
- package/build/src/internal/getKnexEntityLoaderFactory.js +5 -9
- package/build/src/internal/utilityTypes.js +1 -3
- package/build/src/internal/weakMaps.js +1 -6
- package/build/src/knexLoader.d.ts +3 -3
- package/build/src/knexLoader.js +5 -10
- package/package.json +8 -7
- package/src/AuthorizationResultBasedKnexEntityLoader.ts +15 -12
- package/src/BasePostgresEntityDatabaseAdapter.ts +3 -3
- package/src/BaseSQLQueryBuilder.ts +5 -4
- package/src/EnforcingKnexEntityLoader.ts +8 -8
- package/src/KnexEntityLoaderFactory.ts +6 -6
- package/src/PostgresEntity.ts +5 -5
- package/src/PostgresEntityDatabaseAdapter.ts +13 -15
- package/src/PostgresEntityDatabaseAdapterProvider.ts +2 -2
- package/src/PostgresEntityQueryContextProvider.ts +3 -6
- package/src/ReadonlyPostgresEntity.ts +5 -5
- package/src/SQLOperator.ts +1044 -278
- package/src/__integration-tests__/EntityCreationUtils-test.ts +5 -4
- package/src/__integration-tests__/PostgresEntityIntegration-test.ts +42 -16
- package/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts +6 -5
- package/src/__integration-tests__/PostgresInvalidSetup-test.ts +5 -4
- package/src/__integration-tests__/errors-test.ts +5 -4
- package/src/__testfixtures__/ErrorsTestEntity.ts +2 -3
- package/src/__testfixtures__/InvalidTestEntity.ts +2 -3
- package/src/__testfixtures__/PostgresTestEntity.ts +5 -6
- package/src/__testfixtures__/PostgresTriggerTestEntity.ts +7 -5
- package/src/__testfixtures__/PostgresUniqueTestEntity.ts +6 -4
- package/src/__testfixtures__/PostgresValidatorTestEntity.ts +7 -5
- package/src/__testfixtures__/createKnexIntegrationTestEntityCompanionProvider.ts +5 -8
- package/src/__tests__/AuthorizationResultBasedKnexEntityLoader-test.ts +12 -14
- package/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +7 -5
- package/src/__tests__/EnforcingKnexEntityLoader-test.ts +7 -6
- package/src/__tests__/EntityFields-test.ts +1 -1
- package/src/__tests__/PostgresEntity-test.ts +6 -6
- package/src/__tests__/ReadonlyEntity-test.ts +5 -5
- package/src/__tests__/SQLOperator-test.ts +403 -206
- package/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +9 -8
- package/src/__tests__/fixtures/StubPostgresDatabaseAdapterProvider.ts +2 -2
- package/src/__tests__/fixtures/TestEntity.ts +7 -7
- package/src/__tests__/fixtures/TestPaginationEntity.ts +9 -7
- package/src/__tests__/fixtures/createUnitTestPostgresEntityCompanionProvider.ts +3 -6
- package/src/errors/__tests__/wrapNativePostgresCallAsync-test.ts +1 -1
- package/src/errors/wrapNativePostgresCallAsync.ts +2 -2
- package/src/index.ts +20 -20
- package/src/internal/EntityKnexDataManager.ts +19 -21
- package/src/internal/__tests__/EntityKnexDataManager-test.ts +8 -15
- package/src/internal/__tests__/weakMaps-test.ts +1 -1
- package/src/internal/getKnexDataManager.ts +4 -4
- package/src/internal/getKnexEntityLoaderFactory.ts +4 -4
- package/src/knexLoader.ts +4 -4
- package/build/src/AuthorizationResultBasedKnexEntityLoader.js.map +0 -1
- package/build/src/BasePostgresEntityDatabaseAdapter.js.map +0 -1
- package/build/src/BaseSQLQueryBuilder.js.map +0 -1
- package/build/src/EnforcingKnexEntityLoader.js.map +0 -1
- package/build/src/EntityFields.js.map +0 -1
- package/build/src/KnexEntityLoaderFactory.js.map +0 -1
- package/build/src/PaginationStrategy.js.map +0 -1
- package/build/src/PostgresEntity.js.map +0 -1
- package/build/src/PostgresEntityDatabaseAdapter.js.map +0 -1
- package/build/src/PostgresEntityDatabaseAdapterProvider.js.map +0 -1
- package/build/src/PostgresEntityQueryContextProvider.js.map +0 -1
- package/build/src/ReadonlyPostgresEntity.js.map +0 -1
- package/build/src/SQLOperator.js.map +0 -1
- package/build/src/errors/wrapNativePostgresCallAsync.js.map +0 -1
- package/build/src/index.js.map +0 -1
- package/build/src/internal/EntityKnexDataManager.js.map +0 -1
- package/build/src/internal/getKnexDataManager.js.map +0 -1
- package/build/src/internal/getKnexEntityLoaderFactory.js.map +0 -1
- package/build/src/internal/utilityTypes.js.map +0 -1
- package/build/src/internal/weakMaps.js.map +0 -1
- package/build/src/knexLoader.js.map +0 -1
|
@@ -5,16 +5,17 @@ import {
|
|
|
5
5
|
arrayValue,
|
|
6
6
|
entityField,
|
|
7
7
|
identifier,
|
|
8
|
-
unsafeRaw,
|
|
9
8
|
sql,
|
|
10
9
|
SQLEntityField,
|
|
11
|
-
|
|
10
|
+
SQLChainableFragment,
|
|
12
11
|
SQLFragment,
|
|
13
|
-
|
|
12
|
+
SQLExpression,
|
|
14
13
|
SQLIdentifier,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
unsafeRaw,
|
|
15
|
+
type SupportedSQLValue,
|
|
16
|
+
} from '../SQLOperator.ts';
|
|
17
|
+
import type { TestFields } from './fixtures/TestEntity.ts';
|
|
18
|
+
import { testEntityConfiguration } from './fixtures/TestEntity.ts';
|
|
18
19
|
|
|
19
20
|
const getColumnForField = (fieldName: string): string =>
|
|
20
21
|
getDatabaseFieldForEntityField(testEntityConfiguration, fieldName as keyof TestFields);
|
|
@@ -71,7 +72,7 @@ describe('SQLOperator', () => {
|
|
|
71
72
|
|
|
72
73
|
it('handles arrayValue with entity field for = ANY()', () => {
|
|
73
74
|
const values = ['active', 'pending'];
|
|
74
|
-
const fragment = sql`${entityField
|
|
75
|
+
const fragment = sql`${entityField('stringField')} = ANY(${arrayValue(values)})`;
|
|
75
76
|
|
|
76
77
|
expect(fragment.sql).toBe('?? = ANY(?)');
|
|
77
78
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -206,7 +207,7 @@ describe('SQLOperator', () => {
|
|
|
206
207
|
filters.push(sql`category = ${'electronics'}`);
|
|
207
208
|
|
|
208
209
|
if (filters.length > 0) {
|
|
209
|
-
fragments.push(sql`WHERE ${
|
|
210
|
+
fragments.push(sql`WHERE ${SQLExpression.and(...filters)}`);
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
// Add ORDER BY
|
|
@@ -426,33 +427,33 @@ describe('SQLOperator', () => {
|
|
|
426
427
|
|
|
427
428
|
describe(SQLEntityField, () => {
|
|
428
429
|
it('stores the entity field name', () => {
|
|
429
|
-
const field = entityField
|
|
430
|
+
const field = entityField('stringField');
|
|
430
431
|
expect(field.fieldName).toBe('stringField');
|
|
431
432
|
});
|
|
432
433
|
|
|
433
434
|
it('uses ?? placeholder in SQL fragments', () => {
|
|
434
|
-
const fragment = sql`SELECT ${entityField
|
|
435
|
+
const fragment = sql`SELECT ${entityField('stringField')} FROM users`;
|
|
435
436
|
|
|
436
437
|
expect(fragment.sql).toBe('SELECT ?? FROM users');
|
|
437
438
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
438
439
|
});
|
|
439
440
|
|
|
440
441
|
it('translates entity field name to database column name via getKnexBindings', () => {
|
|
441
|
-
const fragment = sql`WHERE ${entityField
|
|
442
|
+
const fragment = sql`WHERE ${entityField('intField')} = ${42}`;
|
|
442
443
|
|
|
443
444
|
expect(fragment.sql).toBe('WHERE ?? = ?');
|
|
444
445
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 42]);
|
|
445
446
|
});
|
|
446
447
|
|
|
447
448
|
it('translates the id field correctly', () => {
|
|
448
|
-
const fragment = sql`WHERE ${entityField
|
|
449
|
+
const fragment = sql`WHERE ${entityField('customIdField')} = ${'some-id'}`;
|
|
449
450
|
|
|
450
451
|
expect(fragment.sql).toBe('WHERE ?? = ?');
|
|
451
452
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['custom_id', 'some-id']);
|
|
452
453
|
});
|
|
453
454
|
|
|
454
455
|
it('works alongside identifiers and values', () => {
|
|
455
|
-
const fragment = sql`SELECT ${identifier('table_name')}.${entityField
|
|
456
|
+
const fragment = sql`SELECT ${identifier('table_name')}.${entityField('stringField')} WHERE ${entityField('intField')} > ${10}`;
|
|
456
457
|
|
|
457
458
|
expect(fragment.sql).toBe('SELECT ??.?? WHERE ?? > ?');
|
|
458
459
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -464,7 +465,7 @@ describe('SQLOperator', () => {
|
|
|
464
465
|
});
|
|
465
466
|
|
|
466
467
|
it('works with multiple entity fields', () => {
|
|
467
|
-
const fragment = sql`SELECT ${entityField
|
|
468
|
+
const fragment = sql`SELECT ${entityField('stringField')}, ${entityField('intField')}, ${entityField('dateField')} FROM test`;
|
|
468
469
|
|
|
469
470
|
expect(fragment.sql).toBe('SELECT ??, ??, ?? FROM test');
|
|
470
471
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -475,7 +476,7 @@ describe('SQLOperator', () => {
|
|
|
475
476
|
});
|
|
476
477
|
|
|
477
478
|
it('works in nested SQL fragments', () => {
|
|
478
|
-
const inner = sql`${entityField
|
|
479
|
+
const inner = sql`${entityField('stringField')} = ${'hello'}`;
|
|
479
480
|
const outer = sql`SELECT * FROM test WHERE ${inner}`;
|
|
480
481
|
|
|
481
482
|
expect(outer.sql).toBe('SELECT * FROM test WHERE ?? = ?');
|
|
@@ -483,10 +484,10 @@ describe('SQLOperator', () => {
|
|
|
483
484
|
});
|
|
484
485
|
});
|
|
485
486
|
|
|
486
|
-
describe('
|
|
487
|
-
describe(
|
|
487
|
+
describe('SQLExpression', () => {
|
|
488
|
+
describe(SQLExpression.inArray, () => {
|
|
488
489
|
it('generates IN clause with values', () => {
|
|
489
|
-
const fragment =
|
|
490
|
+
const fragment = SQLExpression.inArray('stringField', ['active', 'pending']);
|
|
490
491
|
|
|
491
492
|
expect(fragment.sql).toBe('?? IN (?, ?)');
|
|
492
493
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -497,16 +498,34 @@ describe('SQLOperator', () => {
|
|
|
497
498
|
});
|
|
498
499
|
|
|
499
500
|
it('handles empty array', () => {
|
|
500
|
-
const fragment =
|
|
501
|
+
const fragment = SQLExpression.inArray('stringField', []);
|
|
501
502
|
|
|
502
503
|
expect(fragment.sql).toBe('FALSE'); // Always false
|
|
503
504
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
|
|
504
505
|
});
|
|
506
|
+
|
|
507
|
+
it('accepts a SQLFragment expression', () => {
|
|
508
|
+
const fragment = SQLExpression.inArray(sql<TestFields>`${entityField('stringField')}`, [
|
|
509
|
+
'a',
|
|
510
|
+
'b',
|
|
511
|
+
]);
|
|
512
|
+
expect(fragment.sql).toBe('?? IN (?, ?)');
|
|
513
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'a', 'b']);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('accepts a SQLChainableFragment and constrains value type', () => {
|
|
517
|
+
const fragment = SQLExpression.inArray(
|
|
518
|
+
SQLExpression.trim<TestFields, 'stringField'>('stringField'),
|
|
519
|
+
['a', 'b'],
|
|
520
|
+
);
|
|
521
|
+
expect(fragment.sql).toBe('TRIM(??) IN (?, ?)');
|
|
522
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'a', 'b']);
|
|
523
|
+
});
|
|
505
524
|
});
|
|
506
525
|
|
|
507
|
-
describe(
|
|
526
|
+
describe(SQLExpression.notInArray, () => {
|
|
508
527
|
it('generates NOT IN clause with values', () => {
|
|
509
|
-
const fragment =
|
|
528
|
+
const fragment = SQLExpression.notInArray('stringField', ['deleted', 'archived']);
|
|
510
529
|
|
|
511
530
|
expect(fragment.sql).toBe('?? NOT IN (?, ?)');
|
|
512
531
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -517,16 +536,24 @@ describe('SQLOperator', () => {
|
|
|
517
536
|
});
|
|
518
537
|
|
|
519
538
|
it('handles empty array', () => {
|
|
520
|
-
const fragment =
|
|
539
|
+
const fragment = SQLExpression.notInArray('stringField', []);
|
|
521
540
|
|
|
522
541
|
expect(fragment.sql).toBe('TRUE'); // Always true
|
|
523
542
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
|
|
524
543
|
});
|
|
544
|
+
|
|
545
|
+
it('accepts a SQLFragment expression', () => {
|
|
546
|
+
const fragment = SQLExpression.notInArray(sql<TestFields>`${entityField('stringField')}`, [
|
|
547
|
+
'x',
|
|
548
|
+
]);
|
|
549
|
+
expect(fragment.sql).toBe('?? NOT IN (?)');
|
|
550
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'x']);
|
|
551
|
+
});
|
|
525
552
|
});
|
|
526
553
|
|
|
527
|
-
describe(
|
|
554
|
+
describe(SQLExpression.anyArray, () => {
|
|
528
555
|
it('generates = ANY() clause with values', () => {
|
|
529
|
-
const fragment =
|
|
556
|
+
const fragment = SQLExpression.anyArray('stringField', ['active', 'pending']);
|
|
530
557
|
|
|
531
558
|
expect(fragment.sql).toBe('?? = ANY(?)');
|
|
532
559
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -536,16 +563,25 @@ describe('SQLOperator', () => {
|
|
|
536
563
|
});
|
|
537
564
|
|
|
538
565
|
it('handles empty array', () => {
|
|
539
|
-
const fragment =
|
|
566
|
+
const fragment = SQLExpression.anyArray('stringField', []);
|
|
540
567
|
|
|
541
568
|
expect(fragment.sql).toBe('FALSE'); // Always false
|
|
542
569
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
|
|
543
570
|
});
|
|
571
|
+
|
|
572
|
+
it('accepts a SQLFragment expression', () => {
|
|
573
|
+
const fragment = SQLExpression.anyArray(sql<TestFields>`${entityField('stringField')}`, [
|
|
574
|
+
'a',
|
|
575
|
+
'b',
|
|
576
|
+
]);
|
|
577
|
+
expect(fragment.sql).toBe('?? = ANY(?)');
|
|
578
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', ['a', 'b']]);
|
|
579
|
+
});
|
|
544
580
|
});
|
|
545
581
|
|
|
546
|
-
describe(
|
|
582
|
+
describe(SQLExpression.between, () => {
|
|
547
583
|
it('generates BETWEEN clause with numbers', () => {
|
|
548
|
-
const fragment =
|
|
584
|
+
const fragment = SQLExpression.between('intField', 18, 65);
|
|
549
585
|
|
|
550
586
|
expect(fragment.sql).toBe('?? BETWEEN ? AND ?');
|
|
551
587
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 18, 65]);
|
|
@@ -554,23 +590,29 @@ describe('SQLOperator', () => {
|
|
|
554
590
|
it('generates BETWEEN clause with dates', () => {
|
|
555
591
|
const date1 = new Date('2024-01-01');
|
|
556
592
|
const date2 = new Date('2024-12-31');
|
|
557
|
-
const fragment =
|
|
593
|
+
const fragment = SQLExpression.between('dateField', date1, date2);
|
|
558
594
|
|
|
559
595
|
expect(fragment.sql).toBe('?? BETWEEN ? AND ?');
|
|
560
596
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['date_field', date1, date2]);
|
|
561
597
|
});
|
|
562
598
|
|
|
563
599
|
it('generates BETWEEN clause with strings', () => {
|
|
564
|
-
const fragment =
|
|
600
|
+
const fragment = SQLExpression.between('stringField', 'A', 'Z');
|
|
565
601
|
|
|
566
602
|
expect(fragment.sql).toBe('?? BETWEEN ? AND ?');
|
|
567
603
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'A', 'Z']);
|
|
568
604
|
});
|
|
605
|
+
|
|
606
|
+
it('accepts a SQLFragment expression', () => {
|
|
607
|
+
const fragment = SQLExpression.between(sql<TestFields>`${entityField('intField')}`, 1, 100);
|
|
608
|
+
expect(fragment.sql).toBe('?? BETWEEN ? AND ?');
|
|
609
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 1, 100]);
|
|
610
|
+
});
|
|
569
611
|
});
|
|
570
612
|
|
|
571
|
-
describe(
|
|
613
|
+
describe(SQLExpression.notBetween, () => {
|
|
572
614
|
it('generates NOT BETWEEN clause with numbers', () => {
|
|
573
|
-
const fragment =
|
|
615
|
+
const fragment = SQLExpression.notBetween('intField', 18, 65);
|
|
574
616
|
|
|
575
617
|
expect(fragment.sql).toBe('?? NOT BETWEEN ? AND ?');
|
|
576
618
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 18, 65]);
|
|
@@ -579,35 +621,75 @@ describe('SQLOperator', () => {
|
|
|
579
621
|
it('generates NOT BETWEEN clause with dates', () => {
|
|
580
622
|
const date1 = new Date('2024-01-01');
|
|
581
623
|
const date2 = new Date('2024-12-31');
|
|
582
|
-
const fragment =
|
|
624
|
+
const fragment = SQLExpression.notBetween('dateField', date1, date2);
|
|
583
625
|
|
|
584
626
|
expect(fragment.sql).toBe('?? NOT BETWEEN ? AND ?');
|
|
585
627
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['date_field', date1, date2]);
|
|
586
628
|
});
|
|
629
|
+
|
|
630
|
+
it('accepts a SQLFragment expression', () => {
|
|
631
|
+
const fragment = SQLExpression.notBetween(
|
|
632
|
+
sql<TestFields>`${entityField('intField')}`,
|
|
633
|
+
1,
|
|
634
|
+
100,
|
|
635
|
+
);
|
|
636
|
+
expect(fragment.sql).toBe('?? NOT BETWEEN ? AND ?');
|
|
637
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 1, 100]);
|
|
638
|
+
});
|
|
587
639
|
});
|
|
588
640
|
|
|
589
|
-
describe(
|
|
641
|
+
describe(SQLExpression.like, () => {
|
|
590
642
|
it('generates LIKE clause', () => {
|
|
591
|
-
const fragment =
|
|
643
|
+
const fragment = SQLExpression.like('stringField', '%John%');
|
|
592
644
|
|
|
593
645
|
expect(fragment.sql).toBe('?? LIKE ?');
|
|
594
646
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%John%']);
|
|
595
647
|
});
|
|
648
|
+
|
|
649
|
+
it('accepts a SQLFragment expression', () => {
|
|
650
|
+
const fragment = SQLExpression.like(
|
|
651
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
652
|
+
'%John%',
|
|
653
|
+
);
|
|
654
|
+
expect(fragment.sql).toBe('?? LIKE ?');
|
|
655
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%John%']);
|
|
656
|
+
});
|
|
596
657
|
});
|
|
597
658
|
|
|
598
|
-
describe(
|
|
659
|
+
describe(SQLExpression.notLike, () => {
|
|
599
660
|
it('generates NOT LIKE clause', () => {
|
|
600
|
-
const fragment =
|
|
661
|
+
const fragment = SQLExpression.notLike('stringField', '%test%');
|
|
601
662
|
|
|
602
663
|
expect(fragment.sql).toBe('?? NOT LIKE ?');
|
|
603
664
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
|
|
604
665
|
});
|
|
666
|
+
|
|
667
|
+
it('accepts a SQLFragment expression', () => {
|
|
668
|
+
const fragment = SQLExpression.notLike(
|
|
669
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
670
|
+
'%test%',
|
|
671
|
+
);
|
|
672
|
+
expect(fragment.sql).toBe('?? NOT LIKE ?');
|
|
673
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
|
|
674
|
+
});
|
|
605
675
|
});
|
|
606
676
|
|
|
607
|
-
describe(
|
|
677
|
+
describe(SQLExpression.ilike, () => {
|
|
608
678
|
it('generates ILIKE clause for case-insensitive matching', () => {
|
|
609
|
-
const fragment =
|
|
679
|
+
const fragment = SQLExpression.ilike('testIndexedField', '%@example.com');
|
|
680
|
+
|
|
681
|
+
expect(fragment.sql).toBe('?? ILIKE ?');
|
|
682
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
683
|
+
'test_index',
|
|
684
|
+
'%@example.com',
|
|
685
|
+
]);
|
|
686
|
+
});
|
|
610
687
|
|
|
688
|
+
it('accepts a SQLFragment expression', () => {
|
|
689
|
+
const fragment = SQLExpression.ilike(
|
|
690
|
+
sql<TestFields>`${entityField('testIndexedField')}`,
|
|
691
|
+
'%@example.com',
|
|
692
|
+
);
|
|
611
693
|
expect(fragment.sql).toBe('?? ILIKE ?');
|
|
612
694
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
613
695
|
'test_index',
|
|
@@ -616,118 +698,189 @@ describe('SQLOperator', () => {
|
|
|
616
698
|
});
|
|
617
699
|
});
|
|
618
700
|
|
|
619
|
-
describe(
|
|
701
|
+
describe(SQLExpression.notIlike, () => {
|
|
620
702
|
it('generates NOT ILIKE clause for case-insensitive non-matching', () => {
|
|
621
|
-
const fragment =
|
|
703
|
+
const fragment = SQLExpression.notIlike('testIndexedField', '%@spam.com');
|
|
622
704
|
|
|
623
705
|
expect(fragment.sql).toBe('?? NOT ILIKE ?');
|
|
624
706
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['test_index', '%@spam.com']);
|
|
625
707
|
});
|
|
708
|
+
|
|
709
|
+
it('accepts a SQLFragment expression', () => {
|
|
710
|
+
const fragment = SQLExpression.notIlike(
|
|
711
|
+
sql<TestFields>`${entityField('testIndexedField')}`,
|
|
712
|
+
'%@spam.com',
|
|
713
|
+
);
|
|
714
|
+
expect(fragment.sql).toBe('?? NOT ILIKE ?');
|
|
715
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['test_index', '%@spam.com']);
|
|
716
|
+
});
|
|
626
717
|
});
|
|
627
718
|
|
|
628
|
-
describe(
|
|
719
|
+
describe(SQLExpression.isNull, () => {
|
|
629
720
|
it('generates IS NULL', () => {
|
|
630
|
-
const fragment =
|
|
721
|
+
const fragment = SQLExpression.isNull('nullableField');
|
|
722
|
+
|
|
723
|
+
expect(fragment.sql).toBe('?? IS NULL');
|
|
724
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
725
|
+
});
|
|
631
726
|
|
|
727
|
+
it('accepts a SQLFragment expression', () => {
|
|
728
|
+
const fragment = SQLExpression.isNull(sql<TestFields>`${entityField('nullableField')}`);
|
|
632
729
|
expect(fragment.sql).toBe('?? IS NULL');
|
|
633
730
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
634
731
|
});
|
|
635
732
|
});
|
|
636
733
|
|
|
637
|
-
describe(
|
|
734
|
+
describe(SQLExpression.isNotNull, () => {
|
|
638
735
|
it('generates IS NOT NULL', () => {
|
|
639
|
-
const fragment =
|
|
736
|
+
const fragment = SQLExpression.isNotNull('testIndexedField');
|
|
640
737
|
|
|
641
738
|
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
642
739
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['test_index']);
|
|
643
740
|
});
|
|
741
|
+
|
|
742
|
+
it('accepts a SQLFragment expression', () => {
|
|
743
|
+
const fragment = SQLExpression.isNotNull(
|
|
744
|
+
sql<TestFields>`${entityField('testIndexedField')}`,
|
|
745
|
+
);
|
|
746
|
+
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
747
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['test_index']);
|
|
748
|
+
});
|
|
644
749
|
});
|
|
645
750
|
|
|
646
|
-
describe(
|
|
751
|
+
describe(SQLExpression.eq, () => {
|
|
647
752
|
it('generates equality check', () => {
|
|
648
|
-
const fragment =
|
|
753
|
+
const fragment = SQLExpression.eq('stringField', 'active');
|
|
649
754
|
|
|
650
755
|
expect(fragment.sql).toBe('?? = ?');
|
|
651
756
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'active']);
|
|
652
757
|
});
|
|
653
758
|
|
|
654
759
|
it('handles null in equality check', () => {
|
|
655
|
-
const fragment =
|
|
760
|
+
const fragment = SQLExpression.eq('nullableField', null);
|
|
656
761
|
|
|
657
762
|
expect(fragment.sql).toBe('?? IS NULL');
|
|
658
763
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
659
764
|
});
|
|
660
765
|
|
|
661
766
|
it('handles undefined in equality check', () => {
|
|
662
|
-
const fragment =
|
|
767
|
+
const fragment = SQLExpression.eq('nullableField', undefined);
|
|
663
768
|
|
|
664
769
|
expect(fragment.sql).toBe('?? IS NULL');
|
|
665
770
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
666
771
|
});
|
|
772
|
+
|
|
773
|
+
it('accepts a SQLFragment expression', () => {
|
|
774
|
+
const fragment = SQLExpression.eq(sql<TestFields>`${entityField('stringField')}`, 'active');
|
|
775
|
+
expect(fragment.sql).toBe('?? = ?');
|
|
776
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'active']);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it('accepts a SQLChainableFragment and constrains value type', () => {
|
|
780
|
+
const fragment = SQLExpression.eq(
|
|
781
|
+
SQLExpression.trim<TestFields, 'stringField'>('stringField'),
|
|
782
|
+
'trimmed',
|
|
783
|
+
);
|
|
784
|
+
expect(fragment.sql).toBe('TRIM(??) = ?');
|
|
785
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'trimmed']);
|
|
786
|
+
});
|
|
667
787
|
});
|
|
668
788
|
|
|
669
|
-
describe(
|
|
789
|
+
describe(SQLExpression.neq, () => {
|
|
670
790
|
it('generates inequality check', () => {
|
|
671
|
-
const fragment =
|
|
791
|
+
const fragment = SQLExpression.neq('stringField', 'deleted');
|
|
672
792
|
|
|
673
793
|
expect(fragment.sql).toBe('?? != ?');
|
|
674
794
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'deleted']);
|
|
675
795
|
});
|
|
676
796
|
|
|
677
797
|
it('handles null in inequality check', () => {
|
|
678
|
-
const fragment =
|
|
798
|
+
const fragment = SQLExpression.neq('nullableField', null);
|
|
679
799
|
|
|
680
800
|
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
681
801
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
682
802
|
});
|
|
683
803
|
|
|
684
804
|
it('handles undefined in inequality check', () => {
|
|
685
|
-
const fragment =
|
|
805
|
+
const fragment = SQLExpression.neq('nullableField', undefined);
|
|
686
806
|
|
|
687
807
|
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
688
808
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['nullable_field']);
|
|
689
809
|
});
|
|
810
|
+
|
|
811
|
+
it('accepts a SQLFragment expression', () => {
|
|
812
|
+
const fragment = SQLExpression.neq(
|
|
813
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
814
|
+
'deleted',
|
|
815
|
+
);
|
|
816
|
+
expect(fragment.sql).toBe('?? != ?');
|
|
817
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'deleted']);
|
|
818
|
+
});
|
|
690
819
|
});
|
|
691
820
|
|
|
692
|
-
describe(
|
|
821
|
+
describe(SQLExpression.gt, () => {
|
|
693
822
|
it('generates greater than', () => {
|
|
694
|
-
const fragment =
|
|
823
|
+
const fragment = SQLExpression.gt('intField', 18);
|
|
824
|
+
|
|
825
|
+
expect(fragment.sql).toBe('?? > ?');
|
|
826
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 18]);
|
|
827
|
+
});
|
|
695
828
|
|
|
829
|
+
it('accepts a SQLFragment expression', () => {
|
|
830
|
+
const fragment = SQLExpression.gt(sql<TestFields>`${entityField('intField')}`, 18);
|
|
696
831
|
expect(fragment.sql).toBe('?? > ?');
|
|
697
832
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 18]);
|
|
698
833
|
});
|
|
699
834
|
});
|
|
700
835
|
|
|
701
|
-
describe(
|
|
836
|
+
describe(SQLExpression.gte, () => {
|
|
702
837
|
it('generates greater than or equal', () => {
|
|
703
|
-
const fragment =
|
|
838
|
+
const fragment = SQLExpression.gte('intField', 18);
|
|
839
|
+
|
|
840
|
+
expect(fragment.sql).toBe('?? >= ?');
|
|
841
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 18]);
|
|
842
|
+
});
|
|
704
843
|
|
|
844
|
+
it('accepts a SQLFragment expression', () => {
|
|
845
|
+
const fragment = SQLExpression.gte(sql<TestFields>`${entityField('intField')}`, 18);
|
|
705
846
|
expect(fragment.sql).toBe('?? >= ?');
|
|
706
847
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 18]);
|
|
707
848
|
});
|
|
708
849
|
});
|
|
709
850
|
|
|
710
|
-
describe(
|
|
851
|
+
describe(SQLExpression.lt, () => {
|
|
711
852
|
it('generates less than', () => {
|
|
712
|
-
const fragment =
|
|
853
|
+
const fragment = SQLExpression.lt('intField', 65);
|
|
854
|
+
|
|
855
|
+
expect(fragment.sql).toBe('?? < ?');
|
|
856
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 65]);
|
|
857
|
+
});
|
|
713
858
|
|
|
859
|
+
it('accepts a SQLFragment expression', () => {
|
|
860
|
+
const fragment = SQLExpression.lt(sql<TestFields>`${entityField('intField')}`, 65);
|
|
714
861
|
expect(fragment.sql).toBe('?? < ?');
|
|
715
862
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 65]);
|
|
716
863
|
});
|
|
717
864
|
});
|
|
718
865
|
|
|
719
|
-
describe(
|
|
866
|
+
describe(SQLExpression.lte, () => {
|
|
720
867
|
it('generates less than or equal', () => {
|
|
721
|
-
const fragment =
|
|
868
|
+
const fragment = SQLExpression.lte('intField', 65);
|
|
722
869
|
|
|
723
870
|
expect(fragment.sql).toBe('?? <= ?');
|
|
724
871
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 65]);
|
|
725
872
|
});
|
|
873
|
+
|
|
874
|
+
it('accepts a SQLFragment expression', () => {
|
|
875
|
+
const fragment = SQLExpression.lte(sql<TestFields>`${entityField('intField')}`, 65);
|
|
876
|
+
expect(fragment.sql).toBe('?? <= ?');
|
|
877
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 65]);
|
|
878
|
+
});
|
|
726
879
|
});
|
|
727
880
|
|
|
728
|
-
describe(
|
|
881
|
+
describe(SQLExpression.jsonContains, () => {
|
|
729
882
|
it('generates JSON contains', () => {
|
|
730
|
-
const fragment =
|
|
883
|
+
const fragment = SQLExpression.jsonContains('stringField', { premium: true });
|
|
731
884
|
|
|
732
885
|
expect(fragment.sql).toBe('?? @> ?::jsonb');
|
|
733
886
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
@@ -737,8 +890,8 @@ describe('SQLOperator', () => {
|
|
|
737
890
|
});
|
|
738
891
|
|
|
739
892
|
it('generates JSON contains for null and undefined values', () => {
|
|
740
|
-
const fragmentNull =
|
|
741
|
-
const fragmentUndefined =
|
|
893
|
+
const fragmentNull = SQLExpression.jsonContains('stringField', null);
|
|
894
|
+
const fragmentUndefined = SQLExpression.jsonContains('stringField', undefined);
|
|
742
895
|
|
|
743
896
|
expect(fragmentNull.sql).toBe('?? @> ?::jsonb');
|
|
744
897
|
expect(fragmentNull.getKnexBindings(getColumnForField)).toEqual(['string_field', 'null']);
|
|
@@ -751,15 +904,27 @@ describe('SQLOperator', () => {
|
|
|
751
904
|
});
|
|
752
905
|
|
|
753
906
|
it('throws when value is not JSON-serializable', () => {
|
|
754
|
-
expect(() =>
|
|
907
|
+
expect(() => SQLExpression.jsonContains('stringField', (() => {}) as any)).toThrow(
|
|
755
908
|
'jsonContains: value is not JSON-serializable',
|
|
756
909
|
);
|
|
757
910
|
});
|
|
911
|
+
|
|
912
|
+
it('accepts a SQLFragment expression', () => {
|
|
913
|
+
const fragment = SQLExpression.jsonContains(
|
|
914
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
915
|
+
{ premium: true },
|
|
916
|
+
);
|
|
917
|
+
expect(fragment.sql).toBe('?? @> ?::jsonb');
|
|
918
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
919
|
+
'string_field',
|
|
920
|
+
'{"premium":true}',
|
|
921
|
+
]);
|
|
922
|
+
});
|
|
758
923
|
});
|
|
759
924
|
|
|
760
|
-
describe(
|
|
925
|
+
describe(SQLExpression.jsonContainedBy, () => {
|
|
761
926
|
it('generates JSON contained by', () => {
|
|
762
|
-
const fragment =
|
|
927
|
+
const fragment = SQLExpression.jsonContainedBy('stringField', {
|
|
763
928
|
theme: 'dark',
|
|
764
929
|
lang: 'en',
|
|
765
930
|
});
|
|
@@ -772,8 +937,8 @@ describe('SQLOperator', () => {
|
|
|
772
937
|
});
|
|
773
938
|
|
|
774
939
|
it('generates JSON contained by for null and undefined values', () => {
|
|
775
|
-
const fragmentNull =
|
|
776
|
-
const fragmentUndefined =
|
|
940
|
+
const fragmentNull = SQLExpression.jsonContainedBy('stringField', null);
|
|
941
|
+
const fragmentUndefined = SQLExpression.jsonContainedBy('stringField', undefined);
|
|
777
942
|
|
|
778
943
|
expect(fragmentNull.sql).toBe('?? <@ ?::jsonb');
|
|
779
944
|
expect(fragmentNull.getKnexBindings(getColumnForField)).toEqual(['string_field', 'null']);
|
|
@@ -786,35 +951,65 @@ describe('SQLOperator', () => {
|
|
|
786
951
|
});
|
|
787
952
|
|
|
788
953
|
it('throws when value is not JSON-serializable', () => {
|
|
789
|
-
expect(() =>
|
|
954
|
+
expect(() => SQLExpression.jsonContainedBy('stringField', (() => {}) as any)).toThrow(
|
|
790
955
|
'jsonContainedBy: value is not JSON-serializable',
|
|
791
956
|
);
|
|
792
957
|
});
|
|
958
|
+
|
|
959
|
+
it('accepts a SQLFragment expression', () => {
|
|
960
|
+
const fragment = SQLExpression.jsonContainedBy(
|
|
961
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
962
|
+
{ theme: 'dark' },
|
|
963
|
+
);
|
|
964
|
+
expect(fragment.sql).toBe('?? <@ ?::jsonb');
|
|
965
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
966
|
+
'string_field',
|
|
967
|
+
'{"theme":"dark"}',
|
|
968
|
+
]);
|
|
969
|
+
});
|
|
793
970
|
});
|
|
794
971
|
|
|
795
|
-
describe(
|
|
972
|
+
describe(SQLExpression.jsonPath, () => {
|
|
796
973
|
it('generates JSON path access', () => {
|
|
797
|
-
const fragment =
|
|
974
|
+
const fragment = SQLExpression.jsonPath('stringField', 'user');
|
|
798
975
|
|
|
799
976
|
expect(fragment.sql).toBe(`??->?`);
|
|
800
977
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'user']);
|
|
801
978
|
});
|
|
979
|
+
|
|
980
|
+
it('accepts a SQLFragment expression', () => {
|
|
981
|
+
const fragment = SQLExpression.jsonPath(
|
|
982
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
983
|
+
'user',
|
|
984
|
+
);
|
|
985
|
+
expect(fragment.sql).toBe('??->?');
|
|
986
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'user']);
|
|
987
|
+
});
|
|
802
988
|
});
|
|
803
989
|
|
|
804
|
-
describe(
|
|
990
|
+
describe(SQLExpression.jsonPathText, () => {
|
|
805
991
|
it('generates JSON path text access', () => {
|
|
806
|
-
const fragment =
|
|
992
|
+
const fragment = SQLExpression.jsonPathText('stringField', 'email');
|
|
807
993
|
|
|
808
994
|
expect(fragment.sql).toBe(`??->>?`);
|
|
809
995
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
|
|
810
996
|
});
|
|
997
|
+
|
|
998
|
+
it('accepts a SQLFragment expression', () => {
|
|
999
|
+
const fragment = SQLExpression.jsonPathText(
|
|
1000
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
1001
|
+
'email',
|
|
1002
|
+
);
|
|
1003
|
+
expect(fragment.sql).toBe('??->>?');
|
|
1004
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
|
|
1005
|
+
});
|
|
811
1006
|
});
|
|
812
1007
|
|
|
813
|
-
describe(
|
|
1008
|
+
describe(SQLExpression.and, () => {
|
|
814
1009
|
it('combines conditions with AND', () => {
|
|
815
1010
|
const cond1 = sql`age >= ${18}`;
|
|
816
1011
|
const cond2 = sql`status = ${'active'}`;
|
|
817
|
-
const fragment =
|
|
1012
|
+
const fragment = SQLExpression.and(cond1, cond2);
|
|
818
1013
|
|
|
819
1014
|
expect(fragment.sql).toBe('(age >= ?) AND (status = ?)');
|
|
820
1015
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([18, 'active']);
|
|
@@ -822,25 +1017,25 @@ describe('SQLOperator', () => {
|
|
|
822
1017
|
|
|
823
1018
|
it('handles single condition in AND', () => {
|
|
824
1019
|
const cond = sql`age >= ${18}`;
|
|
825
|
-
const fragment =
|
|
1020
|
+
const fragment = SQLExpression.and(cond);
|
|
826
1021
|
|
|
827
1022
|
expect(fragment.sql).toBe('(age >= ?)');
|
|
828
1023
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([18]);
|
|
829
1024
|
});
|
|
830
1025
|
|
|
831
1026
|
it('handles empty conditions in AND', () => {
|
|
832
|
-
const fragment =
|
|
1027
|
+
const fragment = SQLExpression.and();
|
|
833
1028
|
|
|
834
1029
|
expect(fragment.sql).toBe('TRUE');
|
|
835
1030
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
|
|
836
1031
|
});
|
|
837
1032
|
});
|
|
838
1033
|
|
|
839
|
-
describe(
|
|
1034
|
+
describe(SQLExpression.or, () => {
|
|
840
1035
|
it('combines conditions with OR', () => {
|
|
841
1036
|
const cond1 = sql`status = ${'active'}`;
|
|
842
1037
|
const cond2 = sql`status = ${'pending'}`;
|
|
843
|
-
const fragment =
|
|
1038
|
+
const fragment = SQLExpression.or(cond1, cond2);
|
|
844
1039
|
|
|
845
1040
|
expect(fragment.sql).toBe('(status = ?) OR (status = ?)');
|
|
846
1041
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['active', 'pending']);
|
|
@@ -848,34 +1043,34 @@ describe('SQLOperator', () => {
|
|
|
848
1043
|
|
|
849
1044
|
it('handles single condition in OR', () => {
|
|
850
1045
|
const cond = sql`status = ${'active'}`;
|
|
851
|
-
const fragment =
|
|
1046
|
+
const fragment = SQLExpression.or(cond);
|
|
852
1047
|
|
|
853
1048
|
expect(fragment.sql).toBe('(status = ?)');
|
|
854
1049
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['active']);
|
|
855
1050
|
});
|
|
856
1051
|
|
|
857
1052
|
it('handles empty conditions in OR', () => {
|
|
858
|
-
const fragment =
|
|
1053
|
+
const fragment = SQLExpression.or();
|
|
859
1054
|
|
|
860
1055
|
expect(fragment.sql).toBe('FALSE');
|
|
861
1056
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([]);
|
|
862
1057
|
});
|
|
863
1058
|
});
|
|
864
1059
|
|
|
865
|
-
describe(
|
|
1060
|
+
describe(SQLExpression.not, () => {
|
|
866
1061
|
it('negates conditions with NOT', () => {
|
|
867
1062
|
const cond = sql`status = ${'deleted'}`;
|
|
868
|
-
const fragment =
|
|
1063
|
+
const fragment = SQLExpression.not(cond);
|
|
869
1064
|
|
|
870
1065
|
expect(fragment.sql).toBe('NOT (status = ?)');
|
|
871
1066
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['deleted']);
|
|
872
1067
|
});
|
|
873
1068
|
});
|
|
874
1069
|
|
|
875
|
-
describe(
|
|
1070
|
+
describe(SQLExpression.group, () => {
|
|
876
1071
|
it('groups conditions with parentheses', () => {
|
|
877
1072
|
const cond = sql`age >= ${18} AND age <= ${65}`;
|
|
878
|
-
const fragment =
|
|
1073
|
+
const fragment = SQLExpression.group(cond);
|
|
879
1074
|
|
|
880
1075
|
expect(fragment.sql).toBe('(age >= ? AND age <= ?)');
|
|
881
1076
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([18, 65]);
|
|
@@ -884,15 +1079,15 @@ describe('SQLOperator', () => {
|
|
|
884
1079
|
|
|
885
1080
|
describe('complex combinations', () => {
|
|
886
1081
|
it('builds complex queries with multiple helpers', () => {
|
|
887
|
-
const fragment =
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1082
|
+
const fragment = SQLExpression.and<TestFields>(
|
|
1083
|
+
SQLExpression.between('intField', 18, 65),
|
|
1084
|
+
SQLExpression.group(
|
|
1085
|
+
SQLExpression.or(
|
|
1086
|
+
SQLExpression.inArray('stringField', ['active', 'premium']),
|
|
892
1087
|
sql`role = ${'admin'}`,
|
|
893
1088
|
),
|
|
894
1089
|
),
|
|
895
|
-
|
|
1090
|
+
SQLExpression.isNotNull('testIndexedField'),
|
|
896
1091
|
);
|
|
897
1092
|
|
|
898
1093
|
expect(fragment.sql).toBe(
|
|
@@ -910,101 +1105,88 @@ describe('SQLOperator', () => {
|
|
|
910
1105
|
]);
|
|
911
1106
|
});
|
|
912
1107
|
});
|
|
913
|
-
|
|
914
|
-
describe(expression, () => {
|
|
915
|
-
it('wraps a field name into an SQLExpression', () => {
|
|
916
|
-
const fragment = expression<TestFields>('stringField').eq('active');
|
|
917
|
-
|
|
918
|
-
expect(fragment.sql).toBe('?? = ?');
|
|
919
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'active']);
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
it('wraps a SQLFragment into an SQLExpression', () => {
|
|
923
|
-
const raw = sql<TestFields>`LOWER(${entityField<TestFields>('stringField')})`;
|
|
924
|
-
const fragment = expression(raw).eq('test');
|
|
925
|
-
|
|
926
|
-
expect(fragment.sql).toBe('LOWER(??) = ?');
|
|
927
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'test']);
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
1108
|
});
|
|
931
1109
|
|
|
932
|
-
describe(
|
|
933
|
-
// Use
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1110
|
+
describe(SQLChainableFragment, () => {
|
|
1111
|
+
// Use direct SQLChainableFragment construction to test fluent methods in isolation,
|
|
1112
|
+
// without going through helpers like trim() or cast().
|
|
1113
|
+
const makeExpr = <TValue extends SupportedSQLValue>(
|
|
1114
|
+
fragment: SQLFragment<TestFields>,
|
|
1115
|
+
): SQLChainableFragment<TestFields, TValue> =>
|
|
1116
|
+
new SQLChainableFragment(fragment.sql, fragment.bindings);
|
|
1117
|
+
|
|
1118
|
+
const stringFieldFragment = (): SQLFragment<TestFields> => sql`${entityField('stringField')}`;
|
|
1119
|
+
const intFieldFragment = (): SQLFragment<TestFields> => sql`${entityField('intField')}`;
|
|
938
1120
|
|
|
939
1121
|
describe('comparison methods', () => {
|
|
940
1122
|
it('eq(value)', () => {
|
|
941
|
-
const fragment =
|
|
1123
|
+
const fragment = makeExpr<string>(stringFieldFragment()).eq('active');
|
|
942
1124
|
expect(fragment.sql).toBe('?? = ?');
|
|
943
1125
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'active']);
|
|
944
1126
|
});
|
|
945
1127
|
|
|
946
1128
|
it('eq(null) uses IS NULL', () => {
|
|
947
|
-
const fragment =
|
|
1129
|
+
const fragment = makeExpr<string>(stringFieldFragment()).eq(null);
|
|
948
1130
|
expect(fragment.sql).toBe('?? IS NULL');
|
|
949
1131
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
950
1132
|
});
|
|
951
1133
|
|
|
952
1134
|
it('eq(undefined) uses IS NULL', () => {
|
|
953
|
-
const fragment =
|
|
1135
|
+
const fragment = makeExpr<string>(stringFieldFragment()).eq(undefined);
|
|
954
1136
|
expect(fragment.sql).toBe('?? IS NULL');
|
|
955
1137
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
956
1138
|
});
|
|
957
1139
|
|
|
958
1140
|
it('neq(value)', () => {
|
|
959
|
-
const fragment =
|
|
1141
|
+
const fragment = makeExpr<string>(stringFieldFragment()).neq('deleted');
|
|
960
1142
|
expect(fragment.sql).toBe('?? != ?');
|
|
961
1143
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'deleted']);
|
|
962
1144
|
});
|
|
963
1145
|
|
|
964
1146
|
it('neq(null) uses IS NOT NULL', () => {
|
|
965
|
-
const fragment =
|
|
1147
|
+
const fragment = makeExpr<string>(stringFieldFragment()).neq(null);
|
|
966
1148
|
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
967
1149
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
968
1150
|
});
|
|
969
1151
|
|
|
970
1152
|
it('neq(undefined) uses IS NOT NULL', () => {
|
|
971
|
-
const fragment =
|
|
1153
|
+
const fragment = makeExpr<string>(stringFieldFragment()).neq(undefined);
|
|
972
1154
|
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
973
1155
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
974
1156
|
});
|
|
975
1157
|
|
|
976
1158
|
it('gt(value)', () => {
|
|
977
|
-
const fragment =
|
|
1159
|
+
const fragment = makeExpr<number>(intFieldFragment()).gt(10);
|
|
978
1160
|
expect(fragment.sql).toBe('?? > ?');
|
|
979
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['
|
|
1161
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 10]);
|
|
980
1162
|
});
|
|
981
1163
|
|
|
982
1164
|
it('gte(value)', () => {
|
|
983
|
-
const fragment =
|
|
1165
|
+
const fragment = makeExpr<number>(intFieldFragment()).gte(10);
|
|
984
1166
|
expect(fragment.sql).toBe('?? >= ?');
|
|
985
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['
|
|
1167
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 10]);
|
|
986
1168
|
});
|
|
987
1169
|
|
|
988
1170
|
it('lt(value)', () => {
|
|
989
|
-
const fragment =
|
|
1171
|
+
const fragment = makeExpr<number>(intFieldFragment()).lt(100);
|
|
990
1172
|
expect(fragment.sql).toBe('?? < ?');
|
|
991
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['
|
|
1173
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 100]);
|
|
992
1174
|
});
|
|
993
1175
|
|
|
994
1176
|
it('lte(value)', () => {
|
|
995
|
-
const fragment =
|
|
1177
|
+
const fragment = makeExpr<number>(intFieldFragment()).lte(100);
|
|
996
1178
|
expect(fragment.sql).toBe('?? <= ?');
|
|
997
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['
|
|
1179
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 100]);
|
|
998
1180
|
});
|
|
999
1181
|
|
|
1000
1182
|
it('isNull()', () => {
|
|
1001
|
-
const fragment =
|
|
1183
|
+
const fragment = makeExpr<string>(stringFieldFragment()).isNull();
|
|
1002
1184
|
expect(fragment.sql).toBe('?? IS NULL');
|
|
1003
1185
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1004
1186
|
});
|
|
1005
1187
|
|
|
1006
1188
|
it('isNotNull()', () => {
|
|
1007
|
-
const fragment =
|
|
1189
|
+
const fragment = makeExpr<string>(stringFieldFragment()).isNotNull();
|
|
1008
1190
|
expect(fragment.sql).toBe('?? IS NOT NULL');
|
|
1009
1191
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1010
1192
|
});
|
|
@@ -1012,25 +1194,25 @@ describe('SQLOperator', () => {
|
|
|
1012
1194
|
|
|
1013
1195
|
describe('pattern matching methods', () => {
|
|
1014
1196
|
it('like(pattern)', () => {
|
|
1015
|
-
const fragment =
|
|
1197
|
+
const fragment = makeExpr<string>(stringFieldFragment()).like('%test%');
|
|
1016
1198
|
expect(fragment.sql).toBe('?? LIKE ?');
|
|
1017
1199
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
|
|
1018
1200
|
});
|
|
1019
1201
|
|
|
1020
1202
|
it('notLike(pattern)', () => {
|
|
1021
|
-
const fragment =
|
|
1203
|
+
const fragment = makeExpr<string>(stringFieldFragment()).notLike('%test%');
|
|
1022
1204
|
expect(fragment.sql).toBe('?? NOT LIKE ?');
|
|
1023
1205
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
|
|
1024
1206
|
});
|
|
1025
1207
|
|
|
1026
1208
|
it('ilike(pattern)', () => {
|
|
1027
|
-
const fragment =
|
|
1209
|
+
const fragment = makeExpr<string>(stringFieldFragment()).ilike('%test%');
|
|
1028
1210
|
expect(fragment.sql).toBe('?? ILIKE ?');
|
|
1029
1211
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
|
|
1030
1212
|
});
|
|
1031
1213
|
|
|
1032
1214
|
it('notIlike(pattern)', () => {
|
|
1033
|
-
const fragment =
|
|
1215
|
+
const fragment = makeExpr<string>(stringFieldFragment()).notIlike('%test%');
|
|
1034
1216
|
expect(fragment.sql).toBe('?? NOT ILIKE ?');
|
|
1035
1217
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '%test%']);
|
|
1036
1218
|
});
|
|
@@ -1038,75 +1220,71 @@ describe('SQLOperator', () => {
|
|
|
1038
1220
|
|
|
1039
1221
|
describe('collection methods', () => {
|
|
1040
1222
|
it('inArray(values)', () => {
|
|
1041
|
-
const fragment =
|
|
1223
|
+
const fragment = makeExpr<string>(stringFieldFragment()).inArray(['a', 'b']);
|
|
1042
1224
|
expect(fragment.sql).toBe('?? IN (?, ?)');
|
|
1043
1225
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'a', 'b']);
|
|
1044
1226
|
});
|
|
1045
1227
|
|
|
1046
1228
|
it('inArray([]) returns always-false', () => {
|
|
1047
|
-
const fragment =
|
|
1229
|
+
const fragment = makeExpr<string>(stringFieldFragment()).inArray([]);
|
|
1048
1230
|
expect(fragment.sql).toBe('FALSE');
|
|
1049
1231
|
});
|
|
1050
1232
|
|
|
1051
1233
|
it('notInArray(values)', () => {
|
|
1052
|
-
const fragment =
|
|
1234
|
+
const fragment = makeExpr<string>(stringFieldFragment()).notInArray(['x']);
|
|
1053
1235
|
expect(fragment.sql).toBe('?? NOT IN (?)');
|
|
1054
1236
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'x']);
|
|
1055
1237
|
});
|
|
1056
1238
|
|
|
1057
1239
|
it('notInArray([]) returns always-true', () => {
|
|
1058
|
-
const fragment =
|
|
1240
|
+
const fragment = makeExpr<string>(stringFieldFragment()).notInArray([]);
|
|
1059
1241
|
expect(fragment.sql).toBe('TRUE');
|
|
1060
1242
|
});
|
|
1061
1243
|
|
|
1062
1244
|
it('anyArray(values)', () => {
|
|
1063
|
-
const fragment =
|
|
1245
|
+
const fragment = makeExpr<string>(stringFieldFragment()).anyArray(['a', 'b']);
|
|
1064
1246
|
expect(fragment.sql).toBe('?? = ANY(?)');
|
|
1065
1247
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', ['a', 'b']]);
|
|
1066
1248
|
});
|
|
1067
1249
|
|
|
1068
1250
|
it('anyArray([]) returns always-false', () => {
|
|
1069
|
-
const fragment =
|
|
1251
|
+
const fragment = makeExpr<string>(stringFieldFragment()).anyArray([]);
|
|
1070
1252
|
expect(fragment.sql).toBe('FALSE');
|
|
1071
1253
|
});
|
|
1072
1254
|
});
|
|
1073
1255
|
|
|
1074
1256
|
describe('range methods', () => {
|
|
1075
1257
|
it('between(min, max)', () => {
|
|
1076
|
-
const fragment =
|
|
1258
|
+
const fragment = makeExpr<number>(intFieldFragment()).between(1, 100);
|
|
1077
1259
|
expect(fragment.sql).toBe('?? BETWEEN ? AND ?');
|
|
1078
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['
|
|
1260
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 1, 100]);
|
|
1079
1261
|
});
|
|
1080
1262
|
|
|
1081
1263
|
it('notBetween(min, max)', () => {
|
|
1082
|
-
const fragment =
|
|
1264
|
+
const fragment = makeExpr<number>(intFieldFragment()).notBetween(1, 100);
|
|
1083
1265
|
expect(fragment.sql).toBe('?? NOT BETWEEN ? AND ?');
|
|
1084
|
-
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['
|
|
1266
|
+
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['number_field', 1, 100]);
|
|
1085
1267
|
});
|
|
1086
1268
|
});
|
|
1087
1269
|
|
|
1088
|
-
describe('helpers that return
|
|
1089
|
-
it('jsonPath returns an
|
|
1090
|
-
const expr =
|
|
1091
|
-
expect(expr).toBeInstanceOf(
|
|
1270
|
+
describe('helpers that return SQLChainableFragment', () => {
|
|
1271
|
+
it('jsonPath returns an SQLChainableFragment with correct base SQL', () => {
|
|
1272
|
+
const expr = SQLExpression.jsonPath('stringField', 'key');
|
|
1273
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1092
1274
|
expect(expr.sql).toBe('??->?');
|
|
1093
1275
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', 'key']);
|
|
1094
1276
|
});
|
|
1095
1277
|
|
|
1096
|
-
it('jsonPathText returns an
|
|
1097
|
-
const expr =
|
|
1098
|
-
expect(expr).toBeInstanceOf(
|
|
1278
|
+
it('jsonPathText returns an SQLChainableFragment with correct base SQL', () => {
|
|
1279
|
+
const expr = SQLExpression.jsonPathText('stringField', 'email');
|
|
1280
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1099
1281
|
expect(expr.sql).toBe('??->>?');
|
|
1100
1282
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
|
|
1101
1283
|
});
|
|
1102
1284
|
|
|
1103
|
-
it('jsonDeepPath returns an
|
|
1104
|
-
const expr =
|
|
1105
|
-
|
|
1106
|
-
'address',
|
|
1107
|
-
'city',
|
|
1108
|
-
]);
|
|
1109
|
-
expect(expr).toBeInstanceOf(SQLExpression);
|
|
1285
|
+
it('jsonDeepPath returns an SQLChainableFragment with correct base SQL', () => {
|
|
1286
|
+
const expr = SQLExpression.jsonDeepPath('stringField', ['user', 'address', 'city']);
|
|
1287
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1110
1288
|
expect(expr.sql).toBe('?? #> ?');
|
|
1111
1289
|
expect(expr.getKnexBindings(getColumnForField)).toEqual([
|
|
1112
1290
|
'string_field',
|
|
@@ -1115,11 +1293,7 @@ describe('SQLOperator', () => {
|
|
|
1115
1293
|
});
|
|
1116
1294
|
|
|
1117
1295
|
it('jsonDeepPath properly quotes path elements with special characters', () => {
|
|
1118
|
-
const fragment =
|
|
1119
|
-
'user',
|
|
1120
|
-
'first,last',
|
|
1121
|
-
'na}me',
|
|
1122
|
-
]);
|
|
1296
|
+
const fragment = SQLExpression.jsonDeepPath('stringField', ['user', 'first,last', 'na}me']);
|
|
1123
1297
|
|
|
1124
1298
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual([
|
|
1125
1299
|
'string_field',
|
|
@@ -1128,13 +1302,13 @@ describe('SQLOperator', () => {
|
|
|
1128
1302
|
});
|
|
1129
1303
|
|
|
1130
1304
|
it('jsonDeepPath properly quotes empty path elements', () => {
|
|
1131
|
-
const fragment =
|
|
1305
|
+
const fragment = SQLExpression.jsonDeepPath('stringField', ['user', '']);
|
|
1132
1306
|
|
|
1133
1307
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', '{user,""}']);
|
|
1134
1308
|
});
|
|
1135
1309
|
|
|
1136
1310
|
it('jsonDeepPath properly escapes quotes and backslashes in path elements', () => {
|
|
1137
|
-
const fragment =
|
|
1311
|
+
const fragment = SQLExpression.jsonDeepPath('stringField', [
|
|
1138
1312
|
'key"with"quotes',
|
|
1139
1313
|
'back\\slash',
|
|
1140
1314
|
]);
|
|
@@ -1145,13 +1319,19 @@ describe('SQLOperator', () => {
|
|
|
1145
1319
|
]);
|
|
1146
1320
|
});
|
|
1147
1321
|
|
|
1148
|
-
it('
|
|
1149
|
-
const expr =
|
|
1322
|
+
it('jsonDeepPath accepts a SQLFragment expression', () => {
|
|
1323
|
+
const expr = SQLExpression.jsonDeepPath(sql<TestFields>`${entityField('stringField')}`, [
|
|
1150
1324
|
'user',
|
|
1151
|
-
'address',
|
|
1152
1325
|
'city',
|
|
1153
1326
|
]);
|
|
1154
|
-
expect(expr).toBeInstanceOf(
|
|
1327
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1328
|
+
expect(expr.sql).toBe('?? #> ?');
|
|
1329
|
+
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', '{user,city}']);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it('jsonDeepPathText returns an SQLChainableFragment with correct base SQL', () => {
|
|
1333
|
+
const expr = SQLExpression.jsonDeepPathText('stringField', ['user', 'address', 'city']);
|
|
1334
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1155
1335
|
expect(expr.sql).toBe('?? #>> ?');
|
|
1156
1336
|
expect(expr.getKnexBindings(getColumnForField)).toEqual([
|
|
1157
1337
|
'string_field',
|
|
@@ -1159,35 +1339,52 @@ describe('SQLOperator', () => {
|
|
|
1159
1339
|
]);
|
|
1160
1340
|
});
|
|
1161
1341
|
|
|
1162
|
-
it('
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1342
|
+
it('jsonDeepPathText accepts a SQLFragment expression', () => {
|
|
1343
|
+
const expr = SQLExpression.jsonDeepPathText(
|
|
1344
|
+
sql<TestFields>`${entityField('stringField')}`,
|
|
1345
|
+
['user', 'city'],
|
|
1346
|
+
);
|
|
1347
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1348
|
+
expect(expr.sql).toBe('?? #>> ?');
|
|
1349
|
+
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', '{user,city}']);
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
it('cast returns an SQLChainableFragment with correct base SQL', () => {
|
|
1353
|
+
const jsonExpr = SQLExpression.jsonPath('stringField', 'count');
|
|
1354
|
+
const expr = SQLExpression.cast(jsonExpr, 'int');
|
|
1355
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1166
1356
|
expect(expr.sql).toBe('(??->?)::int');
|
|
1167
1357
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field', 'count']);
|
|
1168
1358
|
});
|
|
1169
1359
|
|
|
1360
|
+
it('cast accepts a field name', () => {
|
|
1361
|
+
const expr = SQLExpression.cast('intField', 'text');
|
|
1362
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1363
|
+
expect(expr.sql).toBe('(??)::text');
|
|
1364
|
+
expect(expr.getKnexBindings(getColumnForField)).toEqual(['number_field']);
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1170
1367
|
it('cast rejects unsupported type names', () => {
|
|
1171
|
-
const expr =
|
|
1172
|
-
expect(() =>
|
|
1368
|
+
const expr = SQLExpression.jsonPath('stringField', 'count');
|
|
1369
|
+
expect(() => SQLExpression.cast(expr, 'int; DROP TABLE users' as any)).toThrow(
|
|
1173
1370
|
'cast: unsupported type name',
|
|
1174
1371
|
);
|
|
1175
1372
|
});
|
|
1176
1373
|
|
|
1177
|
-
it('coalesce returns an
|
|
1178
|
-
const expr =
|
|
1179
|
-
sql`${entityField
|
|
1374
|
+
it('coalesce returns an SQLChainableFragment with correct base SQL', () => {
|
|
1375
|
+
const expr = SQLExpression.coalesce<TestFields>(
|
|
1376
|
+
sql`${entityField('nullableField')}`,
|
|
1180
1377
|
'default',
|
|
1181
1378
|
);
|
|
1182
|
-
expect(expr).toBeInstanceOf(
|
|
1379
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1183
1380
|
expect(expr.sql).toBe('COALESCE(??, ?)');
|
|
1184
1381
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['nullable_field', 'default']);
|
|
1185
1382
|
});
|
|
1186
1383
|
|
|
1187
1384
|
it('coalesce with multiple expressions', () => {
|
|
1188
|
-
const fragment =
|
|
1189
|
-
sql`${entityField
|
|
1190
|
-
sql`${entityField
|
|
1385
|
+
const fragment = SQLExpression.coalesce<TestFields>(
|
|
1386
|
+
sql`${entityField('nullableField')}`,
|
|
1387
|
+
sql`${entityField('stringField')}`,
|
|
1191
1388
|
'fallback',
|
|
1192
1389
|
);
|
|
1193
1390
|
expect(fragment.sql).toBe('COALESCE(??, ??, ?)');
|
|
@@ -1198,53 +1395,53 @@ describe('SQLOperator', () => {
|
|
|
1198
1395
|
]);
|
|
1199
1396
|
});
|
|
1200
1397
|
|
|
1201
|
-
it('lower returns an
|
|
1202
|
-
const expr =
|
|
1203
|
-
expect(expr).toBeInstanceOf(
|
|
1398
|
+
it('lower returns an SQLChainableFragment with correct base SQL', () => {
|
|
1399
|
+
const expr = SQLExpression.lower('stringField');
|
|
1400
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1204
1401
|
expect(expr.sql).toBe('LOWER(??)');
|
|
1205
1402
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1206
1403
|
});
|
|
1207
1404
|
|
|
1208
1405
|
it('lower accepts a SQLFragment', () => {
|
|
1209
|
-
const fragment =
|
|
1210
|
-
|
|
1406
|
+
const fragment = SQLExpression.lower(
|
|
1407
|
+
SQLExpression.jsonPathText<TestFields, 'stringField'>('stringField', 'email'),
|
|
1211
1408
|
);
|
|
1212
1409
|
expect(fragment.sql).toBe('LOWER(??->>?)');
|
|
1213
1410
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
|
|
1214
1411
|
});
|
|
1215
1412
|
|
|
1216
|
-
it('upper returns an
|
|
1217
|
-
const expr =
|
|
1218
|
-
expect(expr).toBeInstanceOf(
|
|
1413
|
+
it('upper returns an SQLChainableFragment with correct base SQL', () => {
|
|
1414
|
+
const expr = SQLExpression.upper('stringField');
|
|
1415
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1219
1416
|
expect(expr.sql).toBe('UPPER(??)');
|
|
1220
1417
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1221
1418
|
});
|
|
1222
1419
|
|
|
1223
1420
|
it('upper accepts a SQLFragment', () => {
|
|
1224
|
-
const fragment =
|
|
1225
|
-
|
|
1421
|
+
const fragment = SQLExpression.upper(
|
|
1422
|
+
SQLExpression.jsonPathText<TestFields, 'stringField'>('stringField', 'email'),
|
|
1226
1423
|
);
|
|
1227
1424
|
expect(fragment.sql).toBe('UPPER(??->>?)');
|
|
1228
1425
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'email']);
|
|
1229
1426
|
});
|
|
1230
1427
|
|
|
1231
|
-
it('trim returns an
|
|
1232
|
-
const expr =
|
|
1233
|
-
expect(expr).toBeInstanceOf(
|
|
1428
|
+
it('trim returns an SQLChainableFragment with correct base SQL', () => {
|
|
1429
|
+
const expr = SQLExpression.trim('stringField');
|
|
1430
|
+
expect(expr).toBeInstanceOf(SQLChainableFragment);
|
|
1234
1431
|
expect(expr.sql).toBe('TRIM(??)');
|
|
1235
1432
|
expect(expr.getKnexBindings(getColumnForField)).toEqual(['string_field']);
|
|
1236
1433
|
});
|
|
1237
1434
|
|
|
1238
1435
|
it('trim accepts a SQLFragment', () => {
|
|
1239
|
-
const fragment =
|
|
1240
|
-
|
|
1436
|
+
const fragment = SQLExpression.trim(
|
|
1437
|
+
SQLExpression.jsonPathText<TestFields, 'stringField'>('stringField', 'name'),
|
|
1241
1438
|
);
|
|
1242
1439
|
expect(fragment.sql).toBe('TRIM(??->>?)');
|
|
1243
1440
|
expect(fragment.getKnexBindings(getColumnForField)).toEqual(['string_field', 'name']);
|
|
1244
1441
|
});
|
|
1245
1442
|
|
|
1246
|
-
it('
|
|
1247
|
-
const path =
|
|
1443
|
+
it('SQLChainableFragment still works as a SQLFragment in sql template', () => {
|
|
1444
|
+
const path = SQLExpression.jsonPath('stringField', 'key');
|
|
1248
1445
|
const fragment = sql`${path} IS NOT NULL`;
|
|
1249
1446
|
|
|
1250
1447
|
expect(fragment.sql).toBe('??->? IS NOT NULL');
|
|
@@ -1254,8 +1451,8 @@ describe('SQLOperator', () => {
|
|
|
1254
1451
|
|
|
1255
1452
|
describe('composing multiple expression helpers', () => {
|
|
1256
1453
|
it('lower(trim(field)).eq(value)', () => {
|
|
1257
|
-
const fragment =
|
|
1258
|
-
|
|
1454
|
+
const fragment = SQLExpression.lower(
|
|
1455
|
+
SQLExpression.trim(sql<TestFields>`${entityField('stringField')}`),
|
|
1259
1456
|
).eq('hello');
|
|
1260
1457
|
|
|
1261
1458
|
expect(fragment.sql).toBe('LOWER(TRIM(??)) = ?');
|
|
@@ -1263,8 +1460,8 @@ describe('SQLOperator', () => {
|
|
|
1263
1460
|
});
|
|
1264
1461
|
|
|
1265
1462
|
it('cast(jsonDeepPath(...), type).gt(value)', () => {
|
|
1266
|
-
const fragment =
|
|
1267
|
-
|
|
1463
|
+
const fragment = SQLExpression.cast(
|
|
1464
|
+
SQLExpression.jsonDeepPath<TestFields, 'stringField'>('stringField', ['stats', 'count']),
|
|
1268
1465
|
'int',
|
|
1269
1466
|
).gt(10);
|
|
1270
1467
|
|
|
@@ -1277,8 +1474,8 @@ describe('SQLOperator', () => {
|
|
|
1277
1474
|
});
|
|
1278
1475
|
|
|
1279
1476
|
it('coalesce(jsonPathText(...), default).ilike(pattern)', () => {
|
|
1280
|
-
const fragment =
|
|
1281
|
-
|
|
1477
|
+
const fragment = SQLExpression.coalesce(
|
|
1478
|
+
SQLExpression.jsonPathText<TestFields, 'stringField'>('stringField', 'name'),
|
|
1282
1479
|
'',
|
|
1283
1480
|
).ilike('%test%');
|
|
1284
1481
|
|