{"version":3,"file":"genalgo.cjs","names":["PostgresQueryBuilder","PgIdentifier","permutationsWithDescendingLength","green","yellow","magenta","blue","isIndexProbablyDroppable","dropIndex"],"sources":["../../src/optimizer/genalgo.ts"],"sourcesContent":["import type { NullTestType } from \"@pgsql/types\";\nimport { blue, gray, green, magenta, red, yellow } from \"colorette\";\nimport type { FullSchemaIndex } from \"../schema.js\";\nimport type { JsonbOperator } from \"../sql/analyzer.js\";\nimport type { SortContext } from \"../sql/analyzer.js\";\nimport { PostgresQueryBuilder } from \"../sql/builder.js\";\nimport {\n  dropIndex,\n  type Postgres,\n  type PostgresExplainResult,\n  type PostgresExplainStage,\n  type PostgresTransaction,\n} from \"../sql/database.js\";\nimport { isIndexProbablyDroppable } from \"../sql/indexes.js\";\nimport { permutationsWithDescendingLength } from \"../sql/permutations.js\";\nimport { PgIdentifier } from \"../sql/pg-identifier.js\";\nimport type { Statistics } from \"./statistics.js\";\n\nexport type IndexIdentifier = string;\n\ntype IndexRecommendation = PermutedIndexCandidate & {\n  definition: IndexIdentifier;\n};\n\ntype Color = (a: string) => string;\n\nexport type IndexToCreate = PermutedIndexCandidate & {\n  name: PgIdentifier;\n  definition: IndexIdentifier;\n};\n\nexport class IndexOptimizer {\n  static prefix = \"__qd_\";\n\n  constructor(\n    private readonly db: Postgres,\n    private readonly statistics: Statistics,\n    private existingIndexes: FullSchemaIndex[],\n    private readonly config: {\n      trace?: boolean;\n      debug?: boolean;\n    } = {},\n  ) {}\n\n  async run(\n    builder: PostgresQueryBuilder,\n    indexes: RootIndexCandidate[],\n    beforeQuery?: (tx: PostgresTransaction) => Promise<void>,\n  ): Promise<OptimizeResult> {\n    const baseExplain = await this.testQueryWithStats(builder, async (tx) => {\n      if (beforeQuery) {\n        await beforeQuery(tx);\n      }\n    });\n    const baseCost: number = Number(baseExplain.Plan[\"Total Cost\"]);\n    if (baseCost === 0) {\n      return {\n        kind: \"zero_cost_plan\",\n        explainPlan: baseExplain.Plan,\n      };\n    }\n    const toCreate = this.indexesToCreate(indexes);\n    if (toCreate.length === 0) {\n      // No indexes to try: the 2nd EXPLAIN would be identical to the 1st,\n      // so skip it. On large tables this saves a full re-run of the query.\n      const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n      return {\n        kind: \"ok\",\n        baseCost,\n        finalCost: baseCost,\n        newIndexes: new Set<string>(),\n        existingIndexes: baseIndexes.existingIndexes,\n        triedIndexes: new Map(),\n        baseExplainPlan: baseExplain.Plan,\n        explainPlan: baseExplain.Plan,\n      };\n    }\n    this.statistics.setAdditionalIndexes(toCreate);\n    const finalExplain = await this.testQueryWithStats(builder, async (tx) => {\n      if (beforeQuery) {\n        await beforeQuery(tx);\n      }\n\n      // Then create recommended indexes\n      for (const permutation of toCreate) {\n        const createIndex = PostgresQueryBuilder.createIndex(\n          permutation.definition,\n          permutation.name,\n        )\n          .introspect()\n          .build();\n\n        await tx.exec(createIndex);\n      }\n    });\n    this.statistics.setAdditionalIndexes([]);\n    const finalCost = Number(finalExplain.Plan[\"Total Cost\"]);\n    if (this.config.debug) {\n      console.dir(finalExplain, { depth: null });\n    }\n    const deltaPercentage = ((baseCost - finalCost) / baseCost) * 100;\n    if (finalCost < baseCost) {\n      console.log(\n        ` 🎉🎉🎉 ${green(`+${deltaPercentage.toFixed(2).padStart(5, \"0\")}%`)}`,\n      );\n    } else if (finalCost > baseCost) {\n      console.log(\n        `${red(\n          `-${Math.abs(deltaPercentage).toFixed(2).padStart(5, \"0\")}%`,\n        )} ${gray(\"If there's a better index, we haven't tried it\")}`,\n      );\n    }\n    const baseIndexes = this.findUsedIndexes(baseExplain.Plan);\n    const finalIndexes = this.findUsedIndexes(finalExplain.Plan);\n    const triedIndexes = new Map(\n      toCreate.map((index) => [index.name.toString(), index]),\n    );\n    this.replaceUsedIndexesWithDefinition(finalExplain.Plan, triedIndexes);\n\n    return {\n      kind: \"ok\",\n      baseCost,\n      finalCost,\n      newIndexes: finalIndexes.newIndexes,\n      existingIndexes: baseIndexes.existingIndexes,\n      triedIndexes,\n      baseExplainPlan: baseExplain.Plan,\n      explainPlan: finalExplain.Plan,\n    };\n  }\n\n  async runWithoutIndexes(builder: PostgresQueryBuilder) {\n    return await this.testQueryWithStats(builder, async (tx) => {\n      await this.dropExistingIndexes(tx);\n    });\n  }\n\n  /**\n   * Given the current indexes in the optimizer, transform them in some\n   * way to change which indexes will be assumed to exist when optimizing\n   *\n   * @example\n   * ```\n   * // resets indexes\n   * optimizer.transformIndexes(() => [])\n   *\n   * // adds new index\n   * optimizer.transformIndexes(indexes => [...indexes, newIndex])\n   * ```\n   */\n  transformIndexes(f: (indexes: FullSchemaIndex[]) => FullSchemaIndex[]) {\n    const newIndexes = f(this.existingIndexes);\n    this.existingIndexes = newIndexes;\n    return this;\n  }\n\n  /**\n   * Postgres has a limit of 63 characters for index names.\n   * So we use this to make sure we don't derive it from a list of columns that can\n   * overflow that limit.\n   */\n  private indexName(): PgIdentifier {\n    const indexName =\n      IndexOptimizer.prefix + Math.random().toString(36).substring(2, 16);\n    return PgIdentifier.fromString(indexName);\n  }\n\n  // TODO: this doesn't belong in the optimizer\n  private indexAlreadyExists(\n    table: string,\n    columns: RootIndexCandidate[],\n  ): FullSchemaIndex | undefined {\n    return this.existingIndexes.find(\n      (index) =>\n        index.indexType === \"btree\" &&\n        index.tableName.toString() === table &&\n        index.keyColumns.length === columns.length &&\n        index.keyColumns.every((c, i) => {\n          if (columns[i].column !== c.name.toString()) {\n            return false;\n          }\n\n          // we should assume any index with `WHERE`\n          // can't be counted as a duplicate\n          if (columns[i].where) {\n            return false;\n          }\n\n          if (columns[i].sort) {\n            switch (columns[i].sort.dir) {\n              // Sorting is ASC by default in postgres\n              case \"SORTBY_DEFAULT\":\n              case \"SORTBY_ASC\":\n                if (c.order !== \"ASC\") {\n                  return false;\n                }\n                break;\n              case \"SORTBY_DESC\":\n                if (c.order !== \"DESC\") {\n                  return false;\n                }\n                break;\n            }\n          }\n          return true;\n        }),\n    );\n  }\n\n  /**\n   * Derive the list of indexes [tableA(X, Y, Z), tableB(H, I, J)]\n   **/\n  private indexesToCreate(\n    rootCandidates: RootIndexCandidate[],\n  ): IndexToCreate[] {\n    const expressionCandidates = rootCandidates.filter(\n      (c) => c.jsonbExtraction,\n    );\n    const btreeCandidates = rootCandidates.filter(\n      (c) => !c.jsonbOperator && !c.jsonbExtraction,\n    );\n    const ginCandidates = rootCandidates.filter((c) => c.jsonbOperator);\n\n    const nextStage: IndexToCreate[] = [];\n\n    const permutedIndexes =\n      this.groupPotentialIndexColumnsByTable(btreeCandidates);\n    for (const permutation of permutedIndexes.values()) {\n      const { table: rawTable, schema: rawSchema, columns } = permutation;\n      const permutations = permutationsWithDescendingLength(columns);\n      for (const columns of permutations) {\n        // TODO: accept PgIdentifier values instead\n        // required refactoring `PermutedIndexCandidate`\n        const schema = PgIdentifier.fromString(rawSchema);\n        const table = PgIdentifier.fromString(rawTable);\n        const existingIndex = this.indexAlreadyExists(\n          table.toString(),\n          columns,\n        );\n        if (existingIndex) {\n          continue;\n        }\n        const indexName = this.indexName();\n\n        const definition = this.toDefinition({ table, schema, columns }).raw;\n\n        nextStage.push({\n          name: indexName,\n          schema: schema.toString(),\n          table: table.toString(),\n          columns,\n          definition,\n        });\n      }\n    }\n\n    const ginGroups = this.groupGinCandidatesByColumn(ginCandidates);\n    for (const group of ginGroups.values()) {\n      const { schema: rawSchema, table: rawTable, column, operators } = group;\n      const schema = PgIdentifier.fromString(rawSchema);\n      const table = PgIdentifier.fromString(rawTable);\n\n      // jsonb_path_ops is smaller/faster but only supports @>.\n      // All other operators (key-existence and jsonpath) need the full jsonb_ops.\n      const needsFullOps = operators.some((op) => op !== \"@>\");\n      const opclass = needsFullOps ? undefined : \"jsonb_path_ops\";\n\n      const existingGin = this.ginIndexAlreadyExists(table.toString(), column);\n      if (existingGin) {\n        continue;\n      }\n\n      const indexName = this.indexName();\n      const candidate: RootIndexCandidate = {\n        schema: rawSchema,\n        table: rawTable,\n        column,\n      };\n      const definition = this.toGinDefinition({\n        table,\n        schema,\n        column: PgIdentifier.fromString(column),\n        opclass,\n      });\n\n      nextStage.push({\n        name: indexName,\n        schema: schema.toString(),\n        table: table.toString(),\n        columns: [candidate],\n        definition,\n        indexMethod: \"gin\",\n        opclass,\n      });\n    }\n\n    // Expression B-tree indexes for JSONB path extraction (-> / ->>)\n    const seenExpressions = new Set<string>();\n    for (const candidate of expressionCandidates) {\n      const expression = candidate.jsonbExtraction!;\n      const key = `${candidate.schema}.${candidate.table}.${expression}`;\n      if (seenExpressions.has(key)) continue;\n      seenExpressions.add(key);\n\n      const schema = PgIdentifier.fromString(candidate.schema);\n      const table = PgIdentifier.fromString(candidate.table);\n      const indexName = this.indexName();\n      const definition = this.toExpressionDefinition({\n        table,\n        schema,\n        expression,\n      });\n\n      nextStage.push({\n        name: indexName,\n        schema: schema.toString(),\n        table: table.toString(),\n        columns: [candidate],\n        definition,\n      });\n    }\n\n    return nextStage;\n  }\n\n  private toDefinition({\n    schema,\n    table,\n    columns,\n  }: {\n    schema: PgIdentifier;\n    table: PgIdentifier;\n    columns: RootIndexCandidate[];\n  }) {\n    const make = (col: Color, order: Color, _where: Color, _keyword: Color) => {\n      // TODO: refactor all of this class to accept PgIdentifiers\n      let fullyQualifiedTable: PgIdentifier;\n\n      if (schema.toString() === \"public\") {\n        fullyQualifiedTable = table;\n      } else {\n        fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n      }\n      const baseColumn = `${fullyQualifiedTable}(${columns\n        .map((c) => {\n          const column = PgIdentifier.fromString(c.column);\n          const direction = c.sort && this.sortDirection(c.sort);\n          const nulls = c.sort && this.nullsOrder(c.sort);\n          let sort = col(column.toString());\n          if (direction) {\n            sort += ` ${order(direction)}`;\n          }\n          if (nulls) {\n            sort += ` ${order(nulls)}`;\n          }\n          return sort;\n        })\n        .join(\", \")})`;\n      // TODO: add support for generating partial indexes\n      // if (clauses.length > 0) {\n      //   return `${baseColumn} ${where(\"where\")} ${clauses.join(\" and \")}`;\n      // }\n      return baseColumn;\n    };\n    const id: Color = (a) => a;\n    const raw = make(id, id, id, id);\n    const colored = make(green, yellow, magenta, blue);\n    return { raw, colored };\n  }\n\n  private toGinDefinition({\n    schema,\n    table,\n    column,\n    opclass,\n  }: {\n    schema: PgIdentifier;\n    table: PgIdentifier;\n    column: PgIdentifier;\n    opclass?: string;\n  }): string {\n    let fullyQualifiedTable: PgIdentifier;\n    if (schema.toString() === \"public\") {\n      fullyQualifiedTable = table;\n    } else {\n      fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n    }\n    const opclassSuffix = opclass ? ` ${opclass}` : \"\";\n    return `${fullyQualifiedTable} using gin (${column}${opclassSuffix})`;\n  }\n\n  private toExpressionDefinition({\n    schema,\n    table,\n    expression,\n  }: {\n    schema: PgIdentifier;\n    table: PgIdentifier;\n    expression: string;\n  }): string {\n    let fullyQualifiedTable: PgIdentifier;\n    if (schema.toString() === \"public\") {\n      fullyQualifiedTable = table;\n    } else {\n      fullyQualifiedTable = PgIdentifier.fromParts(schema, table);\n    }\n    return `${fullyQualifiedTable}((${expression}))`;\n  }\n\n  private groupGinCandidatesByColumn(candidates: RootIndexCandidate[]) {\n    const groups = new Map<\n      string,\n      {\n        schema: string;\n        table: string;\n        column: string;\n        operators: JsonbOperator[];\n      }\n    >();\n    for (const c of candidates) {\n      if (!c.jsonbOperator) continue;\n      const key = `${c.schema}.${c.table}.${c.column}`;\n      const existing = groups.get(key);\n      if (existing) {\n        if (!existing.operators.includes(c.jsonbOperator)) {\n          existing.operators.push(c.jsonbOperator);\n        }\n      } else {\n        groups.set(key, {\n          schema: c.schema,\n          table: c.table,\n          column: c.column,\n          operators: [c.jsonbOperator],\n        });\n      }\n    }\n    return groups;\n  }\n\n  private ginIndexAlreadyExists(\n    table: string,\n    column: string,\n  ): FullSchemaIndex | undefined {\n    return this.existingIndexes.find(\n      (index) =>\n        index.indexType === \"gin\" &&\n        index.tableName.toString() === table &&\n        index.keyColumns.some((c) => c.name.toString() === column),\n    );\n  }\n\n  /**\n   * Drop indexes that can be dropped. Ignore the ones that can't\n   */\n  private async dropExistingIndexes(tx: PostgresTransaction) {\n    for (const index of this.existingIndexes) {\n      if (!isIndexProbablyDroppable(index)) {\n        continue;\n      }\n      const indexName = PgIdentifier.fromParts(\n        index.schemaName,\n        index.indexName,\n      );\n      await dropIndex(tx, indexName);\n    }\n  }\n\n  private whereClause(c: RootIndexCandidate, col: Color, keyword: Color) {\n    if (!c.where) {\n      return \"\";\n    }\n    if (c.where.nulltest === \"IS_NULL\") {\n      return `${col(`\"${c.column}\"`)} is ${keyword(\"null\")}`;\n    }\n    if (c.where.nulltest === \"IS_NOT_NULL\") {\n      return `${col(`\"${c.column}\"`)} is not ${keyword(\"null\")}`;\n    }\n    return \"\";\n  }\n\n  private nullsOrder(s: SortContext) {\n    if (!s.nulls) {\n      return \"\";\n    }\n    switch (s.nulls) {\n      case \"SORTBY_NULLS_FIRST\":\n        return \"nulls first\";\n      case \"SORTBY_NULLS_LAST\":\n        return \"nulls last\";\n      case \"SORTBY_NULLS_DEFAULT\":\n      default:\n        return \"\";\n    }\n  }\n\n  private sortDirection(s: SortContext) {\n    if (!s.dir) {\n      return \"\";\n    }\n    switch (s.dir) {\n      case \"SORTBY_DESC\":\n        return \"desc\";\n      case \"SORTBY_ASC\":\n        return \"asc\";\n      case \"SORTBY_DEFAULT\":\n      // god help us if we ever run into this\n      case \"SORTBY_USING\":\n      default:\n        return \"\";\n    }\n  }\n\n  async testQueryWithStats(\n    builder: PostgresQueryBuilder,\n    f?: (tx: PostgresTransaction) => Promise<void>,\n    options?: { params?: unknown[]; genericPlan?: boolean },\n  ): Promise<{ Plan: PostgresExplainStage }> {\n    try {\n      await this.db.transaction(async (tx) => {\n        await f?.(tx);\n        await this.statistics.restoreStats(tx);\n        const flags = [\"format json\"];\n        if (options && !options.genericPlan) {\n          flags.push(\"analyze\");\n          if (this.config.trace) {\n            // trace can only be used alongside analyze\n            // since it depends on the results of the query execution\n            flags.push(\"trace\");\n          }\n        } else {\n          flags.push(\"generic_plan\");\n        }\n        const { commands, query } = builder\n          .introspect()\n          .explain(flags)\n          .buildParts();\n        // this is done in a separate step to prevent sending multiple commands when using parameters\n        await tx.exec(commands);\n        const result = await tx.exec<PostgresExplainResult>(\n          query,\n          options?.params,\n        );\n        const explain = result[0][\"QUERY PLAN\"][0];\n        throw new RollbackError(explain);\n      });\n    } catch (error) {\n      if (error instanceof RollbackError) {\n        return error.value;\n      }\n      throw error;\n    }\n    throw new Error(\"Unreachable\");\n  }\n\n  private groupPotentialIndexColumnsByTable(indexes: RootIndexCandidate[]) {\n    const tableColumns: Map<\n      string,\n      { schema: string; table: string; columns: RootIndexCandidate[] }\n    > = new Map();\n    for (const index of indexes) {\n      const existing = tableColumns.get(`${index.schema}.${index.table}`);\n      if (existing) {\n        existing.columns.push(index);\n      } else {\n        tableColumns.set(`${index.schema}.${index.table}`, {\n          table: index.table,\n          schema: index.schema,\n          columns: [index],\n        });\n      }\n    }\n    return tableColumns;\n  }\n\n  private findUsedIndexes(explain: Record<string, any>) {\n    const newIndexes: Set<string> = new Set();\n    const existingIndexes: Set<string> = new Set();\n    const prefix = IndexOptimizer.prefix;\n    walkExplain(explain, (stage) => {\n      const indexName = stage[\"Index Name\"];\n      if (indexName) {\n        // Check for prefix at start or embedded (for hypertable chunk indexes like _hyper_1_1_chunk___qd_xxx)\n        if (indexName.startsWith(prefix)) {\n          newIndexes.add(indexName);\n        } else if (indexName.includes(prefix)) {\n          // Extract the actual index name from chunk-prefixed names (e.g., _hyper_1_1_chunk___qd_xxx -> __qd_xxx)\n          const actualName = indexName.substring(indexName.indexOf(prefix));\n          newIndexes.add(actualName);\n        } else {\n          existingIndexes.add(indexName);\n        }\n      }\n    });\n    return {\n      newIndexes,\n      existingIndexes,\n    };\n  }\n\n  private replaceUsedIndexesWithDefinition(\n    explain: Record<string, any>,\n    triedIndexes: Map<string, IndexRecommendation>,\n  ) {\n    walkExplain(explain, (stage) => {\n      const indexName = stage[\"Index Name\"];\n      if (typeof indexName === \"string\") {\n        const recommendation = triedIndexes.get(indexName);\n        if (recommendation) {\n          stage[\"Index Name\"] = recommendation.definition;\n        }\n      }\n    });\n  }\n}\n\nfunction walkExplain(explain: Record<string, any>, f: (stage: any) => void) {\n  function go(plan: any) {\n    f(plan);\n    if (plan.Plans) {\n      for (const p of plan.Plans) {\n        go(p);\n      }\n    }\n  }\n  go(explain);\n}\n\nexport type OptimizeResult =\n  | {\n      kind: \"ok\";\n      baseExplainPlan: PostgresExplainStage;\n      baseCost: number;\n      finalCost: number;\n      newIndexes: Set<string>;\n      existingIndexes: Set<string>;\n      triedIndexes: Map<string, IndexRecommendation>;\n      explainPlan: PostgresExplainStage;\n    }\n  | {\n      kind: \"zero_cost_plan\";\n      explainPlan: PostgresExplainStage;\n    };\n\nclass RollbackError<T> {\n  constructor(public readonly value?: T) {}\n}\n\nexport type RootIndexCandidate = {\n  schema: string;\n  table: string;\n  column: string;\n  sort?: SortContext;\n  where?: { nulltest?: NullTestType };\n  jsonbOperator?: JsonbOperator;\n  jsonbExtraction?: string;\n};\n\nexport type PermutedIndexCandidate = {\n  schema: string;\n  table: string;\n  columns: RootIndexCandidate[];\n  // TODO: functional indexes\n  where?: string;\n  indexMethod?: \"btree\" | \"gin\";\n  opclass?: string;\n};\n\nexport const PROCEED = Symbol(\"PROCEED\");\nexport const SKIP = Symbol(\"SKIP\");\n"],"mappings":"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"}