@tanstack/db 0.4.2 → 0.4.3
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/dist/cjs/collection/subscription.cjs +1 -1
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +2 -39
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +1 -0
- package/dist/cjs/query/compiler/joins.cjs +9 -8
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +2 -1
- package/dist/cjs/query/compiler/order-by.cjs +4 -5
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/ir.cjs +38 -0
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +10 -1
- package/dist/esm/collection/subscription.js +1 -1
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +1 -0
- package/dist/esm/query/compiler/index.js +4 -41
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +2 -1
- package/dist/esm/query/compiler/joins.js +9 -8
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js +1 -2
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/ir.d.ts +10 -1
- package/dist/esm/query/ir.js +38 -0
- package/dist/esm/query/ir.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/subscription.ts +1 -1
- package/src/query/compiler/index.ts +4 -1
- package/src/query/compiler/joins.ts +13 -8
- package/src/query/compiler/order-by.ts +1 -2
- package/src/query/ir.ts +67 -1
package/dist/esm/query/ir.js
CHANGED
|
@@ -61,6 +61,43 @@ function isResidualWhere(where) {
|
|
|
61
61
|
function createResidualWhere(expression) {
|
|
62
62
|
return { expression, residual: true };
|
|
63
63
|
}
|
|
64
|
+
function getRefFromAlias(query, alias) {
|
|
65
|
+
if (query.from.alias === alias) {
|
|
66
|
+
return query.from;
|
|
67
|
+
}
|
|
68
|
+
for (const join of query.join || []) {
|
|
69
|
+
if (join.from.alias === alias) {
|
|
70
|
+
return join.from;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function followRef(query, ref, collection) {
|
|
75
|
+
if (ref.path.length === 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (ref.path.length === 1) {
|
|
79
|
+
const field = ref.path[0];
|
|
80
|
+
if (query.select) {
|
|
81
|
+
const selectedField = query.select[field];
|
|
82
|
+
if (selectedField && selectedField.type === `ref`) {
|
|
83
|
+
return followRef(query, selectedField, collection);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { collection, path: [field] };
|
|
87
|
+
}
|
|
88
|
+
if (ref.path.length > 1) {
|
|
89
|
+
const [alias, ...rest] = ref.path;
|
|
90
|
+
const aliasRef = getRefFromAlias(query, alias);
|
|
91
|
+
if (!aliasRef) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (aliasRef.type === `queryRef`) {
|
|
95
|
+
return followRef(aliasRef.query, new PropRef(rest), collection);
|
|
96
|
+
} else {
|
|
97
|
+
return { collection: aliasRef.collection, path: rest };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
64
101
|
export {
|
|
65
102
|
Aggregate,
|
|
66
103
|
CollectionRef,
|
|
@@ -69,6 +106,7 @@ export {
|
|
|
69
106
|
QueryRef,
|
|
70
107
|
Value,
|
|
71
108
|
createResidualWhere,
|
|
109
|
+
followRef,
|
|
72
110
|
getHavingExpression,
|
|
73
111
|
getWhereExpression,
|
|
74
112
|
isExpressionLike,
|
package/dist/esm/query/ir.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.js","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from \"./builder/types\"\nimport type { CollectionImpl } from \"../collection/index.js\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate | Select\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where =\n | BasicExpression<boolean>\n | { expression: BasicExpression<boolean>; residual?: boolean }\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n compareOptions: CompareOptions\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n/**\n * Runtime helper to detect IR expression-like objects.\n * Prefer this over ad-hoc local implementations to keep behavior consistent.\n */\nexport function isExpressionLike(value: any): boolean {\n return (\n value instanceof Aggregate ||\n value instanceof Func ||\n value instanceof PropRef ||\n value instanceof Value\n )\n}\n\n/**\n * Helper functions for working with Where clauses\n */\n\n/**\n * Extract the expression from a Where clause\n */\nexport function getWhereExpression(where: Where): BasicExpression<boolean> {\n return typeof where === `object` && `expression` in where\n ? where.expression\n : where\n}\n\n/**\n * Extract the expression from a HAVING clause\n * HAVING clauses can contain aggregates, unlike regular WHERE clauses\n */\nexport function getHavingExpression(\n having: Having\n): BasicExpression | Aggregate {\n return typeof having === `object` && `expression` in having\n ? having.expression\n : having\n}\n\n/**\n * Check if a Where clause is marked as residual\n */\nexport function isResidualWhere(where: Where): boolean {\n return (\n typeof where === `object` &&\n `expression` in where &&\n where.residual === true\n )\n}\n\n/**\n * Create a residual Where clause from an expression\n */\nexport function createResidualWhere(\n expression: BasicExpression<boolean>\n): Where {\n return { expression, residual: true }\n}\n"],"names":[],"mappings":"AAgEA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAMO,SAAS,iBAAiB,OAAqB;AACpD,SACE,iBAAiB,aACjB,iBAAiB,QACjB,iBAAiB,WACjB,iBAAiB;AAErB;AASO,SAAS,mBAAmB,OAAwC;AACzE,SAAO,OAAO,UAAU,YAAY,gBAAgB,QAChD,MAAM,aACN;AACN;AAMO,SAAS,oBACd,QAC6B;AAC7B,SAAO,OAAO,WAAW,YAAY,gBAAgB,SACjD,OAAO,aACP;AACN;AAKO,SAAS,gBAAgB,OAAuB;AACrD,SACE,OAAO,UAAU,YACjB,gBAAgB,SAChB,MAAM,aAAa;AAEvB;AAKO,SAAS,oBACd,YACO;AACP,SAAO,EAAE,YAAY,UAAU,KAAA;AACjC;"}
|
|
1
|
+
{"version":3,"file":"ir.js","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from \"./builder/types\"\nimport type { Collection, CollectionImpl } from \"../collection/index.js\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate | Select\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where =\n | BasicExpression<boolean>\n | { expression: BasicExpression<boolean>; residual?: boolean }\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n compareOptions: CompareOptions\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n/**\n * Runtime helper to detect IR expression-like objects.\n * Prefer this over ad-hoc local implementations to keep behavior consistent.\n */\nexport function isExpressionLike(value: any): boolean {\n return (\n value instanceof Aggregate ||\n value instanceof Func ||\n value instanceof PropRef ||\n value instanceof Value\n )\n}\n\n/**\n * Helper functions for working with Where clauses\n */\n\n/**\n * Extract the expression from a Where clause\n */\nexport function getWhereExpression(where: Where): BasicExpression<boolean> {\n return typeof where === `object` && `expression` in where\n ? where.expression\n : where\n}\n\n/**\n * Extract the expression from a HAVING clause\n * HAVING clauses can contain aggregates, unlike regular WHERE clauses\n */\nexport function getHavingExpression(\n having: Having\n): BasicExpression | Aggregate {\n return typeof having === `object` && `expression` in having\n ? having.expression\n : having\n}\n\n/**\n * Check if a Where clause is marked as residual\n */\nexport function isResidualWhere(where: Where): boolean {\n return (\n typeof where === `object` &&\n `expression` in where &&\n where.residual === true\n )\n}\n\n/**\n * Create a residual Where clause from an expression\n */\nexport function createResidualWhere(\n expression: BasicExpression<boolean>\n): Where {\n return { expression, residual: true }\n}\n\nfunction getRefFromAlias(\n query: QueryIR,\n alias: string\n): CollectionRef | QueryRef | void {\n if (query.from.alias === alias) {\n return query.from\n }\n\n for (const join of query.join || []) {\n if (join.from.alias === alias) {\n return join.from\n }\n }\n}\n\n/**\n * Follows the given reference in a query\n * until its finds the root field the reference points to.\n * @returns The collection, its alias, and the path to the root field in this collection\n */\nexport function followRef(\n query: QueryIR,\n ref: PropRef<any>,\n collection: Collection\n): { collection: Collection; path: Array<string> } | void {\n if (ref.path.length === 0) {\n return\n }\n\n if (ref.path.length === 1) {\n // This field should be part of this collection\n const field = ref.path[0]!\n // is it part of the select clause?\n if (query.select) {\n const selectedField = query.select[field]\n if (selectedField && selectedField.type === `ref`) {\n return followRef(query, selectedField, collection)\n }\n }\n\n // Either this field is not part of the select clause\n // and thus it must be part of the collection itself\n // or it is part of the select but is not a reference\n // so we can stop here and don't have to follow it\n return { collection, path: [field] }\n }\n\n if (ref.path.length > 1) {\n // This is a nested field\n const [alias, ...rest] = ref.path\n const aliasRef = getRefFromAlias(query, alias!)\n if (!aliasRef) {\n return\n }\n\n if (aliasRef.type === `queryRef`) {\n return followRef(aliasRef.query, new PropRef(rest), collection)\n } else {\n // This is a reference to a collection\n // we can't follow it further\n // so the field must be on the collection itself\n return { collection: aliasRef.collection, path: rest }\n }\n }\n}\n"],"names":[],"mappings":"AAgEA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAMO,SAAS,iBAAiB,OAAqB;AACpD,SACE,iBAAiB,aACjB,iBAAiB,QACjB,iBAAiB,WACjB,iBAAiB;AAErB;AASO,SAAS,mBAAmB,OAAwC;AACzE,SAAO,OAAO,UAAU,YAAY,gBAAgB,QAChD,MAAM,aACN;AACN;AAMO,SAAS,oBACd,QAC6B;AAC7B,SAAO,OAAO,WAAW,YAAY,gBAAgB,SACjD,OAAO,aACP;AACN;AAKO,SAAS,gBAAgB,OAAuB;AACrD,SACE,OAAO,UAAU,YACjB,gBAAgB,SAChB,MAAM,aAAa;AAEvB;AAKO,SAAS,oBACd,YACO;AACP,SAAO,EAAE,YAAY,UAAU,KAAA;AACjC;AAEA,SAAS,gBACP,OACA,OACiC;AACjC,MAAI,MAAM,KAAK,UAAU,OAAO;AAC9B,WAAO,MAAM;AAAA,EACf;AAEA,aAAW,QAAQ,MAAM,QAAQ,CAAA,GAAI;AACnC,QAAI,KAAK,KAAK,UAAU,OAAO;AAC7B,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;AAOO,SAAS,UACd,OACA,KACA,YACwD;AACxD,MAAI,IAAI,KAAK,WAAW,GAAG;AACzB;AAAA,EACF;AAEA,MAAI,IAAI,KAAK,WAAW,GAAG;AAEzB,UAAM,QAAQ,IAAI,KAAK,CAAC;AAExB,QAAI,MAAM,QAAQ;AAChB,YAAM,gBAAgB,MAAM,OAAO,KAAK;AACxC,UAAI,iBAAiB,cAAc,SAAS,OAAO;AACjD,eAAO,UAAU,OAAO,eAAe,UAAU;AAAA,MACnD;AAAA,IACF;AAMA,WAAO,EAAE,YAAY,MAAM,CAAC,KAAK,EAAA;AAAA,EACnC;AAEA,MAAI,IAAI,KAAK,SAAS,GAAG;AAEvB,UAAM,CAAC,OAAO,GAAG,IAAI,IAAI,IAAI;AAC7B,UAAM,WAAW,gBAAgB,OAAO,KAAM;AAC9C,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,QAAI,SAAS,SAAS,YAAY;AAChC,aAAO,UAAU,SAAS,OAAO,IAAI,QAAQ,IAAI,GAAG,UAAU;AAAA,IAChE,OAAO;AAIL,aAAO,EAAE,YAAY,SAAS,YAAY,MAAM,KAAA;AAAA,IAClD;AAAA,EACF;AACF;"}
|
package/package.json
CHANGED
|
@@ -127,7 +127,8 @@ export function compileQuery(
|
|
|
127
127
|
callbacks,
|
|
128
128
|
lazyCollections,
|
|
129
129
|
optimizableOrderByCollections,
|
|
130
|
-
rawQuery
|
|
130
|
+
rawQuery,
|
|
131
|
+
compileQuery
|
|
131
132
|
)
|
|
132
133
|
}
|
|
133
134
|
|
|
@@ -512,3 +513,5 @@ export function followRef(
|
|
|
512
513
|
}
|
|
513
514
|
}
|
|
514
515
|
}
|
|
516
|
+
|
|
517
|
+
export type CompileQueryFn = typeof compileQuery
|
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
UnsupportedJoinTypeError,
|
|
18
18
|
} from "../../errors.js"
|
|
19
19
|
import { ensureIndexForField } from "../../indexes/auto-index.js"
|
|
20
|
-
import { PropRef } from "../ir.js"
|
|
20
|
+
import { PropRef, followRef } from "../ir.js"
|
|
21
21
|
import { inArray } from "../builder/functions.js"
|
|
22
22
|
import { compileExpression } from "./evaluators.js"
|
|
23
|
-
import {
|
|
23
|
+
import type { CompileQueryFn } from "./index.js"
|
|
24
24
|
import type { OrderByOptimizationInfo } from "./order-by.js"
|
|
25
25
|
import type {
|
|
26
26
|
BasicExpression,
|
|
@@ -62,7 +62,8 @@ export function processJoins(
|
|
|
62
62
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
63
63
|
lazyCollections: Set<string>,
|
|
64
64
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
65
|
-
rawQuery: QueryIR
|
|
65
|
+
rawQuery: QueryIR,
|
|
66
|
+
onCompileSubquery: CompileQueryFn
|
|
66
67
|
): NamespacedAndKeyedStream {
|
|
67
68
|
let resultPipeline = pipeline
|
|
68
69
|
|
|
@@ -81,7 +82,8 @@ export function processJoins(
|
|
|
81
82
|
callbacks,
|
|
82
83
|
lazyCollections,
|
|
83
84
|
optimizableOrderByCollections,
|
|
84
|
-
rawQuery
|
|
85
|
+
rawQuery,
|
|
86
|
+
onCompileSubquery
|
|
85
87
|
)
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -105,7 +107,8 @@ function processJoin(
|
|
|
105
107
|
callbacks: Record<string, LazyCollectionCallbacks>,
|
|
106
108
|
lazyCollections: Set<string>,
|
|
107
109
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
108
|
-
rawQuery: QueryIR
|
|
110
|
+
rawQuery: QueryIR,
|
|
111
|
+
onCompileSubquery: CompileQueryFn
|
|
109
112
|
): NamespacedAndKeyedStream {
|
|
110
113
|
// Get the joined table alias and input stream
|
|
111
114
|
const {
|
|
@@ -121,7 +124,8 @@ function processJoin(
|
|
|
121
124
|
lazyCollections,
|
|
122
125
|
optimizableOrderByCollections,
|
|
123
126
|
cache,
|
|
124
|
-
queryMapping
|
|
127
|
+
queryMapping,
|
|
128
|
+
onCompileSubquery
|
|
125
129
|
)
|
|
126
130
|
|
|
127
131
|
// Add the joined table to the tables map
|
|
@@ -392,7 +396,8 @@ function processJoinSource(
|
|
|
392
396
|
lazyCollections: Set<string>,
|
|
393
397
|
optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
|
|
394
398
|
cache: QueryCache,
|
|
395
|
-
queryMapping: QueryMapping
|
|
399
|
+
queryMapping: QueryMapping,
|
|
400
|
+
onCompileSubquery: CompileQueryFn
|
|
396
401
|
): { alias: string; input: KeyedStream; collectionId: string } {
|
|
397
402
|
switch (from.type) {
|
|
398
403
|
case `collectionRef`: {
|
|
@@ -407,7 +412,7 @@ function processJoinSource(
|
|
|
407
412
|
const originalQuery = queryMapping.get(from.query) || from.query
|
|
408
413
|
|
|
409
414
|
// Recursively compile the sub-query with cache
|
|
410
|
-
const subQueryResult =
|
|
415
|
+
const subQueryResult = onCompileSubquery(
|
|
411
416
|
originalQuery,
|
|
412
417
|
allInputs,
|
|
413
418
|
collections,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { orderByWithFractionalIndex } from "@tanstack/db-ivm"
|
|
2
2
|
import { defaultComparator, makeComparator } from "../../utils/comparison.js"
|
|
3
|
-
import { PropRef } from "../ir.js"
|
|
3
|
+
import { PropRef, followRef } from "../ir.js"
|
|
4
4
|
import { ensureIndexForField } from "../../indexes/auto-index.js"
|
|
5
5
|
import { findIndexForField } from "../../utils/index-optimization.js"
|
|
6
6
|
import { compileExpression } from "./evaluators.js"
|
|
7
7
|
import { replaceAggregatesByRefs } from "./group-by.js"
|
|
8
|
-
import { followRef } from "./index.js"
|
|
9
8
|
import type { CompiledSingleRowExpression } from "./evaluators.js"
|
|
10
9
|
import type { OrderByClause, QueryIR, Select } from "../ir.js"
|
|
11
10
|
import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
|
package/src/query/ir.ts
CHANGED
|
@@ -3,7 +3,7 @@ This is the intermediate representation of the query.
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { CompareOptions } from "./builder/types"
|
|
6
|
-
import type { CollectionImpl } from "../collection/index.js"
|
|
6
|
+
import type { Collection, CollectionImpl } from "../collection/index.js"
|
|
7
7
|
import type { NamespacedRow } from "../types"
|
|
8
8
|
|
|
9
9
|
export interface QueryIR {
|
|
@@ -188,3 +188,69 @@ export function createResidualWhere(
|
|
|
188
188
|
): Where {
|
|
189
189
|
return { expression, residual: true }
|
|
190
190
|
}
|
|
191
|
+
|
|
192
|
+
function getRefFromAlias(
|
|
193
|
+
query: QueryIR,
|
|
194
|
+
alias: string
|
|
195
|
+
): CollectionRef | QueryRef | void {
|
|
196
|
+
if (query.from.alias === alias) {
|
|
197
|
+
return query.from
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const join of query.join || []) {
|
|
201
|
+
if (join.from.alias === alias) {
|
|
202
|
+
return join.from
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Follows the given reference in a query
|
|
209
|
+
* until its finds the root field the reference points to.
|
|
210
|
+
* @returns The collection, its alias, and the path to the root field in this collection
|
|
211
|
+
*/
|
|
212
|
+
export function followRef(
|
|
213
|
+
query: QueryIR,
|
|
214
|
+
ref: PropRef<any>,
|
|
215
|
+
collection: Collection
|
|
216
|
+
): { collection: Collection; path: Array<string> } | void {
|
|
217
|
+
if (ref.path.length === 0) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (ref.path.length === 1) {
|
|
222
|
+
// This field should be part of this collection
|
|
223
|
+
const field = ref.path[0]!
|
|
224
|
+
// is it part of the select clause?
|
|
225
|
+
if (query.select) {
|
|
226
|
+
const selectedField = query.select[field]
|
|
227
|
+
if (selectedField && selectedField.type === `ref`) {
|
|
228
|
+
return followRef(query, selectedField, collection)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Either this field is not part of the select clause
|
|
233
|
+
// and thus it must be part of the collection itself
|
|
234
|
+
// or it is part of the select but is not a reference
|
|
235
|
+
// so we can stop here and don't have to follow it
|
|
236
|
+
return { collection, path: [field] }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (ref.path.length > 1) {
|
|
240
|
+
// This is a nested field
|
|
241
|
+
const [alias, ...rest] = ref.path
|
|
242
|
+
const aliasRef = getRefFromAlias(query, alias!)
|
|
243
|
+
if (!aliasRef) {
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (aliasRef.type === `queryRef`) {
|
|
248
|
+
return followRef(aliasRef.query, new PropRef(rest), collection)
|
|
249
|
+
} else {
|
|
250
|
+
// This is a reference to a collection
|
|
251
|
+
// we can't follow it further
|
|
252
|
+
// so the field must be on the collection itself
|
|
253
|
+
return { collection: aliasRef.collection, path: rest }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|