Add UUID2 data type with correct sorting - #110084
Conversation
The `UUID` data type sorts by the second half of the value for historical reasons, which is unexpected and, in particular, hurts the performance of primary indexes built on `UUIDv7` columns whose timestamp is in the first half (see the note in the `UUID` documentation). This introduces `UUID2`, a variant of `UUID` that stores the value as a plain big-endian integer of the 16 canonical bytes, so that natural integer comparison of the underlying value matches the textual (lexicographic) order and the canonical byte order used by other systems. `UUID2` reuses the `UUID` column and `Field` representation (like `DateTime` reuses `UInt32`), so sorting is correct with no extra comparison code, and its binary serialization is the canonical big-endian byte order. Also: - `UUID1` is added as an alias of the current `UUID` type. - A new setting `uuid_type_version` (default 1) controls whether the bare type name `UUID` resolves to `UUID` (1) or `UUID2` (2) at `CREATE`/`ALTER` time. The resolved concrete type is materialized into the stored table definition (including nested types such as `Array(UUID)`), so reads never depend on the session setting, and existing tables are never rewritten. The default will be flipped to 2 in a later, separate change. - Conversions to/from `String`, `UInt128`, `FixedString(16)` and `UUID`, plus `toUUID2` / `toUUID2OrZero` / `toUUID2OrNull` and `min`/`max`/`any`. Implements the first part of #110066 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…IDToNum Accept `UUID2` arguments in `hex`/`bin`, `reinterpretAs*`, `UUIDv7ToDateTime` and `UUIDToNum`. For the layout-dependent functions (`hex`/`bin`, `UUIDv7ToDateTime`, `UUIDToNum`) the big-endian `UUID2` value is converted to the logical `UUID` layout with `UUIDHelpers::swapHalves` before reusing the existing UUID code path, so results match `UUID` for the same textual value. Hashing (`sipHash*`, `cityHash*`, `uniq*`), `GROUP BY`, `min`/`max`/`any` and the `String`/`CSV`/`TSV`/`JSON`/`RowBinary`/`Native`/`Arrow` formats already work for `UUID2`. Bitwise aggregates (`groupBitOr` etc.) are unsupported for `UUID2` as they are for `UUID`. Related: #110066 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add UUID2 support to the data formats and storage features that special-case UUID. The uniform rule: UUID2 shares ColumnUUID with UUID but stores the value with the two 64-bit halves swapped, so at the column boundary the value is run through UUIDHelpers::swapHalves and the existing UUID code path is reused; format-to-ClickHouse type inference keeps returning UUID. - Formats: Parquet (native writer), Arrow (arrow.uuid extension), Avro, BSON, MsgPack, Protobuf, CapnProto (input and output), and JSONExtract. - Storage/functions: generateRandom, bloom_filter skip index, empty/notEmpty. The native Parquet writer switch keys on the column's data type (which is UUID for both UUID and UUID2), so UUID2 is detected via the concrete type in ColumnChunkWriteState::type; the Parquet reader reconciles the inferred UUID to a UUID2 target through the existing UUID->UUID2 cast. Extends the 04402_uuid2_data_type test with function, format-adjacent and storage cases. Related: #110066 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Conflict in src/DataTypes/DataTypesBinaryEncoding.h: master took binary type index 0x37 for QBitWithStride, so UUID2 moved from 0x37 to 0x38 and BINARY_TYPE_INDEX_SIZE was bumped to 0x39. Also added the missing UUID2 rows to the binary type encoding tables in the header comment and in docs/en/sql-reference/data-types/data-types-binary-encoding.md.
…ent the `UUID2` binary encoding After merging master, the first release that will contain the new setting is 26.7, so its entry in `SettingsChangesHistory.cpp` belongs to the "26.7" block instead of the already-released "26.6". Also document the `UUID2` type in the Native format specification (fixed-width table, the `UUID` wire-encoding section, and the type diagram): the wire encoding is the 16 canonical big-endian bytes with no half-swapping, and the binary type encoding tag is `0x38` (`0x37` was taken by `QBitWithStride` in master).
|
Workflow [PR], commit [90657e0] Summary: ❌
AI ReviewSummaryThis PR adds the new Findings
Final VerdictNeeds changes: the LLVM Coverage Report |
|
@ClickHouse/integrations team, please, take a look |
Reconcile the parallel master merge pushed to origin. The only content conflict was in `DataTypesBinaryEncoding.h`: both sides keep `QBitWithStride = 0x37` and `UUID2 = 0x38` (the enum's max, matching `BINARY_TYPE_INDEX_SIZE = 0x39`); resolved to the compilable form with the trailing comma after `QBitWithStride`. The `uuid_type_version` entry stays in the 26.7 settings-changes block and the `UUID2` Native-format documentation is preserved.
The new `toUUID2`, `toUUID2OrZero` and `toUUID2OrNull` conversion
functions declare `UUID2` as their returned type in
`FunctionDocumentation`. `mapTypesToTypesWithLinks` in
`FunctionDocumentation.cpp` maps type names to documentation links and
throws `LOGICAL_ERROR` ("Unexpected data type in function ...") for any
type name it does not recognize. It knew about `UUID` but not `UUID2`,
so rendering the description of these functions threw.
Because the `description` column of `system.functions` and the
`system.documentation` table are rendered lazily, any query touching
them failed with the `LOGICAL_ERROR`, cascading into ~29 Fast test
failures (documentation web-UI tests, `system.functions`/
`system.documentation` tests, `help`/`man` command tests, and others
that read `system.functions`).
Add the missing mapping so `UUID2` links to
`/sql-reference/data-types/uuid2`. `Nullable(UUID2)` (used by
`toUUID2OrNull`) was already handled by the `Nullable` prefix branch.
CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=110084&sha=037bbe206b346508544b6adb3a803469054917cf&name_0=PR&name_1=Fast%20test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o Binary The default native Arrow / ArrowStream writer (`output_format_arrow_use_native_writer = 1`) had no `UUID2` handling in `ArrowIPC::SchemaConverter` / `RecordBatchEncoder`. With the default `output_format_arrow_unsupported_types_as_binary = 1`, a `UUID2` column was silently emitted as a variable-width `Binary` column of the raw internal bytes instead of a real Arrow `uuid` / `fixed_size_binary(16)` value, breaking schema fidelity and the default round-trip through `FORMAT Arrow`. Teach the native writer to treat `UUID2` like `UUID`: the schema maps it to `fixed_size_binary(16)` flagged with the `arrow.uuid` extension, and the encoder applies `UUIDHelpers::swapHalves` at the IPC boundary so the emitted bytes are identical to `UUID` for the same textual value. This mirrors the Apache Arrow library writer path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…buted DDL `ALTER TABLE ... ADD/MODIFY COLUMN x UUID` resolved the concrete type (`UUID` vs `UUID2`, per `uuid_type_version`) on whichever node executed the command, because `executeToTable` forwards the query to `ON CLUSTER` hosts and enqueues it into a `Replicated` database DDL log *before* `parseAlterCommandSegments` runs. Unlike `CREATE`, which bakes the concrete column types into the query AST on the initiator, a bare `UUID` could therefore materialize as `UUID` on one node and `UUID2` on another when nodes disagree on `uuid_type_version` (mixed-version rollouts, older `distributed_ddl_entry_format_version`). Mirror the `CREATE` flow: `materializeUUIDTypeVersion` resolves a bare `UUID` in `ADD/MODIFY COLUMN` on the initiator before the query is forwarded, and `parseAlterCommandSegments` keeps internal DDL (`isDDLOrOnClusterInternal`) verbatim instead of re-resolving with the local node's setting, so every node agrees on the initiator's decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Arrow library is not compiled into the fast-test build, so `FORMAT Arrow` fails there and the query produces empty output. That made both the `UUID2` and `UUID` branches emit nothing, so the byte-identity check passed trivially while the round-trip lines were missing, and the test failed against its reference. All other Arrow tests are already tagged `no-fasttest` for the same reason. Verified locally with an Arrow-enabled build: the byte-identity check and the round-trip both produce the exact reference output. CI report: #110084 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`convertFieldToType` treated every `Field::Types::UUID` value as already being in the destination encoding, but `UUID` and `UUID2` store the value in layouts that differ by swapping the two 64-bit halves. When a `UUID` constant is coerced into `UUID2` (or vice versa) - for example building an `IN`-set for a `UUID2` key from a `toUUID(...)` constant, which reaches `convertFieldToType` via `getSetElementsForConstantValue` with the source type passed as `from_type_hint` - the halves were not swapped, so the compared or stored value was wrong. Detect the `UUID` <-> `UUID2` crossing from `from_type_hint` and apply `UUIDHelpers::swapHalves` in that case; the same-type and no-hint cases are left unchanged. Addresses review feedback on #110084 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`uuid_type_version = 2` was ignored for `CREATE ... ON CLUSTER` when `distributed_ddl_entry_format_version < NORMALIZE_CREATE_ON_INITIATOR_VERSION`. That legacy path enqueues the query verbatim in `execute` before `createTable` normalizes it on the initiator, and each worker then treats the forwarded DDL as already-normalized internal DDL (forcing `uuid_type_version = 1`), so a bare `UUID` column was created as the historical `UUID` type instead of `UUID2`. Materialize the setting into the column-declaration ASTs on the initiator before the legacy enqueue, mirroring how `ALTER` and the modern `CREATE` path bake in the concrete stored type. Also extends `04402_uuid2_data_type` with the legacy `ON CLUSTER` case and the Field-level `UUID`/`UUID2` coercion (`IN`-set) case. Addresses review feedback on #110084 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`FunctionAnyHash` (the dispatcher behind `sipHash64`, `sipHash128`, `cityHash64`, `halfMD5`, `xxHash64`, `farmHash64`, ...) had a specialized branch only for `which.isUUID()`, hashing the value via `executeBigIntType<UUID>`. `UUID2` fell through to `executeGeneric`, which hashed `column->getDataAt(i)` - i.e. the raw in-memory bytes. Because `UUID` and `UUID2` store the same logical value in layouts that differ by swapping the two 64-bit halves, this made the hash functions return different results for `x::UUID` and `x::UUID2` even when `x` is the same textual value, breaking the hashing parity the PR promises (and that `hex`, `reinterpretAs*`, `UUIDToNum`, etc. already provide for `UUID2`). Add a `which.isUUID2()` branch that swaps the halves back into the `UUID` layout with `UUIDHelpers::swapHalves` and then hashes through the existing `executeBigIntType<UUID>` path, so `hash(x::UUID2)` equals `hash(x::UUID)`. `Nullable(UUID2)` already worked via the `Nullable` branch and now inherits the fix through the nested type. Add a hashing-parity regression to `04402_uuid2_data_type`. Addresses review feedback on #110084 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SSL) The Fast test build is compiled without SSL, so `halfMD5` (which is `#if USE_SSL`-gated) does not exist there. The hashing-parity block of `04402_uuid2_data_type` called `halfMD5`, so the whole test failed with `Code: 46. Function with name 'halfMD5' does not exist. (UNKNOWN_FUNCTION)`. `halfMD5` is a `FunctionAnyHash`, exactly like `sipHash64`, `sipHash128`, `cityHash64`, `xxHash64`, and `farmHash64`, so it exercises the same `executeUUID2` code path as those functions. Dropping it keeps the test running in the Fast test build while still covering `UUID2` hashing parity via five representative functions, instead of tagging the whole test `no-fasttest`. CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=110084&sha=883b4ea2562dc0114f97dffc9e809f22d33d47a3&name_0=PR&name_1=Fast%20test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When `uuid_type_version = 2` materializes a bare `UUID` column to `UUID2`, a table created over an external database (`PostgreSQL`, `MySQL`, `SQLite`) with such a column previously failed with `Unsupported type UUID2` because `ExternalResultDescription::init` only recognized `UUID`. Add a `vtUUID2` value type and handle it in the `PostgreSQL`, `MySQL`, and `SQLite` readers (including the `PostgreSQL` array parser and the array nested-column builder in `StoragePostgreSQL`). `UUID2` stores the value in the canonical big-endian layout, while `parse<UUID>` produces the half-swapped `UUID` layout, so the readers swap the halves back with `UUIDHelpers::swapHalves` - matching `SerializationUUID2::deserializeText` - so the textual value round-trips. The `PostgreSQL` write path is serialization-driven and already correct for `UUID2`. Add a unit test in `gtest_insertPostgreSQLValue` asserting that a `PostgreSQL` `uuid` value read into a `UUID2` column round-trips to the original textual value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`UUID2` can now be used as a TimeSeries `id` type, and `uuid_type_version = 2` consistently materializes a bare `UUID` id to `UUID2`. Previously `SET uuid_type_version = 2` broke `TimeSeries` DDL: the inner samples/tags columns were materialized to `UUID2` (by `InterpreterCreateQuery::getColumnsDescription`) while the resolved id type stayed `UUID`, so the consistency check between the inner tables failed with `Column id in the Samples table has type UUID2, but expected UUID`. Changes: - `normalizeTimeSeriesDefinition` now bakes the `uuid_type_version` setting into the declared inner-column types before resolving them, and applies the same materialization to the built-in default id type. This keeps the samples and tags inner tables consistent and makes any later normalization pass read the already-materialized types. It is gated to a primary user CREATE (mirroring the gate in `getColumnsDescription`), so ATTACH, restore, and already-normalized DDL-worker queries are unaffected. - `normalizeTimeSeriesDefinition` id-type validation and `checkArgumentTypeForID` (used by `timeSeriesStoreTags`, `timeSeriesIdToTags`, `timeSeriesIdToGroup`) now accept `UUID2`. - `TimeSeriesIDGenerator::getDefault` generates a default id expression for a `UUID2` id column. Adds test `04517_uuid2_time_series_id` and documents `UUID2` as an allowed id type in the TimeSeries docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🕵 The only CI red on |
|
Thanks for routing this. One correction on which pull request owns that bucket, and then where it is stuck. #110778 is not the fix for 2508-3319. It fixes a sibling bucket of the same assertion, 2508-348f: a stale sort property surviving The owner of 2508-3319 is #113242, the pull request you bumped on 08-18 for sibling 2508-3796. Same mechanism, different fuzzed target type: the fuzzer clones Why #113242 has not landed. Its only functional red is So the chain is #113735, then #113242, and 2508-3319 stops. Family breadth over the last 14 days is 109 rows across 98 pull requests with 5 true master rows, 3319 being the largest single bucket at 19 rows. Two ways forward, and I will take whichever you prefer:
#110778 is a real fix for its own bucket and is unrelated to this. It is 12228 commits behind master but merges clean, and it has been sitting with a green matrix and no review since 08-10, so I will leave it alone unless you want it moved. |
# Conflicts: # docs/_site/customizations/settings-legacy-routes/session-settings.js # docs/reference/functions/regular-functions/other-functions.mdx # src/Analyzer/Passes/FunctionToSubcolumnsPass.cpp # src/Functions/array/arrayElement.cpp # src/Processors/QueryPlan/ReadFromRemote.cpp # src/Storages/MergeTree/MergeTreeIndexBloomFilter.cpp
Follow-ups to the master merge, all mechanical: - `SerializationUUID2::deserializeBinaryBulk` still had the `rows_offset` parameter that master dropped in "Remove rows_offset from the deserialization API", so it no longer overrode the virtual method. - The `LowCardinality` branch of `getFieldFromColumnForASTLiteralImpl` did not pass the `datetime64_as_numbers` argument that master added. - `tryBuildAdditionalFilterAST` now folds two independent fixes: master serializes decimal-backed constants exactly, and this branch serializes a `UUID2` constant as canonical text (its raw `Field` is formatted with `UUID` semantics and would be reparsed as a different value on the shard). The `UUID2` path is selected by a new file-local `typeMayContainUUID2`, so the raw-`Field` literal is kept for everything else, including `DateTime`.
The description promised that the setting covers "every expression a
`CREATE` or `ALTER` persists", but a type name carried inside a string is
only recognized when that string is a literal or is built from the string
functions `foldConstantStringExpression` mirrors without an execution
context (`concat`, `replace`, `replaceOne`, `replaceAll`, `upper`,
`ucase`, `lower`, `lcase`). `CAST(x, trim(' UUID '))` stays historical
`UUID`, as `05038_uuid2_constant_string_folding` already asserts.
Document that limitation instead of implying a broader guarantee, and
regenerate the reference documentation.
# Conflicts: # docs/_site/customizations/settings-legacy-routes/session-settings.js # docs/snippets/components/SessionSettingsExplorer/SessionSettingsExplorer.jsx # src/Interpreters/InterpreterAlterQuery.cpp
The entry was recorded under `26.8`, but the development version is now `26.9`, so the `settings_changes_history` style check failed.
Conflict resolutions:
- `src/Parsers/Kusto/KustoFunctions/KQLStringFunctions.cpp`: master removed the KQL
(Kusto) dialect implementation, so the branch's `UUID2` acceptance in
`base64_encode_fromguid` is moot; took the deletion.
- `tests/queries/0_stateless/02366_kql_func_string.{sql,reference}`: took master's
side for the same reason (the `base64_encode_fromguid` cases were removed there).
- The two generated docs blobs (`settings-legacy-routes/session-settings.js`,
`SessionSettingsExplorer.jsx`): took master's generated data verbatim and
re-inserted only the `uuid_type_version` record, bumping the group counts.
`uuid_type_version = 2` materializes the schema string of a table function in a
persisted definition. `substituteBareUUIDInPlace` used to call
`substituteBareUUIDInTableFunction` for every `ASTFunction`, recognizing the carrier
by name alone. But `format` is both the table function
`format(format_name, structure, data)` and the regular scalar string-formatting
function `format(pattern, ...)`, so a persisted
`SELECT format('{} {}', 'id UUID', 'x')` had its plain data argument rewritten to
`id UUID2`, silently changing the stored query.
The rewrite is now driven by the position of the call: the `table_function` of an
`ASTTableExpression`, the `as_table_function` of an `ASTCreateQuery`, and the whole
argument subtree of such a call (so that a table function nested in a wrapper, for
example `loop(url(...))`, is still reached).
Addresses the review finding on `src/Parsers/ASTDataType.cpp`.
A dictionary-encoded (`LowCardinality`) column written by the Apache Arrow library writer cannot carry the `arrow.uuid` extension keys, because the registered extension type rejects dictionary storage at read time. `CHColumnToArrowColumn` therefore marks such a column with the ClickHouse-specific discriminator (`ClickHouse:type`) alone. The native Arrow IPC reader still required `arrow.uuid` before honoring that key, so `output_format_arrow_use_native_writer = 0` plus `output_format_arrow_low_cardinality_as_dictionary = 1` plus `input_format_arrow_use_native_reader = 1` read the column back as `LowCardinality(FixedString(16))` instead of `LowCardinality(UUID2)` (and likewise lost a plain `UUID`). `isUUIDField` now treats the discriminator as authoritative on the fixed-size-binary path, which also makes `isUUID2Field` work for that writer/reader combination. Addresses the review finding on `src/Processors/Formats/Impl/ArrowIPC/SchemaConverter.cpp`.
…olumn
`ReplacingConstantExpressionsMatcher` re-encodes a folded `UUID`/`UUID2` constant as
text, because an untyped `ASTLiteral` cannot tell the two apart. The text form is
itself ambiguous against a `Variant`, `Dynamic` or `JSON` column: the `DataTypeVariant`
branch of `convertFieldToType` accepts a string into the `String` alternative, so
`WHERE v = toUUID2('...')` on a `Variant(String, UUID2)` sharding key was analyzed as
a `String` and `sipHash64(v)` could select the wrong shard, pruning away the shard
that owns the matching row.
Neither representation is unambiguous there, so such tables are no longer pruned at
all: the constant is left unfolded and `evaluateExpressionOverConstantCondition`
returns no definite answer, which means every shard is queried. A plain `UUID2`
column is unambiguous and keeps being pruned.
Addresses the review finding on `src/Storages/StorageDistributed.cpp`.
The `Docs examples` check runs every documented snippet and compares its output. The `toUUID2`, `dictGetUUID2` and `dictGetUUID2OrDefault` examples carried a hand-written response with a truncated (`⋯`) header and, for `toUUID2`, right-aligned column padding, neither of which the runner produces. The documented responses now match the actual output, and the generated pages were regenerated accordingly.
An Enum8 or Enum16 constant compared with a String or FixedString column was converted to the enum's underlying number instead of its name. convertFieldToTypeImpl had a String to Enum case (via IDataTypeEnum::castToValue) but no case for the other direction. An enum constant arrives as a numeric Field, so the target-is-string branch handled only Field::Types::String and everything else fell through to applyVisitor(FieldVisitorToString(), src), which stringified the number. The converted constant was '1' rather than the name 'V0', and every consumer that builds a point, range or hash from that Field used the wrong bytes. EXPLAIN indexes = 1 showed Condition: (v in ['1', '1']). Five surfaces returned wrong results, all on ordinary SELECT statements with no forced index: primary key and PARTITION BY pruning dropped matching rows, the bloom_filter skip index dropped them, IN and the values table function matched and inserted the wrong value, NOT IN was inverted and returned the row it should have excluded, and the OR-chain to IN rewrite silently dropped a disjunct. Since CAST(enum AS String) has always produced the name, = and IN disagreed on the same constant, which is what makes this a bug rather than a convention. Resolve the name through IDataTypeEnum::castToName, mirroring the existing inverse case, then re-enter convertFieldToTypeImpl so a FixedString target still zero-pads the name to its width. The source type hint is unwrapped first because only the target type is unwrapped by the caller, and Nullable(Enum) reaches this code with the wrapper still attached. Fixing the primitive corrects every consumer at once, and pruning is preserved: an enum constant now reads the same granule count as the equivalent String literal. An enum element inside an Array, Tuple or Map is still converted to the number, because the container recursion passes no element type hint down. That propagation is being added by ClickHouse#110084, so it is not duplicated here; three test cells assert the current behaviour so the gap cannot rot silently.
# Conflicts: # src/Core/Settings.cpp # src/Storages/StorageTimeSeries.cpp # src/Storages/TimeSeries/TimeSeriesIDGenerator.cpp # src/Storages/TimeSeries/normalizeTimeSeriesDefinition.cpp
… helper The `clickhouse_spelling` style check rejects the `clickHouse` case variant, so rename `clickHouseUUIDTypeMetadata` to `getClickHouseUUIDTypeMetadata`.
Master now wraps the default `TimeSeries` `id` component in `LowCardinality` and lets `TimeSeriesIDGenerator` choose an expression for a `LowCardinality` identifier type. The `uuid_type_version = 2` default therefore becomes `Tuple(UInt64, LowCardinality(UUID2))`; update the two references that pin the default identifier type, and mirror the merged documentation text in the generated `time-series.mdx`.
`04652_enum_constant_vs_string_column` documented three cells as known limitations and named this pull request as the change that adds the element type hint propagation through the `Array`/`Tuple`/`Map` recursion of `convertFieldToType` and through `createColumnFromConstantArray` in the bloom filter condition. With the hint in place an `Enum` element inside a container is converted to the name, so replace those cells with assertions of the fixed behavior: `x[1]` yields `7`, and `hasAny`/`hasAll` no longer over prune.
# Conflicts: # src/Storages/TimeSeries/normalizeTimeSeriesDefinition.cpp
|
|
||
| For historical reasons, the `UUID` data type is sorted by the second half of the value. This is unexpected and, in particular, hurts the performance of primary indexes built on `UUIDv7` columns, whose most significant bits are a timestamp (see the note in the [UUID](/reference/data-types/uuid) documentation for details). | ||
|
|
||
| `UUID2` stores the value so that it is sorted by its textual (lexicographic) representation, which matches the canonical byte order used by most other systems. In every other respect it is compatible with `UUID`: it accepts the same textual representation, occupies the same 16 bytes, and supports the same set of functions. |
There was a problem hiding this comment.
This "supports the same set of functions" contract is still not true for typed Kusto parameters. The parser wraps let F = (g:guid) { ... } arguments in kqlParameterCast, and FunctionKQLParameterCast::targetOf still accepts guid only when removeNullable(removeLowCardinality(arg_type)) is UUID (src/Functions/Kusto/FunctionKQLParameterCast.cpp:166-169). That means a column created as g UUID under uuid_type_version = 2 - or any explicit UUID2 / Nullable(UUID2) / LowCardinality(UUID2) argument - is rejected at the call boundary for the same KQL function that worked before the rollout.
I think kqlParameterCast needs the same logical-guid widening you already added for base64_encode_fromguid, plus a focused regression in 05047_kql_typed_parameter_enforcement.
Closes: #110066
Introduces
UUID2, a variant of theUUIDdata type with correct (lexicographic) sorting.Motivation
For historical reasons, the
UUIDdata type sorts by the second half of the value. This is unexpected and, in particular, hurts the performance of primary indexes built onUUIDv7columns, whose most significant bits are a timestamp: with sorting by the second half, primary-key analysis cannot prune granules by the timestamp.What this does
UUID2stores the 128-bit value as a plain big-endian integer of the 16 canonical bytes, so that natural integer comparison of the underlying value matches the textual (lexicographic) order and the canonical byte order used by most other systems. It reuses theUUIDcolumn andFieldrepresentation (likeDateTimereusesUInt32), so sorting is correct with no extra comparison code, and its binary/interchange serialization is the canonical big-endian byte order.UUID1is an alias of the currentUUIDtype.uuid_type_version(default1) controls whether the bare nameUUIDresolves toUUID(1) orUUID2(2) atCREATE/ALTERtime. The resolved concrete type is materialized into the stored table definition (including nested types such asArray(UUID)), so reads never depend on the session setting and existing tables are never rewritten. The default will be flipped to2in a later, separate change.String,UInt128,FixedString(16)andUUID, plustoUUID2/toUUID2OrZero/toUUID2OrNull.hex/bin,reinterpretAs*,UUIDv7ToDateTime,UUIDToNum,empty/notEmpty,min/max, hashing,uniq), formats (RowBinary,Native,JSON,CSV,TSV,Arrow,Parquet,Avro,BSON,MsgPack,Protobuf,CapnProto,JSONExtract) and storage (generateRandom,bloom_filterskip index).The
UUIDtype is unchanged (verified: still sorts by second half, identical conversions, defaultuuid_type_version = 1).Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Added a new data type
UUID2, a variant ofUUIDthat sorts by its textual (lexicographic) representation instead of by the second half of the value. The settinguuid_type_version(default1) selects whether the type nameUUIDresolves toUUIDorUUID2.Documentation entry for user-facing changes
Workflow [PR]
Sync PR [sync-upstream/pr/110084]