SQL frontend over the catalog engine
0

Configure Feed

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

ocaml-sql / test / test_engine.ml
5.8 kB 147 lines
1(* The statement engine runs SQL end to end over a Catalog.Spi backend with no 2 sqlite involved: CREATE TABLE, INSERT, and SELECT against the in-memory 3 reference store. This is the proof that execution lives in [sql] over the 4 storage contract, not in the backend. *) 5 6module E = Sql.Engine 7module Spi = Catalog.Spi 8 9let rows = function E.Rows r -> r | E.Done -> [] 10 11(* a result row of an int and a text column, for exact comparison *) 12let pair = function 13 | [ Catalog.Value.Int a; Catalog.Value.Text b ] -> (Int64.to_int a, b) 14 | _ -> (-1, "?") 15 16let test_create_insert_select () = 17 let cat = Catalog.of_mem [] in 18 ignore (E.exec cat "CREATE TABLE t (a INTEGER, b TEXT)"); 19 Alcotest.(check (list string)) 20 "CREATE TABLE registered the columns" [ "a"; "b" ] (Spi.columns cat "t"); 21 Alcotest.(check bool) 22 "and listed the table" true 23 (List.mem "t" (Spi.tables cat)); 24 ignore (E.exec cat "INSERT INTO t VALUES (1, 'x'), (3, 'z'), (2, 'y')"); 25 Alcotest.(check int) 26 "INSERT appended every row" 3 27 (List.length (Spi.rows cat "t")); 28 let got = 29 List.map pair 30 (rows (E.exec cat "SELECT a, b FROM t WHERE a >= 2 ORDER BY a")) 31 in 32 Alcotest.(check (list (pair int string))) 33 "SELECT projects, filters and orders on the engine" 34 [ (2, "y"); (3, "z") ] 35 got 36 37(* UPDATE and DELETE run on the engine over Spi: the WHERE filter and the SET 38 expressions are evaluated on the register VM per row, written through 39 Spi.update / Spi.delete. *) 40let test_update_delete () = 41 let cat = Catalog.of_mem [] in 42 ignore (E.exec cat "CREATE TABLE t (a INTEGER, b INTEGER)"); 43 ignore (E.exec cat "INSERT INTO t VALUES (1, 10), (2, 20), (3, 30), (4, 40)"); 44 ignore (E.exec cat "UPDATE t SET b = b + 1 WHERE a >= 3"); 45 let int_pair = function 46 | [ Catalog.Value.Int a; Catalog.Value.Int b ] -> 47 (Int64.to_int a, Int64.to_int b) 48 | _ -> (-1, -1) 49 in 50 let after_update = 51 List.map int_pair (rows (E.exec cat "SELECT a, b FROM t ORDER BY a")) 52 in 53 Alcotest.(check (list (pair int int))) 54 "SET b = b + 1 applied only to a >= 3" 55 [ (1, 10); (2, 20); (3, 31); (4, 41) ] 56 after_update; 57 ignore (E.exec cat "DELETE FROM t WHERE b > 25"); 58 let after_delete = 59 List.map int_pair (rows (E.exec cat "SELECT a, b FROM t ORDER BY a")) 60 in 61 Alcotest.(check (list (pair int int))) 62 "DELETE removed the b > 25 rows" 63 [ (1, 10); (2, 20) ] 64 after_delete 65 66let test_drop_table () = 67 let cat = Catalog.of_mem [] in 68 ignore (E.exec cat "CREATE TABLE g (x INTEGER)"); 69 Alcotest.(check bool) "created" true (List.mem "g" (Spi.tables cat)); 70 ignore (E.exec cat "DROP TABLE g"); 71 Alcotest.(check bool) "dropped" false (List.mem "g" (Spi.tables cat)) 72 73(* A nested-loop join over two Spi base tables, with the [ON] folded into the 74 scan filter and the projection resolving columns across both sources. *) 75let test_join () = 76 let cat = Catalog.of_mem [] in 77 ignore (E.exec cat "CREATE TABLE a (k INTEGER, x TEXT)"); 78 ignore (E.exec cat "CREATE TABLE b (k INTEGER, y TEXT)"); 79 ignore (E.exec cat "INSERT INTO a VALUES (1, 'ax'), (2, 'a2')"); 80 ignore (E.exec cat "INSERT INTO b VALUES (1, 'by'), (2, 'b2')"); 81 let got = 82 List.map 83 (function 84 | [ Catalog.Value.Text x; Catalog.Value.Text y ] -> (x, y) 85 | _ -> ("?", "?")) 86 (rows 87 (E.exec cat "SELECT a.x, b.y FROM a JOIN b ON a.k = b.k ORDER BY a.k")) 88 in 89 Alcotest.(check (list (pair string string))) 90 "a.x, b.y on the matched key" 91 [ ("ax", "by"); ("a2", "b2") ] 92 got 93 94(* Aggregates, compounds, and WITH all run on the engine now -- the complete 95 read dialect over Spi. *) 96let test_aggregate_compound_cte () = 97 let cat = Catalog.of_mem [] in 98 ignore (E.exec cat "CREATE TABLE t (k TEXT, v INTEGER)"); 99 ignore 100 (E.exec cat "INSERT INTO t VALUES ('a', 1), ('a', 2), ('b', 10), ('b', 20)"); 101 Alcotest.(check (list (pair string int))) 102 "GROUP BY with HAVING and ORDER BY" 103 [ ("b", 30) ] 104 (List.map 105 (function 106 | [ Catalog.Value.Text k; Catalog.Value.Int s ] -> (k, Int64.to_int s) 107 | _ -> ("?", -1)) 108 (rows 109 (E.exec cat 110 "SELECT k, sum(v) FROM t GROUP BY k HAVING sum(v) > 5 ORDER BY k"))); 111 Alcotest.(check (list int)) 112 "UNION dedups and orders" [ 1; 2; 10; 20; 99 ] 113 (List.map 114 (function [ Catalog.Value.Int n ] -> Int64.to_int n | _ -> -1) 115 (rows 116 (E.exec cat 117 "SELECT v FROM t UNION SELECT * FROM (VALUES (99)) ORDER BY v"))); 118 Alcotest.(check (list int)) 119 "a recursive WITH counts to 4" [ 1; 2; 3; 4 ] 120 (List.map 121 (function [ Catalog.Value.Int n ] -> Int64.to_int n | _ -> -1) 122 (rows 123 (E.exec cat 124 "WITH RECURSIVE c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c \ 125 WHERE n<4) SELECT n FROM c"))) 126 127(* An unsupported shape fails loud rather than silently doing nothing. *) 128let test_unsupported_is_loud () = 129 let cat = Catalog.of_mem [] in 130 ignore (E.exec cat "CREATE TABLE t (x)"); 131 Alcotest.check_raises "INSERT DEFAULT VALUES is rejected, not silently wrong" 132 (Failure "engine: INSERT DEFAULT VALUES is not supported yet") (fun () -> 133 ignore (E.exec cat "INSERT INTO t DEFAULT VALUES")) 134 135let suite = 136 ( "engine", 137 [ 138 Alcotest.test_case "CREATE / INSERT / SELECT over Spi" `Quick 139 test_create_insert_select; 140 Alcotest.test_case "UPDATE / DELETE over Spi" `Quick test_update_delete; 141 Alcotest.test_case "DROP TABLE" `Quick test_drop_table; 142 Alcotest.test_case "join over Spi base tables" `Quick test_join; 143 Alcotest.test_case "aggregate / compound / recursive WITH" `Quick 144 test_aggregate_compound_cte; 145 Alcotest.test_case "unsupported shapes fail loud" `Quick 146 test_unsupported_is_loud; 147 ] )