Skip to content

Commit 6ee1274

Browse files
authored
feat: test temp tables for vendors that put them in same namespace (#174)
1 parent 1dd0796 commit 6ee1274

4 files changed

Lines changed: 80 additions & 54 deletions

File tree

adbc_drivers_validation/model.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ class DriverFeatures(BaseModel):
129129
_secondary_catalog: str | FromEnv | None = PrivateAttr(default=None)
130130
_secondary_catalog_schema: str | FromEnv | None = PrivateAttr(default=None)
131131
supported_xdbc_fields: list[str] = Field(default_factory=list)
132+
# Some databases support temporary tables, but they exist in the same
133+
# namespace as regular tables, so we need to change how we test them.
134+
quirk_bulk_ingest_temporary_shares_namespace: bool = Field(default=False)
132135
# Some vendors sort the columns, so declaring FOREIGN KEY(b, a) REFERENCES
133136
# foo(d, c) still gets returned in the order (a, c), (b, d)
134137
quirk_get_objects_constraints_foreign_normalized: bool = Field(default=False)
@@ -331,8 +334,10 @@ def qualify_temp_table(
331334
"""
332335
raise NotImplementedError
333336

334-
def quote_identifier(self, *identifiers: str) -> str:
335-
return ".".join(self.quote_one_identifier(ident) for ident in identifiers)
337+
def quote_identifier(self, *identifiers: str | None) -> str:
338+
return ".".join(
339+
self.quote_one_identifier(ident) for ident in identifiers if ident
340+
)
336341

337342
def quote_one_identifier(self, identifier: str) -> str:
338343
"""Quote an identifier (e.g. table or column name)."""
@@ -546,6 +551,9 @@ def merge(
546551
# absent a specific bind query, we should bind the
547552
# parameters to the regular query
548553
if parent.query.bind_query_path:
554+
# If one is defined, all of them must be
555+
assert parent.query.bind_schema_path is not None
556+
assert parent.query.bind_path is not None
549557
params["bind_query_path"] = parent.query.bind_query_path
550558
params["bind_schema_path"] = parent.query.bind_schema_path
551559
params["bind_path"] = parent.query.bind_path

adbc_drivers_validation/tests/ingest.py

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ def generate_tests(
5050
driver_param = f"{quirks.name}:{quirks.short_version}"
5151
enabled = {
5252
"test_not_null": quirks.features.statement_bulk_ingest,
53-
"test_temporary": quirks.features.statement_bulk_ingest_temporary,
5453
"test_schema": quirks.features.statement_bulk_ingest_schema,
5554
"test_catalog": quirks.features.statement_bulk_ingest_catalog,
5655
"test_many_columns": quirks.features.statement_bulk_ingest,
@@ -70,6 +69,7 @@ def generate_tests(
7069
enabled = {
7170
"test_replace_catalog": quirks.features.statement_bulk_ingest_catalog,
7271
"test_replace_schema": quirks.features.statement_bulk_ingest_schema,
72+
"test_temporary": quirks.features.statement_bulk_ingest_temporary,
7373
}.get(metafunc.definition.name, None)
7474
for query in quirks.query_set.queries.values():
7575
marks = []
@@ -422,51 +422,69 @@ def test_temporary(
422422
self,
423423
driver: model.DriverQuirks,
424424
conn_factory: typing.Callable[[], adbc_driver_manager.dbapi.Connection],
425+
query: Query,
425426
) -> None:
426-
data1 = pyarrow.Table.from_pydict(
427-
{
428-
"idx": [1, 2, 3],
429-
"value": ["foo", "bar", "baz"],
430-
}
431-
)
432-
data2 = pyarrow.Table.from_pydict(
433-
{
434-
"idx": [4, 5, 6],
435-
"value": ["qux", "quux", "spam"],
436-
}
437-
)
427+
subquery = query.query
428+
assert isinstance(subquery, model.IngestQuery)
429+
data = subquery.input()
430+
expected = subquery.expected()
431+
438432
table_name = "test_ingest_temporary"
439433

440434
idx = driver.quote_identifier("idx")
441435
value = driver.quote_identifier("value")
442-
443-
with conn_factory() as conn:
444-
with conn.cursor() as cursor:
445-
driver.try_drop_table(cursor, table_name=table_name)
446-
cursor.adbc_ingest(table_name, data1, temporary=True)
447-
cursor.adbc_ingest(table_name, data2, temporary=False)
448-
449-
with conn.cursor() as cursor:
450-
assert driver.features.current_schema is not None
451-
normal_table = driver.quote_identifier(
452-
driver.features.current_schema, table_name
453-
)
454-
temp_table = driver.qualify_temp_table(cursor, table_name)
455-
select_normal = (
456-
f"SELECT {idx}, {value} FROM {normal_table} ORDER BY {idx} ASC"
457-
)
458-
select_temporary = (
459-
f"SELECT {idx}, {value} FROM {temp_table} ORDER BY {idx} ASC"
460-
)
461-
462-
result_normal = execute_query_without_prepare(cursor, select_normal)
463-
464-
result_temporary = execute_query_without_prepare(
465-
cursor, select_temporary
466-
)
467-
468-
compare.compare_tables(data1, result_temporary)
469-
compare.compare_tables(data2, result_normal)
436+
if driver.features.quirk_bulk_ingest_temporary_shares_namespace:
437+
with conn_factory() as conn:
438+
with conn.cursor() as cursor:
439+
driver.try_drop_table(cursor, table_name=table_name)
440+
cursor.adbc_ingest(table_name, data, temporary=True)
441+
temp_table = driver.qualify_temp_table(cursor, table_name)
442+
select_temporary = (
443+
f"SELECT {idx}, {value} FROM {temp_table} ORDER BY {idx} ASC"
444+
)
445+
result_temporary = execute_query_without_prepare(
446+
cursor, select_temporary
447+
)
448+
compare.compare_tables(expected, result_temporary)
449+
450+
with conn_factory() as conn:
451+
with conn.cursor() as cursor:
452+
temp_table = driver.qualify_temp_table(cursor, table_name)
453+
select_temporary = (
454+
f"SELECT {idx}, {value} FROM {temp_table} ORDER BY {idx} ASC"
455+
)
456+
with pytest.raises(Exception) as excinfo:
457+
execute_query_without_prepare(cursor, select_temporary)
458+
assert driver.is_table_not_found(table_name, excinfo.value)
459+
else:
460+
with conn_factory() as conn:
461+
with conn.cursor() as cursor:
462+
driver.try_drop_table(cursor, table_name=table_name)
463+
cursor.adbc_ingest(table_name, data.slice(0, 1), temporary=True)
464+
cursor.adbc_ingest(table_name, data.slice(1), temporary=False)
465+
466+
with conn.cursor() as cursor:
467+
assert driver.features.current_schema is not None
468+
normal_table = driver.quote_identifier(
469+
driver.features.current_catalog,
470+
driver.features.current_schema,
471+
table_name,
472+
)
473+
temp_table = driver.qualify_temp_table(cursor, table_name)
474+
select_normal = (
475+
f"SELECT {idx}, {value} FROM {normal_table} ORDER BY {idx} ASC"
476+
)
477+
select_temporary = (
478+
f"SELECT {idx}, {value} FROM {temp_table} ORDER BY {idx} ASC"
479+
)
480+
481+
result_normal = execute_query_without_prepare(cursor, select_normal)
482+
result_temporary = execute_query_without_prepare(
483+
cursor, select_temporary
484+
)
485+
486+
compare.compare_tables(expected.slice(0, 1), result_temporary)
487+
compare.compare_tables(expected.slice(1), result_normal)
470488

471489
def test_schema(
472490
self,

adbc_drivers_validation/tests/query.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,17 @@ def generate_tests(all_quirks: list[model.DriverQuirks], metafunc) -> None:
5252
):
5353
marks.append(pytest.mark.skip(reason="bind not supported"))
5454

55-
if metafunc.definition.name == "test_execute_schema":
55+
if metafunc.definition.name in {
56+
"test_execute_schema",
57+
"test_get_table_schema",
58+
}:
5659
if not isinstance(query.query, model.SelectQuery):
5760
continue
5861
if not query.name.startswith("type/select/"):
5962
# There's no need to repeat this test multiple times per type
6063
continue
6164
if not quirks.features.statement_execute_schema:
6265
marks.append(pytest.mark.skip(reason="not implemented"))
63-
elif metafunc.definition.name == "test_get_table_schema":
64-
if not isinstance(query.query, model.SelectQuery):
65-
continue
66-
elif not query.name.startswith("type/select/"):
67-
continue
68-
elif not quirks.features.connection_get_table_schema:
69-
marks.append(pytest.mark.skip(reason="not implemented"))
7066
elif metafunc.definition.name == "test_query":
7167
if not isinstance(query.query, model.SelectQuery):
7268
continue

adbc_drivers_validation/utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,13 @@ def execute_query_without_prepare(
135135
The result of the query.
136136
"""
137137
cursor.adbc_statement.set_sql_query(query)
138-
handle, _ = cursor.adbc_statement.execute_query()
139-
with pyarrow.RecordBatchReader._import_from_c(handle.address) as reader:
140-
return reader.read_all()
138+
try:
139+
handle, _ = cursor.adbc_statement.execute_query()
140+
with pyarrow.RecordBatchReader._import_from_c(handle.address) as reader:
141+
return reader.read_all()
142+
except Exception as e:
143+
e.add_note(f"Query: {query}")
144+
raise
141145

142146

143147
def arrow_type_name(arrow_type, metadata=None, show_type_parameters=False):

0 commit comments

Comments
 (0)