@tanstack/db 0.0.14 → 0.0.15
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.cjs +117 -104
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +18 -21
- package/dist/cjs/index.cjs +35 -13
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +0 -1
- package/dist/cjs/query/builder/functions.cjs +107 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -0
- package/dist/cjs/query/builder/functions.d.cts +38 -0
- package/dist/cjs/query/builder/index.cjs +499 -0
- package/dist/cjs/query/builder/index.cjs.map +1 -0
- package/dist/cjs/query/builder/index.d.cts +324 -0
- package/dist/cjs/query/builder/ref-proxy.cjs +96 -0
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -0
- package/dist/cjs/query/builder/ref-proxy.d.cts +28 -0
- package/dist/cjs/query/builder/types.d.cts +80 -0
- package/dist/cjs/query/compiler/evaluators.cjs +261 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -0
- package/dist/cjs/query/compiler/evaluators.d.cts +11 -0
- package/dist/cjs/query/compiler/group-by.cjs +271 -0
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -0
- package/dist/cjs/query/compiler/group-by.d.cts +7 -0
- package/dist/cjs/query/compiler/index.cjs +181 -0
- package/dist/cjs/query/compiler/index.cjs.map +1 -0
- package/dist/cjs/query/compiler/index.d.cts +15 -0
- package/dist/cjs/query/compiler/joins.cjs +116 -0
- package/dist/cjs/query/compiler/joins.cjs.map +1 -0
- package/dist/cjs/query/compiler/joins.d.cts +11 -0
- package/dist/cjs/query/compiler/order-by.cjs +89 -0
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -0
- package/dist/cjs/query/compiler/order-by.d.cts +9 -0
- package/dist/cjs/query/compiler/select.cjs +57 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -0
- package/dist/cjs/query/compiler/select.d.cts +15 -0
- package/dist/cjs/query/index.d.cts +6 -5
- package/dist/cjs/query/ir.cjs +57 -0
- package/dist/cjs/query/ir.cjs.map +1 -0
- package/dist/cjs/query/ir.d.cts +81 -0
- package/dist/cjs/query/live-query-collection.cjs +224 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -0
- package/dist/cjs/query/live-query-collection.d.cts +124 -0
- package/dist/cjs/transactions.cjs +20 -13
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/transactions.d.cts +10 -1
- package/dist/cjs/types.d.cts +13 -0
- package/dist/esm/collection.d.ts +18 -21
- package/dist/esm/collection.js +118 -105
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.d.ts +0 -1
- package/dist/esm/index.js +34 -12
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +38 -0
- package/dist/esm/query/builder/functions.js +107 -0
- package/dist/esm/query/builder/functions.js.map +1 -0
- package/dist/esm/query/builder/index.d.ts +324 -0
- package/dist/esm/query/builder/index.js +499 -0
- package/dist/esm/query/builder/index.js.map +1 -0
- package/dist/esm/query/builder/ref-proxy.d.ts +28 -0
- package/dist/esm/query/builder/ref-proxy.js +96 -0
- package/dist/esm/query/builder/ref-proxy.js.map +1 -0
- package/dist/esm/query/builder/types.d.ts +80 -0
- package/dist/esm/query/compiler/evaluators.d.ts +11 -0
- package/dist/esm/query/compiler/evaluators.js +261 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -0
- package/dist/esm/query/compiler/group-by.d.ts +7 -0
- package/dist/esm/query/compiler/group-by.js +271 -0
- package/dist/esm/query/compiler/group-by.js.map +1 -0
- package/dist/esm/query/compiler/index.d.ts +15 -0
- package/dist/esm/query/compiler/index.js +181 -0
- package/dist/esm/query/compiler/index.js.map +1 -0
- package/dist/esm/query/compiler/joins.d.ts +11 -0
- package/dist/esm/query/compiler/joins.js +116 -0
- package/dist/esm/query/compiler/joins.js.map +1 -0
- package/dist/esm/query/compiler/order-by.d.ts +9 -0
- package/dist/esm/query/compiler/order-by.js +89 -0
- package/dist/esm/query/compiler/order-by.js.map +1 -0
- package/dist/esm/query/compiler/select.d.ts +15 -0
- package/dist/esm/query/compiler/select.js +57 -0
- package/dist/esm/query/compiler/select.js.map +1 -0
- package/dist/esm/query/index.d.ts +6 -5
- package/dist/esm/query/ir.d.ts +81 -0
- package/dist/esm/query/ir.js +57 -0
- package/dist/esm/query/ir.js.map +1 -0
- package/dist/esm/query/live-query-collection.d.ts +124 -0
- package/dist/esm/query/live-query-collection.js +224 -0
- package/dist/esm/query/live-query-collection.js.map +1 -0
- package/dist/esm/transactions.d.ts +10 -1
- package/dist/esm/transactions.js +20 -13
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +13 -0
- package/package.json +3 -4
- package/src/collection.ts +152 -129
- package/src/index.ts +0 -1
- package/src/query/builder/functions.ts +267 -0
- package/src/query/builder/index.ts +648 -0
- package/src/query/builder/ref-proxy.ts +156 -0
- package/src/query/builder/types.ts +278 -0
- package/src/query/compiler/evaluators.ts +315 -0
- package/src/query/compiler/group-by.ts +428 -0
- package/src/query/compiler/index.ts +276 -0
- package/src/query/compiler/joins.ts +228 -0
- package/src/query/compiler/order-by.ts +139 -0
- package/src/query/compiler/select.ts +173 -0
- package/src/query/index.ts +64 -5
- package/src/query/ir.ts +128 -0
- package/src/query/live-query-collection.ts +509 -0
- package/src/transactions.ts +27 -16
- package/src/types.ts +15 -0
- package/dist/cjs/query/compiled-query.cjs +0 -160
- package/dist/cjs/query/compiled-query.cjs.map +0 -1
- package/dist/cjs/query/compiled-query.d.cts +0 -20
- package/dist/cjs/query/evaluators.cjs +0 -161
- package/dist/cjs/query/evaluators.cjs.map +0 -1
- package/dist/cjs/query/evaluators.d.cts +0 -14
- package/dist/cjs/query/extractors.cjs +0 -122
- package/dist/cjs/query/extractors.cjs.map +0 -1
- package/dist/cjs/query/extractors.d.cts +0 -22
- package/dist/cjs/query/functions.cjs +0 -152
- package/dist/cjs/query/functions.cjs.map +0 -1
- package/dist/cjs/query/functions.d.cts +0 -21
- package/dist/cjs/query/group-by.cjs +0 -88
- package/dist/cjs/query/group-by.cjs.map +0 -1
- package/dist/cjs/query/group-by.d.cts +0 -40
- package/dist/cjs/query/joins.cjs +0 -141
- package/dist/cjs/query/joins.cjs.map +0 -1
- package/dist/cjs/query/joins.d.cts +0 -14
- package/dist/cjs/query/order-by.cjs +0 -185
- package/dist/cjs/query/order-by.cjs.map +0 -1
- package/dist/cjs/query/order-by.d.cts +0 -3
- package/dist/cjs/query/pipeline-compiler.cjs +0 -89
- package/dist/cjs/query/pipeline-compiler.cjs.map +0 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +0 -10
- package/dist/cjs/query/query-builder.cjs +0 -307
- package/dist/cjs/query/query-builder.cjs.map +0 -1
- package/dist/cjs/query/query-builder.d.cts +0 -225
- package/dist/cjs/query/schema.d.cts +0 -100
- package/dist/cjs/query/select.cjs +0 -130
- package/dist/cjs/query/select.cjs.map +0 -1
- package/dist/cjs/query/select.d.cts +0 -3
- package/dist/cjs/query/types.d.cts +0 -189
- package/dist/cjs/query/utils.cjs +0 -154
- package/dist/cjs/query/utils.cjs.map +0 -1
- package/dist/cjs/query/utils.d.cts +0 -37
- package/dist/cjs/utils.cjs +0 -17
- package/dist/cjs/utils.cjs.map +0 -1
- package/dist/cjs/utils.d.cts +0 -3
- package/dist/esm/query/compiled-query.d.ts +0 -20
- package/dist/esm/query/compiled-query.js +0 -160
- package/dist/esm/query/compiled-query.js.map +0 -1
- package/dist/esm/query/evaluators.d.ts +0 -14
- package/dist/esm/query/evaluators.js +0 -161
- package/dist/esm/query/evaluators.js.map +0 -1
- package/dist/esm/query/extractors.d.ts +0 -22
- package/dist/esm/query/extractors.js +0 -122
- package/dist/esm/query/extractors.js.map +0 -1
- package/dist/esm/query/functions.d.ts +0 -21
- package/dist/esm/query/functions.js +0 -152
- package/dist/esm/query/functions.js.map +0 -1
- package/dist/esm/query/group-by.d.ts +0 -40
- package/dist/esm/query/group-by.js +0 -88
- package/dist/esm/query/group-by.js.map +0 -1
- package/dist/esm/query/joins.d.ts +0 -14
- package/dist/esm/query/joins.js +0 -141
- package/dist/esm/query/joins.js.map +0 -1
- package/dist/esm/query/order-by.d.ts +0 -3
- package/dist/esm/query/order-by.js +0 -185
- package/dist/esm/query/order-by.js.map +0 -1
- package/dist/esm/query/pipeline-compiler.d.ts +0 -10
- package/dist/esm/query/pipeline-compiler.js +0 -89
- package/dist/esm/query/pipeline-compiler.js.map +0 -1
- package/dist/esm/query/query-builder.d.ts +0 -225
- package/dist/esm/query/query-builder.js +0 -307
- package/dist/esm/query/query-builder.js.map +0 -1
- package/dist/esm/query/schema.d.ts +0 -100
- package/dist/esm/query/select.d.ts +0 -3
- package/dist/esm/query/select.js +0 -130
- package/dist/esm/query/select.js.map +0 -1
- package/dist/esm/query/types.d.ts +0 -189
- package/dist/esm/query/utils.d.ts +0 -37
- package/dist/esm/query/utils.js +0 -154
- package/dist/esm/query/utils.js.map +0 -1
- package/dist/esm/utils.d.ts +0 -3
- package/dist/esm/utils.js +0 -17
- package/dist/esm/utils.js.map +0 -1
- package/src/query/compiled-query.ts +0 -234
- package/src/query/evaluators.ts +0 -250
- package/src/query/extractors.ts +0 -214
- package/src/query/functions.ts +0 -297
- package/src/query/group-by.ts +0 -139
- package/src/query/joins.ts +0 -260
- package/src/query/order-by.ts +0 -264
- package/src/query/pipeline-compiler.ts +0 -149
- package/src/query/query-builder.ts +0 -902
- package/src/query/schema.ts +0 -268
- package/src/query/select.ts +0 -208
- package/src/query/types.ts +0 -418
- package/src/query/utils.ts +0 -245
- package/src/utils.ts +0 -15
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group-by.js","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import { filter, groupBy, groupByOperators, map } from \"@electric-sql/d2mini\"\nimport { Func, Ref } from \"../ir.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type {\n Aggregate,\n BasicExpression,\n GroupBy,\n Having,\n Select,\n} from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\n\nconst { sum, count, avg, min, max } = groupByOperators\n\n/**\n * Interface for caching the mapping between GROUP BY expressions and SELECT expressions\n */\ninterface GroupBySelectMapping {\n selectToGroupByIndex: Map<string, number> // Maps SELECT alias to GROUP BY expression index\n groupByExpressions: Array<any> // The GROUP BY expressions for reference\n}\n\n/**\n * Validates that all non-aggregate expressions in SELECT are present in GROUP BY\n * and creates a cached mapping for efficient lookup during processing\n */\nfunction validateAndCreateMapping(\n groupByClause: GroupBy,\n selectClause?: Select\n): GroupBySelectMapping {\n const selectToGroupByIndex = new Map<string, number>()\n const groupByExpressions = [...groupByClause]\n\n if (!selectClause) {\n return { selectToGroupByIndex, groupByExpressions }\n }\n\n // Validate each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n // Aggregate expressions are allowed and don't need to be in GROUP BY\n continue\n }\n\n // Non-aggregate expression must be in GROUP BY\n const groupIndex = groupByExpressions.findIndex((groupExpr) =>\n expressionsEqual(expr, groupExpr)\n )\n\n if (groupIndex === -1) {\n throw new Error(\n `Non-aggregate expression '${alias}' in SELECT must also appear in GROUP BY clause`\n )\n }\n\n // Cache the mapping\n selectToGroupByIndex.set(alias, groupIndex)\n }\n\n return { selectToGroupByIndex, groupByExpressions }\n}\n\n/**\n * Processes the GROUP BY clause with optional HAVING and SELECT\n * Works with the new __select_results structure from early SELECT processing\n */\nexport function processGroupBy(\n pipeline: NamespacedAndKeyedStream,\n groupByClause: GroupBy,\n havingClauses?: Array<Having>,\n selectClause?: Select,\n fnHavingClauses?: Array<(row: any) => any>\n): NamespacedAndKeyedStream {\n // Handle empty GROUP BY (single-group aggregation)\n if (groupByClause.length === 0) {\n // For single-group aggregation, create a single group with all data\n const aggregates: Record<string, any> = {}\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n const aggExpr = expr\n aggregates[alias] = getAggregateFunction(aggExpr)\n }\n }\n }\n\n // Use a constant key for single group\n const keyExtractor = () => ({ __singleGroup: true })\n\n // Apply the groupBy operator with single group\n pipeline = pipeline.pipe(\n groupBy(keyExtractor, aggregates)\n ) as NamespacedAndKeyedStream\n\n // Update __select_results to include aggregate values\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing __select_results from early SELECT processing\n const selectResults = (aggregatedRow as any).__select_results || {}\n const finalResults: Record<string, any> = { ...selectResults }\n\n if (selectClause) {\n // Update with aggregate results\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n finalResults[alias] = aggregatedRow[alias]\n }\n // Non-aggregates keep their original values from early SELECT processing\n }\n }\n\n // Use a single key for the result and update __select_results\n return [\n `single_group`,\n {\n ...aggregatedRow,\n __select_results: finalResults,\n },\n ] as [unknown, Record<string, any>]\n })\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const transformedHavingClause = transformHavingClause(\n havingClause,\n selectClause || {}\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return compiledHaving(namespacedRow)\n })\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n return pipeline\n }\n\n // Multi-group aggregation logic...\n // Validate and create mapping for non-aggregate expressions in SELECT\n const mapping = validateAndCreateMapping(groupByClause, selectClause)\n\n // Pre-compile groupBy expressions\n const compiledGroupByExpressions = groupByClause.map(compileExpression)\n\n // Create a key extractor function using simple __key_X format\n const keyExtractor = ([, row]: [\n string,\n NamespacedRow & { __select_results?: any },\n ]) => {\n // Use the original namespaced row for GROUP BY expressions, not __select_results\n const namespacedRow = { ...row }\n delete (namespacedRow as any).__select_results\n\n const key: Record<string, unknown> = {}\n\n // Use simple __key_X format for each groupBy expression\n for (let i = 0; i < groupByClause.length; i++) {\n const compiledExpr = compiledGroupByExpressions[i]!\n const value = compiledExpr(namespacedRow)\n key[`__key_${i}`] = value\n }\n\n return key\n }\n\n // Create aggregate functions for any aggregated columns in the SELECT clause\n const aggregates: Record<string, any> = {}\n\n if (selectClause) {\n // Scan the SELECT clause for aggregate functions\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type === `agg`) {\n const aggExpr = expr\n aggregates[alias] = getAggregateFunction(aggExpr)\n }\n }\n }\n\n // Apply the groupBy operator\n pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))\n\n // Update __select_results to handle GROUP BY results\n pipeline = pipeline.pipe(\n map(([, aggregatedRow]) => {\n // Start with the existing __select_results from early SELECT processing\n const selectResults = (aggregatedRow as any).__select_results || {}\n const finalResults: Record<string, any> = {}\n\n if (selectClause) {\n // Process each SELECT expression\n for (const [alias, expr] of Object.entries(selectClause)) {\n if (expr.type !== `agg`) {\n // Use cached mapping to get the corresponding __key_X for non-aggregates\n const groupIndex = mapping.selectToGroupByIndex.get(alias)\n if (groupIndex !== undefined) {\n finalResults[alias] = aggregatedRow[`__key_${groupIndex}`]\n } else {\n // Fallback to original SELECT results\n finalResults[alias] = selectResults[alias]\n }\n } else {\n // Use aggregate results\n finalResults[alias] = aggregatedRow[alias]\n }\n }\n } else {\n // No SELECT clause - just use the group keys\n for (let i = 0; i < groupByClause.length; i++) {\n finalResults[`__key_${i}`] = aggregatedRow[`__key_${i}`]\n }\n }\n\n // Generate a simple key for the live collection using group values\n let finalKey: unknown\n if (groupByClause.length === 1) {\n finalKey = aggregatedRow[`__key_0`]\n } else {\n const keyParts: Array<unknown> = []\n for (let i = 0; i < groupByClause.length; i++) {\n keyParts.push(aggregatedRow[`__key_${i}`])\n }\n finalKey = JSON.stringify(keyParts)\n }\n\n return [\n finalKey,\n {\n ...aggregatedRow,\n __select_results: finalResults,\n },\n ] as [unknown, Record<string, any>]\n })\n )\n\n // Apply HAVING clauses if present\n if (havingClauses && havingClauses.length > 0) {\n for (const havingClause of havingClauses) {\n const transformedHavingClause = transformHavingClause(\n havingClause,\n selectClause || {}\n )\n const compiledHaving = compileExpression(transformedHavingClause)\n\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return compiledHaving(namespacedRow)\n })\n )\n }\n }\n\n // Apply functional HAVING clauses if present\n if (fnHavingClauses && fnHavingClauses.length > 0) {\n for (const fnHaving of fnHavingClauses) {\n pipeline = pipeline.pipe(\n filter(([, row]) => {\n // Create a namespaced row structure for functional HAVING evaluation\n const namespacedRow = { result: (row as any).__select_results }\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n return pipeline\n}\n\n/**\n * Helper function to check if two expressions are equal\n */\nfunction expressionsEqual(expr1: any, expr2: any): boolean {\n if (!expr1 || !expr2) return false\n if (expr1.type !== expr2.type) return false\n\n switch (expr1.type) {\n case `ref`:\n // Compare paths as arrays\n if (!expr1.path || !expr2.path) return false\n if (expr1.path.length !== expr2.path.length) return false\n return expr1.path.every(\n (segment: string, i: number) => segment === expr2.path[i]\n )\n case `val`:\n return expr1.value === expr2.value\n case `func`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i])\n )\n )\n case `agg`:\n return (\n expr1.name === expr2.name &&\n expr1.args?.length === expr2.args?.length &&\n (expr1.args || []).every((arg: any, i: number) =>\n expressionsEqual(arg, expr2.args[i])\n )\n )\n default:\n return false\n }\n}\n\n/**\n * Helper function to get an aggregate function based on the Agg expression\n */\nfunction getAggregateFunction(aggExpr: Aggregate) {\n // Pre-compile the value extractor expression\n const compiledExpr = compileExpression(aggExpr.args[0]!)\n\n // Create a value extractor function for the expression to aggregate\n const valueExtractor = ([, namespacedRow]: [string, NamespacedRow]) => {\n const value = compiledExpr(namespacedRow)\n // Ensure we return a number for numeric aggregate functions\n return typeof value === `number` ? value : value != null ? Number(value) : 0\n }\n\n // Return the appropriate aggregate function\n switch (aggExpr.name.toLowerCase()) {\n case `sum`:\n return sum(valueExtractor)\n case `count`:\n return count() // count() doesn't need a value extractor\n case `avg`:\n return avg(valueExtractor)\n case `min`:\n return min(valueExtractor)\n case `max`:\n return max(valueExtractor)\n default:\n throw new Error(`Unsupported aggregate function: ${aggExpr.name}`)\n }\n}\n\n/**\n * Transforms a HAVING clause to replace Agg expressions with references to computed values\n */\nfunction transformHavingClause(\n havingExpr: BasicExpression | Aggregate,\n selectClause: Select\n): BasicExpression {\n switch (havingExpr.type) {\n case `agg`: {\n const aggExpr = havingExpr\n // Find matching aggregate in SELECT clause\n for (const [alias, selectExpr] of Object.entries(selectClause)) {\n if (selectExpr.type === `agg` && aggregatesEqual(aggExpr, selectExpr)) {\n // Replace with a reference to the computed aggregate\n return new Ref([`result`, alias])\n }\n }\n // If no matching aggregate found in SELECT, throw error\n throw new Error(\n `Aggregate function in HAVING clause must also be in SELECT clause: ${aggExpr.name}`\n )\n }\n\n case `func`: {\n const funcExpr = havingExpr\n // Transform function arguments recursively\n const transformedArgs = funcExpr.args.map(\n (arg: BasicExpression | Aggregate) =>\n transformHavingClause(arg, selectClause)\n )\n return new Func(funcExpr.name, transformedArgs)\n }\n\n case `ref`: {\n const refExpr = havingExpr\n // Check if this is a direct reference to a SELECT alias\n if (refExpr.path.length === 1) {\n const alias = refExpr.path[0]!\n if (selectClause[alias]) {\n // This is a reference to a SELECT alias, convert to result.alias\n return new Ref([`result`, alias])\n }\n }\n // Return as-is for other refs\n return havingExpr as BasicExpression\n }\n\n case `val`:\n // Return as-is\n return havingExpr as BasicExpression\n\n default:\n throw new Error(\n `Unknown expression type in HAVING clause: ${(havingExpr as any).type}`\n )\n }\n}\n\n/**\n * Checks if two aggregate expressions are equal\n */\nfunction aggregatesEqual(agg1: Aggregate, agg2: Aggregate): boolean {\n return (\n agg1.name === agg2.name &&\n agg1.args.length === agg2.args.length &&\n agg1.args.every((arg, i) => expressionsEqual(arg, agg2.args[i]))\n )\n}\n"],"names":["aggregates","keyExtractor"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { QueryIR } from '../ir.js';
|
|
2
|
+
import { KeyedStream, ResultStream } from '../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Cache for compiled subqueries to avoid duplicate compilation
|
|
5
|
+
*/
|
|
6
|
+
type QueryCache = WeakMap<QueryIR, ResultStream>;
|
|
7
|
+
/**
|
|
8
|
+
* Compiles a query2 IR into a D2 pipeline
|
|
9
|
+
* @param query The query IR to compile
|
|
10
|
+
* @param inputs Mapping of collection names to input streams
|
|
11
|
+
* @param cache Optional cache for compiled subqueries (used internally for recursion)
|
|
12
|
+
* @returns A stream builder representing the compiled query
|
|
13
|
+
*/
|
|
14
|
+
export declare function compileQuery(query: QueryIR, inputs: Record<string, KeyedStream>, cache?: QueryCache): ResultStream;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { map, filter } from "@electric-sql/d2mini";
|
|
2
|
+
import { compileExpression } from "./evaluators.js";
|
|
3
|
+
import { processJoins } from "./joins.js";
|
|
4
|
+
import { processGroupBy } from "./group-by.js";
|
|
5
|
+
import { processOrderBy } from "./order-by.js";
|
|
6
|
+
import { processSelectToResults } from "./select.js";
|
|
7
|
+
function compileQuery(query, inputs, cache = /* @__PURE__ */ new WeakMap()) {
|
|
8
|
+
const cachedResult = cache.get(query);
|
|
9
|
+
if (cachedResult) {
|
|
10
|
+
return cachedResult;
|
|
11
|
+
}
|
|
12
|
+
const allInputs = { ...inputs };
|
|
13
|
+
const tables = {};
|
|
14
|
+
const { alias: mainTableAlias, input: mainInput } = processFrom(
|
|
15
|
+
query.from,
|
|
16
|
+
allInputs,
|
|
17
|
+
cache
|
|
18
|
+
);
|
|
19
|
+
tables[mainTableAlias] = mainInput;
|
|
20
|
+
let pipeline = mainInput.pipe(
|
|
21
|
+
map(([key, row]) => {
|
|
22
|
+
const ret = [key, { [mainTableAlias]: row }];
|
|
23
|
+
return ret;
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
if (query.join && query.join.length > 0) {
|
|
27
|
+
pipeline = processJoins(
|
|
28
|
+
pipeline,
|
|
29
|
+
query.join,
|
|
30
|
+
tables,
|
|
31
|
+
mainTableAlias,
|
|
32
|
+
allInputs,
|
|
33
|
+
cache
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (query.where && query.where.length > 0) {
|
|
37
|
+
const compiledWheres = query.where.map((where) => compileExpression(where));
|
|
38
|
+
for (const compiledWhere of compiledWheres) {
|
|
39
|
+
pipeline = pipeline.pipe(
|
|
40
|
+
filter(([_key, namespacedRow]) => {
|
|
41
|
+
return compiledWhere(namespacedRow);
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (query.fnWhere && query.fnWhere.length > 0) {
|
|
47
|
+
for (const fnWhere of query.fnWhere) {
|
|
48
|
+
pipeline = pipeline.pipe(
|
|
49
|
+
filter(([_key, namespacedRow]) => {
|
|
50
|
+
return fnWhere(namespacedRow);
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (query.fnSelect) {
|
|
56
|
+
pipeline = pipeline.pipe(
|
|
57
|
+
map(([key, namespacedRow]) => {
|
|
58
|
+
const selectResults = query.fnSelect(namespacedRow);
|
|
59
|
+
return [
|
|
60
|
+
key,
|
|
61
|
+
{
|
|
62
|
+
...namespacedRow,
|
|
63
|
+
__select_results: selectResults
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
} else if (query.select) {
|
|
69
|
+
pipeline = processSelectToResults(pipeline, query.select);
|
|
70
|
+
} else {
|
|
71
|
+
pipeline = pipeline.pipe(
|
|
72
|
+
map(([key, namespacedRow]) => {
|
|
73
|
+
const selectResults = !query.join && !query.groupBy ? namespacedRow[mainTableAlias] : namespacedRow;
|
|
74
|
+
return [
|
|
75
|
+
key,
|
|
76
|
+
{
|
|
77
|
+
...namespacedRow,
|
|
78
|
+
__select_results: selectResults
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (query.groupBy && query.groupBy.length > 0) {
|
|
85
|
+
pipeline = processGroupBy(
|
|
86
|
+
pipeline,
|
|
87
|
+
query.groupBy,
|
|
88
|
+
query.having,
|
|
89
|
+
query.select,
|
|
90
|
+
query.fnHaving
|
|
91
|
+
);
|
|
92
|
+
} else if (query.select) {
|
|
93
|
+
const hasAggregates = Object.values(query.select).some(
|
|
94
|
+
(expr) => expr.type === `agg`
|
|
95
|
+
);
|
|
96
|
+
if (hasAggregates) {
|
|
97
|
+
pipeline = processGroupBy(
|
|
98
|
+
pipeline,
|
|
99
|
+
[],
|
|
100
|
+
// Empty group by means single group
|
|
101
|
+
query.having,
|
|
102
|
+
query.select,
|
|
103
|
+
query.fnHaving
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (query.having && (!query.groupBy || query.groupBy.length === 0)) {
|
|
108
|
+
const hasAggregates = query.select ? Object.values(query.select).some((expr) => expr.type === `agg`) : false;
|
|
109
|
+
if (!hasAggregates) {
|
|
110
|
+
throw new Error(`HAVING clause requires GROUP BY clause`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (query.fnHaving && query.fnHaving.length > 0 && (!query.groupBy || query.groupBy.length === 0)) {
|
|
114
|
+
for (const fnHaving of query.fnHaving) {
|
|
115
|
+
pipeline = pipeline.pipe(
|
|
116
|
+
filter(([_key, namespacedRow]) => {
|
|
117
|
+
return fnHaving(namespacedRow);
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (query.orderBy && query.orderBy.length > 0) {
|
|
123
|
+
const orderedPipeline = processOrderBy(
|
|
124
|
+
pipeline,
|
|
125
|
+
query.orderBy,
|
|
126
|
+
query.limit,
|
|
127
|
+
query.offset
|
|
128
|
+
);
|
|
129
|
+
const resultPipeline2 = orderedPipeline.pipe(
|
|
130
|
+
map(([key, [row, orderByIndex]]) => {
|
|
131
|
+
const finalResults = row.__select_results;
|
|
132
|
+
return [key, [finalResults, orderByIndex]];
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
const result2 = resultPipeline2;
|
|
136
|
+
cache.set(query, result2);
|
|
137
|
+
return result2;
|
|
138
|
+
} else if (query.limit !== void 0 || query.offset !== void 0) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const resultPipeline = pipeline.pipe(
|
|
144
|
+
map(([key, row]) => {
|
|
145
|
+
const finalResults = row.__select_results;
|
|
146
|
+
return [key, [finalResults, void 0]];
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
const result = resultPipeline;
|
|
150
|
+
cache.set(query, result);
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
function processFrom(from, allInputs, cache) {
|
|
154
|
+
switch (from.type) {
|
|
155
|
+
case `collectionRef`: {
|
|
156
|
+
const input = allInputs[from.collection.id];
|
|
157
|
+
if (!input) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Input for collection "${from.collection.id}" not found in inputs map`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return { alias: from.alias, input };
|
|
163
|
+
}
|
|
164
|
+
case `queryRef`: {
|
|
165
|
+
const subQueryInput = compileQuery(from.query, allInputs, cache);
|
|
166
|
+
const extractedInput = subQueryInput.pipe(
|
|
167
|
+
map((data) => {
|
|
168
|
+
const [key, [value, _orderByIndex]] = data;
|
|
169
|
+
return [key, value];
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
return { alias: from.alias, input: extractedInput };
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
throw new Error(`Unsupported FROM type: ${from.type}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
compileQuery
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/query/compiler/index.ts"],"sourcesContent":["import { filter, map } from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { processJoins } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelectToResults } from \"./select.js\"\nimport type { CollectionRef, QueryIR, QueryRef } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n ResultStream,\n} from \"../../types.js\"\n\n/**\n * Cache for compiled subqueries to avoid duplicate compilation\n */\ntype QueryCache = WeakMap<QueryIR, ResultStream>\n\n/**\n * Compiles a query2 IR into a D2 pipeline\n * @param query The query IR to compile\n * @param inputs Mapping of collection names to input streams\n * @param cache Optional cache for compiled subqueries (used internally for recursion)\n * @returns A stream builder representing the compiled query\n */\nexport function compileQuery(\n query: QueryIR,\n inputs: Record<string, KeyedStream>,\n cache: QueryCache = new WeakMap()\n): ResultStream {\n // Check if this query has already been compiled\n const cachedResult = cache.get(query)\n if (cachedResult) {\n return cachedResult\n }\n\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // Process the FROM clause to get the main table\n const { alias: mainTableAlias, input: mainInput } = processFrom(\n query.from,\n allInputs,\n cache\n )\n tables[mainTableAlias] = mainInput\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = mainInput.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join && query.join.length > 0) {\n pipeline = processJoins(\n pipeline,\n query.join,\n tables,\n mainTableAlias,\n allInputs,\n cache\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where && query.where.length > 0) {\n // Compile all WHERE expressions\n const compiledWheres = query.where.map((where) => compileExpression(where))\n\n // Apply each WHERE condition as a filter (they are ANDed together)\n for (const compiledWhere of compiledWheres) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return compiledWhere(namespacedRow)\n })\n )\n }\n }\n\n // Process functional WHERE clauses if they exist\n if (query.fnWhere && query.fnWhere.length > 0) {\n for (const fnWhere of query.fnWhere) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnWhere(namespacedRow)\n })\n )\n }\n }\n\n // Process the SELECT clause early - always create __select_results\n // This eliminates duplication and allows for future DISTINCT implementation\n if (query.fnSelect) {\n // Handle functional select - apply the function to transform the row\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults = query.fnSelect!(namespacedRow)\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n } else if (query.select) {\n pipeline = processSelectToResults(pipeline, query.select, allInputs)\n } else {\n // If no SELECT clause, create __select_results with the main table data\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults =\n !query.join && !query.groupBy\n ? namespacedRow[mainTableAlias]\n : namespacedRow\n\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy && query.groupBy.length > 0) {\n pipeline = processGroupBy(\n pipeline,\n query.groupBy,\n query.having,\n query.select,\n query.fnHaving\n )\n } else if (query.select) {\n // Check if SELECT contains aggregates but no GROUP BY (implicit single-group aggregation)\n const hasAggregates = Object.values(query.select).some(\n (expr) => expr.type === `agg`\n )\n if (hasAggregates) {\n // Handle implicit single-group aggregation\n pipeline = processGroupBy(\n pipeline,\n [], // Empty group by means single group\n query.having,\n query.select,\n query.fnHaving\n )\n }\n }\n\n // Process the HAVING clause if it exists (only applies after GROUP BY)\n if (query.having && (!query.groupBy || query.groupBy.length === 0)) {\n // Check if we have aggregates in SELECT that would trigger implicit grouping\n const hasAggregates = query.select\n ? Object.values(query.select).some((expr) => expr.type === `agg`)\n : false\n\n if (!hasAggregates) {\n throw new Error(`HAVING clause requires GROUP BY clause`)\n }\n }\n\n // Process functional HAVING clauses outside of GROUP BY (treat as additional WHERE filters)\n if (\n query.fnHaving &&\n query.fnHaving.length > 0 &&\n (!query.groupBy || query.groupBy.length === 0)\n ) {\n // If there's no GROUP BY but there are fnHaving clauses, apply them as filters\n for (const fnHaving of query.fnHaving) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy && query.orderBy.length > 0) {\n const orderedPipeline = processOrderBy(\n pipeline,\n query.orderBy,\n query.limit,\n query.offset\n )\n\n // Final step: extract the __select_results and include orderBy index\n const resultPipeline = orderedPipeline.pipe(\n map(([key, [row, orderByIndex]]) => {\n // Extract the final results from __select_results and include orderBy index\n const finalResults = (row as any).__select_results\n return [key, [finalResults, orderByIndex]] as [unknown, [any, string]]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning\n cache.set(query, result)\n return result\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new Error(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n\n // Final step: extract the __select_results and return tuple format (no orderBy)\n const resultPipeline: ResultStream = pipeline.pipe(\n map(([key, row]) => {\n // Extract the final results from __select_results and return [key, [results, undefined]]\n const finalResults = (row as any).__select_results\n return [key, [finalResults, undefined]] as [\n unknown,\n [any, string | undefined],\n ]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning\n cache.set(query, result)\n return result\n}\n\n/**\n * Processes the FROM clause to extract the main table alias and input stream\n */\nfunction processFrom(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new Error(\n `Input for collection \"${from.collection.id}\" not found in inputs map`\n )\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Recursively compile the sub-query with cache\n const subQueryInput = compileQuery(from.query, allInputs, cache)\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput }\n }\n default:\n throw new Error(`Unsupported FROM type: ${(from as any).type}`)\n }\n}\n"],"names":["resultPipeline","result"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JoinClause, QueryIR } from '../ir.js';
|
|
2
|
+
import { KeyedStream, NamespacedAndKeyedStream, ResultStream } from '../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Cache for compiled subqueries to avoid duplicate compilation
|
|
5
|
+
*/
|
|
6
|
+
type QueryCache = WeakMap<QueryIR, ResultStream>;
|
|
7
|
+
/**
|
|
8
|
+
* Processes all join clauses in a query
|
|
9
|
+
*/
|
|
10
|
+
export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, tables: Record<string, KeyedStream>, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache): NamespacedAndKeyedStream;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { map, join, consolidate, filter } from "@electric-sql/d2mini";
|
|
2
|
+
import { compileExpression } from "./evaluators.js";
|
|
3
|
+
import { compileQuery } from "./index.js";
|
|
4
|
+
function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs, cache) {
|
|
5
|
+
let resultPipeline = pipeline;
|
|
6
|
+
for (const joinClause of joinClauses) {
|
|
7
|
+
resultPipeline = processJoin(
|
|
8
|
+
resultPipeline,
|
|
9
|
+
joinClause,
|
|
10
|
+
tables,
|
|
11
|
+
mainTableAlias,
|
|
12
|
+
allInputs,
|
|
13
|
+
cache
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return resultPipeline;
|
|
17
|
+
}
|
|
18
|
+
function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, cache) {
|
|
19
|
+
const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
|
|
20
|
+
joinClause.from,
|
|
21
|
+
allInputs,
|
|
22
|
+
cache
|
|
23
|
+
);
|
|
24
|
+
tables[joinedTableAlias] = joinedInput;
|
|
25
|
+
const joinType = joinClause.type === `cross` ? `inner` : joinClause.type === `outer` ? `full` : joinClause.type;
|
|
26
|
+
const compiledLeftExpr = compileExpression(joinClause.left);
|
|
27
|
+
const compiledRightExpr = compileExpression(joinClause.right);
|
|
28
|
+
const mainPipeline = pipeline.pipe(
|
|
29
|
+
map(([currentKey, namespacedRow]) => {
|
|
30
|
+
const leftKey = compiledLeftExpr(namespacedRow);
|
|
31
|
+
return [leftKey, [currentKey, namespacedRow]];
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
const joinedPipeline = joinedInput.pipe(
|
|
35
|
+
map(([currentKey, row]) => {
|
|
36
|
+
const namespacedRow = { [joinedTableAlias]: row };
|
|
37
|
+
const rightKey = compiledRightExpr(namespacedRow);
|
|
38
|
+
return [rightKey, [currentKey, namespacedRow]];
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
if (![`inner`, `left`, `right`, `full`].includes(joinType)) {
|
|
42
|
+
throw new Error(`Unsupported join type: ${joinClause.type}`);
|
|
43
|
+
}
|
|
44
|
+
return mainPipeline.pipe(
|
|
45
|
+
join(joinedPipeline, joinType),
|
|
46
|
+
consolidate(),
|
|
47
|
+
processJoinResults(joinClause.type)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function processJoinSource(from, allInputs, cache) {
|
|
51
|
+
switch (from.type) {
|
|
52
|
+
case `collectionRef`: {
|
|
53
|
+
const input = allInputs[from.collection.id];
|
|
54
|
+
if (!input) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Input for collection "${from.collection.id}" not found in inputs map`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return { alias: from.alias, input };
|
|
60
|
+
}
|
|
61
|
+
case `queryRef`: {
|
|
62
|
+
const subQueryInput = compileQuery(from.query, allInputs, cache);
|
|
63
|
+
const extractedInput = subQueryInput.pipe(
|
|
64
|
+
map((data) => {
|
|
65
|
+
const [key, [value, _orderByIndex]] = data;
|
|
66
|
+
return [key, value];
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
return { alias: from.alias, input: extractedInput };
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`Unsupported join source type: ${from.type}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function processJoinResults(joinType) {
|
|
76
|
+
return function(pipeline) {
|
|
77
|
+
return pipeline.pipe(
|
|
78
|
+
// Process the join result and handle nulls
|
|
79
|
+
filter((result) => {
|
|
80
|
+
const [_key, [main, joined]] = result;
|
|
81
|
+
const mainNamespacedRow = main == null ? void 0 : main[1];
|
|
82
|
+
const joinedNamespacedRow = joined == null ? void 0 : joined[1];
|
|
83
|
+
if (joinType === `inner`) {
|
|
84
|
+
return !!(mainNamespacedRow && joinedNamespacedRow);
|
|
85
|
+
}
|
|
86
|
+
if (joinType === `left`) {
|
|
87
|
+
return !!mainNamespacedRow;
|
|
88
|
+
}
|
|
89
|
+
if (joinType === `right`) {
|
|
90
|
+
return !!joinedNamespacedRow;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}),
|
|
94
|
+
map((result) => {
|
|
95
|
+
const [_key, [main, joined]] = result;
|
|
96
|
+
const mainKey = main == null ? void 0 : main[0];
|
|
97
|
+
const mainNamespacedRow = main == null ? void 0 : main[1];
|
|
98
|
+
const joinedKey = joined == null ? void 0 : joined[0];
|
|
99
|
+
const joinedNamespacedRow = joined == null ? void 0 : joined[1];
|
|
100
|
+
const mergedNamespacedRow = {};
|
|
101
|
+
if (mainNamespacedRow) {
|
|
102
|
+
Object.assign(mergedNamespacedRow, mainNamespacedRow);
|
|
103
|
+
}
|
|
104
|
+
if (joinedNamespacedRow) {
|
|
105
|
+
Object.assign(mergedNamespacedRow, joinedNamespacedRow);
|
|
106
|
+
}
|
|
107
|
+
const resultKey = `[${mainKey},${joinedKey}]`;
|
|
108
|
+
return [resultKey, mergedNamespacedRow];
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
processJoins
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=joins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joins.js","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@electric-sql/d2mini\"\nimport type { CollectionRef, JoinClause, QueryIR, QueryRef } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n ResultStream,\n} from \"../../types.js\"\n\n/**\n * Cache for compiled subqueries to avoid duplicate compilation\n */\ntype QueryCache = WeakMap<QueryIR, ResultStream>\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableAlias,\n allInputs,\n cache\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(\n joinClause.from,\n allInputs,\n cache\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n // Convert join type to D2 join type\n const joinType: JoinType =\n joinClause.type === `cross`\n ? `inner`\n : joinClause.type === `outer`\n ? `full`\n : (joinClause.type as JoinType)\n\n // Pre-compile the join expressions\n const compiledLeftExpr = compileExpression(joinClause.left)\n const compiledRightExpr = compileExpression(joinClause.right)\n\n // Prepare the main pipeline for joining\n const mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the left side of the join condition\n const leftKey = compiledLeftExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [leftKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n const joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the right side of the join condition\n const rightKey = compiledRightExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [rightKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinType)) {\n throw new Error(`Unsupported join type: ${joinClause.type}`)\n }\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new Error(\n `Input for collection \"${from.collection.id}\" not found in inputs map`\n )\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Recursively compile the sub-query with cache\n const subQueryInput = compileQuery(from.query, allInputs, cache)\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput as KeyedStream }\n }\n default:\n throw new Error(`Unsupported join source type: ${(from as any).type}`)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n"],"names":["joinOperator"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OrderByClause } from '../ir.js';
|
|
2
|
+
import { NamespacedAndKeyedStream, NamespacedRow } from '../../types.js';
|
|
3
|
+
import { IStreamBuilder, KeyValue } from '@electric-sql/d2mini';
|
|
4
|
+
/**
|
|
5
|
+
* Processes the ORDER BY clause
|
|
6
|
+
* Works with the new structure that has both namespaced row data and __select_results
|
|
7
|
+
* Always uses fractional indexing and adds the index as __ordering_index to the result
|
|
8
|
+
*/
|
|
9
|
+
export declare function processOrderBy(pipeline: NamespacedAndKeyedStream, orderByClause: Array<OrderByClause>, limit?: number, offset?: number): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { orderByWithFractionalIndex } from "@electric-sql/d2mini";
|
|
2
|
+
import { compileExpression } from "./evaluators.js";
|
|
3
|
+
function processOrderBy(pipeline, orderByClause, limit, offset) {
|
|
4
|
+
const compiledOrderBy = orderByClause.map((clause) => ({
|
|
5
|
+
compiledExpression: compileExpression(clause.expression),
|
|
6
|
+
direction: clause.direction
|
|
7
|
+
}));
|
|
8
|
+
const valueExtractor = (row) => {
|
|
9
|
+
const orderByContext = { ...row };
|
|
10
|
+
if (row.__select_results) {
|
|
11
|
+
Object.assign(orderByContext, row.__select_results);
|
|
12
|
+
}
|
|
13
|
+
if (orderByClause.length > 1) {
|
|
14
|
+
return compiledOrderBy.map(
|
|
15
|
+
(compiled) => compiled.compiledExpression(orderByContext)
|
|
16
|
+
);
|
|
17
|
+
} else if (orderByClause.length === 1) {
|
|
18
|
+
const compiled = compiledOrderBy[0];
|
|
19
|
+
return compiled.compiledExpression(orderByContext);
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
const ascComparator = (a, b) => {
|
|
24
|
+
if (a == null && b == null) return 0;
|
|
25
|
+
if (a == null) return -1;
|
|
26
|
+
if (b == null) return 1;
|
|
27
|
+
if (typeof a === `string` && typeof b === `string`) {
|
|
28
|
+
return a.localeCompare(b);
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
31
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
32
|
+
const result = ascComparator(a[i], b[i]);
|
|
33
|
+
if (result !== 0) {
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return a.length - b.length;
|
|
38
|
+
}
|
|
39
|
+
if (a instanceof Date && b instanceof Date) {
|
|
40
|
+
return a.getTime() - b.getTime();
|
|
41
|
+
}
|
|
42
|
+
const bothObjects = typeof a === `object` && typeof b === `object`;
|
|
43
|
+
const notNull = a !== null && b !== null;
|
|
44
|
+
if (bothObjects && notNull) {
|
|
45
|
+
return a.toString().localeCompare(b.toString());
|
|
46
|
+
}
|
|
47
|
+
if (a < b) return -1;
|
|
48
|
+
if (a > b) return 1;
|
|
49
|
+
return 0;
|
|
50
|
+
};
|
|
51
|
+
const descComparator = (a, b) => {
|
|
52
|
+
return ascComparator(b, a);
|
|
53
|
+
};
|
|
54
|
+
const makeComparator = () => {
|
|
55
|
+
return (a, b) => {
|
|
56
|
+
if (orderByClause.length > 1) {
|
|
57
|
+
const arrayA = a;
|
|
58
|
+
const arrayB = b;
|
|
59
|
+
for (let i = 0; i < orderByClause.length; i++) {
|
|
60
|
+
const direction = orderByClause[i].direction;
|
|
61
|
+
const compareFn = direction === `desc` ? descComparator : ascComparator;
|
|
62
|
+
const result = compareFn(arrayA[i], arrayB[i]);
|
|
63
|
+
if (result !== 0) {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return arrayA.length - arrayB.length;
|
|
68
|
+
}
|
|
69
|
+
if (orderByClause.length === 1) {
|
|
70
|
+
const direction = orderByClause[0].direction;
|
|
71
|
+
return direction === `desc` ? descComparator(a, b) : ascComparator(a, b);
|
|
72
|
+
}
|
|
73
|
+
return ascComparator(a, b);
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const comparator = makeComparator();
|
|
77
|
+
return pipeline.pipe(
|
|
78
|
+
orderByWithFractionalIndex(valueExtractor, {
|
|
79
|
+
limit,
|
|
80
|
+
offset,
|
|
81
|
+
comparator
|
|
82
|
+
})
|
|
83
|
+
// orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
processOrderBy
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=order-by.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order-by.js","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { OrderByClause } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@electric-sql/d2mini\"\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => ({\n compiledExpression: compileExpression(clause.expression),\n direction: clause.direction,\n }))\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // For ORDER BY expressions, we need to provide access to both:\n // 1. The original namespaced row data (for direct table column references)\n // 2. The __select_results (for SELECT alias references)\n\n // Create a merged context for expression evaluation\n const orderByContext = { ...row }\n\n // If there are select results, merge them at the top level for alias access\n if (row.__select_results) {\n // Add select results as top-level properties for alias access\n Object.assign(orderByContext, row.__select_results)\n }\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create comparator functions\n const ascComparator = (a: any, b: any): number => {\n // Handle null/undefined\n if (a == null && b == null) return 0\n if (a == null) return -1\n if (b == null) return 1\n\n // if a and b are both strings, compare them based on locale\n if (typeof a === `string` && typeof b === `string`) {\n return a.localeCompare(b)\n }\n\n // if a and b are both arrays, compare them element by element\n if (Array.isArray(a) && Array.isArray(b)) {\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n const result = ascComparator(a[i], b[i])\n if (result !== 0) {\n return result\n }\n }\n // All elements are equal up to the minimum length\n return a.length - b.length\n }\n\n // If both are dates, compare them\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime()\n }\n\n // If at least one of the values is an object, convert to strings\n const bothObjects = typeof a === `object` && typeof b === `object`\n const notNull = a !== null && b !== null\n if (bothObjects && notNull) {\n return a.toString().localeCompare(b.toString())\n }\n\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n\n const descComparator = (a: unknown, b: unknown): number => {\n return ascComparator(b, a)\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const makeComparator = () => {\n return (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const direction = orderByClause[i]!.direction\n const compareFn =\n direction === `desc` ? descComparator : ascComparator\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const direction = orderByClause[0]!.direction\n return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)\n }\n\n return ascComparator(a, b)\n }\n }\n\n const comparator = makeComparator()\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":[],"mappings":";;AAWO,SAAS,eACd,UACA,eACA,OACA,QAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,YAAY;AAAA,IACrD,oBAAoB,kBAAkB,OAAO,UAAU;AAAA,IACvD,WAAW,OAAO;AAAA,EAAA,EAClB;AAGI,QAAA,iBAAiB,CAAC,QAAoD;AAMpE,UAAA,iBAAiB,EAAE,GAAG,IAAI;AAGhC,QAAI,IAAI,kBAAkB;AAEjB,aAAA,OAAO,gBAAgB,IAAI,gBAAgB;AAAA,IAAA;AAGhD,QAAA,cAAc,SAAS,GAAG;AAE5B,aAAO,gBAAgB;AAAA,QAAI,CAAC,aAC1B,SAAS,mBAAmB,cAAc;AAAA,MAC5C;AAAA,IAAA,WACS,cAAc,WAAW,GAAG;AAE/B,YAAA,WAAW,gBAAgB,CAAC;AAC3B,aAAA,SAAS,mBAAmB,cAAc;AAAA,IAAA;AAI5C,WAAA;AAAA,EACT;AAGM,QAAA,gBAAgB,CAAC,GAAQ,MAAmB;AAEhD,QAAI,KAAK,QAAQ,KAAK,KAAa,QAAA;AAC/B,QAAA,KAAK,KAAa,QAAA;AAClB,QAAA,KAAK,KAAa,QAAA;AAGtB,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAC3C,aAAA,EAAE,cAAc,CAAC;AAAA,IAAA;AAI1B,QAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AAC/B,eAAA,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK;AACrD,cAAM,SAAS,cAAc,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AACvC,YAAI,WAAW,GAAG;AACT,iBAAA;AAAA,QAAA;AAAA,MACT;AAGK,aAAA,EAAE,SAAS,EAAE;AAAA,IAAA;AAIlB,QAAA,aAAa,QAAQ,aAAa,MAAM;AAC1C,aAAO,EAAE,YAAY,EAAE,QAAQ;AAAA,IAAA;AAIjC,UAAM,cAAc,OAAO,MAAM,YAAY,OAAO,MAAM;AACpD,UAAA,UAAU,MAAM,QAAQ,MAAM;AACpC,QAAI,eAAe,SAAS;AAC1B,aAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU;AAAA,IAAA;AAG5C,QAAA,IAAI,EAAU,QAAA;AACd,QAAA,IAAI,EAAU,QAAA;AACX,WAAA;AAAA,EACT;AAEM,QAAA,iBAAiB,CAAC,GAAY,MAAuB;AAClD,WAAA,cAAc,GAAG,CAAC;AAAA,EAC3B;AAGA,QAAM,iBAAiB,MAAM;AACpB,WAAA,CAAC,GAAY,MAAe;AAE7B,UAAA,cAAc,SAAS,GAAG;AAC5B,cAAM,SAAS;AACf,cAAM,SAAS;AACf,iBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AACvC,gBAAA,YAAY,cAAc,CAAC,EAAG;AAC9B,gBAAA,YACJ,cAAc,SAAS,iBAAiB;AAC1C,gBAAM,SAAS,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAC7C,cAAI,WAAW,GAAG;AACT,mBAAA;AAAA,UAAA;AAAA,QACT;AAEK,eAAA,OAAO,SAAS,OAAO;AAAA,MAAA;AAI5B,UAAA,cAAc,WAAW,GAAG;AACxB,cAAA,YAAY,cAAc,CAAC,EAAG;AAC7B,eAAA,cAAc,SAAS,eAAe,GAAG,CAAC,IAAI,cAAc,GAAG,CAAC;AAAA,MAAA;AAGlE,aAAA,cAAc,GAAG,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,aAAa,eAAe;AAGlC,SAAO,SAAS;AAAA,IACd,2BAA2B,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAA;AAAA;AAAA,EAEH;AACF;"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Aggregate, BasicExpression, Select } from '../ir.js';
|
|
2
|
+
import { KeyedStream, NamespacedAndKeyedStream, NamespacedRow } from '../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Processes the SELECT clause and places results in __select_results
|
|
5
|
+
* while preserving the original namespaced row for ORDER BY access
|
|
6
|
+
*/
|
|
7
|
+
export declare function processSelectToResults(pipeline: NamespacedAndKeyedStream, select: Select, _allInputs: Record<string, KeyedStream>): NamespacedAndKeyedStream;
|
|
8
|
+
/**
|
|
9
|
+
* Processes the SELECT clause (legacy function - kept for compatibility)
|
|
10
|
+
*/
|
|
11
|
+
export declare function processSelect(pipeline: NamespacedAndKeyedStream, select: Select, _allInputs: Record<string, KeyedStream>): KeyedStream;
|
|
12
|
+
/**
|
|
13
|
+
* Processes a single argument in a function context
|
|
14
|
+
*/
|
|
15
|
+
export declare function processArgument(arg: BasicExpression | Aggregate, namespacedRow: NamespacedRow): any;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { map } from "@electric-sql/d2mini";
|
|
2
|
+
import { compileExpression } from "./evaluators.js";
|
|
3
|
+
function processSelectToResults(pipeline, select, _allInputs) {
|
|
4
|
+
const compiledSelect = [];
|
|
5
|
+
const spreadAliases = [];
|
|
6
|
+
for (const [alias, expression] of Object.entries(select)) {
|
|
7
|
+
if (alias.startsWith(`__SPREAD_SENTINEL__`)) {
|
|
8
|
+
const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``);
|
|
9
|
+
spreadAliases.push(tableAlias);
|
|
10
|
+
} else {
|
|
11
|
+
if (isAggregateExpression(expression)) {
|
|
12
|
+
compiledSelect.push({
|
|
13
|
+
alias,
|
|
14
|
+
compiledExpression: () => null
|
|
15
|
+
// Placeholder - will be handled by GROUP BY
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
compiledSelect.push({
|
|
19
|
+
alias,
|
|
20
|
+
compiledExpression: compileExpression(expression)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return pipeline.pipe(
|
|
26
|
+
map(([key, namespacedRow]) => {
|
|
27
|
+
const selectResults = {};
|
|
28
|
+
for (const tableAlias of spreadAliases) {
|
|
29
|
+
const tableData = namespacedRow[tableAlias];
|
|
30
|
+
if (tableData && typeof tableData === `object`) {
|
|
31
|
+
for (const [fieldName, fieldValue] of Object.entries(tableData)) {
|
|
32
|
+
if (!(fieldName in selectResults)) {
|
|
33
|
+
selectResults[fieldName] = fieldValue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const { alias, compiledExpression } of compiledSelect) {
|
|
39
|
+
selectResults[alias] = compiledExpression(namespacedRow);
|
|
40
|
+
}
|
|
41
|
+
return [
|
|
42
|
+
key,
|
|
43
|
+
{
|
|
44
|
+
...namespacedRow,
|
|
45
|
+
__select_results: selectResults
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function isAggregateExpression(expr) {
|
|
52
|
+
return expr.type === `agg`;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
processSelectToResults
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sources":["../../../../src/query/compiler/select.ts"],"sourcesContent":["import { map } from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { Aggregate, BasicExpression, Select } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\n\n/**\n * Processes the SELECT clause and places results in __select_results\n * while preserving the original namespaced row for ORDER BY access\n */\nexport function processSelectToResults(\n pipeline: NamespacedAndKeyedStream,\n select: Select,\n _allInputs: Record<string, KeyedStream>\n): NamespacedAndKeyedStream {\n // Pre-compile all select expressions\n const compiledSelect: Array<{\n alias: string\n compiledExpression: (row: NamespacedRow) => any\n }> = []\n const spreadAliases: Array<string> = []\n\n for (const [alias, expression] of Object.entries(select)) {\n if (alias.startsWith(`__SPREAD_SENTINEL__`)) {\n // Extract the table alias from the sentinel key\n const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``)\n spreadAliases.push(tableAlias)\n } else {\n if (isAggregateExpression(expression)) {\n // For aggregates, we'll store the expression info for GROUP BY processing\n // but still compile a placeholder that will be replaced later\n compiledSelect.push({\n alias,\n compiledExpression: () => null, // Placeholder - will be handled by GROUP BY\n })\n } else {\n compiledSelect.push({\n alias,\n compiledExpression: compileExpression(expression as BasicExpression),\n })\n }\n }\n }\n\n return pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults: Record<string, any> = {}\n\n // First pass: spread table data for any spread sentinels\n for (const tableAlias of spreadAliases) {\n const tableData = namespacedRow[tableAlias]\n if (tableData && typeof tableData === `object`) {\n // Spread the table data into the result, but don't overwrite explicit fields\n for (const [fieldName, fieldValue] of Object.entries(tableData)) {\n if (!(fieldName in selectResults)) {\n selectResults[fieldName] = fieldValue\n }\n }\n }\n }\n\n // Second pass: evaluate all compiled select expressions (non-aggregates)\n for (const { alias, compiledExpression } of compiledSelect) {\n selectResults[alias] = compiledExpression(namespacedRow)\n }\n\n // Return the namespaced row with __select_results added\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [\n string,\n typeof namespacedRow & { __select_results: typeof selectResults },\n ]\n })\n )\n}\n\n/**\n * Processes the SELECT clause (legacy function - kept for compatibility)\n */\nexport function processSelect(\n pipeline: NamespacedAndKeyedStream,\n select: Select,\n _allInputs: Record<string, KeyedStream>\n): KeyedStream {\n // Pre-compile all select expressions\n const compiledSelect: Array<{\n alias: string\n compiledExpression: (row: NamespacedRow) => any\n }> = []\n const spreadAliases: Array<string> = []\n\n for (const [alias, expression] of Object.entries(select)) {\n if (alias.startsWith(`__SPREAD_SENTINEL__`)) {\n // Extract the table alias from the sentinel key\n const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``)\n spreadAliases.push(tableAlias)\n } else {\n if (isAggregateExpression(expression)) {\n // Aggregates should be handled by GROUP BY processing, not here\n throw new Error(\n `Aggregate expressions in SELECT clause should be handled by GROUP BY processing`\n )\n }\n compiledSelect.push({\n alias,\n compiledExpression: compileExpression(expression as BasicExpression),\n })\n }\n }\n\n return pipeline.pipe(\n map(([key, namespacedRow]) => {\n const result: Record<string, any> = {}\n\n // First pass: spread table data for any spread sentinels\n for (const tableAlias of spreadAliases) {\n const tableData = namespacedRow[tableAlias]\n if (tableData && typeof tableData === `object`) {\n // Spread the table data into the result, but don't overwrite explicit fields\n for (const [fieldName, fieldValue] of Object.entries(tableData)) {\n if (!(fieldName in result)) {\n result[fieldName] = fieldValue\n }\n }\n }\n }\n\n // Second pass: evaluate all compiled select expressions\n for (const { alias, compiledExpression } of compiledSelect) {\n result[alias] = compiledExpression(namespacedRow)\n }\n\n return [key, result] as [string, typeof result]\n })\n )\n}\n\n/**\n * Helper function to check if an expression is an aggregate\n */\nfunction isAggregateExpression(\n expr: BasicExpression | Aggregate\n): expr is Aggregate {\n return expr.type === `agg`\n}\n\n/**\n * Processes a single argument in a function context\n */\nexport function processArgument(\n arg: BasicExpression | Aggregate,\n namespacedRow: NamespacedRow\n): any {\n if (isAggregateExpression(arg)) {\n throw new Error(\n `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`\n )\n }\n\n // Pre-compile the expression and evaluate immediately\n const compiledExpression = compileExpression(arg)\n const value = compiledExpression(namespacedRow)\n\n return value\n}\n"],"names":[],"mappings":";;AAagB,SAAA,uBACd,UACA,QACA,YAC0B;AAE1B,QAAM,iBAGD,CAAC;AACN,QAAM,gBAA+B,CAAC;AAEtC,aAAW,CAAC,OAAO,UAAU,KAAK,OAAO,QAAQ,MAAM,GAAG;AACpD,QAAA,MAAM,WAAW,qBAAqB,GAAG;AAE3C,YAAM,aAAa,MAAM,QAAQ,uBAAuB,EAAE;AAC1D,oBAAc,KAAK,UAAU;AAAA,IAAA,OACxB;AACD,UAAA,sBAAsB,UAAU,GAAG;AAGrC,uBAAe,KAAK;AAAA,UAClB;AAAA,UACA,oBAAoB,MAAM;AAAA;AAAA,QAAA,CAC3B;AAAA,MAAA,OACI;AACL,uBAAe,KAAK;AAAA,UAClB;AAAA,UACA,oBAAoB,kBAAkB,UAA6B;AAAA,QAAA,CACpE;AAAA,MAAA;AAAA,IACH;AAAA,EACF;AAGF,SAAO,SAAS;AAAA,IACd,IAAI,CAAC,CAAC,KAAK,aAAa,MAAM;AAC5B,YAAM,gBAAqC,CAAC;AAG5C,iBAAW,cAAc,eAAe;AAChC,cAAA,YAAY,cAAc,UAAU;AACtC,YAAA,aAAa,OAAO,cAAc,UAAU;AAE9C,qBAAW,CAAC,WAAW,UAAU,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC3D,gBAAA,EAAE,aAAa,gBAAgB;AACjC,4BAAc,SAAS,IAAI;AAAA,YAAA;AAAA,UAC7B;AAAA,QACF;AAAA,MACF;AAIF,iBAAW,EAAE,OAAO,mBAAmB,KAAK,gBAAgB;AAC5C,sBAAA,KAAK,IAAI,mBAAmB,aAAa;AAAA,MAAA;AAIlD,aAAA;AAAA,QACL;AAAA,QACA;AAAA,UACE,GAAG;AAAA,UACH,kBAAkB;AAAA,QAAA;AAAA,MAEtB;AAAA,IAID,CAAA;AAAA,EACH;AACF;AAkEA,SAAS,sBACP,MACmB;AACnB,SAAO,KAAK,SAAS;AACvB;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
1
|
+
export { BaseQueryBuilder, Query, type InitialQueryBuilder, type QueryBuilder, type Context, type Source, type GetResult, } from './builder/index.js';
|
|
2
|
+
export { eq, gt, gte, lt, lte, and, or, not, inArray, like, ilike, upper, lower, length, concat, coalesce, add, count, avg, sum, min, max, } from './builder/functions.js';
|
|
3
|
+
export { val, toExpression, isRefProxy } from './builder/ref-proxy.js';
|
|
4
|
+
export type { QueryIR, BasicExpression as Expression, Aggregate, CollectionRef, QueryRef, JoinClause, } from './ir.js';
|
|
5
|
+
export { compileQuery } from './compiler/index.js';
|
|
6
|
+
export { createLiveQueryCollection, liveQueryCollectionOptions, type LiveQueryCollectionConfig, } from './live-query-collection.js';
|