| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- package executor
- import (
- "fmt"
- "strings"
- "github.com/danfragoso/pizzasql-next/pkg/parser"
- "github.com/danfragoso/pizzasql-next/pkg/storage"
- )
- // maxRecursiveCTEIterations bounds fixpoint iteration so a recursive CTE with a
- // cycle cannot spin forever.
- const maxRecursiveCTEIterations = 1000
- // cteTable is a materialized common table expression.
- type cteTable struct {
- columns []string
- rows []storage.Row
- }
- // cteTableFor returns the materialized CTE named name, if any.
- func (e *Executor) cteTableFor(name string) (*cteTable, bool) {
- if e.cteTables == nil {
- return nil, false
- }
- t, ok := e.cteTables[strings.ToLower(name)]
- return t, ok
- }
- // executeWith materializes every CTE in order, then runs the main query with the
- // CTE tables available to table resolution. Nested WITH clauses keep the outer
- // tables visible.
- func (e *Executor) executeWith(stmt *parser.SelectStmt) (*Result, error) {
- prev := e.cteTables
- next := make(map[string]*cteTable, len(stmt.With)+len(prev))
- for k, v := range prev {
- next[k] = v
- }
- e.cteTables = next
- defer func() { e.cteTables = prev }()
- for _, cte := range stmt.With {
- if err := e.materializeCTE(cte); err != nil {
- return nil, err
- }
- }
- main := *stmt
- main.With = nil
- return e.executeSelect(&main)
- }
- func (e *Executor) materializeCTE(cte *parser.CTE) error {
- // WITH RECURSIVE marks the whole clause; only a compound CTE with a
- // self-referencing leg is actually recursive. A plain SELECT is just a CTE.
- compound := cte.Query.Compound
- if !cte.Recursive || compound == nil {
- res, err := e.executeSelect(cte.Query)
- if err != nil {
- return err
- }
- e.registerCTEResult(cte.Name, cte.Columns, res)
- return nil
- }
- anchor, err := e.executeSelect(compound.Left)
- if err != nil {
- return err
- }
- cols := cte.Columns
- if len(cols) == 0 {
- cols = anchor.Columns
- }
- accumulated := resultToCTERows(anchor, cols)
- working := accumulated
- distinct := compound.Op != parser.SetOpUnionAll
- seen := make(map[string]bool)
- if distinct {
- for _, row := range accumulated {
- seen[cteRowKey(row, cols)] = true
- }
- }
- for i := 0; i < maxRecursiveCTEIterations; i++ {
- e.cteTables[strings.ToLower(cte.Name)] = &cteTable{columns: cols, rows: working}
- recursive, err := e.executeSelect(compound.Right)
- if err != nil {
- return err
- }
- fresh := make([]storage.Row, 0)
- for _, row := range resultToCTERows(recursive, cols) {
- if distinct {
- key := cteRowKey(row, cols)
- if seen[key] {
- continue
- }
- seen[key] = true
- }
- fresh = append(fresh, row)
- }
- if len(fresh) == 0 {
- break
- }
- accumulated = append(accumulated, fresh...)
- working = fresh
- }
- e.cteTables[strings.ToLower(cte.Name)] = &cteTable{columns: cols, rows: accumulated}
- return nil
- }
- // registerCTEResult stores a query result as a CTE. Declared column names win;
- // otherwise the result's own column names are used.
- func (e *Executor) registerCTEResult(name string, declared []string, res *Result) {
- cols := declared
- if len(cols) == 0 {
- cols = res.Columns
- }
- e.cteTables[strings.ToLower(name)] = &cteTable{columns: cols, rows: resultToCTERows(res, cols)}
- }
- // resultToCTERows maps each result row to a storage.Row keyed by cols
- // positionally, which renames a recursive term's columns to the anchor's names.
- func resultToCTERows(res *Result, cols []string) []storage.Row {
- rows := make([]storage.Row, 0, len(res.Rows))
- for _, values := range res.Rows {
- row := make(storage.Row, len(cols))
- for i, name := range cols {
- if i < len(values) {
- row[name] = values[i]
- }
- }
- rows = append(rows, row)
- }
- return rows
- }
- // cteRowsToValues converts a materialized CTE to positional row values.
- func cteRowsToValues(cte *cteTable) [][]interface{} {
- values := make([][]interface{}, len(cte.rows))
- for i, row := range cte.rows {
- vals := make([]interface{}, len(cte.columns))
- for j, col := range cte.columns {
- vals[j] = row[col]
- }
- values[i] = vals
- }
- return values
- }
- // cteTableExists reports whether name is a materialized CTE.
- func (e *Executor) cteTableExists(name string) bool {
- _, ok := e.cteTableFor(name)
- return ok
- }
- // materializeJoinSubquery runs a derived table used on the right side of a JOIN
- // and returns its rows and schema.
- func (e *Executor) materializeJoinSubquery(ref *parser.TableRef) ([]storage.Row, *storage.Schema, error) {
- res, err := e.executeSelect(ref.Subquery)
- if err != nil {
- return nil, nil, err
- }
- rows := make([]storage.Row, 0, len(res.Rows))
- for _, values := range res.Rows {
- row := make(storage.Row, len(res.Columns))
- for i, col := range res.Columns {
- if i < len(values) {
- row[col] = values[i]
- }
- }
- rows = append(rows, row)
- }
- return rows, schemaFromColumns(res.Columns), nil
- }
- // cloneRows returns a shallow copy of each row so callers cannot mutate a
- // materialized CTE's stored rows.
- func cloneRows(rows []storage.Row) []storage.Row {
- out := make([]storage.Row, len(rows))
- for i, r := range rows {
- c := make(storage.Row, len(r))
- for k, v := range r {
- c[k] = v
- }
- out[i] = c
- }
- return out
- }
- func cteRowKey(row storage.Row, cols []string) string {
- var b strings.Builder
- for _, c := range cols {
- fmt.Fprintf(&b, "%v\x00", row[c])
- }
- return b.String()
- }
|