odbc_copy silently loses data (partial or total rollback without error) when a DuckDB INTEGER/BIGINT column is bound to a Firebird stored procedure VARCHAR parameter.
The function reports success (True, correct row count) but the transaction is not committed — or only partially committed.
Environment
- DuckDB: 1.3.0 and 1.4.4 (both affected)
- odbc_scanner: commit
52e168c
- ODBC Driver: Firebird ODBC Driver 3.0.1.21
- Database: Firebird 3.0.13
Reproduction
import duckdb
odbc_conn = (
"Driver={Firebird ODBC Driver};"
"Database=127.0.0.1:C:/path/to/database.fdb;"
"UID=SYSDBA;PWD=masterkey;CHARSET=UTF8"
)
# Assume this SP exists on the Firebird side:
# CREATE PROCEDURE my_sp(p_id VARCHAR(20), p_name VARCHAR(100)) AS
# BEGIN
# UPDATE OR INSERT INTO my_table(id, name) VALUES(:p_id, :p_name)
# MATCHING (id);
# END
con = duckdb.connect()
con.execute("LOAD odbc_scanner")
# Source table has INTEGER id column
con.execute("CREATE TABLE src (id INTEGER, name VARCHAR)")
con.execute("INSERT INTO src VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie')")
# This reports success but data is NOT committed
result = con.execute(
"""FROM odbc_copy($odbc_conn,
source_query=$src,
dest_query=$dest,
batch_size=1,
max_records_in_transaction=10000)""",
{
"odbc_conn": odbc_conn,
"src": "SELECT * FROM src",
"dest": "EXECUTE PROCEDURE my_sp(?, ?)",
},
).fetchall()
print(result) # [(True, 3, ...)] ← reports 3 rows, looks successful
# But data is NOT in the database!
What works
| dest_query |
Source type |
SP param type |
Result |
UPDATE OR INSERT ... VALUES (?, ?) |
INTEGER |
n/a (DML) |
Committed ✓ |
EXECUTE PROCEDURE sp(?, ?) |
INTEGER |
BIGINT |
Committed ✓ |
EXECUTE PROCEDURE sp(?, ?) |
VARCHAR |
VARCHAR |
Committed ✓ |
EXECUTE PROCEDURE sp(?, ?) |
INTEGER |
VARCHAR |
Silent loss ✗ |
Key observations
- Plain DML (no SP) always commits regardless of type mismatch.
- SP with matching types commits correctly.
- Only SP + INTEGER→VARCHAR mismatch causes the silent rollback.
odbc_copy returns (True, N, ...) indicating success and N rows processed.
- No error or exception is raised.
- Data loss may be partial (e.g., 11 out of 500 rows) or total (0 out of N).
Root Cause Hypothesis
When odbc_copy binds parameters to an ODBC statement for EXECUTE PROCEDURE, the C-type (SQL_C_SBIGINT or similar) doesn't match the Firebird SP's expected SQL type (SQL_VARCHAR). The Firebird ODBC driver appears to:
- Accept the
SQLExecute call without error.
- Execute the SP body, which may internally fail on type coercion.
- Signal a rollback condition that
odbc_copy doesn't detect.
The odbc_query function (non-parameterized) does NOT have this issue because it sends inline string literals that Firebird parses correctly.
Workaround
Cast INTEGER columns to VARCHAR in the DuckDB source query before passing to odbc_copy:
-- Before (broken):
SELECT id, name FROM my_table
-- After (works):
SELECT id::VARCHAR, name FROM my_table
Suggested Fix
In odbc_copy's parameter binding path, when the ODBC driver reports the expected SQL parameter type (via SQLDescribeParam), convert the DuckDB value to match. Alternatively, detect the mismatch and raise an error rather than silently losing data.
odbc_copysilently loses data (partial or total rollback without error) when a DuckDB INTEGER/BIGINT column is bound to a Firebird stored procedure VARCHAR parameter.The function reports success (
True, correct row count) but the transaction is not committed — or only partially committed.Environment
52e168cReproduction
What works
UPDATE OR INSERT ... VALUES (?, ?)EXECUTE PROCEDURE sp(?, ?)EXECUTE PROCEDURE sp(?, ?)EXECUTE PROCEDURE sp(?, ?)Key observations
odbc_copyreturns(True, N, ...)indicating success and N rows processed.Root Cause Hypothesis
When
odbc_copybinds parameters to an ODBC statement forEXECUTE PROCEDURE, the C-type (SQL_C_SBIGINTor similar) doesn't match the Firebird SP's expected SQL type (SQL_VARCHAR). The Firebird ODBC driver appears to:SQLExecutecall without error.odbc_copydoesn't detect.The
odbc_queryfunction (non-parameterized) does NOT have this issue because it sends inline string literals that Firebird parses correctly.Workaround
Cast INTEGER columns to VARCHAR in the DuckDB source query before passing to
odbc_copy:Suggested Fix
In
odbc_copy's parameter binding path, when the ODBC driver reports the expected SQL parameter type (viaSQLDescribeParam), convert the DuckDB value to match. Alternatively, detect the mismatch and raise an error rather than silently losing data.