Skip to content

Commit 34a7c35

Browse files
committed
Fixed adbc driver and force tests to run
1 parent 0bfc31d commit 34a7c35

File tree

7 files changed

+81
-55
lines changed

7 files changed

+81
-55
lines changed

CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,37 @@ pybind11_add_module(
7979
# add _duckdb_dependencies
8080
target_link_libraries(_duckdb PRIVATE _duckdb_dependencies)
8181

82+
# ────────────────────────────────────────────
83+
# Controlling symbol export
84+
#
85+
# We want to export exactly two symbols: - PyInit__duckdb: this allows CPython
86+
# to load the module - duckdb_adbc_init: the DuckDB ADBC driver
87+
#
88+
# The export of symbols on OSX and Linux is controlled by: - Visibility
89+
# annotations in the code (for this lib we use the PYBIND11_EXPORT macro) -
90+
# Telling the linker which symbols we want exported, which we do below
91+
#
92+
# For Windows, we rely on just the visbility annotations.
93+
# ────────────────────────────────────────────
94+
set_target_properties(
95+
_duckdb
96+
PROPERTIES CXX_VISIBILITY_PRESET hidden
97+
C_VISIBILITY_PRESET hidden
98+
VISIBILITY_INLINES_HIDDEN ON)
99+
100+
if(APPLE)
101+
target_link_options(
102+
_duckdb PRIVATE "LINKER:-exported_symbol,_duckdb_adbc_init"
103+
"LINKER:-exported_symbol,_PyInit__duckdb")
104+
elseif(UNIX AND NOT APPLE)
105+
target_link_options(
106+
_duckdb PRIVATE "LINKER:--export-dynamic-symbol=duckdb_adbc_init"
107+
"LINKER:--export-dynamic-symbol=PyInit__duckdb")
108+
endif()
109+
110+
# if(MSVC) target_link_options(_duckdb PRIVATE "/INCLUDE:duckdb_adbc_init")
111+
# else() target_link_options(_duckdb PRIVATE "-Wl,-u,duckdb_adbc_init") endif()
112+
82113
# ────────────────────────────────────────────
83114
# Put the object file in the correct place
84115
# ────────────────────────────────────────────

adbc_driver_duckdb/__init__.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@
1919

2020
import enum
2121
import functools
22+
import importlib
2223
import typing
2324

2425
import adbc_driver_manager
2526

26-
__all__ = ["StatementOptions", "connect"]
27-
2827

2928
class StatementOptions(enum.Enum):
3029
"""Statement options specific to the DuckDB driver."""
@@ -36,12 +35,16 @@ class StatementOptions(enum.Enum):
3635
def connect(path: typing.Optional[str] = None) -> adbc_driver_manager.AdbcDatabase:
3736
"""Create a low level ADBC connection to DuckDB."""
3837
if path is None:
39-
return adbc_driver_manager.AdbcDatabase(driver=_driver_path(), entrypoint="duckdb_adbc_init")
40-
return adbc_driver_manager.AdbcDatabase(driver=_driver_path(), entrypoint="duckdb_adbc_init", path=path)
38+
return adbc_driver_manager.AdbcDatabase(driver=driver_path(), entrypoint="duckdb_adbc_init")
39+
return adbc_driver_manager.AdbcDatabase(driver=driver_path(), entrypoint="duckdb_adbc_init", path=path)
4140

4241

4342
@functools.cache
44-
def _driver_path() -> str:
45-
import duckdb
46-
47-
return duckdb.duckdb.__file__
43+
def driver_path() -> str:
44+
"""Get the path to the DuckDB ADBC driver."""
45+
duckdb_module_spec = importlib.util.find_spec("_duckdb")
46+
if duckdb_module_spec is None:
47+
msg = "Could not find duckdb shared library. Did you pip install duckdb?"
48+
raise ImportError(msg)
49+
print(f"Found duckdb shared library at {duckdb_module_spec.origin}")
50+
return duckdb_module_spec.origin

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ all = [ # users can install duckdb with 'duckdb[all]', which will install this l
4848
"numpy", # used in duckdb.experimental.spark and in duckdb.fetchnumpy()
4949
"pandas", # used for pandas dataframes all over the place
5050
"pyarrow", # used for pyarrow support
51-
"adbc_driver_manager", # for the adbc driver (TODO: this should live under the duckdb package)
51+
"adbc-driver-manager", # for the adbc driver
5252
]
5353

5454
######################################################################################################
@@ -225,6 +225,7 @@ stubdeps = [ # dependencies used for typehints in the stubs
225225
"pyarrow",
226226
]
227227
test = [ # dependencies used for running tests
228+
"adbc-driver-manager",
228229
"pytest",
229230
"pytest-reraise",
230231
"pytest-timeout",

src/duckdb_py/duckdb_python.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,15 @@ static void RegisterExpectedResultType(py::handle &m) {
10071007
expected_return_type.export_values();
10081008
}
10091009

1010+
/**
1011+
* Fwd declaration of duckdb_adbc_init (the entrypoint of our ADBC driver) that must be exported.
1012+
*/
1013+
extern "C" PYBIND11_EXPORT extern void duckdb_adbc_init(void);
1014+
10101015
PYBIND11_MODULE(DUCKDB_PYTHON_LIB_NAME, m) { // NOLINT
1016+
// Take address to force duckdb_adbc_init symbol inclusion
1017+
(void)&duckdb_adbc_init;
1018+
10111019
py::enum_<duckdb::ExplainType>(m, "ExplainType")
10121020
.value("STANDARD", duckdb::ExplainType::EXPLAIN_STANDARD)
10131021
.value("ANALYZE", duckdb::ExplainType::EXPLAIN_ANALYZE)

tests/fast/adbc/test_adbc.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
import datetime
22
from pathlib import Path
33

4+
import adbc_driver_manager.dbapi
45
import numpy as np
6+
import pyarrow
57
import pytest
68

7-
import duckdb
8-
9-
adbc_driver_manager = pytest.importorskip("adbc_driver_manager.dbapi")
10-
adbc_driver_manager_lib = pytest.importorskip("adbc_driver_manager._lib")
11-
12-
pyarrow = pytest.importorskip("pyarrow")
9+
import adbc_driver_duckdb.dbapi
1310

1411
# When testing local, if you build via BUILD_PYTHON=1 make, you need to manually set up the
1512
# dylib duckdb path.
16-
driver_path = duckdb.duckdb.__file__
13+
driver_path = adbc_driver_duckdb.driver_path()
1714

1815

1916
@pytest.fixture
2017
def duck_conn():
21-
with adbc_driver_manager.connect(driver=driver_path, entrypoint="duckdb_adbc_init") as conn:
18+
with adbc_driver_manager.dbapi.connect(driver=driver_path, entrypoint="duckdb_adbc_init") as conn:
2219
yield conn
2320

2421

@@ -98,7 +95,7 @@ def test_commit(tmp_path):
9895
table = example_table()
9996
db_kwargs = {"path": f"{db}"}
10097
# Start connection with auto-commit off
101-
with adbc_driver_manager.connect(
98+
with adbc_driver_manager.dbapi.connect(
10299
driver=driver_path,
103100
entrypoint="duckdb_adbc_init",
104101
db_kwargs=db_kwargs,
@@ -108,7 +105,7 @@ def test_commit(tmp_path):
108105
cur.adbc_ingest("ingest", table, "create")
109106

110107
# Check Data is not there
111-
with adbc_driver_manager.connect(
108+
with adbc_driver_manager.dbapi.connect(
112109
driver=driver_path,
113110
entrypoint="duckdb_adbc_init",
114111
db_kwargs=db_kwargs,
@@ -118,7 +115,7 @@ def test_commit(tmp_path):
118115
with conn.cursor() as cur:
119116
# This errors because the table does not exist
120117
with pytest.raises(
121-
adbc_driver_manager_lib.InternalError,
118+
adbc_driver_manager._lib.InternalError,
122119
match=r"Table with name ingest does not exist!",
123120
):
124121
cur.execute("SELECT count(*) from ingest")
@@ -127,7 +124,7 @@ def test_commit(tmp_path):
127124

128125
# This now works because we enabled autocommit
129126
with (
130-
adbc_driver_manager.connect(
127+
adbc_driver_manager.dbapi.connect(
131128
driver=driver_path,
132129
entrypoint="duckdb_adbc_init",
133130
db_kwargs=db_kwargs,
@@ -221,7 +218,7 @@ def test_insertion(duck_conn):
221218
# Test Append
222219
with duck_conn.cursor() as cursor:
223220
with pytest.raises(
224-
adbc_driver_manager_lib.InternalError,
221+
adbc_driver_manager.InternalError,
225222
match=r'Table with name "ingest_table" already exists!',
226223
):
227224
cursor.adbc_ingest("ingest_table", table, "create")
@@ -299,7 +296,7 @@ def test_large_chunk(tmp_path):
299296
db.unlink()
300297
db_kwargs = {"path": f"{db}"}
301298
with (
302-
adbc_driver_manager.connect(
299+
adbc_driver_manager.dbapi.connect(
303300
driver=driver_path,
304301
entrypoint="duckdb_adbc_init",
305302
db_kwargs=db_kwargs,
@@ -325,7 +322,7 @@ def test_dictionary_data(tmp_path):
325322
db.unlink()
326323
db_kwargs = {"path": f"{db}"}
327324
with (
328-
adbc_driver_manager.connect(
325+
adbc_driver_manager.dbapi.connect(
329326
driver=driver_path,
330327
entrypoint="duckdb_adbc_init",
331328
db_kwargs=db_kwargs,
@@ -353,7 +350,7 @@ def test_ree_data(tmp_path):
353350
db.unlink()
354351
db_kwargs = {"path": f"{db}"}
355352
with (
356-
adbc_driver_manager.connect(
353+
adbc_driver_manager.dbapi.connect(
357354
driver=driver_path,
358355
entrypoint="duckdb_adbc_init",
359356
db_kwargs=db_kwargs,

tests/fast/adbc/test_connection_get_info.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,19 @@
1-
import pytest
1+
import pyarrow as pa
22

3+
import adbc_driver_duckdb.dbapi
34
import duckdb
45

5-
pa = pytest.importorskip("pyarrow")
6-
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
7-
8-
9-
try:
10-
adbc_driver_duckdb = pytest.importorskip("adbc_driver_duckdb.dbapi")
11-
con = adbc_driver_duckdb.connect()
12-
except adbc_driver_manager.InternalError as e:
13-
pytest.skip(
14-
f"'duckdb_adbc_init' was not exported in this install, try running 'python3 setup.py install': {e}",
15-
allow_module_level=True,
16-
)
17-
186

197
class TestADBCConnectionGetInfo:
208
def test_connection_basic(self):
21-
con = adbc_driver_duckdb.connect()
9+
con = adbc_driver_duckdb.dbapi.connect()
2210
with con.cursor() as cursor:
2311
cursor.execute("select 42")
2412
res = cursor.fetchall()
2513
assert res == [(42,)]
2614

2715
def test_connection_get_info_all(self):
28-
con = adbc_driver_duckdb.connect()
16+
con = adbc_driver_duckdb.dbapi.connect()
2917
adbc_con = con.adbc_connection
3018
res = adbc_con.get_info()
3119
reader = pa.RecordBatchReader._import_from_c(res.address)
@@ -35,7 +23,7 @@ def test_connection_get_info_all(self):
3523
expected_result = pa.array(
3624
[
3725
"duckdb",
38-
"v" + duckdb.__version__, # don't hardcode this, as it will change every version
26+
"v" + duckdb.duckdb_version, # don't hardcode this, as it will change every version
3927
"ADBC DuckDB Driver",
4028
"(unknown)",
4129
"(unknown)",
@@ -49,7 +37,7 @@ def test_connection_get_info_all(self):
4937
assert string_values == expected_result
5038

5139
def test_empty_result(self):
52-
con = adbc_driver_duckdb.connect()
40+
con = adbc_driver_duckdb.dbapi.connect()
5341
adbc_con = con.adbc_connection
5442
res = adbc_con.get_info([1337])
5543
reader = pa.RecordBatchReader._import_from_c(res.address)
@@ -60,7 +48,7 @@ def test_empty_result(self):
6048
assert values.num_chunks == 0
6149

6250
def test_unrecognized_codes(self):
63-
con = adbc_driver_duckdb.connect()
51+
con = adbc_driver_duckdb.dbapi.connect()
6452
adbc_con = con.adbc_connection
6553
res = adbc_con.get_info([0, 1000, 4, 2000])
6654
reader = pa.RecordBatchReader._import_from_c(res.address)

tests/fast/adbc/test_statement_bind.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1+
import adbc_driver_manager
2+
import pyarrow as pa
13
import pytest
24

3-
pa = pytest.importorskip("pyarrow")
4-
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
5-
6-
adbc_driver_duckdb = pytest.importorskip("adbc_driver_duckdb.dbapi")
7-
con = adbc_driver_duckdb.connect()
5+
import adbc_driver_duckdb.dbapi
86

97

108
def _import(handle):
@@ -33,7 +31,7 @@ def test_bind_multiple_rows(self):
3331
names=["ints"],
3432
)
3533

36-
con = adbc_driver_duckdb.connect()
34+
con = adbc_driver_duckdb.dbapi.connect()
3735
with con.cursor() as cursor:
3836
statement = cursor.adbc_statement
3937
statement.set_sql_query("select ? * 2 as i")
@@ -55,7 +53,7 @@ def test_bind_single_row(self):
5553
names=["ints"],
5654
)
5755

58-
con = adbc_driver_duckdb.connect()
56+
con = adbc_driver_duckdb.dbapi.connect()
5957
with con.cursor() as cursor:
6058
statement = cursor.adbc_statement
6159
statement.set_sql_query("select ? * 2 as i")
@@ -90,7 +88,7 @@ def test_multiple_parameters(self):
9088
names=["ints", "strings", "bools"],
9189
)
9290

93-
con = adbc_driver_duckdb.connect()
91+
con = adbc_driver_duckdb.dbapi.connect()
9492
with con.cursor() as cursor:
9593
statement = cursor.adbc_statement
9694
statement.set_sql_query("select ? as a, ? as b, ? as c")
@@ -120,7 +118,7 @@ def test_bind_composite_type(self):
120118
# Create the RecordBatch
121119
record_batch = pa.RecordBatch.from_arrays([struct_array], schema=schema)
122120

123-
con = adbc_driver_duckdb.connect()
121+
con = adbc_driver_duckdb.dbapi.connect()
124122
with con.cursor() as cursor:
125123
statement = cursor.adbc_statement
126124
statement.set_sql_query("select ? as a")
@@ -143,7 +141,7 @@ def test_too_many_parameters(self):
143141
names=["ints", "strings"],
144142
)
145143

146-
con = adbc_driver_duckdb.connect()
144+
con = adbc_driver_duckdb.dbapi.connect()
147145
with con.cursor() as cursor:
148146
statement = cursor.adbc_statement
149147
statement.set_sql_query("select ? as a")
@@ -171,7 +169,7 @@ def test_not_enough_parameters(self):
171169
names=["strings"],
172170
)
173171

174-
con = adbc_driver_duckdb.connect()
172+
con = adbc_driver_duckdb.dbapi.connect()
175173
with con.cursor() as cursor:
176174
statement = cursor.adbc_statement
177175
statement.set_sql_query("select ? as a, ? as b")

0 commit comments

Comments
 (0)