joist-core 2.3.0-next.99 → 2.4.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/EntityManager.cjs +1 -1
- package/build/EntityManager.cjs.map +1 -1
- package/build/EntityManager.d.cts +9 -8
- package/build/EntityManager.d.cts.map +1 -1
- package/build/EntityManager.d.mts +9 -8
- package/build/EntityManager.d.mts.map +1 -1
- package/build/EntityManager.js +1 -1
- package/build/EntityManager.js.map +1 -1
- package/build/dataloaders/findCountDataLoader.cjs +2 -0
- package/build/dataloaders/findCountDataLoader.cjs.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findCountDataLoader.js +2 -0
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.cjs +1 -0
- package/build/dataloaders/findDataLoader.cjs.map +1 -1
- package/build/dataloaders/findDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findDataLoader.js +2 -1
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/dataloaders/findIdsDataLoader.cjs +2 -0
- package/build/dataloaders/findIdsDataLoader.cjs.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findIdsDataLoader.js +2 -0
- package/build/dataloaders/findIdsDataLoader.js.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.cjs +2 -0
- package/build/dataloaders/findPaginatedDataLoader.cjs.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.js +2 -0
- package/build/dataloaders/findPaginatedDataLoader.js.map +1 -1
- package/build/index.cjs +6 -3
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +6 -4
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts +6 -4
- package/build/index.d.mts.map +1 -1
- package/build/index.js +6 -5
- package/build/index.js.map +1 -1
- package/build/keys.cjs +6 -1
- package/build/keys.cjs.map +1 -1
- package/build/keys.d.cts +3 -1
- package/build/keys.d.cts.map +1 -1
- package/build/keys.d.mts +3 -1
- package/build/keys.d.mts.map +1 -1
- package/build/keys.js +6 -2
- package/build/keys.js.map +1 -1
- package/build/queries/find/QueryParser.cjs +1 -2
- package/build/queries/find/QueryParser.cjs.map +1 -1
- package/build/queries/find/QueryParser.d.cts.map +1 -1
- package/build/queries/find/QueryParser.d.mts.map +1 -1
- package/build/queries/find/QueryParser.js +2 -3
- package/build/queries/find/QueryParser.js.map +1 -1
- package/build/queries/find/QueryVisitor.cjs +17 -0
- package/build/queries/find/QueryVisitor.cjs.map +1 -1
- package/build/queries/find/QueryVisitor.d.cts +3 -1
- package/build/queries/find/QueryVisitor.d.cts.map +1 -1
- package/build/queries/find/QueryVisitor.d.mts +3 -1
- package/build/queries/find/QueryVisitor.d.mts.map +1 -1
- package/build/queries/find/QueryVisitor.js +17 -1
- package/build/queries/find/QueryVisitor.js.map +1 -1
- package/build/queries/sql/Expr.cjs +18 -1
- package/build/queries/sql/Expr.cjs.map +1 -1
- package/build/queries/sql/Expr.d.cts +13 -3
- package/build/queries/sql/Expr.d.cts.map +1 -1
- package/build/queries/sql/Expr.d.mts +13 -3
- package/build/queries/sql/Expr.d.mts.map +1 -1
- package/build/queries/sql/Expr.js +18 -2
- package/build/queries/sql/Expr.js.map +1 -1
- package/build/queries/sql/JoinTree.cjs +72 -0
- package/build/queries/sql/JoinTree.cjs.map +1 -0
- package/build/queries/sql/JoinTree.d.cts +107 -0
- package/build/queries/sql/JoinTree.d.cts.map +1 -0
- package/build/queries/sql/JoinTree.d.mts +107 -0
- package/build/queries/sql/JoinTree.d.mts.map +1 -0
- package/build/queries/sql/JoinTree.js +71 -0
- package/build/queries/sql/JoinTree.js.map +1 -0
- package/build/queries/sql/Tables.cjs +103 -6
- package/build/queries/sql/Tables.cjs.map +1 -1
- package/build/queries/sql/Tables.d.cts +72 -13
- package/build/queries/sql/Tables.d.cts.map +1 -1
- package/build/queries/sql/Tables.d.mts +72 -13
- package/build/queries/sql/Tables.d.mts.map +1 -1
- package/build/queries/sql/Tables.js +101 -10
- package/build/queries/sql/Tables.js.map +1 -1
- package/build/queries/sql/custom.cjs +52 -0
- package/build/queries/sql/custom.cjs.map +1 -0
- package/build/queries/sql/custom.d.cts +85 -0
- package/build/queries/sql/custom.d.cts.map +1 -0
- package/build/queries/sql/custom.d.mts +85 -0
- package/build/queries/sql/custom.d.mts.map +1 -0
- package/build/queries/sql/custom.js +49 -0
- package/build/queries/sql/custom.js.map +1 -0
- package/build/queries/sql/execute.cjs +68 -28
- package/build/queries/sql/execute.cjs.map +1 -1
- package/build/queries/sql/execute.d.cts +110 -11
- package/build/queries/sql/execute.d.cts.map +1 -1
- package/build/queries/sql/execute.d.mts +110 -11
- package/build/queries/sql/execute.d.mts.map +1 -1
- package/build/queries/sql/execute.js +69 -29
- package/build/queries/sql/execute.js.map +1 -1
- package/build/queries/sql/expressions/types.d.cts +5 -5
- package/build/queries/sql/expressions/types.d.cts.map +1 -1
- package/build/queries/sql/expressions/types.d.mts +5 -5
- package/build/queries/sql/expressions/types.d.mts.map +1 -1
- package/build/queries/sql/keywords.cjs +1 -1
- package/build/queries/sql/keywords.cjs.map +1 -1
- package/build/queries/sql/keywords.d.cts +1 -1
- package/build/queries/sql/keywords.d.mts +1 -1
- package/build/queries/sql/keywords.js +1 -1
- package/build/queries/sql/keywords.js.map +1 -1
- package/build/queries/sql/query.cjs +100 -38
- package/build/queries/sql/query.cjs.map +1 -1
- package/build/queries/sql/query.d.cts +96 -46
- package/build/queries/sql/query.d.cts.map +1 -1
- package/build/queries/sql/query.d.mts +96 -46
- package/build/queries/sql/query.d.mts.map +1 -1
- package/build/queries/sql/query.js +102 -40
- package/build/queries/sql/query.js.map +1 -1
- package/build/queries/valueFilters.cjs +28 -36
- package/build/queries/valueFilters.cjs.map +1 -1
- package/build/queries/valueFilters.d.cts +2 -4
- package/build/queries/valueFilters.d.cts.map +1 -1
- package/build/queries/valueFilters.d.mts +2 -4
- package/build/queries/valueFilters.d.mts.map +1 -1
- package/build/queries/valueFilters.js +29 -36
- package/build/queries/valueFilters.js.map +1 -1
- package/build/serde/serde.cjs.map +1 -1
- package/build/serde/serde.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,13 +5,14 @@ import { isDeferredAliasCondition } from "../find/DeferredAlias.js";
|
|
|
5
5
|
import { ConditionBuilder } from "../ConditionBuilder.js";
|
|
6
6
|
import { filterSoftDeletes, lazyExcludedSelects, stiSubtypeFilter } from "../entityQueryUtils.js";
|
|
7
7
|
import { deepFindConditions } from "../parsedConditions.js";
|
|
8
|
-
import { BaseExpr,
|
|
8
|
+
import { BaseExpr, TemplateExpr, asNode, deferredCondition, deferredSym, isExpr, orderByToSql, resolveDeferredConditions, selectKeyBrand } from "./Expr.js";
|
|
9
9
|
import { fail } from "../../utils.js";
|
|
10
10
|
import { getBaseMeta } from "../../EntityMetadata.js";
|
|
11
11
|
import { buildWhereClause } from "../renderConditions.js";
|
|
12
12
|
import { AliasAssigner } from "./AliasAssigner.js";
|
|
13
|
-
import { JoinTableHandle, collectionJoin, getTableMetadata, getTableMgmt, isTable, m2mJoinTable, referenceJoinSource } from "./Tables.js";
|
|
13
|
+
import { JoinTableHandle, collectionJoin, getTableMetadata, getTableMgmt, inQueryCondition, isEntityTable, isTable, m2mJoinTable, referenceJoinSource, tableSqlName } from "./Tables.js";
|
|
14
14
|
import { buildExpr } from "./expressions/expression.js";
|
|
15
|
+
import { compileJoinTree } from "./JoinTree.js";
|
|
15
16
|
//#region src/queries/sql/query.ts
|
|
16
17
|
/**
|
|
17
18
|
* `em.query`: SQL-shaped queries as plain object literals.
|
|
@@ -27,10 +28,10 @@ import { buildExpr } from "./expressions/expression.js";
|
|
|
27
28
|
* select: { name: a.first_name, n: bookStats.n },
|
|
28
29
|
* orderBy: { n: "DESC" },
|
|
29
30
|
* });
|
|
30
|
-
* // rows: { name: string; n: number |
|
|
31
|
+
* // rows: { name: string; n: number | undefined }[] (undefined because of the LEFT join)
|
|
31
32
|
*
|
|
32
|
-
* `em.query(pojo)` runs it. `select` decides the row type: a bare table returns entities,
|
|
33
|
-
* `{ key: expr }` object returns typed POJOs, a bare subquery returns
|
|
33
|
+
* `em.query(pojo)` runs it. `select` decides the row type: a bare table returns entities, an array of
|
|
34
|
+
* columns or a `{ key: expr }` object returns typed POJOs, and a bare subquery returns its rows.
|
|
34
35
|
*
|
|
35
36
|
* `query(pojo)` turns the *same* POJO into a value: a derived table with typed columns, a scalar
|
|
36
37
|
* expression, or an entity list. It is the one non-POJO step, and the subquery analog of `table(Author)`:
|
|
@@ -50,24 +51,26 @@ const subqueryBrand = Symbol("joist.subquery");
|
|
|
50
51
|
const entityQueryBrand = Symbol("joist.entityQuery");
|
|
51
52
|
const scalarQueryBrand = Symbol("joist.scalarQuery");
|
|
52
53
|
function query(q) {
|
|
53
|
-
|
|
54
|
-
const output = handle.output();
|
|
55
|
-
if (output.kind === "entity") return { [entityQueryBrand]: handle };
|
|
56
|
-
else if (output.kind === "scalar") return new SubqueryExpr(handle);
|
|
57
|
-
else return newSubqueryProxy(handle);
|
|
54
|
+
return createQueryValue(q);
|
|
58
55
|
}
|
|
59
56
|
/**
|
|
60
|
-
* Builds a query value only when its user-supplied `where`
|
|
57
|
+
* Builds a query value only when its user-supplied `where` or join-tree filters survive pruning.
|
|
61
58
|
*
|
|
62
59
|
* Missing or empty conditions prune the value, including nested groups and `pruneIfUndefined`.
|
|
63
|
-
*
|
|
60
|
+
* Alias-only joins, HAVING, and implicit soft-delete/STI conditions do not keep the value alive.
|
|
64
61
|
*
|
|
65
62
|
* I.e. `queryMaybe({ from: b, where: b.authorId.eq(enabled ? a.id : undefined), select: b.id })`
|
|
66
63
|
* returns `undefined` when `enabled` is false, so an enclosing `notExists` also prunes.
|
|
67
64
|
*/
|
|
68
65
|
function queryMaybe(q) {
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
return createMaybeQueryValue(q);
|
|
67
|
+
}
|
|
68
|
+
/** Applies queryMaybe's structural pruning before creating any kind of read-query value. */
|
|
69
|
+
function createMaybeQueryValue(q) {
|
|
70
|
+
const normalized = normalizeQueryJoins(q);
|
|
71
|
+
const where = normalizeWhere(normalized.where);
|
|
72
|
+
checkCondition(where);
|
|
73
|
+
return isPrunedQueryCondition(where) ? void 0 : createQueryValue(normalized);
|
|
71
74
|
}
|
|
72
75
|
/**
|
|
73
76
|
* Declares a `WITH RECURSIVE` CTE from its two terms, and returns the CTE as a readable value.
|
|
@@ -122,9 +125,9 @@ function recursiveQuery(name, base, step, opts = {}) {
|
|
|
122
125
|
* // Selecting this expression keeps the join to Book b.
|
|
123
126
|
* sql.number`${b.order} * ${2}`
|
|
124
127
|
*
|
|
125
|
-
* // Reference an unmodeled column;
|
|
126
|
-
*
|
|
127
|
-
* sql.condition`${
|
|
128
|
+
* // Reference an unmodeled column; prefer the table's own `column`, which keeps its source key.
|
|
129
|
+
* a.column<string>("ts_search")
|
|
130
|
+
* sql.condition`${a.column("ts_search")} @@ plainto_tsquery(${term})`
|
|
128
131
|
* ```
|
|
129
132
|
*/
|
|
130
133
|
function sql(strings, ...values) {
|
|
@@ -158,10 +161,6 @@ sql.booleanArrayOrNull = typedSql;
|
|
|
158
161
|
sql.condition = function condition(strings, ...values) {
|
|
159
162
|
return deferredCondition((ctx) => new TemplateExpr(strings, values).toSql(ctx));
|
|
160
163
|
};
|
|
161
|
-
/** A column Joist does not model, on a source that is in the query. */
|
|
162
|
-
sql.ref = function ref(source, column) {
|
|
163
|
-
return new RefExpr(handleOf(source), column);
|
|
164
|
-
};
|
|
165
164
|
/**
|
|
166
165
|
* Parses `arg` (a `Query` POJO or `query(...)` value) into a runnable `Plan`.
|
|
167
166
|
*
|
|
@@ -188,8 +187,8 @@ function isReadQueryValue(arg) {
|
|
|
188
187
|
* Compiles scalar or nonempty named expression projections, shared by SELECT and mutation RETURNING.
|
|
189
188
|
* Source-shaped values need read hydration/source rules and are deliberately excluded here.
|
|
190
189
|
*/
|
|
191
|
-
function projectionToSql(select, ctx) {
|
|
192
|
-
const output = projectionOutput(select);
|
|
190
|
+
function projectionToSql(select, ctx, joins) {
|
|
191
|
+
const output = joinedOutput(projectionOutput(select), joins);
|
|
193
192
|
if (output.kind === "scalar") {
|
|
194
193
|
const expr = output.columns[0][1];
|
|
195
194
|
const fragment = expr.toSql(ctx);
|
|
@@ -198,7 +197,7 @@ function projectionToSql(select, ctx) {
|
|
|
198
197
|
...fragment,
|
|
199
198
|
sql: `${fragment.sql} AS value`
|
|
200
199
|
}],
|
|
201
|
-
decodeRows: (_, rows) => rows.map((row) => expr
|
|
200
|
+
decodeRows: (_, rows) => rows.map((row) => decodeValue(expr, row.value)),
|
|
202
201
|
output
|
|
203
202
|
};
|
|
204
203
|
}
|
|
@@ -271,6 +270,9 @@ var SubqueryColumnExpr = class extends BaseExpr {
|
|
|
271
270
|
this.key = key;
|
|
272
271
|
this.inner = inner;
|
|
273
272
|
}
|
|
273
|
+
get [selectKeyBrand]() {
|
|
274
|
+
return this.key;
|
|
275
|
+
}
|
|
274
276
|
toSql(ctx) {
|
|
275
277
|
const alias = ctx.aliasFor(this.handle);
|
|
276
278
|
return {
|
|
@@ -500,6 +502,7 @@ function queryOutput(q) {
|
|
|
500
502
|
columns
|
|
501
503
|
};
|
|
502
504
|
}
|
|
505
|
+
q = normalizeQueryJoins(q);
|
|
503
506
|
const { select } = q;
|
|
504
507
|
if (isTable(select)) return {
|
|
505
508
|
kind: "entity",
|
|
@@ -514,6 +517,20 @@ function projectionOutput(select) {
|
|
|
514
517
|
kind: "scalar",
|
|
515
518
|
columns: [["value", asExpr(select, "select")]]
|
|
516
519
|
};
|
|
520
|
+
if (Array.isArray(select)) {
|
|
521
|
+
if (select.length === 0) fail("A column array projection must not be empty");
|
|
522
|
+
const columns = select.map((value, index) => {
|
|
523
|
+
const expr = asExpr(value, `select[${index}]`);
|
|
524
|
+
const key = expr[selectKeyBrand];
|
|
525
|
+
if (typeof key !== "string") fail(`select[${index}] must be a physical or subquery column`);
|
|
526
|
+
return [key, expr];
|
|
527
|
+
});
|
|
528
|
+
if (new Set(columns.map(([key]) => key)).size !== columns.length) fail("A column array projection must not contain duplicate field names");
|
|
529
|
+
return {
|
|
530
|
+
kind: "pojo",
|
|
531
|
+
columns
|
|
532
|
+
};
|
|
533
|
+
}
|
|
517
534
|
if (isPlainSelect(select)) {
|
|
518
535
|
const columns = [];
|
|
519
536
|
for (const key of Reflect.ownKeys(select)) {
|
|
@@ -529,7 +546,7 @@ function projectionOutput(select) {
|
|
|
529
546
|
return fail("Expected a scalar expression or a nonempty named expression projection");
|
|
530
547
|
}
|
|
531
548
|
/**
|
|
532
|
-
* Adds
|
|
549
|
+
* Adds SQL nullability from unmatched LEFT joins without mutating shared expressions or their codecs.
|
|
533
550
|
* Only direct columns carry sqlSource; COUNT and COALESCE retain their own SQL nullability.
|
|
534
551
|
* I.e. selecting Book.title from a LEFT-joined Book makes its output nullable, but COUNT(Book.id) stays NOT NULL.
|
|
535
552
|
*/
|
|
@@ -637,7 +654,7 @@ function parseSetQuery(q, parent, assigner, recursiveSelf) {
|
|
|
637
654
|
bindings,
|
|
638
655
|
outerRefs: [...new Set(outerRefs.filter((ref) => !cteAliases.has(ref)))],
|
|
639
656
|
output,
|
|
640
|
-
decodeRows:
|
|
657
|
+
decodeRows: (_, rows) => rows.map((row) => decodeRow(row, output.columns))
|
|
641
658
|
};
|
|
642
659
|
}
|
|
643
660
|
/** Validate and quote only named set output sorts, including before a reusable value is parsed. */
|
|
@@ -776,6 +793,7 @@ function describeHandle(handle) {
|
|
|
776
793
|
function parseQuery(q, parent, assigner, recursiveSelf) {
|
|
777
794
|
validateReadQuery(q);
|
|
778
795
|
if (isSetQuery(q)) return parseSetQuery(q, parent, assigner, recursiveSelf);
|
|
796
|
+
q = normalizeQueryJoins(q);
|
|
779
797
|
const ctx = new Ctx(assigner, parent);
|
|
780
798
|
if (recursiveSelf) ctx.setRecursiveSelf(recursiveSelf);
|
|
781
799
|
const joinEntries = resolveReferenceJoins(q.join);
|
|
@@ -823,7 +841,8 @@ function parseQuery(q, parent, assigner, recursiveSelf) {
|
|
|
823
841
|
const sti = stiEntityPlan(q, from);
|
|
824
842
|
const { selects, decodeRows, output } = selectsToSql(q, ctx, from, cti.selects ?? sti.selects);
|
|
825
843
|
const fromInjected = [...injectedConditions(from, softDeletes), ...sti.conditions];
|
|
826
|
-
const
|
|
844
|
+
const predicate = normalizeWhere(q.where);
|
|
845
|
+
const where = conditionToSql(fromInjected.length > 0 ? { and: [predicate, ...fromInjected] } : predicate, ctx, true);
|
|
827
846
|
const having = conditionToSql(q.having, ctx, true);
|
|
828
847
|
const groupBys = (q.groupBy ?? []).map((g) => asExpr(g, "groupBy").toSql(ctx));
|
|
829
848
|
const orderBys = orderBysToSql(q, ctx);
|
|
@@ -936,15 +955,17 @@ function registerSource(source, ctx, assigner) {
|
|
|
936
955
|
};
|
|
937
956
|
};
|
|
938
957
|
} else {
|
|
939
|
-
const
|
|
940
|
-
const
|
|
958
|
+
const mgmt = getTableMgmt(source);
|
|
959
|
+
const { tableName } = mgmt;
|
|
960
|
+
const meta = "meta" in mgmt ? mgmt.meta : void 0;
|
|
961
|
+
const alias = assigner.getAlias(tableName);
|
|
941
962
|
ctx.register(handle, alias);
|
|
942
963
|
return () => {
|
|
943
|
-
const entitySelects = meta.hasLazyColumns ? lazyExcludedSelects(meta, alias) : [kqStar(alias)];
|
|
964
|
+
const entitySelects = !meta ? [] : meta.hasLazyColumns ? lazyExcludedSelects(meta, alias) : [kqStar(alias)];
|
|
944
965
|
return {
|
|
945
966
|
handle,
|
|
946
967
|
alias,
|
|
947
|
-
sql: `${
|
|
968
|
+
sql: `${tableSqlName(mgmt)} AS ${kq(alias)}`,
|
|
948
969
|
bindings: [],
|
|
949
970
|
refs: [],
|
|
950
971
|
entitySelects,
|
|
@@ -996,7 +1017,8 @@ function registerJoinTable(handle, ctx, assigner) {
|
|
|
996
1017
|
/** Generates the `select` clause SQL and returns how to decode the resulting rows. */
|
|
997
1018
|
function selectsToSql(q, ctx, from, entitySelects) {
|
|
998
1019
|
const { select } = q;
|
|
999
|
-
if (isTable(select))
|
|
1020
|
+
if (isTable(select) && !isEntityTable(select)) fail("A custom table cannot be selected as an entity; select its columns individually");
|
|
1021
|
+
if (isEntityTable(select)) {
|
|
1000
1022
|
if (from.handle !== getTableMgmt(select)) fail("Selecting a joined table is not supported yet; select the from table, or select its columns individually");
|
|
1001
1023
|
const alias = ctx.aliasFor(getTableMgmt(select));
|
|
1002
1024
|
const meta = getTableMetadata(select);
|
|
@@ -1027,9 +1049,7 @@ function selectsToSql(q, ctx, from, entitySelects) {
|
|
|
1027
1049
|
output
|
|
1028
1050
|
};
|
|
1029
1051
|
}
|
|
1030
|
-
|
|
1031
|
-
projection.output = joinedOutput(projection.output, q.join);
|
|
1032
|
-
return projection;
|
|
1052
|
+
return projectionToSql(select, ctx, q.join);
|
|
1033
1053
|
}
|
|
1034
1054
|
/**
|
|
1035
1055
|
* Builds the mandatory CTI joins and flat row projection needed by entity hydration.
|
|
@@ -1040,7 +1060,7 @@ function selectsToSql(q, ctx, from, entitySelects) {
|
|
|
1040
1060
|
* `_sN` aliases can be made unique before their projection fragments are built.
|
|
1041
1061
|
*/
|
|
1042
1062
|
function ctiEntityPlan(q, from, assigner) {
|
|
1043
|
-
if (!
|
|
1063
|
+
if (!isEntityTable(q.select) || from.handle !== getTableMgmt(q.select)) return {
|
|
1044
1064
|
joins: [],
|
|
1045
1065
|
selects: void 0
|
|
1046
1066
|
};
|
|
@@ -1102,7 +1122,7 @@ function ctiEntityPlan(q, from, assigner) {
|
|
|
1102
1122
|
}
|
|
1103
1123
|
/** Adds the discriminator projection and subtype filter required by STI entity hydration. */
|
|
1104
1124
|
function stiEntityPlan(q, from) {
|
|
1105
|
-
if (!
|
|
1125
|
+
if (!isEntityTable(q.select) || from.handle !== getTableMgmt(q.select)) return {
|
|
1106
1126
|
selects: void 0,
|
|
1107
1127
|
conditions: []
|
|
1108
1128
|
};
|
|
@@ -1168,7 +1188,7 @@ function decodeRow(row, decoders) {
|
|
|
1168
1188
|
const result = {};
|
|
1169
1189
|
for (const [key, expr] of decoders) {
|
|
1170
1190
|
const value = row[key];
|
|
1171
|
-
const decoded =
|
|
1191
|
+
const decoded = decodeValue(expr, value);
|
|
1172
1192
|
if (key === "__proto__") Object.defineProperty(result, key, {
|
|
1173
1193
|
value: decoded,
|
|
1174
1194
|
enumerable: true,
|
|
@@ -1179,6 +1199,10 @@ function decodeRow(row, decoders) {
|
|
|
1179
1199
|
}
|
|
1180
1200
|
return result;
|
|
1181
1201
|
}
|
|
1202
|
+
/** Decodes a top-level projected SQL NULL using Joist's undefined convention. */
|
|
1203
|
+
function decodeValue(expr, value) {
|
|
1204
|
+
return value === null ? void 0 : expr.decode(value);
|
|
1205
|
+
}
|
|
1182
1206
|
const ORDER_BY_DIRECTIONS = [
|
|
1183
1207
|
"ASC",
|
|
1184
1208
|
"DESC",
|
|
@@ -1188,7 +1212,7 @@ const ORDER_BY_DIRECTIONS = [
|
|
|
1188
1212
|
"DESC NULLS LAST"
|
|
1189
1213
|
];
|
|
1190
1214
|
/**
|
|
1191
|
-
* Generates ORDER BY SQL in entry order for keyed/expression arrays or a single
|
|
1215
|
+
* Generates ORDER BY SQL in entry order for keyed/expression arrays or a single entry.
|
|
1192
1216
|
*
|
|
1193
1217
|
* Expression entries retain bindings and alias references for join pruning. Undefined entries and
|
|
1194
1218
|
* directions are omitted before expression references are collected.
|
|
@@ -1217,7 +1241,7 @@ function orderBysToSql(q, ctx) {
|
|
|
1217
1241
|
});
|
|
1218
1242
|
} else {
|
|
1219
1243
|
if (isExpr(select)) return fail(`the keyed orderBy form needs a POJO or entity select`);
|
|
1220
|
-
if (!(isSubqueryValue(select) ? select[subqueryBrand].columnKeys() :
|
|
1244
|
+
if (!(isSubqueryValue(select) ? select[subqueryBrand].columnKeys() : projectionOutput(select).columns.map(([key]) => key)).includes(key)) return fail(`orderBy key '${key}' is not a key of select`);
|
|
1221
1245
|
result.push({
|
|
1222
1246
|
sql: `${safeKq(key)} ${dir}`,
|
|
1223
1247
|
bindings: [],
|
|
@@ -1260,6 +1284,10 @@ function conditionToSql(cond, ctx, topLevel) {
|
|
|
1260
1284
|
function checkCondition(value) {
|
|
1261
1285
|
if (isDeferredAliasCondition(value) || value && typeof value === "object" && predicateBrand in value && value[predicateBrand] === "domain") fail("Domain alias conditions are only supported by em.find; use table(...) predicates in SQL queries and mutations.");
|
|
1262
1286
|
if (value === void 0 || value === skipCondition) return;
|
|
1287
|
+
if (isInQueryCondition(value)) {
|
|
1288
|
+
checkCondition(normalizeWhere(normalizeQueryJoins(toQuery(value[inQueryCondition].query)).where));
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1263
1291
|
if (!value || typeof value !== "object" || Array.isArray(value) || isExpr(value)) fail("Query predicate must be a condition or an and/or group");
|
|
1264
1292
|
const condition = value;
|
|
1265
1293
|
if ("and" in condition || "or" in condition) {
|
|
@@ -1345,6 +1373,13 @@ function checkConditionKeys(value, allowed, description) {
|
|
|
1345
1373
|
*/
|
|
1346
1374
|
function resolveQueryCondition(cond, ctx) {
|
|
1347
1375
|
if (cond === void 0) return void 0;
|
|
1376
|
+
if (isInQueryCondition(cond)) {
|
|
1377
|
+
const { column, query } = cond[inQueryCondition];
|
|
1378
|
+
const value = createMaybeQueryValue(query);
|
|
1379
|
+
if (value === void 0) return void 0;
|
|
1380
|
+
if (!isExpr(value)) return fail("An in query literal must select one expression");
|
|
1381
|
+
return column.in(value);
|
|
1382
|
+
}
|
|
1348
1383
|
if ("and" in cond && cond.and) return {
|
|
1349
1384
|
and: cond.and.map((child) => resolveQueryCondition(child, ctx)),
|
|
1350
1385
|
pruneIfUndefined: cond.pruneIfUndefined
|
|
@@ -1379,6 +1414,7 @@ function isFilter(cond) {
|
|
|
1379
1414
|
*/
|
|
1380
1415
|
function isPrunedQueryCondition(cond) {
|
|
1381
1416
|
if (cond === void 0 || cond === skipCondition) return true;
|
|
1417
|
+
if (isInQueryCondition(cond)) return isPrunedQueryCondition(normalizeWhere(normalizeQueryJoins(cond[inQueryCondition].query).where));
|
|
1382
1418
|
if ("and" in cond || "or" in cond) {
|
|
1383
1419
|
const children = (cond.and ?? cond.or ?? []).map(isPrunedQueryCondition);
|
|
1384
1420
|
return children.every((pruned) => pruned) || "pruneIfUndefined" in cond && cond.pruneIfUndefined === "any" && children.some((pruned) => pruned);
|
|
@@ -1387,6 +1423,10 @@ function isPrunedQueryCondition(cond) {
|
|
|
1387
1423
|
if ("notExists" in cond) return cond.notExists === void 0;
|
|
1388
1424
|
return false;
|
|
1389
1425
|
}
|
|
1426
|
+
/** Recognizes the direct query-literal condition created by EntityColumn.in. */
|
|
1427
|
+
function isInQueryCondition(value) {
|
|
1428
|
+
return typeof value === "object" && value !== null && inQueryCondition in value;
|
|
1429
|
+
}
|
|
1390
1430
|
/** The physical source aliases a parsed condition tree references. */
|
|
1391
1431
|
function refsOf(parsed) {
|
|
1392
1432
|
return deepFindConditions(parsed, false).flatMap((c) => c.kind === "column" ? [c.alias] : c.kind === "raw" ? c.aliases : c.outerAliases);
|
|
@@ -1520,6 +1560,28 @@ function joinFragmentParts(parts, sep) {
|
|
|
1520
1560
|
function isDefined(value) {
|
|
1521
1561
|
return value !== void 0;
|
|
1522
1562
|
}
|
|
1563
|
+
/** Lowers a relationship tree before SQL rendering or output nullability is calculated. */
|
|
1564
|
+
function normalizeQueryJoins(q) {
|
|
1565
|
+
if (q.join === void 0 || Array.isArray(q.join)) return q;
|
|
1566
|
+
const tree = compileJoinTree(q.from, q.join);
|
|
1567
|
+
return {
|
|
1568
|
+
...q,
|
|
1569
|
+
join: tree.joins,
|
|
1570
|
+
where: { and: [tree.condition, normalizeWhere(q.where)] }
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
/** Gives WHERE arrays the same pruning and validation as explicit AND groups. */
|
|
1574
|
+
function normalizeWhere(where) {
|
|
1575
|
+
return Array.isArray(where) ? { and: where } : where;
|
|
1576
|
+
}
|
|
1577
|
+
/** Creates the runtime query value after the public signature has checked its input. */
|
|
1578
|
+
function createQueryValue(q) {
|
|
1579
|
+
const handle = new SubqueryHandle(toQuery(q));
|
|
1580
|
+
const output = handle.output();
|
|
1581
|
+
if (output.kind === "entity") return { [entityQueryBrand]: handle };
|
|
1582
|
+
else if (output.kind === "scalar") return new SubqueryExpr(handle);
|
|
1583
|
+
else return newSubqueryProxy(handle);
|
|
1584
|
+
}
|
|
1523
1585
|
/** Keeps shorthand declarations callable-only, without recursively copying sql's own shorthand properties. */
|
|
1524
1586
|
function typedSql(strings, ...values) {
|
|
1525
1587
|
return new TemplateExpr(strings, values);
|