mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
Resolve the matched id through `AccessControl` (`tryRead` and `findStorage`), the same way the full-scan fallback does, instead of reading from the matched `storage` directly. Two `users_xml` storages defining the same user name derive the same generated `UUID` from `UsersConfigParser::generateID`, and `findStorage` always resolves that `UUID` to the first storage. The previous fast path read from the specific storage and emitted its name, so `WHERE name = 'dup_user'` could attribute a row to `storage_b` while the full scan (`WHERE name LIKE 'dup\\_user'`) attributed both rows to the first storage. The fast path is now a transparent optimization that produces identical rows to the full scan. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
58 lines
2.1 KiB
Python
58 lines
2.1 KiB
Python
import pytest
|
|
|
|
from helpers.cluster import ClickHouseCluster
|
|
|
|
cluster = ClickHouseCluster(__file__)
|
|
|
|
# `dup_user` is defined in two separate `users_xml` access storages (`storage_a` and
|
|
# `storage_b`). Both storages derive the same generated `UUID` from the user name, so a
|
|
# full scan over `findAll<User>` emits a row for each storage but resolves every row to
|
|
# the first storage. The fast path that `system.users` takes for `name = '...'` and
|
|
# `name IN (...)` predicates has to keep the same semantics instead of attributing the
|
|
# rows to different storages or collapsing the duplicates.
|
|
node = cluster.add_instance(
|
|
"node",
|
|
main_configs=["configs/access_storages.xml"],
|
|
extra_configs=["configs/users_a.xml", "configs/users_b.xml"],
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
|
def started_cluster():
|
|
try:
|
|
cluster.start()
|
|
yield cluster
|
|
finally:
|
|
cluster.shutdown()
|
|
|
|
|
|
def rows(predicate):
|
|
# Sort so the comparison does not depend on row order between the fast path and
|
|
# the full scan.
|
|
return node.query(
|
|
f"SELECT id, name, storage FROM system.users WHERE {predicate} ORDER BY id, name, storage"
|
|
)
|
|
|
|
|
|
def test_fast_path_matches_full_scan_for_duplicate_names():
|
|
# The fallback (full-scan) path: `LIKE` is not extracted as a literal name predicate.
|
|
full_scan = rows("name LIKE 'dup\\_user'")
|
|
|
|
# `dup_user` exists in two storages, so we expect two rows.
|
|
assert full_scan.count("dup_user") == 2
|
|
|
|
# Fast path with equality must return exactly the same rows as the full scan.
|
|
assert rows("name = 'dup_user'") == full_scan
|
|
|
|
# Fast path with `IN` must return exactly the same rows as the full scan.
|
|
assert rows("name IN ('dup_user')") == full_scan
|
|
|
|
|
|
def test_fast_path_matches_full_scan_for_mixed_in():
|
|
# `only_a` is unique to one storage, `dup_user` is in both: three rows total.
|
|
full_scan = rows("name LIKE 'dup\\_user' OR name LIKE 'only\\_a'")
|
|
|
|
assert rows("name IN ('dup_user', 'only_a')") == full_scan
|
|
assert full_scan.count("dup_user") == 2
|
|
assert full_scan.count("only_a") == 1
|