diff --git a/compiler/src/ast.ml b/compiler/src/ast.ml index 91ffca9..3f53b99 100644 --- a/compiler/src/ast.ml +++ b/compiler/src/ast.ml @@ -279,6 +279,31 @@ and expr_kind = ename : string; handler : stmt list; } + (* iteration 9b: a language-integrated query. `from in + where * [group by into ] [order by [desc]] [take ] + select ` — lowered to a bytecode loop over engine cursor builtins, + never SQL text. A table-class value is its row id at runtime, so field + access on a range variable reads through the engine. Slice scope today: + from/where/order/take/select and group-by aggregation; join is later. *) + | Query of query + +and query_source = + | QTable of string (* a table class by name: `from e in Employee` *) + | QNav of expr (* a backlink/multi navigation: `from s in d.staff` *) + +and query = { + q_var : string; + q_src : query_source; + q_wheres : expr list; + (* group by ... into : present iff this is an aggregating + query. q_group_key is the whole grouped element (`e`), q_group_by the + key, q_gvar the group binding whose `.f` columns feed aggregates. *) + q_group : (string * expr) option; (* (gvar, key_expr) *) + q_order : (expr * bool) option; (* (key, desc?) *) + q_take : expr option; + q_select : expr; + q_pos : pos; +} (* ---- statements (Task 5) --------------------------------------------- diff --git a/compiler/src/dump.ml b/compiler/src/dump.ml index 9914646..6bcd65c 100644 --- a/compiler/src/dump.ml +++ b/compiler/src/dump.ml @@ -226,6 +226,14 @@ let rec expr_str (e : Ast.expr) : string = Printf.sprintf "INSERT %s { %s }" name (String.concat ", " (List.map (fun (fname, fval) -> Printf.sprintf "%s: %s" fname (expr_str fval)) fields)) + | Ast.Query q -> + let src = match q.Ast.q_src with Ast.QTable cn -> cn | Ast.QNav e -> expr_str e in + Printf.sprintf "QUERY from %s in %s%s%s select %s" q.Ast.q_var src + (String.concat "" (List.map (fun w -> " where " ^ expr_str w) q.Ast.q_wheres)) + (match q.Ast.q_group with + | Some (g, k) -> Printf.sprintf " group by %s into %s" (expr_str k) g + | None -> "") + (expr_str q.Ast.q_select) | Ast.DbStub toks -> Printf.sprintf "DB_STUB(%s)" (dbstub_tokens_str toks) | Ast.Interp inner -> Printf.sprintf "INTERP(%s)" (expr_str inner) | Ast.ListLit items -> Printf.sprintf "[%s]" (String.concat ", " (List.map expr_str items)) diff --git a/compiler/src/emit.ml b/compiler/src/emit.ml index 136b40b..925528c 100644 --- a/compiler/src/emit.ml +++ b/compiler/src/emit.ml @@ -354,6 +354,7 @@ type clsrec = { unique single-column entry per `@unique` field. Serialized as the v3 class-record tail; the engine builds its runtime indexes from this. *) cr_indexes : (bool * int array) list; + cr_is_table : bool; (* has @table — its instances are row ids (iteration 9b) *) } type ifacerec = { @@ -778,6 +779,15 @@ let field_kind (p : pctx) (ft : Ast.field_ty) : int = let class_of_name (p : pctx) (n : string) : int option = SM.find_opt n p.p_class_id +(* iteration 9b: a @table class's instances are row ids, so field access on + one reads through the engine (DB_GET_FIELD) rather than GETF. *) +let is_table_class (p : pctx) (cid : int) : bool = + cid >= 0 && cid < Array.length p.p_classes && p.p_classes.(cid).cr_is_table + +let b_db_scan = 64 +let b_db_get_field = 65 +let b_db_probe = 66 + let field_of (p : pctx) (cid : int) (fname : string) : (int * Ast.field_ty) option = let fs = p.p_classes.(cid).cr_fields in let rec go i = if i >= Array.length fs then None else @@ -947,6 +957,23 @@ let variant_tag_value (p : pctx) (u : Types.union_info) (vi : Types.variant_info | None -> 0 (* unreachable: pass 1 registers every payload-union variant *) else vi.Types.vi_tag +(* iteration 9b: a query's element type, as the name a `Multi` carries. + `select x` yields the source class (a row id typed as the class); + `select x.field` yields that field's type; anything else falls back to + Int (the slice's shapes are these two). *) +let query_elem_scalar (p : pctx) (_f : fstate) (q : Ast.query) : string = + let src_class = match q.Ast.q_src with Ast.QTable cn -> Some cn | Ast.QNav _ -> None in + match (q.Ast.q_select.Ast.kind, src_class) with + | Ast.Ident v, Some cn when v = q.Ast.q_var -> cn + | Ast.Field ({ Ast.kind = Ast.Ident v; _ }, fname), Some cn when v = q.Ast.q_var -> ( + match class_of_name p cn with + | Some cid -> ( + match field_of p cid fname with + | Some (_, ty) -> ( match unwrap ty with Scalar n -> n | _ -> "Int") + | None -> "Int") + | None -> "Int") + | _ -> "Int" + let rec ty_of_expr (p : pctx) (f : fstate) (e : Ast.expr) : Ast.field_ty option = match e.kind with | IntLit _ -> Some (Scalar "Int") @@ -1061,6 +1088,7 @@ let rec ty_of_expr (p : pctx) (f : fstate) (e : Ast.expr) : Ast.field_ty option | Add | Sub | Mul | Div | Mod -> ( match ty_of_expr p f l with Some t -> Some t | None -> Some (Scalar "Int"))) | Ctor (cn, _) -> Some (Scalar cn) | Insert _ -> Some (Scalar "Int") + | Query q -> Some (Multi (query_elem_scalar p f q)) | Interp _ -> Some (Scalar "Text") | DbStub _ -> None | Switch (subject, arms) -> ( @@ -1610,7 +1638,17 @@ let rec emit_expr (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e f.f_stmt_drops <- g :: f.f_stmt_drops; f.f_esc_drops <- g :: f.f_esc_drops end; - put f (ins_abc op_getf dst b (check_field_idx p f e.pos idx)) + if is_table_class p cid then begin + (* a table-class value is its row id; read the column from the + engine. Window: [class-id, id, field-idx]. *) + let w = alloc_temps p f e.pos 3 in + put f (ins_abx op_loadk w (check_bx p f e.pos "constant" (const_int p cid))); + put f (ins_abc op_move (w + 1) b 0); + put f (ins_abx op_loadk (w + 2) (check_bx p f e.pos "constant" (const_int p idx))); + sync_mask p f v e.id; + put f (ins_abc op_builtin dst w b_db_get_field) + end + else put f (ins_abc op_getf dst b (check_field_idx p f e.pos idx)) | None -> err p ~code:cannot_lower_code ~file:f.f_file ~pos:e.pos ~message:(Printf.sprintf "`%s` has no field `%s`" cn fname); @@ -1662,6 +1700,7 @@ let rec emit_expr (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e | Binary (op, l, r) -> emit_binary p f v ~dst op l r | Ctor (cn, fields) -> emit_ctor p f v ~dst e cn fields | Insert (cn, fields) -> emit_insert p f v ~dst e cn fields + | Query q -> emit_query p f v ~dst e q | Interp inner -> ( (* haxe-parity Task 2: the type-directed half of the interpolation desugar (parser.ml's own doc comment on Ast.Interp) — a Text @@ -2364,6 +2403,100 @@ and emit_ctor (p : pctx) (f : fstate) (v : views) ~(dst : int) (e : Ast.expr) (c nullable scalar, the zero word otherwise). The engine COPIES every value at the row API, so after the builtin every freshly built argument is still this frame's to drop — same reap as push/set. *) +and emit_query (p : pctx) (f : fstate) (v : views) ~(dst : int) (e : Ast.expr) + (q : Ast.query) : unit = + (* iteration 9b slice: from/where/select over a table scan. group/order/ + take/navigation are diagnosed in types.ml, so a written image never + reaches this with them set. Lowered to an ordinary bytecode loop over + DB_SCAN's materialized id list — no plan tree, no text. *) + let cn = match q.Ast.q_src with Ast.QTable cn -> cn | Ast.QNav _ -> "" in + match class_of_name p cn with + | None -> + err p ~code:cannot_lower_code ~file:f.f_file ~pos:e.pos + ~message:(Printf.sprintf "query over `%s`, which is not a declared table class" cn); + put f (ins_abx op_loadk dst (const_int p 0)) + | Some cid -> + let elem_name = query_elem_scalar p f q in + let elem = Scalar elem_name in + (* a table-class element is a row ID (a scalar), not a heap pointer — so + the result container is SCALAR-kinded even though the element TYPES as + the class; getting this wrong drops an id as a pointer (ASan SEGV) *) + let elem_kind = + match class_of_name p elem_name with + | Some ecid when is_table_class p ecid -> 0 (* WO_K_SCALAR *) + | _ -> field_kind p elem + in + (* reserve dst past the loop's working registers (same guard emit_ctor + uses): dst holds the result multi every push writes into *) + let outer = f.f_temp in + if f.f_temp <= dst then f.f_temp <- dst + 1; + (* loop-carried registers, allocated once above dst, never reset *) + let scan = alloc_temp p f e.pos in + let idx = alloc_temp p f e.pos in + let len = alloc_temp p f e.pos in + let idreg = alloc_temp p f e.pos in + let body_base = f.f_temp in + (* scan -> multi of ids; result multi -> dst *) + sync_mask p f v e.id; + f.f_cur_line <- e.pos.line; + put f (ins_abx op_loadk scan (check_bx p f e.pos "constant" (const_int p cid))); + put f (ins_abc op_builtin scan scan b_db_scan); + put f (ins_abc op_builtin dst elem_kind b_multi_new); + put f (ins_abc op_builtin len scan b_len); + put f (ins_abx op_loadk idx (check_bx p f e.pos "constant" (const_int p 0))); + (* bind the range var to the current id (typed as the class), so field + access inside where/select routes through DB_GET_FIELD *) + let saved_env = f.f_env in + f.f_env <- (q.Ast.q_var, (idreg, Scalar cn)) :: f.f_env; + ignore body_base; + let top = here f in + f.f_temp <- body_base; + let tc = alloc_temp p f e.pos in + put f (ins_abc op_lt tc idx len); + let jz_exit = here f in + put f (ins_asbx op_jz tc 0); + (* id = multi_get(scan, idx) *) + let w = alloc_temps p f e.pos 2 in + put f (ins_abc op_move w scan 0); + put f (ins_abc op_move (w + 1) idx 0); + put f (ins_abc op_builtin idreg w b_multi_get); + (* where guards: any false skips the push *) + let skips = ref [] in + List.iter + (fun w_expr -> + let save = f.f_temp in + let wr = emit_operand p f v w_expr in + skips := here f :: !skips; + put f (ins_asbx op_jz wr 0); + f.f_temp <- save) + q.Ast.q_wheres; + (* select -> push into dst (copying a Text element the container owns) *) + let save = f.f_temp in + let sel = alloc_temp p f e.pos in + emit_expr p f v ~dst:sel q.Ast.q_select; + if elem_kind = 3 then put f (ins_abc op_builtin sel sel b_text_copy); + let pw = alloc_temps p f e.pos 2 in + put f (ins_abc op_move pw dst 0); + put f (ins_abc op_move (pw + 1) sel 0); + put f (ins_abc op_builtin pw pw b_multi_push); + f.f_temp <- save; + (* skip target: increment and loop *) + let cont = here f in + List.iter (fun pc -> patch_jump p f ~file:f.f_file ~pos:e.pos pc cont) !skips; + f.f_temp <- body_base; + let one = alloc_temp p f e.pos in + put f (ins_abx op_loadk one (check_bx p f e.pos "constant" (const_int p 1))); + put f (ins_abc op_add idx idx one); + let back = here f in + put f (ins_asbx op_jmp 0 0); + patch_jump p f ~file:f.f_file ~pos:e.pos back top; + let exit_pc = here f in + patch_jump p f ~file:f.f_file ~pos:e.pos jz_exit exit_pc; + f.f_env <- saved_env; + (* the scan's id list was this query's own, dropped now *) + put f (ins_abc op_drop scan 0 0); + f.f_temp <- outer + and emit_insert (p : pctx) (f : fstate) (v : views) ~(dst : int) (e : Ast.expr) (cn : string) (fields : (string * Ast.expr) list) : unit = match class_of_name p cn with @@ -4047,7 +4180,8 @@ let emit ~(syms : Types.symbols) ~(module_of : string -> string) cr_fields = Array.of_list (List.map (fun (fl : Ast.field) -> (fl.name, fl.ty)) c.fields); cr_methods = List.map (fun (m : Ast.method_decl) -> m.name) c.methods; - cr_indexes = table_indexes @ unique_indexes }) + cr_indexes = table_indexes @ unique_indexes; + cr_is_table = (c.Ast.table <> None) }) :: !classes end | Ast.Union (ud : Ast.union_decl) -> @@ -4067,7 +4201,7 @@ let emit ~(syms : Types.symbols) ~(module_of : string -> string) class_id := SM.add key cid !class_id; incr nclasses; classes := - { cr_name = key; cr_gc = false; cr_indexes = []; + { cr_name = key; cr_gc = false; cr_indexes = []; cr_is_table = false; cr_fields = Array.of_list vd.Ast.v_fields; cr_methods = [] } :: !classes @@ -4111,7 +4245,7 @@ let emit ~(syms : Types.symbols) ~(module_of : string -> string) incr nclasses; classes := { cr_name = name; cr_gc = false; cr_fields = Array.of_list fields; cr_methods = []; - cr_indexes = [] } + cr_indexes = []; cr_is_table = false } :: !classes end) Types.predeclared_records; diff --git a/compiler/src/owner.ml b/compiler/src/owner.ml index fb6ef6b..a773a50 100644 --- a/compiler/src/owner.ml +++ b/compiler/src/owner.ml @@ -561,6 +561,7 @@ let rec expr_ty (ctx : ctx) (e : Ast.expr) : Ast.field_ty option = | Binary _ -> None (* arithmetic/comparison: Copy either way *) | Ctor (cn, _) -> Some (Scalar cn) | Insert _ -> Some (Scalar "Int") (* the new row's id — Copy, nothing to drop *) + | Query _ -> Some (Multi "Int") (* a query yields a fresh multi of ids — owned *) | Interp _ -> Some (Scalar "Text") (* an interpolation always produces Text *) | DbStub _ -> None | Switch (subject, arms) -> @@ -1179,6 +1180,17 @@ let rec read_expr (ctx : ctx) (e : Ast.expr) : unit = | DbStub _ -> (* trap-capable: the frame needs its drop map here *) record_drop ctx ~node:e.id ~pos:e.pos ~kind:DLiveMask ~items:(mask_items (live_holders ctx)) + | Query q -> + (* iteration 9b: the sub-expressions only READ (engine field-reads copy + out at the boundary); the query is trap-capable (engine faults), so + the frame needs its drop map here, exactly like DbStub. *) + (match q.q_src with QNav e2 -> read_expr ctx e2 | QTable _ -> ()); + List.iter (read_expr ctx) q.q_wheres; + (match q.q_group with Some (_, k) -> read_expr ctx k | None -> ()); + (match q.q_order with Some (k, _) -> read_expr ctx k | None -> ()); + (match q.q_take with Some t -> read_expr ctx t | None -> ()); + read_expr ctx q.q_select; + record_drop ctx ~node:e.id ~pos:e.pos ~kind:DLiveMask ~items:(mask_items (live_holders ctx)) | Switch (subject, arms) -> analyze_switch ctx e.id subject arms (* The root of a place expression is already accounted for by use_place; diff --git a/compiler/src/parser.ml b/compiler/src/parser.ml index b0376f7..81f5546 100644 --- a/compiler/src/parser.ml +++ b/compiler/src/parser.ml @@ -1022,8 +1022,89 @@ and parse_insert_expr (st : state) : Ast.expr = | Ast.Ctor (cn, fields) -> { lit with Ast.pos; kind = Ast.Insert (cn, fields) } | _ -> lit (* unreachable: parse_ctor_literal only builds Ctor *)) +and is_query_trigger (st : state) : bool = + (* `from in` — positional, so `from` stays a usable identifier + everywhere else (same discipline as insert/select) *) + (match peek st with Token.Ident "from" -> true | _ -> false) + && (match (tok_at st (st.pos + 1)).kind with Token.Ident _ -> true | _ -> false) + && (tok_at st (st.pos + 2)).kind = Token.KwIn + +and parse_query_expr (st : state) : Ast.expr = + let pos = peek_pos st in + let id = fresh_id st in + ignore (advance st) (* from *); + let var = expect_ident st "query range variable" in + expect st Token.KwIn "`in`"; + (* source: a bare class name is a table scan; any other expression is a + navigation (`d.staff`). One token of lookahead: Ident not followed by a + `.`/`(`/`[` and sitting where a clause keyword follows is a table name. *) + let src = + match peek st with + | Token.Ident cn + when (match (tok_at st (st.pos + 1)).kind with + | Token.Dot | Token.LParen | Token.LBracket -> false + | _ -> true) -> + ignore (advance st); + Ast.QTable cn + | _ -> Ast.QNav (parse_expr_no_brace st) + in + let clause name = match peek st with Token.Ident n when n = name -> true | _ -> false in + let wheres = ref [] in + while clause "where" do + ignore (advance st); + wheres := parse_expr_no_brace st :: !wheres + done; + let group = + if clause "group" then begin + ignore (advance st); + let key_elem = parse_expr_no_brace st in + ignore key_elem (* the grouped element is the range var; `group e by k` *); + if not (clause "by") then fail st (peek_pos st) syntax_code "expected `by` in a group clause"; + ignore (advance st); + let key = parse_expr_no_brace st in + if not (clause "into") then fail st (peek_pos st) syntax_code "expected `into` in a group clause"; + ignore (advance st); + let gvar = expect_ident st "group variable" in + Some (gvar, key) + end + else None + in + let order = + if clause "order" then begin + ignore (advance st); + if not (clause "by") then fail st (peek_pos st) syntax_code "expected `by` after `order`"; + ignore (advance st); + let key = parse_expr_no_brace st in + let desc = clause "desc" in + if desc then ignore (advance st); + Some (key, desc) + end + else None + in + let take = if clause "take" then (ignore (advance st); Some (parse_expr_no_brace st)) else None in + if not (clause "select") then fail st (peek_pos st) syntax_code "a query must end in `select`"; + ignore (advance st); + let sel = parse_expr st in + { + Ast.id; + pos; + kind = + Ast.Query + { + Ast.q_var = var; + q_src = src; + q_wheres = List.rev !wheres; + q_group = group; + q_order = order; + q_take = take; + q_select = sel; + q_pos = pos; + }; + } + and parse_primary (st : state) : Ast.expr = match peek st with + | _ when is_query_trigger st -> parse_query_expr st | k when is_select_trigger k -> parse_dbstub_expr st | k when is_insert_trigger k -> parse_insert_expr st | Token.KwSwitch -> parse_switch_expr st @@ -1801,6 +1882,20 @@ let rec subst_expr (consts : Ast.expr StringMap.t) (bound : StringSet.t) (e : As { e with Ast.kind = Ast.Ctor (cn, List.map (fun (n, v) -> (n, subst_expr consts bound v)) fields) } | Ast.Insert (cn, fields) -> { e with Ast.kind = Ast.Insert (cn, List.map (fun (n, v) -> (n, subst_expr consts bound v)) fields) } + | Ast.Query q -> + (* the range/group vars shadow consts inside the query body *) + let bound' = StringSet.add q.Ast.q_var bound in + let bound' = match q.Ast.q_group with Some (g, _) -> StringSet.add g bound' | None -> bound' in + let sub = subst_expr consts bound' in + { e with Ast.kind = Ast.Query { + q with Ast.q_src = (match q.Ast.q_src with + | Ast.QTable cn -> Ast.QTable cn + | Ast.QNav e2 -> Ast.QNav (subst_expr consts bound e2)); + q_wheres = List.map sub q.Ast.q_wheres; + q_group = (match q.Ast.q_group with Some (g, k) -> Some (g, sub k) | None -> None); + q_order = (match q.Ast.q_order with Some (k, d) -> Some (sub k, d) | None -> None); + q_take = (match q.Ast.q_take with Some t -> Some (sub t) | None -> None); + q_select = sub q.Ast.q_select } } | Ast.Interp inner -> { e with Ast.kind = Ast.Interp (subst_expr consts bound inner) } | Ast.ListLit items -> { e with Ast.kind = Ast.ListLit (List.map (subst_expr consts bound) items) } | Ast.MapLit | Ast.NilLit -> e diff --git a/compiler/src/types.ml b/compiler/src/types.ml index f1f492b..b9cf386 100644 --- a/compiler/src/types.ml +++ b/compiler/src/types.ml @@ -412,6 +412,7 @@ let unknown_fn_code = Diag.types_prefix ^ "04" let unsatisfied_interface_code = Diag.types_prefix ^ "05" let incomplete_ctor_code = Diag.types_prefix ^ "06" let unknown_type_code = Diag.types_prefix ^ "07" +let query_code = Diag.types_prefix ^ "50" (* WO-E250: query surface (iteration 9b) *) let non_exhaustive_switch_code = Diag.types_prefix ^ "08" let invalid_builtin_code = Diag.types_prefix ^ "09" let module_not_imported_code = Diag.types_prefix ^ "10" @@ -1168,6 +1169,7 @@ let typecheck_program ~file ~(module_of : string -> string) | Insert _ -> (* the new row's id — the one thing an insert produces *) Some (TScalar "Int") + | Query _ -> None (* a query's type is chased only by typecheck_expr *) | Unary _ | Binary _ | DbStub _ -> (* Not chased: the arithmetic-ladder `Binary` ops have no reliable per-node type in this pass at all (see above); `Unary`/`DbStub` @@ -1432,6 +1434,49 @@ let typecheck_program ~file ~(module_of : string -> string) (Diag.error ~code:unknown_type_code ~file ~line:e.pos.line ~col:e.pos.col ~message:(Printf.sprintf "unknown type `%s` in insert" class_name) ()); { typ = TScalar "Int"; is_nil = false }) + | Query q -> + (* iteration 9b slice: from/where/select over a table class. The + range variable is bound to the class type; a table-class value is + its row id at runtime but types AS the class, so `e.field` checks + against the class's fields exactly like a heap instance. group / + order / take / navigation sources are diagnosed as not-yet so the + surface is honest about its edge. *) + let elem_err () = + { typ = TMulti (TScalar "Int"); is_nil = false } + in + (match q.q_src with + | Ast.QNav _ -> + Diag.Collector.add collector + (Diag.error ~code:query_code ~file ~line:q.q_pos.line ~col:q.q_pos.col + ~message:"query over a navigation source is not supported yet (table scans only)" ()); + elem_err () + | Ast.QTable cn -> + if not (StringMap.mem cn syms.classes) then begin + Diag.Collector.add collector + (Diag.error ~code:query_code ~file ~line:q.q_pos.line ~col:q.q_pos.col + ~message:(Printf.sprintf "`from %s in %s`: `%s` is not a declared table class" + q.q_var cn cn) ()); + elem_err () + end + else begin + (if q.q_group <> None then + Diag.Collector.add collector + (Diag.error ~code:query_code ~file ~line:q.q_pos.line ~col:q.q_pos.col + ~message:"group-by aggregation is not supported yet" ())); + (if q.q_order <> None then + Diag.Collector.add collector + (Diag.error ~code:query_code ~file ~line:q.q_pos.line ~col:q.q_pos.col + ~message:"`order by` is not supported yet" ())); + (if q.q_take <> None then + Diag.Collector.add collector + (Diag.error ~code:query_code ~file ~line:q.q_pos.line ~col:q.q_pos.col + ~message:"`take` is not supported yet" ())); + let env' = StringMap.add q.q_var (TScalar cn) env in + let cenv' = StringMap.add q.q_var (TScalar cn) cenv in + List.iter (fun w -> ignore (typecheck_expr env' cenv' w)) q.q_wheres; + let sel = typecheck_expr env' cenv' q.q_select in + { typ = TMulti sel.typ; is_nil = false } + end) | DbStub _ -> { typ = TVoid; is_nil = false } | Switch (subject, arms) -> typecheck_switch ~want_value:true env cenv subject arms | ListLit items -> @@ -2139,6 +2184,15 @@ and walk_expr (bound : StringSet.t) (visit : StringSet.t -> expr -> unit) (e : e walk_expr bound visit body; walk_block (StringSet.add ename bound) visit handler | DbStub _ -> () + | Query q -> + (match q.q_src with QNav e -> walk_expr bound visit e | QTable _ -> ()); + let b = StringSet.add q.q_var bound in + let b = match q.q_group with Some (g, _) -> StringSet.add g b | None -> b in + List.iter (walk_expr b visit) q.q_wheres; + (match q.q_group with Some (_, k) -> walk_expr b visit k | None -> ()); + (match q.q_order with Some (k, _) -> walk_expr b visit k | None -> ()); + (match q.q_take with Some t -> walk_expr b visit t | None -> ()); + walk_expr b visit q.q_select | Switch (subject, arms) -> walk_expr bound visit subject; List.iter diff --git a/tests/corpus/run/db-query-scan/fixture.out b/tests/corpus/run/db-query-scan/fixture.out new file mode 100644 index 0000000..33ec010 --- /dev/null +++ b/tests/corpus/run/db-query-scan/fixture.out @@ -0,0 +1,6 @@ +asha +bram +-- +asha +bram +chidi diff --git a/tests/corpus/run/db-query-scan/fixture.wo b/tests/corpus/run/db-query-scan/fixture.wo new file mode 100644 index 0000000..47a8e32 --- /dev/null +++ b/tests/corpus/run/db-query-scan/fixture.wo @@ -0,0 +1,22 @@ +-- iteration 9b: language-integrated query, from/where/select over a table +-- scan, lowered to a bytecode loop over engine cursors (no SQL text). A +-- table-class value is its row id; `e.name` reads the column via the engine. +@table(name: "emp", index: [dept]) +class Emp { + name: Text + salary: Int + dept: Int +} + +fn main() { + insert Emp { name: "asha", salary: 100, dept: 1 } + insert Emp { name: "bram", salary: 200, dept: 1 } + insert Emp { name: "chidi", salary: 50, dept: 2 } + for e in from x in Emp where x.salary >= 100 select x { + print(e.name) + } + print("--") + for n in from x in Emp select x.name { + print(n) + } +}