UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1,472 lines 50.3 kB
import { map, join, filter, serializeValue, reduce, tap, distinct, concat } from "@tanstack/db-ivm";
import { optimizeQuery } from "../optimizer.js";
import { DistinctRequiresSelectError, FnSelectWithGroupByError, HavingRequiresGroupByError, LimitOffsetRequireOrderByError, DuplicateAliasInSubqueryError, UnsupportedFromTypeError, UnsupportedFnSelectResultError, CollectionInputNotFoundError } from "../../errors.js";
import { VIRTUAL_PROP_NAMES } from "../../virtual-props.js";
import { BaseQueryBuilder } from "../builder/index.js";
import { ToArrayWrapper, ConcatToArrayWrapper, MaterializeWrapper, CaseWhenWrapper } from "../builder/functions.js";
import { getWhereExpression, collectCollectionSources, IncludesSubquery, ConditionalSelect, Value, isExpressionLike, PropRef } from "../ir.js";
import { ensureIndexForField } from "../../indexes/auto-index.js";
import { deepEquals } from "../../utils.js";
import { compileExpression, toBooleanPredicate, isCaseWhenConditionTrue } from "./evaluators.js";
import { processJoins, registerLazyDemandPlan } from "./joins.js";
import { processGroupBy, containsAggregate } from "./group-by.js";
import { getLazyLoadTargets } from "./lazy-targets.js";
import { processOrderBy } from "./order-by.js";
import { processSelect } from "./select.js";
const INCLUDES_ROUTING = /* @__PURE__ */ Symbol(`includesRouting`);
const INCLUDES_PUBLIC_KEY = /* @__PURE__ */ Symbol(`includesPublicKey`);
const FN_SELECT_STATE = /* @__PURE__ */ Symbol(`fnSelectState`);
const SKIP_INCLUDE = /* @__PURE__ */ Symbol(`skipInclude`);
function getUnsupportedFnSelectResultDescription(value, seen = /* @__PURE__ */ new Set()) {
  if (value instanceof BaseQueryBuilder) return `a child query builder`;
  if (value instanceof ToArrayWrapper) return `toArray()`;
  if (value instanceof ConcatToArrayWrapper) return `concat(toArray())`;
  if (value instanceof MaterializeWrapper) return `materialize()`;
  if (value instanceof CaseWhenWrapper) return `caseWhen()`;
  if (isExpressionLike(value)) {
    return value && typeof value === `object` && `name` in value && typeof value.name === `string` ? `${value.name}()` : `a query expression`;
  }
  if (value === null || typeof value !== `object` || seen.has(value)) {
    return void 0;
  }
  seen.add(value);
  const keys = [
    ...Object.keys(value),
    ...Object.getOwnPropertySymbols(value).filter(
      (key) => Object.prototype.propertyIsEnumerable.call(value, key)
    )
  ];
  for (const key of keys) {
    const entry = value[key];
    const unsupported = getUnsupportedFnSelectResultDescription(entry, seen);
    if (unsupported) return unsupported;
  }
  return void 0;
}
function validateFnSelectResult(value) {
  const unsupportedValueDescription = getUnsupportedFnSelectResultDescription(value);
  if (unsupportedValueDescription) {
    throw new UnsupportedFnSelectResultError(unsupportedValueDescription);
  }
}
function compileQuery(rawQuery, inputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap(), parentKeyStream, childCorrelationField) {
  const cachedResult = parentKeyStream === void 0 ? cache.get(rawQuery) : void 0;
  if (cachedResult) {
    return cachedResult;
  }
  validateQueryStructure(rawQuery);
  const { optimizedQuery, sourceWhereClauses } = optimizeQuery(rawQuery);
  let query = optimizedQuery;
  queryMapping.set(query, rawQuery);
  mapNestedQueries(query, rawQuery, queryMapping);
  const allInputs = { ...inputs };
  bindSourceInputs(rawQuery, allInputs);
  const aliasToCollectionId = {};
  const aliasRemapping = {};
  const sources = {};
  const {
    alias: mainSource,
    collectionId: mainCollectionId,
    pipeline: initialPipeline,
    sources: fromSources,
    sourceIncludes,
    directIncludes,
    isUnionFrom
  } = processFromClause(
    query.from,
    allInputs,
    collections,
    subscriptions,
    callbacks,
    lazySources,
    optimizableOrderByCollections,
    setWindowFn,
    cache,
    queryMapping,
    aliasToCollectionId,
    aliasRemapping,
    sourceWhereClauses
  );
  Object.assign(sources, fromSources);
  let pipeline = initialPipeline;
  const childCorrelationAlias = childCorrelationField?.path[0];
  const joinsParentAfterJoins = !isUnionFrom && parentKeyStream !== void 0 && childCorrelationField !== void 0 && childCorrelationAlias !== mainSource;
  if (!isUnionFrom && parentKeyStream && childCorrelationField && !joinsParentAfterJoins) {
    const mainInput = sources[mainSource];
    let filteredMainInput = mainInput;
    const childFieldPath = childCorrelationField.path.slice(1);
    const childRekeyed = mainInput.pipe(
      map(([key, row]) => {
        const correlationValue = getNestedValue(row, childFieldPath);
        return [correlationValue, [key, row]];
      })
    );
    const joined = childRekeyed.pipe(join(parentKeyStream, `inner`));
    filteredMainInput = joined.pipe(
      filter(([_correlationValue, [childSide]]) => {
        return childSide != null;
      }),
      map(([correlationValue, [childSide, parentSide]]) => {
        const [childKey, childRow] = childSide;
        const tagged = {
          ...childRow,
          __correlationKey: correlationValue,
          [INCLUDES_PUBLIC_KEY]: childKey
        };
        if (parentSide != null) {
          tagged.__parentContext = parentSide;
        }
        const effectiveKey = parentSide != null ? serializeValue([childKey, parentSide]) : childKey;
        return [effectiveKey, tagged];
      })
    );
    sources[mainSource] = filteredMainInput;
    pipeline = wrapInputWithAlias(filteredMainInput, mainSource);
  }
  if (query.join && query.join.length > 0) {
    pipeline = processJoins(
      pipeline,
      query.join,
      sources,
      mainCollectionId,
      mainSource,
      allInputs,
      cache,
      queryMapping,
      collections,
      subscriptions,
      callbacks,
      lazySources,
      optimizableOrderByCollections,
      setWindowFn,
      rawQuery,
      compileQuery,
      aliasToCollectionId,
      aliasRemapping,
      sourceWhereClauses,
      parentKeyStream !== void 0 && !joinsParentAfterJoins
    );
  }
  if (joinsParentAfterJoins) {
    const compiledChildCorrelation = compileExpression(childCorrelationField);
    pipeline = pipeline.pipe(
      map(
        ([key, row]) => [compiledChildCorrelation(row), [key, row]]
      ),
      join(parentKeyStream, `inner`),
      filter(([_correlationValue, [childSide]]) => childSide != null),
      map(([correlationValue, [childSide, parentSide]]) => {
        const [childKey, row] = childSide;
        const namespaced = { ...row };
        namespaced[mainSource] = {
          ...namespaced[mainSource],
          __correlationKey: correlationValue,
          [INCLUDES_PUBLIC_KEY]: childKey
        };
        if (parentSide != null) {
          Object.assign(namespaced, parentSide);
          namespaced.__parentContext = parentSide;
        }
        const effectiveKey = parentSide != null ? serializeValue([childKey, parentSide]) : childKey;
        return [effectiveKey, namespaced];
      })
    );
  }
  if (query.where && query.where.length > 0) {
    for (const where of query.where) {
      const whereExpression = getWhereExpression(where);
      const compiledWhere = compileExpression(whereExpression);
      pipeline = pipeline.pipe(
        filter(([_key, namespacedRow]) => {
          return toBooleanPredicate(compiledWhere(namespacedRow));
        })
      );
    }
  }
  if (query.fnWhere && query.fnWhere.length > 0) {
    for (const fnWhere of query.fnWhere) {
      pipeline = pipeline.pipe(
        filter(([_key, namespacedRow]) => {
          return toBooleanPredicate(fnWhere(namespacedRow));
        })
      );
    }
  }
  const includesResults = !query.select ? [...directIncludes] : [];
  const includesRoutingFns = [];
  for (const { sourceAlias, include } of sourceIncludes) {
    const projectedPaths = query.select != null ? findProjectedSourceIncludePaths(
      query.select,
      sourceAlias,
      include.resultPath
    ) : query.fnSelect ? [] : [
      {
        path: [sourceAlias, ...include.resultPath],
        guards: []
      }
    ];
    if (projectedPaths.length === 0) {
      continue;
    }
    for (const { path: resultPath, guards } of projectedPaths) {
      const fieldName = getUniqueIncludesRoutingKey(
        `${sourceAlias}.${resultPath.join(`.`)}`,
        includesRoutingFns
      );
      const compiledGuards = guards.map((guard) => ({
        condition: compileExpression(guard.condition),
        expected: guard.expected
      }));
      includesResults.push({
        ...include,
        fieldName,
        resultPath
      });
      includesRoutingFns.push({
        fieldName,
        getRouting: (nsRow) => {
          if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
            return { active: false, correlationKey: null, parentContext: null };
          }
          return nsRow[sourceAlias]?.[INCLUDES_ROUTING]?.[include.fieldName] ?? {
            active: false,
            correlationKey: null,
            parentContext: null
          };
        }
      });
    }
  }
  if (query.select && directIncludes.length > 0) {
    for (const include of directIncludes) {
      const projectedPaths = findProjectedResultIncludePaths(
        query.select,
        include.resultPath
      );
      for (const { path: resultPath, guards } of projectedPaths) {
        const fieldName = getUniqueIncludesRoutingKey(
          resultPath.join(`.`),
          includesRoutingFns
        );
        const compiledGuards = guards.map((guard) => ({
          condition: compileExpression(guard.condition),
          expected: guard.expected
        }));
        includesResults.push({
          ...include,
          fieldName,
          resultPath
        });
        includesRoutingFns.push({
          fieldName,
          getRouting: (nsRow) => {
            if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
              return {
                active: false,
                correlationKey: null,
                parentContext: null
              };
            }
            return nsRow[INCLUDES_ROUTING]?.[include.fieldName] ?? {
              active: false,
              correlationKey: null,
              parentContext: null
            };
          }
        });
      }
    }
  }
  if (query.select) {
    const includesEntries = extractIncludesFromSelect(query.select);
    if (includesEntries.length > 0) {
      query = { ...query, select: { ...query.select } };
    }
    for (const { key, path, subquery, guards } of includesEntries) {
      const fieldName = getUniqueIncludesRoutingKey(key, includesRoutingFns);
      const compiledCorrelation = compileExpression(subquery.correlationField);
      const compiledGuards = guards.map((guard) => ({
        condition: compileExpression(guard.condition),
        expected: guard.expected
      }));
      let parentKeys;
      if (subquery.parentProjection && subquery.parentProjection.length > 0) {
        const compiledProjections = subquery.parentProjection.map((ref) => ({
          alias: ref.path[0],
          field: ref.path.slice(1),
          compiled: compileExpression(ref)
        }));
        parentKeys = pipeline.pipe(
          map(([_key, nsRow]) => {
            if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
              return [SKIP_INCLUDE, null];
            }
            const parentContext = {};
            for (const proj of compiledProjections) {
              if (!parentContext[proj.alias]) {
                parentContext[proj.alias] = {};
              }
              const value = proj.compiled(nsRow);
              let target = parentContext[proj.alias];
              for (let i = 0; i < proj.field.length - 1; i++) {
                if (!target[proj.field[i]]) {
                  target[proj.field[i]] = {};
                }
                target = target[proj.field[i]];
              }
              target[proj.field[proj.field.length - 1]] = value;
            }
            return [compiledCorrelation(nsRow), parentContext];
          })
        );
      } else {
        parentKeys = pipeline.pipe(
          map(([_key, nsRow]) => {
            if (!matchesConditionalSelectGuards(compiledGuards, nsRow)) {
              return [SKIP_INCLUDE, null];
            }
            return [compiledCorrelation(nsRow), null];
          })
        );
      }
      parentKeys = parentKeys.pipe(
        filter(([correlationValue]) => correlationValue !== SKIP_INCLUDE)
      );
      parentKeys = parentKeys.pipe(
        reduce(
          (values) => values.map(([v, mult]) => [v, mult > 0 ? 1 : 0])
        )
      );
      const childSourceAlias = subquery.childCorrelationField.path[0];
      const directChildCollection = subquery.query.from.type === `collectionRef` ? subquery.query.from.collection : void 0;
      const lazyTargets = getLazyLoadTargets(
        subquery.query,
        subquery.query.from,
        childSourceAlias,
        subquery.childCorrelationField,
        directChildCollection,
        aliasRemapping
      );
      if (lazyTargets.length > 0) {
        for (const target of lazyTargets) {
          lazySources.add(target.sourceId);
        }
        for (const target of lazyTargets) {
          const targetFieldName = target.path[0];
          if (targetFieldName) {
            ensureIndexForField(targetFieldName, target.path, target.collection);
          }
        }
        const initialKeys = getStaticDemandKeys(
          rawQuery,
          subquery.correlationField
        );
        const demandPlans = lazyTargets.map(
          (target) => registerLazyDemandPlan(callbacks, target, initialKeys)
        );
        const demandWeights = /* @__PURE__ */ new Map();
        parentKeys = parentKeys.pipe(
          tap((data) => {
            for (const [[correlationValue], weight] of data.getInner()) {
              if (correlationValue == null) continue;
              const encoded = serializeValue(correlationValue);
              const previous = demandWeights.get(encoded);
              const nextWeight = (previous?.weight ?? 0) + weight;
              if (nextWeight === 0) {
                demandWeights.delete(encoded);
              } else {
                demandWeights.set(encoded, {
                  key: correlationValue,
                  weight: nextWeight
                });
              }
            }
            const keys = new Set(
              [...demandWeights.values()].filter(({ weight }) => weight > 0).map(({ key: demandedKey }) => demandedKey)
            );
            for (let index = 0; index < lazyTargets.length; index++) {
              const target = lazyTargets[index];
              const plan = demandPlans[index];
              callbacks[target.sourceId]?.setDemand?.(plan, keys);
            }
          })
        );
      }
      const childQuery = subquery.parentFilters && subquery.parentFilters.length > 0 ? {
        ...subquery.query,
        where: [
          ...subquery.query.where || [],
          ...subquery.parentFilters
        ]
      } : subquery.query;
      const childResult = compileQuery(
        childQuery,
        allInputs,
        collections,
        subscriptions,
        callbacks,
        lazySources,
        optimizableOrderByCollections,
        setWindowFn,
        cache,
        queryMapping,
        parentKeys,
        subquery.childCorrelationField
      );
      Object.assign(aliasToCollectionId, childResult.aliasToCollectionId);
      Object.assign(aliasRemapping, childResult.aliasRemapping);
      for (const [alias, whereClause] of childResult.sourceWhereClauses) {
        sourceWhereClauses.set(alias, whereClause);
      }
      includesResults.push({
        pipeline: childResult.pipeline,
        fieldName,
        resultPath: path,
        correlationField: subquery.correlationField,
        childCorrelationField: subquery.childCorrelationField,
        hasOrderBy: !!(subquery.query.orderBy && subquery.query.orderBy.length > 0),
        childCompilationResult: childResult,
        parentProjection: subquery.parentProjection,
        materialization: subquery.materialization,
        scalarField: subquery.scalarField
      });
      if (subquery.parentProjection && subquery.parentProjection.length > 0) {
        const compiledProjs = subquery.parentProjection.map((ref) => ({
          alias: ref.path[0],
          field: ref.path.slice(1),
          compiled: compileExpression(ref)
        }));
        const compiledCorr = compiledCorrelation;
        const compiledRoutingGuards = compiledGuards;
        includesRoutingFns.push({
          fieldName,
          getRouting: (nsRow) => {
            if (!matchesConditionalSelectGuards(compiledRoutingGuards, nsRow)) {
              return {
                active: false,
                correlationKey: null,
                parentContext: null
              };
            }
            const parentContext = {};
            for (const proj of compiledProjs) {
              if (!parentContext[proj.alias]) {
                parentContext[proj.alias] = {};
              }
              const value = proj.compiled(nsRow);
              let target = parentContext[proj.alias];
              for (let i = 0; i < proj.field.length - 1; i++) {
                if (!target[proj.field[i]]) {
                  target[proj.field[i]] = {};
                }
                target = target[proj.field[i]];
              }
              target[proj.field[proj.field.length - 1]] = value;
            }
            return {
              active: true,
              correlationKey: compiledCorr(nsRow),
              parentContext
            };
          }
        });
      } else {
        const compiledRoutingGuards = compiledGuards;
        includesRoutingFns.push({
          fieldName,
          getRouting: (nsRow) => {
            if (!matchesConditionalSelectGuards(compiledRoutingGuards, nsRow)) {
              return {
                active: false,
                correlationKey: null,
                parentContext: null
              };
            }
            return {
              active: true,
              correlationKey: compiledCorrelation(nsRow),
              parentContext: null
            };
          }
        });
      }
      query = {
        ...query,
        select: replaceIncludesInSelect(query.select, path)
      };
    }
  }
  if (query.distinct && !query.fnSelect && !query.select && query.from.type !== `unionAll`) {
    throw new DistinctRequiresSelectError();
  }
  if (query.fnSelect && query.groupBy && query.groupBy.length > 0) {
    throw new FnSelectWithGroupByError();
  }
  if (query.fnSelect) {
    pipeline = pipeline.pipe(
      map(([key, namespacedRow]) => {
        const selectResults = query.fnSelect(namespacedRow);
        validateFnSelectResult(selectResults);
        let selected = selectResults;
        if (selectResults && typeof selectResults === `object`) {
          selected = Array.isArray(selectResults) ? [...selectResults] : { ...selectResults };
          const routing = namespacedRow[INCLUDES_ROUTING];
          if (routing) {
            selected[INCLUDES_ROUTING] = routing;
          }
          if (directIncludes.length > 0) {
            Object.defineProperty(selected, FN_SELECT_STATE, {
              value: {
                sourceRow: namespacedRow,
                fnSelect: query.fnSelect
              },
              enumerable: true,
              configurable: true
            });
          }
        }
        return [
          key,
          {
            ...namespacedRow,
            $selected: selected
          }
        ];
      })
    );
  } else if (query.select) {
    pipeline = processSelect(pipeline, query.select);
  } else {
    pipeline = pipeline.pipe(
      map(([key, namespacedRow]) => {
        const selectResults = !isUnionFrom && !query.join && !query.groupBy ? namespacedRow[mainSource] : namespacedRow;
        return [
          key,
          {
            ...namespacedRow,
            $selected: selectResults
          }
        ];
      })
    );
  }
  if (includesRoutingFns.length > 0) {
    pipeline = pipeline.pipe(
      map(([key, namespacedRow]) => {
        const routing = {};
        for (const { fieldName, getRouting } of includesRoutingFns) {
          routing[fieldName] = getRouting(namespacedRow);
        }
        const selected = Array.isArray(namespacedRow.$selected) ? [...namespacedRow.$selected] : { ...namespacedRow.$selected };
        selected[INCLUDES_ROUTING] = routing;
        return [key, { ...namespacedRow, $selected: selected }];
      })
    );
  }
  const groupByMainSource = parentKeyStream ? mainSource : void 0;
  if (query.groupBy && query.groupBy.length > 0) {
    pipeline = processGroupBy(
      pipeline,
      query.groupBy,
      query.having,
      query.select,
      query.fnHaving,
      mainCollectionId,
      groupByMainSource
    );
  } else if (query.select) {
    const hasAggregates = Object.values(query.select).some(
      (expr) => expr.type === `agg` || containsAggregate(expr)
    );
    if (hasAggregates) {
      pipeline = processGroupBy(
        pipeline,
        [],
        // Empty group by means single group
        query.having,
        query.select,
        query.fnHaving,
        mainCollectionId,
        groupByMainSource
      );
    }
  }
  if (query.having && (!query.groupBy || query.groupBy.length === 0)) {
    const hasAggregates = query.select ? Object.values(query.select).some((expr) => expr.type === `agg`) : false;
    if (!hasAggregates) {
      throw new HavingRequiresGroupByError();
    }
  }
  if (query.fnHaving && query.fnHaving.length > 0 && (!query.groupBy || query.groupBy.length === 0)) {
    for (const fnHaving of query.fnHaving) {
      pipeline = pipeline.pipe(
        filter(([_key, namespacedRow]) => {
          return fnHaving(namespacedRow);
        })
      );
    }
  }
  if (!query.select || !containsAggregate(query.select)) {
    pipeline = canonicalizeSelectedRows(
      pipeline,
      query,
      mainSource,
      parentKeyStream !== void 0
    );
  }
  const keyedSourceWhereClauses = keyWhereClausesBySource(
    rawQuery,
    sourceWhereClauses,
    aliasRemapping
  );
  if (query.distinct) {
    pipeline = pipeline.pipe(distinct(([_key, row]) => row.$selected));
  }
  if (query.orderBy && query.orderBy.length > 0) {
    const includesGroupKeyFn = parentKeyStream && (query.limit !== void 0 || query.offset !== void 0) ? (_key, row) => {
      const correlationKey = row?.[mainSource]?.__correlationKey ?? row?.__correlationKey;
      const parentContext = row?.__parentContext;
      if (parentContext != null) {
        return serializeValue([correlationKey, parentContext]);
      }
      return correlationKey;
    } : void 0;
    const orderedPipeline = processOrderBy(
      rawQuery,
      pipeline,
      query.orderBy,
      query.select || {},
      collections[mainCollectionId],
      optimizableOrderByCollections,
      setWindowFn,
      query.limit,
      query.offset,
      includesGroupKeyFn
    );
    const resultPipeline2 = orderedPipeline.pipe(
      map(([key, [row, orderByIndex]]) => {
        const raw = row.$selected;
        const finalResults = attachVirtualPropsToSelected(
          unwrapValue(raw),
          row
        );
        if (parentKeyStream) {
          const correlationKey = row[mainSource]?.__correlationKey ?? row.__correlationKey;
          const parentContext = row.__parentContext ?? null;
          const publicKey = getIncludesPublicKey(row, mainSource, key);
          const routedResults = stripInternalCorrelation(finalResults);
          return [
            key,
            [
              routedResults,
              orderByIndex,
              correlationKey,
              parentContext,
              publicKey
            ]
          ];
        }
        return [key, [finalResults, orderByIndex]];
      })
    );
    const compilationResult2 = {
      collectionId: mainCollectionId,
      pipeline: resultPipeline2,
      sourceWhereClauses: keyedSourceWhereClauses,
      aliasToCollectionId,
      aliasRemapping,
      includes: includesResults.length > 0 ? includesResults : void 0
    };
    if (parentKeyStream === void 0) cache.set(rawQuery, compilationResult2);
    return compilationResult2;
  } else if (query.limit !== void 0 || query.offset !== void 0) {
    throw new LimitOffsetRequireOrderByError();
  }
  const resultPipeline = pipeline.pipe(
    map(([key, row]) => {
      const raw = row.$selected;
      const finalResults = attachVirtualPropsToSelected(
        unwrapValue(raw),
        row
      );
      if (parentKeyStream) {
        const correlationKey = row[mainSource]?.__correlationKey ?? row.__correlationKey;
        const parentContext = row.__parentContext ?? null;
        const publicKey = getIncludesPublicKey(row, mainSource, key);
        const routedResults = stripInternalCorrelation(finalResults);
        return [
          key,
          [routedResults, void 0, correlationKey, parentContext, publicKey]
        ];
      }
      return [key, [finalResults, void 0]];
    })
  );
  const compilationResult = {
    collectionId: mainCollectionId,
    pipeline: resultPipeline,
    sourceWhereClauses: keyedSourceWhereClauses,
    aliasToCollectionId,
    aliasRemapping,
    includes: includesResults.length > 0 ? includesResults : void 0
  };
  if (parentKeyStream === void 0) cache.set(rawQuery, compilationResult);
  return compilationResult;
}
function keyWhereClausesBySource(query, clauses, aliasRemapping) {
  const sources = collectCollectionSources(query);
  const sourceIds = new Set(sources.map(({ sourceId }) => sourceId));
  const result = /* @__PURE__ */ new Map();
  for (const [key, clause] of clauses) {
    if (sourceIds.has(key)) {
      result.set(key, clause);
      continue;
    }
    const alias = aliasRemapping[key] ?? key;
    for (const source of sources) {
      if (source.alias === alias) result.set(source.sourceId, clause);
    }
  }
  return result;
}
function bindSourceInputs(query, inputs) {
  for (const source of collectCollectionSources(query)) {
    const input = inputs[source.sourceId] ?? inputs[source.alias];
    if (!input) continue;
    inputs[source.sourceId] = input;
    inputs[source.alias] = input;
  }
}
function canonicalizeSelectedRows(pipeline, query, mainSource, isIncludedRelation) {
  const compiledOrder = (query.orderBy ?? []).map(
    ({ expression }) => compileExpression(expression)
  );
  const signature = (row) => ({
    value: row.$selected,
    routing: row.$selected?.[INCLUDES_ROUTING],
    outerCorrelation: isIncludedRelation ? row[mainSource]?.__correlationKey : void 0,
    parentContext: isIncludedRelation ? row.__parentContext ?? row[mainSource]?.__parentContext ?? null : void 0,
    order: compiledOrder.map((evaluate) => evaluate(row))
  });
  return pipeline.pipe(
    reduce((values) => {
      const totalMultiplicity = values.reduce(
        (total, [, multiplicity]) => total + multiplicity,
        0
      );
      if (totalMultiplicity === 0) return [];
      if (totalMultiplicity < 0) {
        throw new Error(`Query row has negative multiplicity`);
      }
      const visible = values.find(([, multiplicity]) => multiplicity > 0)?.[0];
      if (!visible) throw new Error(`Query row has no positive contributor`);
      const visibleSignature = signature(visible);
      for (const [candidate, multiplicity] of values) {
        if (multiplicity > 0 && !deepEquals(visibleSignature, signature(candidate))) {
          throw new Error(
            `Query contributors with the same row key are not congruent`
          );
        }
      }
      return [[visible, 1]];
    })
  );
}
function collectDirectCollectionAliases(query) {
  const aliases = /* @__PURE__ */ new Set();
  for (const source of getFromSources(query.from)) {
    if (source.type === `collectionRef`) {
      aliases.add(source.alias);
    }
  }
  if (query.join) {
    for (const joinClause of query.join) {
      if (joinClause.from.type === `collectionRef`) {
        aliases.add(joinClause.from.alias);
      }
    }
  }
  return aliases;
}
function validateQueryStructure(query, parentCollectionAliases = /* @__PURE__ */ new Set()) {
  const currentLevelAliases = collectDirectCollectionAliases(query);
  for (const alias of currentLevelAliases) {
    if (parentCollectionAliases.has(alias)) {
      throw new DuplicateAliasInSubqueryError(
        alias,
        Array.from(parentCollectionAliases)
      );
    }
  }
  const combinedAliases = /* @__PURE__ */ new Set([
    ...parentCollectionAliases,
    ...currentLevelAliases
  ]);
  if (query.from.type === `unionAll`) {
    for (const branch of query.from.queries) {
      validateQueryStructure(branch, combinedAliases);
    }
  } else {
    for (const source of getFromSources(query.from)) {
      if (source.type === `queryRef`) {
        validateQueryStructure(source.query, combinedAliases);
      }
    }
  }
  if (query.join) {
    for (const joinClause of query.join) {
      if (joinClause.from.type === `queryRef`) {
        validateQueryStructure(joinClause.from.query, combinedAliases);
      }
    }
  }
  if (query.select) {
    for (const { subquery } of extractIncludesFromSelect(query.select)) {
      validateQueryStructure(subquery.query, combinedAliases);
    }
  }
}
function processFromClause(from, allInputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache, queryMapping, aliasToCollectionId, aliasRemapping, sourceWhereClauses) {
  if (from.type === `unionAll`) {
    return processUnionAll(
      from,
      allInputs,
      collections,
      subscriptions,
      callbacks,
      lazySources,
      optimizableOrderByCollections,
      setWindowFn,
      cache,
      queryMapping,
      aliasToCollectionId,
      aliasRemapping,
      sourceWhereClauses
    );
  }
  if (from.type !== `unionFrom`) {
    const { alias, input, collectionId, sourceIncludes: sourceIncludes2 } = processFrom(
      from,
      allInputs,
      collections,
      subscriptions,
      callbacks,
      lazySources,
      optimizableOrderByCollections,
      setWindowFn,
      cache,
      queryMapping,
      aliasToCollectionId,
      aliasRemapping,
      sourceWhereClauses
    );
    return {
      alias,
      pipeline: wrapInputWithAlias(input, alias),
      collectionId,
      sources: { [alias]: input },
      sourceIncludes: sourceIncludes2,
      directIncludes: [],
      isUnionFrom: false
    };
  }
  if (from.sources.length === 0) {
    throw new UnsupportedFromTypeError(`empty unionFrom`);
  }
  const sources = {};
  const sourceIncludes = [];
  let pipeline;
  let mainAlias = ``;
  let mainCollectionId = ``;
  for (const source of from.sources) {
    const {
      alias,
      input,
      collectionId,
      sourceIncludes: childSourceIncludes
    } = processFrom(
      source,
      allInputs,
      collections,
      subscriptions,
      callbacks,
      lazySources,
      optimizableOrderByCollections,
      setWindowFn,
      cache,
      queryMapping,
      aliasToCollectionId,
      aliasRemapping,
      sourceWhereClauses
    );
    if (!mainAlias) {
      mainAlias = alias;
      mainCollectionId = collectionId;
    }
    sources[alias] = input;
    sourceIncludes.push(...childSourceIncludes);
    const branch = wrapInputWithAlias(input, alias).pipe(
      map(([key, row]) => {
        return [`${alias}:${encodeKeyForUnionBranch(key)}`, row];
      })
    );
    pipeline = pipeline ? pipeline.pipe(concat(branch)) : branch;
  }
  return {
    alias: mainAlias,
    pipeline,
    collectionId: mainCollectionId,
    sources,
    sourceIncludes,
    directIncludes: [],
    isUnionFrom: true
  };
}
function processUnionAll(from, allInputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache, queryMapping, aliasToCollectionId, aliasRemapping, sourceWhereClauses) {
  if (from.queries.length === 0) {
    throw new UnsupportedFromTypeError(`empty unionAll`);
  }
  const sources = {};
  const sourceIncludes = [];
  const directIncludes = [];
  let pipeline;
  let mainCollectionId = ``;
  const branchAliases = /* @__PURE__ */ new Set();
  for (let index = 0; index < from.queries.length; index++) {
    const branch = from.queries[index];
    for (const source of getAllSources(branch)) {
      if (branchAliases.has(source.alias)) {
        throw new Error(
          `Duplicate source alias "${source.alias}" in unionAll query branches. Use distinct aliases in each branch before passing them to unionAll().`
        );
      }
      branchAliases.add(source.alias);
    }
    const branchResult = compileQuery(
      branch,
      allInputs,
      collections,
      subscriptions,
      callbacks,
      lazySources,
      optimizableOrderByCollections,
      setWindowFn,
      cache,
      queryMapping
    );
    if (!mainCollectionId) {
      mainCollectionId = branchResult.collectionId;
    }
    Object.assign(aliasToCollectionId, branchResult.aliasToCollectionId);
    Object.assign(aliasRemapping, branchResult.aliasRemapping);
    directIncludes.push(...branchResult.includes ?? []);
    Object.assign(sources, allInputs);
    for (const [alias, where] of branchResult.sourceWhereClauses) {
      sourceWhereClauses.set(alias, where);
    }
    const branchPipeline = branchResult.pipeline.pipe(
      map(([key, [row]]) => {
        return [`${index}:${encodeKeyForUnionBranch(key)}`, row];
      })
    );
    pipeline = pipeline ? pipeline.pipe(concat(branchPipeline)) : branchPipeline;
  }
  return {
    alias: ``,
    pipeline,
    collectionId: mainCollectionId,
    sources,
    sourceIncludes,
    directIncludes,
    isUnionFrom: true
  };
}
function wrapInputWithAlias(input, alias) {
  return input.pipe(
    map(([key, row]) => {
      const { __parentContext, ...cleanRow } = row;
      const nsRow = { [alias]: cleanRow };
      if (__parentContext) {
        Object.assign(nsRow, __parentContext);
        nsRow.__parentContext = __parentContext;
      }
      return [key, nsRow];
    })
  );
}
function encodeKeyForUnionBranch(key) {
  if (typeof key === `string`) {
    return `string:${key}`;
  }
  if (typeof key === `number`) {
    return `number:${String(key)}`;
  }
  if (typeof key === `bigint`) {
    return `bigint:${String(key)}`;
  }
  return `${typeof key}:${JSON.stringify(key)}`;
}
function processFrom(from, allInputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache, queryMapping, aliasToCollectionId, aliasRemapping, sourceWhereClauses) {
  switch (from.type) {
    case `collectionRef`: {
      const input = allInputs[from.sourceId] ?? allInputs[from.alias];
      if (!input) {
        throw new CollectionInputNotFoundError(
          from.alias,
          from.collection.id,
          Object.keys(allInputs)
        );
      }
      aliasToCollectionId[from.alias] = from.collection.id;
      return {
        alias: from.alias,
        input,
        collectionId: from.collection.id,
        sourceIncludes: []
      };
    }
    case `queryRef`: {
      const originalQuery = queryMapping.get(from.query) || from.query;
      const subQueryResult = compileQuery(
        originalQuery,
        allInputs,
        collections,
        subscriptions,
        callbacks,
        lazySources,
        optimizableOrderByCollections,
        setWindowFn,
        cache,
        queryMapping
      );
      Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId);
      Object.assign(aliasRemapping, subQueryResult.aliasRemapping);
      const isUserDefinedSubquery = queryMapping.has(from.query);
      const subqueryFromAlias = getFirstFromAlias(from.query.from);
      const isOptimizerCreated = !isUserDefinedSubquery && from.alias === subqueryFromAlias;
      if (!isOptimizerCreated) {
        for (const [alias, whereClause] of subQueryResult.sourceWhereClauses) {
          sourceWhereClauses.set(alias, whereClause);
        }
      }
      const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(
        (alias) => subQueryResult.aliasToCollectionId[alias] === subQueryResult.collectionId
      );
      if (innerAlias && innerAlias !== from.alias) {
        aliasRemapping[from.alias] = innerAlias;
      }
      const subQueryInput = subQueryResult.pipeline;
      const extractedInput = subQueryInput.pipe(
        map((data) => {
          const [key, [value, _orderByIndex]] = data;
          const unwrapped = unwrapValue(value);
          return [key, unwrapped];
        })
      );
      return {
        alias: from.alias,
        input: extractedInput,
        collectionId: subQueryResult.collectionId,
        sourceIncludes: subQueryResult.includes?.map((include) => ({
          sourceAlias: from.alias,
          include
        })) ?? []
      };
    }
    default:
      throw new UnsupportedFromTypeError(from.type);
  }
}
function isValue(raw) {
  return raw instanceof Value || raw && typeof raw === `object` && `type` in raw && raw.type === `val`;
}
function unwrapValue(value) {
  return isValue(value) ? value.value : value;
}
function attachVirtualPropsToSelected(selected, row) {
  if (!selected || typeof selected !== `object`) {
    return selected;
  }
  let needsMerge = false;
  for (const prop of VIRTUAL_PROP_NAMES) {
    if (selected[prop] == null && prop in row) {
      needsMerge = true;
      break;
    }
  }
  if (!needsMerge) {
    return selected;
  }
  const result = Array.isArray(selected) ? [...selected] : { ...selected };
  for (const prop of VIRTUAL_PROP_NAMES) {
    if (selected[prop] == null && prop in row) {
      result[prop] = row[prop];
    }
  }
  return result;
}
function stripInternalCorrelation(selected) {
  if (!selected || typeof selected !== `object` || !(`__correlationKey` in selected) && !(`__parentContext` in selected) && !(INCLUDES_PUBLIC_KEY in selected)) {
    return selected;
  }
  const result = Array.isArray(selected) ? [...selected] : { ...selected };
  delete result.__correlationKey;
  delete result.__parentContext;
  delete result[INCLUDES_PUBLIC_KEY];
  return result;
}
function getIncludesPublicKey(row, mainSource, fallback) {
  return row[mainSource]?.[INCLUDES_PUBLIC_KEY] ?? fallback;
}
function mapNestedQueries(optimizedQuery, originalQuery, queryMapping) {
  mapNestedFromQueries(optimizedQuery.from, originalQuery.from, queryMapping);
  if (optimizedQuery.join && originalQuery.join) {
    for (let i = 0; i < optimizedQuery.join.length && i < originalQuery.join.length; i++) {
      const optimizedJoin = optimizedQuery.join[i];
      const originalJoin = originalQuery.join[i];
      if (optimizedJoin.from.type === `queryRef` && originalJoin.from.type === `queryRef`) {
        queryMapping.set(optimizedJoin.from.query, originalJoin.from.query);
        mapNestedQueries(
          optimizedJoin.from.query,
          originalJoin.from.query,
          queryMapping
        );
      }
    }
  }
}
function getFromSources(from) {
  if (from.type === `unionFrom`) {
    return from.sources;
  }
  if (from.type === `unionAll`) {
    return [];
  }
  return [from];
}
function getAllSources(query) {
  return [
    ...getFromSources(query.from),
    ...query.join?.map((join2) => join2.from) ?? []
  ];
}
function getFirstFromAlias(from) {
  return getFromSources(from)[0]?.alias ?? ``;
}
function findProjectedSourceIncludePaths(select, sourceAlias, sourcePath) {
  const targetPath = [sourceAlias, ...sourcePath];
  return findProjectedIncludePaths(select, targetPath);
}
function findProjectedResultIncludePaths(select, resultPath) {
  return findProjectedIncludePaths(select, resultPath);
}
function findProjectedIncludePaths(select, targetPath) {
  const resultPaths = [];
  const visitSelectObject = (obj, prefix, guards) => {
    for (const [key, value] of Object.entries(obj)) {
      if (key.startsWith(`__SPREAD_SENTINEL__`)) {
        visitSpreadSentinel(key, value, prefix, guards);
        continue;
      }
      visitSelectValue(value, [...prefix, key], guards);
    }
  };
  const visitSpreadSentinel = (key, value, path, guards) => {
    const rest = key.slice(`__SPREAD_SENTINEL__`.length);
    const splitIndex = rest.lastIndexOf(`__`);
    const pathStr = splitIndex >= 0 ? rest.slice(0, splitIndex) : rest;
    const isRefExpr = value && typeof value === `object` && `type` in value && value.type === `ref`;
    const sourcePath = isRefExpr ? value.path : pathStr.split(`.`).filter(Boolean);
    if (pathStartsWith(targetPath, sourcePath)) {
      resultPaths.push({
        path: [...path, ...targetPath.slice(sourcePath.length)],
        guards
      });
    }
  };
  const visitSelectValue = (value, path, guards) => {
    if (value instanceof PropRef && pathStartsWith(targetPath, value.path)) {
      resultPaths.push({
        path: [...path, ...targetPath.slice(value.path.length)],
        guards
      });
      return;
    }
    if (value instanceof ConditionalSelect) {
      const previousBranchGuards = [];
      for (const branch of value.branches) {
        visitSelectValue(branch.value, path, [
          ...guards,
          ...previousBranchGuards,
          { condition: branch.condition, expected: true }
        ]);
        previousBranchGuards.push({
          condition: branch.condition,
          expected: false
        });
      }
      if (value.defaultValue !== void 0) {
        visitSelectValue(value.defaultValue, path, [
          ...guards,
          ...previousBranchGuards
        ]);
      }
      return;
    }
    if (isNestedSelectObject(value)) {
      visitSelectObject(value, path, guards);
    }
  };
  visitSelectObject(select, [], []);
  return resultPaths;
}
function pathStartsWith(path, prefix) {
  return prefix.length <= path.length && prefix.every((part, i) => path[i] === part);
}
function mapNestedFromQueries(optimizedFrom, originalFrom, queryMapping) {
  if (optimizedFrom.type === `unionAll` && originalFrom.type === `unionAll`) {
    for (let i = 0; i < optimizedFrom.queries.length && i < originalFrom.queries.length; i++) {
      const optimizedBranch = optimizedFrom.queries[i];
      const originalBranch = originalFrom.queries[i];
      queryMapping.set(optimizedBranch, originalBranch);
      mapNestedQueries(optimizedBranch, originalBranch, queryMapping);
    }
    return;
  }
  const optimizedSources = getFromSources(optimizedFrom);
  const originalSources = getFromSources(originalFrom);
  for (let i = 0; i < optimizedSources.length && i < originalSources.length; i++) {
    const optimizedSource = optimizedSources[i];
    const originalSource = originalSources[i];
    if (optimizedSource.type === `queryRef` && originalSource.type === `queryRef`) {
      queryMapping.set(optimizedSource.query, originalSource.query);
      mapNestedQueries(
        optimizedSource.query,
        originalSource.query,
        queryMapping
      );
    }
  }
}
function extractIncludesFromSelect(select) {
  const results = [];
  for (const [key, value] of Object.entries(select)) {
    if (key.startsWith(`__SPREAD_SENTINEL__`)) continue;
    if (value instanceof IncludesSubquery) {
      results.push({
        key: getIncludesRoutingKey([key], results),
        path: [key],
        subquery: value,
        guards: []
      });
    } else if (value instanceof ConditionalSelect) {
      collectIncludesFromConditionalSelect(value, [key], [], results);
    } else if (isNestedSelectObject(value)) {
      assertNoNestedIncludes(value, key);
    }
  }
  return results;
}
function collectIncludesFromConditionalSelect(conditional, prefixPath, guards, results) {
  const previousBranchGuards = [];
  for (const branch of conditional.branches) {
    collectIncludesFromSelectValue(
      branch.value,
      prefixPath,
      [
        ...guards,
        ...previousBranchGuards,
        { condition: branch.condition, expected: true }
      ],
      results
    );
    previousBranchGuards.push({
      condition: branch.condition,
      expected: false
    });
  }
  if (conditional.defaultValue !== void 0) {
    collectIncludesFromSelectValue(
      conditional.defaultValue,
      prefixPath,
      [...guards, ...previousBranchGuards],
      results
    );
  }
}
function collectIncludesFromSelectValue(value, prefixPath, guards, results) {
  if (value instanceof IncludesSubquery) {
    const key = getIncludesRoutingKey(prefixPath, results);
    results.push({ key, path: prefixPath, subquery: value, guards });
    return;
  }
  if (value instanceof ConditionalSelect) {
    collectIncludesFromConditionalSelect(value, prefixPath, guards, results);
    return;
  }
  if (!isNestedSelectObject(value)) {
    return;
  }
  for (const [key, child] of Object.entries(value)) {
    if (key.startsWith(`__SPREAD_SENTINEL__`)) continue;
    collectIncludesFromSelectValue(child, [...prefixPath, key], guards, results);
  }
}
function getIncludesRoutingKey(path, entries) {
  return getUniqueIncludesRoutingKey(path.join(`.`), entries);
}
function getUniqueIncludesRoutingKey(baseKey, entries) {
  const hasKey = (key2) => entries.some((entry) => (entry.key ?? entry.fieldName) === key2);
  if (!hasKey(baseKey)) {
    return baseKey;
  }
  let suffix = entries.length;
  let key = `${baseKey}#${suffix}`;
  while (hasKey(key)) {
    suffix++;
    key = `${baseKey}#${suffix}`;
  }
  return key;
}
function isNestedSelectObject(value) {
  return value != null && typeof value === `object` && !Array.isArray(value) && !isExpressionLike(value) && value.__refProxy !== true;
}
function assertNoNestedIncludes(obj, parentPath) {
  for (const [key, value] of Object.entries(obj)) {
    if (key.startsWith(`__SPREAD_SENTINEL__`)) continue;
    if (value instanceof IncludesSubquery) {
      throw new Error(
        `Includes subqueries must be at the top level of select(). Found nested includes at "${parentPath}.${key}".`
      );
    }
    if (isNestedSelectObject(value)) {
      assertNoNestedIncludes(value, `${parentPath}.${key}`);
    }
  }
}
function replaceIncludesInSelect(select, path) {
  return replaceIncludesInSelectValue(select, path, new Value(null)).value;
}
function replaceIncludesInSelectValue(value, path, replacement) {
  if (path.length === 0) {
    return replaceIncludesValue(value, replacement);
  }
  if (value instanceof ConditionalSelect) {
    return replaceIncludesInConditionalSelect(value, path, replacement);
  }
  if (!isNestedSelectObject(value)) {
    return { value, replaced: false };
  }
  if (path.length === 1) {
    const field = path[0];
    const result2 = replaceIncludesValue(value[field], replacement);
    if (!result2.replaced) {
      return { value, replaced: false };
    }
    return {
      value: {
        ...value,
        [field]: result2.value
      },
      replaced: true
    };
  }
  const [head, ...rest] = path;
  const result = replaceIncludesInSelectValue(value[head], rest, replacement);
  if (!result.replaced) {
    return { value, replaced: false };
  }
  return {
    value: {
      ...value,
      [head]: result.value
    },
    replaced: true
  };
}
function replaceIncludesValue(value, replacement) {
  if (value instanceof IncludesSubquery) {
    return { value: replacement, replaced: true };
  }
  if (value instanceof ConditionalSelect) {
    return replaceIncludesInConditionalSelect(value, [], replacement);
  }
  return { value, replaced: false };
}
function replaceIncludesInConditionalSelect(conditional, path, replacement) {
  let replaced = false;
  const branches = conditional.branches.map((branch) => {
    const result = path.length === 0 ? replaceIncludesValue(branch.value, replacement) : replaceIncludesInSelectValue(branch.value, path, replacement);
    if (!result.replaced) {
      return branch;
    }
    replaced = true;
    return { ...branch, value: result.value };
  });
  let defaultValue = conditional.defaultValue;
  if (conditional.defaultValue !== void 0) {
    const result = path.length === 0 ? replaceIncludesValue(conditional.defaultValue, replacement) : replaceIncludesInSelectValue(
      conditional.defaultValue,
      path,
      replacement
    );
    if (result.replaced) {
      replaced = true;
      defaultValue = result.value;
    }
  }
  if (!replaced) {
    return { value: conditional, replaced: false };
  }
  return {
    value: new ConditionalSelect(branches, defaultValue),
    replaced: true
  };
}
function getNestedValue(obj, path) {
  let value = obj;
  for (const segment of path) {
    if (value == null) return value;
    value = value[segment];
  }
  return value;
}
function matchesConditionalSelectGuards(guards, row) {
  return guards.every(
    (guard) => isCaseWhenConditionTrue(guard.condition(row)) === guard.expected
  );
}
function getStaticDemandKeys(query, ref) {
  const constraints = [];
  const visit = (expression) => {
    if (expression.type !== `func`) return;
    if (expression.name === `and`) {
      expression.args.forEach(visit);
      return;
    }
    if (expression.name !== `eq` && expression.name !== `in`) return;
    const [left, right] = expression.args;
    const value = left?.type === `ref` && pathsEqual(left.path, ref.path) && right instanceof Value ? right.value : right?.type === `ref` && pathsEqual(right.path, ref.path) && left instanceof Value ? left.value : void 0;
    if (value === void 0) return;
    constraints.push(new Set(Array.isArray(value) ? value : [value]));
  };
  query.where?.forEach((where) => visit(getWhereExpression(where)));
  if (constraints.length === 0) return /* @__PURE__ */ new Set();
  return new Set(
    [...constraints[0]].filter(
      (value) => constraints.slice(1).every((constraint) => constraint.has(value))
    )
  );
}
function pathsEqual(left, right) {
  return left.length === right.length && left.every((segment, index) => segment === right[index]);
}
export {
  FN_SELECT_STATE,
  INCLUDES_PUBLIC_KEY,
  INCLUDES_ROUTING,
  compileQuery,
  validateFnSelectResult
};
//# sourceMappingURL=index.js.map