1212 lines
33 KiB
Python
1212 lines
33 KiB
Python
# fmt: off
|
|
|
|
import pytest
|
|
import subprocess
|
|
import sys
|
|
from typing import List
|
|
from conftest import ShellTest
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def test_basic(shell):
|
|
test = ShellTest(shell).statement("select 'asdf' as a")
|
|
result = test.run()
|
|
result.check_stdout("asdf")
|
|
|
|
|
|
def test_range(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement("select * from range(10000)")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("9999")
|
|
|
|
|
|
@pytest.mark.parametrize('generated_file', ["col_1,col_2\n1,2\n10,20"], indirect=True)
|
|
def test_import(shell, generated_file):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement(f'.import "{generated_file.as_posix()}" test_table')
|
|
.statement("select * FROM test_table")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("col_1,col_2\n1,2\n10,20")
|
|
|
|
|
|
@pytest.mark.parametrize('generated_file', ["42\n84"], indirect=True)
|
|
def test_import_sum(shell, generated_file):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (i INTEGER)")
|
|
.statement(f'.import "{generated_file.as_posix()}" a')
|
|
.statement("SELECT SUM(i) FROM a")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("126")
|
|
|
|
|
|
def test_pragma(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement(".headers off")
|
|
.statement(".sep |")
|
|
.statement('.nullvalue ""')
|
|
.statement("CREATE TABLE t0(c0 INT);")
|
|
.statement("PRAGMA table_info('t0');")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("0|c0|INTEGER|false||false")
|
|
|
|
|
|
def test_system_functions(shell):
|
|
test = ShellTest(shell).statement("SELECT 1, current_query() as my_column")
|
|
|
|
result = test.run()
|
|
result.check_stdout("SELECT 1, current_query() as my_column")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
["input", "output"],
|
|
[
|
|
("select LIST_VALUE(1, 2)", "[1, 2]"),
|
|
("select STRUCT_PACK(x := 3, y := 3)", "{'x': 3, 'y': 3}"),
|
|
("select STRUCT_PACK(x := 3, y := LIST_VALUE(1, 2))", "{'x': 3, 'y': [1, 2]}"),
|
|
],
|
|
)
|
|
def test_nested_types(shell, input, output):
|
|
test = ShellTest(shell).statement(input)
|
|
result = test.run()
|
|
result.check_stdout(output)
|
|
|
|
|
|
def test_invalid_cast(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (i STRING)")
|
|
.statement("INSERT INTO a VALUES ('XXXX')")
|
|
.statement("SELECT CAST(i AS INTEGER) FROM a")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("Could not convert")
|
|
|
|
def test_invalid_backup(shell, random_filepath):
|
|
test = ShellTest(shell).statement(f'.backup {random_filepath.as_posix()}')
|
|
result = test.run()
|
|
result.check_stderr("unsupported in the current version of the CLI")
|
|
|
|
def test_newline_in_value(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("""select 'hello
|
|
world' as a""")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("hello\\nworld")
|
|
|
|
def test_newline_in_column_name(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("""select 42 as "hello
|
|
world" """)
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("hello\\nworld")
|
|
|
|
# FIXME: this test was underspecified, no expected result was provided
|
|
def test_bailing_mechanism(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".bail on")
|
|
.statement(".bail off")
|
|
.statement(".binary on")
|
|
.statement("SELECT 42")
|
|
.statement(".binary off")
|
|
.statement("SELECT 42")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("42")
|
|
|
|
# FIXME: no verification at all?
|
|
def test_cd(shell, tmp_path):
|
|
current_dir = Path(os.getcwd())
|
|
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".cd {tmp_path.as_posix()}")
|
|
.statement(f".cd {current_dir.as_posix()}")
|
|
)
|
|
result = test.run()
|
|
|
|
def test_changes_on(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (I INTEGER)")
|
|
.statement(".changes on")
|
|
.statement("INSERT INTO a VALUES (42)")
|
|
.statement("INSERT INTO a VALUES (42)")
|
|
.statement("INSERT INTO a VALUES (42)")
|
|
.statement("DROP TABLE a")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("total_changes: 3")
|
|
|
|
def test_changes_off(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (I INTEGER);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO a VALUES (42);")
|
|
.statement("DROP TABLE a;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("")
|
|
|
|
def test_echo(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".echo on")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("SELECT 42")
|
|
|
|
@pytest.mark.parametrize("alias", ["exit", "quit"])
|
|
def test_exit(shell, alias):
|
|
test = ShellTest(shell).statement(f".{alias}")
|
|
result = test.run()
|
|
|
|
def test_exit_rc(shell):
|
|
test = ShellTest(shell).statement(f".exit 17")
|
|
result = test.run()
|
|
assert result.status_code == 17
|
|
|
|
def test_print(shell):
|
|
test = ShellTest(shell).statement(".print asdf")
|
|
result = test.run()
|
|
result.check_stdout("asdf")
|
|
|
|
def test_headers(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".headers on")
|
|
.statement("SELECT 42 as wilbur")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("wilbur")
|
|
|
|
def test_like(shell):
|
|
test = ShellTest(shell).statement("select 'yo' where 'abc' like 'a%c'")
|
|
result = test.run()
|
|
result.check_stdout("yo")
|
|
|
|
def test_regexp_matches(shell):
|
|
test = ShellTest(shell).statement("select regexp_matches('abc','abc')")
|
|
result = test.run()
|
|
result.check_stdout("true")
|
|
|
|
def test_help(shell):
|
|
test = (
|
|
ShellTest(shell).
|
|
statement(".help")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("Show help text for PATTERN")
|
|
|
|
def test_load_error(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".load {random_filepath.as_posix()}")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("Error")
|
|
|
|
def test_streaming_error(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("SELECT x::INT FROM (SELECT x::VARCHAR x FROM range(10) tbl(x) UNION ALL SELECT 'hello' x) tbl(x);")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("Could not convert string")
|
|
|
|
@pytest.mark.parametrize("stmt", [
|
|
"explain select sum(i) from range(1000) tbl(i)",
|
|
"explain analyze select sum(i) from range(1000) tbl(i)"
|
|
])
|
|
def test_explain(shell, stmt):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(stmt)
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("RANGE")
|
|
|
|
def test_returning_insert(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE table1 (a INTEGER DEFAULT -1, b INTEGER DEFAULT -2, c INTEGER DEFAULT -3);")
|
|
.statement("INSERT INTO table1 VALUES (1, 2, 3) RETURNING *;")
|
|
.statement("SELECT COUNT(*) FROM table1;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("1")
|
|
|
|
def test_pragma_display(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE table1 (mylittlecolumn INTEGER);")
|
|
.statement("pragma table_info('table1');")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("mylittlecolumn")
|
|
|
|
def test_show_display(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE table1 (mylittlecolumn INTEGER);")
|
|
.statement("show table1;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("mylittlecolumn")
|
|
|
|
def test_call_display(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CALL range(4);")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("3")
|
|
|
|
def test_execute_display(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("PREPARE v1 AS SELECT ?::INT;")
|
|
.statement("EXECUTE v1(42);")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("42")
|
|
|
|
@pytest.mark.parametrize('generated_file', ["select 42"], indirect=True)
|
|
def test_read(shell, generated_file):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".read {generated_file.as_posix()}")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("42")
|
|
|
|
@pytest.mark.parametrize('generated_file', ["select 42"], indirect=True)
|
|
def test_execute_file(shell, generated_file):
|
|
test = (
|
|
ShellTest(shell, ['-f', generated_file.as_posix()])
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("42")
|
|
|
|
def test_execute_non_existent_file(shell):
|
|
test = (
|
|
ShellTest(shell, ['-f', '____this_file_does_not_exist'])
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("____this_file_does_not_exist")
|
|
|
|
@pytest.mark.parametrize('generated_file', ["insert into tbl values (42)"], indirect=True)
|
|
def test_execute_files(shell, generated_file):
|
|
test = (
|
|
ShellTest(shell, ['-c', 'CREATE TABLE tbl(i INT)', '-f', generated_file.as_posix(), '-f', generated_file.as_posix(), '-c', 'SELECT SUM(i) FROM tbl'])
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("84")
|
|
|
|
def test_show_basic(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".show")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("rowseparator")
|
|
|
|
def test_timeout(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".timeout")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("unsupported in the current version of the CLI")
|
|
|
|
|
|
def test_save(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".save {random_filepath.as_posix()}")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("unsupported in the current version of the CLI")
|
|
|
|
def test_restore(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".restore {random_filepath.as_posix()}")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("unsupported in the current version of the CLI")
|
|
|
|
@pytest.mark.parametrize("cmd", [
|
|
".vfsinfo",
|
|
".vfsname",
|
|
".vfslist"
|
|
])
|
|
def test_volatile_commands(shell, cmd):
|
|
# The original comment read: don't crash plz
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".{cmd}")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr("")
|
|
|
|
@pytest.mark.parametrize("pattern", [
|
|
"test",
|
|
"tes%",
|
|
"tes*",
|
|
""
|
|
])
|
|
def test_schema(shell, pattern):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("create table test (a int, b varchar);")
|
|
.statement("insert into test values (1, 'hello');")
|
|
.statement(f".schema {pattern}")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("CREATE TABLE test(a INTEGER, b VARCHAR);")
|
|
|
|
def test_schema_indent(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("create table test (a int, b varchar, c int, d int, k int, primary key(a, b));")
|
|
.statement(f".schema -indent")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("CREATE TABLE test(")
|
|
|
|
def test_tables(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE asda (i INTEGER);")
|
|
.statement("CREATE TABLE bsdf (i INTEGER);")
|
|
.statement("CREATE TABLE csda (i INTEGER);")
|
|
.statement(".tables")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("asda bsdf csda")
|
|
|
|
def test_tables_pattern(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE asda (i INTEGER);")
|
|
.statement("CREATE TABLE bsdf (i INTEGER);")
|
|
.statement("CREATE TABLE csda (i INTEGER);")
|
|
.statement(".tables %da")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("asda csda")
|
|
|
|
def test_tables_schema_disambiguation(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA a;")
|
|
.statement("CREATE SCHEMA b;")
|
|
.statement("CREATE TABLE a.foobar(name VARCHAR);")
|
|
.statement("CREATE TABLE b.foobar(name VARCHAR);")
|
|
.statement(".tables")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("a.foobar b.foobar")
|
|
|
|
def test_tables_schema_filtering(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA a;")
|
|
.statement("CREATE SCHEMA b;")
|
|
.statement("CREATE TABLE a.foobar(name VARCHAR);")
|
|
.statement("CREATE TABLE b.foobar(name VARCHAR);")
|
|
.statement("CREATE TABLE a.unique_table(x INTEGER);")
|
|
.statement("CREATE TABLE b.other_table(y INTEGER);")
|
|
.statement(".tables a.%")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("foobar unique_table")
|
|
|
|
def test_tables_backward_compatibility(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE main_table(i INTEGER);")
|
|
.statement("CREATE TABLE unique_table(x INTEGER);")
|
|
.statement(".tables")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("main_table unique_table")
|
|
|
|
def test_tables_with_views(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA a;")
|
|
.statement("CREATE SCHEMA b;")
|
|
.statement("CREATE TABLE a.foobar(name VARCHAR);")
|
|
.statement("CREATE TABLE b.foobar(name VARCHAR);")
|
|
.statement("CREATE VIEW a.test_view AS SELECT 1 AS x;")
|
|
.statement("CREATE VIEW b.test_view AS SELECT 2 AS y;")
|
|
.statement(".tables")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("a.foobar a.test_view b.foobar b.test_view")
|
|
|
|
def test_indexes(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (i INTEGER);")
|
|
.statement("CREATE INDEX a_idx ON a(i);")
|
|
.statement(".indexes a%")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("a_idx")
|
|
|
|
def test_schema_pattern_no_result(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".schema %p%")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("")
|
|
|
|
def test_schema_pattern(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("create table duckdb_p (a int, b varchar, c BIT);")
|
|
.statement("create table p_duck(d INT, f DATE);")
|
|
.statement(".schema %p")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("CREATE TABLE duckdb_p(a INTEGER, b VARCHAR, c BIT);")
|
|
|
|
@pytest.mark.skipif(os.name == 'nt', reason="Windows treats newlines in a problematic manner")
|
|
def test_schema_pattern_extended(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("create table duckdb_p (a int, b varchar, c BIT);")
|
|
.statement("create table p_duck(d INT, f DATE);")
|
|
.statement(".schema %p%")
|
|
)
|
|
result = test.run()
|
|
expected = [
|
|
"CREATE TABLE duckdb_p(a INTEGER, b VARCHAR, c BIT);",
|
|
"CREATE TABLE p_duck(d INTEGER, f DATE);"
|
|
]
|
|
result.check_stdout(expected)
|
|
|
|
def test_clone_error(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".clone")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('Error: unknown command or invalid arguments: "clone". Enter ".help" for help')
|
|
|
|
def test_sha3sum(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".sha3sum")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('')
|
|
|
|
def test_jsonlines(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode jsonlines")
|
|
.statement("SELECT 42,43;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('{"42":42,"43":43}')
|
|
|
|
def test_nested_jsonlines(shell, json_extension):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode jsonlines")
|
|
.statement("SELECT [1,2,3]::JSON AS x;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('{"x":[1,2,3]}')
|
|
|
|
def test_separator(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement(".separator XX")
|
|
.statement("SELECT 42,43;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42XX43')
|
|
|
|
def test_timer(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".timer on")
|
|
.statement("SELECT NULL;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('Run Time (s):')
|
|
|
|
def test_output_csv_mode(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement(f".output {random_filepath.as_posix()}")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.stdout = open(random_filepath, 'rb').read()
|
|
result.check_stdout(b'42')
|
|
|
|
def test_issue_6204(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".output foo.txt")
|
|
.statement("select * from range(2049);")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("")
|
|
|
|
def test_once(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".once {random_filepath.as_posix()}")
|
|
.statement("SELECT 43;")
|
|
)
|
|
result = test.run()
|
|
result.stdout = open(random_filepath, 'rb').read()
|
|
result.check_stdout(b'43')
|
|
|
|
def test_log(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".log {random_filepath.as_posix()}")
|
|
.statement("SELECT 42;")
|
|
.statement(".log off")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('')
|
|
|
|
def test_mode_ascii(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode ascii")
|
|
.statement("SELECT NULL, 42, 'fourty-two', 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('fourty-two')
|
|
|
|
def test_mode_csv(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement("SELECT NULL, 42, 'fourty-two', 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout(',fourty-two,')
|
|
|
|
def test_mode_column(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode column")
|
|
.statement("SELECT NULL, 42, 'fourty-two', 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout(' fourty-two ')
|
|
|
|
def test_mode_html(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode html")
|
|
.statement("SELECT NULL, 42, 'fourty-two', 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('<td>fourty-two</td>')
|
|
|
|
def test_mode_html_escapes(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode html")
|
|
.statement("SELECT '<&>\"\'\'' AS \"&><\"\"\'\";")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('<tr><th>&><"'</th>')
|
|
|
|
def test_mode_tcl_escapes(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode tcl")
|
|
.statement("SELECT '<&>\"\'\'' AS \"&><\"\"\'\";")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('"&><\\"\'"')
|
|
|
|
def test_mode_csv_escapes(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode csv")
|
|
.statement("SELECT 'BEGINVAL,\n\"ENDVAL' AS \"BEGINHEADER\"\",\nENDHEADER\";")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('"BEGINHEADER"",\nENDHEADER"\r\n"BEGINVAL,\n""ENDVAL"')
|
|
|
|
def test_mode_json_infinity(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode json")
|
|
.statement("SELECT 'inf'::DOUBLE AS inf, '-inf'::DOUBLE AS ninf, 'nan'::DOUBLE AS nan, '-nan'::DOUBLE AS nnan;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('[{"inf":1e999,"ninf":-1e999,"nan":null,"nnan":null}]')
|
|
|
|
def test_mode_insert(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode insert")
|
|
.statement("SELECT NULL, 42, 'fourty-two', 42.0, 3.14, 2.71;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('fourty-two')
|
|
result.check_stdout('3.14')
|
|
result.check_stdout('2.71')
|
|
result.check_not_exist('3.140000')
|
|
result.check_not_exist('2.709999')
|
|
result.check_not_exist('3.139999')
|
|
result.check_not_exist('2.710000')
|
|
|
|
def test_mode_insert_table(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode insert my_table")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('my_table')
|
|
|
|
def test_mode_line(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode line")
|
|
.statement("SELECT NULL, 42, 'fourty-two' x, 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('x = fourty-two')
|
|
|
|
def test_mode_list(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode list")
|
|
.statement("SELECT NULL, 42, 'fourty-two' x, 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('|fourty-two|')
|
|
|
|
# Original comment: FIXME sqlite3_column_blob and %! format specifier
|
|
def test_mode_quote(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode quote")
|
|
.statement("SELECT NULL, 42, 'fourty-two' x, 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('fourty-two')
|
|
|
|
def test_mode_tabs(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode tabs")
|
|
.statement("SELECT NULL, 42, 'fourty-two' x, 42.0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('fourty-two')
|
|
|
|
def test_open(shell, tmp_path):
|
|
file_one = tmp_path / "file_one"
|
|
file_two = tmp_path / "file_two"
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".open {file_one.as_posix()}")
|
|
.statement("CREATE TABLE t1 (i INTEGER);")
|
|
.statement("INSERT INTO t1 VALUES (42);")
|
|
.statement(f".open {file_two.as_posix()}")
|
|
.statement("CREATE TABLE t2 (i INTEGER);")
|
|
.statement("INSERT INTO t2 VALUES (43);")
|
|
.statement(f".open {file_one.as_posix()}")
|
|
.statement("SELECT * FROM t1;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42')
|
|
|
|
@pytest.mark.parametrize('generated_file', ["blablabla"], indirect=True)
|
|
def test_open_non_database(shell, generated_file):
|
|
test = (
|
|
ShellTest(shell)
|
|
.add_argument(generated_file.as_posix())
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('not a valid DuckDB database file')
|
|
|
|
def test_enable_profiling(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("PRAGMA enable_profiling")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('')
|
|
|
|
def test_profiling_select(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("PRAGMA enable_profiling")
|
|
.statement("select 42")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('Query Profiling Information')
|
|
result.check_stdout('42')
|
|
|
|
@pytest.mark.parametrize("command", [
|
|
"system",
|
|
"shell"
|
|
])
|
|
def test_echo_command(shell, command):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".{command} echo 42")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42')
|
|
|
|
def test_profiling_optimizer(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("PRAGMA enable_profiling=query_tree_optimizer;")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('Optimizer')
|
|
result.check_stdout('42')
|
|
|
|
def test_profiling_optimizer_detailed(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("PRAGMA enable_profiling;")
|
|
.statement("PRAGMA profiling_mode=detailed;")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('Optimizer')
|
|
result.check_stdout('42')
|
|
|
|
def test_profiling_optimizer_json(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("PRAGMA enable_profiling=json;")
|
|
.statement("PRAGMA profiling_mode=detailed;")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('optimizer')
|
|
result.check_stdout('42')
|
|
|
|
|
|
# Original comment: this fails because db_config is missing
|
|
@pytest.mark.skip(reason="db_config is not supported (yet?)")
|
|
def test_eqp(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".eqp full")
|
|
.statement("SELECT 42;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('DUMMY_SCAN')
|
|
|
|
def test_clone(shell, random_filepath):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (I INTEGER)")
|
|
.statement("INSERT INTO a VALUES (42)")
|
|
.statement(f".clone {random_filepath.as_posix()}")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('unknown command or invalid arguments')
|
|
|
|
|
|
def test_databases(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".databases")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('memory')
|
|
|
|
|
|
def test_dump_create(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (i INTEGER);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO a VALUES (42);")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE TABLE a(i INTEGER)')
|
|
result.check_stdout('COMMIT')
|
|
|
|
@pytest.mark.parametrize("pattern", [
|
|
"a",
|
|
"a%"
|
|
])
|
|
def test_dump_specific(shell, pattern):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (i INTEGER);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO a VALUES (42);")
|
|
.statement(f".dump {pattern}")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE TABLE a(i INTEGER)')
|
|
|
|
# Original comment: more types, tables and views
|
|
def test_dump_mixed(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE a (d DATE, k FLOAT, t TIMESTAMP);")
|
|
.statement("CREATE TABLE b (c INTEGER);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO a VALUES (DATE '1992-01-01', 0.3, NOW());")
|
|
.statement("INSERT INTO b SELECT * FROM range(0,10);")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE TABLE a(d DATE, k FLOAT, t TIMESTAMP);')
|
|
|
|
def test_dump_blobs(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("create table test(t VARCHAR, b BLOB);")
|
|
.statement(".changes off")
|
|
.statement("insert into test values('literal blob', '\\x07\\x08\\x09');")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("'\\x07\\x08\\x09'")
|
|
|
|
def test_dump_schema_qualified(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA other;")
|
|
.statement("CREATE TABLE other.t_in_other(a INT);")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE SCHEMA IF NOT EXISTS other;')
|
|
result.check_stdout('CREATE TABLE other.t_in_other(a INTEGER);')
|
|
result.check_stdout('COMMIT')
|
|
|
|
def test_dump_schema_with_data(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA test_schema;")
|
|
.statement("CREATE TABLE test_schema.tbl(x INT, y VARCHAR);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO test_schema.tbl VALUES (1, 'hello'), (2, 'world');")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE SCHEMA IF NOT EXISTS test_schema;')
|
|
result.check_stdout('CREATE TABLE test_schema.tbl(x INTEGER, y VARCHAR);')
|
|
result.check_stdout("INSERT INTO test_schema.tbl VALUES(1,'hello');")
|
|
result.check_stdout('COMMIT')
|
|
|
|
def test_dump_multiple_schemas(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA s1;")
|
|
.statement("CREATE SCHEMA s2;")
|
|
.statement("CREATE TABLE s1.t1(a INT);")
|
|
.statement("CREATE TABLE s2.t2(b INT);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO s1.t1 VALUES (10);")
|
|
.statement("INSERT INTO s2.t2 VALUES (20);")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE SCHEMA IF NOT EXISTS s1;')
|
|
result.check_stdout('CREATE SCHEMA IF NOT EXISTS s2;')
|
|
result.check_stdout('INSERT INTO s1.t1 VALUES(10);')
|
|
result.check_stdout('INSERT INTO s2.t2 VALUES(20);')
|
|
|
|
def test_dump_quoted_schema(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement('CREATE SCHEMA "my-schema";')
|
|
.statement('CREATE TABLE "my-schema"."my-table"(a INT);')
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE SCHEMA IF NOT EXISTS "my-schema";')
|
|
result.check_stdout('CREATE TABLE IF NOT EXISTS "my-schema"."my-table"(a INTEGER);')
|
|
|
|
def test_dump_if_not_exists(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE SCHEMA other;")
|
|
.statement("CREATE TABLE IF NOT EXISTS other.tbl(x INT);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO other.tbl VALUES (42);")
|
|
.statement(".dump")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('CREATE SCHEMA IF NOT EXISTS other;')
|
|
result.check_stdout('INSERT INTO other.tbl VALUES(42);')
|
|
result.check_stdout('COMMIT')
|
|
|
|
def test_invalid_csv(shell, tmp_path):
|
|
file = tmp_path / 'nonsencsv.csv'
|
|
with open(file, 'wb+') as f:
|
|
f.write(b'\xFF\n')
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".nullvalue NULL")
|
|
.statement("CREATE TABLE test(i INTEGER);")
|
|
.statement(f".import {file.as_posix()} test")
|
|
.statement("SELECT * FROM test;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('NULL')
|
|
|
|
def test_mode_latex(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode latex")
|
|
.statement("CREATE TABLE a (I INTEGER);")
|
|
.statement(".changes off")
|
|
.statement("INSERT INTO a VALUES (42);")
|
|
.statement("SELECT * FROM a;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('\\begin{tabular}')
|
|
|
|
def test_mode_trash(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode trash")
|
|
.statement("select 1")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('')
|
|
|
|
def test_sqlite_comments(shell):
|
|
# Using /* <comment> */
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("""/*
|
|
;
|
|
*/""")
|
|
.statement("select 42")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42')
|
|
|
|
# Using -- <comment>
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("""-- this is a comment ;
|
|
select 42;
|
|
""")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42')
|
|
|
|
# More extreme: -- <comment>
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("""--;;;;;;
|
|
select 42;
|
|
""")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42')
|
|
|
|
# More extreme: /* <comment> */
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement('/* ;;;;;; */ select 42;')
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('42')
|
|
|
|
def test_duckbox(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".mode duckbox")
|
|
.statement("select 42 limit 0;")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('0 rows')
|
|
|
|
# Original comment: #5411 - with maxrows=2, we still display all 4 rows (hiding them would take up more space)
|
|
def test_maxrows(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".maxrows 2")
|
|
.statement("select * from range(4);")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('1')
|
|
|
|
def test_maxrows_outfile(shell, random_filepath):
|
|
file = random_filepath
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".maxrows 2")
|
|
.statement(f".output {file.as_posix()}")
|
|
.statement("SELECT * FROM range(100);")
|
|
)
|
|
result = test.run()
|
|
result.stdout = open(file, 'rb').read().decode('utf8')
|
|
result.check_stdout('50')
|
|
|
|
def test_columns_to_file(shell, random_filepath):
|
|
columns = ', '.join([str(x) for x in range(100)])
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(f".output {random_filepath.as_posix()}")
|
|
.statement(f"SELECT {columns}")
|
|
)
|
|
result = test.run()
|
|
result.stdout = open(random_filepath, 'rb').read().decode('utf8')
|
|
result.check_stdout('99')
|
|
|
|
def test_columnar_mode(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".col")
|
|
.statement("select * from range(4);")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('Row 1')
|
|
|
|
def test_columnar_mode_constant(shell):
|
|
columns = ','.join(["'MyValue" + str(x) + "'" for x in range(100)])
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".col")
|
|
.statement(f"select {columns};")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('MyValue50')
|
|
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".col")
|
|
.statement(f"select {columns} from range(1000);")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('100 columns')
|
|
|
|
def test_nullbyte_rendering(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("select varchar from test_all_types();")
|
|
)
|
|
result = test.run()
|
|
result.check_stdout('goo\\0se')
|
|
|
|
def test_nullbyte_error_rendering(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("select chr(0)::int")
|
|
)
|
|
result = test.run()
|
|
result.check_stderr('INT32')
|
|
|
|
def test_thousand_sep(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".thousand_sep space")
|
|
.statement("SELECT 10000")
|
|
.statement(".thousand_sep ,")
|
|
.statement("SELECT 10000")
|
|
.statement(".thousand_sep none")
|
|
.statement("SELECT 10000")
|
|
.statement(".thousand_sep")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("10 000")
|
|
result.check_stdout("10,000")
|
|
result.check_stdout("10000")
|
|
result.check_stdout("current thousand separator")
|
|
|
|
def test_decimal_sep(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement(".decimal_sep space")
|
|
.statement("SELECT 10.5")
|
|
.statement(".decimal_sep ,")
|
|
.statement("SELECT 10.5")
|
|
.statement(".decimal_sep none")
|
|
.statement("SELECT 10.5")
|
|
.statement(".decimal_sep")
|
|
)
|
|
|
|
result = test.run()
|
|
result.check_stdout("10 5")
|
|
result.check_stdout("10,5")
|
|
result.check_stdout("10.5")
|
|
result.check_stdout("current decimal separator")
|
|
|
|
def test_prepared_statement(shell):
|
|
test = ShellTest(shell).statement("select ?")
|
|
result = test.run()
|
|
result.check_stderr("Prepared statement parameters cannot be used directly")
|
|
|
|
def test_shell_csv_file(shell):
|
|
test = (
|
|
ShellTest(shell, ['data/csv/dates.csv'])
|
|
.statement('SELECT * FROM dates')
|
|
)
|
|
result = test.run()
|
|
result.check_stdout("2008-08-10")
|
|
|
|
def test_tables_invalid_pattern_handling(shell):
|
|
test = (
|
|
ShellTest(shell)
|
|
.statement("CREATE TABLE test_table(i INTEGER);")
|
|
.statement(".tables \"invalid\"pattern\"")
|
|
)
|
|
result = test.run()
|
|
# Should show usage message for invalid pattern
|
|
result.check_stderr("Usage: .tables ?TABLE?")
|
|
|
|
def test_help_prints_to_stdout(shell):
|
|
test = ShellTest(shell, ["--help"])
|
|
result = test.run()
|
|
result.check_stdout("OPTIONS include:")
|
|
|
|
|
|
# fmt: on
|