{"version":3,"file":"normalized-fingerprint.mjs","names":[],"sources":["../../src/sql/normalized-fingerprint.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { deparseSync } from \"pgsql-deparser\";\nimport { is } from \"./ast-utils.js\";\nimport { Walker } from \"./walker.js\";\n\n/**\n * A function that fingerprints a SQL string. Injected so this module stays\n * free of a fingerprint runtime dependency, mirroring how `Analyzer` accepts\n * an injected parser. In practice this is `fingerprint` from\n * `@libpg-query/parser`.\n */\nexport type FingerprintFn = (query: string) => Promise<string>;\n\ntype SelectStmt = Extract<Node, { SelectStmt: unknown }>[\"SelectStmt\"];\n\nconst SELECT_LIST_PLACEHOLDER: Node = {\n  ResTarget: {\n    val: { A_Const: { ival: { ival: 1 }, location: -1 } },\n    location: -1,\n  },\n};\n\n/**\n * A \"bare column\" target: any `ColumnRef`, including `a`, qualified `t.a`, and\n * the wildcards `*` and `t.*`. `count(*)`, `a + b`, literals, and `AS` aliases\n * are not column refs and survive normalization.\n */\nfunction isBareColumn(target: Node): boolean {\n  if (!is(target, \"ResTarget\")) return false;\n  const val = target.ResTarget.val;\n  if (!val) return false;\n  return is(val, \"ColumnRef\");\n}\n\n/**\n * Set-operation arms (`larg`/`rarg`) are inlined `SelectStmt` bodies rather\n * than wrapped `{ SelectStmt }` nodes, so `Walker.traverse` never emits them.\n * Drill into them explicitly; they can nest (`a UNION b UNION c`).\n */\nfunction normalizeSelect(select: SelectStmt): void {\n  const targetList = select.targetList;\n  if (targetList) {\n    const survivors = targetList.filter((t) => !isBareColumn(t));\n    if (survivors.length > 1) {\n      const keyed = survivors.map((t) => ({ key: deparseSync(t), t }));\n      keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n      select.targetList = keyed.map((k) => k.t);\n    } else {\n      select.targetList = survivors.length\n        ? survivors\n        : [SELECT_LIST_PLACEHOLDER];\n    }\n  }\n  if (select.larg) normalizeSelect(select.larg);\n  if (select.rarg) normalizeSelect(select.rarg);\n}\n\n/**\n * Fingerprint a query while normalizing every SELECT target list in the tree\n * so that the presence and order of bare column references don't affect the\n * result. Applies to the top-level SELECT as well as SELECTs nested in CTEs,\n * subqueries, and set-operation arms. A change to the set of\n * aggregates/expressions, the WHERE, the FROM, etc. still moves it.\n *\n * Takes the already-parsed AST and an injected {@link FingerprintFn} so core\n * needs no parser or fingerprint runtime. The AST is cloned before mutation.\n */\nexport async function normalizedFingerprint(\n  ast: ParseResult,\n  fingerprint: FingerprintFn,\n): Promise<string> {\n  const cloned: ParseResult = JSON.parse(JSON.stringify(ast));\n\n  for (const raw of cloned.stmts ?? []) {\n    if (!raw.stmt) continue;\n    Walker.traverse(raw.stmt, (node) => {\n      if (is(node, \"SelectStmt\")) {\n        normalizeSelect(node.SelectStmt);\n      }\n    });\n  }\n\n  return fingerprint(deparseSync(cloned));\n}\n"],"mappings":";;;;;AAeA,MAAM,0BAAgC,EACpC,WAAW;CACT,KAAK,EAAE,SAAS;EAAE,MAAM,EAAE,MAAM,GAAG;EAAE,UAAU;EAAI,EAAE;CACrD,UAAU;CACX,EACF;;;;;;AAOD,SAAS,aAAa,QAAuB;AAC3C,KAAI,CAAC,GAAG,QAAQ,YAAY,CAAE,QAAO;CACrC,MAAM,MAAM,OAAO,UAAU;AAC7B,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO,GAAG,KAAK,YAAY;;;;;;;AAQ7B,SAAS,gBAAgB,QAA0B;CACjD,MAAM,aAAa,OAAO;AAC1B,KAAI,YAAY;EACd,MAAM,YAAY,WAAW,QAAQ,MAAM,CAAC,aAAa,EAAE,CAAC;AAC5D,MAAI,UAAU,SAAS,GAAG;GACxB,MAAM,QAAQ,UAAU,KAAK,OAAO;IAAE,KAAK,YAAY,EAAE;IAAE;IAAG,EAAE;AAChE,SAAM,MAAM,GAAG,MAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,EAAG;AAClE,UAAO,aAAa,MAAM,KAAK,MAAM,EAAE,EAAE;QAEzC,QAAO,aAAa,UAAU,SAC1B,YACA,CAAC,wBAAwB;;AAGjC,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;AAC7C,KAAI,OAAO,KAAM,iBAAgB,OAAO,KAAK;;;;;;;;;;;;AAa/C,eAAsB,sBACpB,KACA,aACiB;CACjB,MAAM,SAAsB,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAE3D,MAAK,MAAM,OAAO,OAAO,SAAS,EAAE,EAAE;AACpC,MAAI,CAAC,IAAI,KAAM;AACf,SAAO,SAAS,IAAI,OAAO,SAAS;AAClC,OAAI,GAAG,MAAM,aAAa,CACxB,iBAAgB,KAAK,WAAW;IAElC;;AAGJ,QAAO,YAAY,YAAY,OAAO,CAAC"}