{"version":3,"file":"index-coverage.cjs","names":[],"sources":["../../src/action-plan/index-coverage.ts"],"sourcesContent":["import type { IndexRecommendation } from \"../query.js\";\n\n/** A unique index recommendation plus how it scored across the queries it helps. */\nexport interface AggregatedIndexRecommendation {\n  index: IndexRecommendation;\n  affectedQueryCount: number;\n  averageCostReduction: number;\n  bestCostReduction: number;\n  affectedQueryHashes: string[];\n}\n\n/**\n * Check if indexA covers indexB (A is a superset that includes B as a prefix).\n *\n * Rules:\n * - Same schema and table\n * - A's columns start with B's columns (B is a prefix of A)\n * - Sort directions must match for prefix columns\n * - WHERE clauses must be compatible\n *\n * Examples:\n * - (a, b, c) covers (a, b) and (a)\n * - (a DESC, b) covers (a DESC) but NOT (a ASC)\n */\nexport function indexCovers(\n  a: IndexRecommendation,\n  b: IndexRecommendation,\n): boolean {\n  // Must be same schema and table\n  if (a.schema !== b.schema || a.table !== b.table) return false;\n\n  // A must have more columns than B (not equal - that would be the same index)\n  if (a.columns.length <= b.columns.length) return false;\n\n  // B's columns must be a prefix of A's columns with matching sort\n  for (let i = 0; i < b.columns.length; i++) {\n    const colA = a.columns[i];\n    const colB = b.columns[i];\n\n    if (colA.column !== colB.column) return false;\n\n    // Sort directions must match\n    // Normalize: treat undefined/null as equivalent to default (ASC)\n    const sortA = normalizeSort(colA.sort);\n    const sortB = normalizeSort(colB.sort);\n    if (sortA !== sortB) return false;\n  }\n\n  // WHERE clause check: if B has a WHERE, A must have the same WHERE\n  if (b.where && a.where !== b.where) return false;\n\n  return true;\n}\n\nfunction normalizeSort(sort: unknown): string {\n  if (!sort) return \"ASC\";\n  if (typeof sort === \"string\") return sort.toUpperCase();\n  if (typeof sort === \"object\" && sort !== null && \"dir\" in sort) {\n    const dir = (sort as { dir?: string }).dir;\n    if (dir === \"SORTBY_DESC\") return \"DESC\";\n    return \"ASC\";\n  }\n  return \"ASC\";\n}\n\nexport interface IndexGroup {\n  /** The covering index (largest/most comprehensive) */\n  primary: AggregatedIndexRecommendation;\n  /** Indexes that are covered by the primary (prefixes of primary) */\n  covered: AggregatedIndexRecommendation[];\n}\n\n/**\n * Group indexes by coverage relationships.\n *\n * Returns groups where each group has a \"primary\" index that covers\n * zero or more \"covered\" indexes. Indexes that don't cover or aren't\n * covered by any other index become their own single-item group.\n */\nexport function groupIndexesByCoverage(\n  indexes: AggregatedIndexRecommendation[],\n): IndexGroup[] {\n  if (indexes.length === 0) return [];\n\n  // Track which indexes have been assigned to a group as \"covered\"\n  const coveredSet = new Set<string>();\n  const groups: IndexGroup[] = [];\n\n  // Sort by column count descending so we process larger indexes first\n  const sorted = [...indexes].sort(\n    (a, b) => b.index.columns.length - a.index.columns.length,\n  );\n\n  for (const idx of sorted) {\n    const key = idx.index.definition;\n\n    // Skip if already covered by another index\n    if (coveredSet.has(key)) continue;\n\n    // Find all indexes this one covers\n    const covered: AggregatedIndexRecommendation[] = [];\n    for (const other of sorted) {\n      if (other.index.definition === key) continue;\n      if (coveredSet.has(other.index.definition)) continue;\n\n      if (indexCovers(idx.index, other.index)) {\n        covered.push(other);\n        coveredSet.add(other.index.definition);\n      }\n    }\n\n    groups.push({\n      primary: idx,\n      covered,\n    });\n  }\n\n  return groups;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwBA,SAAgB,YACd,GACA,GACS;AAET,KAAI,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAO,QAAO;AAGzD,KAAI,EAAE,QAAQ,UAAU,EAAE,QAAQ,OAAQ,QAAO;AAGjD,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,QAAQ,KAAK;EACzC,MAAM,OAAO,EAAE,QAAQ;EACvB,MAAM,OAAO,EAAE,QAAQ;AAEvB,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAMxC,MAFc,cAAc,KAAK,KAExB,KADK,cAAc,KAAK,KACd,CAAE,QAAO;;AAI9B,KAAI,EAAE,SAAS,EAAE,UAAU,EAAE,MAAO,QAAO;AAE3C,QAAO;;AAGT,SAAS,cAAc,MAAuB;AAC5C,KAAI,CAAC,KAAM,QAAO;AAClB,KAAI,OAAO,SAAS,SAAU,QAAO,KAAK,aAAa;AACvD,KAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,SAAS,MAAM;AAE9D,MADa,KAA0B,QAC3B,cAAe,QAAO;AAClC,SAAO;;AAET,QAAO;;;;;;;;;AAiBT,SAAgB,uBACd,SACc;AACd,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAGnC,MAAM,6BAAa,IAAI,KAAa;CACpC,MAAM,SAAuB,EAAE;CAG/B,MAAM,SAAS,CAAC,GAAG,QAAQ,CAAC,MACzB,GAAG,MAAM,EAAE,MAAM,QAAQ,SAAS,EAAE,MAAM,QAAQ,OACpD;AAED,MAAK,MAAM,OAAO,QAAQ;EACxB,MAAM,MAAM,IAAI,MAAM;AAGtB,MAAI,WAAW,IAAI,IAAI,CAAE;EAGzB,MAAM,UAA2C,EAAE;AACnD,OAAK,MAAM,SAAS,QAAQ;AAC1B,OAAI,MAAM,MAAM,eAAe,IAAK;AACpC,OAAI,WAAW,IAAI,MAAM,MAAM,WAAW,CAAE;AAE5C,OAAI,YAAY,IAAI,OAAO,MAAM,MAAM,EAAE;AACvC,YAAQ,KAAK,MAAM;AACnB,eAAW,IAAI,MAAM,MAAM,WAAW;;;AAI1C,SAAO,KAAK;GACV,SAAS;GACT;GACD,CAAC;;AAGJ,QAAO"}