@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 20.6 kB
Source Map (JSON)
{"version":3,"file":"group-by.cjs","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import { filter, groupBy, groupByOperators, map } from \"@tanstack/db-ivm\"\nimport { Func, PropRef } from \"../ir.js\"\nimport {\n AggregateFunctionNotInSelectError,\n NonAggregateExpressionNotInGroupByError,\n UnknownHavingExpressionTypeError,\n UnsupportedAggregateFunctionError,\n} from \"../../errors.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 NonAggregateExpressionNotInGroupByError(alias)\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 UnsupportedAggregateFunctionError(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 PropRef([`result`, alias])\n }\n }\n // If no matching aggregate found in SELECT, throw error\n throw new AggregateFunctionNotInSelectError(aggExpr.name)\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 PropRef([`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 UnknownHavingExpressionTypeError((havingExpr as any).type)\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":["groupByOperators","NonAggregateExpressionNotInGroupByError","aggregates","keyExtractor","groupBy","map","compileExpression","filter","UnsupportedAggregateFunctionError","PropRef","AggregateFunctionNotInSelectError","Func","UnknownHavingExpressionTypeError"],"mappings":"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"}