Repository navigation
fix(deps): update dependency duckdb to v2 - #417
renovate[bot] wants to merge 1 commit into
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one): 1
Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #417 +/- ##
=======================================
Coverage 60.11% 60.11%
=======================================
Files 201 201
Lines 10311 10311
=======================================
Hits 6198 6198
Misses 4113 4113 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Closing in favour of GH-419. |
Renovate Ignore NotificationBecause you closed this PR without merging, Renovate will ignore this update. You will not get PRs for any future If you accidentally closed this PR, or if you changed your mind: rename this PR to get a fresh replacement PR. |
This PR contains the following updates:
<1.6→<2.1Release Notes
duckdb/duckdb-python (duckdb)
v2.0.0: DuckDB v2.0.0Compare Source
duckdb-python 2.0.0 is the Python package for DuckDB 2.0. It supports Python >= 3.11 and drops Python 3.10. From Python 3.12 on, one wheel per platform serves every Python version. The binding layer was rewritten on nanobind, query results stream instead of being collected up front, the relational API was rebuilt on SQL text, and the way DataFrames, Arrow data and polars frames are read was replaced. Many long-standing bugs are fixed, and moving data in and out of Python is much faster. The engine is DuckDB 2.0.0; see its release notes for what changed in SQL and storage.
Highlights
con.execute()starts the query and you read rows as you fetch them;con.complete()andcon.abort()finish or cancel an open result, and an open result or Arrow reader owns the connection until it is done.rel.sql_query()shows exactly what will run. Many new methods and expression helpers (col,lit,when, window functions). One semantic change: a plain string in an expression is a value, not a column name. (#697)with con.appender("t") as a: a.append_row(...), under a microsecond per row. (#661)con.execute()results changed:fetch_arrow_table,fetch_record_batchandarrow(n)are gone; usecon.sql(query).to_arrow_table()or.to_arrow_reader(). (#702)Upgrading from 1.x: what to check
fetch_arrow_table,fetch_record_batch,arrow(n)orpl()on the result ofcon.execute(), switch tocon.sql(query).to_arrow_table(),to_arrow_reader()orpl(). Libraries that call the removed names on an execute result need an update. (#702)fetchone(),fetchmany(),fetch_df_chunk()orexecute()on a relation, userel.limit(n).fetchall(),rel.to_arrow_reader(),rel.materialize(), or run the query throughcon.execute(). (#697)col("s") == "x"now compares with the textx. Wrap column names incol(). (#697)import duckdbhaving opened the default database, note that it happens on the firstduckdb.sql()orduckdb.connect()call instead. (#680)Decimalvalues with a positive exponent, such asDecimal("1E+2"), they now arrive as the number they denote, 100, where 1.5 produced 1.00. (#654)overwrite=False, an existing file is now refused instead of replaced. (#668)str(rel)being free, note that it runs the query each time now.Breaking changes
Arrow export on DB-API results (#702)
Results of
con.execute()are now read row by row and are not kept in memory, so they cannot hand their data to Arrow for free.fetch_arrow_table,fetch_record_batch,arrow(rows_per_batch)andpl()on a DB-API result are removed; they had been deprecated.to_arrow_table()andto_arrow_reader()remain, also asduckdb.to_arrow_table()andduckdb.to_arrow_reader(), but they materialize the rows twice, once as the result and once as Arrow, and emit aUserWarningpointing at the relational path,con.sql(query).to_arrow_table(), which we recommend instead. The reader no longer streams: its first batch arrives after the whole query ran. After a partialfetchoneorfetchmanythe Arrow call returns the remaining rows instead of raising. Withpreserve_insertion_orderoff, the row order can differ between calls.duckdb.arrow(n)with an integer is gone;duckdb.arrow(arrow_object)still builds a relation.Relations are SQL text (#697)
The relational API is a query building API, not an execution API. A relation is a piece of SQL text with a schema bound once when it is built, so every step knows its columns and types. It runs nothing until you ask for data, and it runs again on every read;
rel.sql_query()returns exactly the text that runs, as one SELECT per group of steps and a common table expression where the order of clauses breaks. The engine's binder decides every column name and type; nothing is renamed by the client.What you are most likely to notice:
A bare string operand of an expression is a value, not a column. For one release a warning is raised when the string matches a column name; the warning goes in 2.1. A dotted string is not split into parts; pass the parts separately.
fetchone(),fetchmany(),fetch_df_chunk()andexecute()are gone from relations. The error names the replacement:limit(1).fetchall(),to_arrow_reader()orlimit(n).df()for batches, andmaterialize()to run a relation once and keep its rows.con.sql(text, params=...)is lazy and binds its parameter values on every read; it used to run once.con.sql()of a statement that returns no rows, such asCREATE TABLEor anINSERTwithoutRETURNING, returns a relation over what the statement returned instead ofNone: no rows for DDL, the changed row count for DML. The statement still runs at the call. Only SELECT-shaped statements are lazy;CALL,PRAGMA,CHECKPOINTandEXPLAINrun at the call and the relation holds their output.Over a join,
l.xandr.yresolve inside the join's own SELECT. Once a later step starts a new SELECT the input alias is no longer known and the engine's error is raised. Two columns of the same name arenameandname_1past that point.A persistent view over held rows (
materialize(),values(), a statement that ran at the call) is refused; userel.create("name")orcon.register("name", rel).A relation over a file pins its schema when it is built; a file that changes between building and reading no longer yields rows that disagree with the declared columns. A reader with
filename=Trueor a Hive partition declares that column among its columns.The new verb names are methods, so a column called
offset,sample,head,rename,pivot,drop,unnest,unpivot,previeworgroup_byis reached asrel["offset"].Further differences against 1.5
rel.query(name, sql)leaves no temporary view behind.SELECT nextval('s'), runs at the call and the relation holds its rows.values()converts its rows once at the call;sql_query()shows a reference to them, not a VALUES list.con.sql()SELECT, such as a misspelled column, no longer aborts an open transaction, because building a relation runs nothing.ALTER TABLEworks.bool(expression)raisesTypeError; combine conditions with&,|and~. An operand no expression can hold raisesTypeErrorwhere==used to returnFalse.ConstantExpression({1: "a"})is a MAP; 1.5 made a STRUCT with the key"1".update(set={...})refuses a bare string value; writecol("name")to copy a column orlit("x")for text.str(expression)quotes identifiers, parenthesizes operations and writes literals in; the text differs from 1.5 but means the same.str(rel)runs the query on every call; 1.5 cached the first rendering.memoryviewgiven tovalues()or an expression is read as a BLOB in logical byte order, where 1.5 raisedBufferError. As a query parameter it is still refused.unregister(name)raises when it cannot remove its view instead of reporting success and leaving it behind.Executing statements through the DB-API (
con.execute)A query is now started at
execute()and read as you fetch, instead of being run to completion first. An error in the first rows still raises fromexecute(); an error later in the query raises from the fetch call that reaches it. Statements that write, includingRETURNING, complete insideexecute(). Callingexecute()again while a result is open replaces that result; a replaced statement that may write is run to completion first so the transaction stays valid. Any other call that needs the connection while a result is open, such as a relation fetch or a UDF reaching for its own connection, refuses with an error that names the open result instead of deadlocking. An Arrow reader being read from a relation blocks the connection the same way. Finish reading, or use a cursor for the other work.Two new calls manage the open result:
con.complete()runs it to its end without reading its rows, andcon.abort()cancels the rest of it; aborting a statement that may write fails an open transaction. Both exist asduckdb.complete()andduckdb.abort()for the default connection. A statement that fails inside an explicit transaction aborts that transaction, andcon.rowcountreports the number of rows a statement changed.Importing duckdb opens nothing (#680)
import duckdbno longer creates the default in-memory database or starts its threads. The firstduckdb.sql(),duckdb.execute()or similar module-level call does.Python 3.10 dropped, stable ABI wheels from 3.12 (#672, #673, #684)
The package requires Python 3.11 or later. For Python 3.12 and later there is one
abi3wheel per platform that works on 3.12, 3.13, 3.14 and 3.15; Python 3.11 keeps a wheel of its own. Free-threaded builds of CPython are not supported. On the stable ABI wheels, fetching fixed-width values row by row (integers, booleans, dates, times, timestamps) costs 13 to 17 percent more than on a wheel built against the full Python API; DataFrame and Arrow output are within 5 percent. The wheel is 24.4 MB against 21.5 MB for 1.5.5, and 68.8 MB against 60.9 MB installed.Values and types
Decimal("1E+2")arrives as 100 with typeDECIMAL(3,0); 1.5 produced 1.00. The inferred type and the value now always agree. (#654, #566)dictconverted to a STRUCT matches keys exactly. A missing key no longer gets another key's value. (#660)strthat UTF-8 cannot encode, such as a lone surrogate, raisesInvalidInputExceptionand leaves the database usable, where 1.5 raised a barebad_caston every path. (#671)duckdb.Valueparameter stays NaN; 1.5 turned it into NULL. (#686)intis converted from its stored value without running its own methods. (#679, #408)dictof arrays whose lengths differ fails with an error naming both, such asColumn 'b' has 2 rows where 'a' has 5. 1.5 read past the end of the shorter array. (#700)dictor amap()result whose names differ only in case, such asaandA, are renamed as in a DataFrame (a,A_1). 1.5 refused them as duplicate column names. (#700)Reading Arrow, pandas, numpy and polars inputs
to_arrow_table,to_arrow_readerandarrow. Passbatch_size=to keep the old size.RecordBatchReaderis read as the query needs it, not far ahead of it. Scanning the same reader twice raises "Stream has already been consumed" instead of returning partial or no rows. (#693, #655, #691, #70)ORDER BY, such asLIMITorfirst(), can return different rows between runs. (#700)Files
write_csvandwrite_parquetwithoverwrite=Falserefuse to replace an existing file. Only a single file is checked. (#668, #156)rel.insert_into("db.schema.t")andrel.create("db.schema.t")keep the catalog part. A longer path is refused with a clear error; 1.5 dropped the middle parts and wrote to the wrong table. (#682)Versions
Development builds carry a time stamp suffix of the form
2.0.0.dev2610162032; the engine version string is passed through verbatim.Deprecations
duckdb.experimental.spark) might be deprecated in favor of SQLFrame. Not decided yet.Removed
fetch_arrow_table,fetch_record_batch,arrow(rows_per_batch)andpl()on DB-API results, andduckdb.arrow(n)with an integer. Deprecated in 1.x. (#702)fetchone(),fetchmany(),fetch_df_chunk()andexecute()on relations. (#697)New features
Appender (#661)
con.appender("table")returns a context manager for bulk inserts, mirroring the C++ Appender:append_row(*values),append_rows(rows),flush()andclear(), with a flush on leaving thewithblock. The target's schema is read when the appender is made, and a mismatch raises at the first append. Column names that differ only in case are refused. It answers discussions #369 and #193.Relation methods and expression helpers (#697)
New relation methods:
with_columns,drop,rename,unnest,unpivot,pivot(column, values, aggregate),sample,head,offset,union_by_name,distinct(on=...),group_by(...).agg(...),preview,to_dicts,iter_rows(batch_size=),explain(analyze=True)andjoin(..., suffix=...);jointakeshowin inner, left, right, outer, full, semi, anti, cross, positional, natural and asof, with the condition optional where the kind takes none. New expression functions:col,lit,fn,when().then().otherwise(),star,sql_expr,coalesce,count_all, window constructors with.over(partition_by=, order_by=, rows=, range=),.where(),.try_cast(),.like(),.ilike(),[]for struct fields and list elements, the.str(),.dt(),.list()and.json()function families, aggregate methods, and Python callables as SQL lambdas. A projection may hold an aggregate. Notebooks display a relation once per cell as text and HTML.con.sql(text, params=...)binds lazily, so one relation can be read with fresh values.Python 3.15 and stable ABI wheels
Python 3.15 is in the test matrix and the classifiers. One wheel per platform covers 3.12 and later.
Spark API works against PySpark 4.2.0 and 3.5.9 (#686)
duckdb.experimental.sparkimports on a clean install (it no longer needstyping_extensionsor numpy) and, for the functions it covers, returns Spark's answer or raises; 38 deliberate differences are documented. Fixes #653 and #183 and part of #428.Binding layer rewritten on nanobind (#522)
The C++ bindings moved from pybind11 to nanobind 3. This is what makes the stable ABI wheels possible and accounts for the faster import and columnar paths. Behavior at the Python boundary was checked against the old binding in a ten agent regression sweep before the cutover.
Fixes
Each line says what went wrong in 1.5 or an earlier 2.0 build.
con.table("s.v")for a view in a schema raised "Table does not exist". (#670)rel.query(name, sql)could not refer to the relation's columns by the given name. (#656, #604)create_functionresolved type names on the default connection, so an ENUM created on another connection was unknown, and a failure silently cancelled the transaction. (#681)TIMESTAMPTZ_NSconstant into an Arrow scan scaled it a second time, so modern dates failed to convert and dates near 1970 matched the wrong rows (2.0 regression). (#687)INlist pushed the wrong condition. (#657, #615)infor NaN literals. (#688)map()leaked its argument tuple and the input DataFrames of every call. (#669, #534)sys.modulescleanup. (#658, #638)intsubclasses could convert to the wrong value; a Python error inside a conversion or UDF could be swallowed or replaced, andMemoryErrorwas lost. The Arrow conversion error now carries pyarrow's own reason. (#679)modified()crashed the process; it now reportsNotImplementedException. (#683, #584)ColumnExpression("s.a.b.c")dropped name parts, andStarExpression(exclude=[ColumnExpression("b", "id")])excluded everyidinstead ofb.id. (#686)duckdb.struct_type({})was refused. (#686)describe()failed onGROUP BY ALLwithUNNEST. (#546)df[::2]ofInt64, moved its NULLs to other rows. (#700)fetchnumpyresult with NULL strings, registered and queried again, gave the text<NULL>instead of NULL. (#700)duckdb.Valuein a pandas object column raised an internal error and invalidated the database. (#700)count(*)andEXISTSover a DataFrame converted its first column, so an unused column could fail the query, in 1.5 with an internal error for a lone surrogate. (#700)timedeltascan raised a deprecation warning. (#502)read_jsonsniffed the file again on every read, so a changed file returned rows that did not match the declared columns. (#697)quotecharor a file path escaped as a bareRuntimeError: std::bad_cast. (#697)StarExpression(exclude=[...])with a qualifier over a join of tables was refused; the engine now decides. (#697)unregisterof a registered view that SQL had renamed kept both views. (#697)execute()and refuse the next statement now and then when more than one thread was in use. (#702)duckdbfirst, because the built-in extensions were registered only on import; any ADBC query that needed them, such asadbc_ingestinto a file database, failed with a catalog error. They are now registered when the library loads. (#701, #698)Performance
Measured with the 2.0.0 release wheel against the PyPI 1.5.5 wheel on x86-64 Linux, CPython 3.13, one CPU core unless stated. Each figure is the 2.0 time divided by the 1.5.5 time, so 0.5x means twice as fast. Over the 297 cases of the benchmark suite the geometric mean is 0.74x: 161 cases are faster, 77 slower and 48 unchanged.
Moving data out:
fetchone0.58x.Moving data in:
CREATE TABLE ASfrom such a table on 8 threads 0.05x (193 to 10 ms). (#700)strcolumn: 0.20x on one thread, 0.05x on eight.count(*)over a 2M row DataFrame whose first column isstr: 0.01x (102 ms to 0.87 ms). (#700)con.append(df)andinsert_intofrom a 1M row frame on 16 threads: 0.39x and 0.44x. The new Appender inserts a row in under a microsecond (0.86 us, 0.71 us throughappend_rows), whereexecutemanytakes about 315 us per row. (#661)values()of 10k dates, times, datetimes or timedeltas 0.47x to 0.67x; of 10k bytes 0.49x and memoryviews 0.60x. (#697)At scale:
df(),fetchnumpy()andfetchall()over large results is equal within 1 percent; an Arrow table of 6M rows takes 5 percent more and a polars frame of 6M rows 7 percent more. No path retains memory across calls.Slower:
connect()8.4 to 16.0 ms, because a database now starts up to 256 asynchronous I/O threads by default (async_threads);connect(config={"threads": 1, "async_threads": 1})takes 10.7 ms.execute()andfetchone()arrives later, 4.75x on one thread (1.1 to 5.2 ms) and 1.6x on 16; the first 10k rows take the same time as before.strcolumn is the fast path. A pandas object column of naive datetimes 2.24x.fetchallof 100k DATE, TIME or TIMESTAMP rows 1.28x, 1.23x and 1.11x.Thanks
Thanks to everyone who reported issues, tested the development wheels and contributed fixes to this release.
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.