@simplysm/orm-common 13.0.82 → 13.0.84
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/README.md +106 -0
- package/dist/ddl/initialize.d.ts +2 -2
- package/dist/ddl/initialize.js +1 -1
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/table-ddl.d.ts +1 -1
- package/dist/exec/queryable.d.ts +115 -115
- package/dist/exec/queryable.js +68 -68
- package/dist/exec/queryable.js.map +1 -1
- package/dist/expr/expr.d.ts +248 -248
- package/dist/expr/expr.js +250 -250
- package/dist/query-builder/base/expr-renderer-base.d.ts +7 -7
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +3 -3
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +5 -5
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +7 -7
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +2 -2
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +4 -4
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +2 -2
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +4 -4
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +7 -7
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +46 -46
- package/dist/schema/factory/column-builder.js +25 -25
- package/dist/schema/factory/index-builder.d.ts +22 -22
- package/dist/schema/factory/index-builder.js +14 -14
- package/dist/schema/factory/relation-builder.d.ts +93 -93
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +37 -37
- package/dist/schema/procedure-builder.d.ts +38 -38
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +26 -26
- package/dist/schema/table-builder.d.ts +38 -38
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +29 -29
- package/dist/schema/view-builder.d.ts +26 -26
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +18 -18
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +75 -75
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +32 -32
- package/dist/types/query-def.d.ts.map +1 -1
- package/docs/db-context.md +238 -0
- package/docs/expressions.md +413 -0
- package/docs/query-builder.md +198 -0
- package/docs/queryable.md +420 -0
- package/docs/schema-builders.md +216 -0
- package/docs/types-and-utilities.md +353 -0
- package/package.json +4 -3
- package/src/ddl/initialize.ts +16 -16
- package/src/ddl/table-ddl.ts +1 -1
- package/src/exec/queryable.ts +163 -163
- package/src/expr/expr.ts +257 -257
- package/src/query-builder/base/expr-renderer-base.ts +8 -8
- package/src/query-builder/mssql/mssql-expr-renderer.ts +20 -20
- package/src/query-builder/mssql/mssql-query-builder.ts +28 -28
- package/src/query-builder/mysql/mysql-expr-renderer.ts +22 -22
- package/src/query-builder/mysql/mysql-query-builder.ts +65 -65
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +15 -15
- package/src/query-builder/postgresql/postgresql-query-builder.ts +43 -43
- package/src/query-builder/query-builder.ts +1 -1
- package/src/schema/factory/column-builder.ts +48 -48
- package/src/schema/factory/index-builder.ts +22 -22
- package/src/schema/factory/relation-builder.ts +95 -95
- package/src/schema/procedure-builder.ts +38 -38
- package/src/schema/table-builder.ts +38 -38
- package/src/schema/view-builder.ts +28 -28
- package/src/types/db.ts +41 -41
- package/src/types/expr.ts +79 -79
- package/src/types/query-def.ts +37 -37
- package/tests/ddl/basic.expected.ts +8 -8
package/src/exec/queryable.ts
CHANGED
|
@@ -407,13 +407,13 @@ export class Queryable<
|
|
|
407
407
|
*
|
|
408
408
|
* @param fn - function returning columns to sort by
|
|
409
409
|
* @param orderBy - Sort direction (ASC/DESC). Default: ASC
|
|
410
|
-
* @returns sorting
|
|
410
|
+
* @returns Queryable with sorting conditions added
|
|
411
411
|
*
|
|
412
412
|
* @example
|
|
413
413
|
* ```typescript
|
|
414
414
|
* db.user
|
|
415
|
-
* .orderBy((u) => u.name) //
|
|
416
|
-
* .orderBy((u) => u.age, "DESC") //
|
|
415
|
+
* .orderBy((u) => u.name) // name ASC
|
|
416
|
+
* .orderBy((u) => u.age, "DESC") // age DESC
|
|
417
417
|
* ```
|
|
418
418
|
*/
|
|
419
419
|
orderBy(
|
|
@@ -438,13 +438,13 @@ export class Queryable<
|
|
|
438
438
|
|
|
439
439
|
//#endregion
|
|
440
440
|
|
|
441
|
-
//#region ==========
|
|
441
|
+
//#region ========== Search - WHERE ==========
|
|
442
442
|
|
|
443
443
|
/**
|
|
444
|
-
* WHERE condition
|
|
444
|
+
* Add WHERE condition. Multiple calls are combined with AND.
|
|
445
445
|
*
|
|
446
|
-
* @param predicate -
|
|
447
|
-
* @returns
|
|
446
|
+
* @param predicate - Function returning an array of conditions
|
|
447
|
+
* @returns Queryable with conditions added
|
|
448
448
|
*
|
|
449
449
|
* @example
|
|
450
450
|
* ```typescript
|
|
@@ -471,21 +471,21 @@ export class Queryable<
|
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
/**
|
|
474
|
-
*
|
|
474
|
+
* Perform text search
|
|
475
475
|
*
|
|
476
|
-
*
|
|
477
|
-
* -
|
|
478
|
-
* -
|
|
479
|
-
* -
|
|
476
|
+
* See {@link parseSearchQuery} for search syntax
|
|
477
|
+
* - Space-separated words are OR conditions
|
|
478
|
+
* - Words starting with `+` are required includes (AND condition)
|
|
479
|
+
* - Words starting with `-` are excludes (NOT condition)
|
|
480
480
|
*
|
|
481
|
-
* @param fn -
|
|
482
|
-
* @param searchText -
|
|
483
|
-
* @returns
|
|
481
|
+
* @param fn - Function returning target columns to search
|
|
482
|
+
* @param searchText - Search text
|
|
483
|
+
* @returns Queryable with search conditions added
|
|
484
484
|
*
|
|
485
485
|
* @example
|
|
486
486
|
* ```typescript
|
|
487
487
|
* db.user()
|
|
488
|
-
* .search((u) => [u.name, u.email], "
|
|
488
|
+
* .search((u) => [u.name, u.email], "John Doe -withdrawn")
|
|
489
489
|
* ```
|
|
490
490
|
*/
|
|
491
491
|
search(
|
|
@@ -509,7 +509,7 @@ export class Queryable<
|
|
|
509
509
|
|
|
510
510
|
const conditions: WhereExprUnit[] = [];
|
|
511
511
|
|
|
512
|
-
// OR condition:
|
|
512
|
+
// OR condition: match if any pattern matches in any column
|
|
513
513
|
if (parsed.or.length === 1) {
|
|
514
514
|
const pattern = parsed.or[0];
|
|
515
515
|
const columnMatches = columns.map((col) => expr.like(expr.lower(col), pattern.toLowerCase()));
|
|
@@ -524,13 +524,13 @@ export class Queryable<
|
|
|
524
524
|
conditions.push(expr.or(orConditions));
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
-
// MUST condition:
|
|
527
|
+
// MUST condition: each pattern must match in at least one column (AND)
|
|
528
528
|
for (const pattern of parsed.must) {
|
|
529
529
|
const columnMatches = columns.map((col) => expr.like(expr.lower(col), pattern.toLowerCase()));
|
|
530
530
|
conditions.push(expr.or(columnMatches));
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
-
// NOT condition:
|
|
533
|
+
// NOT condition: must not match in any column (AND NOT)
|
|
534
534
|
for (const pattern of parsed.not) {
|
|
535
535
|
const columnMatches = columns.map((col) => expr.like(expr.lower(col), pattern.toLowerCase()));
|
|
536
536
|
conditions.push(expr.not(expr.or(columnMatches)));
|
|
@@ -545,12 +545,12 @@ export class Queryable<
|
|
|
545
545
|
|
|
546
546
|
//#endregion
|
|
547
547
|
|
|
548
|
-
//#region ==========
|
|
548
|
+
//#region ========== Group - GROUP BY / HAVING ==========
|
|
549
549
|
|
|
550
550
|
/**
|
|
551
|
-
* GROUP BY
|
|
551
|
+
* Add GROUP BY clause
|
|
552
552
|
*
|
|
553
|
-
* @param fn -
|
|
553
|
+
* @param fn - Function returning columns to group by
|
|
554
554
|
* @returns Queryable with GROUP BY applied
|
|
555
555
|
*
|
|
556
556
|
* @example
|
|
@@ -580,10 +580,10 @@ export class Queryable<
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
/**
|
|
583
|
-
* HAVING
|
|
583
|
+
* Add HAVING clause (filtering after GROUP BY)
|
|
584
584
|
*
|
|
585
|
-
* @param predicate -
|
|
586
|
-
* @returns HAVING
|
|
585
|
+
* @param predicate - Function returning an array of conditions
|
|
586
|
+
* @returns Queryable with HAVING applied
|
|
587
587
|
*
|
|
588
588
|
* @example
|
|
589
589
|
* ```typescript
|
|
@@ -618,11 +618,11 @@ export class Queryable<
|
|
|
618
618
|
//#region ========== join - JOIN / JOIN SINGLE ==========
|
|
619
619
|
|
|
620
620
|
/**
|
|
621
|
-
*
|
|
621
|
+
* Perform LEFT OUTER JOIN for 1:N relation (added as array to result)
|
|
622
622
|
*
|
|
623
|
-
* @param as -
|
|
624
|
-
* @param fn -
|
|
625
|
-
* @returns join
|
|
623
|
+
* @param as - Property name to add to result
|
|
624
|
+
* @param fn - Callback function defining join conditions
|
|
625
|
+
* @returns Queryable with join result added as array
|
|
626
626
|
*
|
|
627
627
|
* @example
|
|
628
628
|
* ```typescript
|
|
@@ -650,13 +650,13 @@ export class Queryable<
|
|
|
650
650
|
// 1. join alias Generate
|
|
651
651
|
const joinAlias = `${this.meta.as}.${as}`;
|
|
652
652
|
|
|
653
|
-
// 2. target → Queryable
|
|
653
|
+
// 2. Transform target → Queryable (pass alias)
|
|
654
654
|
const joinQr = new JoinQueryable(this.meta.db, joinAlias);
|
|
655
655
|
|
|
656
|
-
// 3. fn
|
|
656
|
+
// 3. Execute fn (returns Queryable with conditions like where added)
|
|
657
657
|
const resultQr = fn(joinQr, this.meta.columns);
|
|
658
658
|
|
|
659
|
-
// 4.
|
|
659
|
+
// 4. Add join result to new columns
|
|
660
660
|
const joinColumns = transformColumnsAlias(resultQr.meta.columns, joinAlias);
|
|
661
661
|
|
|
662
662
|
return new Queryable({
|
|
@@ -671,11 +671,11 @@ export class Queryable<
|
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
/**
|
|
674
|
-
* N:1
|
|
674
|
+
* Perform LEFT OUTER JOIN for N:1 or 1:1 relation (added as single object to result)
|
|
675
675
|
*
|
|
676
|
-
* @param as -
|
|
677
|
-
* @param fn -
|
|
678
|
-
* @returns join
|
|
676
|
+
* @param as - Property name to add to result
|
|
677
|
+
* @param fn - Callback function defining join conditions
|
|
678
|
+
* @returns Queryable with join result added as single object
|
|
679
679
|
*
|
|
680
680
|
* @example
|
|
681
681
|
* ```typescript
|
|
@@ -706,13 +706,13 @@ export class Queryable<
|
|
|
706
706
|
// 1. join alias Generate
|
|
707
707
|
const joinAlias = `${this.meta.as}.${as}`;
|
|
708
708
|
|
|
709
|
-
// 2. target → Queryable
|
|
709
|
+
// 2. Transform target → Queryable (pass alias)
|
|
710
710
|
const joinQr = new JoinQueryable(this.meta.db, joinAlias);
|
|
711
711
|
|
|
712
|
-
// 3. fn
|
|
712
|
+
// 3. Execute fn (returns Queryable with conditions like where added)
|
|
713
713
|
const resultQr = fn(joinQr, this.meta.columns);
|
|
714
714
|
|
|
715
|
-
// 4.
|
|
715
|
+
// 4. Add join result to new columns
|
|
716
716
|
const joinColumns = transformColumnsAlias(resultQr.meta.columns, joinAlias);
|
|
717
717
|
|
|
718
718
|
return new Queryable({
|
|
@@ -731,22 +731,22 @@ export class Queryable<
|
|
|
731
731
|
//#region ========== join - INCLUDE ==========
|
|
732
732
|
|
|
733
733
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
734
|
+
* Automatically JOIN related tables.
|
|
735
|
+
* Operates based on FK/FKT relations defined in TableBuilder.
|
|
736
736
|
*
|
|
737
|
-
* @param fn -
|
|
738
|
-
* @returns
|
|
739
|
-
* @throws
|
|
737
|
+
* @param fn - Function selecting relations to include (type-checked via PathProxy)
|
|
738
|
+
* @returns Queryable with JOINs added
|
|
739
|
+
* @throws Error if relation is not defined
|
|
740
740
|
*
|
|
741
741
|
* @example
|
|
742
742
|
* ```typescript
|
|
743
|
-
* //
|
|
743
|
+
* // Single relationship include
|
|
744
744
|
* db.post.include((p) => p.user)
|
|
745
745
|
*
|
|
746
|
-
* //
|
|
746
|
+
* // Nested relationship include
|
|
747
747
|
* db.post.include((p) => p.user.company)
|
|
748
748
|
*
|
|
749
|
-
* //
|
|
749
|
+
* // Multiple relationship include
|
|
750
750
|
* db.user
|
|
751
751
|
* .include((u) => u.company)
|
|
752
752
|
* .include((u) => u.posts)
|
|
@@ -784,11 +784,11 @@ export class Queryable<
|
|
|
784
784
|
const parentChain = chainParts.join(".");
|
|
785
785
|
chainParts.push(relationName);
|
|
786
786
|
|
|
787
|
-
//
|
|
787
|
+
// Prevent duplicate add if already JOINed
|
|
788
788
|
const targetAlias = `${result.meta.as}.${chainParts.join(".")}`;
|
|
789
789
|
const existingJoin = result.meta.joins?.find((j) => j.queryable.meta.as === targetAlias);
|
|
790
790
|
if (existingJoin) {
|
|
791
|
-
//
|
|
791
|
+
// Update currentTable to the existing JOIN's table and continue
|
|
792
792
|
const existingFrom = existingJoin.queryable.meta.from;
|
|
793
793
|
if (existingFrom instanceof TableBuilder) {
|
|
794
794
|
currentTable = existingFrom;
|
|
@@ -811,7 +811,7 @@ export class Queryable<
|
|
|
811
811
|
result = result.joinSingle(chainParts.join("."), (joinQr, parentCols) => {
|
|
812
812
|
const qr = joinQr.from(targetTable);
|
|
813
813
|
|
|
814
|
-
// FKT join
|
|
814
|
+
// FKT join is stored as array, so use first element if array
|
|
815
815
|
const srcColsRaw = parentChain ? parentCols[parentChain] : parentCols;
|
|
816
816
|
const srcCols = (
|
|
817
817
|
Array.isArray(srcColsRaw) ? srcColsRaw[0] : srcColsRaw
|
|
@@ -833,15 +833,15 @@ export class Queryable<
|
|
|
833
833
|
relationDef instanceof ForeignKeyTargetBuilder ||
|
|
834
834
|
relationDef instanceof RelationKeyTargetBuilder
|
|
835
835
|
) {
|
|
836
|
-
// FKT/RelationKeyTarget (1:N
|
|
836
|
+
// FKT/RelationKeyTarget (1:N or 1:1): User.posts → Post[]
|
|
837
837
|
// condition: Post.userId = User.id
|
|
838
838
|
const targetTable = relationDef.meta.targetTableFn();
|
|
839
839
|
const fkRelName = relationDef.meta.relationName;
|
|
840
840
|
const sourceFk = targetTable.meta.relations?.[fkRelName];
|
|
841
841
|
if (!(sourceFk instanceof ForeignKeyBuilder) && !(sourceFk instanceof RelationKeyBuilder)) {
|
|
842
842
|
throw new Error(
|
|
843
|
-
`'${
|
|
844
|
-
|
|
843
|
+
`'${fkRelName}' referenced by '${relationName}' ` +
|
|
844
|
+
`is not a valid ForeignKey/RelationKey in ${targetTable.meta.name} table.`,
|
|
845
845
|
);
|
|
846
846
|
}
|
|
847
847
|
const sourceTable = targetTable;
|
|
@@ -853,7 +853,7 @@ export class Queryable<
|
|
|
853
853
|
const buildJoin = (joinQr: JoinQueryable, parentCols: QueryableRecord<DataRecord>) => {
|
|
854
854
|
const qr = joinQr.from(sourceTable);
|
|
855
855
|
|
|
856
|
-
// FKT join
|
|
856
|
+
// FKT join is stored as array, so use first element if array
|
|
857
857
|
const srcColsRaw = parentChain ? parentCols[parentChain] : parentCols;
|
|
858
858
|
const srcCols = (
|
|
859
859
|
Array.isArray(srcColsRaw) ? srcColsRaw[0] : srcColsRaw
|
|
@@ -886,15 +886,15 @@ export class Queryable<
|
|
|
886
886
|
//#region ========== Subquery - WRAP / UNION ==========
|
|
887
887
|
|
|
888
888
|
/**
|
|
889
|
-
*
|
|
889
|
+
* Wrap the current Queryable as a Subquery
|
|
890
890
|
*
|
|
891
|
-
*
|
|
891
|
+
* Required when using count() after distinct() or groupBy()
|
|
892
892
|
*
|
|
893
|
-
* @returns
|
|
893
|
+
* @returns Queryable wrapped as a Subquery
|
|
894
894
|
*
|
|
895
895
|
* @example
|
|
896
896
|
* ```typescript
|
|
897
|
-
* //
|
|
897
|
+
* // Count after DISTINCT
|
|
898
898
|
* const count = await db.user()
|
|
899
899
|
* .select((u) => ({ name: u.name }))
|
|
900
900
|
* .distinct()
|
|
@@ -903,7 +903,7 @@ export class Queryable<
|
|
|
903
903
|
* ```
|
|
904
904
|
*/
|
|
905
905
|
wrap(): Queryable<TData, never> {
|
|
906
|
-
//
|
|
906
|
+
// Wrap the current Queryable as a Subquery
|
|
907
907
|
const wrapAlias = this.meta.db.getNextAlias();
|
|
908
908
|
return new Queryable({
|
|
909
909
|
db: this.meta.db,
|
|
@@ -953,18 +953,18 @@ export class Queryable<
|
|
|
953
953
|
//#region ========== recursive - WITH RECURSIVE ==========
|
|
954
954
|
|
|
955
955
|
/**
|
|
956
|
-
* recursive CTE(Common Table Expression)
|
|
956
|
+
* Generate a recursive CTE (Common Table Expression)
|
|
957
957
|
*
|
|
958
|
-
*
|
|
958
|
+
* Used for querying hierarchical data (org charts, category trees, etc.)
|
|
959
959
|
*
|
|
960
|
-
* @param fn -
|
|
961
|
-
* @returns recursive CTE
|
|
960
|
+
* @param fn - Callback function that defines the recursive part
|
|
961
|
+
* @returns Queryable with the recursive CTE applied
|
|
962
962
|
*
|
|
963
963
|
* @example
|
|
964
964
|
* ```typescript
|
|
965
|
-
* //
|
|
965
|
+
* // Query org chart hierarchy
|
|
966
966
|
* db.employee()
|
|
967
|
-
* .where((e) => [expr.null(e.managerId)]) //
|
|
967
|
+
* .where((e) => [expr.null(e.managerId)]) // Root nodes
|
|
968
968
|
* .recursive((cte) =>
|
|
969
969
|
* cte.from(Employee)
|
|
970
970
|
* .where((e) => [expr.eq(e.managerId, e.self[0].id)])
|
|
@@ -982,13 +982,13 @@ export class Queryable<
|
|
|
982
982
|
columns: transformColumnsAlias(newFroms[0].meta.columns, this.meta.as, ""),
|
|
983
983
|
});
|
|
984
984
|
}
|
|
985
|
-
//
|
|
985
|
+
// Generate dynamic CTE name
|
|
986
986
|
const cteName = this.meta.db.getNextAlias();
|
|
987
987
|
|
|
988
|
-
// 2. target
|
|
988
|
+
// 2. Transform target to Queryable (pass CTE name)
|
|
989
989
|
const cteQr = new RecursiveQueryable(this, cteName);
|
|
990
990
|
|
|
991
|
-
// 3. fn
|
|
991
|
+
// 3. Execute fn (returns Queryable with conditions like where added)
|
|
992
992
|
const resultQr = fn(cteQr);
|
|
993
993
|
|
|
994
994
|
return new Queryable({
|
|
@@ -998,7 +998,7 @@ export class Queryable<
|
|
|
998
998
|
columns: transformColumnsAlias(this.meta.columns, this.meta.as, ""),
|
|
999
999
|
with: {
|
|
1000
1000
|
name: cteName,
|
|
1001
|
-
base: this as any, // circular
|
|
1001
|
+
base: this as any, // Block circular reference type inference
|
|
1002
1002
|
recursive: resultQr,
|
|
1003
1003
|
},
|
|
1004
1004
|
});
|
|
@@ -1006,10 +1006,10 @@ export class Queryable<
|
|
|
1006
1006
|
|
|
1007
1007
|
//#endregion
|
|
1008
1008
|
|
|
1009
|
-
//#region ========== [query]
|
|
1009
|
+
//#region ========== [query] Select - SELECT ==========
|
|
1010
1010
|
|
|
1011
1011
|
/**
|
|
1012
|
-
* SELECT query
|
|
1012
|
+
* Execute a SELECT query and return the result array
|
|
1013
1013
|
*
|
|
1014
1014
|
* @returns Query result array
|
|
1015
1015
|
*
|
|
@@ -1029,10 +1029,10 @@ export class Queryable<
|
|
|
1029
1029
|
}
|
|
1030
1030
|
|
|
1031
1031
|
/**
|
|
1032
|
-
*
|
|
1032
|
+
* Return a single result (Error if more than 1)
|
|
1033
1033
|
*
|
|
1034
|
-
* @returns
|
|
1035
|
-
* @throws
|
|
1034
|
+
* @returns Single result or undefined
|
|
1035
|
+
* @throws When more than one result is returned
|
|
1036
1036
|
*
|
|
1037
1037
|
* @example
|
|
1038
1038
|
* ```typescript
|
|
@@ -1053,7 +1053,7 @@ export class Queryable<
|
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
1055
1055
|
/**
|
|
1056
|
-
* query
|
|
1056
|
+
* Return query source name (for error messages)
|
|
1057
1057
|
*/
|
|
1058
1058
|
private _getSourceName(): string {
|
|
1059
1059
|
const from = this.meta.from;
|
|
@@ -1067,9 +1067,9 @@ export class Queryable<
|
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
1069
|
/**
|
|
1070
|
-
*
|
|
1070
|
+
* Return the first result (only the first even if multiple exist)
|
|
1071
1071
|
*
|
|
1072
|
-
* @returns
|
|
1072
|
+
* @returns First result or undefined
|
|
1073
1073
|
*
|
|
1074
1074
|
* @example
|
|
1075
1075
|
* ```typescript
|
|
@@ -1084,11 +1084,11 @@ export class Queryable<
|
|
|
1084
1084
|
}
|
|
1085
1085
|
|
|
1086
1086
|
/**
|
|
1087
|
-
*
|
|
1087
|
+
* Return the number of result rows
|
|
1088
1088
|
*
|
|
1089
|
-
* @param fn -
|
|
1090
|
-
* @returns
|
|
1091
|
-
* @throws distinct()
|
|
1089
|
+
* @param fn - Function to specify the column to count (optional)
|
|
1090
|
+
* @returns Number of rows
|
|
1091
|
+
* @throws Error when called directly after distinct() or groupBy() (use wrap() first)
|
|
1092
1092
|
*
|
|
1093
1093
|
* @example
|
|
1094
1094
|
* ```typescript
|
|
@@ -1115,9 +1115,9 @@ export class Queryable<
|
|
|
1115
1115
|
}
|
|
1116
1116
|
|
|
1117
1117
|
/**
|
|
1118
|
-
*
|
|
1118
|
+
* Check whether data matching the conditions exists
|
|
1119
1119
|
*
|
|
1120
|
-
* @returns
|
|
1120
|
+
* @returns true if exists, false otherwise
|
|
1121
1121
|
*
|
|
1122
1122
|
* @example
|
|
1123
1123
|
* ```typescript
|
|
@@ -1239,7 +1239,7 @@ export class Queryable<
|
|
|
1239
1239
|
buildResultMeta(val[0], fullKey);
|
|
1240
1240
|
}
|
|
1241
1241
|
} else if (typeof val === "object") {
|
|
1242
|
-
//
|
|
1242
|
+
// Single object (N:1, 1:1 relationship)
|
|
1243
1243
|
joins[fullKey] = { isSingle: true };
|
|
1244
1244
|
buildResultMeta(val, fullKey);
|
|
1245
1245
|
}
|
|
@@ -1253,25 +1253,25 @@ export class Queryable<
|
|
|
1253
1253
|
|
|
1254
1254
|
//#endregion
|
|
1255
1255
|
|
|
1256
|
-
//#region ========== [query]
|
|
1256
|
+
//#region ========== [query] Insert - INSERT ==========
|
|
1257
1257
|
|
|
1258
1258
|
/**
|
|
1259
|
-
* INSERT query
|
|
1259
|
+
* Execute an INSERT query
|
|
1260
1260
|
*
|
|
1261
|
-
*
|
|
1261
|
+
* Automatically splits into chunks of 1000 for MSSQL's row limit
|
|
1262
1262
|
*
|
|
1263
|
-
* @param records -
|
|
1264
|
-
* @param outputColumns -
|
|
1265
|
-
* @returns outputColumns
|
|
1263
|
+
* @param records - Array of records to insert
|
|
1264
|
+
* @param outputColumns - Column name array to receive (optional)
|
|
1265
|
+
* @returns When outputColumns specified, returns array of inserted records
|
|
1266
1266
|
*
|
|
1267
1267
|
* @example
|
|
1268
1268
|
* ```typescript
|
|
1269
|
-
* //
|
|
1269
|
+
* // Simple insert
|
|
1270
1270
|
* await db.user().insert([
|
|
1271
1271
|
* { name: "Gildong Hong", email: "hong@test.com" },
|
|
1272
1272
|
* ]);
|
|
1273
1273
|
*
|
|
1274
|
-
* //
|
|
1274
|
+
* // Return ID after insert
|
|
1275
1275
|
* const [inserted] = await db.user().insert(
|
|
1276
1276
|
* [{ name: "Gildong Hong" }],
|
|
1277
1277
|
* ["id"],
|
|
@@ -1291,7 +1291,7 @@ export class Queryable<
|
|
|
1291
1291
|
return outputColumns ? [] : undefined;
|
|
1292
1292
|
}
|
|
1293
1293
|
|
|
1294
|
-
// MSSQL 1000
|
|
1294
|
+
// Split into chunks for MSSQL's 1000 row limit
|
|
1295
1295
|
const CHUNK_SIZE = 1000;
|
|
1296
1296
|
const allResults: Pick<TFrom["$inferColumns"], K>[] = [];
|
|
1297
1297
|
|
|
@@ -1314,11 +1314,11 @@ export class Queryable<
|
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
1316
|
/**
|
|
1317
|
-
*
|
|
1317
|
+
* INSERT if no data matches the WHERE condition
|
|
1318
1318
|
*
|
|
1319
|
-
* @param record -
|
|
1320
|
-
* @param outputColumns -
|
|
1321
|
-
* @returns outputColumns
|
|
1319
|
+
* @param record - Record to insert
|
|
1320
|
+
* @param outputColumns - Column name array to receive (optional)
|
|
1321
|
+
* @returns When outputColumns specified, returns the inserted record
|
|
1322
1322
|
*
|
|
1323
1323
|
* @example
|
|
1324
1324
|
* ```typescript
|
|
@@ -1347,11 +1347,11 @@ export class Queryable<
|
|
|
1347
1347
|
}
|
|
1348
1348
|
|
|
1349
1349
|
/**
|
|
1350
|
-
* INSERT INTO ... SELECT (
|
|
1350
|
+
* INSERT INTO ... SELECT (INSERT the current SELECT results into another Table)
|
|
1351
1351
|
*
|
|
1352
|
-
* @param targetTable -
|
|
1353
|
-
* @param outputColumns -
|
|
1354
|
-
* @returns outputColumns
|
|
1352
|
+
* @param targetTable - Target Table to insert into
|
|
1353
|
+
* @param outputColumns - Column name array to receive (optional)
|
|
1354
|
+
* @returns When outputColumns specified, returns array of inserted records
|
|
1355
1355
|
*
|
|
1356
1356
|
* @example
|
|
1357
1357
|
* ```typescript
|
|
@@ -1389,7 +1389,7 @@ export class Queryable<
|
|
|
1389
1389
|
const from = this.meta.from as TableBuilder<any, any> | ViewBuilder<any, any, any>;
|
|
1390
1390
|
const outputDef = this._getCudOutputDef();
|
|
1391
1391
|
|
|
1392
|
-
// AI column
|
|
1392
|
+
// Set overrideIdentity if AI column has explicit values
|
|
1393
1393
|
const overrideIdentity =
|
|
1394
1394
|
outputDef.aiColName != null &&
|
|
1395
1395
|
records.some((r) => (r as Record<string, unknown>)[outputDef.aiColName!] !== undefined);
|
|
@@ -1458,15 +1458,15 @@ export class Queryable<
|
|
|
1458
1458
|
//#region ========== [query] Modify - UPDATE / DELETE ==========
|
|
1459
1459
|
|
|
1460
1460
|
/**
|
|
1461
|
-
* UPDATE query
|
|
1461
|
+
* Execute an UPDATE query
|
|
1462
1462
|
*
|
|
1463
|
-
* @param recordFwd -
|
|
1464
|
-
* @param outputColumns -
|
|
1465
|
-
* @returns outputColumns
|
|
1463
|
+
* @param recordFwd - Function that returns the columns and values to update
|
|
1464
|
+
* @param outputColumns - Column name array to receive (optional)
|
|
1465
|
+
* @returns When outputColumns specified, returns array of updated records
|
|
1466
1466
|
*
|
|
1467
1467
|
* @example
|
|
1468
1468
|
* ```typescript
|
|
1469
|
-
* //
|
|
1469
|
+
* // Simple update
|
|
1470
1470
|
* await db.user()
|
|
1471
1471
|
* .where((u) => [expr.eq(u.id, 1)])
|
|
1472
1472
|
* .update((u) => ({
|
|
@@ -1474,7 +1474,7 @@ export class Queryable<
|
|
|
1474
1474
|
* updatedAt: expr.val("DateTime", DateTime.now()),
|
|
1475
1475
|
* }));
|
|
1476
1476
|
*
|
|
1477
|
-
* //
|
|
1477
|
+
* // Reference existing value
|
|
1478
1478
|
* await db.product()
|
|
1479
1479
|
* .update((p) => ({
|
|
1480
1480
|
* price: expr.mul(p.price, expr.val("number", 1.1)),
|
|
@@ -1503,19 +1503,19 @@ export class Queryable<
|
|
|
1503
1503
|
}
|
|
1504
1504
|
|
|
1505
1505
|
/**
|
|
1506
|
-
* DELETE query
|
|
1506
|
+
* Execute a DELETE query
|
|
1507
1507
|
*
|
|
1508
|
-
* @param outputColumns -
|
|
1509
|
-
* @returns outputColumns
|
|
1508
|
+
* @param outputColumns - Column name array to receive (optional)
|
|
1509
|
+
* @returns When outputColumns specified, returns array of deleted records
|
|
1510
1510
|
*
|
|
1511
1511
|
* @example
|
|
1512
1512
|
* ```typescript
|
|
1513
|
-
* //
|
|
1513
|
+
* // Simple delete
|
|
1514
1514
|
* await db.user()
|
|
1515
1515
|
* .where((u) => [expr.eq(u.id, 1)])
|
|
1516
1516
|
* .delete();
|
|
1517
1517
|
*
|
|
1518
|
-
* //
|
|
1518
|
+
* // Return deleted data
|
|
1519
1519
|
* const deleted = await db.user()
|
|
1520
1520
|
* .where((u) => [expr.eq(u.isExpired, true)])
|
|
1521
1521
|
* .delete(["id", "name"]);
|
|
@@ -1591,18 +1591,18 @@ export class Queryable<
|
|
|
1591
1591
|
//#region ========== [query] Modify - UPSERT ==========
|
|
1592
1592
|
|
|
1593
1593
|
/**
|
|
1594
|
-
* UPSERT (UPDATE or INSERT) query
|
|
1594
|
+
* Execute an UPSERT (UPDATE or INSERT) query
|
|
1595
1595
|
*
|
|
1596
|
-
*
|
|
1596
|
+
* UPDATE if data matching the WHERE condition exists, otherwise INSERT
|
|
1597
1597
|
*
|
|
1598
|
-
* @param updateFn -
|
|
1599
|
-
* @param insertFn -
|
|
1600
|
-
* @param outputColumns -
|
|
1601
|
-
* @returns outputColumns
|
|
1598
|
+
* @param updateFn - Function that returns the columns and values to update
|
|
1599
|
+
* @param insertFn - Function that returns the record to insert (optional, defaults to same as updateFn)
|
|
1600
|
+
* @param outputColumns - Column name array to receive (optional)
|
|
1601
|
+
* @returns When outputColumns specified, returns array of affected records
|
|
1602
1602
|
*
|
|
1603
1603
|
* @example
|
|
1604
1604
|
* ```typescript
|
|
1605
|
-
* // UPDATE/INSERT
|
|
1605
|
+
* // Same data for UPDATE/INSERT
|
|
1606
1606
|
* await db.user()
|
|
1607
1607
|
* .where((u) => [expr.eq(u.email, "test@test.com")])
|
|
1608
1608
|
* .upsert(() => ({
|
|
@@ -1610,7 +1610,7 @@ export class Queryable<
|
|
|
1610
1610
|
* email: expr.val("string", "test@test.com"),
|
|
1611
1611
|
* }));
|
|
1612
1612
|
*
|
|
1613
|
-
* // UPDATE/INSERT
|
|
1613
|
+
* // Different data for UPDATE/INSERT
|
|
1614
1614
|
* await db.user()
|
|
1615
1615
|
* .where((u) => [expr.eq(u.email, "test@test.com")])
|
|
1616
1616
|
* .upsert(
|
|
@@ -1679,14 +1679,14 @@ export class Queryable<
|
|
|
1679
1679
|
|
|
1680
1680
|
const { select: _sel, ...existsSelectQuery } = this.getSelectQueryDef();
|
|
1681
1681
|
|
|
1682
|
-
// updateRecord
|
|
1682
|
+
// Generate updateRecord
|
|
1683
1683
|
const updateQrRecord = updateRecordFn(this.meta.columns);
|
|
1684
1684
|
const updateRecord: Record<string, Expr> = {};
|
|
1685
1685
|
for (const [key, value] of Object.entries(updateQrRecord)) {
|
|
1686
1686
|
updateRecord[key] = expr.toExpr(value);
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
|
-
// insertRecord
|
|
1689
|
+
// Generate insertRecord (pass updateRecordRaw as second argument)
|
|
1690
1690
|
const insertRecordRaw = insertRecordFn(updateQrRecord);
|
|
1691
1691
|
const insertRecord = Object.fromEntries(
|
|
1692
1692
|
Object.entries(insertRecordRaw).map(([key, value]) => [key, expr.toExpr(value)]),
|
|
@@ -1713,13 +1713,13 @@ export class Queryable<
|
|
|
1713
1713
|
//#region ========== DDL Helper ==========
|
|
1714
1714
|
|
|
1715
1715
|
/**
|
|
1716
|
-
* FK constraint on/off (
|
|
1716
|
+
* FK constraint on/off (can be used within a transaction)
|
|
1717
1717
|
*/
|
|
1718
1718
|
async switchFk(enabled: boolean): Promise<void> {
|
|
1719
1719
|
const from = this.meta.from;
|
|
1720
1720
|
if (!(from instanceof TableBuilder) && !(from instanceof ViewBuilder)) {
|
|
1721
1721
|
throw new Error(
|
|
1722
|
-
"switchFk
|
|
1722
|
+
"switchFk can only be used on TableBuilder or ViewBuilder based queryables.",
|
|
1723
1723
|
);
|
|
1724
1724
|
}
|
|
1725
1725
|
await this.meta.db.switchFk(this.meta.db.getQueryDefObjectName(from), enabled);
|
|
@@ -1727,7 +1727,7 @@ export class Queryable<
|
|
|
1727
1727
|
|
|
1728
1728
|
//#endregion
|
|
1729
1729
|
|
|
1730
|
-
//#region ========== CUD
|
|
1730
|
+
//#region ========== CUD Common ==========
|
|
1731
1731
|
|
|
1732
1732
|
private _getCudOutputDef(): {
|
|
1733
1733
|
pkColNames: string[];
|
|
@@ -1762,12 +1762,12 @@ export class Queryable<
|
|
|
1762
1762
|
//#region ========== Helper Functions ==========
|
|
1763
1763
|
|
|
1764
1764
|
/**
|
|
1765
|
-
* FK column
|
|
1765
|
+
* Match FK column array with the target Table's PK and return PK column name array
|
|
1766
1766
|
*
|
|
1767
|
-
* @param fkCols - FK column
|
|
1768
|
-
* @param targetTable -
|
|
1769
|
-
* @returns
|
|
1770
|
-
* @throws FK/PK column
|
|
1767
|
+
* @param fkCols - FK column name array
|
|
1768
|
+
* @param targetTable - Target Table builder being referenced
|
|
1769
|
+
* @returns Matched PK column name array
|
|
1770
|
+
* @throws When FK/PK column count mismatch
|
|
1771
1771
|
*/
|
|
1772
1772
|
export function getMatchedPrimaryKeys(
|
|
1773
1773
|
fkCols: string[],
|
|
@@ -1776,25 +1776,25 @@ export function getMatchedPrimaryKeys(
|
|
|
1776
1776
|
const pk = targetTable.meta.primaryKey;
|
|
1777
1777
|
if (pk == null || fkCols.length !== pk.length) {
|
|
1778
1778
|
throw new Error(
|
|
1779
|
-
`FK/PK column
|
|
1779
|
+
`FK/PK column count mismatch (target: ${targetTable.meta.name}, FK: ${fkCols.length}, PK: ${pk?.length ?? 0})`,
|
|
1780
1780
|
);
|
|
1781
1781
|
}
|
|
1782
1782
|
return pk;
|
|
1783
1783
|
}
|
|
1784
1784
|
|
|
1785
1785
|
/**
|
|
1786
|
-
*
|
|
1786
|
+
* Common helper to transform nested columns structure to a new alias
|
|
1787
1787
|
*
|
|
1788
|
-
* Subquery/JOIN
|
|
1789
|
-
*
|
|
1788
|
+
* When wrapping as Subquery/JOIN, transforms existing alias to new alias while
|
|
1789
|
+
* keeping nested keys (posts.userId) as flattened keys.
|
|
1790
1790
|
*
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
1791
|
+
* e.g.: If the path of posts[0].userId column is ["T1.posts", "userId"],
|
|
1792
|
+
* transforming to new alias "T2" yields ["T2", "posts.userId"].
|
|
1793
1793
|
*
|
|
1794
|
-
* @param columns -
|
|
1795
|
-
* @param alias -
|
|
1796
|
-
* @param keyPrefix -
|
|
1797
|
-
* @returns
|
|
1794
|
+
* @param columns - Column record to transform
|
|
1795
|
+
* @param alias - New Table alias (e.g., "T2")
|
|
1796
|
+
* @param keyPrefix - Current nested path (for recursive calls, default "")
|
|
1797
|
+
* @returns Transformed column record
|
|
1798
1798
|
*/
|
|
1799
1799
|
function transformColumnsAlias<TRecord extends DataRecord>(
|
|
1800
1800
|
columns: QueryableRecord<TRecord>,
|
|
@@ -1898,9 +1898,9 @@ export type NullableQueryableRecord<TData extends DataRecord> = {
|
|
|
1898
1898
|
};
|
|
1899
1899
|
|
|
1900
1900
|
/**
|
|
1901
|
-
* QueryableRecord
|
|
1901
|
+
* Reverse-transform from QueryableRecord to DataRecord
|
|
1902
1902
|
*
|
|
1903
|
-
* ExprUnit<T
|
|
1903
|
+
* Unwraps ExprUnit<T> to T, recursively unwrapping nested objects/arrays
|
|
1904
1904
|
*/
|
|
1905
1905
|
export type UnwrapQueryableRecord<R> = {
|
|
1906
1906
|
[K in keyof R]: R[K] extends ExprUnit<infer T>
|
|
@@ -1914,30 +1914,30 @@ export type UnwrapQueryableRecord<R> = {
|
|
|
1914
1914
|
: never;
|
|
1915
1915
|
};
|
|
1916
1916
|
|
|
1917
|
-
//#region ========== PathProxy -
|
|
1917
|
+
//#region ========== PathProxy - Type-safe path builder for include ==========
|
|
1918
1918
|
|
|
1919
1919
|
/**
|
|
1920
|
-
*
|
|
1921
|
-
* ColumnPrimitive
|
|
1920
|
+
* Proxy type for specifying relationship paths in include() in a type-safe manner
|
|
1921
|
+
* Only non-ColumnPrimitive fields (FK, FKT relationships) are accessible
|
|
1922
1922
|
*
|
|
1923
1923
|
* @example
|
|
1924
1924
|
* ```typescript
|
|
1925
|
-
* // item.user.company
|
|
1925
|
+
* // Accessing item.user.company internally collects path ["user", "company"]
|
|
1926
1926
|
* db.post.include(item => item.user.company)
|
|
1927
1927
|
*
|
|
1928
|
-
* // item.title
|
|
1929
|
-
* db.post.include(item => item.title) //
|
|
1928
|
+
* // item.title is string (ColumnPrimitive), so this is a compile error
|
|
1929
|
+
* db.post.include(item => item.title) // compile error
|
|
1930
1930
|
* ```
|
|
1931
1931
|
*/
|
|
1932
1932
|
/**
|
|
1933
|
-
*
|
|
1933
|
+
* Extract element type if array
|
|
1934
1934
|
*/
|
|
1935
1935
|
type UnwrapArray<TArray> = TArray extends (infer TElement)[] ? TElement : TArray;
|
|
1936
1936
|
|
|
1937
1937
|
const PATH_SYMBOL = Symbol("path");
|
|
1938
1938
|
|
|
1939
1939
|
/**
|
|
1940
|
-
*
|
|
1940
|
+
* Type-safe path proxy for include()
|
|
1941
1941
|
*/
|
|
1942
1942
|
export type PathProxy<TObject> = {
|
|
1943
1943
|
[K in keyof TObject as TObject[K] extends ColumnPrimitive ? never : K]-?: PathProxy<
|
|
@@ -1946,8 +1946,8 @@ export type PathProxy<TObject> = {
|
|
|
1946
1946
|
} & { readonly [PATH_SYMBOL]: string[] };
|
|
1947
1947
|
|
|
1948
1948
|
/**
|
|
1949
|
-
* PathProxy instance
|
|
1950
|
-
* Proxy
|
|
1949
|
+
* Generate PathProxy instance
|
|
1950
|
+
* Uses Proxy to intercept property access and collect paths
|
|
1951
1951
|
*/
|
|
1952
1952
|
function createPathProxy<TObject>(path: string[] = []): PathProxy<TObject> {
|
|
1953
1953
|
return new Proxy({} as PathProxy<TObject>, {
|
|
@@ -1962,22 +1962,22 @@ function createPathProxy<TObject>(path: string[] = []): PathProxy<TObject> {
|
|
|
1962
1962
|
//#endregion
|
|
1963
1963
|
|
|
1964
1964
|
/**
|
|
1965
|
-
*
|
|
1965
|
+
* Generate a Queryable factory function for a Table or View
|
|
1966
1966
|
*
|
|
1967
|
-
*
|
|
1967
|
+
* Used when defining per-Table/View getters in DbContext
|
|
1968
1968
|
*
|
|
1969
1969
|
* @param db - DbContext instance
|
|
1970
|
-
* @param tableOrView - TableBuilder
|
|
1971
|
-
* @param as -
|
|
1972
|
-
* @returns
|
|
1970
|
+
* @param tableOrView - TableBuilder or ViewBuilder instance
|
|
1971
|
+
* @param as - Alias specification (optional, auto-created if not specified)
|
|
1972
|
+
* @returns Factory function that returns a Queryable
|
|
1973
1973
|
*
|
|
1974
1974
|
* @example
|
|
1975
1975
|
* ```typescript
|
|
1976
1976
|
* class AppDbContext extends DbContext {
|
|
1977
|
-
* //
|
|
1977
|
+
* // A new alias is assigned on each call
|
|
1978
1978
|
* user = queryable(this, User);
|
|
1979
1979
|
*
|
|
1980
|
-
* //
|
|
1980
|
+
* // Usage example
|
|
1981
1981
|
* async getActiveUsers() {
|
|
1982
1982
|
* return this.user()
|
|
1983
1983
|
* .where((u) => [expr.eq(u.isActive, true)])
|
|
@@ -1992,8 +1992,8 @@ export function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<
|
|
|
1992
1992
|
as?: string,
|
|
1993
1993
|
): () => Queryable<TBuilder["$inferSelect"], TBuilder extends TableBuilder<any, any> ? TBuilder : never> {
|
|
1994
1994
|
return () => {
|
|
1995
|
-
// as
|
|
1996
|
-
// as
|
|
1995
|
+
// If as is not specified, use db.getNextAlias() (counter increments)
|
|
1996
|
+
// If as is specified, use it as-is (counter does not increment)
|
|
1997
1997
|
const finalAs = as ?? db.getNextAlias();
|
|
1998
1998
|
|
|
1999
1999
|
// TableBuilder + columns
|
|
@@ -2017,7 +2017,7 @@ export function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<
|
|
|
2017
2017
|
if (tableOrView instanceof ViewBuilder && tableOrView.meta.viewFn != null) {
|
|
2018
2018
|
const baseQr = tableOrView.meta.viewFn(db);
|
|
2019
2019
|
|
|
2020
|
-
// TFrom
|
|
2020
|
+
// Return with TFrom set to ViewBuilder
|
|
2021
2021
|
return new Queryable({
|
|
2022
2022
|
db,
|
|
2023
2023
|
from: tableOrView,
|