SQL frontend over the catalog engine
0

Configure Feed

Select the types of activity you want to include in your feed.

ocaml-sql / lib / query.ml
100 kB 2487 lines
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6open Ast 7 8(* SQLite's value-level semantics (comparison order, collation, text conversion, 9 aggregate folds) live in {!Value_ops}, shared with the register-VM paths; 10 open it so the tree-walker uses the same folds and orders by the same 11 rule. *) 12open Value_ops 13 14(* The built-in scalar / table-valued functions live in {!Func} (also shared 15 with the VM); open it so the tree-walker dispatches them through the same 16 value-level implementations. *) 17open Func 18 19(* The value-level binary operators (comparison with affinity, AND/OR/LIKE/GLOB/ 20 JSON, IN-list) live in {!Value_op}; open it so the tree-walker evaluates an 21 operator through the same shared implementation as the VM. *) 22open Value_op 23 24type catalog = { 25 columns : string -> string list; 26 rows : string -> Catalog.Value.t list list; 27 rows_rowid : string -> (int64 option * Catalog.Value.t list) list; 28 (** Each row paired with its rowid, or [None] for a row source that has no 29 rowid (a view, a derived table, [VALUES]). Lets the executor resolve 30 the implicit [rowid]/[oid]/[_rowid_] column of a full table scan. *) 31 index_scan : 32 string -> 33 (string * Catalog.Value.t) list -> 34 (string * Catalog.Value.t list list) option; 35 (** [index_scan table eqs] returns [Some (index_name, rows)] if an index 36 on [table] serves the column-equality bindings [eqs] -- the rows whose 37 indexed columns match, by which the caller narrows a scan -- or [None] 38 to fall back to a full {!rows} scan. *) 39 index_for : string -> string list -> string option; 40 (** [index_for table cols] is the name of a unique index on [table] all of 41 whose columns are among [cols], if any -- the structural counterpart 42 of {!index_scan} (no values), used to explain the plan. *) 43 collation : string -> string -> string option; 44 (** [collation table col] is [col]'s declared [COLLATE] sequence 45 (uppercase) on [table], or [None] for the default binary / a derived 46 source. *) 47} 48 49let of_catalog cat = 50 { 51 columns = Catalog.Spi.columns cat; 52 rows = Catalog.Spi.rows cat; 53 rows_rowid = Catalog.Spi.rows_rowid cat; 54 index_scan = Catalog.Spi.index_scan cat; 55 index_for = Catalog.Spi.index_for cat; 56 collation = Catalog.Spi.collation cat; 57 } 58 59(* The runner used for every SELECT [eval] reaches: a scalar / [IN] / [EXISTS] 60 subquery, a FROM-subquery, a CTE body, and the recursive-CTE seed / arms, 61 given the enclosing-row [cat], the outer scope, the subquery, and the params. 62 Held in a private ref behind {!set_cat_subquery_runner}: the catalog-VM 63 engine that fulfils it ({!Engine.run_select}) sits above this module, so it 64 is injected, not called directly. {!Engine} installs it on load; a consumer 65 that reaches a SELECT without the engine linked gets a clear error. *) 66let cat_subquery_runner : 67 (catalog -> 68 (string * (string * Catalog.Value.t) list) list -> 69 Ast.select -> 70 Catalog.Value.t array -> 71 Catalog.Value.t list list) 72 ref = 73 ref (fun _ _ _ _ -> 74 failwith "Query: no SELECT runner installed (link Sql.Engine)") 75 76let set_cat_subquery_runner f = cat_subquery_runner := f 77 78(* ── Parameter numbering ─────────────────────────────────────── *) 79 80(* Renumber [?] placeholders left-to-right. [next] is a monotonic counter; the 81 explicit [let] bindings force left-to-right traversal so placeholders are 82 numbered in source order regardless of OCaml's argument evaluation. *) 83let rec renum_expr next = function 84 | Param _ -> Param (next ()) 85 | (Lit _ | Col _ | Star | Qualified_star _) as e -> e 86 | Unop_not e -> Unop_not (renum_expr next e) 87 | Binary (op, a, b) -> 88 let a = renum_expr next a in 89 let b = renum_expr next b in 90 Binary (op, a, b) 91 | Is_null e -> Is_null (renum_expr next e) 92 | Is_not_null e -> Is_not_null (renum_expr next e) 93 | Is (a, b) -> 94 let a = renum_expr next a in 95 Is (a, renum_expr next b) 96 | In_list (e, l) -> 97 let e = renum_expr next e in 98 In_list (e, renum_exprs next l) 99 | In_select (e, s) -> 100 let e = renum_expr next e in 101 In_select (e, renum_select next s) 102 | Scalar_select s -> Scalar_select (renum_select next s) 103 | Exists s -> Exists (renum_select next s) 104 | Func (n, args) -> Func (n, renum_exprs next args) 105 | Distinct_agg (n, e) -> Distinct_agg (n, renum_expr next e) 106 | Filter (a, p) -> Filter (renum_expr next a, renum_expr next p) 107 | Ordered_agg (a, ob) -> 108 Ordered_agg 109 (renum_expr next a, List.map (fun (e, d) -> (renum_expr next e, d)) ob) 110 | Window (a, w) -> 111 let bound = function 112 | (Unbounded_preceding | Current_row | Unbounded_following) as b -> b 113 | Preceding e -> Preceding (renum_expr next e) 114 | Following e -> Following (renum_expr next e) 115 in 116 Window 117 ( renum_expr next a, 118 { 119 partition = List.map (renum_expr next) w.partition; 120 order = List.map (fun (e, d) -> (renum_expr next e, d)) w.order; 121 frame = 122 Option.map 123 (fun (f : frame) -> 124 { f with start = bound f.start; end_ = bound f.end_ }) 125 w.frame; 126 } ) 127 | Cast (e, ty) -> Cast (renum_expr next e, ty) 128 | Collate (e, c) -> Collate (renum_expr next e, c) 129 | Case (base, branches, els) -> 130 let base = Option.map (renum_expr next) base in 131 let branches = 132 List.map 133 (fun (c, r) -> 134 let c = renum_expr next c in 135 (c, renum_expr next r)) 136 branches 137 in 138 let els = Option.map (renum_expr next) els in 139 Case (base, branches, els) 140 141and renum_exprs next = function 142 | [] -> [] 143 | e :: rest -> 144 let e = renum_expr next e in 145 e :: renum_exprs next rest 146 147(* Source order: SELECT cols, FROM (subquery), JOINs, WHERE, ORDER BY, LIMIT. *) 148and renum_select next (s : select) = 149 let ctes = 150 List.map 151 (fun (c : cte) -> { c with query = renum_select next c.query }) 152 s.ctes 153 in 154 let cols = 155 List.map 156 (fun (rc : result_col) -> { rc with expr = renum_expr next rc.expr }) 157 s.cols 158 in 159 let from = 160 match s.from with 161 | (Unit | Table _) as f -> f 162 | Select fs -> Select { fs with fs_select = renum_select next fs.fs_select } 163 | Values rows -> Values (List.map (List.map (renum_expr next)) rows) 164 | Table_function tf -> 165 Table_function 166 { tf with tf_args = List.map (renum_expr next) tf.tf_args } 167 in 168 let joins = 169 List.map 170 (fun (j : join) -> 171 let table = 172 match j.table.subquery with 173 | Some q -> { j.table with subquery = Some (renum_select next q) } 174 | None -> j.table 175 in 176 { j with table; on = renum_expr next j.on }) 177 s.joins 178 in 179 let where = Option.map (renum_expr next) s.where in 180 let group_by = List.map (renum_expr next) s.group_by in 181 let having = Option.map (renum_expr next) s.having in 182 let compound = 183 List.map (fun (op, arm) -> (op, renum_select next arm)) s.compound 184 in 185 let order_by = List.map (fun (e, d) -> (renum_expr next e, d)) s.order_by in 186 let limit = Option.map (renum_expr next) s.limit in 187 let offset = Option.map (renum_expr next) s.offset in 188 { 189 s with 190 ctes; 191 cols; 192 from; 193 joins; 194 where; 195 group_by; 196 having; 197 compound; 198 order_by; 199 limit; 200 offset; 201 } 202 203let number_params stmt = 204 let counter = ref 0 in 205 let next () = 206 let i = !counter in 207 incr counter; 208 i 209 in 210 let e = renum_expr next and es = renum_exprs next in 211 let rcs l = 212 List.map (fun (rc : result_col) -> { rc with expr = e rc.expr }) l 213 in 214 match stmt with 215 | Select s -> Select (renum_select next s) 216 | Insert (i : insert) -> 217 let source = 218 match i.source with 219 | Values rows -> Values (List.map es rows) 220 | Select s -> Select (renum_select next s) 221 | Default_values -> Default_values 222 in 223 let upsert = 224 Option.map 225 (fun (u : upsert) -> 226 match u.action with 227 | Nothing -> u 228 | Update (sets, w) -> 229 let sets = List.map (fun (c, x) -> (c, e x)) sets in 230 { u with action = Update (sets, Option.map e w) }) 231 i.upsert 232 in 233 let returning = rcs i.returning in 234 Insert { i with source; upsert; returning } 235 | Delete d -> 236 let where = Option.map e d.where in 237 Delete { d with where; returning = rcs d.returning } 238 | Update u -> 239 let sets = List.map (fun (c, x) -> (c, e x)) u.sets in 240 let where = Option.map e u.where in 241 Update { u with sets; where; returning = rcs u.returning } 242 | Create_table_as cta -> 243 Create_table_as { cta with query = renum_select next cta.query } 244 | ( Create_table _ | Create_index _ | Create_view _ | Create_trigger _ 245 | Drop _ | Alter_table _ | Pragma _ | Begin | Commit | Rollback 246 | Savepoint _ | Release _ | Rollback_to _ | Vacuum | Analyze ) as s -> 247 s 248 249(* ── Values ──────────────────────────────────────────────────── *) 250 251(* ── Three-valued logic ──────────────────────────────────────── *) 252 253(* The collating sequence an operand assigns by a top-level [COLLATE], if any. 254 In a comparison the left operand's explicit collation wins, else the right's 255 (datatype3.html sec.7.1). Column-declared collation is not yet threaded 256 here. *) 257let explicit_collation = function Collate (_, c) -> Some c | _ -> None 258 259(* SQLite integer coercion (as the bitwise/modulo operators apply it): an 260 integer stays itself, a real truncates toward zero, text parses its leading 261 numeric prefix (else 0), NULL is handled by the caller. *) 262 263(* [a << b] / [a >> b]: a negative shift count shifts the other way; a count of 264 64 or more clears the value, matching sqlite3. *) 265let shift dir x n = 266 let n = Int64.to_int n in 267 let lsl_ x n = if n >= 64 then 0L else Int64.shift_left x n in 268 (* arithmetic right shift, so a negative x keeps its sign, as in sqlite3 *) 269 let asr_ x n = 270 if n >= 64 then if x < 0L then -1L else 0L else Int64.shift_right x n 271 in 272 match dir with 273 | `Left -> if n >= 0 then lsl_ x n else asr_ x (-n) 274 | `Right -> if n >= 0 then asr_ x n else lsl_ x (-n) 275 276(* SQLite arithmetic: NULL is contagious; integer op integer stays integer 277 (with [/] truncating and division-by-zero giving NULL); any real operand, or 278 a text operand coerced to a number, makes the result real. The bitwise and 279 modulo operators always work on integers (operands coerced via {!intify}). *) 280(* Does this operand carry REAL affinity? A real literal, or text that parses as 281 a real but not an integer. Mod is computed on integers but returns a real when 282 either operand is real, as sqlite3 does. *) 283(* Add/Sub/Mul/Div: integer when both operands are integers ([/] truncates, /0 284 is NULL), otherwise real (text operands coerced to a number). *) 285(* SQLite computes an integer Add/Sub/Mul/Div that would overflow [int64] as 286 floating point instead of wrapping (e.g. [9223372036854775807 + 1] is a real). 287 Each op detects overflow on the native result and falls back to a float. *) 288let add_checked x y = 289 let r = Int64.add x y in 290 (* overflow iff the operands share a sign and the result's sign flips *) 291 if x >= 0L = (y >= 0L) && r >= 0L <> (x >= 0L) then 292 Float (Int64.to_float x +. Int64.to_float y) 293 else Int r 294 295let sub_checked x y = 296 let r = Int64.sub x y in 297 if x >= 0L <> (y >= 0L) && r >= 0L <> (x >= 0L) then 298 Float (Int64.to_float x -. Int64.to_float y) 299 else Int r 300 301let mul_checked x y = 302 let r = Int64.mul x y in 303 (* check the special [min_int * -1] first so the div below never hits it *) 304 let overflow = 305 x <> 0L && ((x = -1L && y = Int64.min_int) || Int64.div r x <> y) 306 in 307 if overflow then Float (Int64.to_float x *. Int64.to_float y) else Int r 308 309let arith_int op x y = 310 match op with 311 | Add -> add_checked x y 312 | Sub -> sub_checked x y 313 | Mul -> mul_checked x y 314 | Div -> 315 if y = 0L then Null 316 else if x = Int64.min_int && y = -1L then 317 Float (Int64.to_float x /. Int64.to_float y) 318 else Int (Int64.div x y) 319 | _ -> assert false 320 321let arith_float op a b = 322 let f = function 323 | Int i -> Int64.to_float i 324 | Float f -> f 325 | v -> ( 326 match float_of_string_opt (String.trim (text_of_value v)) with 327 | Some f -> f 328 | None -> 0.) 329 in 330 let x = f a and y = f b in 331 match op with 332 | Add -> Float (x +. y) 333 | Sub -> Float (x -. y) 334 | Mul -> Float (x *. y) 335 | Div -> if y = 0. then Null else Float (x /. y) 336 | _ -> assert false 337 338let arith op a b = 339 if a = Null || b = Null then Null 340 else 341 match op with 342 | Mod -> 343 let y = intify b in 344 if y = 0L then Null 345 else 346 let r = Int64.rem (intify a) y in 347 if is_real a || is_real b then Float (Int64.to_float r) else Int r 348 | Bit_and -> Int (Int64.logand (intify a) (intify b)) 349 | Bit_or -> Int (Int64.logor (intify a) (intify b)) 350 | Shl -> Int (shift `Left (intify a) (intify b)) 351 | Shr -> Int (shift `Right (intify a) (intify b)) 352 | _ -> ( 353 (* Apply numeric affinity to text operands first, so '5'+2 is integer 354 arithmetic (7) while '5.5'+2 is real (7.5), matching sqlite3. *) 355 match (numeric_affinity a, numeric_affinity b) with 356 | Int x, Int y -> arith_int op x y 357 | a, b -> arith_float op a b) 358 359(* ── Environments and lookup ─────────────────────────────────── *) 360 361(* A [binding] is one named row fragment: an (alias, [(column, value)]) pair -- 362 the public form used for an outer [scope] (a correlated subquery's enclosing 363 row, or a trigger's OLD/NEW rows). *) 364type binding = string * (string * value) list 365 366(* Internally a row is a list of [frame]s -- one per table joined in, plus the 367 scope at the tail so inner tables shadow it for unqualified names. A table row 368 is a [Pos] frame: the alias, the shared column-name list (one allocation, 369 reused across every row of that table), and the row's values -- so building a 370 row's environment costs a 3-word record, not an n-pair association list. A 371 scope binding becomes an [Assoc] frame. *) 372(* A [Pos] frame carries the optional rowid of a real-table row (for the 373 implicit [rowid]/[oid]/[_rowid_] column); [None] for an index-narrowed scan, 374 a derived table, or a scope binding, where rowid is not resolvable. *) 375type frame = 376 | Pos of string * string list * value list * int64 option 377 | Assoc of binding 378 379type env = frame list 380 381(* The [rowid] pseudo-column and its two aliases, matched case-insensitively. A 382 real column of the same name shadows the implicit one, so this is consulted 383 only after the declared columns. *) 384let is_rowid_name n = 385 match String.lowercase_ascii n with 386 | "rowid" | "_rowid_" | "oid" -> true 387 | _ -> false 388 389(* Parallel-list lookup: the value paired with [name] in [cols]/[vals] (short 390 [vals] reads as trailing NULLs), or [None]. Column names match exactly, as 391 the association-list lookup it replaces did. *) 392let rec assoc2 name cols vals = 393 match (cols, vals) with 394 | c :: cs, v :: vs -> if c = name then Some v else assoc2 name cs vs 395 | c :: cs, [] -> if c = name then Some Null else assoc2 name cs [] 396 | [], _ -> None 397 398let frame_alias = function Pos (a, _, _, _) | Assoc (a, _) -> a 399 400let frame_lookup name = function 401 | Pos (_, cols, vals, rid) -> ( 402 match assoc2 name cols vals with 403 | Some _ as r -> r 404 | None -> ( 405 match rid with 406 | Some r when is_rowid_name name -> Some (Int r) 407 | _ -> None)) 408 | Assoc (_, row) -> List.assoc_opt name row 409 410(* The frame's values in column order -- the expansion of [alias.*]. The rowid 411 is a hidden column and never part of [*]. *) 412let frame_values = function 413 | Pos (_, _, vals, _) -> vals 414 | Assoc (_, row) -> List.map snd row 415 416(* The same frame with every value NULL -- the inner side of an unmatched outer 417 join row. The synthetic row has no rowid. *) 418let null_pad_frame = function 419 | Pos (a, cols, vals, _) -> Pos (a, cols, List.map (fun _ -> Null) vals, None) 420 | Assoc (a, row) -> Assoc (a, List.map (fun (c, _) -> (c, Null)) row) 421 422(* Drop the [drop] columns from a frame -- the NATURAL/USING common columns, 423 which survive on the left side and so are listed once. *) 424let trim_frame drop = function 425 | Pos (a, cols, vals, rid) -> 426 let keep = 427 List.filter 428 (fun (c, _) -> not (List.mem c drop)) 429 (List.combine cols vals) 430 in 431 Pos (a, List.map fst keep, List.map snd keep, rid) 432 | Assoc (a, row) -> 433 Assoc (a, List.filter (fun (c, _) -> not (List.mem c drop)) row) 434 435let frames_of_scope (scope : binding list) : env = 436 List.map (fun b -> Assoc b) scope 437 438(* Materialise an environment as public [binding]s -- used only to hand the 439 current row to a correlated subquery as its outer scope. Re-pairs a [Pos] 440 frame's columns with its values (short values read as trailing NULLs); this 441 is the one place a row's association list is rebuilt, and only per outer row, 442 not per scanned row. *) 443let bindings_of_frames (env : env) : binding list = 444 let rec pairs cols vals = 445 match (cols, vals) with 446 | c :: cs, v :: vs -> (c, v) :: pairs cs vs 447 | c :: cs, [] -> (c, Null) :: pairs cs [] 448 | [], _ -> [] 449 in 450 List.map 451 (function Pos (a, cols, vals, _) -> (a, pairs cols vals) | Assoc b -> b) 452 env 453 454(* These walkers are top-level (capturing nothing) so a column lookup -- run 455 once per [Col] per row -- allocates neither a closure nor a [lazy] cell. *) 456let rec aliased q = function 457 | [] -> None 458 | f :: rest -> if frame_alias f = q then Some f else aliased q rest 459 460let rec aliased_ci qu = function 461 | [] -> None 462 | f :: rest -> 463 if String.uppercase_ascii (frame_alias f) = qu then Some f 464 else aliased_ci qu rest 465 466let rec lookup_unqual name = function 467 | [] -> Fmt.failwith "unknown column %S" name 468 | f :: rest -> ( 469 match frame_lookup name f with 470 | Some v -> v 471 | None -> lookup_unqual name rest) 472 473let lookup env qual name = 474 match qual with 475 | Some q -> ( 476 (* Exact alias first; then a case-insensitive match (the uppercase form is 477 built only if the exact match fails), since SQL identifiers (and the 478 OLD/NEW pseudo-tables, written in any case) are case-insensitive. *) 479 let frame = 480 match aliased q env with 481 | Some _ as r -> r 482 | None -> aliased_ci (String.uppercase_ascii q) env 483 in 484 match frame with 485 | None -> Fmt.failwith "unknown table %S in %s.%s" q q name 486 | Some f -> ( 487 match frame_lookup name f with 488 | Some v -> v 489 | None -> Fmt.failwith "unknown column %s.%s" q name)) 490 | None -> lookup_unqual name env 491 492(* ── Evaluation ──────────────────────────────────────────────── *) 493 494let frag cat (tr : table_ref) = 495 let alias = Option.value tr.alias ~default:tr.name in 496 let cols = cat.columns tr.name in 497 (alias, cols) 498 499(* An all-NULL environment carrying the FROM tables' column names. It resolves a 500 bare column over an EMPTY aggregate group (no rows): the aggregate is still 501 folded over zero rows, but a bare column reads as NULL -- as sqlite3 does for 502 an aggregate select list (or a HAVING) that names a column on an empty 503 table. *) 504let schema_env cat (s : select) : env = 505 (* Best-effort: a derived/subquery operand whose columns are not in the base 506 catalog is skipped (its bare columns simply stay unresolved, as before). *) 507 let frame tr = 508 match frag cat tr with 509 | alias, cols -> 510 Some (Pos (alias, cols, List.map (fun _ -> Null) cols, None)) 511 | exception Failure _ -> None 512 in 513 let from_frames = 514 match s.from with Table tr -> Option.to_list (frame tr) | _ -> [] 515 in 516 from_frames @ List.filter_map (fun (j : join) -> frame j.table) s.joins 517 518(* [max]/[min] are aggregates only in their one-argument form; [max(a,b,...)] 519 and [min(a,b,...)] are scalar functions over their arguments. *) 520let is_agg name args = 521 match name with 522 | "count" | "sum" | "avg" | "total" | "group_concat" | "string_agg" -> true 523 | "min" | "max" -> List.length args <= 1 524 | _ -> false 525 526(* Is [e] an aggregate call, possibly wrapped in FILTER/ORDER BY decorations? *) 527let rec is_agg_expr = function 528 | Func (n, a) -> is_agg n a 529 | Distinct_agg _ -> true 530 | Filter (e, _) | Ordered_agg (e, _) -> is_agg_expr e 531 | _ -> false 532 533(* Does [e] contain an aggregate of {e this} query anywhere? Unlike 534 {!is_agg_expr} this looks inside ordinary sub-expressions ([f(a, max(b))], [a 535 + count(c)]) but NOT into a subquery -- an aggregate there is the inner 536 query's, so it does not make the enclosing query an aggregate query. *) 537let rec contains_agg = function 538 | Func (n, args) -> is_agg n args || List.exists contains_agg args 539 | Distinct_agg _ | Filter _ | Ordered_agg _ -> true 540 | Binary (_, a, b) | Is (a, b) -> contains_agg a || contains_agg b 541 | Unop_not a | Is_null a | Is_not_null a | Cast (a, _) | Collate (a, _) -> 542 contains_agg a 543 | In_list (a, l) -> contains_agg a || List.exists contains_agg l 544 | In_select (a, _) -> contains_agg a 545 | Case (base, brs, els) -> 546 Option.fold ~none:false ~some:contains_agg base 547 || List.exists (fun (c, r) -> contains_agg c || contains_agg r) brs 548 || Option.fold ~none:false ~some:contains_agg els 549 | Window _ | Scalar_select _ | Exists _ | Lit _ | Col _ | Param _ | Star 550 | Qualified_star _ -> 551 false 552 553let is_aggregate (rc : Ast.result_col) = contains_agg rc.expr 554 555(* Does [s] aggregate at all -- it groups, has HAVING, or a select column folds 556 an aggregate? A LIMIT/OFFSET cannot be pushed into the scan of such a query 557 (the aggregate folds every row first), so a fast path must recognise and skip 558 it. *) 559let is_aggregate_query (s : select) = 560 s.group_by <> [] || s.having <> None || List.exists is_aggregate s.cols 561 562let dedup rows = 563 let seen = Hashtbl.create 64 in 564 List.filter 565 (fun r -> 566 if Hashtbl.mem seen r then false 567 else ( 568 Hashtbl.add seen r (); 569 true)) 570 rows 571 572let take n rows = 573 let rec go n = function 574 | _ when n <= 0 -> [] 575 | [] -> [] 576 | x :: rest -> x :: go (n - 1) rest 577 in 578 go n rows 579 580let rec drop n = function 581 | rows when n <= 0 -> rows 582 | [] -> [] 583 | _ :: rest -> drop (n - 1) rest 584 585(* ── Index access ────────────────────────────────────────────── *) 586 587(* Top-level ANDed equalities [col = const] on table alias [alias], with the 588 right-hand side resolved to a constant (a literal or a bound parameter). 589 These are the predicates an index can drive; anything else is left to the row 590 filter. *) 591let collect_eqs params alias where = 592 let const = function 593 | Lit v -> Some v 594 | Param i -> if i < Array.length params then Some params.(i) else None 595 | _ -> None 596 in 597 let here = function None -> true | Some q -> q = alias in 598 let rec go acc = function 599 | Binary (And, a, b) -> go (go acc a) b 600 | Binary (Eq, Col (q, c), rhs) when here q -> ( 601 match const rhs with Some v -> (c, v) :: acc | None -> acc) 602 | Binary (Eq, lhs, Col (q, c)) when here q -> ( 603 match const lhs with Some v -> (c, v) :: acc | None -> acc) 604 | _ -> acc 605 in 606 match where with None -> [] | Some w -> go [] w 607 608(* The columns of [alias] equated to a constant (literal or parameter) at top 609 level -- the structural view of {!collect_eqs}, independent of any parameter 610 values, used to explain the plan. *) 611let eq_columns alias where = 612 let is_const = function Lit _ | Param _ -> true | _ -> false in 613 let here = function None -> true | Some q -> q = alias in 614 let rec go acc = function 615 | Binary (And, a, b) -> go (go acc a) b 616 | Binary (Eq, Col (q, c), e) when here q && is_const e -> c :: acc 617 | Binary (Eq, e, Col (q, c)) when here q && is_const e -> c :: acc 618 | _ -> acc 619 in 620 match where with None -> [] | Some w -> go [] w 621 622(* Does [e] reference table alias [a]? A join's inner table can be looked up by 623 index only when the other side of the equality does not. *) 624let rec mentions a = function 625 | Col (Some q, _) | Qualified_star q -> q = a 626 | Col (None, _) | Lit _ | Param _ | Star -> false 627 | Unop_not e | Is_null e | Is_not_null e -> mentions a e 628 | Binary (_, x, y) | Is (x, y) -> mentions a x || mentions a y 629 | In_list (e, l) -> mentions a e || List.exists (mentions a) l 630 | In_select (e, _) -> mentions a e 631 | Scalar_select _ | Exists _ -> false 632 | Collate (e, _) -> mentions a e 633 | Func (_, args) -> List.exists (mentions a) args 634 | Distinct_agg (_, e) -> mentions a e 635 | Filter (x, p) -> mentions a x || mentions a p 636 | Ordered_agg (x, ob) -> 637 mentions a x || List.exists (fun (e, _) -> mentions a e) ob 638 | Window (x, w) -> 639 mentions a x 640 || List.exists (mentions a) w.partition 641 || List.exists (fun (e, _) -> mentions a e) w.order 642 | Cast (e, _) -> mentions a e 643 | Case (base, branches, els) -> 644 Option.fold ~none:false ~some:(mentions a) base 645 || List.exists (fun (c, r) -> mentions a c || mentions a r) branches 646 || Option.fold ~none:false ~some:(mentions a) els 647 648(* Columns of the join's inner table [ialias] equated, in [on], to something 649 that does not reference the inner table -- the structural view used to 650 explain an index-driven join. *) 651let join_eq_columns ialias on = 652 let rec go acc = function 653 | Binary (And, a, b) -> go (go acc a) b 654 | Binary (Eq, Col (Some q, c), e) when q = ialias && not (mentions ialias e) 655 -> 656 c :: acc 657 | Binary (Eq, e, Col (Some q, c)) when q = ialias && not (mentions ialias e) 658 -> 659 c :: acc 660 | _ -> acc 661 in 662 go [] on 663 664(* The inner column -> outer expression map of a join's ON equalities: like 665 {!join_eq_columns} but keeping the other side of each equality, so a hash 666 probe key can be precomputed once and only evaluated per outer row. *) 667let join_probe_map ialias on = 668 let rec go acc = function 669 | Binary (And, a, b) -> go (go acc a) b 670 | Binary (Eq, Col (Some q, c), e) when q = ialias && not (mentions ialias e) 671 -> 672 (c, e) :: acc 673 | Binary (Eq, e, Col (Some q, c)) when q = ialias && not (mentions ialias e) 674 -> 675 (c, e) :: acc 676 | _ -> acc 677 in 678 go [] on 679 680(* Every table alias a join's ON clause references. A join can only run once the 681 relations it mentions are already in scope, so this bounds reordering. *) 682let rec on_aliases = function 683 | Col (Some q, _) | Qualified_star q -> [ q ] 684 | Col (None, _) | Lit _ | Param _ | Star -> [] 685 | Unop_not e | Is_null e | Is_not_null e | Cast (e, _) -> on_aliases e 686 | Binary (_, a, b) | Is (a, b) -> on_aliases a @ on_aliases b 687 | In_list (e, l) -> on_aliases e @ List.concat_map on_aliases l 688 | In_select (e, _) -> on_aliases e 689 | Scalar_select _ | Exists _ -> [] 690 | Collate (e, _) -> on_aliases e 691 | Func (_, args) -> List.concat_map on_aliases args 692 | Distinct_agg (_, e) -> on_aliases e 693 | Filter (x, p) -> on_aliases x @ on_aliases p 694 | Ordered_agg (x, ob) -> 695 on_aliases x @ List.concat_map (fun (e, _) -> on_aliases e) ob 696 | Window (x, w) -> 697 on_aliases x 698 @ List.concat_map on_aliases w.partition 699 @ List.concat_map (fun (e, _) -> on_aliases e) w.order 700 | Case (b, brs, el) -> 701 Option.fold ~none:[] ~some:on_aliases b 702 @ List.concat_map (fun (c, r) -> on_aliases c @ on_aliases r) brs 703 @ Option.fold ~none:[] ~some:on_aliases el 704 705let join_alias (j : join) = Option.value j.table.alias ~default:j.table.name 706 707(* sqlite3 lets a [WHERE] or [ORDER BY] reference a select-list alias ([SELECT a 708 AS x ... WHERE x>1 ORDER BY x] resolves x to a). Substitute each such alias 709 with the expression it names, unless a real column of a scanned table shadows 710 it (a real column wins). Subqueries are not descended into -- they have their 711 own scope. An aggregate alias substituted into WHERE then raises, as sqlite3 712 does (an aggregate is not allowed in WHERE). *) 713let resolve_where_aliases cat (s : select) = 714 match (s.where, s.order_by) with 715 | None, [] -> s 716 | _ -> 717 let table_cols (tr : table_ref) = 718 match tr.subquery with None -> cat.columns tr.name | Some _ -> [] 719 in 720 let real_cols = 721 (match s.from with Table tr -> table_cols tr | _ -> []) 722 @ List.concat_map (fun (j : join) -> table_cols j.table) s.joins 723 in 724 let aliases = 725 List.filter_map 726 (fun (rc : result_col) -> 727 match rc.alias with 728 | Some a when not (List.mem a real_cols) -> Some (a, rc.expr) 729 | _ -> None) 730 s.cols 731 in 732 if aliases = [] then s 733 else 734 let rec sub e = 735 match e with 736 | Col (None, name) -> ( 737 match List.assoc_opt name aliases with Some e' -> e' | None -> e) 738 | Binary (op, a, b) -> Binary (op, sub a, sub b) 739 | Unop_not a -> Unop_not (sub a) 740 | Is_null a -> Is_null (sub a) 741 | Is_not_null a -> Is_not_null (sub a) 742 | Is (a, b) -> Is (sub a, sub b) 743 | In_list (a, l) -> In_list (sub a, List.map sub l) 744 | Cast (a, t) -> Cast (sub a, t) 745 | Collate (a, c) -> Collate (sub a, c) 746 | Func (n, args) -> Func (n, List.map sub args) 747 | Case (b, brs, el) -> 748 Case 749 ( Option.map sub b, 750 List.map (fun (c, r) -> (sub c, sub r)) brs, 751 Option.map sub el ) 752 | Distinct_agg (n, a) -> Distinct_agg (n, sub a) 753 | Filter (a, b) -> Filter (sub a, sub b) 754 | Ordered_agg (a, ob) -> 755 Ordered_agg (sub a, List.map (fun (e, d) -> (sub e, d)) ob) 756 | Lit _ | Col _ | Param _ | Star | Qualified_star _ | In_select _ 757 | Scalar_select _ | Exists _ | Window _ -> 758 e 759 in 760 { 761 s with 762 where = Option.map sub s.where; 763 order_by = List.map (fun (e, d) -> (sub e, d)) s.order_by; 764 } 765 766(* Cost-based join ordering (Leis et al., VLDB 2015: join ordering dominates). 767 Inner joins commute, so reorder them cheapest-first by an exact-cardinality 768 cost model -- an index-covered ON is a lookup (cost 0), otherwise the inner 769 table's row count -- subject to each join's ON dependencies being in scope. 770 Outer joins are not reorderable, so a query containing one keeps FROM 771 order. *) 772let plan_joins cat from_alias joins = 773 if joins = [] || List.exists (fun j -> j.type_ <> Inner || j.natural) joins 774 then joins 775 else 776 let cost j = 777 let eq = join_eq_columns (join_alias j) j.on in 778 if eq <> [] && cat.index_for j.table.name eq <> None then 0 779 else List.length (cat.rows j.table.name) 780 in 781 let ready avail j = 782 List.for_all 783 (fun q -> q = join_alias j || List.mem q avail) 784 (on_aliases j.on) 785 in 786 let rec go avail remaining acc = 787 match remaining with 788 | [] -> List.rev acc 789 | _ -> 790 let candidates = 791 match List.filter (ready avail) remaining with 792 | [] -> remaining (* a cross/unsatisfiable join: do not stall *) 793 | r -> r 794 in 795 let best = 796 List.fold_left 797 (fun b j -> if cost j < cost b then j else b) 798 (List.hd candidates) (List.tl candidates) 799 in 800 go (join_alias best :: avail) 801 (List.filter (fun j -> j != best) remaining) 802 (best :: acc) 803 in 804 go [ from_alias ] joins [] 805 806(* The chosen access path for the [from] table: an index narrowing when one 807 covers the equalities, else a full scan. The narrowed rows are still passed 808 through the row filter, so the choice is a pure optimisation. *) 809let from_access cat params (tr : table_ref) where = 810 let alias = Option.value tr.alias ~default:tr.name in 811 match collect_eqs params alias where with 812 | [] -> None 813 | eqs -> cat.index_scan tr.name eqs 814 815(* The output column names of a select, for use as a derived table's columns: 816 the explicit alias, else the bare column name, else a positional name. A 817 [SELECT *] expands to every column in scope -- the FROM table plus each 818 joined table, recursing into a derived (subquery) operand. *) 819let rec select_columns cat s = 820 let table_cols (tr : table_ref) = 821 match tr.subquery with 822 | Some q -> select_columns cat q 823 | None -> cat.columns tr.name 824 in 825 let star_cols () = 826 let from_cols = 827 match s.from with 828 | Unit -> [] 829 | Table tr -> table_cols tr 830 | Select { fs_select; _ } -> select_columns cat fs_select 831 | Values (r :: _) -> List.mapi (fun j _ -> Fmt.str "column%d" (j + 1)) r 832 | Values [] -> [] 833 | Table_function { tf_name; _ } -> table_function_columns tf_name 834 in 835 from_cols @ List.concat_map (fun (j : join) -> table_cols j.table) s.joins 836 in 837 List.concat 838 (List.mapi 839 (fun i (rc : result_col) -> 840 match rc.expr with 841 | Star -> star_cols () 842 | Qualified_star q -> cat.columns q 843 | _ -> ( 844 match rc.alias with 845 | Some a -> [ a ] 846 | None -> ( 847 match rc.expr with 848 | Col (_, c) -> [ c ] 849 | _ -> [ Fmt.str "column%d" (i + 1) ]))) 850 s.cols) 851 852(* The output expressions of [s] in order, expanding [*]/[t.*] to a column 853 reference per underlying column. Used to resolve an ORDER BY ordinal to the 854 expression of that output column. *) 855let output_exprs cat (s : select) = 856 let star () = 857 select_columns cat { s with cols = [ { expr = Star; alias = None } ] } 858 in 859 List.concat_map 860 (fun (rc : result_col) -> 861 match rc.expr with 862 | Star -> List.map (fun c -> Col (None, c)) (star ()) 863 | Qualified_star q -> List.map (fun c -> Col (Some q, c)) (cat.columns q) 864 | e -> [ e ]) 865 s.cols 866 867(* sqlite3 lets ORDER BY and GROUP BY use a 1-based output-column ordinal 868 ([ORDER BY 2] / [GROUP BY 2] use the second result column). Replace each 869 integer-literal key with the expression of that output column, so the normal 870 machinery uses it; an out-of-range or non-integer key is left as is. *) 871let resolve_ordinals cat (s : select) = 872 if s.order_by = [] && s.group_by = [] then s 873 else 874 let outs = Array.of_list (output_exprs cat s) in 875 let n = Array.length outs in 876 (* A 1-based ordinal becomes its output expression; [ORDER BY k COLLATE c] 877 parses as a [Collate] around the ordinal, so look through it (the 878 collation stays, wrapping the resolved column). *) 879 let rec nth = function 880 | Lit (Int i) when i >= 1L && Int64.to_int i <= n -> 881 outs.(Int64.to_int i - 1) 882 | Collate (e, c) -> Collate (nth e, c) 883 | e -> e 884 in 885 (* An ORDER BY key may name an explicit output-column alias; the alias takes 886 precedence over an input column (sqlite3), so resolve it to that output 887 expression -- this is how [ORDER BY n] reaches an aggregate aliased 888 [n]. *) 889 let alias_expr name = 890 List.find_map 891 (fun (rc : result_col) -> 892 match rc.alias with Some a when a = name -> Some rc.expr | _ -> None) 893 s.cols 894 in 895 let rec order_key = function 896 | Collate (e, c) -> Collate (order_key e, c) 897 | Col (None, name) as e -> ( 898 match alias_expr name with Some x -> x | None -> nth e) 899 | e -> nth e 900 in 901 { 902 s with 903 order_by = List.map (fun (e, d) -> (order_key e, d)) s.order_by; 904 group_by = List.map nth s.group_by; 905 } 906 907let collation_bases (s : select) = 908 let of_ref (tr : table_ref) = 909 match tr.subquery with 910 | None -> [ (Option.value tr.alias ~default:tr.name, tr.name) ] 911 | Some _ -> [] 912 in 913 (match s.from with Table tr -> of_ref tr | _ -> []) 914 @ List.concat_map (fun (j : join) -> of_ref j.table) s.joins 915 916let collation_for cat bases = function 917 | Col (Some q, name) -> ( 918 match List.assoc_opt q bases with 919 | Some tbl -> cat.collation tbl name 920 | None -> None) 921 | Col (None, name) -> ( 922 let cols_of tbl = try cat.columns tbl with Failure _ -> [] in 923 match 924 List.find_opt (fun (_, tbl) -> List.mem name (cols_of tbl)) bases 925 with 926 | Some (_, tbl) -> cat.collation tbl name 927 | None -> None) 928 | _ -> None 929 930let has_explicit_collation = function Collate _ -> true | _ -> false 931let is_col_expr = function Col _ -> true | _ -> false 932 933let collation_cmp_operands col_coll a b = 934 if has_explicit_collation a || has_explicit_collation b then (a, b) 935 else if is_col_expr a then 936 match col_coll a with Some c -> (Collate (a, c), b) | None -> (a, b) 937 else if is_col_expr b then 938 match col_coll b with Some c -> (a, Collate (b, c)) | None -> (a, b) 939 else (a, b) 940 941let collation_lhs col_coll e = 942 if has_explicit_collation e then e 943 else match col_coll e with Some c -> Collate (e, c) | None -> e 944 945let rec resolve_collation_expr col_coll = function 946 | Binary (((And | Or) as op), a, b) -> 947 Binary 948 ( op, 949 resolve_collation_expr col_coll a, 950 resolve_collation_expr col_coll b ) 951 | Unop_not a -> Unop_not (resolve_collation_expr col_coll a) 952 | Binary (((Eq | Neq | Lt | Le | Gt | Ge) as op), a, b) -> 953 let a, b = collation_cmp_operands col_coll a b in 954 Binary (op, a, b) 955 (* [x IN ..] uses the left operand's collation, like [x = ..]. *) 956 | In_list (e, l) -> In_list (collation_lhs col_coll e, l) 957 | In_select (e, sub) -> In_select (collation_lhs col_coll e, sub) 958 | e -> e 959 960(* Apply each base-table column's declared COLLATE sequence to the comparisons 961 of [s]'s WHERE/ON/HAVING and to its ORDER BY keys, by wrapping the column in 962 an explicit [Collate] node -- which the existing collation machinery then 963 honours. sqlite3's rule: an explicit COLLATE on either operand wins; 964 otherwise the left operand's column collation, else the right's. Only bare 965 columns of a scanned base table carry a declared collation (a 966 subquery/derived source has none). *) 967let resolve_collations cat (s : select) = 968 let bases = collation_bases s in 969 if bases = [] then s 970 else 971 let col_coll = collation_for cat bases in 972 let rw = resolve_collation_expr col_coll in 973 let lhs_coll = collation_lhs col_coll in 974 let order_key (e, dir) = (lhs_coll e, dir) in 975 { 976 s with 977 where = Option.map rw s.where; 978 having = Option.map rw s.having; 979 group_by = List.map lhs_coll s.group_by; 980 order_by = List.map order_key s.order_by; 981 joins = List.map (fun (j : join) -> { j with on = rw j.on }) s.joins; 982 } 983 984(* Does select [s] read from a table named [name] (directly, via a join, a 985 FROM-subquery, or a compound arm)? Used to spot a self-referential CTE. *) 986let rec references_table name (s : select) = 987 (match s.from with 988 | Unit -> false 989 | Table tr -> tr.name = name 990 | Select { fs_select; _ } -> references_table name fs_select 991 | Values _ | Table_function _ -> false) 992 || List.exists 993 (fun (j : join) -> 994 j.table.name = name 995 || 996 match j.table.subquery with 997 | Some q -> references_table name q 998 | None -> false) 999 s.joins 1000 || List.exists (fun (_, arm) -> references_table name arm) s.compound 1001 1002(* Combine compound arms by their set operator. UNION/EXCEPT/INTERSECT yield 1003 distinct rows; UNION ALL keeps duplicates. *) 1004let set_op op left right = 1005 match op with 1006 | Union_all -> left @ right 1007 | Union -> dedup (left @ right) 1008 | Except -> dedup (List.filter (fun r -> not (List.mem r right)) left) 1009 | Intersect -> dedup (List.filter (fun r -> List.mem r right) left) 1010 1011(* ── Evaluation and SELECT execution ─────────────────────────── *) 1012 1013(* Pick how to find the inner rows for a join: a full scan, an index lookup, or 1014 a hash table keyed on the equijoin columns. *) 1015let join_strategy cat ialias env_of eq_cols (j : Ast.join) = 1016 if eq_cols = [] then `Scan 1017 else 1018 match cat.index_for (j.table : table_ref).name eq_cols with 1019 | Some _ -> `Index 1020 | None -> 1021 let tbl : (value list, value list) Hashtbl.t = Hashtbl.create 256 in 1022 List.iter 1023 (fun vals -> 1024 let row = [ env_of vals ] in 1025 let key = List.map (fun c -> lookup row (Some ialias) c) eq_cols in 1026 Hashtbl.add tbl key vals) 1027 (cat.rows j.table.name); 1028 `Hash tbl 1029 1030(* Resolve each NATURAL join's implicit ON -- equality on the columns common to 1031 the declared-order left and the joined table -- and return, per join in 1032 declared order, a triple of: the resolved join, the common columns to drop 1033 from its frame (so [SELECT *] lists each common column once), and the left 1034 frame template (alias, columns) accumulated before it (used to NULL-pad the 1035 left of an unmatched right row in a RIGHT/FULL join). *) 1036let resolve_natural cat base_frame joins = 1037 let _, acc = 1038 List.fold_left 1039 (fun (lframes, acc) (j : Ast.join) -> 1040 let ialias, icols = frag cat j.table in 1041 let entry = 1042 if (not j.natural) && j.using = [] then (j, [], lframes) 1043 else 1044 let owner c = 1045 List.find_map 1046 (fun (a, cs) -> if List.mem c cs then Some a else None) 1047 lframes 1048 in 1049 let common = 1050 if j.natural then List.filter (fun c -> owner c <> None) icols 1051 else j.using 1052 in 1053 let eqs = 1054 List.filter_map 1055 (fun c -> 1056 Option.map 1057 (fun a -> 1058 Binary (Eq, Col (Some a, c), Col (Some ialias, c))) 1059 (owner c)) 1060 common 1061 in 1062 let on = 1063 match eqs with 1064 | [] -> Lit (Int 1L) 1065 | e :: r -> List.fold_left (fun acc x -> Binary (And, acc, x)) e r 1066 in 1067 ({ j with on }, common, lframes) 1068 in 1069 (lframes @ [ (ialias, icols) ], acc @ [ entry ])) 1070 ([ base_frame ], []) joins 1071 in 1072 acc 1073 1074(* The distinct [Window] nodes appearing anywhere in [cols] (window functions do 1075 not nest, so a Window is not descended into). Pure syntax -- no executor 1076 dependencies, so it sits outside the evaluation rec group. *) 1077let window_exprs cols = 1078 let acc = ref [] in 1079 let rec go e = 1080 match e with 1081 | Window _ -> if not (List.mem e !acc) then acc := !acc @ [ e ] 1082 | Binary (_, a, b) | Filter (a, b) | Is (a, b) -> 1083 go a; 1084 go b 1085 | Unop_not a 1086 | Is_null a 1087 | Is_not_null a 1088 | Cast (a, _) 1089 | Distinct_agg (_, a) 1090 | Collate (a, _) -> 1091 go a 1092 | In_list (a, l) -> 1093 go a; 1094 List.iter go l 1095 | Func (_, args) -> List.iter go args 1096 | Ordered_agg (a, ob) -> 1097 go a; 1098 List.iter (fun (e, _) -> go e) ob 1099 | Case (b, brs, el) -> 1100 Option.iter go b; 1101 List.iter 1102 (fun (c, r) -> 1103 go c; 1104 go r) 1105 brs; 1106 Option.iter go el 1107 | In_select _ | Scalar_select _ | Exists _ | Lit _ | Col _ | Param _ | Star 1108 | Qualified_star _ -> 1109 () 1110 in 1111 List.iter (fun (rc : Ast.result_col) -> go rc.expr) cols; 1112 !acc 1113 1114(* Replace every [Window] node in [e] with the literal [lookup] gives for it. *) 1115let rec subst_windows lookup e = 1116 let r = subst_windows lookup in 1117 match e with 1118 | Window _ -> Lit (lookup e) 1119 | Binary (op, a, b) -> Binary (op, r a, r b) 1120 | Is (a, b) -> Is (r a, r b) 1121 | Unop_not a -> Unop_not (r a) 1122 | Is_null a -> Is_null (r a) 1123 | Is_not_null a -> Is_not_null (r a) 1124 | In_list (a, l) -> In_list (r a, List.map r l) 1125 | Cast (a, t) -> Cast (r a, t) 1126 | Collate (a, c) -> Collate (r a, c) 1127 | Func (n, args) -> Func (n, List.map r args) 1128 | Case (b, brs, el) -> 1129 Case 1130 ( Option.map r b, 1131 List.map (fun (c, x) -> (r c, r x)) brs, 1132 Option.map r el ) 1133 | ( Lit _ | Col _ | Param _ | Star | Qualified_star _ | In_select _ 1134 | Scalar_select _ | Exists _ | Distinct_agg _ | Filter _ | Ordered_agg _ ) 1135 as e -> 1136 e 1137 1138(* The frame [lo, hi] (ordered positions, clamped to the partition) for the row 1139 at [pos]: from the explicit ROWS/RANGE clause, else the default frame (whole 1140 partition with no ORDER BY, else UNBOUNDED PRECEDING to the current peer 1141 group). RANGE offsets are not value-based here; they fall back to the peer 1142 group, which agrees with RANGE for distinct order keys. [evcur] evaluates a 1143 bound offset against the current row. *) 1144let window_frame ~pos ~k ~peer_start ~peer_end ~evcur w = 1145 let off e = Int64.to_int (intify (evcur e)) in 1146 let lo, hi = 1147 match w.frame with 1148 | None -> (0, if w.order = [] then k - 1 else peer_end.(pos)) 1149 | Some f -> 1150 let rows = f.rows in 1151 let resolve at_start = function 1152 | Unbounded_preceding -> 0 1153 | Unbounded_following -> k - 1 1154 | Current_row -> 1155 if rows then pos 1156 else if at_start then peer_start.(pos) 1157 else peer_end.(pos) 1158 | Preceding e -> if rows then pos - off e else peer_start.(pos) 1159 | Following e -> if rows then pos + off e else peer_end.(pos) 1160 in 1161 (resolve true f.start, resolve false f.end_) 1162 in 1163 (max 0 lo, min (k - 1) hi) 1164 1165(* Wrap [cat] so [name] resolves to the given materialised [cols]/[rows] (no 1166 index), shadowing any real table -- the mechanism behind CTEs and derived 1167 tables in JOIN position. *) 1168let bind_relation cat name cols rows = 1169 { 1170 columns = (fun n -> if n = name then cols else cat.columns n); 1171 rows = (fun n -> if n = name then rows else cat.rows n); 1172 rows_rowid = 1173 (fun n -> 1174 if n = name then List.map (fun r -> (None, r)) rows 1175 else cat.rows_rowid n); 1176 index_scan = (fun n eqs -> if n = name then None else cat.index_scan n eqs); 1177 index_for = (fun n cs -> if n = name then None else cat.index_for n cs); 1178 collation = (fun n c -> if n = name then None else cat.collation n c); 1179 } 1180 1181let is_col_node = function Col _ -> true | _ -> false 1182 1183let subquery_column ?(scope = []) cat params s = 1184 !cat_subquery_runner cat scope s params 1185 |> List.map (function v :: _ -> v | [] -> Null) 1186 1187let eval_scalar_select cat params env s = 1188 (* A scalar subquery yields the first column of its first row (NULL if it 1189 returns none). Correlated via the same outer [env] scope. *) 1190 match !cat_subquery_runner cat (bindings_of_frames env) s params with 1191 | (v :: _) :: _ -> v 1192 | [] :: _ | [] -> Null 1193 1194let eval_exists cat params env s = 1195 bool 1196 (match !cat_subquery_runner cat (bindings_of_frames env) s params with 1197 | _ :: _ -> true 1198 | [] -> false) 1199 1200let rec eval cat params env = function 1201 | Lit v -> v 1202 | Param i -> if i < Array.length params then params.(i) else Null 1203 | Col (q, n) -> lookup env q n 1204 | Star | Qualified_star _ -> failwith "'*' is only valid as a select item" 1205 | Unop_not e -> not_value (eval cat params env e) 1206 | Binary (op, a, b) -> eval_binary cat params env op a b 1207 | Collate (e, _) -> eval cat params env e 1208 | Is_null e -> bool (eval cat params env e = Null) 1209 | Is_not_null e -> bool (eval cat params env e <> Null) 1210 | Is (a, b) -> is_value (eval cat params env a) (eval cat params env b) 1211 | In_list (e, l) -> eval_in_list cat params env e l 1212 | In_select (e, s) -> eval_in_select cat params env e s 1213 | Scalar_select s -> eval_scalar_select cat params env s 1214 | Exists s -> eval_exists cat params env s 1215 (* Aggregates are computed by {!aggregate} over the whole group; reaching one 1216 here means it was used as a scalar (in WHERE, ON, or ORDER BY). *) 1217 | Func (name, args) when is_agg name args -> 1218 Fmt.failwith 1219 "%s() is an aggregate function and is only valid as a select item, not \ 1220 in WHERE, ON, or ORDER BY" 1221 name 1222 | Distinct_agg (name, _) -> 1223 Fmt.failwith 1224 "%s() is an aggregate function and is only valid as a select item, not \ 1225 in WHERE, ON, or ORDER BY" 1226 name 1227 | Filter _ | Ordered_agg _ -> 1228 failwith 1229 "an aggregate FILTER/ORDER BY is only valid as a select item, not in \ 1230 WHERE, ON, or ORDER BY" 1231 | Window _ -> 1232 failwith 1233 "a window function is only valid in the select list, not in WHERE, ON, \ 1234 or GROUP BY" 1235 (* iif(C, T, F) is sugar for CASE and short-circuits, so it is evaluated 1236 before its branches rather than as an ordinary (eager-argument) 1237 function. *) 1238 | Func ("iif", [ c; t; f ]) -> 1239 if tri_of (eval cat params env c) = T then eval cat params env t 1240 else eval cat params env f 1241 | Func (name, args) -> apply name (List.map (eval cat params env) args) 1242 | Cast (e, ty) -> cast ty (eval cat params env e) 1243 (* CASE: the base (simple form) is evaluated once and matched with [=] 1244 semantics, so a NULL base matches no WHEN -- including WHEN NULL. First 1245 match wins; no match and no ELSE is NULL. *) 1246 | Case (base, branches, els) -> eval_case cat params env base branches els 1247 1248and eval_binary cat params env op a b = 1249 match op with 1250 | And | Or | Like | Glob | Concat | Json_get | Json_get_text -> 1251 binop_value op (eval cat params env a) (eval cat params env b) 1252 | Add | Sub | Mul | Div | Mod | Bit_and | Bit_or | Shl | Shr -> 1253 arith op (eval cat params env a) (eval cat params env b) 1254 | _ -> 1255 let coll = 1256 match explicit_collation a with 1257 | Some _ as c -> c 1258 | None -> explicit_collation b 1259 in 1260 let va = eval cat params env a and vb = eval cat params env b in 1261 compare_with_affinity ?coll ~a_col:(is_col_node a) ~b_col:(is_col_node b) 1262 op va vb 1263 1264and eval_in_list cat params env e l = 1265 let v = eval cat params env e in 1266 let cands = List.map (eval cat params env) l in 1267 (* The membership test uses the left operand's collation (a declared COLLATE 1268 made explicit by [resolve_collations]) and its affinity. *) 1269 let coll = explicit_collation e in 1270 let bare = 1271 match e with Collate (inner, c) when c <> "BINARY" -> inner | _ -> e 1272 in 1273 in_list_value ?coll ~e_col:(is_col_node bare) v cands 1274 1275and eval_in_select cat params env e s = 1276 (* The subquery sees the current row as its outer scope, so a correlated [IN 1277 (SELECT ... WHERE inner.x = outer.y)] resolves [outer.y]. *) 1278 let scope = bindings_of_frames env in 1279 in_eval (eval cat params env e) (subquery_column ~scope cat params s) 1280 1281and eval_case cat params env base branches els = 1282 let bv = Option.map (eval cat params env) base in 1283 let hit cond = 1284 match bv with 1285 | Some b -> 1286 equal_value (compare_op Eq b (eval cat params env cond)) (Int 1L) 1287 | None -> tri_of (eval cat params env cond) = T 1288 in 1289 let rec go = function 1290 | (c, r) :: rest -> if hit c then eval cat params env r else go rest 1291 | [] -> ( match els with Some e -> eval cat params env e | None -> Null) 1292 in 1293 go branches 1294 1295(* Inner-table equalities [col = e] in a join's ON clause where [e], evaluated 1296 against the current outer row, gives a concrete value -- the bindings that 1297 drive an index-nested-loop join. *) 1298let join_eqs cat params env ialias on = 1299 let rec go acc = function 1300 | Binary (And, a, b) -> go (go acc a) b 1301 | Binary (Eq, Col (Some q, c), e) when q = ialias && not (mentions ialias e) 1302 -> 1303 (c, eval cat params env e) :: acc 1304 | Binary (Eq, e, Col (Some q, c)) when q = ialias && not (mentions ialias e) 1305 -> 1306 (c, eval cat params env e) :: acc 1307 | _ -> acc 1308 in 1309 go [] on 1310 1311(* RIGHT/FULL OUTER: the inner table's unmatched rows must also survive, so the 1312 left stream is materialised and every inner row is scanned for any match. 1313 FULL additionally keeps unmatched left rows (NULL inner), like LEFT. The 1314 emission order is not specified by SQL; callers that care add ORDER BY. *) 1315let right_full_join cat params (j : Ast.join) envs ~env_of ~trim ~null_frame 1316 ~left_template = 1317 let lefts = envs in 1318 let inners = Array.of_list (cat.rows j.table.name) in 1319 let hit = Array.make (Array.length inners) false in 1320 let null_left = 1321 match lefts with 1322 | e :: _ -> List.map null_pad_frame e 1323 | [] -> 1324 List.map 1325 (fun (a, cs) -> Pos (a, cs, List.map (fun _ -> Null) cs, None)) 1326 left_template 1327 in 1328 let out = ref [] in 1329 List.iter 1330 (fun env -> 1331 let any = ref false in 1332 Array.iteri 1333 (fun k vals -> 1334 match tri_of (eval cat params (env @ [ env_of vals ]) j.on) with 1335 | T -> 1336 any := true; 1337 hit.(k) <- true; 1338 out := (env @ [ trim (env_of vals) ]) :: !out 1339 | F | U -> ()) 1340 inners; 1341 if (not !any) && j.type_ = Full then out := (env @ [ null_frame ]) :: !out) 1342 lefts; 1343 Array.iteri 1344 (fun k vals -> 1345 if not hit.(k) then out := (null_left @ [ trim (env_of vals) ]) :: !out) 1346 inners; 1347 List.rev !out 1348 1349(* Add one joined table to the running row stream, picking the access strategy 1350 once for the whole join (not per outer row): - index-nested-loop when a 1351 unique index covers the ON equi-columns; - hash join when the ON has 1352 equi-columns but no index: build a hash table on the inner relation keyed by 1353 those columns once, then probe per outer row (O(inner + outer) instead of the 1354 O(inner x outer) nested-loop); - plain nested-loop scan otherwise (no 1355 equi-column, e.g. a cross join). The full ON predicate is always re-checked, 1356 so the strategy only narrows candidates and NULL keys (which never 1357 equi-match) fall out. *) 1358let join_with ?(drop = []) ?(left_template = []) cat params (j : Ast.join) envs 1359 = 1360 let ialias, icols = frag cat j.table in 1361 let env_of vals = Pos (ialias, icols, vals, None) in 1362 (* A NATURAL join lists each common column once: the ON is evaluated on the 1363 full inner frame, but [drop]ped columns are removed from the frame stored 1364 in the row (they survive on the left and are equal there). With nothing to 1365 drop (the common non-NATURAL case) this is the identity, avoiding a per-row 1366 copy of the frame. *) 1367 let trim = if drop = [] then Fun.id else trim_frame drop in 1368 (* A LEFT JOIN keeps an outer row that matched nothing, with the inner table's 1369 columns all NULL. *) 1370 let null_frame = 1371 trim (Pos (ialias, icols, List.map (fun _ -> Null) icols, None)) 1372 in 1373 let eq_cols = join_eq_columns ialias j.on in 1374 let strategy = join_strategy cat ialias env_of eq_cols j in 1375 (* The hash probe key as expressions over the outer row, resolved once (in 1376 [eq_cols] order) so each outer row only evaluates them -- no per-row 1377 [(col,value)] list rebuilt and re-looked-up. *) 1378 let probe_exprs = 1379 match strategy with 1380 | `Hash _ -> 1381 let m = join_probe_map ialias j.on in 1382 List.map (fun c -> List.assoc c m) eq_cols 1383 | _ -> [] 1384 in 1385 let candidates env = 1386 match strategy with 1387 | `Scan -> cat.rows j.table.name 1388 | `Index -> ( 1389 match 1390 cat.index_scan j.table.name (join_eqs cat params env ialias j.on) 1391 with 1392 | Some (_, rows) -> rows 1393 | None -> cat.rows j.table.name) 1394 | `Hash tbl -> 1395 let key = List.map (eval cat params env) probe_exprs in 1396 Hashtbl.find_all tbl key 1397 in 1398 let matched env = 1399 List.filter_map 1400 (fun vals -> 1401 (* Build the inner frame once and reuse it for both the ON test and the 1402 emitted row. *) 1403 let frame = env_of vals in 1404 match tri_of (eval cat params (env @ [ frame ]) j.on) with 1405 | T -> Some (env @ [ trim frame ]) 1406 | F | U -> None) 1407 (candidates env) 1408 in 1409 match j.type_ with 1410 | Inner -> List.concat_map matched envs 1411 | Left -> 1412 List.concat_map 1413 (fun env -> 1414 match matched env with [] -> [ env @ [ null_frame ] ] | rows -> rows) 1415 envs 1416 | Right | Full -> 1417 right_full_join cat params j envs ~env_of ~trim ~null_frame ~left_template 1418 1419let project cat params (s : Ast.select) env = 1420 (* A star-free select list (the common case) maps one value per item -- no 1421 per-item singleton list and no [concat_map] to flatten. Only [*] and [t.*], 1422 which expand to a whole frame, need the general path. *) 1423 let is_star (rc : Ast.result_col) = 1424 match rc.expr with Star | Qualified_star _ -> true | _ -> false 1425 in 1426 if List.exists is_star s.cols then 1427 List.concat_map 1428 (fun (rc : Ast.result_col) -> 1429 match rc.expr with 1430 | Star -> List.concat_map frame_values env 1431 | Qualified_star q -> ( 1432 match aliased_ci (String.uppercase_ascii q) env with 1433 | Some f -> frame_values f 1434 | None -> Fmt.failwith "no such table: %s" q) 1435 | e -> [ eval cat params env e ]) 1436 s.cols 1437 else 1438 List.map (fun (rc : Ast.result_col) -> eval cat params env rc.expr) s.cols 1439 1440(* Fold one aggregate call over the group [envs]. With [distinct], the non-NULL 1441 argument values are deduplicated (preserving first-seen order) before the 1442 fold -- [count(DISTINCT x)], [sum(DISTINCT x)], etc. *) 1443let agg_call ?(distinct = false) cat params envs name args = 1444 let arg1 = function [ a ] -> a | _ -> Star in 1445 let dedup vs = if distinct then distinct_values vs else vs in 1446 let agg_vals arg = 1447 dedup 1448 (List.filter_map 1449 (fun env -> 1450 match eval cat params env arg with Null -> None | v -> Some v) 1451 envs) 1452 in 1453 match (name, args) with 1454 | "count", args -> ( 1455 match arg1 args with 1456 | Star -> Int (Int64.of_int (List.length envs)) 1457 | a -> Int (Int64.of_int (List.length (agg_vals a)))) 1458 | ("group_concat" | "string_agg"), args -> ( 1459 (* group_concat(X [, sep]) / string_agg(X, sep): non-NULL X values joined 1460 by sep (default ","); NULL over an empty group. *) 1461 let arg, sep = 1462 match args with 1463 | [ a ] -> (a, ",") 1464 | a :: s :: _ -> 1465 ( a, 1466 match envs with 1467 | env :: _ -> text_of_value (eval cat params env s) 1468 | [] -> "," ) 1469 | [] -> (Star, ",") 1470 in 1471 match agg_vals arg with 1472 | [] -> Null 1473 | vals -> Text (String.concat sep (List.map text_of_value vals))) 1474 | _ -> numeric_agg name (agg_vals (arg1 args)) 1475 1476(* Evaluate one expression over a group [envs] with aggregate semantics: every 1477 aggregate call (even nested in a larger expression, e.g. [sum(x) > 5]) is 1478 folded over the group and substituted by its value; the rest is then read 1479 from an arbitrary row of the group. Used for select-list columns and 1480 HAVING. *) 1481let agg_value ?(empty = []) ?rep cat params envs e = 1482 (* FILTER (WHERE p) restricts the group rows; ORDER BY sorts them (only 1483 order-sensitive aggregates like group_concat observe the order). *) 1484 let filtered p envs = 1485 List.filter (fun env -> tri_of (eval cat params env p) = T) envs 1486 in 1487 let sorted ob envs = 1488 let rec cmp a b = function 1489 | [] -> 0 1490 | (e, dir) :: rest -> 1491 let c = compare_values (eval cat params a e) (eval cat params b e) in 1492 let c = match dir with Asc -> c | Desc -> -c in 1493 if c <> 0 then c else cmp a b rest 1494 in 1495 List.stable_sort (fun a b -> cmp a b ob) envs 1496 in 1497 (* Compute an aggregate expression's value over [envs], peeling FILTER/ORDER 1498 BY decorations down to the core Func / Distinct_agg call. *) 1499 let rec agg_core envs = function 1500 | Func (name, args) -> agg_call ~distinct:false cat params envs name args 1501 | Distinct_agg (name, arg) -> 1502 agg_call ~distinct:true cat params envs name [ arg ] 1503 | Filter (inner, p) -> agg_core (filtered p envs) inner 1504 | Ordered_agg (inner, ob) -> agg_core (sorted ob envs) inner 1505 | _ -> Null 1506 in 1507 let rec subst e = 1508 match e with 1509 | e when is_agg_expr e -> Lit (agg_core envs e) 1510 | Unop_not a -> Unop_not (subst a) 1511 | Binary (op, a, b) -> Binary (op, subst a, subst b) 1512 | Is (a, b) -> Is (subst a, subst b) 1513 | Is_null a -> Is_null (subst a) 1514 | Is_not_null a -> Is_not_null (subst a) 1515 | In_list (a, l) -> In_list (subst a, List.map subst l) 1516 | Cast (a, ty) -> Cast (subst a, ty) 1517 | Collate (a, c) -> Collate (subst a, c) 1518 | Case (base, branches, els) -> 1519 Case 1520 ( Option.map subst base, 1521 List.map (fun (c, r) -> (subst c, subst r)) branches, 1522 Option.map subst els ) 1523 (* Recurse into a non-aggregate function's args for a nested aggregate. *) 1524 | Func (n, args) -> Func (n, List.map subst args) 1525 | (Lit _ | Col _ | Param _ | Star | Qualified_star _ | In_select _) as e -> 1526 e 1527 | (Scalar_select _ | Exists _) as e -> e 1528 | (Distinct_agg _ | Filter _ | Ordered_agg _ | Window _) as e -> e 1529 in 1530 let e = subst e in 1531 (* Bare columns: from [rep] (the min/max row), else an arbitrary or empty 1532 row. *) 1533 let row = 1534 match rep with 1535 | Some r -> r 1536 | None -> ( match envs with e :: _ -> e | [] -> empty) 1537 in 1538 eval cat params row e 1539 1540let aggregate ?(empty = []) ?rep cat params (s : Ast.select) envs = 1541 (* [*] in a grouped/aggregated select expands to the columns of the group's 1542 representative row (the min/max row when one is chosen, else an arbitrary 1543 member), just as a bare column does. *) 1544 let rep_row () = 1545 match rep with 1546 | Some r -> r 1547 | None -> ( match envs with e :: _ -> e | [] -> empty) 1548 in 1549 let has_star = 1550 List.exists 1551 (fun (rc : Ast.result_col) -> 1552 match rc.expr with Star | Qualified_star _ -> true | _ -> false) 1553 s.cols 1554 in 1555 if not has_star then 1556 List.map 1557 (fun (rc : Ast.result_col) -> 1558 agg_value ~empty ?rep cat params envs rc.expr) 1559 s.cols 1560 else 1561 List.concat_map 1562 (fun (rc : Ast.result_col) -> 1563 match rc.expr with 1564 | Star -> List.concat_map frame_values (rep_row ()) 1565 | Qualified_star q -> ( 1566 match aliased_ci (String.uppercase_ascii q) (rep_row ()) with 1567 | Some f -> frame_values f 1568 | None -> Fmt.failwith "no such table: %s" q) 1569 | e -> [ agg_value ~empty ?rep cat params envs e ]) 1570 s.cols 1571 1572(* The representative row for bare (non-aggregate) columns: sqlite3 takes them 1573 from the row achieving a lone [min()]/[max()] in the select list (NULLs are 1574 ignored, as by the aggregate). [None] when there is no such aggregate, so the 1575 caller uses an arbitrary row. *) 1576let minmax_rep cat params (s : select) envs = 1577 let target = 1578 List.find_map 1579 (fun (rc : Ast.result_col) -> 1580 match rc.expr with 1581 | Func ("min", [ a ]) -> Some (`Min, a) 1582 | Func ("max", [ a ]) -> Some (`Max, a) 1583 | _ -> None) 1584 s.cols 1585 in 1586 match (target, envs) with 1587 | None, _ | _, [] -> None 1588 | Some (dir, arg), e0 :: rest -> 1589 let better cur env = 1590 let cv = eval cat params cur arg and ev = eval cat params env arg in 1591 if ev = Null then false 1592 else if cv = Null then true 1593 else 1594 let c = compare_values ev cv in 1595 match dir with `Min -> c < 0 | `Max -> c > 0 1596 in 1597 Some 1598 (List.fold_left 1599 (fun best env -> if better best env then env else best) 1600 e0 rest) 1601 1602let order_cmp cat params order_by e1 e2 = 1603 let rec cmp = function 1604 | [] -> 0 1605 | (oe, dir) :: rest -> 1606 let va = eval cat params e1 oe and vb = eval cat params e2 oe in 1607 let c = 1608 match explicit_collation oe with 1609 | Some name -> compare_values_coll name va vb 1610 | None -> compare_values va vb 1611 in 1612 let c = match dir with Asc -> c | Desc -> -c in 1613 if c <> 0 then c else cmp rest 1614 in 1615 cmp order_by 1616 1617let order cat params s envs = 1618 if s.order_by = [] then envs 1619 else List.stable_sort (order_cmp cat params s.order_by) envs 1620 1621(* Partition rows into groups by the GROUP BY key tuple, keeping the groups in 1622 first-appearance order; each group is the list of its rows. *) 1623let group_envs cat params keys envs = 1624 (* Two rows fall in the same group when their key values are equal under the 1625 key's collation: a NOCASE GROUP BY key folds case. [resolve_collations] has 1626 already made a column's declared collation explicit, so a non-default 1627 sequence shows up as a [Collate] wrapper. *) 1628 let group_key env = 1629 List.map 1630 (fun e -> 1631 match (explicit_collation e, eval cat params env e) with 1632 | Some c, Text s -> collation_key c (Text s) 1633 | _, v -> v) 1634 keys 1635 in 1636 let tbl = Hashtbl.create 16 in 1637 let seen = ref [] in 1638 List.iter 1639 (fun env -> 1640 let k = group_key env in 1641 match Hashtbl.find_opt tbl k with 1642 | Some r -> r := env :: !r 1643 | None -> 1644 Hashtbl.add tbl k (ref [ env ]); 1645 seen := k :: !seen) 1646 envs; 1647 List.rev_map (fun k -> List.rev !(Hashtbl.find tbl k)) !seen 1648 1649(* One row's window value: ranking from its position, navigation/aggregate over 1650 the frame [lo, hi] (ordered positions). [at i e] evaluates [e] at the i-th 1651 ordered row. *) 1652let window_lag_lead ~name ~args ~k ~pos ~at ~evcur = 1653 let off = 1654 match List.nth_opt args 1 with 1655 | Some o -> Int64.to_int (intify (evcur o)) 1656 | None -> 1 1657 in 1658 let dflt = 1659 match List.nth_opt args 2 with Some d -> evcur d | None -> Null 1660 in 1661 let t = if name = "lag" then pos - off else pos + off in 1662 if t >= 0 && t < k then at t (List.hd args) else dflt 1663 1664let window_nth_value ~args ~lo ~hi ~at ~evcur = 1665 let nn = Int64.to_int (intify (evcur (List.nth args 1))) in 1666 let t = lo + (nn - 1) in 1667 if nn >= 1 && t <= hi then at t (List.hd args) else Null 1668 1669let window_ntile ~args ~k ~pos ~evcur = 1670 let nb = max 1 (Int64.to_int (intify (evcur (List.hd args)))) in 1671 let base = k / nb and rem = k mod nb in 1672 let big = rem * (base + 1) in 1673 let b = if pos < big then pos / (base + 1) else rem + ((pos - big) / base) in 1674 Int (Int64.of_int (b + 1)) 1675 1676let frame_rows ~envs_a ~oa ~lo ~hi = 1677 if lo > hi then [] 1678 else List.init (hi - lo + 1) (fun r -> envs_a.(oa.(lo + r))) 1679 1680let window_value cat params ~name ~args ~distinct ~envs_a ~oa ~peer_start ~k 1681 ~dense ~pos ~lo ~hi = 1682 let at i e = eval cat params envs_a.(oa.(i)) e in 1683 let evcur e = eval cat params envs_a.(oa.(pos)) e in 1684 match name with 1685 | "row_number" -> Int (Int64.of_int (pos + 1)) 1686 | "rank" -> Int (Int64.of_int (peer_start.(pos) + 1)) 1687 | "dense_rank" -> Int (Int64.of_int dense) 1688 | "first_value" -> if lo <= hi then at lo (List.hd args) else Null 1689 | "last_value" -> if lo <= hi then at hi (List.hd args) else Null 1690 | "lag" | "lead" -> window_lag_lead ~name ~args ~k ~pos ~at ~evcur 1691 | "nth_value" -> window_nth_value ~args ~lo ~hi ~at ~evcur 1692 | "ntile" -> window_ntile ~args ~k ~pos ~evcur 1693 | _ -> 1694 let frame = frame_rows ~envs_a ~oa ~lo ~hi in 1695 agg_call ~distinct cat params frame name args 1696 1697(* Compute one partition's window values into [arr]: order [idxs] by the 1698 window's ORDER BY, find peer-group bounds (peers share a rank and a frame), 1699 and write each row's value (ranking by position, aggregate over its 1700 frame). *) 1701let fill_window_partition cat params ~name ~args ~distinct w envs_a arr idxs = 1702 let oa = 1703 Array.of_list 1704 (List.stable_sort 1705 (fun i j -> order_cmp cat params w.order envs_a.(i) envs_a.(j)) 1706 idxs) 1707 in 1708 let k = Array.length oa in 1709 let peer_start = Array.make k 0 and peer_end = Array.make k 0 in 1710 let p = ref 0 in 1711 while !p < k do 1712 let q = ref !p in 1713 while 1714 !q + 1 < k 1715 && order_cmp cat params w.order envs_a.(oa.(!p)) envs_a.(oa.(!q + 1)) = 0 1716 do 1717 incr q 1718 done; 1719 for r = !p to !q do 1720 peer_start.(r) <- !p; 1721 peer_end.(r) <- !q 1722 done; 1723 p := !q + 1 1724 done; 1725 let dense = ref 0 and prev_start = ref (-1) in 1726 for pos = 0 to k - 1 do 1727 if peer_start.(pos) <> !prev_start then begin 1728 incr dense; 1729 prev_start := peer_start.(pos) 1730 end; 1731 let evcur e = eval cat params envs_a.(oa.(pos)) e in 1732 let lo, hi = window_frame ~pos ~k ~peer_start ~peer_end ~evcur w in 1733 arr.(oa.(pos)) <- 1734 window_value cat params ~name ~args ~distinct ~envs_a ~oa ~peer_start ~k 1735 ~dense:!dense ~pos ~lo ~hi 1736 done 1737 1738(* Evaluate the window function [Window (f, w)] over [envs], returning a value 1739 per env (aligned to [envs] order). Rows are partitioned by [w.partition], 1740 ordered within each partition by [w.order]; ranking functions use the 1741 position, aggregates use the default frame (the whole partition with no ORDER 1742 BY, else running through the current row's peer group). *) 1743let eval_window cat params we envs = 1744 let fexpr, w = match we with Window (f, w) -> (f, w) | _ -> assert false in 1745 let name, args, distinct = 1746 match fexpr with 1747 | Func (n, a) -> (n, a, false) 1748 | Distinct_agg (n, a) -> (n, [ a ], true) 1749 | _ -> failwith "window: not a function call" 1750 in 1751 let envs_a = Array.of_list envs in 1752 let n = Array.length envs_a in 1753 let arr = Array.make n Null in 1754 (* partition the row indices by the partition-key tuple *) 1755 let tbl = Hashtbl.create 16 and keys = ref [] in 1756 Array.iteri 1757 (fun i env -> 1758 let k = List.map (eval cat params env) w.partition in 1759 match Hashtbl.find_opt tbl k with 1760 | Some r -> r := i :: !r 1761 | None -> 1762 Hashtbl.add tbl k (ref [ i ]); 1763 keys := k :: !keys) 1764 envs_a; 1765 let parts = List.rev_map (fun k -> List.rev !(Hashtbl.find tbl k)) !keys in 1766 List.iter 1767 (fun idxs -> 1768 fill_window_partition cat params ~name ~args ~distinct w envs_a arr idxs) 1769 parts; 1770 arr 1771 1772(* Project [envs] when the select list has window functions: compute each window 1773 value per env, then project, substituting the windows. The outer ORDER BY 1774 sorts after the windows are computed (their frames are independent of it). *) 1775let window_project ~ordered cat params (s : Ast.select) envs = 1776 let wins = 1777 List.map 1778 (fun we -> (we, eval_window cat params we envs)) 1779 (window_exprs s.cols) 1780 in 1781 let indexed = List.mapi (fun i env -> (i, env)) envs in 1782 let indexed = 1783 if ordered then 1784 List.stable_sort 1785 (fun (_, e1) (_, e2) -> order_cmp cat params s.order_by e1 e2) 1786 indexed 1787 else indexed 1788 in 1789 List.map 1790 (fun (i, env) -> 1791 let lookup we = (List.assoc we wins).(i) in 1792 List.concat_map 1793 (fun (rc : Ast.result_col) -> 1794 match rc.expr with 1795 | Star -> List.concat_map frame_values env 1796 | Qualified_star q -> ( 1797 match aliased_ci (String.uppercase_ascii q) env with 1798 | Some f -> frame_values f 1799 | None -> Fmt.failwith "no such table: %s" q) 1800 | e -> [ eval cat params env (subst_windows lookup e) ]) 1801 s.cols) 1802 indexed 1803 1804(* Run one SELECT/VALUES core to output rows: combine, filter, group/aggregate 1805 or project, distinct. [~order] applies the core's own ORDER BY (true for a 1806 plain select; false for a compound's cores, whose ordering is the 1807 compound-wide ORDER BY applied to the combined rows). *) 1808(* Turn the WHERE-filtered row environments [envs] into output rows: a GROUP BY 1809 folds each group's aggregates (ordering the groups when [ordered]); an 1810 aggregate select list or HAVING with no GROUP BY is one implicit group; a 1811 window select projects through {!window_project}; otherwise it is an ordinary 1812 sort-and-project. Used by {!aggregate_scan}, which is handed already-scanned 1813 rows for the grouping / window post-pass. *) 1814let core_rows ~ordered cat params s envs = 1815 (* The all-NULL schema env for an empty implicit group (an empty table with no 1816 GROUP BY), so a bare column in HAVING/the select list reads as NULL. *) 1817 let empty = schema_env cat s in 1818 (* HAVING keeps a group only when its (aggregate) predicate is true; a NULL 1819 result is untrue and drops the group. *) 1820 let keep_group g = 1821 match s.having with 1822 | None -> true 1823 | Some h -> tri_of (agg_value ~empty cat params g h) = T 1824 in 1825 if s.group_by <> [] then 1826 let groups = group_envs cat params s.group_by envs in 1827 let groups = 1828 if not ordered then groups 1829 (* a compound arm: combine_compound re-orders, so leave first-seen *) 1830 else 1831 (* sqlite3 emits GROUP BY result rows in the group key's ascending order 1832 when there is no ORDER BY; an explicit ORDER BY overrides it. *) 1833 let keys = 1834 if s.order_by <> [] then s.order_by 1835 else List.map (fun e -> (e, Asc)) s.group_by 1836 in 1837 List.stable_sort 1838 (fun g1 g2 -> order_cmp cat params keys (List.hd g1) (List.hd g2)) 1839 groups 1840 in 1841 List.filter_map 1842 (fun g -> 1843 let rep = minmax_rep cat params s g in 1844 if keep_group g then Some (aggregate ?rep cat params s g) else None) 1845 groups 1846 else if List.exists is_aggregate s.cols || s.having <> None then 1847 (* No GROUP BY but an aggregate select list or a HAVING clause: the whole 1848 input is one implicit group (lang_select.html). *) 1849 let rep = minmax_rep cat params s envs in 1850 if keep_group envs then [ aggregate ~empty ?rep cat params s envs ] else [] 1851 else if window_exprs s.cols <> [] then 1852 window_project ~ordered cat params s envs 1853 else 1854 let envs = if ordered then order cat params s envs else envs in 1855 List.map (project cat params s) envs 1856 1857(* ORDER BY over a compound's output rows: a bare integer is an ordinal (1-based 1858 output column), a bare name is an output column, anything else is evaluated 1859 against the row keyed by the output column names. *) 1860let compound_order cat params names order_by rows = 1861 if order_by = [] then rows 1862 else 1863 let nth row i = Option.value (List.nth_opt row i) ~default:Null in 1864 let col_index name = 1865 let rec go i = function 1866 | [] -> None 1867 | x :: _ when x = name -> Some i 1868 | _ :: r -> go (i + 1) r 1869 in 1870 go 0 names 1871 in 1872 (* [ORDER BY k COLLATE c] wraps the ordinal in [Collate]; the collation is 1873 read by [cmp] below, so here just look through it to the key itself. *) 1874 let rec key row oe = 1875 match oe with 1876 | Lit (Int n) -> nth row (Int64.to_int n - 1) 1877 | Col (_, name) -> ( 1878 match col_index name with Some i -> nth row i | None -> Null) 1879 | Collate (e, _) -> key row e 1880 | _ -> eval cat params [ Pos ("", names, row, None) ] oe 1881 in 1882 List.stable_sort 1883 (fun r1 r2 -> 1884 let rec cmp = function 1885 | [] -> 0 1886 | (oe, dir) :: rest -> 1887 let c = 1888 match explicit_collation oe with 1889 | Some name -> compare_values_coll name (key r1 oe) (key r2 oe) 1890 | None -> compare_values (key r1 oe) (key r2 oe) 1891 in 1892 let c = match dir with Asc -> c | Desc -> -c in 1893 if c <> 0 then c else cmp rest 1894 in 1895 cmp order_by) 1896 rows 1897 1898let apply_limit cat params s rows = 1899 (* OFFSET first, then LIMIT. A negative/NULL offset skips nothing; a negative 1900 LIMIT is unbounded; LIMIT 0 yields no rows (lang_select.html). *) 1901 (* An offset/limit beyond the row count (up to int64 max) must not overflow 1902 [Int64.to_int]; compare against the length first. *) 1903 let count = Int64.of_int (List.length rows) in 1904 let rows = 1905 match s.offset with 1906 | None -> rows 1907 | Some e -> ( 1908 match eval cat params [] e with 1909 | Int n when n >= count -> [] 1910 | Int n when n > 0L -> drop (Int64.to_int n) rows 1911 | _ -> rows) 1912 in 1913 match s.limit with 1914 | None -> rows 1915 | Some e -> ( 1916 match eval cat params [] e with 1917 | Int n when n < 0L -> rows 1918 | Int n when n >= Int64.of_int (List.length rows) -> rows 1919 | Int n -> take (Int64.to_int n) rows 1920 | Null -> rows 1921 | _ -> failwith "LIMIT must be an integer") 1922 1923(* Materialise each join operand that is a subquery [(q) alias] under its alias, 1924 so the join machinery sees it as an ordinary named relation. *) 1925let bind_join_subqueries ?(scope = []) cat params joins = 1926 List.fold_left 1927 (fun cat (j : Ast.join) -> 1928 match j.table.subquery with 1929 | None -> cat 1930 | Some sub -> 1931 let cols = select_columns cat sub in 1932 let rows = !cat_subquery_runner cat scope sub params in 1933 bind_relation cat j.table.name cols rows) 1934 cat joins 1935 1936let combine ?(scope = []) cat params s : env list = 1937 let cat = bind_join_subqueries ~scope cat params s.joins in 1938 let no_rowid = List.map (fun r -> (None, r)) in 1939 let alias, cols, from_rows = 1940 match s.from with 1941 | Unit -> ("", [], [ (None, []) ]) 1942 | Table tr -> 1943 let alias, cols = frag cat tr in 1944 (* A full scan carries each row's rowid (for the implicit rowid column); 1945 an index-narrowed scan drops it, so rowid stays unresolvable 1946 there. *) 1947 let rows = 1948 match from_access cat params tr s.where with 1949 | Some (_, rows) -> no_rowid rows 1950 | None -> cat.rows_rowid tr.name 1951 in 1952 (alias, cols, rows) 1953 | Select { fs_select; fs_alias } -> 1954 let alias = Option.value fs_alias ~default:"" in 1955 let cols = select_columns cat fs_select in 1956 (alias, cols, no_rowid (!cat_subquery_runner cat scope fs_select params)) 1957 | Values tuples -> 1958 let n = match tuples with r :: _ -> List.length r | [] -> 0 in 1959 let cols = List.init n (fun i -> Fmt.str "column%d" (i + 1)) in 1960 let env = frames_of_scope scope in 1961 ("", cols, no_rowid (List.map (List.map (eval cat params env)) tuples)) 1962 | Table_function { tf_name; tf_args; tf_alias } -> 1963 let alias = Option.value tf_alias ~default:tf_name in 1964 let env = frames_of_scope scope in 1965 let cols, rows = 1966 table_function tf_name (List.map (eval cat params env) tf_args) 1967 in 1968 (alias, cols, no_rowid rows) 1969 in 1970 let base = 1971 List.map (fun (rid, vals) -> [ Pos (alias, cols, vals, rid) ]) from_rows 1972 in 1973 let resolved = resolve_natural cat (alias, cols) s.joins in 1974 let info_of j = 1975 match List.find_opt (fun (j', _, _) -> j' == j) resolved with 1976 | Some (_, d, lt) -> (d, lt) 1977 | None -> ([], []) 1978 in 1979 let joined = 1980 List.fold_left 1981 (fun envs j -> 1982 let drop, left_template = info_of j in 1983 join_with cat params ~drop ~left_template j envs) 1984 base 1985 (plan_joins cat alias (List.map (fun (j, _, _) -> j) resolved)) 1986 in 1987 (* The outer [scope] sits at the tail: inner tables shadow it for unqualified 1988 names, but a qualified [outer.col] / [OLD.col] / [NEW.col] still 1989 resolves. *) 1990 if scope = [] then joined 1991 else 1992 let tail = frames_of_scope scope in 1993 List.map (fun env -> env @ tail) joined 1994 1995(* Recursive CTE by semi-naive (delta) evaluation -- the SQLite / differential 1996 algorithm: seed with the non-recursive arm; each round runs the recursive 1997 arm(s) over only the previous round's new rows ([working]), so work is the 1998 delta, not the whole accumulated relation. UNION dedups new rows against the 1999 full result; UNION ALL keeps them. [cat] already binds [name] (to be rebound 2000 per round). A row cap guards against a non-terminating recursion. *) 2001let run_recursive cat name cols params query = 2002 let op = match query.compound with (op, _) :: _ -> op | [] -> Union_all in 2003 let dedup_union = op = Union in 2004 let strip s = { s with compound = []; order_by = []; limit = None } in 2005 let initial = strip query in 2006 let arms = List.map (fun (_, arm) -> strip arm) query.compound in 2007 let rebind rows = bind_relation cat name cols rows in 2008 (* The seed and each recursive arm are stripped non-compound cores; run them 2009 on the catalog VM (the injected runner), so recursive-CTE evaluation never 2010 reaches the tree-walker. *) 2011 let run cat' s = !cat_subquery_runner cat' [] s params in 2012 let seed = run (rebind []) initial in 2013 let rec loop result working = 2014 if working = [] then result 2015 else 2016 let cat' = rebind working in 2017 let delta = List.concat_map (fun arm -> run cat' arm) arms in 2018 let delta = 2019 if dedup_union then 2020 dedup (List.filter (fun r -> not (List.mem r result)) delta) 2021 else delta 2022 in 2023 if delta = [] then result 2024 else if List.length result + List.length delta > 1_000_000 then 2025 failwith "recursive CTE exceeded 1,000,000 rows" 2026 else loop (result @ delta) delta 2027 in 2028 loop seed seed 2029 2030(* Bind each WITH table expression as a row source, materialized once (the 2031 state-of-the-art choice when a CTE is read more than once, as our nested-loop 2032 joins do). Later CTEs see the earlier ones. A recursive body evaluates to a 2033 fixpoint; a non-recursive one runs on the catalog VM (the injected 2034 runner). *) 2035let materialize_ctes cat params ctes = 2036 List.fold_left 2037 (fun cat (c : cte) -> 2038 let cols = 2039 match c.columns with [] -> select_columns cat c.query | cs -> cs 2040 in 2041 let rows = 2042 if references_table c.name c.query then 2043 run_recursive cat c.name cols params c.query 2044 else !cat_subquery_runner cat [] c.query params 2045 in 2046 bind_relation cat c.name cols rows) 2047 cat ctes 2048 2049(* Does any expression reference the implicit [rowid] pseudo-column? A subquery 2050 or window expression returns [true] conservatively (the caller then keeps such 2051 a select on the tree-walker rather than the rowid-free VM scan). *) 2052(* Whether [e] references a [rowid] column. A subquery / [EXISTS] reads as [true] 2053 (its scan is separate). A [Window] is opaque by default (the plain aggregate 2054 path cannot scan it); with [~through_window], it descends into the window's 2055 function, partition, and order keys -- the window node itself is what the 2056 window post-pass compiles, not a rowid reference, but a rowid inside its 2057 scanned components still matters. *) 2058let rec expr_references_rowid ?(through_window = false) e = 2059 let r = expr_references_rowid ~through_window in 2060 match e with 2061 | Col (_, name) -> is_rowid_name name 2062 | Lit _ | Param _ | Star | Qualified_star _ -> false 2063 | Unop_not e 2064 | Is_null e 2065 | Is_not_null e 2066 | Cast (e, _) 2067 | Collate (e, _) 2068 | Distinct_agg (_, e) -> 2069 r e 2070 | Binary (_, a, b) | Is (a, b) | Filter (a, b) -> r a || r b 2071 | In_list (e, l) -> r e || List.exists r l 2072 | Func (_, args) -> List.exists r args 2073 | Ordered_agg (a, ob) -> r a || List.exists (fun (e, _) -> r e) ob 2074 | Case (base, brs, els) -> 2075 Option.fold ~none:false ~some:r base 2076 || List.exists (fun (c, x) -> r c || r x) brs 2077 || Option.fold ~none:false ~some:r els 2078 | Window (f, w) -> 2079 through_window 2080 && (r f || List.exists r w.partition 2081 || List.exists (fun (oe, _) -> r oe) w.order) 2082 | In_select _ | Scalar_select _ | Exists _ -> true 2083 2084(* Whether [s] is an aggregate/GROUP BY select that {!aggregate_scan} can run: 2085 it groups or aggregates or has HAVING and uses no window function. The scan 2086 carries the implicit rowid column ([rowid_sentinel]) like any base-table 2087 scan, so an aggregate over [rowid] (e.g. [count(rowid)]) compiles too. *) 2088let aggregate_compilable (s : select) = 2089 (s.group_by <> [] || s.having <> None || List.exists is_aggregate s.cols) 2090 && window_exprs s.cols = [] 2091 2092(* A select with a window function over a [rowid]-free projection: like an 2093 aggregate, it scans its (filtered, joined) source rows then post-processes 2094 them ({!window_project}, reached through {!aggregate_scan}). Lets the 2095 register VM run the scan instead of falling back wholesale. *) 2096let window_compilable (s : select) = 2097 let rowid = expr_references_rowid ~through_window:true in 2098 window_exprs s.cols <> [] 2099 && not 2100 (List.exists (fun (rc : Ast.result_col) -> rowid rc.expr) s.cols 2101 || List.exists (fun (e, _) -> rowid e) s.order_by) 2102 2103(* Run an aggregate/GROUP BY [s] from its already-scanned source [rows] and 2104 reuse {!core_rows} for the grouping/aggregation/HAVING and ORDER BY, then 2105 dedup (DISTINCT) and apply OFFSET/LIMIT. [segments] names the join's tables 2106 in FROM order as [(alias, cols)]; each scanned row is the concatenation of 2107 its tables' columns in that order, so it is sliced back into one [Pos] 2108 binding per table -- a single-table aggregate is just a one-element 2109 [segments]. Lets the register VM do the (possibly joined) scan and reuse this 2110 aggregation. *) 2111let aggregate_scan cat params (s : select) segments rows = 2112 let bind vals = 2113 let env, _ = 2114 List.fold_left 2115 (fun (acc, rest) (alias, cols) -> 2116 let n = List.length cols in 2117 (Pos (alias, cols, take n rest, None) :: acc, drop n rest)) 2118 ([], vals) segments 2119 in 2120 List.rev env 2121 in 2122 let envs = List.map bind rows in 2123 let rows = core_rows ~ordered:true cat params s envs in 2124 let rows = if s.distinct then dedup rows else rows in 2125 apply_limit cat params s rows 2126 2127(* Combine the already-computed arm rows of a compound [s] -- [base_rows] for 2128 the leading arm and [(op, rows)] for each following arm: fold the set 2129 operators, order by [s.order_by] over the output columns, then apply 2130 OFFSET/LIMIT. Lets a caller compute each arm however it likes (the register 2131 VM) and reuse this compound orchestration. *) 2132let combine_compound cat params (s : select) base_rows compound_rows = 2133 let combined = 2134 List.fold_left 2135 (fun acc (op, rows) -> set_op op acc rows) 2136 base_rows compound_rows 2137 in 2138 let ordered = 2139 compound_order cat params (select_columns cat s) s.order_by combined 2140 in 2141 apply_limit cat params s ordered 2142 2143(* Each FROM source of [s] as (alias, its column names) -- the column scope a 2144 subquery body resolves against. A name that does not resolve (e.g. a CTE 2145 absent from the base catalog) contributes no columns, so a subquery using it 2146 reads as correlated and is left for the tree-walker rather than wrongly 2147 hoisted. *) 2148let from_scope cat (s : select) : (string * string list) list = 2149 let cols_of name = try cat.columns name with Failure _ -> [] in 2150 let alias_or_name (tr : table_ref) = 2151 match tr.alias with Some a -> a | None -> tr.name 2152 in 2153 let of_ref (tr : table_ref) = 2154 match tr.subquery with 2155 | Some q -> (alias_or_name tr, select_columns cat q) 2156 | None -> (alias_or_name tr, cols_of tr.name) 2157 in 2158 let leading = 2159 match s.from with 2160 | Table tr -> [ of_ref tr ] 2161 | Select { fs_select; fs_alias } -> 2162 [ (Option.value fs_alias ~default:"", select_columns cat fs_select) ] 2163 | Values tuples -> 2164 let n = match tuples with r :: _ -> List.length r | [] -> 0 in 2165 [ ("", List.init n (fun i -> Fmt.str "column%d" (i + 1))) ] 2166 | Unit | Table_function _ -> [] 2167 in 2168 leading @ List.map (fun (j : join) -> of_ref j.table) s.joins 2169 2170(* Whether [s], read as a subquery under the column scope [bound] (alias -> its 2171 columns), references a FREE column -- one bound by neither [bound] nor [s]'s 2172 own FROM. A free column makes the subquery CORRELATED to an enclosing query, 2173 so it cannot be materialised on its own. A nested subquery extends the scope, 2174 so it is checked under [scope]. Conservative: a qualified column whose alias 2175 is unknown, or an unqualified column in no in-scope table, counts as free. *) 2176let rec correlated cat bound (s : select) = 2177 let scope = bound @ from_scope cat s in 2178 let col_free = function 2179 | Col (Some q, _) -> not (List.mem_assoc q scope) 2180 | Col (None, c) -> 2181 not (List.exists (fun (_, cols) -> List.mem c cols) scope) 2182 | _ -> false 2183 in 2184 let rec free e = 2185 match e with 2186 | Col _ -> col_free e 2187 | Lit _ | Param _ | Star | Qualified_star _ -> false 2188 | Unop_not x 2189 | Is_null x 2190 | Is_not_null x 2191 | Cast (x, _) 2192 | Collate (x, _) 2193 | Distinct_agg (_, x) -> 2194 free x 2195 | Binary (_, a, b) | Is (a, b) | Filter (a, b) -> free a || free b 2196 | In_list (x, l) -> free x || List.exists free l 2197 | Func (_, args) -> List.exists free args 2198 | Ordered_agg (x, ob) -> free x || List.exists (fun (e, _) -> free e) ob 2199 | Window (x, w) -> 2200 free x 2201 || List.exists free w.partition 2202 || List.exists (fun (e, _) -> free e) w.order 2203 | Case (base, branches, els) -> 2204 Option.fold ~none:false ~some:free base 2205 || List.exists (fun (c, r) -> free c || free r) branches 2206 || Option.fold ~none:false ~some:free els 2207 | Scalar_select sub | Exists sub -> correlated cat scope sub 2208 | In_select (x, sub) -> free x || correlated cat scope sub 2209 in 2210 List.exists (fun (rc : result_col) -> free rc.expr) s.cols 2211 || Option.fold ~none:false ~some:free s.where 2212 || Option.fold ~none:false ~some:free s.having 2213 || List.exists free s.group_by 2214 || List.exists (fun (e, _) -> free e) s.order_by 2215 || List.exists (fun (j : join) -> free j.on) s.joins 2216 || List.exists (fun (_, arm) -> correlated cat bound arm) s.compound 2217 2218exception Subst_hard 2219(** Raised by {!subst_expr} on a node the simple substituter does not descend 2220 into (a nested subquery or a window), so the caller falls back. *) 2221 2222(* Rebuild [e], applying [replace qual name e] to each column reference and 2223 recursing structurally everywhere else. A nested subquery / window raises 2224 {!Subst_hard}. *) 2225let rec subst_expr ~replace e = 2226 let go = subst_expr ~replace in 2227 match e with 2228 | Col (qual, name) -> replace qual name e 2229 | Scalar_select _ | In_select _ | Exists _ | Window _ -> raise Subst_hard 2230 | Lit _ | Param _ | Star | Qualified_star _ -> e 2231 | Unop_not a -> Unop_not (go a) 2232 | Binary (op, a, b) -> Binary (op, go a, go b) 2233 | Is (a, b) -> Is (go a, go b) 2234 | Is_null a -> Is_null (go a) 2235 | Is_not_null a -> Is_not_null (go a) 2236 | In_list (a, l) -> In_list (go a, List.map go l) 2237 | Func (n, args) -> Func (n, List.map go args) 2238 | Distinct_agg (n, a) -> Distinct_agg (n, go a) 2239 | Filter (a, b) -> Filter (go a, go b) 2240 | Ordered_agg (a, ob) -> 2241 Ordered_agg (go a, List.map (fun (e, d) -> (go e, d)) ob) 2242 | Cast (a, t) -> Cast (go a, t) 2243 | Collate (a, c) -> Collate (go a, c) 2244 | Case (b, br, el) -> 2245 Case 2246 ( Option.map go b, 2247 List.map (fun (c, r) -> (go c, go r)) br, 2248 Option.map go el ) 2249 2250(* Substitute each FREE column of subquery [s] -- a column bound by neither 2251 [s]'s own FROM (which shadows the outer query) nor a deeper scope -- with its 2252 value from [scope] (the enclosing row), turning a correlated subquery into an 2253 uncorrelated one that can run on its own. [None] when [s] is too tangled for 2254 the simple rule (a FROM-subquery, a nested subquery, or a window), so the 2255 caller evaluates it under [~scope] on the tree-walker instead. *) 2256let substitute_scope cat scope (s : select) : select option = 2257 let own = from_scope cat s in 2258 let own_aliases = List.map fst own in 2259 let own_cols = List.concat_map snd own in 2260 let bound qual name = 2261 match qual with 2262 | Some q -> List.mem q own_aliases 2263 | None -> List.mem name own_cols 2264 in 2265 (* Resolve a free column against [scope]. The alias matches case-insensitively 2266 (as the tree-walker's {!lookup} does, for the [OLD]/[NEW] pseudo-tables 2267 written in any case); the column name matches exactly, as 2268 {!frame_lookup}. *) 2269 let lookup qual name = 2270 let alias_eq q a = 2271 String.equal q a 2272 || String.equal (String.uppercase_ascii q) (String.uppercase_ascii a) 2273 in 2274 let rec find = function 2275 | (alias, cols) :: rest -> ( 2276 match qual with 2277 | Some q when not (alias_eq q alias) -> find rest 2278 | _ -> ( 2279 match List.assoc_opt name cols with 2280 | Some v -> Some v 2281 | None -> find rest)) 2282 | [] -> None 2283 in 2284 find scope 2285 in 2286 (* A free column must resolve to a scope value; one that does not (a deeper 2287 correlation than [scope] carries) makes the whole substitution [None], so 2288 the caller keeps the tree-walker, rather than leaving an unbound column 2289 that would fail when the substituted select runs without a scope. *) 2290 let replace qual name e = 2291 if bound qual name then e 2292 else 2293 match lookup qual name with Some v -> Lit v | None -> raise Subst_hard 2294 in 2295 let sub = subst_expr ~replace in 2296 let sub_col (rc : Ast.result_col) = { rc with expr = sub rc.expr } in 2297 (* A FROM/join subquery is left untouched, which is sound only when it is 2298 uncorrelated; a lateral one (referencing the outer scope) falls back. *) 2299 let from_subq_ok q = not (correlated cat [] q) in 2300 try 2301 (match s.from with 2302 | Select { fs_select; _ } when not (from_subq_ok fs_select) -> 2303 raise Subst_hard 2304 | _ -> ()); 2305 List.iter 2306 (fun (j : join) -> 2307 match j.table.subquery with 2308 | Some q when not (from_subq_ok q) -> raise Subst_hard 2309 | Some _ | None -> ()) 2310 s.joins; 2311 Some 2312 { 2313 s with 2314 cols = List.map sub_col s.cols; 2315 where = Option.map sub s.where; 2316 group_by = List.map sub s.group_by; 2317 having = Option.map sub s.having; 2318 order_by = List.map (fun (e, d) -> (sub e, d)) s.order_by; 2319 joins = List.map (fun (j : join) -> { j with on = sub j.on }) s.joins; 2320 } 2321 with Subst_hard -> None 2322 2323(* Replace each uncorrelated scalar / [IN (SELECT ...)] / [EXISTS] subquery in 2324 [s]'s scalar-expression positions with its materialised value, so the 2325 register VM -- which has no subquery opcode -- sees a literal or IN-list 2326 instead and compiles the outer query. A correlated subquery is left untouched 2327 (the VM falls back for it). The subquery is evaluated with the outer [params] 2328 (a [?] indexes the same array) but no row scope, which is exactly its value 2329 when uncorrelated; safe to evaluate eagerly because {!eval} is itself eager 2330 on AND/OR. *) 2331let hoist_subqueries ~run cat (s : select) : select = 2332 let first_col = function v :: _ -> v | [] -> Null in 2333 let scalar sub = 2334 match run sub with (v :: _) :: _ -> Lit v | _ -> Lit Null 2335 in 2336 let rec go e = 2337 match e with 2338 | Scalar_select sub when not (correlated cat [] sub) -> scalar sub 2339 | Exists sub when not (correlated cat [] sub) -> Lit (bool (run sub <> [])) 2340 | In_select (x, sub) when not (correlated cat [] sub) -> 2341 In_list (go x, List.map (fun v -> Lit v) (List.map first_col (run sub))) 2342 | Lit _ | Param _ | Col _ | Star | Qualified_star _ | Scalar_select _ 2343 | Exists _ -> 2344 e 2345 | In_select (x, sub) -> In_select (go x, sub) 2346 | Unop_not x -> Unop_not (go x) 2347 | Is_null x -> Is_null (go x) 2348 | Is_not_null x -> Is_not_null (go x) 2349 | Cast (x, t) -> Cast (go x, t) 2350 | Collate (x, c) -> Collate (go x, c) 2351 | Distinct_agg (n, x) -> Distinct_agg (n, go x) 2352 | Binary (op, a, b) -> Binary (op, go a, go b) 2353 | Is (a, b) -> Is (go a, go b) 2354 | Filter (a, b) -> Filter (go a, go b) 2355 | In_list (x, l) -> In_list (go x, List.map go l) 2356 | Func (n, args) -> Func (n, List.map go args) 2357 | Ordered_agg (x, ob) -> 2358 Ordered_agg (go x, List.map (fun (e, d) -> (go e, d)) ob) 2359 | Window (x, w) -> 2360 Window 2361 ( go x, 2362 { 2363 w with 2364 partition = List.map go w.partition; 2365 order = List.map (fun (e, d) -> (go e, d)) w.order; 2366 } ) 2367 | Case (base, br, els) -> 2368 Case 2369 ( Option.map go base, 2370 List.map (fun (c, r) -> (go c, go r)) br, 2371 Option.map go els ) 2372 in 2373 { 2374 s with 2375 cols = 2376 List.map (fun (rc : result_col) -> { rc with expr = go rc.expr }) s.cols; 2377 where = Option.map go s.where; 2378 group_by = List.map go s.group_by; 2379 having = Option.map go s.having; 2380 order_by = List.map (fun (e, d) -> (go e, d)) s.order_by; 2381 joins = List.map (fun (j : join) -> { j with on = go j.on }) s.joins; 2382 } 2383 2384(* The join-row environments of a bare [FROM from joins] (no projection, no 2385 WHERE) -- each is a [binding list] usable as an outer [~scope]. This drives 2386 [UPDATE ... FROM], whose auxiliary tables are visible to SET and WHERE. *) 2387let from_bindings ?(scope = []) cat params from joins = 2388 let s = 2389 { 2390 ctes = []; 2391 distinct = false; 2392 cols = [ { expr = Star; alias = None } ]; 2393 from; 2394 joins; 2395 where = None; 2396 group_by = []; 2397 having = None; 2398 compound = []; 2399 order_by = []; 2400 limit = None; 2401 offset = None; 2402 } 2403 in 2404 List.map bindings_of_frames (combine ~scope cat params s) 2405 2406(* The access path the executor takes, one line per table. Structural (no 2407 parameter values): the from table uses an index when one covers its 2408 equalities (the same columns {!from_access} narrows on, so EXPLAIN and run 2409 agree), and joins are nested-loop scans. *) 2410let explain cat (s : Ast.select) = 2411 let line tbl = function 2412 | Some descr -> Fmt.str "SEARCH %s USING %s" tbl descr 2413 | None -> Fmt.str "SCAN %s" tbl 2414 in 2415 let from_alias = 2416 match s.from with 2417 | Unit -> "" 2418 | Table tr -> Option.value tr.alias ~default:tr.name 2419 | Select { fs_alias; _ } -> Option.value fs_alias ~default:"" 2420 | Values _ -> "" 2421 | Table_function { tf_name; tf_alias; _ } -> 2422 Option.value tf_alias ~default:tf_name 2423 in 2424 let from_line = 2425 match s.from with 2426 | Unit -> "SCAN (constant)" 2427 | Table tr -> 2428 line tr.name (cat.index_for tr.name (eq_columns from_alias s.where)) 2429 | Select _ -> "SCAN (subquery)" 2430 | Values _ -> "SCAN (values)" 2431 | Table_function { tf_name; _ } -> Fmt.str "SCAN (%s)" tf_name 2432 in 2433 let join_line (j : Ast.join) = 2434 line j.table.name 2435 (cat.index_for j.table.name (join_eq_columns (join_alias j) j.on)) 2436 in 2437 (* Explain the same order the executor runs (cost-based for inner joins). *) 2438 String.concat "\n" 2439 (from_line :: List.map join_line (plan_joins cat from_alias s.joins)) 2440 2441(* ── Write-path evaluation ────────────────────────────────────── *) 2442 2443(* Evaluate an expression with no row in scope -- for INSERT ... VALUES, whose 2444 tuples are constants and bound parameters. A column reference here raises (no 2445 table is in scope), which is the correct error. *) 2446let eval_const ?(scope = []) cat params e = 2447 eval cat params (frames_of_scope scope) e 2448 2449(* Does [vals] (a row of [table], aligned to [cols]) satisfy [where]? Used to 2450 pick the rows a DELETE removes. No WHERE matches every row. *) 2451let row_matches ?(scope = []) ?rowid cat params table cols where vals = 2452 match where with 2453 | None -> true 2454 | Some w -> 2455 let env = Pos (table, cols, vals, rowid) :: frames_of_scope scope in 2456 tri_of (eval cat params env w) = T 2457 2458(* Evaluate [e] against a single [table] row ([vals] aligned to [cols]) -- the 2459 right-hand side of an UPDATE ... SET assignment, which may read the row's 2460 current column values. *) 2461let eval_in_row ?(scope = []) ?rowid cat params table cols vals e = 2462 eval cat params (Pos (table, cols, vals, rowid) :: frames_of_scope scope) e 2463 2464(* Evaluate an UPSERT DO UPDATE expression: the conflicting row's current values 2465 are in scope under [table], and the would-be-inserted values under the 2466 [excluded] pseudo-table (both aligned to [cols]). *) 2467let eval_upsert ?(scope = []) cat params ~table ~cols ~current ~excluded e = 2468 eval cat params 2469 (Pos (table, cols, current, None) 2470 :: Pos ("excluded", cols, excluded, None) 2471 :: frames_of_scope scope) 2472 e 2473 2474(* Project a RETURNING column list over each affected [row] of [table] ([cols] 2475 are the table's column names, aligned to [row]). [*] expands to the whole 2476 row; other items are expressions over the row. *) 2477let returning_rows cat params ~table ~cols returning rows = 2478 List.map 2479 (fun row -> 2480 let env = [ Pos (table, cols, row, None) ] in 2481 List.concat_map 2482 (fun (rc : Ast.result_col) -> 2483 match rc.expr with 2484 | Star | Qualified_star _ -> row 2485 | e -> [ eval cat params env e ]) 2486 returning) 2487 rows