{"version":3,"file":"walker.mjs","names":[],"sources":["../../src/sql/walker.ts"],"sourcesContent":["import type { Node, NullTestType, RangeVar } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport type {\n  DiscoveredColumnReference,\n  JsonbOperator,\n  SortContext,\n} from \"./analyzer.js\";\nimport { getNodeKind, is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { parseNudges } from \"./nudges.js\";\nimport type { Nudge } from \"./nudges.js\";\n\nconst JSONB_EXTRACTION_OPS = new Set([\"->\", \"->>\"]);\nconst COMPARISON_OPS = new Set([\n  \"=\",\n  \"<>\",\n  \"!=\",\n  \"<\",\n  \"<=\",\n  \">\",\n  \">=\",\n  \"~~\",\n  \"~~*\",\n  \"!~~\",\n  \"!~~*\",\n]);\n\n/** Information about tables that appear in the query */\nexport type TableMappings = Map<string, ColumnReferencePart>;\ntype SeenReferences = Map<string, number>;\n\n/**\n * Walks the AST of a sql query and extracts query metadata.\n * This pattern is used to segregate the mutable state that's more common for the\n * AST walking process from the rest of the analyzer.\n */\nexport class Walker {\n  private tableMappings: TableMappings = new Map();\n  private tempTables = new Set<string>();\n  private highlights: DiscoveredColumnReference[] = [];\n  private indexRepresentations = new Set<string>();\n  private indexesToCheck: DiscoveredColumnReference[] = [];\n  private highlightPositions = new Set<number>();\n  // used for tallying the amount of times we see stuff so\n  // we have a better idea of what to start off the algorithm with\n  private seenReferences: SeenReferences = new Map();\n  private shadowedAliases: ColumnReferencePart[] = [];\n  private nudges: Nudge[] = [];\n\n  constructor(private readonly query: string) {}\n\n  walk(root: Node) {\n    // reset state in case the class instance is reused\n    // reassigning vars here instead of using `map.clear()` to prevent\n    // accidentally mutating existing references\n    this.tableMappings = new Map();\n    this.tempTables = new Set<string>();\n    this.highlights = [];\n    this.indexRepresentations = new Set<string>();\n    this.indexesToCheck = [];\n    this.highlightPositions = new Set<number>();\n    this.seenReferences = new Map();\n    this.shadowedAliases = [];\n    this.nudges = [];\n\n    Walker.traverse(root, (node, stack) => {\n      const nodeNudges = parseNudges(node, stack);\n      this.nudges = [...this.nudges, ...nodeNudges];\n\n      // comments are not parsed here as they seem to be ignored.\n      //\n      // results cannot be indexed in any way because they alias a CTE\n      // with alias as (select ...)\n      //      ^^^^^\n      if (is(node, \"CommonTableExpr\")) {\n        if (node.CommonTableExpr.ctename) {\n          this.tempTables.add(node.CommonTableExpr.ctename);\n        }\n      }\n      // results cannot be indexed in any way because they alias a subquery\n      // select ... from (...) as alias\n      //                          ^^^^^\n      if (is(node, \"RangeSubselect\")) {\n        if (node.RangeSubselect.alias?.aliasname) {\n          this.tempTables.add(node.RangeSubselect.alias.aliasname);\n        }\n      }\n      // select ... from (...) where col is null\n      //                                 ^^^^^^^\n      if (is(node, \"NullTest\")) {\n        if (\n          node.NullTest.arg &&\n          node.NullTest.nulltesttype &&\n          is(node.NullTest.arg, \"ColumnRef\")\n        ) {\n          this.add(node.NullTest.arg, {\n            where: { nulltest: node.NullTest.nulltesttype },\n          });\n        }\n      }\n      // The parser does not wrap the `relation` field of UpdateStmt\n      // in a { RangeVar: ... } node (it's a raw RangeVar struct), so the\n      // generic RangeVar handler below never sees it. Handle it explicitly.\n      if (is(node, \"UpdateStmt\")) {\n        if (node.UpdateStmt.relation) {\n          this.registerTable(node.UpdateStmt.relation);\n        }\n      }\n      // Same as UpdateStmt: DeleteStmt.relation is a raw RangeVar struct.\n      if (is(node, \"DeleteStmt\")) {\n        if (node.DeleteStmt.relation) {\n          this.registerTable(node.DeleteStmt.relation);\n        }\n      }\n      // can be indexed as the alias refers to a regular table\n      // but the alias has to be mapped to the original table name\n      // select ... from table as alias\n      //                          ^^^^^\n      if (is(node, \"RangeVar\") && node.RangeVar.relname) {\n        this.registerTable(node.RangeVar);\n      }\n      // select ... from table order by col asc\n      //                       ^^^^^^^^^^^^^^^^\n      if (is(node, \"SortBy\")) {\n        // we don't care about sorting by anything that's not a column reference\n        // because it couldn't be indexed anyway.\n        // TODO: mark that expression as unindexable? It's just better for debugging\n        if (node.SortBy.node && is(node.SortBy.node, \"ColumnRef\")) {\n          this.add(node.SortBy.node, {\n            sort: {\n              dir: node.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\",\n              nulls: node.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\",\n            },\n          });\n        }\n      }\n      // select ... from table1 join table2 t2 on table1.col = t2.col\n      //                                    ^^\n      if (is(node, \"JoinExpr\") && node.JoinExpr.quals) {\n        if (is(node.JoinExpr.quals, \"A_Expr\")) {\n          if (\n            node.JoinExpr.quals.A_Expr.lexpr &&\n            is(node.JoinExpr.quals.A_Expr.lexpr, \"ColumnRef\")\n          ) {\n            this.add(node.JoinExpr.quals.A_Expr.lexpr);\n          }\n          if (\n            node.JoinExpr.quals.A_Expr.rexpr &&\n            is(node.JoinExpr.quals.A_Expr.rexpr, \"ColumnRef\")\n          ) {\n            this.add(node.JoinExpr.quals.A_Expr.rexpr);\n          }\n        }\n      }\n      // select ... from table where data @> '{\"key\": \"val\"}'\n      //                             ^^^^\n      if (is(node, \"A_Expr\") && node.A_Expr.kind === \"AEXPR_OP\") {\n        const opName =\n          node.A_Expr.name?.[0] &&\n          is(node.A_Expr.name[0], \"String\") &&\n          node.A_Expr.name[0].String.sval;\n        if (\n          opName &&\n          (opName === \"@>\" ||\n            opName === \"?\" ||\n            opName === \"?|\" ||\n            opName === \"?&\" ||\n            opName === \"@@\" ||\n            opName === \"@?\")\n        ) {\n          const jsonbOperator = opName as JsonbOperator;\n          if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"ColumnRef\")) {\n            this.add(node.A_Expr.lexpr, { jsonbOperator });\n          }\n          if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"ColumnRef\")) {\n            this.add(node.A_Expr.rexpr, { jsonbOperator });\n          }\n        }\n        // select ... from table where data->>'email' = 'alice@example.com'\n        //                             ^^^^^^^^^^^^^^\n        if (opName && COMPARISON_OPS.has(opName)) {\n          for (const operand of [node.A_Expr.lexpr, node.A_Expr.rexpr]) {\n            if (!operand) continue;\n            const extraction = extractJsonbPath(operand);\n            if (extraction) {\n              this.add(extraction.columnRef, {\n                jsonbExtraction: extraction.expression,\n              });\n            }\n          }\n        }\n      }\n      // any column reference anywhere\n      if (is(node, \"ColumnRef\")) {\n        // TODO: this approach needs refinement\n        for (let i = 0; i < stack.length; i++) {\n          const inReturningList =\n            stack[i] === \"returningList\" &&\n            stack[i + 1] === \"ResTarget\" &&\n            stack[i + 2] === \"val\" &&\n            stack[i + 3] === \"ColumnRef\";\n          if (inReturningList) {\n            this.add(node, { ignored: true });\n            return;\n          }\n          if (\n            // stack[i] === \"SelectStmt\" &&\n            stack[i + 1] === \"targetList\" &&\n            stack[i + 2] === \"ResTarget\" &&\n            stack[i + 3] === \"val\" &&\n            stack[i + 4] === \"ColumnRef\"\n          ) {\n            // we don't want to index the columns that are being selected\n            this.add(node, { ignored: true });\n            return;\n          }\n\n          // TODO: add functional index support here\n          if (stack[i] === \"FuncCall\" && stack[i + 1] === \"args\") {\n            // args of a function call can't be indexed (without functional indexes)\n            this.add(node, { ignored: true });\n            return;\n          }\n          // CAST(\"columnName\" as \"test\")\n          if (stack[i] === \"TypeCast\" && stack[i + 1] === \"arg\") {\n            this.add(node, { ignored: true });\n            return;\n          }\n        }\n        this.add(node);\n      }\n    });\n\n    return {\n      highlights: this.highlights,\n      indexRepresentations: this.indexRepresentations,\n      indexesToCheck: this.indexesToCheck,\n      shadowedAliases: this.shadowedAliases,\n      tempTables: this.tempTables,\n      tableMappings: this.tableMappings,\n      nudges: this.nudges,\n    };\n  }\n\n  private add(\n    node: Extract<Node, { ColumnRef: unknown }>,\n    options?: {\n      ignored?: boolean;\n      sort?: SortContext;\n      where?: { nulltest?: NullTestType };\n      jsonbOperator?: JsonbOperator;\n      jsonbExtraction?: string;\n    },\n  ) {\n    if (!node.ColumnRef.location) {\n      console.error(`Node did not have a location. Skipping`, node);\n      return;\n    }\n    if (!node.ColumnRef.fields) {\n      console.error(node);\n      throw new Error(\"Column reference must have fields\");\n    }\n    let ignored = options?.ignored ?? false;\n    let runningLength: number = node.ColumnRef.location;\n    const parts: ColumnReferencePart[] = node.ColumnRef.fields.map(\n      (field, i, length) => {\n        if (!is(field, \"String\") || !field.String.sval) {\n          const out = deparseSync(field);\n          ignored = true;\n          return {\n            quoted: out.startsWith('\"'),\n            text: out,\n            start: runningLength,\n          };\n        }\n        const start = runningLength;\n        const size = field.String.sval?.length ?? 0;\n        let quoted = false;\n        if (node.ColumnRef.location !== undefined) {\n          const boundary = this.query[runningLength];\n          if (boundary === '\"') {\n            quoted = true;\n          }\n        }\n        // +1 for the dot that comes after\n        const isLastIteration = i === length.length - 1;\n        runningLength += size + (isLastIteration ? 0 : 1) + (quoted ? 2 : 0);\n        return {\n          text: field.String.sval,\n          start,\n          quoted,\n        };\n      },\n    );\n    const end = runningLength;\n    if (this.highlightPositions.has(node.ColumnRef.location)) {\n      return;\n    }\n    this.highlightPositions.add(node.ColumnRef.location);\n    const highlighted = `${this.query.slice(node.ColumnRef.location, end)}`;\n    const seen = this.seenReferences.get(highlighted);\n    if (!ignored) {\n      this.seenReferences.set(highlighted, (seen ?? 0) + 1);\n    }\n    const ref: DiscoveredColumnReference = {\n      frequency: seen ?? 1,\n      representation: highlighted,\n      parts,\n      ignored: ignored ?? false,\n      position: {\n        start: node.ColumnRef.location,\n        end,\n      },\n    };\n    if (options?.sort) {\n      ref.sort = options.sort;\n    }\n    if (options?.where) {\n      ref.where = options.where;\n    }\n    if (options?.jsonbOperator) {\n      ref.jsonbOperator = options.jsonbOperator;\n    }\n    if (options?.jsonbExtraction) {\n      ref.jsonbExtraction = options.jsonbExtraction;\n    }\n    this.highlights.push(ref);\n  }\n\n  private registerTable(rangeVar: RangeVar) {\n    if (!rangeVar.relname) return;\n\n    const columnReference: ColumnReferencePart = {\n      text: rangeVar.relname,\n      start: rangeVar.location,\n      quoted: false,\n    };\n    if (rangeVar.schemaname) {\n      columnReference.schema = rangeVar.schemaname;\n    }\n    this.tableMappings.set(rangeVar.relname, columnReference);\n\n    if (rangeVar.alias?.aliasname) {\n      const aliasName = rangeVar.alias.aliasname;\n      const existingMapping = this.tableMappings.get(aliasName);\n      const part: ColumnReferencePart = {\n        text: rangeVar.relname,\n        start: rangeVar.location,\n        quoted: true,\n        alias: aliasName,\n      };\n      if (rangeVar.schemaname) {\n        part.schema = rangeVar.schemaname;\n      }\n      if (existingMapping) {\n        const isSystemCatalog = rangeVar.relname?.startsWith(\"pg_\") ?? false;\n        if (!isSystemCatalog) {\n          console.warn(\n            `Ignoring alias ${aliasName} as it shadows an existing mapping for ${existingMapping.text}. We currently do not support alias shadowing.`,\n          );\n        }\n        this.shadowedAliases.push(part);\n        return;\n      }\n      this.tableMappings.set(aliasName, part);\n    }\n  }\n\n  /**\n   * Descend only into shallow combinators of a node such as\n   * - And\n   * - Or\n   * - Not\n   * - ::typecast\n   * without deep traversing into subqueries. Useful for checking members\n   * of a `WHERE` clause\n   */\n  static shallowMatch<K extends KeysOfUnion<Node>>(\n    expr: Node,\n    kind: K,\n    callback: (node: Extract<Node, Record<K, unknown>>) => void,\n  ) {\n    if (is(expr, kind)) {\n      callback(expr as Extract<Node, Record<K, unknown>>);\n      return;\n    }\n    if (is(expr, \"BoolExpr\") && expr.BoolExpr.args) {\n      for (const arg of expr.BoolExpr.args) {\n        Walker.shallowMatch(arg, kind, callback);\n      }\n      return;\n    }\n    if (is(expr, \"A_Expr\")) {\n      if (expr.A_Expr.lexpr)\n        Walker.shallowMatch(expr.A_Expr.lexpr, kind, callback);\n      if (expr.A_Expr.rexpr)\n        Walker.shallowMatch(expr.A_Expr.rexpr, kind, callback);\n      return;\n    }\n    if (is(expr, \"NullTest\") && expr.NullTest.arg) {\n      Walker.shallowMatch(expr.NullTest.arg, kind, callback);\n      return;\n    }\n    if (is(expr, \"BooleanTest\") && expr.BooleanTest.arg) {\n      Walker.shallowMatch(expr.BooleanTest.arg, kind, callback);\n      return;\n    }\n    if (is(expr, \"SubLink\") && expr.SubLink.testexpr) {\n      Walker.shallowMatch(expr.SubLink.testexpr, kind, callback);\n      return;\n    }\n    if (is(expr, \"TypeCast\") && expr.TypeCast.arg) {\n      Walker.shallowMatch(expr.TypeCast.arg, kind, callback);\n      return;\n    }\n  }\n\n  static traverse(\n    node: Node,\n    callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void,\n  ) {\n    Walker.doTraverse(node, [], callback);\n  }\n\n  private static doTraverse(\n    node: unknown,\n    stack: (KeysOfUnion<Node> | string)[],\n    callback: (node: Node, stack: (KeysOfUnion<Node> | string)[]) => void,\n  ) {\n    if (isANode(node)) {\n      callback(node, [...stack, getNodeKind(node)]);\n    }\n    if (typeof node !== \"object\" || node === null) {\n      return;\n    }\n    if (Array.isArray(node)) {\n      for (const item of node) {\n        if (isANode(item)) {\n          Walker.doTraverse(item, stack, callback);\n        }\n      }\n    } else if (isANode(node)) {\n      const keys = Object.keys(node);\n      // @ts-expect-error | nodes don't allow dynamic access but it's the only way to do it\n      Walker.doTraverse(node[keys[0]], [...stack, getNodeKind(node)], callback);\n    } else {\n      for (const [key, child] of Object.entries(node)) {\n        Walker.doTraverse(\n          child,\n          [...stack, key as KeysOfUnion<Node>],\n          callback,\n        );\n      }\n    }\n  }\n}\n\nexport type ColumnReferencePart = {\n  schema?: string;\n  /** the text of the column reference (excluding any potential quotes) */\n  text: string;\n  start?: number;\n  quoted: boolean;\n  alias?: string;\n};\n\ntype JsonbExtraction = {\n  columnRef: Extract<Node, { ColumnRef: unknown }>;\n  expression: string;\n};\n\n/**\n * Given an operand of a comparison (e.g. the left side of `=`), check whether\n * it is a JSONB path extraction expression such as `data->>'email'` or\n * `(data->>'age')::int`.  If so, return the root ColumnRef (for the walker to\n * register) and the full expression string (for use in the expression index).\n *\n * Handles:\n * - `data->>'email'`            — simple extraction\n * - `(data->>'age')::int`       — extraction with cast\n * - `data->'addr'->>'city'`     — chained extraction\n * - `t.data->>'email'`          — table-qualified (strips qualifier from expression)\n */\nfunction extractJsonbPath(node: Node): JsonbExtraction | undefined {\n  let exprNode = node;\n  // Unwrap TypeCast (e.g. `(data->>'age')::int`)\n  if (is(exprNode, \"TypeCast\") && exprNode.TypeCast.arg) {\n    exprNode = exprNode.TypeCast.arg;\n  }\n  if (!is(exprNode, \"A_Expr\") || exprNode.A_Expr.kind !== \"AEXPR_OP\") {\n    return undefined;\n  }\n  const innerOp =\n    exprNode.A_Expr.name?.[0] &&\n    is(exprNode.A_Expr.name[0], \"String\") &&\n    exprNode.A_Expr.name[0].String.sval;\n  if (!innerOp || !JSONB_EXTRACTION_OPS.has(innerOp)) {\n    return undefined;\n  }\n  const rootCol = findRootColumnRef(exprNode);\n  if (!rootCol) {\n    return undefined;\n  }\n  // Deep-clone the whole outer node so we can strip qualifiers without\n  // mutating the original AST.\n  const cloned: Node = JSON.parse(JSON.stringify(node));\n  stripTableQualifiers(cloned);\n  const expression = deparseSync(cloned);\n  return { columnRef: rootCol, expression };\n}\n\n/**\n * Walk the left side of a chain of `->` / `->>` operators to find the\n * root ColumnRef (the JSONB column itself).\n */\nfunction findRootColumnRef(\n  node: Node,\n): Extract<Node, { ColumnRef: unknown }> | undefined {\n  if (is(node, \"ColumnRef\")) {\n    return node;\n  }\n  if (is(node, \"A_Expr\") && node.A_Expr.lexpr) {\n    return findRootColumnRef(node.A_Expr.lexpr);\n  }\n  if (is(node, \"TypeCast\") && node.TypeCast.arg) {\n    return findRootColumnRef(node.TypeCast.arg);\n  }\n  return undefined;\n}\n\n/**\n * Remove table/schema qualifiers from ColumnRef nodes inside the\n * expression so the deparsed expression contains only the column name.\n * e.g.  `t.data->>'email'`  →  `data->>'email'`\n */\nfunction stripTableQualifiers(node: unknown): void {\n  if (typeof node !== \"object\" || node === null) return;\n  if (\n    \"ColumnRef\" in (node as Record<string, unknown>) &&\n    (node as any).ColumnRef?.fields\n  ) {\n    const fields: unknown[] = (node as any).ColumnRef.fields;\n    if (fields.length > 1) {\n      (node as any).ColumnRef.fields = [fields[fields.length - 1]];\n    }\n    return;\n  }\n  for (const value of Object.values(node as Record<string, unknown>)) {\n    if (Array.isArray(value)) {\n      for (const item of value) {\n        stripTableQualifiers(item);\n      }\n    } else if (typeof value === \"object\" && value !== null) {\n      stripTableQualifiers(value);\n    }\n  }\n}\n"],"mappings":"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"}