Merge with main

This commit is contained in:
Jorge Hermo
2025-01-15 22:10:35 +01:00
100 changed files with 6879 additions and 1579 deletions

139
testing/extensions.py Executable file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
import os
import subprocess
import select
import time
import uuid
sqlite_exec = "./target/debug/limbo"
sqlite_flags = os.getenv("SQLITE_FLAGS", "-q").split(" ")
def init_limbo():
pipe = subprocess.Popen(
[sqlite_exec, *sqlite_flags],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=0,
)
return pipe
def execute_sql(pipe, sql):
end_suffix = "END_OF_RESULT"
write_to_pipe(pipe, sql)
write_to_pipe(pipe, f"SELECT '{end_suffix}';\n")
stdout = pipe.stdout
stderr = pipe.stderr
output = ""
while True:
ready_to_read, _, error_in_pipe = select.select(
[stdout, stderr], [], [stdout, stderr]
)
ready_to_read_or_err = set(ready_to_read + error_in_pipe)
if stderr in ready_to_read_or_err:
exit_on_error(stderr)
if stdout in ready_to_read_or_err:
fragment = stdout.read(select.PIPE_BUF)
output += fragment.decode()
if output.rstrip().endswith(end_suffix):
output = output.rstrip().removesuffix(end_suffix)
break
output = strip_each_line(output)
return output
def strip_each_line(lines: str) -> str:
lines = lines.split("\n")
lines = [line.strip() for line in lines if line != ""]
return "\n".join(lines)
def write_to_pipe(pipe, command):
if pipe.stdin is None:
raise RuntimeError("Failed to write to shell")
pipe.stdin.write((command + "\n").encode())
pipe.stdin.flush()
def exit_on_error(stderr):
while True:
ready_to_read, _, _ = select.select([stderr], [], [])
if not ready_to_read:
break
print(stderr.read().decode(), end="")
exit(1)
def run_test(pipe, sql, validator=None):
print(f"Running test: {sql}")
result = execute_sql(pipe, sql)
if validator is not None:
if not validator(result):
print(f"Test FAILED: {sql}")
print(f"Returned: {result}")
raise Exception("Validation failed")
print("Test PASSED")
def validate_blob(result):
# HACK: blobs are difficult to test because the shell
# tries to return them as utf8 strings, so we call hex
# and assert they are valid hex digits
return int(result, 16) is not None
def validate_string_uuid(result):
return len(result) == 36 and result.count("-") == 4
def returns_null(result):
return result == "" or result == b"\n" or result == b""
def assert_now_unixtime(result):
return result == str(int(time.time()))
def assert_specific_time(result):
return result == "1736720789"
def main():
specific_time = "01945ca0-3189-76c0-9a8f-caf310fc8b8e"
extension_path = "./target/debug/liblimbo_uuid.so"
pipe = init_limbo()
try:
# before extension loads, assert no function
run_test(pipe, "SELECT uuid4();", returns_null)
run_test(pipe, "SELECT uuid4_str();", returns_null)
run_test(pipe, f".load {extension_path}", returns_null)
print("Extension loaded successfully.")
run_test(pipe, "SELECT hex(uuid4());", validate_blob)
run_test(pipe, "SELECT uuid4_str();", validate_string_uuid)
run_test(pipe, "SELECT hex(uuid7());", validate_blob)
run_test(
pipe,
"SELECT uuid7_timestamp_ms(uuid7()) / 1000;",
)
run_test(pipe, "SELECT uuid7_str();", validate_string_uuid)
run_test(pipe, "SELECT uuid_str(uuid7());", validate_string_uuid)
run_test(pipe, "SELECT hex(uuid_blob(uuid7_str()));", validate_blob)
run_test(pipe, "SELECT uuid_str(uuid_blob(uuid7_str()));", validate_string_uuid)
run_test(
pipe,
f"SELECT uuid7_timestamp_ms('{specific_time}') / 1000;",
assert_specific_time,
)
except Exception as e:
print(f"Test FAILED: {e}")
pipe.terminate()
exit(1)
pipe.terminate()
print("All tests passed successfully.")
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,18 @@
#!/usr/bin/env tclsh
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/tester.tcl
do_execsql_test_on_specific_db {:memory:} basic-insert {
create table temp (t1 integer, primary key (t1));
insert into temp values (1);
select * from temp;
} {1}
do_execsql_test_on_specific_db {:memory:} must-be-int-insert {
create table temp (t1 integer, primary key (t1));
insert into temp values (1),(2.0),('3'),('4.0');
select * from temp;
} {1
2
3
4}

View File

@@ -106,6 +106,14 @@ Jamie|coat
Jamie|accessories
Cindy|}
do_execsql_test left-join-row-id {
select u.rowid, p.rowid from users u left join products as p on u.rowid = p.rowid where u.rowid >= 10 limit 5;
} {10|10
11|11
12|
13|
14|}
do_execsql_test left-join-constant-condition-true {
select u.first_name, p.name from users u left join products as p on true limit 1;
} {Jamie|hat}

View File

