mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-15 07:06:52 +00:00
1d15bb3ada827e7460518a724e571b588c4a8cbf
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8887c82789 |
fix(python)!: raise on bool(Expr) instead of silently dropping predicates (#4682)
Python's `and` / `or` / `not` and chained comparisons call `bool()` on
an `Expr`. `Expr` had no
`__bool__`, so that was always `True` and the filter silently lost a
predicate: `where(lit(2) <
col("id") < lit(5))` returned ids 0..4 (lower bound dropped),
`(col("id") > 7) and (col("g") == 0)`
returned all even ids, and `(a) or (b)` kept only `a` (see the issue for
the repro and output).
Changes (`python/python/lancedb/expr.py`,
`python/python/lancedb/query.py`):
- `Expr.__bool__` raises `TypeError("The truth value of an Expr is
ambiguous. ...")` with a hint to
use `&`, `|`, `~` with parentheses and `Expr.isin()` for membership.
`TypeError` rather than
`ValueError` because the object does not support truthiness at all
(polars `Expr` and SQLAlchemy
clauses raise `TypeError`; the codebase raises `TypeError` for
unsupported `lit()` values).
pandas, numpy and pyarrow `Expression` use `ValueError`.
- `LanceHybridQueryBuilder` tested `if self._where:`, which would have
started raising for every
`.where(Expr)` on a hybrid query. It is now `isinstance(self._where,
Expr) or self._where`, so
an Expr is always applied and an empty SQL string is still skipped as
before.
- Notes section in the `Expr` docstring.
Not changed: no other code in `python/python/lancedb` tests the
truthiness of a filter or of an
`Expr` (checked `where`/`filter`/`predicate`/`condition` handling in
`query.py`, `table.py`,
`remote/`, `merge.py`, `functions.py`, `pydantic.py`: they use `is None`
/ `isinstance`). Cross-SDK:
`nodejs` has no `Expr` class on `main` yet, and JavaScript cannot
overload `&&` / `||` / `!`
either, so there is nothing equivalent to port; the Rust core is not
affected.
Behaviour changes to be aware of: code that already used `and` / `or` /
`not` / chained
comparisons on an `Expr` (which returned wrong rows) now raises
`TypeError`. `x in [expr, ...]`,
`any([...])` and `all([...])` on Exprs, and `==` between two `Query`
objects whose `filter` are
different Expr objects (pydantic compares the fields), now raise instead
of returning a meaningless
answer; `Expr` stays unhashable and `isinstance` / `is None` checks are
unaffected.
Tests (`python/python/tests/test_expr.py`): `TestExprTruthiness`
(`bool`, `and`, `or`, `not`,
chained comparison, `if`, list membership, `any`/`all` raise with a
helpful message; `&`, `|`, `~`
still combine) and `TestExprWhereBuilders` (plain, vector, FTS, hybrid
and async `.where(Expr)`
return the right rows, hybrid with an SQL string and with an empty
string is unchanged, chained
comparison is rejected before any query runs). With the source change
stashed 11 of the new tests
fail; with only `__bool__` added and the hybrid change reverted the
hybrid test fails with the
new `TypeError`, so it guards the second change. `test_expr.py`: 144
passed (123 before this change).
Verification caveat: `main`'s Python layer is newer than the newest
published wheel, so the tests
were run against the 0.41.0b14 native module
(`python/python/lancedb/expr.py` and `query.py` are
identical to the wheel); the full suite (`pytest python/tests
--ignore=python/tests/docs -m "not slow and not s3_test"`)
has the identical 25 failures before and after (wheel/main skew in
remote options, remote db,
index and table tests; none in expr/query tests): 2604 passed before,
2625 passed after (the 21
new tests). `ruff format --check python` and `ruff check python` are
clean. Not run: Rust build, Node tests, remote (cloud)
tests.
Fixes #4681
Co-authored-by: Xuanwo <github@xuanwo.io>
|
||
|
|
1e313d3c0d |
fix(python): parse decimal exponent literals exactly (#4449)
Decimal literals such as Decimal("1E-7") and Decimal("1E+3") currently
raise ValueError while building an expression. Parse the coefficient and
exponent exactly, and normalize representable negative scales to zero so
both typed filters and predicate.to_sql() queries preserve the value.
Checked arithmetic bounds the Decimal128 coefficient to 38 digits,
preserves existing scale limits, and rejects nonfinite or overflowing
values. This does not add Decimal256 literal support.
Added 21 cases covering actual Arrow-backed table queries, SQL queries,
zero, signed exponents, precision boundaries, and errors. Both Python
3.11 and 3.13 expression suites pass 123 tests; the baseline fails 10 of
the new cases. Python binding Rust tests pass 13; official make doctest
passes 77 with 6 skips; Ruff, Rustfmt, hooks, Pyright and
workspace/tests/all-features Clippy with -D warnings pass. A fresh
environment restored from the original dependency freeze passes the full
required suite: 1528 passed, 37 skipped, 68 deselected (143.51s); while
optional Torch suites have the same baseline remote mock-server ordering
failure.
Tested against f4544628; observed official main
|
||
|
|
25645d82d4 |
feat(python): accept expressions in update filters (#3876)
## Summary - allow Python sync, async, and remote table updates to accept type-safe `Expr` filters - serialize expression filters before invoking the existing update implementation - cover numeric-looking text and apostrophe-containing text in sync and async regression tests ## Root cause `Table.update` was the remaining Python write path that required callers to construct a raw SQL predicate. Dynamic text interpolated without SQL literal encoding could therefore be parsed as an integer, float, or unterminated string instead of Utf8. The expression API already encodes literals safely for query and delete filters. ## Validation - `cd python && .venv/bin/pytest python/tests/test_table.py::test_update_async python/tests/test_table.py::test_update_expr_filter_literals_async python/tests/test_table.py::test_update python/tests/test_table.py::test_update_expr_filter_literals -q` - `cd python && .venv/bin/pytest python/tests/test_expr.py -q` - `cd python && .venv/bin/ruff format --check .` - `cd python && .venv/bin/ruff check .` Fixes #1869 <!-- lance-gatekeeper-fix:v1 agent=01f1e7b69c65e8b6d3b3c1e1a7918179 generation=1 --> --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Co-authored-by: Xuanwo <github@xuanwo.io> |
||
|
|
a075aa62f8 |
fix(python): treat naive lit(datetime) as UTC wall clock (#3262) (#3775)
## Summary Fixes naive `lit(datetime)` equality filters against table timestamp columns on non-UTC hosts, and adds the integration matrix from #3262. ## Failure (before) On a machine in US Eastern (UTC−4 / EDT), with PyPI `lancedb==0.36.0`: ```python from datetime import datetime import lancedb from lancedb.expr import col, lit db = lancedb.connect("memory://") ts = datetime(2024, 7, 1, 10, 0, 0) # naive table = db.create_table("t", [{"id": 1, "ts": ts}]) rows = table.search().where(col("ts") == lit(ts)).to_list() # actual: [] (0 rows) # expected: 1 row ``` ### Root cause In `python/src/expr.rs`, `expr_lit` converted every `datetime` via Python's `.timestamp()`: - **naive** `.timestamp()` = local wall → UTC epoch (shifted by host offset) - **PyArrow naive** storage = UTC wall-clock microseconds (no local shift) So `lit(naive)` became `CAST('2024-07-01 14:00:00' AS TIMESTAMP)` on EDT while the table held `10:00:00`. ## After Naive datetimes are interpreted as UTC wall clock (`replace(tzinfo=timezone.utc).timestamp()`), matching Arrow storage. Aware datetimes still use `.timestamp()` (correct epoch). Same repro on this branch: **1 matching row**. ## Tests Added `TestExprDatetimeTimezoneIntegration` covering: | Case | Result | |------|--------| | both naive | match | | both same TZ (UTC) | match | | different TZs, same instant | match | | table TZ + naive lit | match (wall clock) | | table naive + aware lit | match | | naive lit SQL is wall clock, not local-shifted | asserts `10:00:00` in SQL | ### Verification ```bash cd python maturin develop pytest python/tests/test_expr.py -v ``` **102 passed** (full `test_expr.py`, including the 6 new cases). Closes #3262 --------- Co-authored-by: Will Jones <willjones127@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3bcff0165e |
feat: support date, datetime, bytes, and Decimal literals in expr builder (#3235)
### **Summary** Closes #3212 Extends the Python `lit()` helper to natively support three additional types (`date`, `datetime`, and `Decimal`) and implements reflexive operators for the `Expr` class. This implementation specifically addresses the blocking feedback regarding precision loss, CI discovery, and query engine limitations: * **Logic Refactoring**: Simplified `lit()` by combining `date` and `datetime` normalization into ISO-8601 strings, ensuring stable SQL parsing across different engine locales. * **Precision Preservation**: `decimal.Decimal` objects are now passed as high-precision strings to the Rust bridge, bypassing intermediate float conversions and preserving full 128-bit decimal precision for DataFusion. * **Averted CI Failures**: Temporarily deferred `bytes` literal support to a future PR to resolve a known DataFusion `expr_to_sql` limitation that was crashing the `Doctest` runner. * **Reflexive Operators**: Added support for "literal-first" arithmetic and logical operations (e.g., `10 + col('a')` or `True & col('active')`). Redundant reflexive comparisons (e.g., `__rlt__`) were pruned as Python's data model handles them automatically. * **Integration Verification**: Added dedicated integration tests in the official test directory to ensure the query engine correctly handles the new types and preserves bit-perfect fidelity. ### **Changes** #### [python/python/lancedb/expr.py](file:///c:/Users/Laksh/Documents/lancedb/python/python/lancedb/expr.py) * Updated `lit()` to handle `date`, `datetime`, and `Decimal` natively. * Implemented reflexive operators (`__radd__`, `__rand__`, `__rmul__`, etc.) to support literals on the left-hand side. * Removed the problematic `bytes` doctest example and `lit()` type support to unblock CI. #### [python/src/expr.rs](file:///c:/Users/Laksh/Documents/lancedb/python/src/expr.rs) * Modified the Rust FFI bridge to extract `Decimal` objects as strings. * Ensured the `expr_lit` handler is ready to receive normalized temporal strings. * Consolidated imports and added missing operator documentation. #### [python/python/lancedb/_lancedb.pyi](file:///c:/Users/Laksh/Documents/lancedb/python/python/lancedb/_lancedb.pyi) * Updated type stubs for `expr_lit` to include `Any` (allowing for `Decimal`). ### **Testing** Added several new advanced test cases in [python/python/tests/test_expr.py](file:///c:/Users/Laksh/Documents/lancedb/python/python/tests/test_expr.py) covering: * **High-precision Decimal preservation**: Verified against 128-bit boundaries with a "one point off" test case (`1.234567890123456789 < 1.234567890123456790`). * **Reflexive operator positioning**: Verified successful query construction with literals on the left. * **Timezone-aware normalization**: Confirmed stable behavior for `datetime` objects. * **Integration Testing**: Confirmed Date32 and Decimal columns return the correct Python types and values from the engine during `.to_arrow()` calls. --------- Co-authored-by: Will Jones <willjones127@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |