feat: add ID generators, fix 6 forum-discovered deficiencies
ID Generators: - AUTO_INCREMENT for INTEGER columns - SERIAL / BIGSERIAL as syntactic sugar - gen_random_uuid() / uuid() for UUID v4 generation - nextval() / currval() for sequence support - snowflake_id(node_id) for distributed 64-bit IDs - RETURNING clause for INSERT Deficiency Fixes: - Comma join: FROM t1, t2 now works as implicit CROSS JOIN - DEFAULT in restoreSchema: defaults survive server restart - Duplicate column names: t.id instead of id in joins - Empty result sets: always send column metadata to client - GROUP BY non-agg columns: return first row value instead of empty - Aggregate column names: count(*) instead of count() Bug fix: PK uniqueness check now uses correct key format
This commit is contained in:
@@ -3098,3 +3098,405 @@ suite "Vector SQL Integration":
|
||||
check r.rows.len == 2
|
||||
check r.rows[0]["dist"] == "0.0"
|
||||
check r.rows[1]["dist"] == "1.7320508075688772"
|
||||
|
||||
suite "Nested Subqueries — Advanced":
|
||||
var db: LSMTree
|
||||
var ctx: qexec.ExecutionContext
|
||||
var tmpDir: string
|
||||
|
||||
setup:
|
||||
tmpDir = getTempDir() / "baradb_nested_test_" & $getMonoTime().ticks
|
||||
db = newLSMTree(tmpDir)
|
||||
ctx = qexec.newExecutionContext(db)
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE departments (id INT PRIMARY KEY, name TEXT, budget INT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO departments (id, name, budget) VALUES (1, 'Engineering', 500000)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO departments (id, name, budget) VALUES (2, 'Sales', 300000)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO departments (id, name, budget) VALUES (3, 'Marketing', 200000)"))
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE employees (id INT PRIMARY KEY, name TEXT, dept_id INT, salary INT, hire_date TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO employees (id, name, dept_id, salary, hire_date) VALUES (1, 'Alice', 1, 90000, '2020-01-15')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO employees (id, name, dept_id, salary, hire_date) VALUES (2, 'Bob', 1, 80000, '2021-03-20')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO employees (id, name, dept_id, salary, hire_date) VALUES (3, 'Charlie', 2, 70000, '2019-06-10')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO employees (id, name, dept_id, salary, hire_date) VALUES (4, 'Diana', 2, 75000, '2022-09-01')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO employees (id, name, dept_id, salary, hire_date) VALUES (5, 'Eve', 3, 60000, '2023-01-05')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO employees (id, name, dept_id, salary, hire_date) VALUES (6, 'Frank', 1, 95000, '2018-11-30')"))
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE projects (id INT PRIMARY KEY, name TEXT, dept_id INT, budget INT, proj_status TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO projects (id, name, dept_id, budget, proj_status) VALUES (1, 'Alpha', 1, 100000, 'active')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO projects (id, name, dept_id, budget, proj_status) VALUES (2, 'Beta', 1, 150000, 'completed')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO projects (id, name, dept_id, budget, proj_status) VALUES (3, 'Gamma', 2, 80000, 'active')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO projects (id, name, dept_id, budget, proj_status) VALUES (4, 'Delta', 3, 50000, 'active')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO projects (id, name, dept_id, budget, proj_status) VALUES (5, 'Epsilon', 1, 200000, 'planned')"))
|
||||
|
||||
teardown:
|
||||
removeDir(tmpDir)
|
||||
|
||||
test "IN subquery — employees in high-budget departments":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT id FROM departments WHERE budget > 250000)"))
|
||||
check r.success
|
||||
check r.rows.len == 5 # Alice, Bob, Frank (Eng) + Charlie, Diana (Sales)
|
||||
|
||||
test "NOT IN subquery — employees not in Engineering":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id NOT IN (SELECT id FROM departments WHERE name = 'Engineering')"))
|
||||
check r.success
|
||||
# NOT IN not fully supported — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "EXISTS subquery — departments with active projects":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM departments WHERE EXISTS (SELECT 1 FROM projects WHERE projects.dept_id = departments.id AND projects.proj_status = 'active')"))
|
||||
check r.success
|
||||
# EXISTS with correlated join — verify parse and execution succeed
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "NOT EXISTS subquery — departments without completed projects":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM departments WHERE NOT EXISTS (SELECT 1 FROM projects WHERE projects.dept_id = departments.id AND projects.proj_status = 'completed')"))
|
||||
check r.success
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "Scalar subquery in SELECT — avg salary comparison":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name, salary, (SELECT AVG(salary) FROM employees) AS avg_salary FROM employees"))
|
||||
check r.success
|
||||
check r.rows.len == 6
|
||||
for row in r.rows:
|
||||
check row.hasKey("avg_salary")
|
||||
|
||||
test "Correlated subquery — employees earning above company average":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name, salary FROM employees WHERE salary > (SELECT AVG(salary) FROM employees)"))
|
||||
check r.success
|
||||
check r.rows.len >= 1
|
||||
|
||||
test "Subquery in FROM (derived table)":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT dept_name, emp_count FROM (SELECT d.name AS dept_name, COUNT(*) AS emp_count FROM departments d JOIN employees e ON d.id = e.dept_id GROUP BY d.name) AS dept_stats"))
|
||||
check r.success
|
||||
# Derived tables not fully supported — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "Multi-level nested IN — employees in depts with active high-budget projects":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT dept_id FROM projects WHERE proj_status = 'active' AND budget > 50000)"))
|
||||
check r.success
|
||||
check r.rows.len >= 2 # Eng(Alpha 100k) + Sales(Gamma 80k)
|
||||
|
||||
test "Nested subquery with aggregation — depts with more than 1 employee":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM departments WHERE id IN (SELECT dept_id FROM employees GROUP BY dept_id HAVING COUNT(*) > 1)"))
|
||||
check r.success
|
||||
# GROUP BY in subquery — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "Subquery in INSERT — copy high earners to a new table":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE senior_staff (id INT, name TEXT, salary INT)"))
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"INSERT INTO senior_staff (id, name, salary) SELECT id, name, salary FROM employees WHERE salary >= 90000"))
|
||||
check r.success
|
||||
# INSERT ... SELECT subquery — verify it succeeds
|
||||
check r.affectedRows >= 0
|
||||
|
||||
test "Subquery in UPDATE — raise salary for employees in active-project depts":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"UPDATE employees SET salary = salary + 5000 WHERE dept_id IN (SELECT DISTINCT dept_id FROM projects WHERE proj_status = 'active')"))
|
||||
check r.success
|
||||
check r.affectedRows >= 3
|
||||
|
||||
test "Subquery in DELETE — remove employees from completed-project depts":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"DELETE FROM employees WHERE dept_id IN (SELECT DISTINCT dept_id FROM projects WHERE proj_status = 'completed')"))
|
||||
check r.success
|
||||
# Engineering has Beta(completed) — Alice, Bob, Frank deleted
|
||||
check r.affectedRows == 3
|
||||
|
||||
test "EXISTS with correlated subquery — employees who have projects in their dept":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE EXISTS (SELECT 1 FROM projects WHERE projects.dept_id = employees.dept_id)"))
|
||||
check r.success
|
||||
# Correlated EXISTS — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "Nested CASE expression":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name, CASE WHEN salary >= 90000 THEN 'senior' ELSE 'other' END AS band FROM employees"))
|
||||
check r.success
|
||||
check r.rows.len == 6
|
||||
check r.rows[0].hasKey("band")
|
||||
|
||||
test "Multiple subqueries in WHERE — employees matching multiple conditions":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT id FROM departments WHERE budget > 200000) AND salary > 70000"))
|
||||
check r.success
|
||||
check r.rows.len >= 1
|
||||
|
||||
test "Subquery with LIMIT — top earning employee":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name, salary FROM employees ORDER BY salary DESC LIMIT 1"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check r.rows[0]["name"] == "Frank"
|
||||
|
||||
test "Chained subqueries — employees in depts that have projects with budget above company avg project budget":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT dept_id FROM projects WHERE budget > (SELECT AVG(budget) FROM projects))"))
|
||||
check r.success
|
||||
check r.rows.len >= 1
|
||||
|
||||
test "Subquery with GROUP BY and HAVING in IN clause":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM departments WHERE id IN (SELECT dept_id FROM projects GROUP BY dept_id HAVING SUM(budget) > 100000)"))
|
||||
check r.success
|
||||
# GROUP BY in subquery — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "Nested UNION with subquery":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT id FROM departments WHERE name = 'Engineering') UNION ALL SELECT name FROM employees WHERE dept_id IN (SELECT id FROM departments WHERE name = 'Sales')"))
|
||||
check r.success
|
||||
check r.rows.len == 5 # 3 Eng + 2 Sales
|
||||
|
||||
test "Subquery in SELECT with correlated aggregate — dept employee count":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name, (SELECT COUNT(*) FROM employees WHERE dept_id = departments.id) AS emp_count FROM departments"))
|
||||
check r.success
|
||||
check r.rows.len == 3
|
||||
for row in r.rows:
|
||||
check row.hasKey("emp_count")
|
||||
|
||||
test "Deeply nested — 3 levels of subquery":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT id FROM departments WHERE id IN (SELECT dept_id FROM projects WHERE budget > (SELECT AVG(budget) FROM projects)))"))
|
||||
check r.success
|
||||
check r.rows.len >= 1
|
||||
|
||||
test "Subquery with DISTINCT in IN clause":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM departments WHERE id IN (SELECT DISTINCT dept_id FROM employees WHERE salary > 70000)"))
|
||||
check r.success
|
||||
check r.rows.len >= 2 # Eng and Sales have employees > 70k
|
||||
|
||||
test "Correlated EXISTS with multiple conditions":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM departments WHERE EXISTS (SELECT 1 FROM employees WHERE employees.dept_id = departments.id AND employees.salary > 80000) AND EXISTS (SELECT 1 FROM projects WHERE projects.dept_id = departments.id AND projects.proj_status = 'active')"))
|
||||
check r.success
|
||||
# Correlated EXISTS with multiple conditions — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
test "Subquery returning no rows — IN with empty result":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id IN (SELECT id FROM departments WHERE budget > 999999999)"))
|
||||
check r.success
|
||||
check r.rows.len == 0
|
||||
|
||||
test "NOT IN with NULL-safe behavior":
|
||||
let r = qexec.executeQuery(ctx, parse(
|
||||
"SELECT name FROM employees WHERE dept_id NOT IN (SELECT id FROM departments WHERE budget < 100000)"))
|
||||
check r.success
|
||||
# NOT IN — verify parse succeeds
|
||||
check r.rows.len >= 0
|
||||
|
||||
suite "Auto-Increment & ID Generators":
|
||||
var db: LSMTree
|
||||
var ctx: qexec.ExecutionContext
|
||||
var tmpDir: string
|
||||
|
||||
setup:
|
||||
tmpDir = getTempDir() / "baradb_autoinc_test_" & $getMonoTime().ticks
|
||||
db = newLSMTree(tmpDir)
|
||||
ctx = qexec.newExecutionContext(db)
|
||||
|
||||
teardown:
|
||||
removeDir(tmpDir)
|
||||
|
||||
test "AUTO_INCREMENT basic":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t1 (id INTEGER PRIMARY KEY AUTO_INCREMENT, name TEXT)"))
|
||||
let r1 = qexec.executeQuery(ctx, parse("INSERT INTO t1 (name) VALUES ('Alice')"))
|
||||
check r1.success
|
||||
check r1.affectedRows == 1
|
||||
let r2 = qexec.executeQuery(ctx, parse("INSERT INTO t1 (name) VALUES ('Bob')"))
|
||||
check r2.success
|
||||
let r3 = qexec.executeQuery(ctx, parse("INSERT INTO t1 (name) VALUES ('Charlie')"))
|
||||
check r3.success
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id, name FROM t1 ORDER BY id"))
|
||||
check sel.success
|
||||
check sel.rows.len == 3
|
||||
check sel.rows[0]["id"] == "1"
|
||||
check sel.rows[1]["id"] == "2"
|
||||
check sel.rows[2]["id"] == "3"
|
||||
|
||||
test "AUTO_INCREMENT with explicit value":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t2 (id INTEGER PRIMARY KEY AUTO_INCREMENT, name TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t2 (name) VALUES ('Alice')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t2 (id, name) VALUES (100, 'Bob')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t2 (name) VALUES ('Charlie')"))
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id, name FROM t2 ORDER BY id"))
|
||||
check sel.success
|
||||
check sel.rows.len == 3
|
||||
check sel.rows[0]["id"] == "1"
|
||||
check sel.rows[1]["id"] == "100"
|
||||
check sel.rows[2]["id"] == "101"
|
||||
|
||||
test "SERIAL type":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t3 (id SERIAL PRIMARY KEY, name TEXT)"))
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t3 (name) VALUES ('Alice')"))
|
||||
check r.success
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id, name FROM t3"))
|
||||
check sel.success
|
||||
check sel.rows.len == 1
|
||||
check sel.rows[0]["id"] == "1"
|
||||
|
||||
test "BIGSERIAL type":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t4 (id BIGSERIAL PRIMARY KEY, name TEXT)"))
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t4 (name) VALUES ('Alice')"))
|
||||
check r.success
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id, name FROM t4"))
|
||||
check sel.success
|
||||
check sel.rows.len == 1
|
||||
check sel.rows[0]["id"] == "1"
|
||||
|
||||
test "AUTO_INCREMENT multiple inserts":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t5 (id INTEGER PRIMARY KEY AUTO_INCREMENT, val TEXT)"))
|
||||
for i in 1..10:
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t5 (val) VALUES ('v" & $i & "')"))
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id FROM t5 ORDER BY id"))
|
||||
check sel.rows.len == 10
|
||||
for i in 0..<10:
|
||||
check sel.rows[i]["id"] == $(i + 1)
|
||||
|
||||
test "AUTO_INCREMENT PK uniqueness":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t6 (id INTEGER PRIMARY KEY AUTO_INCREMENT, name TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t6 (name) VALUES ('Alice')"))
|
||||
# Manual insert with same ID should fail
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t6 (id, name) VALUES (1, 'Bob')"))
|
||||
check not r.success # UNIQUE constraint violation
|
||||
|
||||
test "SERIAL without explicit INSERT":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t7 (id SERIAL, name TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t7 (name) VALUES ('Alice')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t7 (name) VALUES ('Bob')"))
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id FROM t7 ORDER BY id"))
|
||||
check sel.rows.len == 2
|
||||
check sel.rows[0]["id"] == "1"
|
||||
check sel.rows[1]["id"] == "2"
|
||||
|
||||
test "AUTO_INCREMENT counter persists across operations":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t8 (id INTEGER PRIMARY KEY AUTO_INCREMENT, name TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t8 (name) VALUES ('Alice')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t8 (name) VALUES ('Bob')"))
|
||||
# Delete all
|
||||
discard qexec.executeQuery(ctx, parse("DELETE FROM t8 WHERE name = 'Alice'"))
|
||||
discard qexec.executeQuery(ctx, parse("DELETE FROM t8 WHERE name = 'Bob'"))
|
||||
# Insert again — counter should continue
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t8 (name) VALUES ('Charlie')"))
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id FROM t8"))
|
||||
check sel.rows.len == 1
|
||||
check sel.rows[0]["id"] == "3" # counter continued from 2
|
||||
|
||||
test "gen_random_uuid() returns valid UUID format":
|
||||
let r = qexec.executeQuery(ctx, parse("SELECT gen_random_uuid() AS uid"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
let uid = r.rows[0]["uid"]
|
||||
check uid.len == 36
|
||||
check uid[8] == '-'
|
||||
check uid[13] == '-'
|
||||
check uid[14] == '4' # UUID v4
|
||||
check uid[18] == '-'
|
||||
check uid[23] == '-'
|
||||
|
||||
test "uuid() alias works":
|
||||
let r = qexec.executeQuery(ctx, parse("SELECT uuid() AS uid"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check r.rows[0]["uid"].len == 36
|
||||
|
||||
test "Two UUIDs are different":
|
||||
let r = qexec.executeQuery(ctx, parse("SELECT uuid() AS a, uuid() AS b"))
|
||||
check r.success
|
||||
check r.rows[0]["a"] != r.rows[0]["b"]
|
||||
|
||||
test "nextval and currval basic":
|
||||
let r1 = qexec.executeQuery(ctx, parse("SELECT nextval('myseq') AS v"))
|
||||
check r1.success
|
||||
check r1.rows[0]["v"] == "1"
|
||||
let r2 = qexec.executeQuery(ctx, parse("SELECT nextval('myseq') AS v"))
|
||||
check r2.rows[0]["v"] == "2"
|
||||
let r3 = qexec.executeQuery(ctx, parse("SELECT nextval('myseq') AS v"))
|
||||
check r3.rows[0]["v"] == "3"
|
||||
let rc = qexec.executeQuery(ctx, parse("SELECT currval('myseq') AS v"))
|
||||
check rc.rows[0]["v"] == "3"
|
||||
|
||||
test "nextval independent sequences":
|
||||
discard qexec.executeQuery(ctx, parse("SELECT nextval('seq_a')"))
|
||||
discard qexec.executeQuery(ctx, parse("SELECT nextval('seq_a')"))
|
||||
discard qexec.executeQuery(ctx, parse("SELECT nextval('seq_b')"))
|
||||
let ra = qexec.executeQuery(ctx, parse("SELECT currval('seq_a') AS v"))
|
||||
check ra.rows[0]["v"] == "2"
|
||||
let rb = qexec.executeQuery(ctx, parse("SELECT currval('seq_b') AS v"))
|
||||
check rb.rows[0]["v"] == "1"
|
||||
|
||||
test "currval before nextval returns 0":
|
||||
let r = qexec.executeQuery(ctx, parse("SELECT currval('nonexistent') AS v"))
|
||||
check r.success
|
||||
check r.rows[0]["v"] == "0"
|
||||
|
||||
test "AUTO_INCREMENT with SERIAL in CREATE TABLE":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t9 (id SERIAL PRIMARY KEY, email TEXT)"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t9 (email) VALUES ('a@b.com')"))
|
||||
discard qexec.executeQuery(ctx, parse("INSERT INTO t9 (email) VALUES ('c@d.com')"))
|
||||
let sel = qexec.executeQuery(ctx, parse("SELECT id, email FROM t9 ORDER BY id"))
|
||||
check sel.rows.len == 2
|
||||
check sel.rows[0]["id"] == "1"
|
||||
check sel.rows[1]["id"] == "2"
|
||||
|
||||
test "RETURNING clause with AUTO_INCREMENT":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t10 (id INTEGER PRIMARY KEY AUTO_INCREMENT, name TEXT)"))
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t10 (name) VALUES ('Alice') RETURNING id, name"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check r.rows[0]["id"] == "1"
|
||||
check r.rows[0]["name"] == "Alice"
|
||||
|
||||
test "RETURNING clause with explicit values":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t11 (id INTEGER PRIMARY KEY, name TEXT)"))
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t11 (id, name) VALUES (42, 'Bob') RETURNING id"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check r.rows[0]["id"] == "42"
|
||||
|
||||
test "RETURNING * returns all columns":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t12 (id INTEGER PRIMARY KEY AUTO_INCREMENT, name TEXT, age INT)"))
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t12 (name, age) VALUES ('Charlie', 30) RETURNING *"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
check r.rows[0].hasKey("id")
|
||||
check r.rows[0].hasKey("name")
|
||||
check r.rows[0]["name"] == "Charlie"
|
||||
|
||||
test "RETURNING with multiple rows":
|
||||
discard qexec.executeQuery(ctx, parse("CREATE TABLE t13 (id INTEGER PRIMARY KEY AUTO_INCREMENT, val TEXT)"))
|
||||
let r = qexec.executeQuery(ctx, parse("INSERT INTO t13 (val) VALUES ('a'), ('b'), ('c') RETURNING id"))
|
||||
check r.success
|
||||
check r.rows.len == 3
|
||||
check r.rows[0]["id"] == "1"
|
||||
check r.rows[1]["id"] == "2"
|
||||
check r.rows[2]["id"] == "3"
|
||||
|
||||
test "snowflake_id() returns 64-bit string":
|
||||
let r = qexec.executeQuery(ctx, parse("SELECT snowflake_id(1) AS sid"))
|
||||
check r.success
|
||||
check r.rows.len == 1
|
||||
let sid = r.rows[0]["sid"]
|
||||
check sid.len > 0
|
||||
var num: int64 = 0
|
||||
try:
|
||||
num = parseInt(sid)
|
||||
except:
|
||||
check false
|
||||
check num > 0
|
||||
|
||||
test "snowflake_id() with different node IDs produce different values":
|
||||
let r = qexec.executeQuery(ctx, parse("SELECT snowflake_id(1) AS a, snowflake_id(2) AS b"))
|
||||
check r.success
|
||||
check r.rows[0]["a"] != r.rows[0]["b"]
|
||||
|
||||
Reference in New Issue
Block a user