{"version":3,"file":"nudges.mjs","names":[],"sources":["../../src/sql/nudges.ts"],"sourcesContent":["import type { A_Const, List, Node, ResTarget } from \"@pgsql/types\";\nimport { is, isANode, type KeysOfUnion } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\ntype NudgeKind =\n  | \"LARGE_IMPROVEMENT_FOUND\"\n  | \"SMALL_IMPROVEMENT_FOUND\"\n  | \"AVOID_SELECT_STAR\"\n  | \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\"\n  | \"MISSING_WHERE_CLAUSE\"\n  | \"MISSING_LIMIT_CLAUSE\"\n  | \"USE_IS_NULL_NOT_EQUALS\"\n  | \"AVOID_DISTINCT_WITHOUT_REASON\"\n  | \"MISSING_JOIN_CONDITION\"\n  | \"AVOID_LEADING_WILDCARD_LIKE\"\n  | \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\"\n  | \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\"\n  | \"PREFER_NOT_EXISTS_OVER_NOT_IN\"\n  | \"AVOID_ORDER_BY_RANDOM\"\n  | \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\"\n  | \"PREFER_UNION_ALL_OVER_UNION\"\n  | \"NULLS_FIRST_IN_DESC_ORDER\"\n  | \"AVOID_SCALAR_SUBQUERY_IN_SELECT\"\n  | \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\"\n  | \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\"\n  | \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\"\n  | \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\"\n  | \"LIMIT_WITHOUT_ORDER_BY\";\n\nexport type Nudge = {\n  kind: NudgeKind;\n  severity: \"CRITICAL\" | \"WARNING\" | \"INFO\";\n  message: string;\n  location?: number;\n};\n\ntype NudgeCreator = (node: Node) => Nudge[];\n\nconst findFuncCallsOnColumns: NudgeCreator = (whereClause) => {\n  const nudges: Nudge[] = [];\n  Walker.shallowMatch(whereClause, \"FuncCall\", (node) => {\n    if (node.FuncCall.args && containsColumnRef(node.FuncCall.args)) {\n      nudges.push({\n        kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n        severity: \"WARNING\",\n        message:\n          \"Avoid using functions on columns in conditions — prevents index usage\",\n        location: node.FuncCall.location,\n      });\n    }\n  });\n  Walker.shallowMatch(whereClause, \"CoalesceExpr\", (node) => {\n    if (node.CoalesceExpr.args && containsColumnRef(node.CoalesceExpr.args)) {\n      nudges.push({\n        kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n        severity: \"WARNING\",\n        message:\n          \"Avoid using functions on columns in conditions — prevents index usage\",\n        location: node.CoalesceExpr.location,\n      });\n    }\n  });\n  Walker.shallowMatch(whereClause, \"TypeCast\", (node) => {\n    if (node.TypeCast.arg && hasColumnRefInNode(node.TypeCast.arg)) {\n      nudges.push({\n        kind: \"AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE\",\n        severity: \"WARNING\",\n        message: \"Avoid using functions on columns in WHERE clause\",\n        location: node.TypeCast.location,\n      });\n    }\n  });\n  return nudges;\n};\n\n/**\n * Detect nudges for a single node during AST traversal.\n * Returns an array of nudges found for this node.\n */\nexport function parseNudges(\n  node: Node,\n  stack: (KeysOfUnion<Node> | string)[],\n): Nudge[] {\n  const nudges: Nudge[] = [];\n\n  if (is(node, \"SelectStmt\")) {\n    if (node.SelectStmt.whereClause) {\n      nudges.push(...findFuncCallsOnColumns(node.SelectStmt.whereClause));\n    }\n    const star = node.SelectStmt.targetList?.find(\n      (target): target is { ResTarget: ResTarget } => {\n        if (\n          !(\n            is(target, \"ResTarget\") &&\n            target.ResTarget.val &&\n            is(target.ResTarget.val, \"ColumnRef\")\n          )\n        ) {\n          return false;\n        }\n        const fields = target.ResTarget.val.ColumnRef.fields;\n        if (!fields?.some((field) => is(field, \"A_Star\"))) {\n          return false;\n        }\n        // Qualified star (e.g. users.*) is deliberate — skip\n        if (fields.length > 1) {\n          return false;\n        }\n        return true;\n      },\n    );\n    if (star) {\n      // Skip when all FROM items are subqueries — the outer * just\n      // passes through whatever the subquery already selected.\n      const fromClause = node.SelectStmt.fromClause;\n      const allSubselects =\n        fromClause &&\n        fromClause.length > 0 &&\n        fromClause.every((item) => is(item, \"RangeSubselect\"));\n\n      if (!allSubselects) {\n        nudges.push({\n          kind: \"AVOID_SELECT_STAR\",\n          severity: \"INFO\",\n          message: \"Avoid using SELECT *\",\n          location: star.ResTarget.location,\n        });\n      }\n    }\n\n    // Detect correlated scalar subqueries in SELECT (N+1 problem)\n    for (const target of node.SelectStmt.targetList ?? []) {\n      if (\n        is(target, \"ResTarget\") &&\n        target.ResTarget.val &&\n        is(target.ResTarget.val, \"SubLink\") &&\n        target.ResTarget.val.SubLink.subLinkType === \"EXPR_SUBLINK\"\n      ) {\n        nudges.push({\n          kind: \"AVOID_SCALAR_SUBQUERY_IN_SELECT\",\n          severity: \"WARNING\",\n          message:\n            \"Avoid correlated scalar subqueries in SELECT; consider rewriting as a JOIN\",\n          location: target.ResTarget.val.SubLink.location,\n        });\n      }\n    }\n  }\n\n  // Detect unbounded queries (missing WHERE or LIMIT on table queries)\n  if (is(node, \"SelectStmt\")) {\n    // Only check top-level SELECT statements (not subqueries)\n    const isSubquery = stack.some(\n      (item) =>\n        item === \"RangeSubselect\" ||\n        item === \"SubLink\" ||\n        item === \"CommonTableExpr\",\n    );\n\n    if (!isSubquery) {\n      const hasFromClause =\n        node.SelectStmt.fromClause && node.SelectStmt.fromClause.length > 0;\n      if (hasFromClause) {\n        // Check if this SELECT queries actual tables (not just subqueries or CTEs)\n        const hasActualTables = node.SelectStmt.fromClause!.some((fromItem) => {\n          return (\n            is(fromItem, \"RangeVar\") ||\n            (is(fromItem, \"JoinExpr\") && hasActualTablesInJoin(fromItem))\n          );\n        });\n\n        if (hasActualTables) {\n          const firstTable = node.SelectStmt.fromClause!.find((item) =>\n            is(item, \"RangeVar\"),\n          );\n          const fromLocation = firstTable?.RangeVar.location;\n\n          if (!node.SelectStmt.whereClause) {\n            nudges.push({\n              kind: \"MISSING_WHERE_CLAUSE\",\n              severity: \"INFO\",\n              message: \"Missing WHERE clause\",\n              location: fromLocation,\n            });\n          }\n\n          // A bare aggregate select (aggregates in the target list, no GROUP BY)\n          // returns exactly one row, so a LIMIT adds nothing. Aggregates with\n          // GROUP BY can still return many rows and stay eligible.\n          const isBareAggregate =\n            !node.SelectStmt.groupClause?.length &&\n            containsAggregate(node.SelectStmt.targetList, true);\n\n          if (!node.SelectStmt.limitCount && !isBareAggregate) {\n            nudges.push({\n              kind: \"MISSING_LIMIT_CLAUSE\",\n              severity: \"INFO\",\n              message: \"Missing LIMIT clause\",\n              location: fromLocation,\n            });\n          }\n        }\n      }\n    }\n  }\n\n  // Detect LIMIT without ORDER BY (non-deterministic results)\n  // LIMIT ALL parses as limitCount with A_Const.isnull — exclude it\n  if (\n    is(node, \"SelectStmt\") &&\n    node.SelectStmt.limitCount &&\n    !isNullConstant(node.SelectStmt.limitCount) &&\n    !node.SelectStmt.sortClause\n  ) {\n    nudges.push({\n      kind: \"LIMIT_WITHOUT_ORDER_BY\",\n      severity: \"WARNING\",\n      message:\n        \"LIMIT without ORDER BY returns non-deterministic results — add ORDER BY for predictable output\",\n    });\n  }\n\n  // Detect ORDER BY DESC without explicit NULLS LAST (NULLs sort first by default)\n  if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n    for (const sortItem of node.SelectStmt.sortClause) {\n      if (!is(sortItem, \"SortBy\")) continue;\n\n      const sortDir = sortItem.SortBy.sortby_dir ?? \"SORTBY_DEFAULT\";\n      const sortNulls = sortItem.SortBy.sortby_nulls ?? \"SORTBY_NULLS_DEFAULT\";\n\n      if (sortDir === \"SORTBY_DESC\" && sortNulls === \"SORTBY_NULLS_DEFAULT\") {\n        if (sortItem.SortBy.node && is(sortItem.SortBy.node, \"ColumnRef\")) {\n          const sortColumnName = getLastColumnRefField(sortItem.SortBy.node);\n\n          const hasIsNotNull =\n            sortColumnName !== null &&\n            whereHasIsNotNull(node.SelectStmt.whereClause, sortColumnName);\n\n          if (!hasIsNotNull) {\n            nudges.push({\n              kind: \"NULLS_FIRST_IN_DESC_ORDER\",\n              severity: \"INFO\",\n              message:\n                \"ORDER BY … DESC sorts NULLs first — add NULLS LAST to push them to the end\",\n              location: sortItem.SortBy.node.ColumnRef.location,\n            });\n          }\n        }\n      }\n    }\n  }\n\n  // Detect NULL comparison issues (= NULL instead of IS NULL)\n  if (is(node, \"A_Expr\")) {\n    const isEqualityOp =\n      node.A_Expr.kind === \"AEXPR_OP\" &&\n      node.A_Expr.name &&\n      node.A_Expr.name.length > 0 &&\n      is(node.A_Expr.name[0], \"String\") &&\n      (node.A_Expr.name[0].String.sval === \"=\" ||\n        node.A_Expr.name[0].String.sval === \"!=\" ||\n        node.A_Expr.name[0].String.sval === \"<>\");\n\n    if (isEqualityOp) {\n      const leftIsNull = isNullConstant(node.A_Expr.lexpr);\n      const rightIsNull = isNullConstant(node.A_Expr.rexpr);\n\n      if (leftIsNull || rightIsNull) {\n        nudges.push({\n          kind: \"USE_IS_NULL_NOT_EQUALS\",\n          severity: \"WARNING\",\n          message: \"Use IS NULL instead of = or != or <> for NULL comparisons\",\n          location: node.A_Expr.location,\n        });\n      }\n    }\n\n    // Detect LIKE with leading wildcards\n    const isLikeOp =\n      node.A_Expr.kind === \"AEXPR_LIKE\" || node.A_Expr.kind === \"AEXPR_ILIKE\";\n\n    if (isLikeOp && node.A_Expr.rexpr) {\n      const patternString = getStringConstantValue(node.A_Expr.rexpr);\n      if (patternString && patternString.startsWith(\"%\")) {\n        let stringNode: A_Const | undefined;\n        if (is(node.A_Expr.rexpr, \"A_Const\")) {\n          stringNode = node.A_Expr.rexpr.A_Const;\n        }\n        nudges.push({\n          kind: \"AVOID_LEADING_WILDCARD_LIKE\",\n          severity: \"WARNING\",\n          message:\n            \"Leading wildcard in LIKE/ILIKE prevents index usage — consider a GIN trigram index (pg_trgm) or full-text search\",\n          location: stringNode?.location,\n        });\n      }\n    }\n  }\n\n  // Detect ORDER BY random()\n  if (is(node, \"SelectStmt\") && node.SelectStmt.sortClause) {\n    for (const sortItem of node.SelectStmt.sortClause) {\n      if (\n        is(sortItem, \"SortBy\") &&\n        sortItem.SortBy.node &&\n        is(sortItem.SortBy.node, \"FuncCall\") &&\n        sortItem.SortBy.node.FuncCall.funcname?.some(\n          (name) => is(name, \"String\") && name.String.sval === \"random\",\n        )\n      ) {\n        nudges.push({\n          kind: \"AVOID_ORDER_BY_RANDOM\",\n          severity: \"WARNING\",\n          message: \"Avoid using ORDER BY random()\",\n          location: sortItem.SortBy.node.FuncCall.location,\n        });\n      }\n    }\n  }\n\n  // Detect DISTINCT usage\n  if (is(node, \"SelectStmt\") && node.SelectStmt.distinctClause) {\n    nudges.push({\n      kind: \"AVOID_DISTINCT_WITHOUT_REASON\",\n      severity: \"WARNING\",\n      message: \"Avoid using DISTINCT without a reason\",\n    });\n  }\n\n  // Detect cartesian joins (missing JOIN conditions) and functions on columns in ON\n  if (is(node, \"JoinExpr\")) {\n    if (node.JoinExpr.quals) {\n      nudges.push(...findFuncCallsOnColumns(node.JoinExpr.quals));\n    } else if (!node.JoinExpr.usingClause) {\n      nudges.push({\n        kind: \"MISSING_JOIN_CONDITION\",\n        severity: \"WARNING\",\n        message: \"Missing JOIN condition\",\n      });\n    }\n  }\n\n  // Detect multiple tables in FROM without explicit JOINs (old-style cartesian joins)\n  if (\n    is(node, \"SelectStmt\") &&\n    node.SelectStmt.fromClause &&\n    node.SelectStmt.fromClause.length > 1\n  ) {\n    // Check if there are multiple RangeVar (tables) directly in FROM clause\n    const tables = node.SelectStmt.fromClause.filter((item) =>\n      is(item, \"RangeVar\"),\n    );\n    if (tables.length > 1) {\n      // Collect table aliases/names for cross-table equality check\n      const tableNames = new Set(\n        tables.map(\n          (t) => t.RangeVar.alias?.aliasname || t.RangeVar.relname || \"\",\n        ),\n      );\n\n      // Don't fire if WHERE already has a cross-table equality (old-style implicit join)\n      if (\n        !whereHasCrossTableEquality(node.SelectStmt.whereClause, tableNames)\n      ) {\n        nudges.push({\n          kind: \"MISSING_JOIN_CONDITION\",\n          severity: \"WARNING\",\n          message: \"Missing JOIN condition\",\n          location: tables[1].RangeVar.location,\n        });\n      }\n    }\n  }\n\n  // Detect too many OR conditions on the same column\n  if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"OR_EXPR\") {\n    const orCount = countBoolOrConditions(node);\n    if (orCount >= 3 && allOrBranchesReferenceSameColumn(node)) {\n      nudges.push({\n        kind: \"CONSIDER_IN_INSTEAD_OF_MANY_ORS\",\n        severity: \"WARNING\",\n        message: \"Consider using IN instead of many ORs\",\n        location: node.BoolExpr.location,\n      });\n    }\n  }\n\n  // Detect NOT IN (SELECT ...) — prefer NOT EXISTS\n  if (is(node, \"BoolExpr\") && node.BoolExpr.boolop === \"NOT_EXPR\") {\n    const args = node.BoolExpr.args;\n    if (args && args.length === 1) {\n      const inner = args[0];\n      if (\n        isANode(inner) &&\n        is(inner, \"SubLink\") &&\n        inner.SubLink.subLinkType === \"ANY_SUBLINK\"\n      ) {\n        nudges.push({\n          kind: \"PREFER_NOT_EXISTS_OVER_NOT_IN\",\n          severity: \"WARNING\",\n          message: \"Prefer NOT EXISTS over NOT IN (SELECT ...)\",\n          location: inner.SubLink.location,\n        });\n      }\n    }\n  }\n\n  // Detect COUNT(column) or COUNT(1) — suggest COUNT(*)\n  if (is(node, \"FuncCall\")) {\n    const funcName = node.FuncCall.funcname;\n    const isCount =\n      funcName &&\n      funcName.length === 1 &&\n      is(funcName[0], \"String\") &&\n      funcName[0].String.sval === \"count\";\n\n    if (\n      isCount &&\n      node.FuncCall.args &&\n      !node.FuncCall.agg_star &&\n      !node.FuncCall.agg_distinct\n    ) {\n      nudges.push({\n        kind: \"PREFER_COUNT_STAR_OVER_COUNT_COLUMN\",\n        severity: \"INFO\",\n        message:\n          \"Prefer COUNT(*) over COUNT(column) or COUNT(1) — COUNT(*) counts rows without checking for NULLs. If you need to count non-NULL values, COUNT(column) is correct.\",\n        location: node.FuncCall.location,\n      });\n    }\n  }\n\n  // Detect non-aggregate conditions in HAVING clause\n  if (is(node, \"SelectStmt\") && node.SelectStmt.havingClause) {\n    if (!containsAggregate(node.SelectStmt.havingClause)) {\n      const having = node.SelectStmt.havingClause;\n      let location: number | undefined;\n      if (is(having, \"A_Expr\")) location = having.A_Expr.location;\n      else if (is(having, \"BoolExpr\")) location = having.BoolExpr.location;\n\n      nudges.push({\n        kind: \"PREFER_WHERE_OVER_HAVING_FOR_NON_AGGREGATES\",\n        severity: \"INFO\",\n        message: \"Non-aggregate condition in HAVING should be in WHERE\",\n        location,\n      });\n    }\n  }\n\n  // Detect JSONB set-returning functions in WHERE context\n  if (is(node, \"FuncCall\")) {\n    const inWhereClause = stack.some((item) => item === \"whereClause\");\n    if (inWhereClause && node.FuncCall.args) {\n      const name = getFuncName(node);\n      if (\n        name &&\n        JSONB_SET_RETURNING_FUNCTIONS.has(name) &&\n        containsColumnRef(node.FuncCall.args)\n      ) {\n        nudges.push({\n          kind: \"CONSIDER_JSONB_CONTAINMENT_OPERATOR\",\n          severity: \"INFO\",\n          message:\n            \"JSONB set-returning functions (e.g. jsonb_array_elements) cannot be used as an index access path. If the query checks for containment or key existence, GIN-compatible operators (@>, ?, ?|, ?&, @?, @@) may allow index usage.\",\n          location: node.FuncCall.location,\n        });\n      }\n    }\n  }\n\n  // Too many parameters in a tuple\n  if (is(node, \"A_Expr\")) {\n    if (node.A_Expr.kind === \"AEXPR_IN\") {\n      let list: List | undefined;\n      if (node.A_Expr.lexpr && is(node.A_Expr.lexpr, \"List\")) {\n        list = node.A_Expr.lexpr.List;\n      } else if (node.A_Expr.rexpr && is(node.A_Expr.rexpr, \"List\")) {\n        list = node.A_Expr.rexpr.List;\n      }\n\n      if (list?.items && list.items.length >= 10) {\n        nudges.push({\n          kind: \"REPLACE_LARGE_IN_TUPLE_WITH_ANY_ARRAY\",\n          message:\n            \"`in (...)` queries with large tuples can often be replaced with `= ANY($1)` using a single parameter\",\n          severity: \"INFO\",\n          location: node.A_Expr.location,\n        });\n      }\n    }\n  }\n\n  // Detect CASE inside aggregate functions (prefer FILTER)\n  if (is(node, \"FuncCall\")) {\n    const funcname =\n      node.FuncCall.funcname?.[0] &&\n      is(node.FuncCall.funcname[0], \"String\") &&\n      node.FuncCall.funcname[0].String.sval;\n\n    if (\n      funcname &&\n      [\"sum\", \"count\", \"avg\", \"min\", \"max\"].includes(funcname.toLowerCase())\n    ) {\n      const firstArg = node.FuncCall.args?.[0];\n      if (firstArg && isANode(firstArg) && is(firstArg, \"CaseExpr\")) {\n        const caseExpr = firstArg.CaseExpr;\n        // Only flag simple CASE expressions (single WHEN clause)\n        if (caseExpr.args && caseExpr.args.length === 1) {\n          // Check ELSE clause: must be absent, ELSE 0, or ELSE NULL\n          const defresult = caseExpr.defresult;\n          const isSimpleElse =\n            !defresult ||\n            (isANode(defresult) &&\n              is(defresult, \"A_Const\") &&\n              (defresult.A_Const.isnull !== undefined ||\n                (defresult.A_Const.ival !== undefined &&\n                  (defresult.A_Const.ival.ival === 0 ||\n                    defresult.A_Const.ival.ival === undefined))));\n\n          if (isSimpleElse) {\n            nudges.push({\n              kind: \"PREFER_FILTER_OVER_CASE_IN_AGGREGATE\",\n              severity: \"INFO\",\n              message:\n                \"Use FILTER (WHERE ...) instead of CASE inside aggregate functions\",\n              location: node.FuncCall.location,\n            });\n          }\n        }\n      }\n    }\n  }\n\n  // Detect UNION without ALL (implicit deduplication)\n  if (\n    is(node, \"SelectStmt\") &&\n    node.SelectStmt.op === \"SETOP_UNION\" &&\n    !node.SelectStmt.all\n  ) {\n    nudges.push({\n      kind: \"PREFER_UNION_ALL_OVER_UNION\",\n      severity: \"INFO\",\n      message:\n        \"UNION removes duplicates with an implicit sort — use UNION ALL if deduplication is not needed\",\n    });\n  }\n\n  // Detect COUNT(*)/COUNT(1) compared to 0 or 1 (existence check)\n  if (\n    is(node, \"A_Expr\") &&\n    node.A_Expr.kind === \"AEXPR_OP\" &&\n    node.A_Expr.name &&\n    node.A_Expr.name.length > 0\n  ) {\n    const opNode = node.A_Expr.name[0];\n    const op = is(opNode, \"String\") ? opNode.String.sval : null;\n\n    if (\n      op &&\n      isExistenceCheckPattern(node.A_Expr.lexpr, node.A_Expr.rexpr, op)\n    ) {\n      nudges.push({\n        kind: \"USE_EXISTS_NOT_COUNT_FOR_EXISTENCE_CHECK\",\n        severity: \"INFO\",\n        message: \"Use EXISTS instead of COUNT for existence checks\",\n        location: node.A_Expr.location,\n      });\n    }\n  }\n\n  return nudges;\n}\n\nfunction containsColumnRef(args: unknown[]): boolean {\n  // Recursively check if any argument contains a ColumnRef\n  for (const arg of args) {\n    if (hasColumnRefInNode(arg)) {\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction hasColumnRefInNode(node: unknown): boolean {\n  if (isANode(node) && is(node, \"ColumnRef\")) {\n    return true;\n  }\n\n  if (typeof node !== \"object\" || node === null) {\n    return false;\n  }\n\n  if (Array.isArray(node)) {\n    return node.some((item) => hasColumnRefInNode(item));\n  }\n\n  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    return hasColumnRefInNode(node[keys[0]]);\n  }\n\n  for (const child of Object.values(node)) {\n    if (hasColumnRefInNode(child)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction hasActualTablesInJoin(\n  joinExpr: Extract<Node, { JoinExpr: unknown }>,\n): boolean {\n  // Check left side of join\n  if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"RangeVar\")) {\n    return true;\n  }\n  if (joinExpr.JoinExpr.larg && is(joinExpr.JoinExpr.larg, \"JoinExpr\")) {\n    if (hasActualTablesInJoin(joinExpr.JoinExpr.larg)) {\n      return true;\n    }\n  }\n\n  // Check right side of join\n  if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"RangeVar\")) {\n    return true;\n  }\n  if (joinExpr.JoinExpr.rarg && is(joinExpr.JoinExpr.rarg, \"JoinExpr\")) {\n    if (hasActualTablesInJoin(joinExpr.JoinExpr.rarg)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction isNullConstant(node: unknown): boolean {\n  if (!node || typeof node !== \"object\") {\n    return false;\n  }\n\n  if (isANode(node) && is(node, \"A_Const\")) {\n    // Check if it's a NULL constant\n    return node.A_Const.isnull !== undefined;\n  }\n\n  return false;\n}\n\nfunction getStringConstantValue(node: unknown): string | null {\n  if (!node || typeof node !== \"object\") {\n    return null;\n  }\n\n  if (isANode(node) && is(node, \"A_Const\") && node.A_Const.sval) {\n    return node.A_Const.sval.sval || null;\n  }\n\n  return null;\n}\n\nconst JSONB_SET_RETURNING_FUNCTIONS = new Set([\n  \"jsonb_array_elements\",\n  \"json_array_elements\",\n  \"jsonb_array_elements_text\",\n  \"json_array_elements_text\",\n  \"jsonb_each\",\n  \"json_each\",\n  \"jsonb_each_text\",\n  \"json_each_text\",\n]);\n\nfunction getFuncName(\n  node: Extract<Node, { FuncCall: unknown }>,\n): string | null {\n  const names = node.FuncCall.funcname;\n  if (!names || names.length === 0) return null;\n  const last = names[names.length - 1];\n  if (isANode(last) && is(last, \"String\") && last.String.sval) {\n    return last.String.sval;\n  }\n  return null;\n}\n\nfunction getLastColumnRefField(\n  columnRef: Extract<Node, { ColumnRef: unknown }>,\n): string | null {\n  const fields = columnRef.ColumnRef.fields;\n  if (!fields || fields.length === 0) return null;\n\n  const lastField = fields[fields.length - 1];\n  if (isANode(lastField) && is(lastField, \"String\")) {\n    return lastField.String.sval || null;\n  }\n  return null;\n}\n\nfunction whereHasIsNotNull(\n  whereClause: Node | undefined,\n  columnName: string,\n): boolean {\n  if (!whereClause) return false;\n\n  let found = false;\n  Walker.shallowMatch(whereClause, \"NullTest\", (node) => {\n    if (\n      node.NullTest.nulltesttype === \"IS_NOT_NULL\" &&\n      node.NullTest.arg &&\n      is(node.NullTest.arg, \"ColumnRef\")\n    ) {\n      const name = getLastColumnRefField(node.NullTest.arg);\n      if (name === columnName) {\n        found = true;\n      }\n    }\n  });\n\n  return found;\n}\n\nconst AGGREGATE_FUNCTIONS = new Set([\n  \"count\",\n  \"sum\",\n  \"avg\",\n  \"min\",\n  \"max\",\n  \"array_agg\",\n  \"string_agg\",\n  \"bool_and\",\n  \"bool_or\",\n  \"every\",\n]);\n\nfunction containsAggregate(node: unknown, skipSubqueries = false): boolean {\n  if (!node || typeof node !== \"object\") return false;\n\n  if (Array.isArray(node)) {\n    return node.some((child) => containsAggregate(child, skipSubqueries));\n  }\n\n  // A subquery's aggregate doesn't bound the enclosing query, so callers that\n  // reason about the enclosing statement (e.g. bare-aggregate detection) skip it.\n  if (skipSubqueries && isANode(node) && is(node, \"SubLink\")) {\n    return false;\n  }\n\n  if (isANode(node) && is(node, \"FuncCall\")) {\n    const funcname = node.FuncCall.funcname;\n    if (funcname) {\n      for (const f of funcname) {\n        if (\n          isANode(f) &&\n          is(f, \"String\") &&\n          AGGREGATE_FUNCTIONS.has(f.String.sval?.toLowerCase() ?? \"\")\n        ) {\n          return true;\n        }\n      }\n    }\n  }\n\n  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    return containsAggregate(node[keys[0]], skipSubqueries);\n  }\n\n  for (const child of Object.values(node)) {\n    if (containsAggregate(child, skipSubqueries)) return true;\n  }\n\n  return false;\n}\n\nfunction countBoolOrConditions(\n  node: Extract<Node, { BoolExpr: unknown }>,\n): number {\n  if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n    return 1;\n  }\n\n  let count = 0;\n  for (const arg of node.BoolExpr.args) {\n    if (\n      isANode(arg) &&\n      is(arg, \"BoolExpr\") &&\n      arg.BoolExpr.boolop === \"OR_EXPR\"\n    ) {\n      count += countBoolOrConditions(arg);\n    } else {\n      count += 1;\n    }\n  }\n\n  return count;\n}\n\n/**\n * Check whether every leaf of a top-level OR expression references the same\n * left-hand column (e.g. `status = 'a' OR status = 'b' OR status = 'c'`).\n * Returns false when ORs span different columns — IN rewrite doesn't apply.\n */\nfunction allOrBranchesReferenceSameColumn(\n  node: Extract<Node, { BoolExpr: unknown }>,\n): boolean {\n  const columns = collectOrLeafColumns(node);\n  if (columns.length === 0) return false;\n  return columns.every((col) => col === columns[0]);\n}\n\nfunction collectOrLeafColumns(\n  node: Extract<Node, { BoolExpr: unknown }>,\n): string[] {\n  if (node.BoolExpr.boolop !== \"OR_EXPR\" || !node.BoolExpr.args) {\n    return [];\n  }\n\n  const columns: string[] = [];\n  for (const arg of node.BoolExpr.args) {\n    if (!isANode(arg)) continue;\n    if (is(arg, \"BoolExpr\") && arg.BoolExpr.boolop === \"OR_EXPR\") {\n      columns.push(...collectOrLeafColumns(arg));\n    } else if (is(arg, \"A_Expr\")) {\n      const col = getLeftColumnKey(arg);\n      if (col) {\n        columns.push(col);\n      } else {\n        // Non-column comparison — can't be rewritten as IN\n        return [];\n      }\n    } else {\n      // Non-A_Expr leaf (e.g. SubLink, BoolExpr AND) — not a simple OR chain\n      return [];\n    }\n  }\n  return columns;\n}\n\n/**\n * Get a string key for the left-hand column of an A_Expr equality comparison.\n * For `t1.col = value` returns `\"t1.col\"`, for `col = value` returns `\"col\"`.\n */\nfunction getLeftColumnKey(\n  expr: Extract<Node, { A_Expr: unknown }>,\n): string | null {\n  if (!expr.A_Expr.lexpr || !isANode(expr.A_Expr.lexpr)) return null;\n  if (!is(expr.A_Expr.lexpr, \"ColumnRef\")) return null;\n  const fields = expr.A_Expr.lexpr.ColumnRef.fields;\n  if (!fields) return null;\n  return fields\n    .filter(\n      (f): f is Extract<Node, { String: unknown }> =>\n        isANode(f) && is(f, \"String\"),\n    )\n    .map((f) => f.String.sval)\n    .join(\".\");\n}\n\n/**\n * Check if a WHERE clause contains an equality between columns from different\n * tables (indicating an old-style implicit join condition).\n */\nfunction whereHasCrossTableEquality(\n  whereClause: Node | undefined,\n  tableNames: Set<string>,\n): boolean {\n  if (!whereClause) return false;\n\n  if (isANode(whereClause) && is(whereClause, \"A_Expr\")) {\n    if (\n      whereClause.A_Expr.kind === \"AEXPR_OP\" &&\n      whereClause.A_Expr.name?.some(\n        (n) => is(n, \"String\") && n.String.sval === \"=\",\n      )\n    ) {\n      const leftTable = getColumnRefTableQualifier(whereClause.A_Expr.lexpr);\n      const rightTable = getColumnRefTableQualifier(whereClause.A_Expr.rexpr);\n      if (\n        leftTable &&\n        rightTable &&\n        leftTable !== rightTable &&\n        tableNames.has(leftTable) &&\n        tableNames.has(rightTable)\n      ) {\n        return true;\n      }\n    }\n  }\n\n  // Recurse into AND/OR boolean expressions\n  if (isANode(whereClause) && is(whereClause, \"BoolExpr\")) {\n    for (const arg of whereClause.BoolExpr.args ?? []) {\n      if (isANode(arg) && whereHasCrossTableEquality(arg, tableNames)) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Extract the table qualifier (first field) from a ColumnRef node.\n * e.g. `t1.uid` → `\"t1\"`, `uid` → null\n */\nfunction getColumnRefTableQualifier(node: unknown): string | null {\n  if (!node || !isANode(node as Node) || !is(node as Node, \"ColumnRef\"))\n    return null;\n  const fields = (node as Extract<Node, { ColumnRef: unknown }>).ColumnRef\n    .fields;\n  if (!fields || fields.length < 2) return null;\n  const first = fields[0];\n  if (isANode(first) && is(first, \"String\")) {\n    return first.String.sval || null;\n  }\n  return null;\n}\n\nfunction isCountFuncCall(node: unknown): boolean {\n  if (!node || typeof node !== \"object\") return false;\n  if (!isANode(node) || !is(node, \"FuncCall\")) return false;\n  const fc = node.FuncCall;\n  const isCount =\n    fc.funcname?.some((n) => is(n, \"String\") && n.String.sval === \"count\") ??\n    false;\n  if (!isCount) return false;\n  // COUNT(*) has agg_star\n  if (fc.agg_star) return true;\n  // COUNT(1) or COUNT(literal) — single constant argument\n  if (\n    fc.args &&\n    fc.args.length === 1 &&\n    isANode(fc.args[0]) &&\n    is(fc.args[0], \"A_Const\")\n  ) {\n    return true;\n  }\n  return false;\n}\n\nfunction isSubLinkWithCount(node: unknown): boolean {\n  if (!node || typeof node !== \"object\") return false;\n  if (!isANode(node) || !is(node, \"SubLink\")) return false;\n  const sub = node.SubLink;\n  const subselect = sub.subselect;\n  if (!subselect || !isANode(subselect) || !is(subselect, \"SelectStmt\"))\n    return false;\n  const targets = subselect.SelectStmt.targetList;\n  if (!targets || targets.length !== 1) return false;\n  const target = targets[0];\n  if (!isANode(target) || !is(target, \"ResTarget\") || !target.ResTarget.val)\n    return false;\n  return isCountFuncCall(target.ResTarget.val);\n}\n\nfunction isCountExpression(node: unknown): boolean {\n  return isCountFuncCall(node) || isSubLinkWithCount(node);\n}\n\nfunction getIntegerConstantValue(node: unknown): number | null {\n  if (!node || typeof node !== \"object\") return null;\n  if (!isANode(node) || !is(node, \"A_Const\")) return null;\n  if (node.A_Const.ival === undefined) return null;\n  // protobuf: ival: {} means 0, ival: { ival: N } means N\n  return node.A_Const.ival.ival ?? 0;\n}\n\nfunction isExistenceCheckPattern(\n  lexpr: unknown,\n  rexpr: unknown,\n  op: string,\n): boolean {\n  // count_expr > 0, count_expr >= 1, count_expr != 0, count_expr <> 0\n  if (isCountExpression(lexpr)) {\n    const val = getIntegerConstantValue(rexpr);\n    if (val !== null) {\n      if (op === \">\" && val === 0) return true;\n      if (op === \">=\" && val === 1) return true;\n      if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n    }\n  }\n\n  // Reversed: 0 < count_expr, 1 <= count_expr, 0 != count_expr\n  if (isCountExpression(rexpr)) {\n    const val = getIntegerConstantValue(lexpr);\n    if (val !== null) {\n      if (op === \"<\" && val === 0) return true;\n      if (op === \"<=\" && val === 1) return true;\n      if ((op === \"!=\" || op === \"<>\") && val === 0) return true;\n    }\n  }\n\n  return false;\n}\n"],"mappings":"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"}