@@ -459,6 +459,118 @@ do_execsql_test bitwise-and-int-agg-int-agg {
} {66}
foreach {testname lhs rhs ans} {
int-int 1 2 4
int-neg_int 8 -2 2
int-float 1 4.0 16
int-text 1 'a' 1
int-text_float 1 '3.0' 8
int-text_int 1 '1' 2
int-null 1 NULL {}
int-int-overflow 1 64 0
int-int-underflow 1 -64 0
int-float-overflow 1 64.0 0
int-float-underflow 1 -64.0 0
} {
do_execsql_test shift-left-$testname "SELECT $lhs << $rhs" $::ans
}
foreach {testname lhs rhs ans} {
float-int 1.0 2 4
float-neg_int 8.0 -2 2
float-float 1.0 4.0 16
float-text 1.0 'a' 1
float-text_float 1.0 '3.0' 8
float-text_int 1.0 '1' 2
float-null 1.0 NULL {}
float-int-overflow 1.0 64 0
float-int-underflow 1.0 -64 0
float-float-overflow 1.0 64.0 0
float-float-underflow 1.0 -64.0 0
} {
do_execsql_test shift-left-$testname "SELECT $lhs << $rhs" $::ans
}
foreach {testname lhs rhs ans} {
text-int 'a' 2 0
text-float 'a' 4.0 0
text-text 'a' 'a' 0
text_int-text_int '1' '1' 2
text_int-text_float '1' '3.0' 8
text_int-text '1' 'a' 1
text_float-text_int '1.0' '1' 2
text_float-text_float '1.0' '3.0' 8
text_float-text '1.0' 'a' 1
text-null '1' NULL {}
} {
do_execsql_test shift-left-$testname "SELECT $lhs << $rhs" $::ans
}
foreach {testname lhs rhs ans} {
null-int NULL 2 {}
null-float NULL 4.0 {}
null-text NULL 'a' {}
null-null NULL NULL {}
} {
do_execsql_test shift-left-$testname "SELECT $lhs << $rhs" $::ans
}
foreach {testname lhs rhs ans} {
int-int 8 2 2
int-neg_int 8 -2 32
int-float 8 1.0 4
int-text 8 'a' 8
int-text_float 8 '3.0' 1
int-text_int 8 '1' 4
int-null 8 NULL {}
int-int-overflow 8 64 0
int-int-underflow 8 -64 0
int-float-overflow 8 64.0 0
int-float-underflow 8 -64.0 0
} {
do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans
}
foreach {testname lhs rhs ans} {
float-int 8.0 2 2
float-neg_int 8.0 -2 32
float-float 8.0 1.0 4
float-text 8.0 'a' 8
float-text_float 8.0 '3.0' 1
float-text_int 8.0 '1' 4
float-null 8.0 NULL {}
float-int-overflow 8.0 64 0
float-int-underflow 8.0 -64 0
float-float-overflow 8.0 64.0 0
float-float-underflow 8.0 -64.0 0
} {
do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans
}
foreach {testname lhs rhs ans} {
text-int 'a' 2 0
text-float 'a' 4.0 0
text-text 'a' 'a' 0
text_int-text_int '8' '1' 4
text_int-text_float '8' '3.0' 1
text_int-text '8' 'a' 8
text_float-text_int '8.0' '1' 4
text_float-text_float '8.0' '3.0' 1
text_float-text '8.0' 'a' 8
text-null '8' NULL {}
} {
do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans
}
foreach {testname lhs rhs ans} {
null-int NULL 2 {}
null-float NULL 4.0 {}
null-text NULL 'a' {}
null-null NULL NULL {}
} {
do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans
}
do_execsql_test bitwise-not-null {
SELECT ~NULL
} {}

View File

@@ -80,6 +80,14 @@ do_execsql_test select_with_quoting_2 {
select "users".`id` from users where `users`.[id] = 5;
} {5}
do_execsql_test select-rowid {
select rowid, first_name from users u where rowid = 5;
} {5|Edward}
do_execsql_test select-rowid-2 {
select u.rowid, first_name from users u where rowid = 5;
} {5|Edward}
do_execsql_test seekrowid {
select * from users u where u.id = 5;
} {"5|Edward|Miller|christiankramer@example.com|725-281-1033|08522 English Plain|Lake Keith|ID|23283|15"}

View File

@@ -365,3 +365,21 @@ do_execsql_test nested-parens-conditionals-and-double-or {
8171|Andrea|Lee|dgarrison@example.com|001-594-430-0646|452 Anthony Stravenue|Sandraville|CA|28572|12
9110|Anthony|Barrett|steven05@example.net|(562)928-9177x8454|86166 Foster Inlet Apt. 284|North Jeffreyburgh|CA|80147|97
9279|Annette|Lynn|joanne37@example.com|(272)700-7181|2676 Laura Points Apt. 683|Tristanville|NY|48646|91}}
# Regression test for nested parens + OR + AND. This returned 0 rows before the fix.
# It should always return 1 row because it is true for id = 6.
do_execsql_test nested-parens-and-inside-or-regression-test {
SELECT count(1) FROM users
WHERE (
(
(
(id != 5)
AND
(id = 5 OR TRUE)
)
OR FALSE
)
AND
(id = 6 OR FALSE)
);
} {1}