{"version":3,"file":"display-query.cjs","names":["is"],"sources":["../../src/sql/display-query.ts"],"sourcesContent":["import type { Node, ParseResult } from \"@pgsql/types\";\nimport { is } from \"./ast-utils.js\";\n\nexport interface CompactSelectListOptions {\n  /**\n   * Compact only when the SELECT has more than this many target-list entries.\n   * Default: 2.\n   */\n  minTargets?: number;\n  /**\n   * Compact only when the target-list slice (from first target to FROM)\n   * exceeds this many bytes. Default: 40.\n   */\n  minSliceBytes?: number;\n}\n\nconst DEFAULTS: Required<CompactSelectListOptions> = {\n  minTargets: 2,\n  minSliceBytes: 40,\n};\n\nconst ENCODER = new TextEncoder();\nconst DECODER = new TextDecoder();\n\n/**\n * Returns `query` with the top-level SELECT target list replaced by `...`,\n * e.g. `SELECT ... FROM users WHERE id = $1`. Returns `undefined` when the\n * query shouldn't be compacted: parse failure, not a plain SELECT,\n * UNION/INTERSECT/EXCEPT, no FROM clause, or below the configured threshold.\n *\n * NOT VALID SQL — never parse or execute the result. Display-only.\n *\n * Caller passes the AST (already parsed via `@pgsql/parser`) so this module\n * stays free of a parser runtime dependency, mirroring how `Analyzer`\n * accepts an injected parser.\n */\nexport function compactSelectList(\n  query: string,\n  ast: ParseResult,\n  options: CompactSelectListOptions = {},\n): string | undefined {\n  const { minTargets, minSliceBytes } = { ...DEFAULTS, ...options };\n\n  const stmt = ast.stmts?.[0]?.stmt;\n  if (!stmt || !is(stmt, \"SelectStmt\")) return undefined;\n\n  const select = stmt.SelectStmt;\n\n  // Skip set-operations — ambiguous which arm to compact.\n  if (select.op && select.op !== \"SETOP_NONE\") return undefined;\n\n  const targetList = select.targetList;\n  if (!targetList || targetList.length < minTargets + 1) {\n    // Below the target-count threshold — but the slice may still be long.\n    // Fall through to the byte threshold check after computing the slice.\n    if (!targetList || targetList.length === 0) return undefined;\n  }\n\n  const fromClause = select.fromClause;\n  if (!fromClause || fromClause.length === 0) return undefined;\n\n  const firstTarget = targetList[0];\n  if (!is(firstTarget, \"ResTarget\")) return undefined;\n  const startByte = firstTarget.ResTarget.location;\n  if (typeof startByte !== \"number\") return undefined;\n\n  const fromItemByte = locationOf(fromClause[0]);\n  if (typeof fromItemByte !== \"number\") return undefined;\n\n  const queryBytes = ENCODER.encode(query);\n\n  const fromKeywordByte = findFromKeyword(queryBytes, startByte, fromItemByte);\n  if (fromKeywordByte === undefined) return undefined;\n\n  const sliceBytes = fromKeywordByte - startByte;\n  const enoughTargets = targetList.length > minTargets;\n  const enoughBytes = sliceBytes > minSliceBytes;\n  if (!enoughTargets && !enoughBytes) return undefined;\n\n  const before = DECODER.decode(queryBytes.slice(0, startByte));\n  const after = DECODER.decode(queryBytes.slice(fromKeywordByte));\n  return `${before}... ${after}`;\n}\n\n/** Best-effort byte offset for common from-clause node kinds. */\nfunction locationOf(node: Node): number | undefined {\n  if (is(node, \"RangeVar\")) return node.RangeVar.location;\n  if (is(node, \"RangeSubselect\")) return node.RangeSubselect.location;\n  if (is(node, \"RangeFunction\")) return node.RangeFunction.location;\n  if (is(node, \"JoinExpr\")) {\n    return node.JoinExpr.larg ? locationOf(node.JoinExpr.larg) : undefined;\n  }\n  return undefined;\n}\n\nconst BYTE_F = 0x46;\nconst BYTE_f = 0x66;\nconst BYTE_R = 0x52;\nconst BYTE_r = 0x72;\nconst BYTE_O = 0x4f;\nconst BYTE_o = 0x6f;\nconst BYTE_M = 0x4d;\nconst BYTE_m = 0x6d;\nconst BYTE_LPAREN = 0x28;\nconst BYTE_RPAREN = 0x29;\nconst BYTE_SQUOTE = 0x27;\nconst BYTE_DQUOTE = 0x22;\nconst BYTE_DASH = 0x2d;\nconst BYTE_SLASH = 0x2f;\nconst BYTE_STAR = 0x2a;\nconst BYTE_LF = 0x0a;\n\n/**\n * Scan forward in `bytes` starting at `from` (inclusive) up to `until`\n * (exclusive) for the first `FROM` keyword that is at parenthesis depth 0\n * and not inside a string or comment. Case-insensitive, requires word\n * boundaries on both sides. Returns the byte offset of the matching `F`.\n */\nfunction findFromKeyword(\n  bytes: Uint8Array,\n  from: number,\n  until: number,\n): number | undefined {\n  let i = from;\n  let depth = 0;\n  let inLineComment = false;\n  let inBlockComment = false;\n  let inSingleQuote = false;\n  let inDoubleQuote = false;\n\n  while (i < until) {\n    const c = bytes[i];\n\n    if (inLineComment) {\n      if (c === BYTE_LF) inLineComment = false;\n      i++;\n      continue;\n    }\n    if (inBlockComment) {\n      if (c === BYTE_STAR && bytes[i + 1] === BYTE_SLASH) {\n        inBlockComment = false;\n        i += 2;\n        continue;\n      }\n      i++;\n      continue;\n    }\n    if (inSingleQuote) {\n      if (c === BYTE_SQUOTE) {\n        // '' is an escaped single quote inside a string literal.\n        if (bytes[i + 1] === BYTE_SQUOTE) {\n          i += 2;\n          continue;\n        }\n        inSingleQuote = false;\n      }\n      i++;\n      continue;\n    }\n    if (inDoubleQuote) {\n      if (c === BYTE_DQUOTE) {\n        if (bytes[i + 1] === BYTE_DQUOTE) {\n          i += 2;\n          continue;\n        }\n        inDoubleQuote = false;\n      }\n      i++;\n      continue;\n    }\n\n    if (c === BYTE_DASH && bytes[i + 1] === BYTE_DASH) {\n      inLineComment = true;\n      i += 2;\n      continue;\n    }\n    if (c === BYTE_SLASH && bytes[i + 1] === BYTE_STAR) {\n      inBlockComment = true;\n      i += 2;\n      continue;\n    }\n    if (c === BYTE_SQUOTE) {\n      inSingleQuote = true;\n      i++;\n      continue;\n    }\n    if (c === BYTE_DQUOTE) {\n      inDoubleQuote = true;\n      i++;\n      continue;\n    }\n    if (c === BYTE_LPAREN) {\n      depth++;\n      i++;\n      continue;\n    }\n    if (c === BYTE_RPAREN) {\n      depth--;\n      i++;\n      continue;\n    }\n\n    if (\n      depth === 0 &&\n      (c === BYTE_F || c === BYTE_f) &&\n      isFromKeywordAt(bytes, i)\n    ) {\n      return i;\n    }\n    i++;\n  }\n  return undefined;\n}\n\nfunction isFromKeywordAt(bytes: Uint8Array, pos: number): boolean {\n  const r = bytes[pos + 1];\n  const o = bytes[pos + 2];\n  const m = bytes[pos + 3];\n  if (\n    !(\n      (r === BYTE_R || r === BYTE_r) &&\n      (o === BYTE_O || o === BYTE_o) &&\n      (m === BYTE_M || m === BYTE_m)\n    )\n  ) {\n    return false;\n  }\n  if (pos > 0 && isWordByte(bytes[pos - 1])) return false;\n  const next = bytes[pos + 4];\n  if (next !== undefined && isWordByte(next)) return false;\n  return true;\n}\n\nfunction isWordByte(c: number): boolean {\n  return (\n    (c >= 0x30 && c <= 0x39) ||\n    (c >= 0x41 && c <= 0x5a) ||\n    (c >= 0x61 && c <= 0x7a) ||\n    c === 0x5f\n  );\n}\n"],"mappings":"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"}