Repository navigation
Keep DateTime64 precision and timezone in named Tuple, Nested, Variant and Map binds - #1130
Closed
sebastianbreguel wants to merge 1 commit into
Closed
sebastianbreguel wants to merge 1 commit into
sebastianbreguel wants to merge 1 commit into
Conversation
sebastianbreguel
requested review from
joe-clickhouse and
peter-leonov-ch
as code owners
October 27, 2026 15:29
2 tasks done
Contributor
Author
|
Folded into #1118, since both change binding.py and test_params.py. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a
datetimeis bound server-side to aDateTime64element,_promote_datetime64wraps it inDT64Paramto keep sub-second precision. This only worked at the top level, inArray, and in unnamedTuple. Other container types sent whole seconds (ClickHouse 26.3,D = datetime(2024, 1, 2, 3, 4, 5, 123456),U= the same time in UTC):Named elements failed because
"a DateTime64(6)"was read as a type calleda.What changed, all in
clickhouse_connect/driver/binding.py:_strip_element_nameremoves the name from a named element before promotion. It handles quoted names, including backtick or double quotes with doubled or backslash-escaped quotes, and names containing parentheses.DateTime64 (6)is not mistaken for a name._promote_datetime64now also handlesNested(as an array of tuples),Variant, andMap.Mapis handled only withdict_parameter_format='map'. JSON mode serializes the dict itself and is unchanged.Variantis promoted only when it has no plainDateTimealternative. I checked on the server that a value with a fraction makes it pickDateTime64instead ofDateTime. Promoting would therefore change the variant thatVariant(DateTime, DateTime64(6))gets today, so that case stays as it is.DT64Paramtakes an optionaltz. A promoted element carries the timezone named in its ownDateTime64type, soTuple(a DateTime64(6, 'Asia/Tokyo'), b DateTime64(6, 'UTC'))renders each element in its own zone. Before, the whole parameter used one timezone. Top-level binds andArraybinds produce the same strings as before. ADT64Paramwith no timezone placed inside one of these containers gets the element's timezone too. The wrapper is copied, so the caller's object is not changed. The SQLAlchemy subclass_DateTime64QueryParamhonors that timezone. ADT64Paramthat already has a timezone, and the_64suffix, are unchanged._unwrap_outerand the element-timezone lookup are cached (lru_cache(maxsize=256)). Before this, every array element parsed the same type string again. Both caches hold only immutable results.Still unchanged: plain
DateTime(non-64) elements in a multi-timezoneTuplestill share the first timezone found. That case has no wrapper, and fixing it is a separate change.Verification
New unit tests in
TestBindQueryDateTime64Precision: named tuple, named tuple tz hint, per-element timezones, quoted names (4 variants), existingDT64Param/ SQLAlchemy wrappers taking the element timezone (directly and throughVariant), nested, variant, map. Plus a guard showing thatVariant(DateTime, DateTime64(6))is unchanged.Integration tests
test_datetime64_params_in_containers(6 cases, including a wrappedDT64Param) andtest_datetime64_params_in_map, sync and async. They compare against the server'stoString. Onmain, 25 of the selected unit and integration cases fail. With the fix all pass.ruff format --check,ruff check, andmypyare clean. Unit tests: 4352 passed. Integration (-n 4, ClickHouse 26.3, including the SQLAlchemy suite): 1806 passed, 29 skipped. 2 failures aretest_wrong_port_error_message, which needs local port 9000 (environment). 18 unit errors aretest_httpclient.pytests that hardcodelocalhost:8123(environment).Performance:
bind_queryonorigin/mainvs this PR, timed with/usr/bin/time -l. There were 16 interleaved runs per side, each in a fresh process, and the order alternated each round. Python 3.12, Apple Silicon. Wall and CPU cover the bind loop. I started each case once the 1-minute load average was under 3; it stayed between 2.5 and 3.3 during the runs. Cases:plain:{x:Int32}, {s:String}, {d:DateTime}, 20,000 binds.arr1k:Array(DateTime64(6))with 1,000 values, 200 binds.arr100k: 100,000 values, 2 binds._tz: the same type with'Asia/Tokyo'.Nothing got slower. DateTime64 array binds are about 18 to 43% faster because of the parse cache, and
plainabout 11%. Peak RSS is within noise: the medians differ by at most 0.1 MiB.Checklist