blob: 1db468c502fe20db850ad4c0c08d8ab645f45b8f [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Tests for bulk ingest functionality using GizmoSQL."""
import pyarrow as pa
import adbc_driver_manager
# Aliases for long constant names to keep lines under 88 chars
INGEST_TARGET_TABLE = adbc_driver_manager.StatementOptions.INGEST_TARGET_TABLE.value
INGEST_MODE = adbc_driver_manager.StatementOptions.INGEST_MODE.value
def test_ingest_create(gizmosql):
"""Test creating a new table via bulk ingest."""
table_name = "test_ingest_create"
# Create sample data
data = pa.table(
{
"id": pa.array([1, 2, 3], type=pa.int64()),
"name": pa.array(["Alice", "Bob", "Charlie"], type=pa.string()),
"value": pa.array([1.1, 2.2, 3.3], type=pa.float64()),
}
)
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
# Configure for bulk ingest - create mode
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.create",
}
)
# Bind the data
stmt.bind_stream(data.to_reader())
# Execute the ingest
rows_affected = stmt.execute_update()
assert rows_affected == 3
# Verify the data was ingested by querying it back
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_sql_query(f"SELECT * FROM {table_name} ORDER BY id")
stream, _ = stmt.execute_query()
reader = pa.RecordBatchReader._import_from_c(stream.address)
result = reader.read_all()
assert result.num_rows == 3
assert result.column("id").to_pylist() == [1, 2, 3]
assert result.column("name").to_pylist() == ["Alice", "Bob", "Charlie"]
def test_ingest_append(gizmosql):
"""Test appending to an existing table via bulk ingest."""
table_name = "test_ingest_append"
# First create the table with initial data
initial_data = pa.table(
{
"id": pa.array([1, 2], type=pa.int64()),
"value": pa.array([10, 20], type=pa.int32()),
}
)
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.create",
}
)
stmt.bind_stream(initial_data.to_reader())
stmt.execute_update()
# Now append more data
append_data = pa.table(
{
"id": pa.array([3, 4], type=pa.int64()),
"value": pa.array([30, 40], type=pa.int32()),
}
)
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.append",
}
)
stmt.bind_stream(append_data.to_reader())
rows_affected = stmt.execute_update()
assert rows_affected == 2
# Verify all data is present
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_sql_query(f"SELECT COUNT(*) as cnt FROM {table_name}")
stream, _ = stmt.execute_query()
reader = pa.RecordBatchReader._import_from_c(stream.address)
result = reader.read_all()
assert result.column("cnt")[0].as_py() == 4
def test_ingest_replace(gizmosql):
"""Test replacing a table via bulk ingest."""
table_name = "test_ingest_replace"
# First create the table with initial data
initial_data = pa.table(
{
"id": pa.array([1, 2, 3], type=pa.int64()),
}
)
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.create",
}
)
stmt.bind_stream(initial_data.to_reader())
stmt.execute_update()
# Now replace with new data
replace_data = pa.table(
{
"id": pa.array([100, 200], type=pa.int64()),
}
)
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.replace",
}
)
stmt.bind_stream(replace_data.to_reader())
rows_affected = stmt.execute_update()
assert rows_affected == 2
# Verify only the new data is present
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_sql_query(f"SELECT * FROM {table_name} ORDER BY id")
stream, _ = stmt.execute_query()
reader = pa.RecordBatchReader._import_from_c(stream.address)
result = reader.read_all()
assert result.num_rows == 2
assert result.column("id").to_pylist() == [100, 200]
def test_ingest_create_append(gizmosql):
"""Test create_append mode - creates if not exists, appends if exists."""
table_name = "test_ingest_create_append"
data = pa.table(
{
"id": pa.array([1, 2], type=pa.int64()),
}
)
# First call should create the table
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.create_append",
}
)
stmt.bind_stream(data.to_reader())
rows_affected = stmt.execute_update()
assert rows_affected == 2
# Second call should append
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.create_append",
}
)
stmt.bind_stream(data.to_reader())
rows_affected = stmt.execute_update()
assert rows_affected == 2
# Verify we have 4 rows total
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_sql_query(f"SELECT COUNT(*) as cnt FROM {table_name}")
stream, _ = stmt.execute_query()
reader = pa.RecordBatchReader._import_from_c(stream.address)
result = reader.read_all()
assert result.column("cnt")[0].as_py() == 4
def test_ingest_multiple_batches(gizmosql):
"""Test ingesting data with multiple record batches."""
table_name = "test_ingest_multi_batch"
# Create data with multiple batches
schema = pa.schema(
[
("id", pa.int64()),
("value", pa.string()),
]
)
batches = [
pa.record_batch(
[
pa.array([1, 2, 3], type=pa.int64()),
pa.array(["a", "b", "c"], type=pa.string()),
],
schema=schema,
),
pa.record_batch(
[
pa.array([4, 5], type=pa.int64()),
pa.array(["d", "e"], type=pa.string()),
],
schema=schema,
),
]
reader = pa.RecordBatchReader.from_batches(schema, batches)
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_options(
**{
INGEST_TARGET_TABLE: table_name,
INGEST_MODE: "adbc.ingest.mode.create",
}
)
stmt.bind_stream(reader)
rows_affected = stmt.execute_update()
assert rows_affected == 5
# Verify all data was ingested
with adbc_driver_manager.AdbcStatement(gizmosql) as stmt:
stmt.set_sql_query(f"SELECT COUNT(*) as cnt FROM {table_name}")
stream, _ = stmt.execute_query()
reader = pa.RecordBatchReader._import_from_c(stream.address)
result = reader.read_all()
assert result.column("cnt")[0].as_py() == 5
def test_ingest_via_dbapi_with_reader(gizmosql_dbapi):
"""Test bulk ingest using the DBAPI interface with RecordBatchReader."""
table_name = "test_ingest_dbapi_reader"
data = pa.table(
{
"id": pa.array([1, 2, 3], type=pa.int64()),
"name": pa.array(["x", "y", "z"], type=pa.string()),
}
)
# Test using the cursor's adbc_ingest method with explicit reader
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, data.to_reader())
assert rows_affected == 3
# Verify via DBAPI query
with gizmosql_dbapi.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM {table_name}")
result = cur.fetchone()
assert result[0] == 3
def test_ingest_via_dbapi_with_table(gizmosql_dbapi):
"""Test bulk ingest using the DBAPI interface with Table directly."""
table_name = "test_ingest_dbapi_table"
data = pa.table(
{
"id": pa.array([1, 2, 3], type=pa.int64()),
"name": pa.array(["x", "y", "z"], type=pa.string()),
}
)
# Test using the cursor's adbc_ingest method with Table directly
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, data)
assert rows_affected == 3
# Verify via DBAPI query
with gizmosql_dbapi.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM {table_name}")
result = cur.fetchone()
assert result[0] == 3
def test_ingest_dbapi_modes(gizmosql_dbapi):
"""Test different ingest modes via DBAPI (append, replace, create_append)."""
table_name = "test_ingest_dbapi_modes"
# Create initial table
initial_data = pa.table(
{
"id": pa.array([1, 2], type=pa.int64()),
}
)
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, initial_data, mode="create")
assert rows_affected == 2
# Test append mode
append_data = pa.table(
{
"id": pa.array([3, 4], type=pa.int64()),
}
)
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, append_data, mode="append")
assert rows_affected == 2
# Verify 4 rows total
with gizmosql_dbapi.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM {table_name}")
assert cur.fetchone()[0] == 4
# Test replace mode
replace_data = pa.table(
{
"id": pa.array([100, 200, 300], type=pa.int64()),
}
)
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, replace_data, mode="replace")
assert rows_affected == 3
# Verify 3 rows (replaced)
with gizmosql_dbapi.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM {table_name}")
assert cur.fetchone()[0] == 3
def test_ingest_dbapi_create_append(gizmosql_dbapi):
"""Test create_append mode via DBAPI."""
table_name = "test_ingest_dbapi_create_append"
data = pa.table(
{
"id": pa.array([1, 2], type=pa.int64()),
}
)
# First call should create the table
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, data, mode="create_append")
assert rows_affected == 2
# Second call should append to existing table
with gizmosql_dbapi.cursor() as cur:
rows_affected = cur.adbc_ingest(table_name, data, mode="create_append")
assert rows_affected == 2
# Verify 4 rows total
with gizmosql_dbapi.cursor() as cur:
cur.execute(f"SELECT COUNT(*) FROM {table_name}")
assert cur.fetchone()[0] == 4