Skip to content

Commit

Permalink
test(duckdb): move tests to specific backend test suites
Browse files Browse the repository at this point in the history
  • Loading branch information
cpcloud authored and kszucs committed Jan 29, 2024
1 parent e9aa348 commit 9638eeb
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 138 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SELECT
SUM(t0.a) AS "Sum(a)"
FROM ibis_pandas_memtable_fw3sdos5brerlgtmbkopvh334m AS t0
67 changes: 62 additions & 5 deletions ibis/backends/duckdb/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import os

import duckdb
import pandas as pd
import pyarrow as pa
Expand All @@ -8,7 +10,7 @@

import ibis
import ibis.expr.datatypes as dt
from ibis.conftest import LINUX, SANDBOXED
from ibis.conftest import LINUX, SANDBOXED, not_windows
from ibis.util import gen_name


Expand Down Expand Up @@ -62,8 +64,6 @@ def test_load_extension(ext_directory):


def test_cross_db(tmpdir):
import duckdb

path1 = str(tmpdir.join("test1.ddb"))
with duckdb.connect(path1) as con1:
con1.execute("CREATE SCHEMA foo")
Expand All @@ -86,8 +86,6 @@ def test_cross_db(tmpdir):


def test_attach_detach(tmpdir):
import duckdb

path1 = str(tmpdir.join("test1.ddb"))
with duckdb.connect(path1):
pass
Expand Down Expand Up @@ -211,3 +209,62 @@ def test_insert_preserves_column_case(con):
t2 = con.create_table(name2, df2, temp=True)
con.insert(name1, t2)
assert t1.count().execute() == 8


def test_default_backend(snapshot):
df = pd.DataFrame({"a": [1, 2, 3]})
t = ibis.memtable(df)
expr = t.a.sum()

# run this twice to ensure that we hit the optimizations in
# `_default_backend`
for _ in range(2):
assert expr.execute() == df.a.sum()

sql = ibis.to_sql(expr)
snapshot.assert_match(sql, "out.sql")


@pytest.mark.parametrize(
"url",
[
param(lambda p: p, id="no-scheme-duckdb-ext"),
param(lambda p: f"duckdb://{p}", id="absolute-path"),
param(
lambda p: f"duckdb://{os.path.relpath(p)}",
marks=[
not_windows
], # hard to test in CI since tmpdir & cwd are on different drives
id="relative-path",
),
param(lambda _: "duckdb://", id="in-memory-empty"),
param(lambda _: "duckdb://:memory:", id="in-memory-explicit"),
param(lambda p: f"duckdb://{p}?read_only=1", id="duckdb_read_write_int"),
param(lambda p: f"duckdb://{p}?read_only=False", id="duckdb_read_write_upper"),
param(lambda p: f"duckdb://{p}?read_only=false", id="duckdb_read_write_lower"),
],
)
def test_connect_duckdb(url, tmp_path):
path = os.path.abspath(tmp_path / "test.duckdb")
with duckdb.connect(path):
pass
con = ibis.connect(url(path))
one = ibis.literal(1)
assert con.execute(one) == 1


@pytest.mark.parametrize(
"out_method, extension", [("to_csv", "csv"), ("to_parquet", "parquet")]
)
def test_connect_local_file(out_method, extension, test_employee_data_1, tmp_path):
getattr(test_employee_data_1, out_method)(tmp_path / f"out.{extension}")
con = ibis.connect(tmp_path / f"out.{extension}")
t = next(iter(con.tables.values()))
assert not t.head().execute().empty


@not_windows
def test_invalid_connect(tmp_path):
url = f"duckdb://{tmp_path}?read_only=invalid_value"
with pytest.raises(ValueError):
ibis.connect(url)
32 changes: 32 additions & 0 deletions ibis/backends/sqlite/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from __future__ import annotations

import os
import sqlite3
import uuid
from pathlib import Path

import numpy as np
import pandas.testing as tm
import pytest
from pytest import param

import ibis
import ibis.expr.types as ir
from ibis import config, udf
from ibis.conftest import not_windows

pytest.importorskip("sqlalchemy")

Expand Down Expand Up @@ -132,3 +136,31 @@ def total(x) -> float:
expr = total(con.tables.functional_alltypes.limit(2).select(n=ibis.NA).n)
result = con.execute(expr)
assert result == 0.0


@pytest.mark.sqlite
@pytest.mark.parametrize(
"url, ext",
[
param(lambda p: p, "sqlite", id="no-scheme-sqlite-ext"),
param(lambda p: p, "db", id="no-scheme-db-ext"),
param(lambda p: f"sqlite://{p}", "db", id="absolute-path"),
param(
lambda p: f"sqlite://{os.path.relpath(p)}",
"db",
marks=[
not_windows
], # hard to test in CI since tmpdir & cwd are on different drives
id="relative-path",
),
param(lambda _: "sqlite://", "db", id="in-memory-empty"),
param(lambda _: "sqlite://:memory:", "db", id="in-memory-explicit"),
],
)
def test_connect_sqlite(url, ext, tmp_path):
path = os.path.abspath(tmp_path / f"test.{ext}")
with sqlite3.connect(path):
pass
con = ibis.connect(url(path))
one = ibis.literal(1)
assert con.execute(one) == 1
133 changes: 0 additions & 133 deletions ibis/backends/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import contextlib
import importlib
import inspect
import os
import platform
import re
import string
import subprocess
Expand Down Expand Up @@ -756,116 +754,6 @@ def test_connect_url(url):
assert con.execute(one) == 1


