@tanstack/db 0.0.16 → 0.0.18
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/SortedMap.cjs.map +1 -1
- package/dist/cjs/collection.cjs +53 -8
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +150 -45
- package/dist/cjs/deferred.cjs.map +1 -1
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/optimistic-action.cjs.map +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.cjs +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +116 -0
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/transactions.d.cts +179 -0
- package/dist/cjs/types.d.cts +170 -12
- package/dist/esm/SortedMap.js.map +1 -1
- package/dist/esm/collection.d.ts +150 -45
- package/dist/esm/collection.js +53 -8
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/deferred.js.map +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/optimistic-action.js.map +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live-query-collection.js +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/transactions.d.ts +179 -0
- package/dist/esm/transactions.js +116 -0
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +170 -12
- package/package.json +2 -2
- package/src/collection.ts +175 -55
- package/src/proxy.ts +0 -1
- package/src/query/live-query-collection.ts +2 -1
- package/src/transactions.ts +179 -0
- package/src/types.ts +200 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from \"../../collection.js\"\nimport { CollectionRef, QueryRef } from \"../ir.js\"\nimport { createRefProxy, isRefProxy, toExpression } from \"./ref-proxy.js\"\nimport type { NamespacedRow } from \"../../types.js\"\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByClause,\n OrderByDirection,\n QueryIR,\n} from \"../ir.js\"\nimport type {\n Context,\n GroupByCallback,\n JoinOnCallback,\n MergeContext,\n MergeContextWithJoinType,\n OrderByCallback,\n RefProxyForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from \"./types.js\"\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string\n ): [string, CollectionRef | QueryRef] {\n if (Object.keys(source).length !== 1) {\n throw new Error(`Only one source is allowed in the ${context}`)\n }\n\n const alias = Object.keys(source)[0]!\n const sourceValue = source[alias]\n\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new Error(\n `A sub query passed to a ${context} must have a from clause itself`\n )\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new Error(`Invalid source`)\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefProxyForContext<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new Error(`Join condition must be an equality expression`)\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefProxyForContext<TContext>) => TSelectObject\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const selectObject = callback(refProxy)\n\n // Check if any tables were spread during the callback\n const spreadSentinels = (refProxy as any).__spreadSentinels as Set<string>\n\n // Convert the select object to use expressions, including spread sentinels\n const select: Record<string, BasicExpression | Aggregate> = {}\n\n // First, add spread sentinels for any tables that were spread\n for (const spreadAlias of spreadSentinels) {\n const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`\n select[sentinelKey] = toExpression(spreadAlias) // Use alias as a simple reference\n }\n\n // Then add the explicit select fields\n for (const [key, value] of Object.entries(selectObject)) {\n if (isRefProxy(value)) {\n select[key] = toExpression(value)\n } else if (\n typeof value === `object` &&\n `type` in value &&\n (value.type === `agg` || value.type === `func`)\n ) {\n select[key] = value as BasicExpression | Aggregate\n } else {\n select[key] = toExpression(value)\n }\n }\n\n return new BaseQueryBuilder({\n ...this.query,\n select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n direction: OrderByDirection = `asc`\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n // Create the new OrderBy structure with expression and direction\n const orderByClause: OrderByClause = {\n expression: toExpression(result),\n direction,\n }\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, orderByClause],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Replace existing groupBy expressions instead of extending them\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: newExpressions,\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .fn.having(row => row.count > 5)\n * ```\n */\n having(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new Error(`Query must have a from clause`)\n }\n return this.query as QueryIR\n }\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Export the types from types.ts for convenience\nexport type { Context, Source, GetResult } from \"./types.js\"\n"],"names":["CollectionImpl","CollectionRef","QueryRef","refProxy","createRefProxy","toExpression","isRefProxy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from \"../../collection.js\"\nimport { CollectionRef, QueryRef } from \"../ir.js\"\nimport { createRefProxy, isRefProxy, toExpression } from \"./ref-proxy.js\"\nimport type { NamespacedRow } from \"../../types.js\"\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByClause,\n OrderByDirection,\n QueryIR,\n} from \"../ir.js\"\nimport type {\n Context,\n GroupByCallback,\n JoinOnCallback,\n MergeContext,\n MergeContextWithJoinType,\n OrderByCallback,\n RefProxyForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from \"./types.js\"\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string\n ): [string, CollectionRef | QueryRef] {\n if (Object.keys(source).length !== 1) {\n throw new Error(`Only one source is allowed in the ${context}`)\n }\n\n const alias = Object.keys(source)[0]!\n const sourceValue = source[alias]\n\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new Error(\n `A sub query passed to a ${context} must have a from clause itself`\n )\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new Error(`Invalid source`)\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefProxyForContext<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new Error(`Join condition must be an equality expression`)\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefProxyForContext<TContext>) => TSelectObject\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const selectObject = callback(refProxy)\n\n // Check if any tables were spread during the callback\n const spreadSentinels = (refProxy as any).__spreadSentinels as Set<string>\n\n // Convert the select object to use expressions, including spread sentinels\n const select: Record<string, BasicExpression | Aggregate> = {}\n\n // First, add spread sentinels for any tables that were spread\n for (const spreadAlias of spreadSentinels) {\n const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`\n select[sentinelKey] = toExpression(spreadAlias) // Use alias as a simple reference\n }\n\n // Then add the explicit select fields\n for (const [key, value] of Object.entries(selectObject)) {\n if (isRefProxy(value)) {\n select[key] = toExpression(value)\n } else if (\n typeof value === `object` &&\n `type` in value &&\n (value.type === `agg` || value.type === `func`)\n ) {\n select[key] = value as BasicExpression | Aggregate\n } else {\n select[key] = toExpression(value)\n }\n }\n\n return new BaseQueryBuilder({\n ...this.query,\n select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n direction: OrderByDirection = `asc`\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n // Create the new OrderBy structure with expression and direction\n const orderByClause: OrderByClause = {\n expression: toExpression(result),\n direction,\n }\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, orderByClause],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Replace existing groupBy expressions instead of extending them\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: newExpressions,\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .fn.having(row => row.count > 5)\n * ```\n */\n having(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new Error(`Query must have a from clause`)\n }\n return this.query as QueryIR\n }\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Export the types from types.ts for convenience\nexport type { Context, Source, GetResult } from \"./types.js\"\n"],"names":["CollectionImpl","CollectionRef","QueryRef","refProxy","createRefProxy","toExpression","isRefProxy"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ref-proxy.cjs","sources":["../../../../src/query/builder/ref-proxy.ts"],"sourcesContent":["import { PropRef, Value } from \"../ir.js\"\nimport type { BasicExpression } from \"../ir.js\"\n\nexport interface RefProxy<T = any> {\n /** @internal */\n readonly __refProxy: true\n /** @internal */\n readonly __path: Array<string>\n /** @internal */\n readonly __type: T\n}\n\n/**\n * Creates a proxy object that records property access paths\n * Used in callbacks like where, select, etc. to create type-safe references\n */\nexport function createRefProxy<T extends Record<string, any>>(\n aliases: Array<string>\n): RefProxy<T> & T {\n const cache = new Map<string, any>()\n const spreadSentinels = new Set<string>() // Track which aliases have been spread\n\n function createProxy(path: Array<string>): any {\n const pathKey = path.join(`.`)\n if (cache.has(pathKey)) {\n return cache.get(pathKey)\n }\n\n const proxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return path\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const newPath = [...path, String(prop)]\n return createProxy(newPath)\n },\n\n has(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`)\n return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(target) {\n // If this is a table-level proxy (path length 1), mark it as spread\n if (path.length === 1) {\n const aliasName = path[0]!\n spreadSentinels.add(aliasName)\n }\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {\n return { enumerable: false, configurable: true }\n }\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n })\n\n cache.set(pathKey, proxy)\n return proxy\n }\n\n // Create the root proxy with all aliases as top-level properties\n const rootProxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return []\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (prop === `__spreadSentinels`) return spreadSentinels // Expose spread sentinels\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const propStr = String(prop)\n if (aliases.includes(propStr)) {\n return createProxy([propStr])\n }\n\n return undefined\n },\n\n has(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n )\n return true\n if (typeof prop === `string` && aliases.includes(prop)) return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(_target) {\n return [...aliases, `__refProxy`, `__path`, `__type`, `__spreadSentinels`]\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n ) {\n return { enumerable: false, configurable: true }\n }\n if (typeof prop === `string` && aliases.includes(prop)) {\n return { enumerable: true, configurable: true }\n }\n return undefined\n },\n })\n\n return rootProxy\n}\n\n/**\n * Converts a value to an Expression\n * If it's a RefProxy, creates a Ref, otherwise creates a Value\n */\nexport function toExpression<T = any>(value: T): BasicExpression<T>\nexport function toExpression(value: RefProxy<any>): BasicExpression<any>\nexport function toExpression(value: any): BasicExpression<any> {\n if (isRefProxy(value)) {\n return new PropRef(value.__path)\n }\n // If it's already an Expression (Func, Ref, Value) or Agg, return it directly\n if (\n value &&\n typeof value === `object` &&\n `type` in value &&\n (value.type === `func` ||\n value.type === `ref` ||\n value.type === `val` ||\n value.type === `agg`)\n ) {\n return value\n }\n return new Value(value)\n}\n\n/**\n * Type guard to check if a value is a RefProxy\n */\nexport function isRefProxy(value: any): value is RefProxy {\n return value && typeof value === `object` && value.__refProxy === true\n}\n\n/**\n * Helper to create a Value expression from a literal\n */\nexport function val<T>(value: T): BasicExpression<T> {\n return new Value(value)\n}\n"],"names":["PropRef","Value"],"mappings":";;;AAgBO,SAAS,eACd,SACiB;
|
|
1
|
+
{"version":3,"file":"ref-proxy.cjs","sources":["../../../../src/query/builder/ref-proxy.ts"],"sourcesContent":["import { PropRef, Value } from \"../ir.js\"\nimport type { BasicExpression } from \"../ir.js\"\n\nexport interface RefProxy<T = any> {\n /** @internal */\n readonly __refProxy: true\n /** @internal */\n readonly __path: Array<string>\n /** @internal */\n readonly __type: T\n}\n\n/**\n * Creates a proxy object that records property access paths\n * Used in callbacks like where, select, etc. to create type-safe references\n */\nexport function createRefProxy<T extends Record<string, any>>(\n aliases: Array<string>\n): RefProxy<T> & T {\n const cache = new Map<string, any>()\n const spreadSentinels = new Set<string>() // Track which aliases have been spread\n\n function createProxy(path: Array<string>): any {\n const pathKey = path.join(`.`)\n if (cache.has(pathKey)) {\n return cache.get(pathKey)\n }\n\n const proxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return path\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const newPath = [...path, String(prop)]\n return createProxy(newPath)\n },\n\n has(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`)\n return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(target) {\n // If this is a table-level proxy (path length 1), mark it as spread\n if (path.length === 1) {\n const aliasName = path[0]!\n spreadSentinels.add(aliasName)\n }\n return Reflect.ownKeys(target)\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {\n return { enumerable: false, configurable: true }\n }\n return Reflect.getOwnPropertyDescriptor(target, prop)\n },\n })\n\n cache.set(pathKey, proxy)\n return proxy\n }\n\n // Create the root proxy with all aliases as top-level properties\n const rootProxy = new Proxy({} as any, {\n get(target, prop, receiver) {\n if (prop === `__refProxy`) return true\n if (prop === `__path`) return []\n if (prop === `__type`) return undefined // Type is only for TypeScript inference\n if (prop === `__spreadSentinels`) return spreadSentinels // Expose spread sentinels\n if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)\n\n const propStr = String(prop)\n if (aliases.includes(propStr)) {\n return createProxy([propStr])\n }\n\n return undefined\n },\n\n has(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n )\n return true\n if (typeof prop === `string` && aliases.includes(prop)) return true\n return Reflect.has(target, prop)\n },\n\n ownKeys(_target) {\n return [...aliases, `__refProxy`, `__path`, `__type`, `__spreadSentinels`]\n },\n\n getOwnPropertyDescriptor(target, prop) {\n if (\n prop === `__refProxy` ||\n prop === `__path` ||\n prop === `__type` ||\n prop === `__spreadSentinels`\n ) {\n return { enumerable: false, configurable: true }\n }\n if (typeof prop === `string` && aliases.includes(prop)) {\n return { enumerable: true, configurable: true }\n }\n return undefined\n },\n })\n\n return rootProxy\n}\n\n/**\n * Converts a value to an Expression\n * If it's a RefProxy, creates a Ref, otherwise creates a Value\n */\nexport function toExpression<T = any>(value: T): BasicExpression<T>\nexport function toExpression(value: RefProxy<any>): BasicExpression<any>\nexport function toExpression(value: any): BasicExpression<any> {\n if (isRefProxy(value)) {\n return new PropRef(value.__path)\n }\n // If it's already an Expression (Func, Ref, Value) or Agg, return it directly\n if (\n value &&\n typeof value === `object` &&\n `type` in value &&\n (value.type === `func` ||\n value.type === `ref` ||\n value.type === `val` ||\n value.type === `agg`)\n ) {\n return value\n }\n return new Value(value)\n}\n\n/**\n * Type guard to check if a value is a RefProxy\n */\nexport function isRefProxy(value: any): value is RefProxy {\n return value && typeof value === `object` && value.__refProxy === true\n}\n\n/**\n * Helper to create a Value expression from a literal\n */\nexport function val<T>(value: T): BasicExpression<T> {\n return new Value(value)\n}\n"],"names":["PropRef","Value"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluators.cjs","sources":["../../../../src/query/compiler/evaluators.ts"],"sourcesContent":["import type { BasicExpression, Func, PropRef } from \"../ir.js\"\nimport type { NamespacedRow } from \"../../types.js\"\n\n/**\n * Compiled expression evaluator function type\n */\nexport type CompiledExpression = (namespacedRow: NamespacedRow) => any\n\n/**\n * Compiles an expression into an optimized evaluator function.\n * This eliminates branching during evaluation by pre-compiling the expression structure.\n */\nexport function compileExpression(expr: BasicExpression): CompiledExpression {\n switch (expr.type) {\n case `val`: {\n // For constant values, return a function that just returns the value\n const value = expr.value\n return () => value\n }\n\n case `ref`: {\n // For references, pre-compile the property path navigation\n return compileRef(expr)\n }\n\n case `func`: {\n // For functions, pre-compile the function and its arguments\n return compileFunction(expr)\n }\n\n default:\n throw new Error(`Unknown expression type: ${(expr as any).type}`)\n }\n}\n\n/**\n * Compiles a reference expression into an optimized evaluator\n */\nfunction compileRef(ref: PropRef): CompiledExpression {\n const [tableAlias, ...propertyPath] = ref.path\n\n if (!tableAlias) {\n throw new Error(`Reference path cannot be empty`)\n }\n\n // Pre-compile the property path navigation\n if (propertyPath.length === 0) {\n // Simple table reference\n return (namespacedRow) => namespacedRow[tableAlias]\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n return tableData?.[prop]\n }\n } else {\n // Multiple property navigation\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n if (tableData === undefined) {\n return undefined\n }\n\n let value: any = tableData\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n}\n\n/**\n * Compiles a function expression into an optimized evaluator\n */\nfunction compileFunction(func: Func): CompiledExpression {\n // Pre-compile all arguments\n const compiledArgs = func.args.map(compileExpression)\n\n switch (func.name) {\n // Comparison operators\n case `eq`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a === b\n }\n }\n case `gt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a > b\n }\n }\n case `gte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a >= b\n }\n }\n case `lt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a < b\n }\n }\n case `lte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a <= b\n }\n }\n\n // Boolean operators\n case `and`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (!compiledArg(namespacedRow)) {\n return false\n }\n }\n return true\n }\n case `or`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (compiledArg(namespacedRow)) {\n return true\n }\n }\n return false\n }\n case `not`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => !arg(namespacedRow)\n }\n\n // Array operators\n case `in`: {\n const valueEvaluator = compiledArgs[0]!\n const arrayEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const array = arrayEvaluator(namespacedRow)\n if (!Array.isArray(array)) {\n return false\n }\n return array.includes(value)\n }\n }\n\n // String operators\n case `like`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, false)\n }\n }\n case `ilike`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, true)\n }\n }\n\n // String functions\n case `upper`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toUpperCase() : value\n }\n }\n case `lower`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toLowerCase() : value\n }\n }\n case `length`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n if (typeof value === `string`) {\n return value.length\n }\n if (Array.isArray(value)) {\n return value.length\n }\n return 0\n }\n }\n case `concat`:\n return (namespacedRow) => {\n return compiledArgs\n .map((evaluator) => {\n const arg = evaluator(namespacedRow)\n try {\n return String(arg ?? ``)\n } catch {\n try {\n return JSON.stringify(arg) || ``\n } catch {\n return `[object]`\n }\n }\n })\n .join(``)\n }\n case `coalesce`:\n return (namespacedRow) => {\n for (const evaluator of compiledArgs) {\n const value = evaluator(namespacedRow)\n if (value !== null && value !== undefined) {\n return value\n }\n }\n return null\n }\n\n // Math functions\n case `add`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) + (b ?? 0)\n }\n }\n case `subtract`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) - (b ?? 0)\n }\n }\n case `multiply`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) * (b ?? 0)\n }\n }\n case `divide`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n const divisor = b ?? 0\n return divisor !== 0 ? (a ?? 0) / divisor : null\n }\n }\n\n default:\n throw new Error(`Unknown function: ${func.name}`)\n }\n}\n\n/**\n * Evaluates LIKE/ILIKE patterns\n */\nfunction evaluateLike(\n value: any,\n pattern: any,\n caseInsensitive: boolean\n): boolean {\n if (typeof value !== `string` || typeof pattern !== `string`) {\n return false\n }\n\n const searchValue = caseInsensitive ? value.toLowerCase() : value\n const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern\n\n // Convert SQL LIKE pattern to regex\n // First escape all regex special chars except % and _\n let regexPattern = searchPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n\n // Then convert SQL wildcards to regex\n regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence\n regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(searchValue)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"evaluators.cjs","sources":["../../../../src/query/compiler/evaluators.ts"],"sourcesContent":["import type { BasicExpression, Func, PropRef } from \"../ir.js\"\nimport type { NamespacedRow } from \"../../types.js\"\n\n/**\n * Compiled expression evaluator function type\n */\nexport type CompiledExpression = (namespacedRow: NamespacedRow) => any\n\n/**\n * Compiles an expression into an optimized evaluator function.\n * This eliminates branching during evaluation by pre-compiling the expression structure.\n */\nexport function compileExpression(expr: BasicExpression): CompiledExpression {\n switch (expr.type) {\n case `val`: {\n // For constant values, return a function that just returns the value\n const value = expr.value\n return () => value\n }\n\n case `ref`: {\n // For references, pre-compile the property path navigation\n return compileRef(expr)\n }\n\n case `func`: {\n // For functions, pre-compile the function and its arguments\n return compileFunction(expr)\n }\n\n default:\n throw new Error(`Unknown expression type: ${(expr as any).type}`)\n }\n}\n\n/**\n * Compiles a reference expression into an optimized evaluator\n */\nfunction compileRef(ref: PropRef): CompiledExpression {\n const [tableAlias, ...propertyPath] = ref.path\n\n if (!tableAlias) {\n throw new Error(`Reference path cannot be empty`)\n }\n\n // Pre-compile the property path navigation\n if (propertyPath.length === 0) {\n // Simple table reference\n return (namespacedRow) => namespacedRow[tableAlias]\n } else if (propertyPath.length === 1) {\n // Single property access - most common case\n const prop = propertyPath[0]!\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n return tableData?.[prop]\n }\n } else {\n // Multiple property navigation\n return (namespacedRow) => {\n const tableData = namespacedRow[tableAlias]\n if (tableData === undefined) {\n return undefined\n }\n\n let value: any = tableData\n for (const prop of propertyPath) {\n if (value == null) {\n return value\n }\n value = value[prop]\n }\n return value\n }\n }\n}\n\n/**\n * Compiles a function expression into an optimized evaluator\n */\nfunction compileFunction(func: Func): CompiledExpression {\n // Pre-compile all arguments\n const compiledArgs = func.args.map(compileExpression)\n\n switch (func.name) {\n // Comparison operators\n case `eq`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a === b\n }\n }\n case `gt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a > b\n }\n }\n case `gte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a >= b\n }\n }\n case `lt`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a < b\n }\n }\n case `lte`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return a <= b\n }\n }\n\n // Boolean operators\n case `and`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (!compiledArg(namespacedRow)) {\n return false\n }\n }\n return true\n }\n case `or`:\n return (namespacedRow) => {\n for (const compiledArg of compiledArgs) {\n if (compiledArg(namespacedRow)) {\n return true\n }\n }\n return false\n }\n case `not`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => !arg(namespacedRow)\n }\n\n // Array operators\n case `in`: {\n const valueEvaluator = compiledArgs[0]!\n const arrayEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const array = arrayEvaluator(namespacedRow)\n if (!Array.isArray(array)) {\n return false\n }\n return array.includes(value)\n }\n }\n\n // String operators\n case `like`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, false)\n }\n }\n case `ilike`: {\n const valueEvaluator = compiledArgs[0]!\n const patternEvaluator = compiledArgs[1]!\n return (namespacedRow) => {\n const value = valueEvaluator(namespacedRow)\n const pattern = patternEvaluator(namespacedRow)\n return evaluateLike(value, pattern, true)\n }\n }\n\n // String functions\n case `upper`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toUpperCase() : value\n }\n }\n case `lower`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n return typeof value === `string` ? value.toLowerCase() : value\n }\n }\n case `length`: {\n const arg = compiledArgs[0]!\n return (namespacedRow) => {\n const value = arg(namespacedRow)\n if (typeof value === `string`) {\n return value.length\n }\n if (Array.isArray(value)) {\n return value.length\n }\n return 0\n }\n }\n case `concat`:\n return (namespacedRow) => {\n return compiledArgs\n .map((evaluator) => {\n const arg = evaluator(namespacedRow)\n try {\n return String(arg ?? ``)\n } catch {\n try {\n return JSON.stringify(arg) || ``\n } catch {\n return `[object]`\n }\n }\n })\n .join(``)\n }\n case `coalesce`:\n return (namespacedRow) => {\n for (const evaluator of compiledArgs) {\n const value = evaluator(namespacedRow)\n if (value !== null && value !== undefined) {\n return value\n }\n }\n return null\n }\n\n // Math functions\n case `add`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) + (b ?? 0)\n }\n }\n case `subtract`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) - (b ?? 0)\n }\n }\n case `multiply`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n return (a ?? 0) * (b ?? 0)\n }\n }\n case `divide`: {\n const argA = compiledArgs[0]!\n const argB = compiledArgs[1]!\n return (namespacedRow) => {\n const a = argA(namespacedRow)\n const b = argB(namespacedRow)\n const divisor = b ?? 0\n return divisor !== 0 ? (a ?? 0) / divisor : null\n }\n }\n\n default:\n throw new Error(`Unknown function: ${func.name}`)\n }\n}\n\n/**\n * Evaluates LIKE/ILIKE patterns\n */\nfunction evaluateLike(\n value: any,\n pattern: any,\n caseInsensitive: boolean\n): boolean {\n if (typeof value !== `string` || typeof pattern !== `string`) {\n return false\n }\n\n const searchValue = caseInsensitive ? value.toLowerCase() : value\n const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern\n\n // Convert SQL LIKE pattern to regex\n // First escape all regex special chars except % and _\n let regexPattern = searchPattern.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n\n // Then convert SQL wildcards to regex\n regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence\n regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(searchValue)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-by.cjs","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import { filter, groupBy, groupByOperators, map } from \"@electric-sql/d2mini\"\nimport { Func, PropRef } 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 PropRef([`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 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 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":["groupByOperators","aggregates","keyExtractor","groupBy","map","compileExpression","filter","PropRef","Func"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"group-by.cjs","sources":["../../../../src/query/compiler/group-by.ts"],"sourcesContent":["import { filter, groupBy, groupByOperators, map } from \"@electric-sql/d2mini\"\nimport { Func, PropRef } 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 PropRef([`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 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 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":["groupByOperators","aggregates","keyExtractor","groupBy","map","compileExpression","filter","PropRef","Func"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","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":["map","processJoins","compileExpression","filter","processSelectToResults","processGroupBy","processOrderBy","resultPipeline","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","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":["map","processJoins","compileExpression","filter","processSelectToResults","processGroupBy","processOrderBy","resultPipeline","result"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"joins.cjs","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":["compileExpression","map","joinOperator","consolidate","compileQuery","filter"],"mappings":";;;;;AAyBO,SAAS,aACd,UACA,aACA,QACA,gBACA,WACA,OAC0B;AAC1B,MAAI,iBAAiB;AAErB,aAAW,cAAc,aAAa;
|
|
1
|
+
{"version":3,"file":"joins.cjs","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":["compileExpression","map","joinOperator","consolidate","compileQuery","filter"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"order-by.cjs","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":["compileExpression","orderByWithFractionalIndex"],"mappings":";;;;AAWO,SAAS,eACd,UACA,eACA,OACA,QAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,YAAY;AAAA,IACrD,oBAAoBA,WAAAA,kBAAkB,OAAO,UAAU;AAAA,IACvD,WAAW,OAAO;AAAA,EAAA,EAClB;
|
|
1
|
+
{"version":3,"file":"order-by.cjs","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":["compileExpression","orderByWithFractionalIndex"],"mappings":";;;;AAWO,SAAS,eACd,UACA,eACA,OACA,QAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,YAAY;AAAA,IACrD,oBAAoBA,WAAAA,kBAAkB,OAAO,UAAU;AAAA,IACvD,WAAW,OAAO;AAAA,EAAA,EAClB;AAGF,QAAM,iBAAiB,CAAC,QAAoD;AAM1E,UAAM,iBAAiB,EAAE,GAAG,IAAA;AAG5B,QAAI,IAAI,kBAAkB;AAExB,aAAO,OAAO,gBAAgB,IAAI,gBAAgB;AAAA,IACpD;AAEA,QAAI,cAAc,SAAS,GAAG;AAE5B,aAAO,gBAAgB;AAAA,QAAI,CAAC,aAC1B,SAAS,mBAAmB,cAAc;AAAA,MAAA;AAAA,IAE9C,WAAW,cAAc,WAAW,GAAG;AAErC,YAAM,WAAW,gBAAgB,CAAC;AAClC,aAAO,SAAS,mBAAmB,cAAc;AAAA,IACnD;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,CAAC,GAAQ,MAAmB;AAEhD,QAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,QAAI,KAAK,KAAM,QAAO;AACtB,QAAI,KAAK,KAAM,QAAO;AAGtB,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAClD,aAAO,EAAE,cAAc,CAAC;AAAA,IAC1B;AAGA,QAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACxC,eAAS,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;AAChB,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO,EAAE,SAAS,EAAE;AAAA,IACtB;AAGA,QAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,aAAO,EAAE,YAAY,EAAE,QAAA;AAAA,IACzB;AAGA,UAAM,cAAc,OAAO,MAAM,YAAY,OAAO,MAAM;AAC1D,UAAM,UAAU,MAAM,QAAQ,MAAM;AACpC,QAAI,eAAe,SAAS;AAC1B,aAAO,EAAE,SAAA,EAAW,cAAc,EAAE,UAAU;AAAA,IAChD;AAEA,QAAI,IAAI,EAAG,QAAO;AAClB,QAAI,IAAI,EAAG,QAAO;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,CAAC,GAAY,MAAuB;AACzD,WAAO,cAAc,GAAG,CAAC;AAAA,EAC3B;AAGA,QAAM,iBAAiB,MAAM;AAC3B,WAAO,CAAC,GAAY,MAAe;AAEjC,UAAI,cAAc,SAAS,GAAG;AAC5B,cAAM,SAAS;AACf,cAAM,SAAS;AACf,iBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,gBAAM,YAAY,cAAc,CAAC,EAAG;AACpC,gBAAM,YACJ,cAAc,SAAS,iBAAiB;AAC1C,gBAAM,SAAS,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAC7C,cAAI,WAAW,GAAG;AAChB,mBAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO,OAAO,SAAS,OAAO;AAAA,MAChC;AAGA,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,YAAY,cAAc,CAAC,EAAG;AACpC,eAAO,cAAc,SAAS,eAAe,GAAG,CAAC,IAAI,cAAc,GAAG,CAAC;AAAA,MACzE;AAEA,aAAO,cAAc,GAAG,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,aAAa,eAAA;AAGnB,SAAO,SAAS;AAAA,IACdC,OAAAA,2BAA2B,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA;AAAA,EAAA;AAGL;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.cjs","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":["compileExpression","map"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"select.cjs","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":["compileExpression","map"],"mappings":";;;;AAaO,SAAS,uBACd,UACA,QACA,YAC0B;AAE1B,QAAM,iBAGD,CAAA;AACL,QAAM,gBAA+B,CAAA;AAErC,aAAW,CAAC,OAAO,UAAU,KAAK,OAAO,QAAQ,MAAM,GAAG;AACxD,QAAI,MAAM,WAAW,qBAAqB,GAAG;AAE3C,YAAM,aAAa,MAAM,QAAQ,uBAAuB,EAAE;AAC1D,oBAAc,KAAK,UAAU;AAAA,IAC/B,OAAO;AACL,UAAI,sBAAsB,UAAU,GAAG;AAGrC,uBAAe,KAAK;AAAA,UAClB;AAAA,UACA,oBAAoB,MAAM;AAAA;AAAA,QAAA,CAC3B;AAAA,MACH,OAAO;AACL,uBAAe,KAAK;AAAA,UAClB;AAAA,UACA,oBAAoBA,WAAAA,kBAAkB,UAA6B;AAAA,QAAA,CACpE;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO,SAAS;AAAA,IACdC,OAAAA,IAAI,CAAC,CAAC,KAAK,aAAa,MAAM;AAC5B,YAAM,gBAAqC,CAAA;AAG3C,iBAAW,cAAc,eAAe;AACtC,cAAM,YAAY,cAAc,UAAU;AAC1C,YAAI,aAAa,OAAO,cAAc,UAAU;AAE9C,qBAAW,CAAC,WAAW,UAAU,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC/D,gBAAI,EAAE,aAAa,gBAAgB;AACjC,4BAAc,SAAS,IAAI;AAAA,YAC7B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,iBAAW,EAAE,OAAO,mBAAA,KAAwB,gBAAgB;AAC1D,sBAAc,KAAK,IAAI,mBAAmB,aAAa;AAAA,MACzD;AAGA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,UACE,GAAG;AAAA,UACH,kBAAkB;AAAA,QAAA;AAAA,MACpB;AAAA,IAKJ,CAAC;AAAA,EAAA;AAEL;AAkEA,SAAS,sBACP,MACmB;AACnB,SAAO,KAAK,SAAS;AACvB;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\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\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\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 = BasicExpression<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 direction: OrderByDirection\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"],"names":[],"mappings":";;AA4DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;
|
|
1
|
+
{"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\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\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\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 = BasicExpression<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 direction: OrderByDirection\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"],"names":[],"mappings":";;AA4DA,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;;;;;;;"}
|
|
@@ -27,7 +27,7 @@ function liveQueryCollectionOptions(config) {
|
|
|
27
27
|
const collections = extractCollectionsFromQuery(query);
|
|
28
28
|
const allCollectionsReady = () => {
|
|
29
29
|
return Object.values(collections).every(
|
|
30
|
-
(collection2) => collection2.status === `ready`
|
|
30
|
+
(collection2) => collection2.status === `ready` || collection2.status === `initialCommit`
|
|
31
31
|
);
|
|
32
32
|
};
|
|
33
33
|
let graphCache;
|