{"version":3,"file":"analyzer.mjs","names":[],"sources":["../../src/sql/analyzer.ts"],"sourcesContent":["import type {\n  NullTestType,\n  ParseResult,\n  SortByDir,\n  SortByNulls,\n} from \"@pgsql/types\";\nimport {\n  bgMagentaBright,\n  blue,\n  type Color,\n  dim,\n  strikethrough,\n} from \"colorette\";\nimport type { RootIndexCandidate } from \"../optimizer/genalgo.js\";\nimport type { ExportedStats } from \"../optimizer/statistics.js\";\nimport { getNodeKind } from \"./ast-utils.js\";\nimport type { Nudge } from \"./nudges.js\";\nimport { ColumnReferencePart, TableMappings, Walker } from \"./walker.js\";\n\nexport interface DatabaseDriver {\n  query(query: string, params: unknown[]): Promise<unknown[]>;\n}\n\nexport const ignoredIdentifier = \"__qd_placeholder\";\n\nexport interface SQLCommenterTag {\n  key: string;\n  value: string;\n}\n\nexport type SortContext = {\n  dir: SortByDir;\n  nulls: SortByNulls;\n};\n\nexport type DiscoveredColumnReference = {\n  /** How often the column reference appears in the query. */\n  frequency: number;\n  /**\n   * Representation of the column reference exactly\n   * as it appears in the query.\n   */\n  representation: string;\n  /**\n   * Parts of the column reference separated by dots in the query.\n   * The table reference (if it exists) is resolved if the query\n   * uses an alias.\n   *\n   * Has 3 different potential configurations (in theory)\n   * `a.b.c` - a column reference with a table and a schema reference\n   * `a.b` - a column reference with a table reference but no schema\n   * `a` - a column reference with no table reference.\n   *\n   * We use a simple array here to allow parsing of any syntactically correct\n   * but logically incorrect query. The checks happen later when we're deriving\n   * potential indexes from parts of a column reference in `deriveIndexes`\n   */\n  parts: ColumnReferencePart[];\n  /**\n   * Whether the column reference is invalid. This\n   */\n  ignored: boolean;\n  /** The position of the column reference in the query. */\n  position: {\n    start: number;\n    end: number;\n  };\n  /**\n   * A sort direction associated by the column reference.\n   * Only relevant to references from sorts\n   */\n  sort?: SortContext;\n  where?: { nulltest?: NullTestType };\n  jsonbOperator?: JsonbOperator;\n  jsonbExtraction?: string;\n};\n\nexport type JsonbOperator = \"@>\" | \"?\" | \"?|\" | \"?&\" | \"@@\" | \"@?\";\n\nexport type StatementType =\n  | \"select\"\n  | \"insert\"\n  | \"update\"\n  | \"delete\"\n  | \"merge\"\n  | \"other\";\n\nconst STATEMENT_TYPE_MAP: Record<string, StatementType> = {\n  SelectStmt: \"select\",\n  InsertStmt: \"insert\",\n  UpdateStmt: \"update\",\n  DeleteStmt: \"delete\",\n  MergeStmt: \"merge\",\n};\n\n/** A function defined by @pgsql/parser */\nexport type Parser = (query: string) => Promise<unknown>;\n\nexport type TableReference = {\n  schema?: string;\n  table: string;\n};\n\nexport type AnalysisResult = {\n  statementType: StatementType;\n  indexesToCheck: DiscoveredColumnReference[];\n  ansiHighlightedQuery: string;\n  referencedTables: TableReference[];\n  shadowedAliases: ColumnReferencePart[];\n  tags: SQLCommenterTag[];\n  queryWithoutTags: string;\n  formattedQueryWithoutTags?: string;\n  nudges: Nudge[];\n};\n\nexport type SQLCommenterExtraction = {\n  tags: SQLCommenterTag[];\n  queryWithoutTags: string;\n};\n\n/**\n * Analyzes a query and returns a list of column references that\n * should be indexed.\n *\n * This should be instantiated once per analyzed query.\n */\nexport class Analyzer {\n  constructor(private readonly parser: Parser) {}\n  async analyze(\n    query: string,\n    formattedQuery?: string,\n  ): Promise<AnalysisResult> {\n    const ast = (await this.parser(query)) as ParseResult;\n    if (!ast.stmts) {\n      throw new Error(\n        \"Query did not have any statements. This should probably never happen?\",\n      );\n    }\n    const stmt = ast.stmts[0].stmt;\n    if (!stmt) {\n      throw new Error(\n        \"Query did not have any statements. This should probably never happen?\",\n      );\n    }\n    const stmtKind = getNodeKind(stmt);\n    const statementType: StatementType =\n      STATEMENT_TYPE_MAP[stmtKind] ?? \"other\";\n    const walker = new Walker(query);\n    const {\n      highlights,\n      indexRepresentations,\n      indexesToCheck,\n      shadowedAliases,\n      tempTables,\n      tableMappings,\n      nudges,\n    } = walker.walk(stmt);\n    const sortedHighlights = highlights.sort(\n      (a, b) => b.position.end - a.position.end,\n    );\n    let currQuery = query;\n    for (const highlight of sortedHighlights) {\n      // our parts might have\n      const parts = this.resolveTableAliases(highlight.parts, tableMappings);\n      if (parts.length === 0) {\n        console.error(highlight);\n        throw new Error(\"Highlight must have at least one part\");\n      }\n      let color: Color;\n      let skip = false;\n      if (highlight.ignored) {\n        color = (x) => dim(strikethrough(x));\n        skip = true;\n      } else if (\n        parts.length === 2 &&\n        tempTables.has(parts[0].text) &&\n        // sometimes temp tables are aliased as existing tables\n        // we don't want to ignore them if they are\n        !tableMappings.has(parts[0].text)\n      ) {\n        color = blue;\n        skip = true;\n      } else {\n        color = bgMagentaBright;\n      }\n      const queryRepr = highlight.representation;\n      const queryBeforeMatch = currQuery.slice(0, highlight.position.start);\n      const queryAfterToken = currQuery.slice(highlight.position.end);\n      currQuery = `${queryBeforeMatch}${color(queryRepr)}${this.colorizeKeywords(\n        queryAfterToken,\n        color,\n      )}`;\n      const reprKey = highlight.jsonbExtraction\n        ? `${queryRepr}::${highlight.jsonbExtraction}`\n        : queryRepr;\n      if (indexRepresentations.has(reprKey)) {\n        skip = true;\n      }\n      if (!skip) {\n        indexesToCheck.push(highlight);\n        indexRepresentations.add(reprKey);\n      }\n    }\n\n    const referencedTables: TableReference[] = [];\n    for (const value of tableMappings.values()) {\n      // aliased mappings are not concrete tables\n      // eg: select * from table t -> t is not a table\n      if (!value.alias) {\n        referencedTables.push({\n          schema: value.schema,\n          table: value.text,\n        });\n      }\n    }\n    const { tags, queryWithoutTags } = this.extractSqlcommenter(query);\n\n    const formattedQueryWithoutTags = formattedQuery\n      ? this.extractSqlcommenter(formattedQuery).queryWithoutTags\n      : undefined;\n\n    return {\n      statementType,\n      indexesToCheck,\n      ansiHighlightedQuery: currQuery,\n      referencedTables,\n      shadowedAliases,\n      tags,\n      queryWithoutTags,\n      formattedQueryWithoutTags,\n      nudges,\n    };\n  }\n\n  deriveIndexes(\n    tables: ExportedStats[],\n    discovered: DiscoveredColumnReference[],\n    referencedTables: TableReference[],\n  ): RootIndexCandidate[] {\n    /**\n     * There are 3 different kinds of parts a col reference can have\n     * {a} = just a column within context. Find out the table\n     * {a, b} = a column reference with a table reference. There's still ambiguity here\n     * with what the schema could be in case there are 2 tables with the same name in different schemas.\n     * {a, b, c} = a column reference with a table reference and a schema reference.\n     * This is the best case scenario.\n     */\n    const allIndexes: RootIndexCandidate[] = [];\n    const seenIndexes = new Set<string>();\n    function addIndex(index: RootIndexCandidate) {\n      const extractionSuffix = index.jsonbExtraction\n        ? `:\"${index.jsonbExtraction}\"`\n        : \"\";\n      const key = `\"${index.schema}\":\"${index.table}\":\"${index.column}\"${extractionSuffix}`;\n      if (seenIndexes.has(key)) {\n        return;\n      }\n      seenIndexes.add(key);\n      allIndexes.push(index);\n    }\n    const matchingTables = this.filterReferences(referencedTables, tables);\n    for (const colReference of discovered) {\n      const partsCount = colReference.parts.length;\n      const columnOnlyReference = partsCount === 1;\n      const tableReference = partsCount === 2;\n      const fullReference = partsCount === 3;\n      if (columnOnlyReference) {\n        // select c from x\n        const [column] = colReference.parts;\n        const referencedColumn = this.normalize(column);\n        for (const table of matchingTables) {\n          if (!this.hasColumn(table, referencedColumn)) {\n            continue;\n          }\n          const index: RootIndexCandidate = {\n            schema: table.schemaName,\n            table: table.tableName,\n            column: referencedColumn,\n          };\n          if (colReference.sort) {\n            index.sort = colReference.sort;\n          }\n          if (colReference.where) {\n            index.where = colReference.where;\n          }\n          if (colReference.jsonbOperator) {\n            index.jsonbOperator = colReference.jsonbOperator;\n          }\n          if (colReference.jsonbExtraction) {\n            index.jsonbExtraction = colReference.jsonbExtraction;\n          }\n          addIndex(index);\n        }\n      } else if (tableReference) {\n        // select b.c from x\n        const [table, column] = colReference.parts;\n        const referencedTable = this.normalize(table);\n        const referencedColumn = this.normalize(column);\n        for (const matchingTable of matchingTables) {\n          if (!this.hasColumn(matchingTable, referencedColumn)) {\n            continue;\n          }\n          const index: RootIndexCandidate = {\n            schema: matchingTable.schemaName,\n            table: referencedTable,\n            column: referencedColumn,\n          };\n          if (colReference.sort) {\n            index.sort = colReference.sort;\n          }\n          if (colReference.where) {\n            index.where = colReference.where;\n          }\n          if (colReference.jsonbOperator) {\n            index.jsonbOperator = colReference.jsonbOperator;\n          }\n          if (colReference.jsonbExtraction) {\n            index.jsonbExtraction = colReference.jsonbExtraction;\n          }\n          addIndex(index);\n        }\n      } else if (fullReference) {\n        // select a.b.c from x\n        const [schema, table, column] = colReference.parts;\n        const referencedSchema = this.normalize(schema);\n        const referencedTable = this.normalize(table);\n        const referencedColumn = this.normalize(column);\n        const index: RootIndexCandidate = {\n          schema: referencedSchema,\n          table: referencedTable,\n          column: referencedColumn,\n        };\n        if (colReference.sort) {\n          index.sort = colReference.sort;\n        }\n        if (colReference.where) {\n          index.where = colReference.where;\n        }\n        if (colReference.jsonbOperator) {\n          index.jsonbOperator = colReference.jsonbOperator;\n        }\n        if (colReference.jsonbExtraction) {\n          index.jsonbExtraction = colReference.jsonbExtraction;\n        }\n        addIndex(index);\n      } else {\n        // select huh.a.b.c from x\n        console.error(\n          \"Column reference has too many parts. The query is malformed\",\n          colReference,\n        );\n        continue;\n      }\n    }\n    return allIndexes;\n  }\n\n  private filterReferences(\n    referencedTables: TableReference[],\n    tables: ExportedStats[],\n  ): ExportedStats[] {\n    const matchingTables: ExportedStats[] = [];\n    for (const referencedTable of referencedTables) {\n      const refs = tables.filter(({ tableName, schemaName }) => {\n        // not every referenced table carries a schema with it\n        let schemaMatches = true;\n        if (referencedTable.schema) {\n          schemaMatches = schemaName === referencedTable.schema;\n        }\n        return schemaMatches && tableName === referencedTable.table;\n      });\n      matchingTables.push(...refs);\n    }\n    return matchingTables;\n  }\n\n  private hasColumn(table: ExportedStats, columnName: string): boolean {\n    return (\n      table.columns?.some((column) => column.columnName === columnName) ?? false\n    );\n  }\n\n  private colorizeKeywords(query: string, color: Color) {\n    return query\n      .replace(\n        // eh? This kinda sucks\n        /(^\\s+)(asc|desc)?(\\s+(nulls first|nulls last))?/i,\n        (_, pre, dir, spaceNulls, nulls) => {\n          return `${pre}${dir ? color(dir) : \"\"}${\n            nulls ? spaceNulls.replace(nulls, color(nulls)) : \"\"\n          }`;\n        },\n      )\n      .replace(/(^\\s+)(is (null|not null))/i, (_, pre, nulltest) => {\n        return `${pre}${color(nulltest)}`;\n      });\n  }\n\n  /**\n   * Resolves aliases such as `a.b` to `x.b` if `a` is a known\n   * alias to a table called x.\n   *\n   * Ignores all other combination of parts such as `a.b.c`\n   */\n  private resolveTableAliases(\n    parts: ColumnReferencePart[],\n    tableMappings: TableMappings,\n  ): ColumnReferencePart[] {\n    // we don't want to resolve aliases for references such as\n    // `a.b.c` - this is fully qualified with a schema and can't be an alias\n    // `c` - because there's no table reference here (as far as we can tell)\n    if (parts.length !== 2) {\n      return parts;\n    }\n    const tablePart = parts[0];\n    const mapping = tableMappings.get(tablePart.text);\n    if (mapping) {\n      parts[0] = mapping;\n    }\n    return parts;\n  }\n\n  private normalize(columnReference: ColumnReferencePart): string {\n    return columnReference.quoted\n      ? columnReference.text\n      : // postgres automatically lowercases column names if not quoted\n        columnReference.text.toLowerCase();\n  }\n\n  private extractSqlcommenter(query: string): SQLCommenterExtraction {\n    const trimmedQuery = query.trimEnd();\n    const startPosition = trimmedQuery.lastIndexOf(\"/*\");\n    const endPosition = trimmedQuery.lastIndexOf(\"*/\");\n    if (startPosition === -1 || endPosition === -1) {\n      return { tags: [], queryWithoutTags: trimmedQuery };\n    }\n    // Only treat as SQLCommenter if the comment is at the end of the query.\n    // pg_stat_statements (PG 18+) puts /*, ... */ inside IN clauses —\n    // those have SQL after the closing */, so skip them.\n    const afterComment = trimmedQuery.slice(endPosition + 2).trim();\n    if (afterComment && afterComment !== \";\") {\n      return { tags: [], queryWithoutTags: trimmedQuery };\n    }\n    const queryWithoutTags = trimmedQuery.slice(0, startPosition);\n    const tagString = trimmedQuery.slice(startPosition + 2, endPosition).trim();\n    if (!tagString || typeof tagString !== \"string\") {\n      return { tags: [], queryWithoutTags: queryWithoutTags };\n    }\n    const tags: SQLCommenterTag[] = [];\n    for (const match of tagString.split(\",\")) {\n      const [key, value] = match.split(\"=\");\n      // just because a comment has a `,` but not a `=` in it doesn't\n      // mean that it's a malformed sqlcommenter tag. It might just be\n      // a long comment with good punctuation.\n      if (!key || !value) {\n        // however, if there was a previously valid tag, the comment\n        // is more likely to be a malformed sqlcommenter tag\n        if (tags.length > 0) {\n          console.warn(\n            `Invalid sqlcommenter tag: ${match} in comment: ${tagString}. Ignoring`,\n          );\n        }\n        continue;\n      }\n      try {\n        let sliceStart = 0;\n        if (value.startsWith(\"'\")) {\n          sliceStart = 1;\n        }\n        let sliceEnd = value.length;\n        if (value.endsWith(\"'\")) {\n          sliceEnd -= 1;\n        }\n\n        const decoded = decodeURIComponent(value.slice(sliceStart, sliceEnd));\n        // should we be trimming here?\n        tags.push({ key: key.trim(), value: decoded });\n      } catch (err) {\n        // we want to be very conservative with this parser and ignore errors\n        console.error(err);\n      }\n    }\n    return { tags, queryWithoutTags };\n  }\n}\n"],"mappings":"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"}