not_windows = pytest.mark.skipif(
condition=platform.system() == "Windows",
reason=(
"windows prevents two connections to the same duckdb file even in "
"the same process"
),
)


@pytest.fixture(params=["duckdb", "sqlite"])
def tmp_db(request, tmp_path):
api = request.param
mod = pytest.importorskip(api)
db = tmp_path / "test.db"
mod.connect(str(db)).execute("CREATE TABLE tmp_t AS SELECT 1 AS a").fetchall()
return db


@pytest.mark.duckdb
@pytest.mark.parametrize(
"url",
[
param(lambda p: p, id="no-scheme-duckdb-ext"),
param(lambda p: f"duckdb://{p}", id="absolute-path"),
param(
lambda p: f"duckdb://{os.path.relpath(p)}",
marks=[
not_windows
], # hard to test in CI since tmpdir & cwd are on different drives
id="relative-path",
),
param(lambda _: "duckdb://", id="in-memory-empty"),
param(lambda _: "duckdb://:memory:", id="in-memory-explicit"),
param(
lambda p: f"duckdb://{p}?read_only=1",
id="duckdb_read_write_int",
),
param(
lambda p: f"duckdb://{p}?read_only=False",
id="duckdb_read_write_upper",
),
param(
lambda p: f"duckdb://{p}?read_only=false",
id="duckdb_read_write_lower",
),
],
)
def test_connect_duckdb(url, tmp_path):
duckdb = pytest.importorskip("duckdb")
path = os.path.abspath(tmp_path / "test.duckdb")
with duckdb.connect(path):
pass
con = ibis.connect(url(path))
one = ibis.literal(1)
assert con.execute(one) == 1


@pytest.mark.sqlite
@pytest.mark.parametrize(
"url, ext",
[
param(lambda p: p, "sqlite", id="no-scheme-sqlite-ext"),
param(lambda p: p, "db", id="no-scheme-db-ext"),
param(lambda p: f"sqlite://{p}", "db", id="absolute-path"),
param(
lambda p: f"sqlite://{os.path.relpath(p)}",
"db",
marks=[
not_windows
], # hard to test in CI since tmpdir & cwd are on different drives
id="relative-path",
),
param(lambda _: "sqlite://", "db", id="in-memory-empty"),
param(lambda _: "sqlite://:memory:", "db", id="in-memory-explicit"),
],
)
def test_connect_sqlite(url, ext, tmp_path):
import sqlite3

path = os.path.abspath(tmp_path / f"test.{ext}")
with sqlite3.connect(path):
pass
con = ibis.connect(url(path))
one = ibis.literal(1)
assert con.execute(one) == 1


@pytest.mark.duckdb
@pytest.mark.parametrize(
"out_method, extension",
[
("to_csv", "csv"),
("to_parquet", "parquet"),
],
)
def test_connect_local_file(out_method, extension, test_employee_data_1, tmp_path):
getattr(test_employee_data_1, out_method)(tmp_path / f"out.{extension}")
con = ibis.connect(tmp_path / f"out.{extension}")
t = next(iter(con.tables.values()))
assert not t.head().execute().empty


@not_windows
def test_invalid_connect(tmp_path):
pytest.importorskip("duckdb")
url = f"duckdb://{tmp_path}?read_only=invalid_value"
with pytest.raises(ValueError):
ibis.connect(url)


@pytest.mark.parametrize(
("arg", "lambda_", "expected"),
[
Expand Down Expand Up @@ -971,7 +859,6 @@ def test_create_from_in_memory_table(con, temp_table, arg, func, monkeypatch):
assert temp_table in con.list_tables()


@pytest.mark.usefixtures("backend")
def test_default_backend_option(con, monkeypatch):
# verify that there's nothing already set
assert ibis.options.default_backend is None
Expand Down Expand Up @@ -1028,26 +915,6 @@ def test_default_backend_no_duckdb_read_parquet():
)


@pytest.mark.duckdb
def test_default_backend():
pytest.importorskip("duckdb")

df = pd.DataFrame({"a": [1, 2, 3]})
t = ibis.memtable(df)
expr = t.a.sum()
# run this twice to ensure that we hit the optimizations in
# `_default_backend`
for _ in range(2):
assert expr.execute() == df.a.sum()

sql = ibis.to_sql(expr)
rx = """\
SELECT
SUM\\((t\\d+)\\.a\\) AS ".+"
FROM \\w+ AS \\1"""
assert re.match(rx, sql) is not None


@pytest.mark.parametrize("dtype", [None, "f8"])
def test_dunder_array_table(alltypes, dtype):
expr = alltypes.group_by("string_col").int_col.sum().order_by("string_col")
Expand Down
6 changes: 6 additions & 0 deletions ibis/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ def add_ibis(monkeypatch, doctest_namespace):
# the clash doesn't really pop up in practice, but we can rename it to
# `all_` in 6.0 if desired
doctest_namespace["all"] = builtins.all


not_windows = pytest.mark.skipif(
condition=WINDOWS,
reason="windows prevents two connections to the same file even in the same process",
)

0 comments on commit 9638eeb

Please sign in to comment.