// Logical AST produced by the subset parser. The binder lowers this
// to column-index based physical expressions (see binder.mbt).

///|
pub(all) enum AggFn {
  Sum
  Avg
  Count // count(expr): counts non-null inputs
  CountStar // count(*): counts rows
  CountDistinct
  SdSamp // stddev / stddev_samp
  SdPop // stddev_pop
  StringAgg // string_agg(value, sep): sep folded to a literal at parse
  VarSamp // variance / var_samp
  VarPop // var_pop
  Median
  Min
  Max
} derive(Eq, Debug)

///|
pub extend AggFn with Eq::{equal, not_equal}

///|
pub extend AggFn with @debug.Debug::{to_repr}

///|
pub(all) enum ExtractField {
  Year
  Month
  Day
} derive(Eq, Debug)

///|
pub extend ExtractField with Eq::{equal, not_equal}

///|
pub extend ExtractField with @debug.Debug::{to_repr}

///|
/// INTERVAL literal unit (`interval '90' day`); only date arithmetic
/// consumes it.
pub(all) enum IntervalUnit {
  Days
  Months
  Years
} derive(Eq, Debug)

///|
pub extend IntervalUnit with Eq::{equal, not_equal}

///|
pub extend IntervalUnit with @debug.Debug::{to_repr}

///|
pub(all) enum LExpr {
  Col(String)
  ColQ(String, String) // qualifier.name (table name or alias)
  Lit(@types.Scalar)
  Arith(@types.ArithOp, LExpr, LExpr)
  Cmp(@types.CmpOp, LExpr, LExpr)
  And(LExpr, LExpr)
  Or(LExpr, LExpr)
  Not(LExpr)
  Between(LExpr, LExpr, LExpr) // value, low, high (both inclusive)
  In(LExpr, Array[LExpr]) // binder desugars into an OR-chain of equalities
  NotIn(LExpr, Array[LExpr]) // desugars into NOT of the In chain
  Like(LExpr, LExpr) // pattern must fold to a string constant
  NotLike(LExpr, LExpr) // pattern must fold to a string constant
  IsNull(LExpr, Bool) // IS NULL (false) / IS NOT NULL (true)
  Star // bare `*` as a whole select item: expands to every FROM column
  Extract(ExtractField, LExpr) // EXTRACT(year|month|day FROM date)
  Case(Array[(LExpr, LExpr)], LExpr?) // searched CASE: (when, then)*, else?
  Agg(AggFn, LExpr?) // None only for count(*)
  ScalarSub(Select) // non-correlated scalar subquery, folded to a constant
  InSelect(LExpr, Select, Bool) // value IN / NOT IN (SELECT ...), non-correlated
  Exists(Select, Bool) // [NOT] EXISTS (SELECT ...), non-correlated, folded at bind
  Func(String, Array[LExpr]) // scalar function call (name lowercased)
  Cast(LExpr, @types.DataType) // CAST(expr AS type)
  IntervalLit(Int64, IntervalUnit) // INTERVAL  day|month|year
} derive(Eq, Debug)

///|
pub extend LExpr with Eq::{equal, not_equal}

///|
pub extend LExpr with @debug.Debug::{to_repr}

///|
pub(all) struct SelectItem {
  expr : LExpr
  label : String?
} derive(Eq, Debug)

///|
pub extend SelectItem with Eq::{equal, not_equal}

///|
pub extend SelectItem with @debug.Debug::{to_repr}

///|
pub(all) struct OrderItem {
  key : LExpr
  desc : Bool
  nulls_first : Bool? // None = engine default (NULLS LAST)
} derive(Eq, Debug)

///|
pub extend OrderItem with Eq::{equal, not_equal}

///|
pub extend OrderItem with @debug.Debug::{to_repr}

///|
/// How a FROM item attaches to the accumulated rows before it. The
/// first item's kind is meaningless (Inner, ignored).
pub(all) enum JoinKind {
  Inner // comma lists and JOIN ... ON
  Left // LEFT [OUTER] JOIN ... ON
  Right // RIGHT [OUTER] JOIN ... ON: preserves the joining table
  Full // FULL [OUTER] JOIN ... ON: preserves both sides
  Semi // synthetic: correlated EXISTS decorrelated by the binder
  Anti // synthetic: correlated NOT IN decorrelated: a NULL probe key is UNKNOWN and drops the row
  AntiLoose // synthetic: correlated NOT EXISTS: only a match drops the row
  Cross // CROSS JOIN
} derive(Eq, Debug)

///|
pub extend JoinKind with Eq::{equal, not_equal}

///|
pub extend JoinKind with @debug.Debug::{to_repr}

///|
pub(all) enum TableRef {
  Named(String) // catalog table
  Sub(Select) // derived table: FROM ( SELECT ... ) AS alias
} derive(Eq, Debug)

///|
pub extend TableRef with Eq::{equal, not_equal}

///|
pub extend TableRef with @debug.Debug::{to_repr}

///|
pub(all) struct FromItem {
  table : TableRef
  tbl_alias : String? // `tbl name` / `tbl AS name`; column qualifier
  col_aliases : Array[String]? // derived-table column list: AS t (a, b)
  join : JoinKind
  on : LExpr? // ON condition for explicit joins
} derive(Eq, Debug)

///|
pub extend FromItem with Eq::{equal, not_equal}

///|
pub extend FromItem with @debug.Debug::{to_repr}

///|
/// The function of one window definition. Agg carries the aggregate
/// name with its input (None only for count(*)); the parser only lets
/// aggregates and the three ranking functions take OVER.
pub(all) enum WinFn {
  RowNumber
  Rank
  DenseRank
  Agg(AggFn, LExpr?)
} derive(Eq, Debug)

///|
pub extend WinFn with Eq::{equal, not_equal}

///|
pub extend WinFn with @debug.Debug::{to_repr}

///|
/// One `fn(...) OVER (PARTITION BY ... ORDER BY ...)` definition in the
/// select list. The select item itself carries an index marker; the
/// binder lowers the spec against the item's binding space.
pub(all) struct WindowSpec {
  fn_ : WinFn
  partition : Array[LExpr]
  order : Array[(LExpr, Bool, Bool?)] // expr, desc, nulls_first
} derive(Eq, Debug)

///|
pub extend WindowSpec with Eq::{equal, not_equal}

///|
pub extend WindowSpec with @debug.Debug::{to_repr}

///|
/// W2 subset: the full select pipeline over a FROM chain joined in
/// written order (left-deep; comma lists behave as INNER joins with the
/// join conditions supplied by WHERE).
pub(all) struct Select {
  from : Array[FromItem]
  filter : LExpr?
  group_by : Array[LExpr]
  having : LExpr?
  items : Array[SelectItem]
  windows : Array[WindowSpec] // select-list window definitions, index = marker index
  order_by : Array[OrderItem]
  limit : Int32?
  offset : Int32? // rows skipped before LIMIT applies
  distinct : Bool // SELECT DISTINCT: dedupe the projected rows
  setop : (SetOpKind, Bool, Select)? // setop right; bool = ALL
} derive(Eq, Debug)

///|
/// The set-operation kind joining a select into the chain before it.
pub(all) enum SetOpKind {
  Union
  Intersect
  Except
} derive(Eq, Debug)

///|
pub extend SetOpKind with Eq::{equal, not_equal}

///|
pub extend SetOpKind with @debug.Debug::{to_repr}

///|
pub extend Select with Eq::{equal, not_equal}

///|
pub extend Select with @debug.Debug::{to_repr}