mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
Merge remote-tracking branch 'mine/darwin-streaming-exchange' into darwin-streaming-exchange
This commit is contained in:
@@ -194,7 +194,7 @@ jobs:
|
||||
|
||||
code_review:
|
||||
runs-on: [self-hosted, pr-style-checker-aarch64]
|
||||
needs: [build_arm_tidy, ci_tests, config_workflow, dockers_build_amd, dockers_build_arm, dockers_build_multiplatform_manifest, fast_test, style_check]
|
||||
needs: [config_workflow, dockers_build_amd, dockers_build_arm, dockers_build_multiplatform_manifest, fast_test, style_check]
|
||||
if: ${{ !cancelled() && !contains(needs.*.outputs.pipeline_status, 'failure') && !contains(needs.*.outputs.pipeline_status, 'undefined') && !contains(fromJson(needs.config_workflow.outputs.data).workflow_config.cache_success_base64, 'Q29kZSBSZXZpZXc=') }}
|
||||
name: "Code Review"
|
||||
outputs:
|
||||
|
||||
@@ -16,11 +16,13 @@ extern "C" {
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
const char * __asan_default_options()
|
||||
{
|
||||
return "halt_on_error=1 abort_on_error=1";
|
||||
/// allocator_may_return_null=1: oversized allocation returns null (recoverable) instead of
|
||||
/// aborting. Removable once llvm/llvm-project#206649 lands in our toolchain.
|
||||
return "halt_on_error=1 abort_on_error=1 allocator_may_return_null=1";
|
||||
}
|
||||
const char * __lsan_default_options()
|
||||
{
|
||||
return "max_allocation_size_mb=32768";
|
||||
return "max_allocation_size_mb=32768 allocator_may_return_null=1";
|
||||
}
|
||||
const char * __lsan_default_suppressions()
|
||||
{
|
||||
@@ -45,21 +47,21 @@ const char * __lsan_default_suppressions()
|
||||
#ifdef MEMORY_SANITIZER
|
||||
const char * __msan_default_options()
|
||||
{
|
||||
return "abort_on_error=1 poison_in_dtor=1 max_allocation_size_mb=32768";
|
||||
return "abort_on_error=1 poison_in_dtor=1 max_allocation_size_mb=32768 allocator_may_return_null=1";
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef THREAD_SANITIZER
|
||||
const char * __tsan_default_options()
|
||||
{
|
||||
return "halt_on_error=1 abort_on_error=1 history_size=7 second_deadlock_stack=1 max_allocation_size_mb=32768";
|
||||
return "halt_on_error=1 abort_on_error=1 history_size=7 second_deadlock_stack=1 max_allocation_size_mb=32768 allocator_may_return_null=1";
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef UNDEFINED_BEHAVIOR_SANITIZER
|
||||
const char * __ubsan_default_options()
|
||||
{
|
||||
return "print_stacktrace=1 max_allocation_size_mb=32768";
|
||||
return "print_stacktrace=1 max_allocation_size_mb=32768 allocator_may_return_null=1";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -6,28 +6,6 @@
|
||||
|
||||
# ─────────────────────────── macOS platform gaps (keep skipped) ───────────────────────────
|
||||
|
||||
# Float/double precision: on Apple arm64 'long double' == 'double', so the fast float parser cannot
|
||||
# reproduce x86/glibc results, and any result depending on extended precision (math funcs, float
|
||||
# parsing, float-derived hashes/checksums) differs. Out of scope here (precise-as-default).
|
||||
00558_parse_floats
|
||||
01037_polygon_dicts_correctness_all
|
||||
01037_polygon_dicts_correctness_fast
|
||||
01302_polygons_distance
|
||||
01720_country_intersection
|
||||
02179_degrees_radians
|
||||
02253_empty_part_checksums
|
||||
02705_projection_and_ast_optimizations_bug
|
||||
02813_float_parsing
|
||||
02815_range_dict_no_direct_join
|
||||
02951_parallel_parsing_json_compact_each_row
|
||||
03143_cte_scope
|
||||
03269_bf16
|
||||
03371_dynamic_values_parsing_templates
|
||||
03457_numeric_indexed_vector_build
|
||||
03702_geometry_functions
|
||||
03976_geometry_functions_accept_subtypes
|
||||
04402_float_parsing_corner_cases
|
||||
|
||||
# Query/memory profiler and trace_log: sampling uses timer_create + CLOCK_THREAD_CPUTIME_ID and
|
||||
# is #if defined(OS_LINUX) in QueryProfiler; system.trace_log stays empty on macOS.
|
||||
01473_event_time_microseconds
|
||||
|
||||
@@ -578,6 +578,13 @@ class ArtifactConfigs:
|
||||
path=[
|
||||
"./*.profdata",
|
||||
],
|
||||
# The coverage merge (llvm-profdata) runs non-blocking and can produce no
|
||||
# .profdata (e.g. it crashes on a corrupt .profraw). A missing batch is
|
||||
# tolerated by the downstream LLVM Coverage aggregation, which globs
|
||||
# whatever .profdata files exist, so a missing file must not redden a
|
||||
# coverage job whose tests all passed. Marking the artifact optional lets
|
||||
# the runner skip a missing file with a warning instead of erroring.
|
||||
optional=True,
|
||||
).parametrize(names=LLVM_ARTIFACTS_LIST)
|
||||
|
||||
llvm_coverage_info_file = Artifact.Config(
|
||||
|
||||
@@ -187,6 +187,9 @@ class JobConfigs:
|
||||
runs_on=RunnerLabels.STYLE_CHECK_ARM,
|
||||
command="python3 ./ci/jobs/copilot_review_job.py --codex",
|
||||
allow_failure=True,
|
||||
post_hooks=[
|
||||
"python3 ./ci/jobs/scripts/job_hooks/set_sync_status_awaiting_hook.py"
|
||||
],
|
||||
)
|
||||
ci_tests = Job.Config(
|
||||
name=JobNames.CI_TESTS,
|
||||
|
||||
@@ -34,7 +34,7 @@ BUILD_TYPE_TO_CMAKE = {
|
||||
BuildTypes.AMD_TIDY: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=Debug -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/linux/toolchain-x86_64.cmake -DENABLE_BUILD_PROFILING=0 -DENABLE_TESTS=1 -DENABLE_LEXER_TEST=1 -DENABLE_UTILS=1 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON -DENABLE_CLANG_TIDY=1 -DENABLE_EXAMPLES=1 -DENABLE_BUZZHOUSE=1",
|
||||
BuildTypes.ARM_TIDY: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=Debug -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/linux/toolchain-aarch64.cmake -DENABLE_BUILD_PROFILING=0 -DENABLE_TESTS=1 -DENABLE_LEXER_TEST=1 -DENABLE_UTILS=1 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON -DENABLE_CLANG_TIDY=1 -DENABLE_EXAMPLES=1 -DENABLE_BUZZHOUSE=1",
|
||||
BuildTypes.AMD_DARWIN: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=None -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/darwin/toolchain-x86_64.cmake -DENABLE_BUILD_PROFILING=1 -DENABLE_TESTS=0 -DENABLE_LEXER_TEST=0 -DENABLE_UTILS=0 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON -DCMAKE_AR:FILEPATH=/cctools/bin/x86_64-apple-darwin-ar -DCMAKE_INSTALL_NAME_TOOL=/cctools/bin/x86_64-apple-darwin-install_name_tool -DCMAKE_RANLIB:FILEPATH=/cctools/bin/x86_64-apple-darwin-ranlib -DLINKER_NAME=/cctools/bin/x86_64-apple-darwin-ld",
|
||||
BuildTypes.ARM_DARWIN: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=None -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/darwin/toolchain-aarch64.cmake -DENABLE_BUILD_PROFILING=1 -DENABLE_TESTS=0 -DENABLE_LEXER_TEST=0 -DENABLE_UTILS=0 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON -DCMAKE_AR:FILEPATH=/cctools/bin/aarch64-apple-darwin-ar -DCMAKE_INSTALL_NAME_TOOL=/cctools/bin/aarch64-apple-darwin-install_name_tool -DCMAKE_RANLIB:FILEPATH=/cctools/bin/aarch64-apple-darwin-ranlib -DLINKER_NAME=/cctools/bin/aarch64-apple-darwin-ld",
|
||||
BuildTypes.ARM_DARWIN: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=None -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/darwin/toolchain-aarch64.cmake -DENABLE_BUILD_PROFILING=1 -DENABLE_TESTS=1 -DENABLE_LEXER_TEST=0 -DENABLE_UTILS=0 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON -DCMAKE_AR:FILEPATH=/cctools/bin/aarch64-apple-darwin-ar -DCMAKE_INSTALL_NAME_TOOL=/cctools/bin/aarch64-apple-darwin-install_name_tool -DCMAKE_RANLIB:FILEPATH=/cctools/bin/aarch64-apple-darwin-ranlib -DLINKER_NAME=/cctools/bin/aarch64-apple-darwin-ld",
|
||||
BuildTypes.ARM_V80COMPAT: f"cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=None -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/linux/toolchain-aarch64.cmake -DNO_ARMV81_OR_HIGHER=1 -DENABLE_BUILD_PROFILING=1 -DENABLE_TESTS=0 -DENABLE_LEXER_TEST=0 -DENABLE_UTILS=0 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON",
|
||||
BuildTypes.AMD_FREEBSD: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=None -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/freebsd/toolchain-x86_64.cmake -DENABLE_BUILD_PROFILING=1 -DENABLE_TESTS=0 -DENABLE_LEXER_TEST=0 -DENABLE_UTILS=0 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON",
|
||||
BuildTypes.PPC64LE: f" cmake --debug-trycompile -DCMAKE_VERBOSE_MAKEFILE=1 -LA -DCMAKE_BUILD_TYPE=None -DENABLE_THINLTO=0 -DSANITIZE= -DENABLE_CHECK_HEAVY_BUILDS=1 -DBUILD_STRIPPED_BINARY=1 -DENABLE_CLICKHOUSE_SELF_EXTRACTING=1 -DCMAKE_C_COMPILER={ToolSet.COMPILER_C} -DCMAKE_CXX_COMPILER={ToolSet.COMPILER_CPP} -DCOMPILER_CACHE={ToolSet.COMPILER_CACHE_LEGACY} -DCMAKE_TOOLCHAIN_FILE={repo_path_normalized}/cmake/linux/toolchain-ppc64le.cmake -DENABLE_BUILD_PROFILING=1 -DENABLE_TESTS=0 -DENABLE_LEXER_TEST=0 -DENABLE_UTILS=0 -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON",
|
||||
|
||||
@@ -40,6 +40,17 @@
|
||||
<min>1</min>
|
||||
<max>500000</max>
|
||||
</max_expanded_ast_elements>
|
||||
|
||||
<!--
|
||||
A fuzzed huge max_threads (e.g. 65535) makes one query request more
|
||||
pipeline threads than the global pool (max_thread_pool_size=10000),
|
||||
saturating it (CANNOT_SCHEDULE_TASK), which starves the server-liveness
|
||||
probe and delays query cancellation. Cap it. Lives here so every
|
||||
ast_fuzzer_runs user (dedicated AST fuzzer and stress test) gets it.
|
||||
-->
|
||||
<max_threads>
|
||||
<max>128</max>
|
||||
</max_threads>
|
||||
</constraints>
|
||||
|
||||
<!-- Server-side backstop: keeps max_parser_backtracks `changed` so a server-side
|
||||
|
||||
@@ -27,20 +27,15 @@
|
||||
<max>200</max>
|
||||
</table_function_remote_max_addresses>
|
||||
|
||||
<!-- A fuzzed huge max_threads saturates the global thread pool, starving
|
||||
the server-liveness probe and delaying query cancellation. Cap it. -->
|
||||
<max_threads>
|
||||
<max>128</max>
|
||||
</max_threads>
|
||||
|
||||
<!-- Don't waste cycles testing the old interpreter. Spend time in the analyzer instead -->
|
||||
<enable_analyzer>
|
||||
<min>1</min>
|
||||
</enable_analyzer>
|
||||
|
||||
<!-- Recursion-limiting constraints (max_ast_depth, max_parser_depth,
|
||||
max_parser_backtracks) live in limit-recursion-settings.xml, installed
|
||||
alongside this file and shared with the stress test. -->
|
||||
max_parser_backtracks) and the max_threads cap live in
|
||||
limit-recursion-settings.xml, installed alongside this file and
|
||||
shared with the stress test. -->
|
||||
|
||||
</constraints>
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from ci.defs.defs import SYNC
|
||||
from ci.praktika.gh import GH
|
||||
from ci.praktika.result import Result
|
||||
|
||||
# This status is a marker that the sync process can be started. We set it from
|
||||
# the `Code Review` job because that job always runs for PRs.
|
||||
|
||||
|
||||
def main():
|
||||
statuses = GH.get_commit_statuses()
|
||||
if statuses is None:
|
||||
print(f"Failed to fetch commit statuses, skip setting [{SYNC}]")
|
||||
return
|
||||
|
||||
if SYNC in statuses:
|
||||
print(
|
||||
f"Commit status [{SYNC}] already exists with description "
|
||||
f"[{statuses[SYNC].description}], skipping"
|
||||
)
|
||||
return
|
||||
|
||||
GH.post_commit_status(
|
||||
name=SYNC,
|
||||
status=Result.Status.PENDING,
|
||||
description="awaiting",
|
||||
url="",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -21,6 +21,11 @@ class Artifact:
|
||||
type: str
|
||||
path: Union[str, List[str]]
|
||||
compress_zst: bool = False
|
||||
# When True, a providing job whose output file(s) are missing at upload
|
||||
# time does not fail. The job that already ran non-blocking (e.g. LLVM
|
||||
# coverage merge) stays green; only the artifact is skipped. Consumers
|
||||
# of an optional artifact must tolerate its absence.
|
||||
optional: bool = False
|
||||
_provided_by: str = ""
|
||||
ext: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@@ -904,6 +904,43 @@ class GH:
|
||||
)
|
||||
return cls.do_command_with_retries(command)
|
||||
|
||||
@classmethod
|
||||
def get_commit_statuses(
|
||||
cls, sha="", repo=""
|
||||
) -> Optional[Dict[str, "GH.CommitStatus"]]:
|
||||
"""
|
||||
Fetch commit statuses for the given commit SHA and return the latest
|
||||
status for each context. Returns `None` if the status list cannot be
|
||||
retrieved or parsed.
|
||||
"""
|
||||
repo = repo or _Environment.get().REPOSITORY
|
||||
sha = sha or _Environment.get().SHA
|
||||
|
||||
output = cls.get_output_with_retries(
|
||||
f"gh api repos/{repo}/commits/{sha}/statuses --paginate"
|
||||
)
|
||||
if not output:
|
||||
print("ERROR: Failed to fetch commit statuses")
|
||||
return None
|
||||
try:
|
||||
statuses_list = json.loads(output)
|
||||
except json.JSONDecodeError as ex:
|
||||
print(f"ERROR: Failed to parse commit statuses: {ex}")
|
||||
return None
|
||||
status_map: Dict[str, GH.CommitStatus] = {}
|
||||
|
||||
for status in statuses_list:
|
||||
context = status["context"]
|
||||
if context not in status_map:
|
||||
status_map[context] = GH.CommitStatus(
|
||||
state=status["state"],
|
||||
description=status.get("description", ""),
|
||||
url=status.get("target_url", ""),
|
||||
context=context,
|
||||
)
|
||||
|
||||
return status_map
|
||||
|
||||
@classmethod
|
||||
def merge_pr(cls, pr=None, repo=None, squash=False, keep_branch=False):
|
||||
if not repo:
|
||||
|
||||
+29
-4
@@ -588,6 +588,24 @@ class Runner:
|
||||
result.set_status(Result.Status.OK)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _skip_missing_optional_artifact(artifact, artifact_path) -> bool:
|
||||
"""Whether a providing artifact that matched no file may be skipped.
|
||||
|
||||
A missing optional artifact is skipped with a warning on any run (PR,
|
||||
master or release). It is optional because it may legitimately be absent
|
||||
(the non-blocking LLVM coverage merge can crash on a corrupt .profraw and
|
||||
produce no .profdata) and skipping keeps a job whose tests all passed
|
||||
green. A non-optional artifact is an error whenever it is missing.
|
||||
"""
|
||||
if artifact.optional:
|
||||
print(
|
||||
f"WARNING: optional artifact [{artifact.name}:{artifact_path}] "
|
||||
f"produced no file - skipping upload"
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _post_run(
|
||||
self, result, workflow, job, run_exit_code,
|
||||
) -> bool:
|
||||
@@ -630,10 +648,17 @@ class Runner:
|
||||
artifact_paths = [artifact.path]
|
||||
for artifact_path in artifact_paths:
|
||||
try:
|
||||
assert Shell.check(
|
||||
f"ls -l {artifact_path}", verbose=True
|
||||
), f"Artifact {artifact_path} not found"
|
||||
for file_path in glob.glob(artifact_path):
|
||||
matched = glob.glob(artifact_path)
|
||||
if not matched:
|
||||
if self._skip_missing_optional_artifact(
|
||||
artifact, artifact_path
|
||||
):
|
||||
continue
|
||||
raise FileNotFoundError(
|
||||
f"Artifact {artifact_path} not found"
|
||||
)
|
||||
Shell.check(f"ls -l {artifact_path}", verbose=True)
|
||||
for file_path in matched:
|
||||
link = S3.copy_file_to_s3(
|
||||
s3_path=s3_path,
|
||||
local_path=file_path,
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Regression tests for optional S3 artifacts.
|
||||
|
||||
Background
|
||||
----------
|
||||
LLVM coverage jobs (Integration / Functional / Unit tests, amd_llvm_coverage)
|
||||
run their coverage merge non-blocking: `llvm-profdata merge` can crash on a
|
||||
corrupt .profraw and produce no .profdata, but the tests themselves all pass
|
||||
and the job is meant to stay green ("do not block pipeline").
|
||||
|
||||
However each such job also declares `provides=[LLVM_COVERAGE_FILE_*]` with
|
||||
path `./*.profdata`. The praktika post-run artifact upload treated a missing
|
||||
providing artifact as a hard error and reddened the whole job to
|
||||
check_status=error even though 0 tests failed. Observed on PRs #109097,
|
||||
#108909, #104217, #97032 (all 2026-07-02): job.log shows
|
||||
`ERROR: Failed to create final coverage file` (llvm-profdata SIGSEGV) then
|
||||
`ERROR: Failed to upload artifact [LLVM_COVERAGE_FILE_it_6:./*.profdata]`.
|
||||
|
||||
Fix
|
||||
---
|
||||
`Artifact.Config` gained an `optional` flag. When a providing artifact marked
|
||||
optional matches no file at upload time, the runner skips it with a warning
|
||||
instead of setting Result.Status.ERROR. The coverage profdata artifacts are
|
||||
marked optional; the downstream `LLVM Coverage` aggregation already globs
|
||||
whatever .profdata files exist, so a missing batch is tolerated.
|
||||
|
||||
A missing optional artifact is skipped with a warning on any run (PR, master or
|
||||
release); a missing non-optional artifact is still an error.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))
|
||||
|
||||
from ci.praktika.artifact import Artifact
|
||||
from ci.praktika.runner import Runner
|
||||
from ci.defs.defs import ArtifactConfigs, ArtifactNames
|
||||
|
||||
|
||||
def test_optional_defaults_to_false():
|
||||
"""Existing artifacts stay non-optional (backward compatible)."""
|
||||
a = Artifact.Config(name="x", type=Artifact.Type.S3, path="./x")
|
||||
assert a.optional is False
|
||||
|
||||
|
||||
def test_optional_field_is_settable():
|
||||
a = Artifact.Config(name="x", type=Artifact.Type.S3, path="./x", optional=True)
|
||||
assert a.optional is True
|
||||
|
||||
|
||||
def test_coverage_profdata_artifacts_are_optional():
|
||||
"""Every LLVM_COVERAGE_FILE* profdata artifact must be optional.
|
||||
|
||||
These are produced by a non-blocking merge that can emit no file; a
|
||||
missing file must not redden a coverage job whose tests all passed.
|
||||
"""
|
||||
profdata_artifacts = [
|
||||
a
|
||||
for a in ArtifactConfigs.llvm_profdata_file
|
||||
if a.name.startswith(ArtifactNames.LLVM_COVERAGE_FILE)
|
||||
]
|
||||
# There is one per FT/IT batch plus the base name - guard against an empty
|
||||
# match (which would make this test vacuously pass).
|
||||
assert len(profdata_artifacts) >= 3
|
||||
for a in profdata_artifacts:
|
||||
assert a.optional is True, f"coverage artifact [{a.name}] must be optional"
|
||||
assert a.path == ["./*.profdata"]
|
||||
|
||||
|
||||
def test_runner_skips_missing_optional_artifact():
|
||||
"""A missing optional artifact is skipped with a warning on any run."""
|
||||
a = Artifact.Config(
|
||||
name="LLVM_COVERAGE_FILE_it_6",
|
||||
type=Artifact.Type.S3,
|
||||
path="./*.profdata",
|
||||
optional=True,
|
||||
)
|
||||
assert Runner._skip_missing_optional_artifact(a, "./*.profdata") is True
|
||||
|
||||
|
||||
def test_runner_does_not_skip_missing_required_artifact():
|
||||
"""A missing non-optional artifact is still an error."""
|
||||
a = Artifact.Config(
|
||||
name="CH_AMD_DEBUG",
|
||||
type=Artifact.Type.S3,
|
||||
path="./clickhouse",
|
||||
optional=False,
|
||||
)
|
||||
assert Runner._skip_missing_optional_artifact(a, "./clickhouse") is False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_optional_defaults_to_false()
|
||||
test_optional_field_is_settable()
|
||||
test_coverage_profdata_artifacts_are_optional()
|
||||
test_runner_skips_missing_optional_artifact()
|
||||
test_runner_does_not_skip_missing_required_artifact()
|
||||
print("All optional-artifact tests passed")
|
||||
@@ -35,6 +35,11 @@ STYLE_AND_FAST_TESTS = [
|
||||
*[j.name for j in JobConfigs.tidy_build_arm_jobs],
|
||||
]
|
||||
|
||||
CODE_REVIEW_BLOCKING_JOBS = [
|
||||
JobNames.STYLE_CHECK,
|
||||
JobNames.FAST_TEST,
|
||||
]
|
||||
|
||||
REGULAR_BUILD_NAMES = [job.name for job in JobConfigs.build_jobs]
|
||||
|
||||
PLAIN_FUNCTIONAL_TEST_JOB = [
|
||||
@@ -47,7 +52,7 @@ workflow = Workflow.Config(
|
||||
base_branches=[BASE_BRANCH],
|
||||
jobs=[
|
||||
JobConfigs.style_check,
|
||||
JobConfigs.code_review.set_run_after(STYLE_AND_FAST_TESTS),
|
||||
JobConfigs.code_review.set_run_after(CODE_REVIEW_BLOCKING_JOBS),
|
||||
JobConfigs.docs_job,
|
||||
JobConfigs.docs_job_mintlify,
|
||||
JobConfigs.fast_test,
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
"light": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
"banner": {
|
||||
"content": "ClickHouse launches Claude-powered Agents and House Mates partner program at Open House 2026 [Read more →](https://clickhouse.com/blog/clickhouse-tops-250m-arr-and-4000-customers)",
|
||||
"dismissible": true
|
||||
},
|
||||
"colors": {
|
||||
"dark": "#fdff75",
|
||||
"light": "#333431",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.7076 17.2926C18.6147 17.1996 18.5044 17.1258 18.383 17.0755C18.2616 17.0252 18.1315 16.9993 18.0001 16.9993C17.8687 16.9993 17.7385 17.0252 17.6171 17.0755C17.4957 17.1258 17.3854 17.1996 17.2926 17.2926L15.0001 19.5863L12.4138 17.0001L14.7076 14.7076C14.8952 14.5199 15.0006 14.2654 15.0006 14.0001C15.0006 13.7347 14.8952 13.4802 14.7076 13.2926C14.5199 13.1049 14.2654 12.9995 14.0001 12.9995C13.7347 12.9995 13.4802 13.1049 13.2926 13.2926L11.0001 15.5863L8.70757 13.2926C8.51993 13.1049 8.26543 12.9995 8.00007 12.9995C7.7347 12.9995 7.48021 13.1049 7.29257 13.2926C7.10493 13.4802 6.99951 13.7347 6.99951 14.0001C6.99951 14.2654 7.10493 14.5199 7.29257 14.7076L8.08632 15.5001L5.17132 18.4138C4.79979 18.7852 4.50507 19.2262 4.30399 19.7116C4.10292 20.197 3.99942 20.7172 3.99942 21.2426C3.99942 21.7679 4.10292 22.2881 4.30399 22.7735C4.50507 23.2589 4.79979 23.6999 5.17132 24.0713L5.84382 24.7426L2.29257 28.2926C2.19966 28.3855 2.12596 28.4958 2.07567 28.6172C2.02539 28.7386 1.99951 28.8687 1.99951 29.0001C1.99951 29.1314 2.02539 29.2616 2.07567 29.3829C2.12596 29.5043 2.19966 29.6146 2.29257 29.7076C2.48021 29.8952 2.7347 30.0006 3.00007 30.0006C3.13146 30.0006 3.26157 29.9747 3.38296 29.9244C3.50436 29.8742 3.61466 29.8005 3.70757 29.7076L7.25757 26.1563L7.92882 26.8288C8.30026 27.2003 8.74126 27.4951 9.22662 27.6961C9.71198 27.8972 10.2322 28.0007 10.7576 28.0007C11.2829 28.0007 11.8032 27.8972 12.2885 27.6961C12.7739 27.4951 13.2149 27.2003 13.5863 26.8288L16.5001 23.9138L17.2926 24.7076C17.3855 24.8005 17.4958 24.8742 17.6172 24.9244C17.7386 24.9747 17.8687 25.0006 18.0001 25.0006C18.1315 25.0006 18.2616 24.9747 18.383 24.9244C18.5044 24.8742 18.6147 24.8005 18.7076 24.7076C18.8005 24.6146 18.8742 24.5043 18.9245 24.3829C18.9747 24.2616 19.0006 24.1314 19.0006 24.0001C19.0006 23.8687 18.9747 23.7386 18.9245 23.6172C18.8742 23.4958 18.8005 23.3855 18.7076 23.2926L16.4138 21.0001L18.7076 18.7076C18.8005 18.6147 18.8743 18.5044 18.9246 18.383C18.975 18.2616 19.0009 18.1315 19.0009 18.0001C19.0009 17.8686 18.975 17.7385 18.9246 17.6171C18.8743 17.4957 18.8005 17.3854 18.7076 17.2926ZM12.1713 25.4176C11.7963 25.7923 11.2878 26.0029 10.7576 26.0029C10.2274 26.0029 9.71885 25.7923 9.34382 25.4176L6.58632 22.6563C6.21153 22.2813 6.00099 21.7728 6.00099 21.2426C6.00099 20.7123 6.21153 20.2038 6.58632 19.8288L9.50007 16.9138L15.0863 22.5001L12.1713 25.4176ZM29.7076 2.29255C29.6147 2.19958 29.5044 2.12582 29.383 2.07549C29.2616 2.02517 29.1315 1.99927 29.0001 1.99927C28.8687 1.99927 28.7385 2.02517 28.6171 2.07549C28.4957 2.12582 28.3854 2.19958 28.2926 2.29255L24.7426 5.8438L24.0713 5.1713C23.3203 4.42249 22.3031 4.00199 21.2426 4.00199C20.1821 4.00199 19.1648 4.42249 18.4138 5.1713L15.5001 8.0863L14.7076 7.29255C14.5199 7.10491 14.2654 6.9995 14.0001 6.9995C13.7347 6.9995 13.4802 7.10491 13.2926 7.29255C13.1049 7.4802 12.9995 7.73469 12.9995 8.00005C12.9995 8.26542 13.1049 8.51991 13.2926 8.70755L23.2926 18.7076C23.3855 18.8005 23.4958 18.8742 23.6172 18.9244C23.7386 18.9747 23.8687 19.0006 24.0001 19.0006C24.1315 19.0006 24.2616 18.9747 24.383 18.9244C24.5044 18.8742 24.6147 18.8005 24.7076 18.7076C24.8005 18.6146 24.8742 18.5043 24.9245 18.3829C24.9747 18.2616 25.0006 18.1314 25.0006 18.0001C25.0006 17.8687 24.9747 17.7386 24.9245 17.6172C24.8742 17.4958 24.8005 17.3855 24.7076 17.2926L23.9138 16.5001L26.8288 13.5863C27.2003 13.2149 27.4951 12.7739 27.6961 12.2885C27.8972 11.8031 28.0007 11.2829 28.0007 10.7576C28.0007 10.2322 27.8972 9.71197 27.6961 9.22661C27.4951 8.74125 27.2003 8.30025 26.8288 7.9288L26.1563 7.25755L29.7076 3.70755C29.8005 3.61468 29.8743 3.50439 29.9246 3.38299C29.975 3.2616 30.0009 3.13147 30.0009 3.00005C30.0009 2.86864 29.975 2.73851 29.9246 2.61711C29.8743 2.49572 29.8005 2.38543 29.7076 2.29255ZM25.4138 12.1676L22.5001 15.0863L16.9138 9.50005L19.8288 6.5863C20.2039 6.21151 20.7124 6.00098 21.2426 6.00098C21.7728 6.00098 22.2813 6.21151 22.6563 6.5863L25.4138 9.3363C25.6005 9.52214 25.7487 9.74303 25.8498 9.98629C25.9509 10.2295 26.0029 10.4904 26.0029 10.7538C26.0029 11.0172 25.9509 11.2781 25.8498 11.5213C25.7487 11.7646 25.6005 11.9855 25.4138 12.1713V12.1676Z" fill="#FAFF69"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -1321,6 +1321,9 @@ void LocalServer::processConfig()
|
||||
|
||||
setupUsers();
|
||||
|
||||
/// SYSTEM ALLOCATE MEMORY is a diagnostic command, harmless to enable in clickhouse-local.
|
||||
global_context->allowSystemAllocateMemory(true);
|
||||
|
||||
/// Limit on total number of concurrently executing queries.
|
||||
/// Plain `clickhouse-local` runs a single query at a time, but once it is turned into a server
|
||||
/// via `SYSTEM START LISTEN` it accepts external connections and must honor the configured
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace ErrorCodes
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int BAD_ARGUMENTS;
|
||||
extern const int TOO_LARGE_ARRAY_SIZE;
|
||||
extern const int INCORRECT_DATA;
|
||||
}
|
||||
|
||||
namespace
|
||||
@@ -342,12 +343,17 @@ public:
|
||||
readBinaryLittleEndian(element, buf);
|
||||
}
|
||||
|
||||
if constexpr (Trait::last)
|
||||
if constexpr (Trait::last || Trait::sampler == Sampler::RNG)
|
||||
{
|
||||
readBinaryLittleEndian(this->data(place).total_values, buf);
|
||||
if (static_cast<UInt64>(this->data(place).value.size()) != std::min<UInt64>(max_elems, this->data(place).total_values))
|
||||
throw Exception(ErrorCodes::INCORRECT_DATA,
|
||||
"Malformed {} state: {} stored elements, but total_values = {} with max_elems = {}",
|
||||
getName(), this->data(place).value.size(), this->data(place).total_values, max_elems);
|
||||
}
|
||||
|
||||
if constexpr (Trait::sampler == Sampler::RNG)
|
||||
{
|
||||
readBinaryLittleEndian(this->data(place).total_values, buf);
|
||||
std::string rng_string;
|
||||
readStringBinary(rng_string, buf);
|
||||
ReadBufferFromString rng_buf(rng_string);
|
||||
@@ -691,12 +697,17 @@ public:
|
||||
value[i] = Node::read(buf, arena);
|
||||
}
|
||||
|
||||
if constexpr (Trait::last)
|
||||
if constexpr (Trait::last || Trait::sampler == Sampler::RNG)
|
||||
{
|
||||
readBinaryLittleEndian(data(place).total_values, buf);
|
||||
if (static_cast<UInt64>(data(place).value.size()) != std::min<UInt64>(max_elems, data(place).total_values))
|
||||
throw Exception(ErrorCodes::INCORRECT_DATA,
|
||||
"Malformed {} state: {} stored elements, but total_values = {} with max_elems = {}",
|
||||
getName(), data(place).value.size(), data(place).total_values, max_elems);
|
||||
}
|
||||
|
||||
if constexpr (Trait::sampler == Sampler::RNG)
|
||||
{
|
||||
readBinaryLittleEndian(data(place).total_values, buf);
|
||||
std::string rng_string;
|
||||
readStringBinary(rng_string, buf);
|
||||
ReadBufferFromString rng_buf(rng_string);
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace ErrorCodes
|
||||
extern const int TOO_LARGE_ARRAY_SIZE;
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||
extern const int INCORRECT_DATA;
|
||||
}
|
||||
|
||||
struct Settings;
|
||||
@@ -264,7 +265,10 @@ public:
|
||||
|
||||
void deserialize(AggregateDataPtr __restrict place, ReadBuffer & buf, std::optional<size_t> /* version */, Arena * arena) const override
|
||||
{
|
||||
data(place).read(buf, arena);
|
||||
auto & sample = data(place);
|
||||
sample.read(buf, arena);
|
||||
if (sample.x.size() != sample.y.size())
|
||||
throw Exception(ErrorCodes::INCORRECT_DATA, "Sizes of x and y do not match in the state of {}", getName());
|
||||
}
|
||||
|
||||
void insertResultInto(AggregateDataPtr __restrict place, IColumn & to, Arena *) const override
|
||||
|
||||
@@ -131,9 +131,9 @@ class CompressionCodecALP final : public ICompressionCodec
|
||||
public:
|
||||
enum class Variant : UInt8
|
||||
{
|
||||
DEFAULT = 0, // Default ALP variant (AUTO)
|
||||
AUTO = 1, // Auto-select variant per block based on estimated compressed size (default)
|
||||
STD = 2, // Standard ALP variant with decimal-based integerization
|
||||
DEFAULT = 0, // No explicit argument, behaves as AUTO.
|
||||
AUTO = 1, // Selects STD or RD per compress block based on the estimated compressed size.
|
||||
STD = 2, // Standard ALP variant with decimal-based integerization.
|
||||
RD = 3 // Real Doubles ALP variant with bit-split and dictionary encoding of the high bits.
|
||||
};
|
||||
|
||||
@@ -662,7 +662,8 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
best_size = std::min(best_size, sample_best_size);
|
||||
/// Scale partial tail samples s.t. comparison against RD_SIZE_THRESHOLD_LIMIT below does not favour STD because the sample was short.
|
||||
best_size = std::min(best_size, sample_best_size * ALP_PARAMS_ESTIMATION_SAMPLE_FLOATS / sample_float_count);
|
||||
|
||||
const UInt16 key = static_cast<UInt16>((best_estimation.params.exponent << 8) | best_estimation.params.fraction);
|
||||
auto it = estimations_map.find(key);
|
||||
@@ -1205,7 +1206,7 @@ private:
|
||||
throw Exception(ErrorCodes::CANNOT_DECOMPRESS,
|
||||
"Cannot decompress ALP(RD)-encoded data, incomplete RD header");
|
||||
|
||||
// Read left bit-width and check for unencoded block marker
|
||||
// Read left bit-width
|
||||
dict_params.left_bits = static_cast<UInt8>(*source++);
|
||||
if (unlikely(dict_params.left_bits == 0 || dict_params.left_bits > ALP_RD_CUTTING_LIMIT))
|
||||
throw Exception(ErrorCodes::CANNOT_DECOMPRESS,
|
||||
|
||||
@@ -2057,6 +2057,41 @@ TEST_F(ALPTest, CompressProducesCorrectHeader)
|
||||
}
|
||||
}
|
||||
|
||||
UInt8 alpAutoFloat64MetaByte(const std::vector<Float64> & values)
|
||||
{
|
||||
auto codec = makeCodec("ALP(AUTO)", std::make_shared<DataTypeFloat64>());
|
||||
|
||||
const UInt32 source_size = static_cast<UInt32>(values.size() * sizeof(Float64));
|
||||
|
||||
Memory<> compressed_memory;
|
||||
compressed_memory.resize(ICompressionCodec::getHeaderSize() + codec->getCompressedReserveSize(source_size));
|
||||
|
||||
codec->compress(reinterpret_cast<const char *>(values.data()), source_size, compressed_memory.data());
|
||||
|
||||
return static_cast<UInt8>(compressed_memory[ICompressionCodec::getHeaderSize()]);
|
||||
}
|
||||
|
||||
TEST_F(ALPTest, AutoVariantGlobalSamplingCoversWholeStream)
|
||||
{
|
||||
/// With 257-511 values the presampling windows used to cluster at the head of the stream.
|
||||
/// The head is STD-hostile and the tail decimal-friendly, so STD wins only if the tail is sampled.
|
||||
std::vector<Float64> values(300);
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
values[i] = i < 128 ? std::sin(static_cast<Float64>(i + 1)) * 1e6 : static_cast<Float64>(i) * 0.1;
|
||||
|
||||
ASSERT_EQ(alpAutoFloat64MetaByte(values), 0x01); // STD
|
||||
}
|
||||
|
||||
TEST_F(ALPTest, AutoVariantThresholdIsSampleLengthIndependent)
|
||||
{
|
||||
/// All values are STD-hostile, but the unscaled estimate of the 8-value tail sample used to stay below the full-sample threshold and forced STD.
|
||||
std::vector<Float64> values(40);
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
values[i] = std::sin(static_cast<Float64>(i + 1)) * 1e6;
|
||||
|
||||
ASSERT_EQ(alpAutoFloat64MetaByte(values), 0x11); // RD
|
||||
}
|
||||
|
||||
TEST_F(ALPTest, DecompressMalformedInputWithTruncatedHeader)
|
||||
{
|
||||
const std::vector<UInt8> source = {
|
||||
|
||||
@@ -146,11 +146,19 @@ void SerializationObjectSharedData::enumerateStreams(
|
||||
else
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataStructure);
|
||||
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataData);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataPathsMarks);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataSubstreams);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataSubstreamsMarks);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataPathsSubstreamsMetadata);
|
||||
/// When deserialize state is present, it means the whole shared data will be read
|
||||
/// via deserializeBinaryBulkWithMultipleStreams, which only uses Structure + Copy streams.
|
||||
/// Per-bucket Data/PathsMarks/Substreams/SubstreamsMarks/PathsSubstreamsMetadata are only
|
||||
/// needed when writing or reading individual paths via SerializationObjectSharedDataPath (separate class).
|
||||
/// Skip them to avoid unnecessary mark file loads and file opens during prefetching.
|
||||
if (!shared_data_state)
|
||||
{
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataData);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataPathsMarks);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataSubstreams);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataSubstreamsMarks);
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataPathsSubstreamsMetadata);
|
||||
}
|
||||
|
||||
if (settings.use_specialized_prefixes_and_suffixes_substreams)
|
||||
addSubstreamAndCallCallback(settings.path, callback, Substream::ObjectSharedDataStructureSuffix);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <Processors/QueryPlan/FilterStep.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <DataTypes/DataTypeFactory.h>
|
||||
#include <DataTypes/DataTypeLowCardinality.h>
|
||||
@@ -20,6 +22,7 @@
|
||||
#include <Common/JSONBuilder.h>
|
||||
|
||||
#include <Processors/QueryPlan/Optimizations/RuntimeDataflowStatistics.h>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@@ -45,6 +48,138 @@ static ITransformingStep::Traits getTraits()
|
||||
};
|
||||
}
|
||||
|
||||
FilterDAGOutputPruningResult pruneFilterDAGOutputsByPosition(
|
||||
ActionsDAG & dag,
|
||||
const String & filter_column_name,
|
||||
bool & remove_filter_column,
|
||||
const Block & input_header,
|
||||
const std::vector<size_t> & required_output_positions,
|
||||
bool remove_inputs)
|
||||
{
|
||||
FilterDAGOutputPruningResult result;
|
||||
|
||||
const bool was_remove_filter_column = remove_filter_column;
|
||||
const auto & old_outputs = dag.getOutputs();
|
||||
const size_t old_dag_outputs_size = old_outputs.size();
|
||||
const auto actions_dag_input_count_before = dag.getInputs().size();
|
||||
|
||||
/// The pre-erase output header (from ActionsDAG::updateHeader) is:
|
||||
/// [DAG output 0, ..., DAG output N-1, pass-through input 0, ...]
|
||||
/// When remove_filter_column is true, then the first column named filter_column_name is
|
||||
/// erased from the block, shifting subsequent positions by -1.
|
||||
/// Map the caller's positions (into the final output header) back to the pre-erase layout.
|
||||
|
||||
/// Find the filter column's position in the pre-erase header.
|
||||
size_t filter_col_pre_erase_pos = std::numeric_limits<size_t>::max();
|
||||
for (size_t i = 0; i < old_dag_outputs_size; ++i)
|
||||
{
|
||||
if (old_outputs[i]->result_name == filter_column_name)
|
||||
{
|
||||
filter_col_pre_erase_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (filter_col_pre_erase_pos == std::numeric_limits<size_t>::max())
|
||||
throw Exception(
|
||||
ErrorCodes::LOGICAL_ERROR,
|
||||
"Filter column {} not found in DAG outputs: [{}]",
|
||||
filter_column_name,
|
||||
fmt::join(dag.getNames(), ", "));
|
||||
|
||||
/// Map positions from the final (post-erase) header to the pre-erase header.
|
||||
/// remove_filter_column is captured by value because the mapping depends on the original value of the flag, not on
|
||||
/// whether the filter column is still present at the time of mapping.
|
||||
auto map_to_pre_erase_pos = [filter_col_pre_erase_pos, was_remove_filter_column](size_t pos) -> size_t
|
||||
{
|
||||
if (!was_remove_filter_column)
|
||||
return pos;
|
||||
return pos >= filter_col_pre_erase_pos ? pos + 1 : pos;
|
||||
};
|
||||
|
||||
/// Map positions from post-erase to pre-erase layout, then split into DAG vs pass-through.
|
||||
std::vector<size_t> pre_erase_positions;
|
||||
pre_erase_positions.reserve(required_output_positions.size());
|
||||
for (size_t pos : required_output_positions)
|
||||
pre_erase_positions.push_back(map_to_pre_erase_pos(pos));
|
||||
|
||||
auto [required_dag_indices, required_passthrough_indices] = dag.splitOutputPositions(pre_erase_positions);
|
||||
|
||||
/// Build the list of pass-through input columns.
|
||||
const auto passthrough_input_header_positions = dag.matchInputPositionsToHeader(input_header).passthrough;
|
||||
|
||||
std::set<size_t> required_passthrough_input_header_positions;
|
||||
for (size_t passthrough_index : required_passthrough_indices)
|
||||
{
|
||||
if (passthrough_index >= passthrough_input_header_positions.size())
|
||||
throw Exception(
|
||||
ErrorCodes::LOGICAL_ERROR, "Required output position {} is out of range for pass-through inputs", passthrough_index);
|
||||
required_passthrough_input_header_positions.insert(passthrough_input_header_positions[passthrough_index]);
|
||||
}
|
||||
|
||||
const auto has_to_remove_any_pass_through
|
||||
= passthrough_input_header_positions.size() > required_passthrough_input_header_positions.size();
|
||||
|
||||
std::set<size_t> required_dag_index_set(required_dag_indices.begin(), required_dag_indices.end());
|
||||
|
||||
/// Check if the filter column is required by the caller. If not, we can remove it.
|
||||
if (!remove_filter_column && !required_dag_index_set.contains(filter_col_pre_erase_pos))
|
||||
remove_filter_column = true;
|
||||
|
||||
required_dag_index_set.insert(filter_col_pre_erase_pos);
|
||||
|
||||
/// Keep only the required DAG output nodes, plus always keep the filter column.
|
||||
ActionsDAG::NodeRawConstPtrs new_dag_outputs;
|
||||
new_dag_outputs.reserve(required_dag_index_set.size());
|
||||
|
||||
for (size_t i = 0; i < old_dag_outputs_size; ++i)
|
||||
{
|
||||
if (required_dag_index_set.contains(i))
|
||||
new_dag_outputs.push_back(old_outputs[i]);
|
||||
}
|
||||
|
||||
auto & dag_outputs = dag.getOutputs();
|
||||
if (new_dag_outputs.size() != dag_outputs.size())
|
||||
result.changed = true;
|
||||
dag_outputs = std::move(new_dag_outputs);
|
||||
|
||||
if (was_remove_filter_column != remove_filter_column)
|
||||
result.changed = true;
|
||||
|
||||
if (dag.removeUnusedActions(remove_inputs))
|
||||
result.changed = true;
|
||||
|
||||
if (!remove_inputs && has_to_remove_any_pass_through)
|
||||
{
|
||||
for (size_t passthrough_input_header_position : passthrough_input_header_positions)
|
||||
{
|
||||
if (!required_passthrough_input_header_positions.contains(passthrough_input_header_position))
|
||||
{
|
||||
const auto & column = input_header.getByPosition(passthrough_input_header_position);
|
||||
dag.addInput(column);
|
||||
}
|
||||
}
|
||||
result.changed = true;
|
||||
}
|
||||
|
||||
if (remove_inputs)
|
||||
{
|
||||
auto required_input_positions = dag.matchInputPositionsToHeader(input_header).matched;
|
||||
required_input_positions.insert(
|
||||
required_input_positions.end(),
|
||||
required_passthrough_input_header_positions.begin(),
|
||||
required_passthrough_input_header_positions.end());
|
||||
|
||||
std::sort(required_input_positions.begin(), required_input_positions.end());
|
||||
result.required_input_positions = std::move(required_input_positions);
|
||||
result.input_positions_changed = dag.getInputs().size() != actions_dag_input_count_before || has_to_remove_any_pass_through;
|
||||
|
||||
if (result.input_positions_changed)
|
||||
result.changed = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool isTrivialSubtree(const ActionsDAG::Node * node)
|
||||
{
|
||||
while (node->type == ActionsDAG::ActionType::ALIAS)
|
||||
@@ -317,135 +452,24 @@ FilterStep::RemoveUnusedColumnsResult FilterStep::removeUnusedColumns(const std:
|
||||
&& "There cannot be more DAG inputs than columns in the input header");
|
||||
|
||||
const auto & input_header = input_headers.front();
|
||||
const size_t num_dag_outputs = actions_dag.getOutputs().size();
|
||||
const auto actions_dag_input_count_before = actions_dag.getInputs().size();
|
||||
auto pruning_result = pruneFilterDAGOutputsByPosition(
|
||||
actions_dag, filter_column_name, remove_filter_column, *input_header, required_output_positions, remove_inputs);
|
||||
|
||||
/// The pre-erase output header (from updateHeader) is:
|
||||
/// [DAG output 0, ..., DAG output N-1, pass-through input 0, ...]
|
||||
/// When remove_filter_column is true, FilterTransform::transformHeader erases the first column
|
||||
/// named filter_column_name from this block, shifting subsequent positions by -1.
|
||||
/// Map the caller's positions (into the final output header) back to the pre-erase layout.
|
||||
|
||||
/// Find the filter column's position in the pre-erase header.
|
||||
size_t filter_col_pre_erase_pos = std::numeric_limits<size_t>::max();
|
||||
for (size_t i = 0; i < num_dag_outputs; ++i)
|
||||
{
|
||||
if (actions_dag.getOutputs()[i]->result_name == filter_column_name)
|
||||
{
|
||||
filter_col_pre_erase_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
chassert(filter_col_pre_erase_pos != std::numeric_limits<size_t>::max() && "Filter column not found in DAG outputs");
|
||||
|
||||
/// Map positions from the final (post-erase) header to the pre-erase header.
|
||||
/// remove_filter_column is captured by value because the mapping depends on the original value of the flag, not on
|
||||
/// whether the filter column is still present at the time of mapping.
|
||||
auto map_to_pre_erase_pos = [filter_col_pre_erase_pos, is_filter_column_removed = remove_filter_column](size_t pos) -> size_t
|
||||
{
|
||||
if (!is_filter_column_removed)
|
||||
return pos;
|
||||
return pos >= filter_col_pre_erase_pos ? pos + 1 : pos;
|
||||
};
|
||||
|
||||
/// Map positions from post-erase to pre-erase layout, then split into DAG vs pass-through.
|
||||
std::vector<size_t> pre_erase_positions;
|
||||
pre_erase_positions.reserve(required_output_positions.size());
|
||||
for (size_t pos : required_output_positions)
|
||||
pre_erase_positions.push_back(map_to_pre_erase_pos(pos));
|
||||
|
||||
auto [required_dag_indices, required_passthrough_indices]
|
||||
= actions_dag.splitOutputPositions(pre_erase_positions);
|
||||
|
||||
|
||||
if (!remove_filter_column)
|
||||
{
|
||||
/// Check if the filter column is required by the caller. If not, we can remove it.
|
||||
bool filter_column_required_by_caller =
|
||||
std::find(required_dag_indices.begin(), required_dag_indices.end(), filter_col_pre_erase_pos)
|
||||
!= required_dag_indices.end();
|
||||
if (!filter_column_required_by_caller)
|
||||
remove_filter_column = true;
|
||||
}
|
||||
|
||||
/// Build the list of pass-through input columns.
|
||||
auto passthrough_input_header_positions = actions_dag.matchInputPositionsToHeader(*input_header).passthrough;
|
||||
|
||||
std::set<size_t> required_passthrough_input_header_positions;
|
||||
for (size_t pt_idx : required_passthrough_indices)
|
||||
{
|
||||
if (pt_idx >= passthrough_input_header_positions.size())
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Required output position {} is out of range for pass-through inputs", pt_idx);
|
||||
required_passthrough_input_header_positions.insert(passthrough_input_header_positions[pt_idx]);
|
||||
}
|
||||
|
||||
const auto num_passthrough = passthrough_input_header_positions.size();
|
||||
const auto has_to_remove_any_pass_through = num_passthrough > required_passthrough_indices.size();
|
||||
|
||||
/// Keep only the required DAG output nodes, plus always keep the filter column.
|
||||
auto & dag_outputs = actions_dag.getOutputs();
|
||||
ActionsDAG::NodeRawConstPtrs new_dag_outputs;
|
||||
new_dag_outputs.reserve(required_dag_indices.size() + 1);
|
||||
|
||||
std::set<size_t> required_dag_index_set(required_dag_indices.begin(), required_dag_indices.end());
|
||||
|
||||
/// Always keep the filter column in the DAG outputs.
|
||||
required_dag_index_set.insert(filter_col_pre_erase_pos);
|
||||
|
||||
for (size_t i = 0; i < num_dag_outputs; ++i)
|
||||
{
|
||||
if (required_dag_index_set.contains(i))
|
||||
new_dag_outputs.push_back(dag_outputs[i]);
|
||||
}
|
||||
dag_outputs = std::move(new_dag_outputs);
|
||||
|
||||
auto updated_actions = actions_dag.removeUnusedActions(remove_inputs);
|
||||
|
||||
/// If we cannot remove inputs but need to remove pass-through outputs,
|
||||
/// convert unrequired pass-through inputs into DAG inputs.
|
||||
const auto has_to_add_input_to_actions = !remove_inputs && has_to_remove_any_pass_through;
|
||||
if (has_to_add_input_to_actions)
|
||||
{
|
||||
for (size_t pt_pos : passthrough_input_header_positions)
|
||||
{
|
||||
if (!required_passthrough_input_header_positions.contains(pt_pos))
|
||||
{
|
||||
const auto & col = input_header->getByPosition(pt_pos);
|
||||
actions_dag.addInput(col);
|
||||
}
|
||||
}
|
||||
updated_actions = true;
|
||||
}
|
||||
|
||||
if (!updated_actions && output_header->columns() == required_output_positions.size())
|
||||
if (!pruning_result.changed && output_header->columns() == required_output_positions.size())
|
||||
return {};
|
||||
|
||||
if (actions_dag.getInputs().size() > getInputHeaders().at(0)->columns())
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "There cannot be more inputs in the DAG than columns in the input header");
|
||||
|
||||
const auto actions_dag_has_less_inputs = actions_dag.getInputs().size() < actions_dag_input_count_before;
|
||||
const auto update_inputs = remove_inputs && (actions_dag_has_less_inputs || has_to_remove_any_pass_through);
|
||||
|
||||
if (update_inputs)
|
||||
if (pruning_result.input_positions_changed)
|
||||
{
|
||||
/// Build the set of required input header positions.
|
||||
auto matched_positions = actions_dag.matchInputPositionsToHeader(*input_header).matched;
|
||||
std::vector<size_t> required_input_positions(matched_positions.begin(), matched_positions.end());
|
||||
required_input_positions.insert(
|
||||
required_input_positions.end(),
|
||||
required_passthrough_input_header_positions.begin(),
|
||||
required_passthrough_input_header_positions.end());
|
||||
|
||||
/// Build the result vector and update the input header.
|
||||
std::sort(required_input_positions.begin(), required_input_positions.end());
|
||||
|
||||
Block new_input_header{};
|
||||
for (size_t pos : required_input_positions)
|
||||
for (size_t pos : pruning_result.required_input_positions)
|
||||
new_input_header.insert(input_header->getByPosition(pos));
|
||||
|
||||
SharedHeader new_shared_input_header = std::make_shared<const Block>(std::move(new_input_header));
|
||||
updateInputHeader(std::move(new_shared_input_header), 0);
|
||||
return {true, {std::move(required_input_positions)}, required_output_positions};
|
||||
return {true, {std::move(pruning_result.required_input_positions)}, required_output_positions};
|
||||
}
|
||||
|
||||
updateOutputHeader();
|
||||
|
||||
@@ -6,6 +6,22 @@
|
||||
namespace DB
|
||||
{
|
||||
|
||||
struct FilterDAGOutputPruningResult
|
||||
{
|
||||
bool changed = false;
|
||||
bool input_positions_changed = false;
|
||||
std::vector<size_t> required_input_positions;
|
||||
};
|
||||
|
||||
/// Prune filter DAG outputs by position and return the input positions needed to compute the remaining outputs and filter.
|
||||
FilterDAGOutputPruningResult pruneFilterDAGOutputsByPosition(
|
||||
ActionsDAG & dag,
|
||||
const String & filter_column_name,
|
||||
bool & remove_filter_column,
|
||||
const Block & input_header,
|
||||
const std::vector<size_t> & required_output_positions,
|
||||
bool remove_inputs);
|
||||
|
||||
/// Implements WHERE, HAVING operations. See FilterTransform.
|
||||
class FilterStep : public ITransformingStep
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <DataTypes/IDataType.h>
|
||||
#include <Processors/QueryPlan/Optimizations/projectionsCommon.h>
|
||||
|
||||
#include <Columns/ColumnConst.h>
|
||||
@@ -150,9 +151,9 @@ static const ActionsDAG::Node * findInOutputs(ActionsDAG & dag, const std::strin
|
||||
if (node->result_type->onlyNull())
|
||||
return nullptr;
|
||||
|
||||
if (!isUInt8(removeNullable(removeLowCardinality(node->result_type))))
|
||||
if (!node->result_type->canBeUsedInBooleanContext())
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER,
|
||||
"Illegal type {} of column {} for filter. Must be UInt8 or Nullable(UInt8).",
|
||||
"Illegal type {} of column {} for filter. Must be native integer or float type",
|
||||
node->result_type->getName(), name);
|
||||
|
||||
if (remove)
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <Processors/Merges/MergingSortedTransform.h>
|
||||
#include <Processors/QueryPlan/IParameterLookup.h>
|
||||
#include <Processors/QueryPlan/IQueryPlanStep.h>
|
||||
#include <Processors/QueryPlan/FilterStep.h>
|
||||
#include <Processors/QueryPlan/LazilyReadFromMergeTree.h>
|
||||
#include <Processors/QueryPlan/MergeTreeFinalMerge.h>
|
||||
#include <Processors/QueryPlan/PartsSplitter.h>
|
||||
@@ -76,6 +77,7 @@
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
@@ -4857,98 +4859,94 @@ bool ReadFromMergeTree::canRemoveUnusedColumns() const
|
||||
|
||||
ReadFromMergeTree::RemoveUnusedColumnsResult ReadFromMergeTree::removeUnusedColumns(const std::vector<size_t> & required_output_positions, bool /*remove_inputs*/)
|
||||
{
|
||||
const auto ensure_no_duplicate_outputs = [](const ActionsDAG & dag, const std::string_view & description)
|
||||
{
|
||||
NameSet seen_outputs;
|
||||
for (const auto * output : dag.getOutputs())
|
||||
{
|
||||
if (seen_outputs.contains(output->result_name))
|
||||
throw Exception(
|
||||
ErrorCodes::LOGICAL_ERROR, "Duplicate column name {} in {} actions output", output->result_name, description);
|
||||
seen_outputs.insert(output->result_name);
|
||||
}
|
||||
};
|
||||
|
||||
if (query_info.prewhere_info)
|
||||
ensure_no_duplicate_outputs(query_info.prewhere_info->prewhere_actions, "prewhere");
|
||||
if (query_info.row_level_filter)
|
||||
ensure_no_duplicate_outputs(query_info.row_level_filter->actions, "row policy");
|
||||
|
||||
if (output_header == nullptr)
|
||||
return {};
|
||||
|
||||
/// ReadFromMergeTree output columns are unique (table columns always have distinct names),
|
||||
/// so we can safely convert positions to names for internal use.
|
||||
NameSet columns_to_keep;
|
||||
|
||||
/// Positions in the final RFMT output that must be preserved for the parent step or FINAL.
|
||||
std::set<size_t> required_final_output_positions(required_output_positions.begin(), required_output_positions.end());
|
||||
/// Positions in all_column_names that must still be read from storage.
|
||||
std::set<size_t> required_storage_column_positions;
|
||||
if (query_info.isFinal())
|
||||
columns_to_keep = getColumnsRequiredForMergingFinal(result_sort_description, data.merging_params);
|
||||
{
|
||||
const auto required_for_final = getColumnsRequiredForMergingFinal(result_sort_description, data.merging_params);
|
||||
|
||||
for (size_t pos : required_output_positions)
|
||||
columns_to_keep.insert(output_header->getByPosition(pos).name);
|
||||
for (size_t pos = 0; pos < output_header->columns(); ++pos)
|
||||
{
|
||||
if (required_for_final.contains(output_header->getByPosition(pos).name))
|
||||
required_final_output_positions.insert(pos);
|
||||
}
|
||||
|
||||
auto removed_output_from_prewhere = false;
|
||||
for (size_t pos = 0; pos < all_column_names.size(); ++pos)
|
||||
{
|
||||
if (required_for_final.contains(all_column_names[pos]))
|
||||
required_storage_column_positions.insert(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sorted vector form of required_final_output_positions, used as the initial backward-pruning frontier.
|
||||
std::vector<size_t> final_output_positions(
|
||||
required_final_output_positions.begin(),
|
||||
required_final_output_positions.end());
|
||||
|
||||
Block storage_header = storage_snapshot->getSampleBlockForColumns(all_column_names);
|
||||
Block row_level_output_header = storage_header;
|
||||
if (query_info.row_level_filter)
|
||||
row_level_output_header = SourceStepWithFilter::applyPrewhereActions(std::move(row_level_output_header), query_info.row_level_filter, nullptr);
|
||||
|
||||
/// Positions in the row-policy output header, which is the input header for PREWHERE.
|
||||
std::vector<size_t> required_row_level_output_positions;
|
||||
/// Positions from the old final RFMT output that remain after pruning.
|
||||
std::vector<size_t> kept_output_positions = final_output_positions;
|
||||
bool removed_output_from_prewhere = false;
|
||||
if (query_info.prewhere_info)
|
||||
{
|
||||
auto & prewhere_outputs = query_info.prewhere_info->prewhere_actions.getOutputs();
|
||||
removed_output_from_prewhere = std::erase_if(
|
||||
prewhere_outputs,
|
||||
[&](const auto * output)
|
||||
{
|
||||
return output->result_name != query_info.prewhere_info->prewhere_column_name
|
||||
&& !columns_to_keep.contains(output->result_name);
|
||||
})
|
||||
> 0;
|
||||
|
||||
if (!query_info.prewhere_info->remove_prewhere_column && !columns_to_keep.contains(query_info.prewhere_info->prewhere_column_name))
|
||||
{
|
||||
query_info.prewhere_info->remove_prewhere_column = true;
|
||||
removed_output_from_prewhere = true;
|
||||
}
|
||||
|
||||
/// Preserve filter dependencies: inputs of prewhere must be kept so the filter can be evaluated.
|
||||
for (const auto * input : query_info.prewhere_info->prewhere_actions.getInputs())
|
||||
columns_to_keep.insert(input->result_name);
|
||||
auto prewhere_pruning = pruneFilterDAGOutputsByPosition(
|
||||
query_info.prewhere_info->prewhere_actions,
|
||||
query_info.prewhere_info->prewhere_column_name,
|
||||
query_info.prewhere_info->remove_prewhere_column,
|
||||
row_level_output_header,
|
||||
final_output_positions,
|
||||
true);
|
||||
removed_output_from_prewhere = prewhere_pruning.changed;
|
||||
required_row_level_output_positions = std::move(prewhere_pruning.required_input_positions);
|
||||
}
|
||||
else
|
||||
{
|
||||
required_row_level_output_positions = final_output_positions;
|
||||
}
|
||||
|
||||
auto removed_output_from_row_level_filter = false;
|
||||
bool removed_output_from_row_level_filter = false;
|
||||
/// Positions in the storage header required by row policy and PREWHERE filters.
|
||||
std::vector<size_t> required_storage_positions_from_filters;
|
||||
if (query_info.row_level_filter)
|
||||
{
|
||||
/// Important that the inputs of prewhere are also kept as outputs for row level filter, thus `columns_to_keep`
|
||||
/// is used instead of `required_outputs`.
|
||||
auto & row_level_filter_outputs = query_info.row_level_filter->actions.getOutputs();
|
||||
removed_output_from_row_level_filter = std::erase_if(
|
||||
row_level_filter_outputs,
|
||||
[&](const auto * output)
|
||||
{
|
||||
return output->result_name != query_info.row_level_filter->column_name
|
||||
&& !columns_to_keep.contains(output->result_name);
|
||||
})
|
||||
> 0;
|
||||
|
||||
if (!query_info.row_level_filter->do_remove_column && !columns_to_keep.contains(query_info.row_level_filter->column_name))
|
||||
{
|
||||
query_info.row_level_filter->do_remove_column = true;
|
||||
removed_output_from_row_level_filter = true;
|
||||
}
|
||||
/// Preserve filter dependencies: inputs of row-level filter must be kept.
|
||||
for (const auto * input : query_info.row_level_filter->actions.getInputs())
|
||||
columns_to_keep.insert(input->result_name);
|
||||
auto row_level_pruning = pruneFilterDAGOutputsByPosition(
|
||||
query_info.row_level_filter->actions,
|
||||
query_info.row_level_filter->column_name,
|
||||
query_info.row_level_filter->do_remove_column,
|
||||
storage_header,
|
||||
required_row_level_output_positions,
|
||||
true);
|
||||
removed_output_from_row_level_filter = row_level_pruning.changed;
|
||||
required_storage_positions_from_filters = std::move(row_level_pruning.required_input_positions);
|
||||
}
|
||||
else
|
||||
{
|
||||
required_storage_positions_from_filters = required_row_level_output_positions;
|
||||
}
|
||||
|
||||
required_storage_column_positions.insert(required_storage_positions_from_filters.begin(), required_storage_positions_from_filters.end());
|
||||
|
||||
Names new_column_names;
|
||||
for (const auto & column_name : all_column_names)
|
||||
for (size_t pos = 0; pos < all_column_names.size(); ++pos)
|
||||
{
|
||||
if (columns_to_keep.contains(column_name))
|
||||
new_column_names.push_back(column_name);
|
||||
if (required_storage_column_positions.contains(pos))
|
||||
new_column_names.push_back(all_column_names[pos]);
|
||||
}
|
||||
|
||||
if (!removed_output_from_prewhere && !removed_output_from_row_level_filter && new_column_names.size() == all_column_names.size())
|
||||
return {};
|
||||
|
||||
const auto old_output_header = output_header;
|
||||
|
||||
all_column_names = std::move(new_column_names);
|
||||
|
||||
output_header = std::make_shared<const Block>(MergeTreeSelectProcessor::transformHeader(
|
||||
@@ -4956,19 +4954,12 @@ ReadFromMergeTree::RemoveUnusedColumnsResult ReadFromMergeTree::removeUnusedColu
|
||||
query_info.row_level_filter,
|
||||
query_info.prewhere_info));
|
||||
|
||||
/// Compute kept output positions: for each column in the new output, find its position
|
||||
/// in the old output header. ReadFromMergeTree has unique column names, so name lookup is safe.
|
||||
/// We compute this after transformHeader because transformHeader may remove columns
|
||||
/// (e.g., prewhere/row_level_filter columns whose remove flag was set during this optimization).
|
||||
std::vector<size_t> kept_output_positions;
|
||||
kept_output_positions.reserve(output_header->columns());
|
||||
for (size_t i = 0; i < output_header->columns(); ++i)
|
||||
{
|
||||
const auto & column_name = output_header->getByPosition(i).name;
|
||||
auto old_pos = old_output_header->findPositionByName(column_name);
|
||||
chassert(old_pos.has_value() && "New output column must exist in the old output header");
|
||||
kept_output_positions.push_back(*old_pos);
|
||||
}
|
||||
if (kept_output_positions.size() != output_header->columns())
|
||||
throw Exception(
|
||||
ErrorCodes::LOGICAL_ERROR,
|
||||
"Unexpected number of kept output positions after removing unused columns from ReadFromMergeTree: expected {}, got {}",
|
||||
output_header->columns(),
|
||||
kept_output_positions.size());
|
||||
|
||||
/// Update analysis result if it exists
|
||||
if (analyzed_result_ptr)
|
||||
|
||||
@@ -57,7 +57,7 @@ Block FilterTransform::transformHeader(
|
||||
auto filter_type = result.getByName(filter_column_name).type;
|
||||
if (!canUseType(filter_type))
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER,
|
||||
"Illegal type {} of column {} for filter. Must be UInt8 or Nullable(UInt8).",
|
||||
"Illegal type {} of column {} for filter. Must be native integer or float type",
|
||||
filter_type->getName(), filter_column_name);
|
||||
|
||||
if (remove_filter_column)
|
||||
|
||||
@@ -130,6 +130,16 @@ const std::vector<String> & ColumnsSubstreams::getColumnSubstreams(size_t column
|
||||
return columns_substreams[column_position].second;
|
||||
}
|
||||
|
||||
const std::vector<String> * ColumnsSubstreams::tryGetColumnSubstreams(const String & column_name) const
|
||||
{
|
||||
for (const auto & [name, substreams] : columns_substreams)
|
||||
{
|
||||
if (name == column_name)
|
||||
return &substreams;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ColumnsSubstreams ColumnsSubstreams::merge(const ColumnsSubstreams & left, const ColumnsSubstreams & right, const std::vector<String> & columns_order)
|
||||
{
|
||||
std::unordered_map<std::string_view, size_t> left_column_to_position;
|
||||
|
||||
@@ -36,6 +36,9 @@ public:
|
||||
|
||||
const std::vector<String> & getColumnSubstreams(size_t column_position) const;
|
||||
|
||||
/// Returns the recorded substreams for a column by name, or nullptr if the column is not present.
|
||||
const std::vector<String> * tryGetColumnSubstreams(const String & column_name) const;
|
||||
|
||||
void writeText(WriteBuffer & buf) const;
|
||||
void readText(ReadBuffer & buf);
|
||||
String toString() const;
|
||||
|
||||
@@ -449,6 +449,20 @@ void MergeTreeDataPartWriterCompact::finishDataSerialization(bool sync)
|
||||
|
||||
plain_file->finalize();
|
||||
marks_file->finalize();
|
||||
|
||||
/// Release the data (`data.bin`) and marks (`data.cmrk*`) file descriptors now that everything
|
||||
/// has been flushed and synced. Otherwise the writer keeps these handles open until it is
|
||||
/// destroyed, which happens only after the part's temporary directory has been renamed to its
|
||||
/// final name. Renaming a directory that still has open file descriptors inside fails on
|
||||
/// filesystems backed by Windows (WSL, CIFS/SMB, Docker Desktop bind mounts).
|
||||
/// See https://github.com/ClickHouse/ClickHouse/issues/56288.
|
||||
///
|
||||
/// Only plain_file and marks_file own the file descriptors. The wrapper buffers
|
||||
/// (plain_hashing, streams_by_codec, marks_*_hashing, marks_compressor) were already finalized in
|
||||
/// fillDataChecksums, and a finalized WriteBuffer never touches its underlying buffer on
|
||||
/// destruction, so releasing the file streams here is safe even though the wrappers outlive them.
|
||||
plain_file = nullptr;
|
||||
marks_file = nullptr;
|
||||
}
|
||||
|
||||
static void fillIndexGranularityImpl(
|
||||
@@ -591,7 +605,14 @@ void MergeTreeDataPartWriterCompact::cancel() noexcept
|
||||
|
||||
plain_hashing.cancel();
|
||||
|
||||
plain_file->cancel();
|
||||
/// plain_file and marks_file may already be released: finishDataSerialization resets them as
|
||||
/// soon as the data is flushed and synced, before the part is committed. cancel() can still run
|
||||
/// afterwards (e.g. MergeTreeTemporaryPart::cancel from the sink destructor when a quorum INSERT
|
||||
/// finishes the part but the subsequent quorum wait throws), so guard against the null streams.
|
||||
/// The wrapper buffers above were finalized, so their cancel() is a no-op and never touches the
|
||||
/// underlying file. This mirrors the null guards in MergeTreeDataPartWriterOnDisk::cancel.
|
||||
if (plain_file)
|
||||
plain_file->cancel();
|
||||
|
||||
if (marks_source_hashing)
|
||||
marks_source_hashing->cancel();
|
||||
@@ -601,7 +622,8 @@ void MergeTreeDataPartWriterCompact::cancel() noexcept
|
||||
|
||||
marks_file_hashing->cancel();
|
||||
|
||||
marks_file->cancel();
|
||||
if (marks_file)
|
||||
marks_file->cancel();
|
||||
|
||||
Base::cancel();
|
||||
}
|
||||
|
||||
@@ -390,6 +390,13 @@ void MergeTreeDataPartWriterOnDisk::finishPrimaryIndexSerialization(bool sync)
|
||||
}
|
||||
|
||||
index_file_hashing_stream = nullptr;
|
||||
|
||||
/// Release the primary index (`primary.cidx` / `primary.idx`) file descriptor now that the data
|
||||
/// has been flushed and synced, so it is not held open across the part-directory rename. See the
|
||||
/// detailed rationale in MergeTreeDataPartWriterCompact::finishDataSerialization and
|
||||
/// https://github.com/ClickHouse/ClickHouse/issues/56288. The wrapper streams above were reset
|
||||
/// first, so no live buffer references index_file_stream.
|
||||
index_file_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,11 @@ enum class ExecuteTTLType : uint8_t
|
||||
namespace MutationHelpers
|
||||
{
|
||||
|
||||
/// Placeholder substream that `getColumnsForNewDataPart` records for a column that will be written
|
||||
/// later by the mutation and is therefore not yet present in the part. It is not a real stream and
|
||||
/// must never be resolved against the source part's checksums (a part may happen to contain a real
|
||||
/// column whose name collides with this sentinel).
|
||||
static const String NOT_YET_WRITTEN_COLUMN_SUBSTREAM_PLACEHOLDER = "dummy";
|
||||
|
||||
static bool haveMutationsOfDynamicColumns(const MergeTreeData::DataPartPtr & data_part, const MutationCommands & commands)
|
||||
{
|
||||
@@ -150,6 +155,36 @@ static bool haveMutationsOfDynamicColumns(const MergeTreeData::DataPartPtr & dat
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Wide parts written before `columns_substreams.txt` was introduced (in 25.8) can contain a column
|
||||
/// with a dynamic structure (`Dynamic`, `JSON`, ...) whose data-dependent substreams (`variant_discr`,
|
||||
/// the variant element streams, ...) are not recorded anywhere we can enumerate without a
|
||||
/// deserialization state. State-less `serialization->enumerateStreams` stops after `dynamic_structure`
|
||||
/// for such a column (see `getStreamCounts`), so a partial mutation cannot account for all of its
|
||||
/// streams and could leave one neither rewritten nor hardlinked into the new part. To stay safe we
|
||||
/// rewrite the whole part in that case (the resulting part gets a `columns_substreams.txt`, so later
|
||||
/// mutations can take the partial path again). The file is also discarded when found corrupted, which
|
||||
/// lands here too.
|
||||
///
|
||||
/// The guard is `hasDynamicStructure`, not the broader `hasDynamicSubcolumns`: only a data-dependent
|
||||
/// dynamic structure (`Dynamic`, `JSON`) makes state-less enumeration incomplete. A plain `Map` (and a
|
||||
/// plain `Variant`) reports `hasDynamicSubcolumns` too, but its serialization enumerates all physical
|
||||
/// streams without a column/state, so forcing a full rewrite for it would be needless (it would turn a
|
||||
/// cheap single-column mutation of an old part into a rewrite of all the `Map` data).
|
||||
static bool hasDynamicColumnsWithoutRecordedSubstreams(const MergeTreeData::DataPartPtr & data_part)
|
||||
{
|
||||
if (!isWidePart(data_part))
|
||||
return false;
|
||||
|
||||
const auto & columns_substreams = data_part->getColumnsSubstreams();
|
||||
for (const auto & column : data_part->getColumns())
|
||||
{
|
||||
if (column.type->hasDynamicStructure() && !columns_substreams.tryGetColumnSubstreams(column.name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static UInt64 getExistingRowsCount(const Block & block)
|
||||
{
|
||||
auto column = block.getByName(RowExistsColumn::name).column;
|
||||
@@ -208,7 +243,8 @@ static void splitAndModifyMutationCommands(
|
||||
auto part_columns = part->getColumnsDescription();
|
||||
const auto & table_columns = metadata_snapshot->getColumns();
|
||||
|
||||
if (haveMutationsOfDynamicColumns(part, commands) || !isWidePart(part) || !isFullPartStorage(part->getDataPartStorage()))
|
||||
if (haveMutationsOfDynamicColumns(part, commands) || hasDynamicColumnsWithoutRecordedSubstreams(part)
|
||||
|| !isWidePart(part) || !isFullPartStorage(part->getDataPartStorage()))
|
||||
{
|
||||
NameSet mutated_columns;
|
||||
NameSet dropped_columns;
|
||||
@@ -828,7 +864,7 @@ getColumnsForNewDataPart(
|
||||
if (fill_columns_substreams)
|
||||
{
|
||||
new_columns_substreams.addColumn(it->name);
|
||||
new_columns_substreams.addSubstreamToLastColumn("dummy");
|
||||
new_columns_substreams.addSubstreamToLastColumn(NOT_YET_WRITTEN_COLUMN_SUBSTREAM_PLACEHOLDER);
|
||||
}
|
||||
|
||||
++it;
|
||||
@@ -986,9 +1022,31 @@ static std::unordered_map<String, size_t> getStreamCounts(
|
||||
const Names & column_names)
|
||||
{
|
||||
std::unordered_map<String, size_t> stream_counts;
|
||||
const auto & columns_substreams = data_part->getColumnsSubstreams();
|
||||
|
||||
for (const auto & column_name : column_names)
|
||||
{
|
||||
/// When columns_substreams.txt is available, prefer its recorded substreams over
|
||||
/// enumerateStreams. The file is the ground truth of what streams exist on disk, and
|
||||
/// for columns with a data-dependent dynamic structure (Dynamic, JSON) a state-less
|
||||
/// enumerateStreams is incomplete: it stops after `dynamic_structure` and never reports
|
||||
/// data-dependent substreams like `variant_discr`.
|
||||
const auto * recorded_substreams = columns_substreams.tryGetColumnSubstreams(column_name);
|
||||
|
||||
/// A not-yet-written column in a new part carries only a single placeholder substream
|
||||
/// (see getColumnsForNewDataPart). It has no real streams on disk yet, so we fall back
|
||||
/// to enumerateStreams to preserve correct shared-stream accounting for regular columns
|
||||
/// (e.g. Nested array sizes).
|
||||
if (recorded_substreams && !(recorded_substreams->size() == 1 && (*recorded_substreams)[0] == NOT_YET_WRITTEN_COLUMN_SUBSTREAM_PLACEHOLDER))
|
||||
{
|
||||
for (const auto & substream : *recorded_substreams)
|
||||
{
|
||||
if (auto stream_name = IMergeTreeDataPart::getStreamNameOrHash(substream, ".bin", source_part_checksums))
|
||||
++stream_counts[*stream_name];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto serialization = data_part->tryGetSerialization(column_name))
|
||||
{
|
||||
auto callback = [&](const ISerialization::SubstreamPath & substream_path)
|
||||
@@ -3486,6 +3544,7 @@ bool MutateTask::prepare()
|
||||
/// Also currently mutations of types with dynamic subcolumns in Wide part are possible only by
|
||||
/// rewriting the whole part.
|
||||
if (MutationHelpers::haveMutationsOfDynamicColumns(ctx->source_part, ctx->commands_for_part)
|
||||
|| MutationHelpers::hasDynamicColumnsWithoutRecordedSubstreams(ctx->source_part)
|
||||
|| !isWidePart(ctx->source_part)
|
||||
|| !isFullPartStorage(ctx->source_part->getDataPartStorage())
|
||||
|| (ctx->interpreter && ctx->interpreter->isAffectingAllColumns()))
|
||||
|
||||
@@ -587,9 +587,7 @@ static void writeMetadataFiles(
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(metadata_object, oss, 4);
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(metadata_object, 4);
|
||||
|
||||
auto buffer_metadata = object_storage->writeObject(
|
||||
StoredObject(path_resolver.resolve(generated_metadata_info.path)),
|
||||
|
||||
@@ -773,9 +773,7 @@ ExpireSnapshotsResult expireSnapshots(
|
||||
|
||||
updateMetadataForExpiration(metadata, expired_ref_names, partition.retained_snapshots, partition.expired_snapshot_ids);
|
||||
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(metadata, oss, 4);
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(metadata, 4);
|
||||
auto metadata_info = filename_generator.generateMetadataPathWithInfo();
|
||||
auto hint_path = filename_generator.generateVersionHint();
|
||||
if (!writeMetadataFileAndVersionHint(
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <Columns/ColumnConst.h>
|
||||
#include <Columns/ColumnSet.h>
|
||||
#include <Core/UUID.h>
|
||||
@@ -157,9 +156,7 @@ namespace
|
||||
{
|
||||
String dumpMetadataObjectToString(const Poco::JSON::Object::Ptr & metadata_object)
|
||||
{
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(metadata_object, oss);
|
||||
return removeEscapedSlashes(oss.str());
|
||||
return stringifyJSON(metadata_object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1244,8 +1241,12 @@ void IcebergMetadata::addDeleteTransformers(
|
||||
{
|
||||
NameAndTypePair name_and_type
|
||||
= persistent_components.schema_processor->getFieldCharacteristics(delete_file.schema_id, col_id);
|
||||
block_for_set.insert(ColumnWithTypeAndName(name_and_type.type, name_and_type.name));
|
||||
equality_indexes_delete_file.push_back(delete_file_header.getPositionByName(name_and_type.name));
|
||||
size_t position_in_delete_file = delete_file_header.getPositionByName(name_and_type.name);
|
||||
/// Take the type from the delete file header rather than from the table schema:
|
||||
/// the nullability of a column in the delete file may differ from its nullability
|
||||
/// in the table schema, and the columns read below have exactly the header's types.
|
||||
block_for_set.insert(ColumnWithTypeAndName(delete_file_header.getByPosition(position_in_delete_file).type, name_and_type.name));
|
||||
equality_indexes_delete_file.push_back(position_in_delete_file);
|
||||
}
|
||||
/// Then we read the content of the delete file.
|
||||
auto mutable_columns_for_set = block_for_set.cloneEmptyColumns();
|
||||
|
||||
@@ -64,8 +64,10 @@
|
||||
#include <Encoder.hh>
|
||||
#include <Generic.hh>
|
||||
#include <GenericDatum.hh>
|
||||
#include <Schema.hh>
|
||||
#include <Specific.hh>
|
||||
#include <Stream.hh>
|
||||
#include <Types.hh>
|
||||
#include <ValidSchema.hh>
|
||||
|
||||
#include <Poco/JSON/Object.h>
|
||||
@@ -219,6 +221,13 @@ String removeEscapedSlashes(const String & json_str)
|
||||
return result;
|
||||
}
|
||||
|
||||
String stringifyJSON(const Poco::Dynamic::Var & json, unsigned indent)
|
||||
{
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(json, oss, indent);
|
||||
return removeEscapedSlashes(oss.str());
|
||||
}
|
||||
|
||||
static void extendSchemaForPartitions(
|
||||
String & schema,
|
||||
const std::vector<String> & partition_columns,
|
||||
@@ -234,10 +243,7 @@ static void extendSchemaForPartitions(
|
||||
partition_fields->add(field);
|
||||
}
|
||||
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(partition_fields, oss);
|
||||
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(partition_fields);
|
||||
|
||||
std::string from = "#";
|
||||
size_t start_pos = schema.find(from);
|
||||
@@ -247,6 +253,43 @@ static void extendSchemaForPartitions(
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void setVersionedField(avro::GenericRecord & rec, const auto & value, const String & field_name)
|
||||
{
|
||||
size_t field_index = rec.fieldIndex(field_name);
|
||||
const avro::NodePtr & field_schema = rec.schema()->leafAt(static_cast<UInt32>(field_index));
|
||||
|
||||
if (field_schema->type() == avro::AVRO_UNION)
|
||||
{
|
||||
avro::GenericUnion field(field_schema);
|
||||
field.selectBranch(1);
|
||||
field.datum() = avro::GenericDatum(value);
|
||||
rec.fieldAt(field_index) = avro::GenericDatum(field_schema, field);
|
||||
}
|
||||
else
|
||||
{
|
||||
rec.fieldAt(field_index) = avro::GenericDatum(value);
|
||||
}
|
||||
}
|
||||
|
||||
Poco::JSON::Object::Ptr getCurrentSchema(const Poco::JSON::Object::Ptr & metadata)
|
||||
{
|
||||
Int32 current_schema_id = metadata->getValue<Int32>(Iceberg::f_current_schema_id);
|
||||
auto schemas = metadata->getArray(Iceberg::f_schemas);
|
||||
for (size_t i = 0; i < schemas->size(); ++i)
|
||||
{
|
||||
auto schema = schemas->getObject(static_cast<UInt32>(i));
|
||||
if (schema->getValue<Int32>(Iceberg::f_schema_id) == current_schema_id)
|
||||
return schema;
|
||||
}
|
||||
throw Exception(
|
||||
ErrorCodes::ICEBERG_SPECIFICATION_VIOLATION,
|
||||
"Not found schema with current-schema-id {} in the schemas list",
|
||||
current_schema_id);
|
||||
}
|
||||
}
|
||||
|
||||
void generateManifestFile(
|
||||
Poco::JSON::Object::Ptr metadata,
|
||||
const std::vector<String> & partition_columns,
|
||||
@@ -282,19 +325,14 @@ void generateManifestFile(
|
||||
if (root_schema->type() != avro::AVRO_RECORD)
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Iceberg manifest file schema must be record");
|
||||
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
int current_schema_id = metadata->getValue<Int32>(Iceberg::f_current_schema_id);
|
||||
Poco::JSON::Stringifier::stringify(metadata->getArray(Iceberg::f_schemas)->getObject(current_schema_id), oss, 4);
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(getCurrentSchema(metadata), 4);
|
||||
|
||||
auto adapter = std::make_unique<OutputStreamWriteBufferAdapter>(buf);
|
||||
avro::DataFileWriter<avro::GenericDatum> writer(std::move(adapter), schema);
|
||||
writer.setMetadata(Iceberg::f_schema, json_representation);
|
||||
writer.setMetadata(Iceberg::f_format_version, std::to_string(version));
|
||||
|
||||
std::ostringstream oss_partition_spec; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(partition_spec->getArray(Iceberg::f_fields), oss_partition_spec);
|
||||
writer.setMetadata(Iceberg::f_partition_spec, oss_partition_spec.str());
|
||||
writer.setMetadata(Iceberg::f_partition_spec, stringifyJSON(partition_spec->getArray(Iceberg::f_fields)));
|
||||
writer.setMetadata(Iceberg::f_partition_spec_id, std::to_string(partition_spec_id));
|
||||
writer.setMetadata(Iceberg::f_format_version, std::to_string(version));
|
||||
for (size_t file_idx = 0; file_idx < data_file_names.size(); ++file_idx)
|
||||
@@ -306,34 +344,14 @@ void generateManifestFile(
|
||||
manifest.field(Iceberg::f_status) = avro::GenericDatum(1);
|
||||
Int64 snapshot_id = new_snapshot->getValue<Int64>(Iceberg::f_metadata_snapshot_id);
|
||||
|
||||
auto set_versioned_field = [&](const auto & value, const String & field_name)
|
||||
{
|
||||
if (version > 1)
|
||||
{
|
||||
size_t field_index = 0;
|
||||
if (!schema.root()->nameIndex(field_name, field_index))
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Not found field {} in schema", field_name);
|
||||
|
||||
const avro::NodePtr & union_schema = schema.root()->leafAt(static_cast<UInt32>(field_index));
|
||||
|
||||
avro::GenericUnion field(union_schema);
|
||||
field.selectBranch(1);
|
||||
field.datum() = avro::GenericDatum(value);
|
||||
manifest.field(field_name) = avro::GenericDatum(union_schema, field);
|
||||
}
|
||||
else
|
||||
{
|
||||
manifest.field(field_name) = avro::GenericDatum(value);
|
||||
}
|
||||
};
|
||||
set_versioned_field(snapshot_id, Iceberg::f_snapshot_id);
|
||||
setVersionedField(manifest, snapshot_id, Iceberg::f_snapshot_id);
|
||||
|
||||
if (version > 1)
|
||||
{
|
||||
Int64 sequence_number = user_defined_sequence_number.value_or(new_snapshot->getValue<Int64>(Iceberg::f_metadata_sequence_number));
|
||||
|
||||
set_versioned_field(sequence_number, Iceberg::f_sequence_number);
|
||||
set_versioned_field(sequence_number, Iceberg::f_file_sequence_number);
|
||||
setVersionedField(manifest, sequence_number, Iceberg::f_sequence_number);
|
||||
setVersionedField(manifest, sequence_number, Iceberg::f_file_sequence_number);
|
||||
}
|
||||
avro::GenericRecord & data_file = manifest.field(Iceberg::f_data_file).value<avro::GenericRecord>();
|
||||
if (version > 1)
|
||||
@@ -508,38 +526,18 @@ void generateManifestList(
|
||||
entry.field(Iceberg::f_min_sequence_number) = new_snapshot->getValue<Int64>(Iceberg::f_metadata_sequence_number);
|
||||
}
|
||||
|
||||
auto set_versioned_field = [&](const auto & value, const String & field_name)
|
||||
{
|
||||
if (version == 1)
|
||||
{
|
||||
size_t field_index = 0;
|
||||
if (!schema.root()->nameIndex(field_name, field_index))
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Not found field {} in schema", field_name);
|
||||
|
||||
const avro::NodePtr & union_schema = schema.root()->leafAt(static_cast<UInt32>(field_index));
|
||||
|
||||
avro::GenericUnion field(union_schema);
|
||||
field.selectBranch(1);
|
||||
field.datum() = avro::GenericDatum(value);
|
||||
entry.field(field_name) = avro::GenericDatum(union_schema, field);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.field(field_name) = value;
|
||||
}
|
||||
};
|
||||
entry.field(Iceberg::f_added_snapshot_id) = new_snapshot->getValue<Int64>(Iceberg::f_metadata_snapshot_id);
|
||||
auto summary = new_snapshot->getObject(Iceberg::f_summary);
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
set_versioned_field(1, Iceberg::f_added_files_count);
|
||||
set_versioned_field(std::stoi(summary->getValue<String>(Iceberg::f_total_data_files)), Iceberg::f_existing_files_count);
|
||||
set_versioned_field(0, Iceberg::f_deleted_files_count);
|
||||
setVersionedField(entry, 1, Iceberg::f_added_files_count);
|
||||
setVersionedField(entry, std::stoi(summary->getValue<String>(Iceberg::f_total_data_files)), Iceberg::f_existing_files_count);
|
||||
setVersionedField(entry, 0, Iceberg::f_deleted_files_count);
|
||||
if (summary->has(Iceberg::f_added_position_deletes))
|
||||
set_versioned_field(summary->getValue<Int64>(Iceberg::f_added_position_deletes), Iceberg::f_deleted_rows_count);
|
||||
setVersionedField(entry, summary->getValue<Int64>(Iceberg::f_added_position_deletes), Iceberg::f_deleted_rows_count);
|
||||
else
|
||||
set_versioned_field(0, Iceberg::f_deleted_rows_count);
|
||||
setVersionedField(entry, 0, Iceberg::f_deleted_rows_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -555,20 +553,24 @@ void generateManifestList(
|
||||
|
||||
if (entry_content == Iceberg::FileContentType::DATA)
|
||||
{
|
||||
set_versioned_field(
|
||||
setVersionedField(
|
||||
entry,
|
||||
summary->has(Iceberg::f_added_records) ? summary->getValue<Int64>(Iceberg::f_added_records) : 0,
|
||||
Iceberg::f_added_rows_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_versioned_field(
|
||||
setVersionedField(
|
||||
entry,
|
||||
summary->has(Iceberg::f_added_position_deletes) ? summary->getValue<Int64>(Iceberg::f_added_position_deletes) : 0,
|
||||
Iceberg::f_added_rows_count);
|
||||
}
|
||||
set_versioned_field(
|
||||
setVersionedField(
|
||||
entry,
|
||||
0,
|
||||
Iceberg::f_existing_rows_count);
|
||||
set_versioned_field(0, Iceberg::f_deleted_rows_count);
|
||||
setVersionedField(entry, 0, Iceberg::f_deleted_rows_count);
|
||||
|
||||
writer.write(entry_datum);
|
||||
}
|
||||
|
||||
@@ -1075,9 +1077,7 @@ bool IcebergStorageSink::initializeMetadata()
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(metadata, oss, 4);
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(metadata, 4);
|
||||
|
||||
fiu_do_on(FailPoints::iceberg_writes_cleanup,
|
||||
{
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace DB
|
||||
|
||||
String removeEscapedSlashes(const String & json_str);
|
||||
|
||||
String stringifyJSON(const Poco::Dynamic::Var & json, unsigned indent = 0);
|
||||
|
||||
void generateManifestFile(
|
||||
Poco::JSON::Object::Ptr metadata,
|
||||
const std::vector<String> & partition_columns,
|
||||
|
||||
@@ -519,9 +519,7 @@ static bool writeMetadataFiles(
|
||||
buffer_manifest_list->finalize();
|
||||
}
|
||||
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(metadata, oss, 4);
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(metadata, 4);
|
||||
|
||||
fiu_do_on(FailPoints::iceberg_writes_cleanup,
|
||||
{
|
||||
@@ -760,9 +758,7 @@ void alter(
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(metadata, oss, 4);
|
||||
std::string json_representation = removeEscapedSlashes(oss.str());
|
||||
std::string json_representation = stringifyJSON(metadata, 4);
|
||||
|
||||
auto metadata_info = filename_generator.generateMetadataPathWithInfo();
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <config.h>
|
||||
@@ -1051,9 +1050,7 @@ std::pair<Poco::JSON::Object::Ptr, String> createEmptyMetadataFile(
|
||||
sort_orders->add(sort_order);
|
||||
new_metadata_file_content->set(Iceberg::f_sort_orders, sort_orders);
|
||||
|
||||
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
|
||||
Poco::JSON::Stringifier::stringify(new_metadata_file_content, oss, 4);
|
||||
return {new_metadata_file_content, removeEscapedSlashes(oss.str())};
|
||||
return {new_metadata_file_content, stringifyJSON(new_metadata_file_content, 4)};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -288,7 +288,7 @@ struct DataType
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Exception();
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unsupported Paimon type: {}", real_type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -160,12 +160,6 @@ EOL
|
||||
<max>200</max>
|
||||
</table_function_remote_max_addresses>
|
||||
|
||||
<!-- A fuzzed huge max_threads saturates the global thread pool, starving
|
||||
the server-liveness probe and delaying query cancellation. Cap it. -->
|
||||
<max_threads>
|
||||
<max>128</max>
|
||||
</max_threads>
|
||||
|
||||
<!-- Don't waste cycles testing the old interpreter. Spend time in the analyzer instead -->
|
||||
<enable_analyzer>
|
||||
<min>1</min>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
0
|
||||
1.2727922061357855
|
||||
0.32741954624177244
|
||||
0.32741954624177244
|
||||
1.272792
|
||||
0.32742
|
||||
0.32742
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
select polygonsDistanceCartesian([[[(0, 0),(0, 3),(1, 2.9),(2, 2.6),(2.6, 2),(2.9, 1),(3, 0),(0, 0)]]], [[[(1., 1.),(1., 4.),(4., 4.),(4., 1.),(1., 1.)]]]);
|
||||
select polygonsDistanceCartesian([[[(0, 0), (0, 0.1), (0.1, 0.1), (0.1, 0)]]], [[[(1., 1.),(1., 4.),(4., 4.),(4., 1.),(1., 1.)]]]);
|
||||
select polygonsDistanceSpherical([[[(23.725750, 37.971536)]]], [[[(4.3826169, 50.8119483)]]]);
|
||||
select round(polygonsDistanceCartesian([[[(0, 0),(0, 3),(1, 2.9),(2, 2.6),(2.6, 2),(2.9, 1),(3, 0),(0, 0)]]], [[[(1., 1.),(1., 4.),(4., 4.),(4., 1.),(1., 1.)]]]), 6);
|
||||
select round(polygonsDistanceCartesian([[[(0, 0), (0, 0.1), (0.1, 0.1), (0.1, 0)]]], [[[(1., 1.),(1., 4.),(4., 4.),(4., 1.),(1., 1.)]]]), 6);
|
||||
select round(polygonsDistanceSpherical([[[(23.725750, 37.971536)]]], [[[(4.3826169, 50.8119483)]]]), 6);
|
||||
|
||||
drop table if exists polygon_01302;
|
||||
create table polygon_01302 (x Array(Array(Array(Tuple(Float64, Float64)))), y Array(Array(Array(Tuple(Float64, Float64))))) engine=Memory();
|
||||
insert into polygon_01302 values ([[[(23.725750, 37.971536)]]], [[[(4.3826169, 50.8119483)]]]);
|
||||
select polygonsDistanceSpherical(x, y) from polygon_01302;
|
||||
select round(polygonsDistanceSpherical(x, y), 6) from polygon_01302;
|
||||
|
||||
drop table polygon_01302;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: zookeeper, no-replicated-database, no-shared-merge-tree
|
||||
# Tags: zookeeper, no-replicated-database, no-shared-merge-tree, no-darwin
|
||||
# no-replicated-database because it adds extra replicas
|
||||
# no-shared-merge-tree do something with parts on local fs
|
||||
# no-darwin: replace_long_file_name_to_hash=0 is not honored on APFS (case-insensitive), so checksums differ
|
||||
# add_minmax_index_for_numeric_columns=0: Adds extra files, which changes the hashes
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Must not have no arguments.
|
||||
Test single tokenizer argument.
|
||||
-- tokenizer must be splitByNonAlpha, ngrams, sparseGrams, splitByString or array.
|
||||
-- tokenizer must be splitByNonAlpha, ngrams, sparseGrams, splitByString, asciiCJK or array.
|
||||
-- tokenizer can be identifier or function.
|
||||
Test asciiCJK tokenizer.
|
||||
Test ngrams tokenizer.
|
||||
@@ -24,6 +24,9 @@ Test dictionary_block_frontcoding_compression argument.
|
||||
Test posting_list_codec argument.
|
||||
-- posting_list_codec must be a string.
|
||||
-- posting_list_codec must be none or bitpacking.
|
||||
Test positions argument.
|
||||
-- positions argument is experimental
|
||||
-- positions argument is must be 0 or 1
|
||||
Types are incorrect.
|
||||
Same argument appears >1 times.
|
||||
Non-existing argument.
|
||||
|
||||
@@ -57,7 +57,7 @@ ENGINE = MergeTree
|
||||
ORDER BY tuple();
|
||||
DROP TABLE tab;
|
||||
|
||||
SELECT '-- tokenizer must be splitByNonAlpha, ngrams, sparseGrams, splitByString or array.';
|
||||
SELECT '-- tokenizer must be splitByNonAlpha, ngrams, sparseGrams, splitByString, asciiCJK or array.';
|
||||
|
||||
CREATE TABLE tab
|
||||
(
|
||||
@@ -516,6 +516,49 @@ CREATE TABLE tab
|
||||
ENGINE = MergeTree
|
||||
ORDER BY tuple(); -- { serverError BAD_ARGUMENTS }
|
||||
|
||||
SELECT 'Test positions argument.';
|
||||
|
||||
SELECT '-- positions argument is experimental';
|
||||
|
||||
CREATE TABLE tab
|
||||
(
|
||||
str String,
|
||||
INDEX idx str TYPE text(tokenizer = splitByNonAlpha, positions = 1)
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY tuple(); -- { serverError SUPPORT_IS_DISABLED }
|
||||
|
||||
CREATE TABLE tab
|
||||
(
|
||||
str String,
|
||||
INDEX idx str TYPE text(tokenizer = splitByNonAlpha, positions = 1)
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY tuple()
|
||||
SETTINGS allow_experimental_text_index_positions = 1;
|
||||
|
||||
DROP TABLE tab;
|
||||
|
||||
SELECT '-- positions argument is must be 0 or 1';
|
||||
|
||||
CREATE TABLE tab
|
||||
(
|
||||
str String,
|
||||
INDEX idx str TYPE text(tokenizer = splitByNonAlpha, positions = 2)
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY tuple()
|
||||
SETTINGS allow_experimental_text_index_positions = 1; -- { serverError BAD_ARGUMENTS }
|
||||
|
||||
CREATE TABLE tab
|
||||
(
|
||||
str String,
|
||||
INDEX idx str TYPE text(tokenizer = splitByNonAlpha, positions = 'abc')
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY tuple()
|
||||
SETTINGS allow_experimental_text_index_positions = 1; -- { serverError BAD_ARGUMENTS }
|
||||
|
||||
SELECT 'Types are incorrect.';
|
||||
|
||||
CREATE TABLE tab
|
||||
|
||||
@@ -51,3 +51,48 @@ select * from simple_agg_groupArrayLastArray order by key, value;
|
||||
select * from simple_agg_groupArrayLastArray final order by key, value;
|
||||
1 [7,8,3,4,5]
|
||||
2 [7,8,6,1,2]
|
||||
-- Deserializing broken states.
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArrayLastState(1)(number) AS String
|
||||
) AS AggregateFunction(groupArrayLast(42), UInt64)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArrayLastState(1)(toString(number)) AS String
|
||||
) AS AggregateFunction(groupArrayLast(42), String)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArraySampleState(1)(number) AS String
|
||||
) AS AggregateFunction(groupArraySample(42), UInt64)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArraySampleState(1)(toString(number)) AS String
|
||||
) AS AggregateFunction(groupArraySample(42), String)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
WITH (
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArrayLastState(1)(number) AS String
|
||||
) AS AggregateFunction(groupArrayLast(42), UInt64)
|
||||
)
|
||||
FROM numbers(10)
|
||||
) AS s
|
||||
SELECT
|
||||
length(groupArrayLastMerge(42)(s))
|
||||
FROM numbers(2); -- { serverError INCORRECT_DATA }
|
||||
|
||||
@@ -34,3 +34,48 @@ system stop merges simple_agg_groupArrayLastArray;
|
||||
insert into simple_agg_groupArrayLastArray values (1, [7,8]), (2, [7,8]);
|
||||
select * from simple_agg_groupArrayLastArray order by key, value;
|
||||
select * from simple_agg_groupArrayLastArray final order by key, value;
|
||||
-- Deserializing broken states.
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArrayLastState(1)(number) AS String
|
||||
) AS AggregateFunction(groupArrayLast(42), UInt64)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArrayLastState(1)(toString(number)) AS String
|
||||
) AS AggregateFunction(groupArrayLast(42), String)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArraySampleState(1)(number) AS String
|
||||
) AS AggregateFunction(groupArraySample(42), UInt64)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArraySampleState(1)(toString(number)) AS String
|
||||
) AS AggregateFunction(groupArraySample(42), String)
|
||||
)
|
||||
FROM numbers(2)
|
||||
FORMAT Null; -- { serverError INCORRECT_DATA }
|
||||
WITH (
|
||||
SELECT
|
||||
CAST(
|
||||
CAST(
|
||||
groupArrayLastState(1)(number) AS String
|
||||
) AS AggregateFunction(groupArrayLast(42), UInt64)
|
||||
)
|
||||
FROM numbers(10)
|
||||
) AS s
|
||||
SELECT
|
||||
length(groupArrayLastMerge(42)(s))
|
||||
FROM numbers(2); -- { serverError INCORRECT_DATA }
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
0
|
||||
0
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
CREATE TABLE test_tuple (`p` DateTime, `i` int, `j` int) ENGINE = MergeTree PARTITION BY (toDate(p), i) ORDER BY j SETTINGS index_granularity = 1;
|
||||
insert into test_tuple values (1, 1, 1);
|
||||
set parallel_replicas_local_plan = 1, parallel_replicas_support_projection = 1, optimize_aggregation_in_order = 0, optimize_use_projections = 1;
|
||||
SELECT count() FROM test_tuple PREWHERE sipHash64(sipHash64(p, toString(toDate(p))), toString(toDate(p))) % -0. WHERE i > NULL settings optimize_trivial_count_query=0, optimize_use_implicit_projections=1; -- { serverError ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER }
|
||||
SELECT count() FROM test_tuple PREWHERE sipHash64(sipHash64(p, toString(toDate(p))), toString(toDate(p))) % -0. WHERE i > NULL settings optimize_trivial_count_query=0, optimize_use_implicit_projections=1;
|
||||
SELECT count() FROM test_tuple PREWHERE sipHash64(sipHash64(p, toString(toDate(p))), toString(toDate(p))) % -0. WHERE i > NULL settings optimize_trivial_count_query=0, optimize_use_implicit_projections=0;
|
||||
|
||||
@@ -1 +1 @@
|
||||
-2.5574077246549023 0.6663667453928805 1
|
||||
-2.557408 0.666367 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
drop table if exists t1;
|
||||
CREATE TABLE t1 (c0 Int32) ENGINE = MergeTree() ORDER BY c0 PARTITION BY (- (c0));
|
||||
insert into t1 values(1);
|
||||
SELECT (- ((((tan (t1.c0)))+(t1.c0)))), (cos ((sin (pow(t1.c0,t1.c0))))), ((gcd((- (t1.c0)),((t1.c0)+(t1.c0))))*((- ((- (t1.c0)))))) FROM t1 GROUP BY (sqrt ((- (t1.c0)))), t1.c0, pow((erf ((- (t1.c0)))),t1.c0);
|
||||
SELECT round((- ((((tan (t1.c0)))+(t1.c0)))), 6), round((cos ((sin (pow(t1.c0,t1.c0))))), 6), ((gcd((- (t1.c0)),((t1.c0)+(t1.c0))))*((- ((- (t1.c0)))))) FROM t1 GROUP BY (sqrt ((- (t1.c0)))), t1.c0, pow((erf ((- (t1.c0)))),t1.c0);
|
||||
drop table t1;
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
-- Tags: no-darwin
|
||||
-- no-darwin: the imprecise float parser (precise_float_parsing = 0) rounds to different least-significant digits on Darwin.
|
||||
|
||||
SELECT
|
||||
toFloat64('1.7091'),
|
||||
toFloat64('1.5008753E7'),
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
SELECT largestTriangleThreeBuckets(1)(1, nan);
|
||||
SELECT largestTriangleThreeBuckets(1)(1, nan);
|
||||
SELECT finalizeAggregation(
|
||||
CAST(
|
||||
substring(s, 1, 1) || char(0) || substring(s, 2) AS AggregateFunction(largestTriangleThreeBuckets(2), Float64, Float64)
|
||||
)
|
||||
)
|
||||
FROM (
|
||||
SELECT CAST(
|
||||
groupArrayState(number::Float64) AS String) AS s
|
||||
FROM numbers(2)
|
||||
); -- { serverError INCORRECT_DATA }
|
||||
|
||||
@@ -41,7 +41,7 @@ L1Distance(a32, a32_1): 384
|
||||
L1Distance(a16, a16_1): 384
|
||||
LinfDistance(a32, a32_1): 1
|
||||
LinfDistance(a16, a16_1): 1
|
||||
LpDistance(a32, a32_1, 5): 3.2875036590344515
|
||||
LpDistance(a16, a16_1, 5): 3.2875036590344515
|
||||
LpDistance(a32, a32_1, 5): 3.287504
|
||||
LpDistance(a16, a16_1, 5): 3.287504
|
||||
1.09375 8C3F 1000110000111111 2 16268 8C3F
|
||||
1.09375 1 1.09375 1.0859375 0
|
||||
|
||||
@@ -82,7 +82,7 @@ SELECT a32, a16, a32_1, a16_1,
|
||||
L2Distance(a32, a32_1), L2Distance(a16, a16_1),
|
||||
L1Distance(a32, a32_1), L1Distance(a16, a16_1),
|
||||
LinfDistance(a32, a32_1), LinfDistance(a16, a16_1),
|
||||
LpDistance(a32, a32_1, 5), LpDistance(a16, a16_1, 5)
|
||||
round(LpDistance(a32, a32_1, 5), 6) AS `LpDistance(a32, a32_1, 5)`, round(LpDistance(a16, a16_1, 5), 6) AS `LpDistance(a16, a16_1, 5)`
|
||||
FORMAT Vertical;
|
||||
|
||||
-- Introspection
|
||||
|
||||
@@ -13,13 +13,14 @@ ORDER BY ds;
|
||||
INSERT INTO uin_value_details (ds, uin, value) values ('2023-12-26', 105, 5), ('2023-12-26', 104, 4), ('2023-12-26', 103, 3);
|
||||
INSERT INTO uin_value_details (ds, uin, value) values ('2023-12-27', 10000001, 7), ('2023-12-27', 10000002, 3);
|
||||
|
||||
select numericIndexedVectorShortDebugString(groupNumericIndexedVectorState(uin, value)) from uin_value_details;
|
||||
-- Normalize value_type: UInt64 demangles to 'unsigned long long' on macOS libc++ (vs 'unsigned long' on Linux).
|
||||
select replaceRegexpAll(numericIndexedVectorShortDebugString(groupNumericIndexedVectorState(uin, value)), '"value_type":"unsigned long long"', '"value_type":"unsigned long"') from uin_value_details;
|
||||
select groupNumericIndexedVector(uin, value) from uin_value_details;
|
||||
select numericIndexedVectorAllValueSum(groupNumericIndexedVectorState(uin, value)) from uin_value_details;
|
||||
select numericIndexedVectorCardinality(groupNumericIndexedVectorState(uin, value)) from uin_value_details;
|
||||
|
||||
select numericIndexedVectorShortDebugString(groupNumericIndexedVectorStateIf(uin, value, ds = '2023-12-26')) from uin_value_details;
|
||||
select numericIndexedVectorShortDebugString(groupNumericIndexedVectorStateIf(uin, value, ds = '2023-12-27')) from uin_value_details;
|
||||
select replaceRegexpAll(numericIndexedVectorShortDebugString(groupNumericIndexedVectorStateIf(uin, value, ds = '2023-12-26')), '"value_type":"unsigned long long"', '"value_type":"unsigned long"') from uin_value_details;
|
||||
select replaceRegexpAll(numericIndexedVectorShortDebugString(groupNumericIndexedVectorStateIf(uin, value, ds = '2023-12-27')), '"value_type":"unsigned long long"', '"value_type":"unsigned long"') from uin_value_details;
|
||||
|
||||
select numericIndexedVectorCardinality(groupNumericIndexedVectorState('BSI', 64, 0)(uin, value)) from uin_value_details;
|
||||
select numericIndexedVectorCardinality(groupNumericIndexedVectorState('BSI', 32, 0)(uin, value)) from uin_value_details;
|
||||
|
||||
@@ -35,3 +35,6 @@ equality_ids: [2]
|
||||
file_format: PARQUET
|
||||
file_path_ends_with_parquet: 1
|
||||
file_size_positive: 1
|
||||
--- equality deletes with mismatched nullability ---
|
||||
bar
|
||||
world
|
||||
|
||||
@@ -33,3 +33,8 @@ ORDER BY equality_ids, sequence_number
|
||||
FORMAT Vertical;
|
||||
|
||||
DROP TABLE eq_deletes_t;
|
||||
|
||||
-- Equality deletes on the `data` column, which is required in the table schema
|
||||
-- but optional in the delete file schema (their nullability may legally differ).
|
||||
SELECT '--- equality deletes with mismatched nullability ---';
|
||||
SELECT data FROM icebergS3(s3_conn, filename = 'deletes_db/eq_deletes_required_table') ORDER BY data;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
26.90785650380128
|
||||
26.907857
|
||||
0
|
||||
83.10115760606212
|
||||
83.101158
|
||||
0
|
||||
0
|
||||
0
|
||||
33.41852433000025
|
||||
33.418524
|
||||
0
|
||||
-95.5
|
||||
0
|
||||
|
||||
@@ -13,8 +13,8 @@ INSERT INTO geo VALUES ('MULTILINESTRING((1 0,10 0,10 10,0 10,1 0),(4 4,5 4,5 5,
|
||||
INSERT INTO geo_dst SELECT id, readWKT(geom) FROM geo ORDER BY id;
|
||||
INSERT INTO geo_dst VALUES (6, NULL);
|
||||
|
||||
SELECT perimeterCartesian(geom) FROM geo_dst ORDER BY id;
|
||||
SELECT areaCartesian(geom) FROM geo_dst ORDER BY id;
|
||||
SELECT round(perimeterCartesian(geom), 6) FROM geo_dst ORDER BY id;
|
||||
SELECT round(areaCartesian(geom), 6) FROM geo_dst ORDER BY id;
|
||||
|
||||
CREATE TABLE variant_table
|
||||
(
|
||||
|
||||
@@ -13,13 +13,7 @@ CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
CH_CLIENT_OPTS="--allow_experimental_variant_type=1 --allow_suspicious_types_in_order_by=1 --use_variant_default_implementation_for_comparisons=0"
|
||||
# max_execution_time guards only the concurrent query loops below: if the COW
|
||||
# bug regressed, a corrupted-column query could hang, so cap it. The setup
|
||||
# phase must NOT inherit this cap -- a sanitizer host can starve a single
|
||||
# trivial INSERT past 10s, failing setup with a spurious TIMEOUT_EXCEEDED.
|
||||
CH_CLIENT_SETUP="$CLICKHOUSE_CLIENT $CH_CLIENT_OPTS"
|
||||
CH_CLIENT="$CLICKHOUSE_CLIENT $CH_CLIENT_OPTS --max_execution_time=10"
|
||||
CH_CLIENT="$CLICKHOUSE_CLIENT --allow_experimental_variant_type=1 --allow_suspicious_types_in_order_by=1 --use_variant_default_implementation_for_comparisons=0"
|
||||
|
||||
$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS test_variant_distinct"
|
||||
$CLICKHOUSE_CLIENT -q "CREATE TABLE test_variant_distinct (v1 Variant(String, UInt64, Array(UInt32)), v2 Variant(String, UInt64, Array(UInt32))) ENGINE = Memory"
|
||||
@@ -37,7 +31,7 @@ for i in $(seq 1 10); do
|
||||
inserts+="INSERT INTO test_variant_distinct VALUES ([1,2,$i], NULL);"
|
||||
inserts+="INSERT INTO test_variant_distinct VALUES (NULL, $i);"
|
||||
done
|
||||
echo "$inserts" | $CH_CLIENT_SETUP -n
|
||||
echo "$inserts" | $CH_CLIENT -n
|
||||
|
||||
# Run concurrent queries from multiple connections to trigger the race.
|
||||
# The permute optimization (ORDER BY) corrupts shared non-active variant
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
perimeterSpherical with readWKTPolygon
|
||||
0.06981051179132045
|
||||
0.069811
|
||||
areaSpherical with readWKTPolygon
|
||||
-0.0003046096848622019
|
||||
-0.000305
|
||||
perimeterCartesian with readWKTPolygon
|
||||
4
|
||||
areaCartesian with readWKTPolygon
|
||||
-1
|
||||
perimeterSpherical with readWKTRing
|
||||
0.06981051179132045
|
||||
0.069811
|
||||
perimeterSpherical with readWKTPoint
|
||||
0
|
||||
perimeterSpherical with readWKTMultiPolygon
|
||||
0.06981051179132045
|
||||
0.069811
|
||||
perimeterSpherical with readWKT (Geometry variant)
|
||||
0.06981051179132045
|
||||
0.069811
|
||||
perimeterSpherical with column alias
|
||||
[[(0,0),(1,0),(1,1),(0,1),(0,0)]] 0.06981051179132045
|
||||
[[(0,0),(1,0),(1,1),(0,1),(0,0)]] 0.069811
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
-- https://github.com/ClickHouse/ClickHouse/issues/95880
|
||||
|
||||
SELECT 'perimeterSpherical with readWKTPolygon';
|
||||
SELECT perimeterSpherical(readWKTPolygon('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'));
|
||||
SELECT round(perimeterSpherical(readWKTPolygon('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')), 6);
|
||||
|
||||
SELECT 'areaSpherical with readWKTPolygon';
|
||||
SELECT areaSpherical(readWKTPolygon('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'));
|
||||
SELECT round(areaSpherical(readWKTPolygon('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')), 6);
|
||||
|
||||
SELECT 'perimeterCartesian with readWKTPolygon';
|
||||
SELECT perimeterCartesian(readWKTPolygon('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'));
|
||||
@@ -15,16 +15,16 @@ SELECT 'areaCartesian with readWKTPolygon';
|
||||
SELECT areaCartesian(readWKTPolygon('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'));
|
||||
|
||||
SELECT 'perimeterSpherical with readWKTRing';
|
||||
SELECT perimeterSpherical(readWKTRing('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'));
|
||||
SELECT round(perimeterSpherical(readWKTRing('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')), 6);
|
||||
|
||||
SELECT 'perimeterSpherical with readWKTPoint';
|
||||
SELECT perimeterSpherical(readWKTPoint('POINT(0 0)'));
|
||||
SELECT round(perimeterSpherical(readWKTPoint('POINT(0 0)')), 6);
|
||||
|
||||
SELECT 'perimeterSpherical with readWKTMultiPolygon';
|
||||
SELECT perimeterSpherical(readWKTMultiPolygon('MULTIPOLYGON(((0 0, 1 0, 1 1, 0 1, 0 0)))'));
|
||||
SELECT round(perimeterSpherical(readWKTMultiPolygon('MULTIPOLYGON(((0 0, 1 0, 1 1, 0 1, 0 0)))')), 6);
|
||||
|
||||
SELECT 'perimeterSpherical with readWKT (Geometry variant)';
|
||||
SELECT perimeterSpherical(readWKT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'));
|
||||
SELECT round(perimeterSpherical(readWKT('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')), 6);
|
||||
|
||||
SELECT 'perimeterSpherical with column alias';
|
||||
WITH 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))' AS wkt SELECT readWKTPolygon(wkt) AS w2, perimeterSpherical(w2);
|
||||
WITH 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))' AS wkt SELECT readWKTPolygon(wkt) AS w2, round(perimeterSpherical(w2), 6);
|
||||
|
||||
@@ -86,7 +86,7 @@ SELECT count(), sum(
|
||||
|
||||
|
||||
SELECT '# Low Cardinality (Full Block, All Dictionary-Encoded, No Exceptions)';
|
||||
TRUNCATE TABLE base64; INSERT INTO base64 SELECT number AS i, if(number % 4 = 1 AND number < 1021, toFloat64(2), toFloat64(1)) AS f FROM numbers(1024);
|
||||
TRUNCATE TABLE base64; INSERT INTO base64 SELECT number AS i, if(number % 3 = 0, toFloat64(2), toFloat64(1)) AS f FROM numbers(1024);
|
||||
TRUNCATE TABLE alp64; INSERT INTO alp64 SELECT i, f FROM base64;
|
||||
TRUNCATE TABLE base32; INSERT INTO base32 SELECT i, toFloat32(f) FROM base64;
|
||||
TRUNCATE TABLE alp32; INSERT INTO alp32 SELECT i, f FROM base32;
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
prewhere
|
||||
1
|
||||
2
|
||||
row policy
|
||||
1
|
||||
2
|
||||
row policy bare column analyzer on
|
||||
1
|
||||
2
|
||||
1 20
|
||||
2 30
|
||||
row policy bare column analyzer off
|
||||
1
|
||||
2
|
||||
1 20
|
||||
2 30
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
SET enable_analyzer = 1;
|
||||
SET query_plan_remove_unused_columns = 1;
|
||||
|
||||
DROP ROW POLICY IF EXISTS p_04305 ON t_04305;
|
||||
DROP TABLE IF EXISTS t_04305;
|
||||
|
||||
CREATE TABLE t_04305
|
||||
(
|
||||
a UInt8,
|
||||
b UInt8
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY tuple();
|
||||
|
||||
INSERT INTO t_04305 VALUES (0, 10), (1, 20), (2, 30);
|
||||
|
||||
SELECT 'prewhere';
|
||||
SELECT materialize(a) AS a
|
||||
FROM t_04305
|
||||
PREWHERE a
|
||||
ORDER BY a;
|
||||
|
||||
SELECT 'row policy';
|
||||
CREATE ROW POLICY p_04305 ON t_04305 USING a TO ALL;
|
||||
|
||||
SELECT materialize(a) AS a
|
||||
FROM t_04305
|
||||
ORDER BY a;
|
||||
|
||||
SELECT 'row policy bare column analyzer on';
|
||||
SELECT a FROM t_04305 ORDER BY a SETTINGS enable_analyzer = 1;
|
||||
SELECT a, sum(b) FROM t_04305 GROUP BY a ORDER BY a SETTINGS enable_analyzer = 1;
|
||||
|
||||
SELECT 'row policy bare column analyzer off';
|
||||
SELECT a FROM t_04305 ORDER BY a SETTINGS enable_analyzer = 0;
|
||||
SELECT a, sum(b) FROM t_04305 GROUP BY a ORDER BY a SETTINGS enable_analyzer = 0;
|
||||
|
||||
DROP ROW POLICY p_04305 ON t_04305;
|
||||
DROP TABLE t_04305;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
0
|
||||
1 10
|
||||
2 20
|
||||
3 30
|
||||
10
|
||||
20
|
||||
30
|
||||
1
|
||||
2
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
3
|
||||
2
|
||||
3
|
||||
2
|
||||
3
|
||||
2
|
||||
2
|
||||
2
|
||||
3
|
||||
2
|
||||
3
|
||||
2
|
||||
3
|
||||
2
|
||||
3
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
-- Regression test for https://github.com/ClickHouse/ClickHouse/issues/106099
|
||||
-- A permissive row policy whose USING expression is a bare physical column
|
||||
-- reference made any query that reads the same column fail with
|
||||
-- LOGICAL_ERROR: "Duplicate column name <c> in row policy actions output".
|
||||
-- The bug was introduced by the position-based unused-column-removal pass
|
||||
-- in `ReadFromMergeTree` (PR #100586). The duplicate originated in
|
||||
-- `buildFilterInfo` / `generateFilterActions`, which appended every required
|
||||
-- input as a filter output without checking whether it was already there;
|
||||
-- for a bare-column filter the filter result IS the input node, so the same
|
||||
-- node ended up in the outputs twice.
|
||||
--
|
||||
-- The data set includes a row with `c0 = 0` so that the test fails not only
|
||||
-- when the LOGICAL_ERROR reappears, but also when the policy stops being
|
||||
-- enforced (e.g. if a future change accidentally bypasses the bare-column
|
||||
-- filter): the falsy `c0 = 0` row must be excluded from every output.
|
||||
SET enable_analyzer = 1;
|
||||
SET query_plan_remove_unused_columns = 1;
|
||||
|
||||
DROP TABLE IF EXISTS t_106099;
|
||||
DROP ROW POLICY IF EXISTS pol_106099 ON t_106099;
|
||||
|
||||
CREATE TABLE t_106099 (c0 Int32, c1 Int32) ENGINE = MergeTree ORDER BY tuple();
|
||||
INSERT INTO t_106099 VALUES (0, 0), (1, 10), (2, 20), (3, 30);
|
||||
|
||||
CREATE ROW POLICY pol_106099 ON t_106099 USING c0 TO ALL;
|
||||
|
||||
-- Original reproducer from the issue (used to throw LOGICAL_ERROR). The `0`
|
||||
-- row must be filtered out -- if the policy stopped being applied we would
|
||||
-- see four rows instead of three.
|
||||
SELECT c0 FROM t_106099 ORDER BY c0;
|
||||
SELECT count() FROM t_106099; -- aa
|
||||
SELECT countIf(c0 = 0) FROM t_106099; -- must be 0: the policy filters falsy rows
|
||||
|
||||
-- The same bug also affected `SELECT *` once the policy column had to be
|
||||
-- read by the query.
|
||||
SELECT * FROM t_106099 ORDER BY c0;
|
||||
|
||||
-- Selecting a column that is NOT used by the policy still gets filtered.
|
||||
SELECT c1 FROM t_106099 ORDER BY c1;
|
||||
|
||||
-- The bug was independent of `enable_analyzer` (both 0 and 1 hit it). The
|
||||
-- filter must apply under both code paths.
|
||||
SELECT c0 FROM t_106099 ORDER BY c0 SETTINGS enable_analyzer = 0;
|
||||
SELECT c0 FROM t_106099 ORDER BY c0 SETTINGS enable_analyzer = 1;
|
||||
SELECT count() FROM t_106099 SETTINGS enable_analyzer = 0;
|
||||
SELECT count() FROM t_106099 SETTINGS enable_analyzer = 1;
|
||||
|
||||
-- Wrapping the USING expression (so the filter result is no longer the bare
|
||||
-- column node) was the documented workaround and must keep working. The `0`
|
||||
-- row is still falsy under each wrapping and must be filtered out.
|
||||
DROP ROW POLICY pol_106099 ON t_106099;
|
||||
CREATE ROW POLICY pol_106099 ON t_106099 USING c0 + 0 TO ALL;
|
||||
SELECT c0 FROM t_106099 ORDER BY c0;
|
||||
SELECT count() FROM t_106099; -- a
|
||||
|
||||
DROP ROW POLICY pol_106099 ON t_106099;
|
||||
CREATE ROW POLICY pol_106099 ON t_106099 USING c0 != 0 TO ALL;
|
||||
SELECT c0 FROM t_106099 ORDER BY c0;
|
||||
SELECT count() FROM t_106099; -- b
|
||||
|
||||
DROP ROW POLICY pol_106099 ON t_106099;
|
||||
CREATE ROW POLICY pol_106099 ON t_106099 USING materialize(c0) TO ALL;
|
||||
SELECT c0 FROM t_106099 ORDER BY c0;
|
||||
SELECT count() FROM t_106099; -- c
|
||||
|
||||
DROP ROW POLICY pol_106099 ON t_106099;
|
||||
|
||||
-- Same producer-side dedup also runs for `additional_table_filters`.
|
||||
SELECT c0 FROM t_106099 ORDER BY c0 SETTINGS additional_table_filters = {'t_106099':'c0'}, enable_analyzer = 0;
|
||||
SELECT c0 FROM t_106099 ORDER BY c0 SETTINGS additional_table_filters = {'t_106099':'c0'}, enable_analyzer = 1;
|
||||
-- `count()` previously failed with `ILLEGAL_TYPE_OF_COLUMN_FOR_FILTER` because
|
||||
-- projection optimization rejected non-`UInt8` filter columns. With the
|
||||
-- lenient FilterStep type check the optimizer bails gracefully and the
|
||||
-- normal read path runs.
|
||||
SELECT count() FROM t_106099 SETTINGS additional_table_filters = {'t_106099':'c0'}, enable_analyzer = 0;
|
||||
SELECT count() FROM t_106099 SETTINGS additional_table_filters = {'t_106099':'c0'}, enable_analyzer = 1;
|
||||
|
||||
DROP TABLE t_106099;
|
||||
|
||||
DROP TABLE IF EXISTS t_106099_u8;
|
||||
DROP ROW POLICY IF EXISTS pol_106099_u8 ON t_106099_u8;
|
||||
CREATE TABLE t_106099_u8 (c0 UInt8) ENGINE = MergeTree ORDER BY tuple() SETTINGS index_granularity = 1;
|
||||
INSERT INTO t_106099_u8 VALUES (0), (2), (3);
|
||||
ALTER TABLE t_106099_u8 ADD PROJECTION p (SELECT c0 ORDER BY c0);
|
||||
ALTER TABLE t_106099_u8 MATERIALIZE PROJECTION p SETTINGS mutations_sync = 2;
|
||||
CREATE ROW POLICY pol_106099_u8 ON t_106099_u8 USING c0 TO ALL;
|
||||
-- Result must be real values, not constant `1` (the projection helper bails
|
||||
-- because of the passthrough; the read path returns the original column).
|
||||
SELECT c0 FROM t_106099_u8 ORDER BY c0 SETTINGS enable_analyzer = 0;
|
||||
SELECT c0 FROM t_106099_u8 ORDER BY c0 SETTINGS enable_analyzer = 1;
|
||||
SELECT count() FROM t_106099_u8 SETTINGS enable_analyzer = 0;
|
||||
SELECT count() FROM t_106099_u8 SETTINGS enable_analyzer = 1;
|
||||
SELECT c0 FROM t_106099_u8 ORDER BY c0 SETTINGS force_optimize_projection = 1, enable_analyzer = 0;
|
||||
SELECT c0 FROM t_106099_u8 ORDER BY c0 SETTINGS force_optimize_projection = 1, enable_analyzer = 1;
|
||||
-- Same `UInt8` projection trap via `additional_table_filters`.
|
||||
DROP ROW POLICY pol_106099_u8 ON t_106099_u8;
|
||||
SELECT c0 FROM t_106099_u8 ORDER BY c0 SETTINGS additional_table_filters = {'t_106099_u8':'c0'}, enable_analyzer = 0;
|
||||
SELECT c0 FROM t_106099_u8 ORDER BY c0 SETTINGS additional_table_filters = {'t_106099_u8':'c0'}, enable_analyzer = 1;
|
||||
DROP TABLE t_106099_u8;
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test that reading the whole JSON column with ADVANCED shared data serialization
|
||||
# does not open per-bucket data/marks/substreams files that are not needed for reading.
|
||||
# When reading the whole JSON column, only Structure (per bucket) and Copy streams are used;
|
||||
# the per-bucket Data, PathsMarks, Substreams, SubstreamsMarks, PathsSubstreamsMetadata
|
||||
# files should not have their marks loaded.
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
TABLE_NAME="test_json_file_open_${CLICKHOUSE_DATABASE}"
|
||||
NUM_BUCKETS=2
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS ${TABLE_NAME}"
|
||||
${CLICKHOUSE_CLIENT} -q "
|
||||
CREATE TABLE ${TABLE_NAME} (json JSON(max_dynamic_paths=0))
|
||||
ENGINE = MergeTree ORDER BY tuple()
|
||||
SETTINGS
|
||||
min_bytes_for_wide_part = 0,
|
||||
min_rows_for_wide_part = 0,
|
||||
ratio_of_defaults_for_sparse_serialization = 1,
|
||||
object_serialization_version = 'v3',
|
||||
object_shared_data_serialization_version = 'advanced',
|
||||
object_shared_data_serialization_version_for_zero_level_parts = 'advanced',
|
||||
object_shared_data_buckets_for_wide_part = ${NUM_BUCKETS}
|
||||
"
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "SYSTEM STOP MERGES ${TABLE_NAME}"
|
||||
|
||||
# Insert data with many paths. All go to shared data (max_dynamic_paths=0).
|
||||
${CLICKHOUSE_CLIENT} -q "
|
||||
INSERT INTO ${TABLE_NAME} FORMAT JSONAsObject
|
||||
{\"a\": 1, \"b\": 2, \"c\": 3, \"d\": 4, \"e\": 5, \"f\": 6}
|
||||
{\"a\": 10, \"b\": 20, \"c\": 30, \"d\": 40, \"e\": 50, \"f\": 60}
|
||||
{\"a\": 100, \"b\": 200, \"c\": 300, \"d\": 400, \"e\": 500, \"f\": 600}
|
||||
{\"a\": 1000, \"b\": 2000, \"c\": 3000, \"d\": 4000, \"e\": 5000, \"f\": 6000}
|
||||
"
|
||||
|
||||
# With max_dynamic_paths=0, the mark files needed to read the whole JSON column are:
|
||||
# 1 object_structure
|
||||
# NUM_BUCKETS structure files (one per bucket)
|
||||
# 3 copy streams (sizes, paths_indexes, values)
|
||||
# Total: NUM_BUCKETS + 4
|
||||
#
|
||||
# Without the fix, prefetchForColumn also opens per-bucket data streams
|
||||
# (Data, PathsMarks, Substreams, SubstreamsMarks, PathsSubstreamsMetadata = 5 per bucket),
|
||||
# giving NUM_BUCKETS * 6 + 4 total. With the fix these are skipped.
|
||||
EXPECTED_MARKS=$((NUM_BUCKETS + 4))
|
||||
|
||||
# Clear mark cache and run SELECT json with prefetch enabled, counting MarkCacheMisses.
|
||||
${CLICKHOUSE_CLIENT} -q "SYSTEM CLEAR MARK CACHE"
|
||||
|
||||
QUERY_ID=$(${CLICKHOUSE_CLIENT} -q "SELECT lower(hex(reverse(reinterpretAsString(generateUUIDv4()))))")
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query_id "${QUERY_ID}" -q "
|
||||
SELECT json FROM ${TABLE_NAME} FORMAT Null
|
||||
SETTINGS max_threads = 1, load_marks_asynchronously = 0, local_filesystem_read_prefetch = 1, remote_filesystem_read_prefetch = 1, enable_parallel_replicas = 0
|
||||
"
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "SYSTEM FLUSH LOGS query_log"
|
||||
|
||||
MARK_CACHE_MISSES=$(${CLICKHOUSE_CLIENT} -q "
|
||||
SELECT ProfileEvents['MarkCacheMisses']
|
||||
FROM system.query_log
|
||||
WHERE event_date >= yesterday()
|
||||
AND event_time >= now() - 600
|
||||
AND query_id = '${QUERY_ID}'
|
||||
AND type = 'QueryFinish'
|
||||
AND current_database = currentDatabase()
|
||||
")
|
||||
|
||||
# MarkCacheMisses should be at most EXPECTED_MARKS.
|
||||
# It can be lower if some marks were already cached, but must not be higher —
|
||||
# that would mean unnecessary per-bucket data streams were loaded.
|
||||
if [ "${MARK_CACHE_MISSES}" -le "${EXPECTED_MARKS}" ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAIL: MarkCacheMisses (${MARK_CACHE_MISSES}) > expected (${EXPECTED_MARKS})"
|
||||
fi
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS ${TABLE_NAME}"
|
||||
@@ -0,0 +1,8 @@
|
||||
5000 5000
|
||||
Array(UInt64) 500
|
||||
Decimal(18, 2) 2000
|
||||
Float64 500
|
||||
Int64 500
|
||||
Map(UInt64, UInt64) 1000
|
||||
String 500
|
||||
1
|
||||
@@ -0,0 +1,31 @@
|
||||
-- Mutations that rewrite a Dynamic column (which has data-dependent substreams such as
|
||||
-- `variant_discr`) interleaved with merges, on Wide parts. Regression coverage for the
|
||||
-- mutation stream-accounting of dynamic-structure columns; CHECK TABLE validates that
|
||||
-- every recorded substream of every part is present on disk.
|
||||
-- See https://github.com/ClickHouse/ClickHouse/issues/107561
|
||||
|
||||
DROP TABLE IF EXISTS t_dyn_mut;
|
||||
CREATE TABLE t_dyn_mut (id UInt64, s UInt64, y Dynamic(max_types=3))
|
||||
ENGINE = MergeTree ORDER BY id
|
||||
SETTINGS min_bytes_for_wide_part = 0, min_rows_for_wide_part = 0;
|
||||
|
||||
INSERT INTO t_dyn_mut SELECT number, number, number::Int64 FROM numbers(1000);
|
||||
INSERT INTO t_dyn_mut SELECT number, number, 's' || number FROM numbers(1000);
|
||||
INSERT INTO t_dyn_mut SELECT number, number, number::Float64 FROM numbers(1000);
|
||||
INSERT INTO t_dyn_mut SELECT number, number, [number] FROM numbers(1000);
|
||||
OPTIMIZE TABLE t_dyn_mut FINAL;
|
||||
|
||||
-- Mutation that rewrites the Dynamic column.
|
||||
ALTER TABLE t_dyn_mut UPDATE y = id::Decimal64(2) WHERE id % 2 = 0 SETTINGS mutations_sync = 2;
|
||||
-- Mutation that does NOT touch the Dynamic column (its streams must be hardlinked intact).
|
||||
ALTER TABLE t_dyn_mut UPDATE s = s + 1 WHERE id % 3 = 0 SETTINGS mutations_sync = 2;
|
||||
-- Type change that re-decides the variant structure.
|
||||
ALTER TABLE t_dyn_mut MODIFY COLUMN y Dynamic(max_types = 1) SETTINGS mutations_sync = 2;
|
||||
INSERT INTO t_dyn_mut SELECT number, number, map(number, number) FROM numbers(1000);
|
||||
OPTIMIZE TABLE t_dyn_mut FINAL;
|
||||
|
||||
SELECT count(), countIf(y IS NOT NULL) FROM t_dyn_mut;
|
||||
SELECT dynamicType(y) AS t, count() FROM t_dyn_mut GROUP BY t ORDER BY t;
|
||||
CHECK TABLE t_dyn_mut SETTINGS check_query_single_value_result = 1;
|
||||
|
||||
DROP TABLE t_dyn_mut;
|
||||
@@ -0,0 +1,3 @@
|
||||
1000 1000
|
||||
Int64 1000
|
||||
1
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Regression for the "dummy" placeholder substream of a not-yet-written Dynamic column.
|
||||
-- During a partial mutation `getColumnsForNewDataPart` records a placeholder substream for a column
|
||||
-- that the mutation will write but that is not yet present in the source part. The mutation
|
||||
-- stream-accounting must not resolve that placeholder against the source part's checksums: if the
|
||||
-- table happens to contain an unrelated column literally named `dummy`, resolving the placeholder
|
||||
-- to that column's real `dummy.bin` stream would mark the unchanged stream as skipped and drop it
|
||||
-- from the new part.
|
||||
-- See https://github.com/ClickHouse/ClickHouse/issues/107561
|
||||
|
||||
DROP TABLE IF EXISTS t_dyn_dummy;
|
||||
CREATE TABLE t_dyn_dummy (id UInt64, dummy String)
|
||||
ENGINE = MergeTree ORDER BY id
|
||||
SETTINGS min_bytes_for_wide_part = 0, min_rows_for_wide_part = 0;
|
||||
|
||||
INSERT INTO t_dyn_dummy SELECT number, 'value_' || number FROM numbers(1000);
|
||||
|
||||
-- Add a Dynamic column and materialize it on the existing part via a partial mutation. The mutation
|
||||
-- does not rewrite the unrelated `dummy` column, so its stream must be hardlinked into the new part
|
||||
-- intact rather than skipped because of a placeholder/name collision.
|
||||
ALTER TABLE t_dyn_dummy ADD COLUMN y Dynamic(max_types = 3) DEFAULT id::Int64 SETTINGS mutations_sync = 2;
|
||||
ALTER TABLE t_dyn_dummy MATERIALIZE COLUMN y SETTINGS mutations_sync = 2;
|
||||
|
||||
-- The unchanged `dummy` column must still be present and consistent.
|
||||
SELECT count(), countIf(dummy = 'value_' || toString(id)) FROM t_dyn_dummy;
|
||||
SELECT dynamicType(y) AS t, count() FROM t_dyn_dummy GROUP BY t ORDER BY t;
|
||||
CHECK TABLE t_dyn_dummy SETTINGS check_query_single_value_result = 1;
|
||||
|
||||
DROP TABLE t_dyn_dummy;
|
||||
@@ -0,0 +1,8 @@
|
||||
columns_substreams.txt present before: 1
|
||||
columns_substreams.txt present after delete+attach: 0
|
||||
Data after mutation:
|
||||
2000 2000 2000
|
||||
Int64 1000
|
||||
String 1000
|
||||
CHECK TABLE result: 1
|
||||
columns_substreams.txt regenerated with variant_discr: 1
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-fasttest, no-shared-merge-tree, no-object-storage
|
||||
|
||||
# Regression for a Wide part that has a Dynamic column but no columns_substreams.txt, as written by
|
||||
# servers from before that file existed (Dynamic became production-ready in 25.3, the file was added
|
||||
# to Wide parts in 25.8). For such a part the mutation stream-accounting cannot enumerate the
|
||||
# data-dependent substreams of the Dynamic column (variant_discr, ...) without a deserialization
|
||||
# state, so a partial mutation could leave one of those streams neither rewritten nor hardlinked.
|
||||
# The mutation must instead rewrite the whole part. We simulate the old part by deleting
|
||||
# columns_substreams.txt and reloading the table, then run a partial mutation that does NOT touch the
|
||||
# Dynamic column and validate the resulting part with CHECK TABLE.
|
||||
# See https://github.com/ClickHouse/ClickHouse/issues/107561
|
||||
|
||||
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CUR_DIR"/../shell_config.sh
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS t_dyn_old_part"
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "
|
||||
CREATE TABLE t_dyn_old_part (id UInt64, s UInt64, y Dynamic(max_types=3))
|
||||
ENGINE = MergeTree ORDER BY id
|
||||
SETTINGS min_bytes_for_wide_part = 0, min_rows_for_wide_part = 0;
|
||||
"
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "INSERT INTO t_dyn_old_part SELECT number, number, number::Int64 FROM numbers(1000)"
|
||||
${CLICKHOUSE_CLIENT} --query "INSERT INTO t_dyn_old_part SELECT number, number, 's' || number FROM numbers(1000)"
|
||||
${CLICKHOUSE_CLIENT} --query "OPTIMIZE TABLE t_dyn_old_part FINAL"
|
||||
|
||||
DATA_PATH=$(${CLICKHOUSE_CLIENT} --query "SELECT path FROM system.parts WHERE database = currentDatabase() AND table = 't_dyn_old_part' AND active")
|
||||
|
||||
echo -n "columns_substreams.txt present before: "
|
||||
test -f "${DATA_PATH}columns_substreams.txt" && echo 1 || echo 0
|
||||
|
||||
# Detach the table, delete columns_substreams.txt to simulate a part written before the file existed,
|
||||
# then reload it from disk.
|
||||
${CLICKHOUSE_CLIENT} --query "DETACH TABLE t_dyn_old_part"
|
||||
rm -f "${DATA_PATH}columns_substreams.txt"
|
||||
${CLICKHOUSE_CLIENT} --query "ATTACH TABLE t_dyn_old_part"
|
||||
|
||||
echo -n "columns_substreams.txt present after delete+attach: "
|
||||
test -f "${DATA_PATH}columns_substreams.txt" && echo 1 || echo 0
|
||||
|
||||
# Partial mutation that does NOT touch the Dynamic column. Because the source part has a Dynamic
|
||||
# column with no recorded substreams, the whole part must be rewritten.
|
||||
${CLICKHOUSE_CLIENT} --query "ALTER TABLE t_dyn_old_part UPDATE s = s + 1 WHERE id % 2 = 0 SETTINGS mutations_sync = 2"
|
||||
|
||||
echo "Data after mutation:"
|
||||
${CLICKHOUSE_CLIENT} --query "SELECT count(), countIf(y IS NOT NULL), countIf(s = id + (id % 2 = 0)) FROM t_dyn_old_part"
|
||||
${CLICKHOUSE_CLIENT} --query "SELECT dynamicType(y) AS t, count() FROM t_dyn_old_part GROUP BY t ORDER BY t"
|
||||
|
||||
echo -n "CHECK TABLE result: "
|
||||
${CLICKHOUSE_CLIENT} --query "CHECK TABLE t_dyn_old_part SETTINGS check_query_single_value_result = 1"
|
||||
|
||||
# The rewritten part is in the modern format again: columns_substreams.txt exists and records the
|
||||
# Dynamic column's variant_discr substream.
|
||||
NEW_DATA_PATH=$(${CLICKHOUSE_CLIENT} --query "SELECT path FROM system.parts WHERE database = currentDatabase() AND table = 't_dyn_old_part' AND active")
|
||||
echo -n "columns_substreams.txt regenerated with variant_discr: "
|
||||
grep -q "variant_discr" "${NEW_DATA_PATH}columns_substreams.txt" 2>/dev/null && echo 1 || echo 0
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "DROP TABLE t_dyn_old_part"
|
||||
@@ -0,0 +1 @@
|
||||
OK: descriptive error
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-fasttest
|
||||
|
||||
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CUR_DIR"/../shell_config.sh
|
||||
|
||||
# A Paimon schema whose second field uses an unsupported nested ROW type.
|
||||
# Previously the parser threw a bare Exception() with code OK and no message.
|
||||
# It must now report a descriptive BAD_ARGUMENTS error.
|
||||
TABLE_DIR="${CLICKHOUSE_USER_FILES_UNIQUE}/paimon_unsupported_type"
|
||||
rm -rf "${TABLE_DIR}"
|
||||
mkdir -p "${TABLE_DIR}/schema"
|
||||
|
||||
cat > "${TABLE_DIR}/schema/schema-0" <<'EOF'
|
||||
{ "version": 3, "id": 0,
|
||||
"fields": [
|
||||
{"id": 0, "name": "c0", "type": "INT"},
|
||||
{"id": 1, "name": "c1", "type": {"type": "ROW", "fields": [{"id": 2, "name": "f0", "type": "INT"}]}}
|
||||
],
|
||||
"highestFieldId": 2, "partitionKeys": [], "primaryKeys": [], "options": {}, "timeMillis": 1751900000000 }
|
||||
EOF
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "SELECT * FROM paimonLocal('${TABLE_DIR}')" 2>&1 \
|
||||
| grep -q "Unsupported Paimon type: ROW.*(BAD_ARGUMENTS)" && echo "OK: descriptive error"
|
||||
|
||||
rm -rf "${TABLE_DIR}"
|
||||
@@ -1,3 +1,5 @@
|
||||
-- Tags: no-darwin
|
||||
-- no-darwin: the imprecise float parser (precise_float_parsing = 0) rounds to different least-significant digits on Darwin.
|
||||
-- Boundary and corner-case coverage for string -> Float32/Float64 parsing, exercising both the
|
||||
-- fast (precise_float_parsing = 0) and precise (precise_float_parsing = 1) code paths.
|
||||
-- toFloat*OrNull is used so that invalid inputs yield NULL instead of aborting the query.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
no leaked descriptors
|
||||
100
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: zookeeper, no-replicated-database, no-fasttest, no-shared-merge-tree, no-ordinary-database
|
||||
# Tag zookeeper: uses ReplicatedMergeTree, so it must be skipped when Keeper is unavailable
|
||||
# (e.g. the `--no-zookeeper` lane); otherwise CREATE TABLE fails in a genuinely Keeper-less run.
|
||||
# Tag no-replicated-database: relies on a single local replica and reads its /proc fds
|
||||
# Tag no-shared-merge-tree: SharedMergeTree does not honor sleep_before_commit_local_part_in_replicated_table_ms
|
||||
# Tag no-ordinary-database: the fd scan locates the part files by the table's UUID, which is
|
||||
# the nil UUID under the Ordinary engine, so the globs would match nothing and the test
|
||||
# would pass vacuously even on an unfixed build (the part path has no UUID under Ordinary).
|
||||
|
||||
# Regression test for https://github.com/ClickHouse/ClickHouse/issues/56288
|
||||
#
|
||||
# The MergeTree part writer used to keep the data ('data.bin'), marks ('data.cmrk*')
|
||||
# and primary index ('primary.cidx') file descriptors open until the writer object was
|
||||
# destroyed, which happened only after the part's temporary directory had already been
|
||||
# renamed to its final name. Renaming a directory that still has open file descriptors
|
||||
# inside fails on filesystems backed by Windows (WSL, CIFS/SMB, Docker Desktop bind
|
||||
# mounts), breaking INSERT. On ext4/XFS the rename succeeds regardless, so we cannot
|
||||
# reproduce the failure directly; instead we assert the invariant that fixes it: once a
|
||||
# part has been committed to disk, the server holds no descriptor open *for writing* into
|
||||
# it. We must restrict the check to write descriptors because read descriptors into a
|
||||
# committed part are legitimate and expected -- prewarming the mark cache and the primary
|
||||
# key cache (and the page cache, SELECTs, merges) open the very same files O_RDONLY right
|
||||
# after the part is committed -- whereas only the writer's own leaked descriptors are the
|
||||
# bug, and the writer always opens these files O_WRONLY.
|
||||
#
|
||||
# `sleep_before_commit_local_part_in_replicated_table_ms` pauses the insert *after* the
|
||||
# part directory has been renamed but *before* the temporary part (which owns the writer
|
||||
# and its descriptors) is destroyed -- exactly the window in which the leaked descriptors
|
||||
# used to be observable.
|
||||
|
||||
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CUR_DIR"/../shell_config.sh
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "DROP TABLE IF EXISTS t_fd_leak SYNC"
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "
|
||||
CREATE TABLE t_fd_leak (a UInt64, b String)
|
||||
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/t_fd_leak', 'r1')
|
||||
ORDER BY a
|
||||
SETTINGS
|
||||
min_bytes_for_wide_part = 1000000000, -- force a compact part (single data.bin + data.cmrk*)
|
||||
min_rows_for_wide_part = 1000000000,
|
||||
sleep_before_commit_local_part_in_replicated_table_ms = 6000
|
||||
"
|
||||
|
||||
# Stop background merges so a merge cannot open the part's files for writing and be
|
||||
# mistaken for the writer's leaked descriptors.
|
||||
${CLICKHOUSE_CLIENT} -q "SYSTEM STOP MERGES t_fd_leak"
|
||||
|
||||
uuid=$(${CLICKHOUSE_CLIENT} -q "SELECT uuid FROM system.tables WHERE database = currentDatabase() AND name = 't_fd_leak'")
|
||||
|
||||
# The fd scan globs the /proc symlink targets by this UUID; without a real UUID the part
|
||||
# path is not matched and the check would pass vacuously. The no-ordinary-database tag
|
||||
# guarantees a non-nil UUID, but assert it explicitly so a future change cannot silently
|
||||
# turn the test into a no-op.
|
||||
if [ -z "$uuid" ] || [ "$uuid" = "00000000-0000-0000-0000-000000000000" ]; then
|
||||
echo "could not determine a non-nil table UUID for the fd scan" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Insert in the background; the server will pause for several seconds during the commit.
|
||||
${CLICKHOUSE_CLIENT} -q "INSERT INTO t_fd_leak SELECT number, toString(number) FROM numbers(100)" >/dev/null 2>&1 &
|
||||
insert_pid=$!
|
||||
|
||||
# While the insert is paused after the rename, look for any descriptor (across all
|
||||
# processes) still open *for writing* into this table's committed part data files.
|
||||
#
|
||||
# A genuinely leaked writer descriptor is held for the *whole* commit pause (the temporary
|
||||
# part that owns the writer is not destroyed until after
|
||||
# `sleep_before_commit_local_part_in_replicated_table_ms`), so it is observable on every
|
||||
# poll of the window. We therefore require the write descriptor to *persist* across several
|
||||
# consecutive polls before declaring a leak. A single transient sighting is not enough:
|
||||
# on a slow (sanitizer) build under load the poll can momentarily race the temp->final
|
||||
# rename or catch a reused /proc fd number, and neither of those persists.
|
||||
leaked=0
|
||||
consecutive_writes=0
|
||||
# Number of consecutive polls that must all see an open write descriptor. The real bug
|
||||
# keeps the descriptor open for the entire multi-second pause, so it is seen on every poll;
|
||||
# 3 in a row (spanning at least ~0.2s) is far longer than any sub-millisecond race window.
|
||||
required_consecutive=3
|
||||
for _ in $(seq 1 200); do
|
||||
# 'find -lname' lists every '/proc/PID/fd/FD' symlink whose target points into this
|
||||
# table's part data/index/mark files (its glob matches '/' too, so "*$uuid*" spans
|
||||
# the store path).
|
||||
write_fd_seen=0
|
||||
while read -r fd_link; do
|
||||
# Only a descriptor into a *committed* part is the bug. While a part is still being
|
||||
# written its data files live in a temporary directory ('tmp_insert_...') and the
|
||||
# writer legitimately holds them open for writing -- that is normal and is exactly
|
||||
# the descriptor that should be released by the rename. The bug is a descriptor that
|
||||
# *survives* the rename of that directory to its final committed name, so its /proc
|
||||
# symlink then resolves to the final path. Skip any target still pointing into a
|
||||
# temporary part directory; otherwise, on a slow (sanitizer) build under load, the
|
||||
# poll can catch the in-progress writer and report a spurious leak.
|
||||
target=$(readlink "$fd_link" 2>/dev/null) || continue
|
||||
case "$(basename "$(dirname "$target")")" in
|
||||
tmp_*|tmp-*|delete_tmp_*) continue ;;
|
||||
esac
|
||||
# /proc/PID/fdinfo/FD reports the open flags; the low two bits of the octal
|
||||
# 'flags' field are the access mode (0 = O_RDONLY, 1 = O_WRONLY, 2 = O_RDWR).
|
||||
# Ignore read-only descriptors (cache prewarming, the page cache, SELECTs).
|
||||
flags=$(grep -m1 '^flags:' "${fd_link/\/fd\//\/fdinfo\/}" 2>/dev/null | tr -dc '0-7')
|
||||
[ -n "$flags" ] || continue
|
||||
if [ $(( flags & 3 )) -ne 0 ]; then
|
||||
write_fd_seen=1
|
||||
break
|
||||
fi
|
||||
done < <(find /proc/[0-9]*/fd -mindepth 1 -maxdepth 1 \( \
|
||||
-lname "*${uuid}*/data.bin" \
|
||||
-o -lname "*${uuid}*/primary.cidx" \
|
||||
-o -lname "*${uuid}*/primary.idx" \
|
||||
-o -lname "*${uuid}*.cmrk[0-9]*" \) 2>/dev/null)
|
||||
if [ "$write_fd_seen" -eq 1 ]; then
|
||||
consecutive_writes=$((consecutive_writes + 1))
|
||||
if [ "$consecutive_writes" -ge "$required_consecutive" ]; then
|
||||
leaked=1
|
||||
break
|
||||
fi
|
||||
else
|
||||
# A transient sighting does not persist; reset so only a sustained leak counts.
|
||||
consecutive_writes=0
|
||||
fi
|
||||
# Stop once the insert has finished: the observation window is closed.
|
||||
kill -0 "$insert_pid" 2>/dev/null || break
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
wait "$insert_pid" 2>/dev/null
|
||||
|
||||
if [ "$leaked" -eq 0 ]; then
|
||||
echo "no leaked descriptors"
|
||||
else
|
||||
echo "leaked descriptors found"
|
||||
fi
|
||||
|
||||
${CLICKHOUSE_CLIENT} -q "SELECT count() FROM t_fd_leak"
|
||||
${CLICKHOUSE_CLIENT} -q "DROP TABLE t_fd_leak SYNC"
|
||||
@@ -0,0 +1 @@
|
||||
100
|
||||
@@ -0,0 +1,47 @@
|
||||
-- Tags: zookeeper, no-parallel, no-shared-merge-tree, no-replicated-database
|
||||
-- no-parallel: enables a server-global failpoint (replicated_merge_tree_insert_quorum_fail_0)
|
||||
-- no-shared-merge-tree: the quorum INSERT path and the failpoint are specific to ReplicatedMergeTree
|
||||
-- no-replicated-database: creates two explicit replicas (r1, r2) sharing one ZooKeeper path
|
||||
|
||||
-- Regression test for a crash introduced while fixing https://github.com/ClickHouse/ClickHouse/issues/56288.
|
||||
--
|
||||
-- MergeTreeDataPartWriterCompact::finishDataSerialization releases the data ('data.bin') and marks
|
||||
-- ('data.cmrk*') file descriptors as soon as the part is flushed and synced, before the part is
|
||||
-- committed. The writer's cancel() can still run afterwards: a quorum INSERT finalizes the part
|
||||
-- (commits it locally), and if the following quorum step fails, the sink destructor calls cancel()
|
||||
-- on the already-finished temporary part. cancel() must therefore tolerate the released (null)
|
||||
-- streams instead of dereferencing them, otherwise the original error turns into a segfault in a
|
||||
-- noexcept function during cleanup. The failpoint forces the quorum step to fail right after the
|
||||
-- part has been finalized.
|
||||
|
||||
DROP TABLE IF EXISTS t_compact_cancel_r1 SYNC;
|
||||
DROP TABLE IF EXISTS t_compact_cancel_r2 SYNC;
|
||||
|
||||
CREATE TABLE t_compact_cancel_r1 (a UInt64, b String)
|
||||
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/04405_cancel_after_finish', 'r1')
|
||||
ORDER BY a
|
||||
SETTINGS min_bytes_for_wide_part = 1000000000, min_rows_for_wide_part = 1000000000; -- force a compact part
|
||||
|
||||
CREATE TABLE t_compact_cancel_r2 (a UInt64, b String)
|
||||
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/04405_cancel_after_finish', 'r2')
|
||||
ORDER BY a
|
||||
SETTINGS min_bytes_for_wide_part = 1000000000, min_rows_for_wide_part = 1000000000;
|
||||
|
||||
-- The second replica never fetches the part, so quorum = 2 is reached but never satisfied.
|
||||
SYSTEM STOP FETCHES t_compact_cancel_r2;
|
||||
|
||||
SYSTEM ENABLE FAILPOINT replicated_merge_tree_insert_quorum_fail_0;
|
||||
|
||||
-- The compact part is finalized (the writer releases its data/marks streams), then the forced fault
|
||||
-- makes the quorum step throw, and the sink destructor cancels the finished part. Before the fix this
|
||||
-- segfaulted in MergeTreeDataPartWriterCompact::cancel.
|
||||
INSERT INTO t_compact_cancel_r1 SELECT number, toString(number) FROM numbers(100)
|
||||
SETTINGS insert_quorum = 2, insert_quorum_parallel = 0, insert_keeper_max_retries = 0, insert_keeper_fault_injection_probability = 0; -- { serverError UNKNOWN_STATUS_OF_INSERT }
|
||||
|
||||
SYSTEM DISABLE FAILPOINT replicated_merge_tree_insert_quorum_fail_0;
|
||||
|
||||
-- The server must still be alive, and the locally committed part must be readable.
|
||||
SELECT count() FROM t_compact_cancel_r1;
|
||||
|
||||
DROP TABLE t_compact_cancel_r1 SYNC;
|
||||
DROP TABLE t_compact_cancel_r2 SYNC;
|
||||
@@ -0,0 +1,6 @@
|
||||
columns_substreams.txt present before: 1
|
||||
columns_substreams.txt present after delete+attach: 0
|
||||
Data after mutation:
|
||||
1000 1000 1000
|
||||
CHECK TABLE result: 1
|
||||
Stayed on partial path (no columns_substreams.txt regenerated): 1
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-fasttest, no-shared-merge-tree, no-object-storage
|
||||
|
||||
# Companion to 04401: a Wide part with a plain Map column but no columns_substreams.txt (as written by
|
||||
# servers from before that file existed) must NOT be force-rewritten by a partial mutation of an
|
||||
# unrelated column. A basic Map serialization enumerates all of its physical streams without a
|
||||
# deserialization state (unlike Dynamic/JSON, whose data-dependent substreams require the state), so a
|
||||
# partial mutation can account for every stream and stay on the cheap partial path. We simulate the old
|
||||
# part by deleting columns_substreams.txt and reloading the table, run a partial mutation that does NOT
|
||||
# touch the Map column, validate with CHECK TABLE, and assert the rewritten part did NOT regenerate
|
||||
# columns_substreams.txt (which would have meant a needless full rewrite of all the Map data).
|
||||
# See https://github.com/ClickHouse/ClickHouse/issues/107561
|
||||
|
||||
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CUR_DIR"/../shell_config.sh
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "DROP TABLE IF EXISTS t_map_old_part"
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "
|
||||
CREATE TABLE t_map_old_part (id UInt64, s UInt64, m Map(String, String))
|
||||
ENGINE = MergeTree ORDER BY id
|
||||
SETTINGS min_bytes_for_wide_part = 0, min_rows_for_wide_part = 0;
|
||||
"
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "INSERT INTO t_map_old_part SELECT number, number, map('id', toString(number), 'k', toString(number * 2)) FROM numbers(1000)"
|
||||
${CLICKHOUSE_CLIENT} --query "OPTIMIZE TABLE t_map_old_part FINAL"
|
||||
|
||||
DATA_PATH=$(${CLICKHOUSE_CLIENT} --query "SELECT path FROM system.parts WHERE database = currentDatabase() AND table = 't_map_old_part' AND active")
|
||||
|
||||
echo -n "columns_substreams.txt present before: "
|
||||
test -f "${DATA_PATH}columns_substreams.txt" && echo 1 || echo 0
|
||||
|
||||
# Detach the table, delete columns_substreams.txt to simulate a part written before the file existed,
|
||||
# then reload it from disk.
|
||||
${CLICKHOUSE_CLIENT} --query "DETACH TABLE t_map_old_part"
|
||||
rm -f "${DATA_PATH}columns_substreams.txt"
|
||||
${CLICKHOUSE_CLIENT} --query "ATTACH TABLE t_map_old_part"
|
||||
|
||||
echo -n "columns_substreams.txt present after delete+attach: "
|
||||
test -f "${DATA_PATH}columns_substreams.txt" && echo 1 || echo 0
|
||||
|
||||
# Partial mutation that does NOT touch the Map column. The Map's streams are fully enumerable without a
|
||||
# deserialization state, so there is no correctness need to rewrite the whole part: it must stay on the
|
||||
# partial path.
|
||||
${CLICKHOUSE_CLIENT} --query "ALTER TABLE t_map_old_part UPDATE s = s + 1 WHERE id % 2 = 0 SETTINGS mutations_sync = 2"
|
||||
|
||||
echo "Data after mutation:"
|
||||
${CLICKHOUSE_CLIENT} --query "SELECT count(), countIf(s = id + (id % 2 = 0)), countIf(m['id'] = toString(id) AND m['k'] = toString(id * 2)) FROM t_map_old_part"
|
||||
|
||||
echo -n "CHECK TABLE result: "
|
||||
${CLICKHOUSE_CLIENT} --query "CHECK TABLE t_map_old_part SETTINGS check_query_single_value_result = 1"
|
||||
|
||||
# A partial mutation of a part with no columns_substreams.txt does not write one (it is only filled from
|
||||
# a non-empty source). So the absence of the file proves the part stayed on the partial path; if the Map
|
||||
# column had wrongly forced a full rewrite, the part would have regained columns_substreams.txt.
|
||||
NEW_DATA_PATH=$(${CLICKHOUSE_CLIENT} --query "SELECT path FROM system.parts WHERE database = currentDatabase() AND table = 't_map_old_part' AND active")
|
||||
echo -n "Stayed on partial path (no columns_substreams.txt regenerated): "
|
||||
test -f "${NEW_DATA_PATH}columns_substreams.txt" && echo 0 || echo 1
|
||||
|
||||
${CLICKHOUSE_CLIENT} --query "DROP TABLE t_map_old_part"
|
||||
@@ -0,0 +1,2 @@
|
||||
server alive
|
||||
failed to allocate
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-fasttest
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
# With allocator_may_return_null=1 an oversized single allocation returns null (recoverable)
|
||||
# instead of aborting, so the "server alive" query after it still runs. ASan/MSan also print a
|
||||
# benign "<Sanitizer> failed to allocate <N> bytes" warning; TSan and non-sanitizer builds
|
||||
# recover silently. Route the sanitizer log to our own file (last log_path wins, so it stays out
|
||||
# of the runner's fatal log) and normalize the warning to its build-invariant substring with
|
||||
# "grep -o" so one .reference matches every build.
|
||||
log="${CLICKHOUSE_TMP:-.}/04417_$$"
|
||||
rm -f "$log"*
|
||||
o="log_path=$log"
|
||||
ASAN_OPTIONS="${ASAN_OPTIONS:+$ASAN_OPTIONS:}$o" MSAN_OPTIONS="${MSAN_OPTIONS:+$MSAN_OPTIONS:}$o" \
|
||||
TSAN_OPTIONS="${TSAN_OPTIONS:+$TSAN_OPTIONS:}$o" UBSAN_OPTIONS="${UBSAN_OPTIONS:+$UBSAN_OPTIONS:}$o" \
|
||||
$CLICKHOUSE_LOCAL --query "SYSTEM ALLOCATE MEMORY 999999999999999999; SELECT 'server alive'"
|
||||
grep -hom1 'failed to allocate' "$log"* 2>/dev/null || echo 'failed to allocate'
|
||||
rm -f "$log"*
|
||||
@@ -0,0 +1,8 @@
|
||||
--- statistical: filter on index column only ---
|
||||
status: applicable
|
||||
source: statistical
|
||||
empirical_status: disabled
|
||||
--- statistical: filter touches non-index column, bails to applicability_only ---
|
||||
status: applicable
|
||||
source: applicability_only
|
||||
empirical_status: disabled
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tags: no-fasttest, no-replicated-database, no-random-merge-tree-settings
|
||||
# no-fasttest: column statistics (tdigest/uniq) require the full build
|
||||
# no-replicated-database: hypothetical indexes are session-scoped and not replicated
|
||||
# no-random-merge-tree-settings: test requires deterministic index_granularity
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
# shellcheck source=../shell_config.sh
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
$CLICKHOUSE_CLIENT -n -q "
|
||||
SET allow_experimental_statistics = 1;
|
||||
SET allow_statistics_optimize = 1;
|
||||
SET materialize_statistics_on_insert = 1;
|
||||
|
||||
DROP TABLE IF EXISTS t_hypo_stat_mc;
|
||||
CREATE TABLE t_hypo_stat_mc (a UInt64, b UInt64 STATISTICS(tdigest, uniq), c UInt64 STATISTICS(tdigest, uniq))
|
||||
ENGINE = MergeTree ORDER BY a
|
||||
SETTINGS index_granularity = 100, index_granularity_bytes = 0, min_bytes_for_wide_part = 0, auto_statistics_types = '';
|
||||
INSERT INTO t_hypo_stat_mc SELECT number, number % 100, number % 50 FROM numbers(10000);
|
||||
"
|
||||
|
||||
# Statistical path is used only when the filter touches just the index's columns.
|
||||
echo "--- statistical: filter on index column only ---"
|
||||
$CLICKHOUSE_CLIENT -n -q "
|
||||
SET allow_experimental_statistics = 1;
|
||||
SET allow_statistics_optimize = 1;
|
||||
CREATE HYPOTHETICAL INDEX idx_b ON t_hypo_stat_mc (b) TYPE minmax GRANULARITY 1;
|
||||
EXPLAIN WHATIF empirical = 0 SELECT * FROM t_hypo_stat_mc WHERE b < 50;
|
||||
" | grep -E '^\s+status:|^\s+source:|^\s+empirical_status:'
|
||||
|
||||
# Filter references a non-index column (c), so statistical estimation bails to applicability_only.
|
||||
echo "--- statistical: filter touches non-index column, bails to applicability_only ---"
|
||||
$CLICKHOUSE_CLIENT -n -q "
|
||||
SET allow_experimental_statistics = 1;
|
||||
SET allow_statistics_optimize = 1;
|
||||
CREATE HYPOTHETICAL INDEX idx_b ON t_hypo_stat_mc (b) TYPE minmax GRANULARITY 1;
|
||||
EXPLAIN WHATIF empirical = 0 SELECT * FROM t_hypo_stat_mc WHERE b < 50 AND c > 10;
|
||||
" | grep -E '^\s+status:|^\s+source:|^\s+empirical_status:'
|
||||
|
||||
$CLICKHOUSE_CLIENT -q "DROP TABLE IF EXISTS t_hypo_stat_mc"
|
||||
@@ -217,3 +217,71 @@ public class IcebergInsert {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## How is `eq_deletes_required_table` generated?
|
||||
|
||||
The table has a `data STRING NOT NULL` column (required in the Iceberg schema) and an
|
||||
equality delete file in which the same column is optional, so the nullability of the
|
||||
column differs between the delete file and the table schema. The delete file removes
|
||||
`hello` and `foo`, leaving 2 of 4 rows.
|
||||
|
||||
Generated with the Iceberg Java API through the PySpark JVM gateway (Spark SQL alone
|
||||
cannot write equality deletes), inside the `clickhouse/integration-tests-runner` image:
|
||||
|
||||
``` python
|
||||
import pyspark
|
||||
|
||||
spark = (
|
||||
pyspark.sql.SparkSession.builder.appName("gen")
|
||||
.config("spark.sql.catalog.spark_catalog", "org.apache.iceberg.spark.SparkSessionCatalog")
|
||||
.config("spark.sql.catalog.spark_catalog.type", "hadoop")
|
||||
.config("spark.sql.catalog.spark_catalog.warehouse", "/out/warehouse")
|
||||
.config("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions")
|
||||
.master("local")
|
||||
.getOrCreate()
|
||||
)
|
||||
|
||||
TABLE = "eq_deletes_required_table"
|
||||
spark.sql(
|
||||
f"CREATE TABLE {TABLE} (id INT NOT NULL, data STRING NOT NULL) USING iceberg"
|
||||
" TBLPROPERTIES ('format-version'='2')"
|
||||
)
|
||||
spark.sql(f"INSERT INTO {TABLE} VALUES (1, 'hello'), (2, 'world'), (3, 'foo'), (4, 'bar')")
|
||||
|
||||
# Write an equality delete file whose `data` column is optional in the file schema,
|
||||
# although it is required in the table schema.
|
||||
jvm = spark.sparkContext._jvm
|
||||
gateway = spark.sparkContext._gateway
|
||||
hadoop_conf = spark.sparkContext._jsc.hadoopConfiguration()
|
||||
|
||||
table = jvm.org.apache.iceberg.hadoop.HadoopTables(hadoop_conf).load(f"/out/warehouse/default/{TABLE}")
|
||||
schema = table.schema()
|
||||
field_id = schema.findField("data").fieldId()
|
||||
types = jvm.org.apache.iceberg.types.Types
|
||||
delete_field = types.NestedField.optional(field_id, "data", types.StringType.get())
|
||||
delete_schema = jvm.org.apache.iceberg.Schema([delete_field])
|
||||
|
||||
equality_ids = gateway.new_array(jvm.int, 1)
|
||||
equality_ids[0] = field_id
|
||||
|
||||
appender_factory = jvm.org.apache.iceberg.data.GenericAppenderFactory(
|
||||
schema, table.spec(), equality_ids, delete_schema, None
|
||||
)
|
||||
out = table.io().newOutputFile(table.locationProvider().newDataLocation("eq-delete-required-optional.parquet"))
|
||||
writer = appender_factory.newEqDeleteWriter(
|
||||
jvm.org.apache.iceberg.encryption.EncryptedFiles.plainAsEncryptedOutput(out),
|
||||
jvm.org.apache.iceberg.FileFormat.PARQUET,
|
||||
None,
|
||||
)
|
||||
for value in ["hello", "foo"]:
|
||||
record = jvm.org.apache.iceberg.data.GenericRecord.create(delete_schema)
|
||||
record.setField("data", value)
|
||||
writer.write(record)
|
||||
writer.close()
|
||||
|
||||
table.newRowDelta().addDeletes(writer.toDeleteFile()).commit()
|
||||
spark.stop()
|
||||
```
|
||||
|
||||
The resulting `/out/warehouse/default/eq_deletes_required_table` directory is copied here
|
||||
as is, except for the `.*.crc` files.
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"format-version" : 2,
|
||||
"table-uuid" : "35a5afa6-4947-4e9a-ab1a-0ae79844b538",
|
||||
"location" : "/out/warehouse/default/eq_deletes_required_table",
|
||||
"last-sequence-number" : 0,
|
||||
"last-updated-ms" : 1783354452257,
|
||||
"last-column-id" : 2,
|
||||
"current-schema-id" : 0,
|
||||
"schemas" : [ {
|
||||
"type" : "struct",
|
||||
"schema-id" : 0,
|
||||
"fields" : [ {
|
||||
"id" : 1,
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"type" : "int"
|
||||
}, {
|
||||
"id" : 2,
|
||||
"name" : "data",
|
||||
"required" : true,
|
||||
"type" : "string"
|
||||
} ]
|
||||
} ],
|
||||
"default-spec-id" : 0,
|
||||
"partition-specs" : [ {
|
||||
"spec-id" : 0,
|
||||
"fields" : [ ]
|
||||
} ],
|
||||
"last-partition-id" : 999,
|
||||
"default-sort-order-id" : 0,
|
||||
"sort-orders" : [ {
|
||||
"order-id" : 0,
|
||||
"fields" : [ ]
|
||||
} ],
|
||||
"properties" : {
|
||||
"owner" : "root",
|
||||
"write.parquet.compression-codec" : "zstd"
|
||||
},
|
||||
"current-snapshot-id" : -1,
|
||||
"refs" : { },
|
||||
"snapshots" : [ ],
|
||||
"statistics" : [ ],
|
||||
"partition-statistics" : [ ],
|
||||
"snapshot-log" : [ ],
|
||||
"metadata-log" : [ ]
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"format-version" : 2,
|
||||
"table-uuid" : "35a5afa6-4947-4e9a-ab1a-0ae79844b538",
|
||||
"location" : "/out/warehouse/default/eq_deletes_required_table",
|
||||
"last-sequence-number" : 1,
|
||||
"last-updated-ms" : 1783354456558,
|
||||
"last-column-id" : 2,
|
||||
"current-schema-id" : 0,
|
||||
"schemas" : [ {
|
||||
"type" : "struct",
|
||||
"schema-id" : 0,
|
||||
"fields" : [ {
|
||||
"id" : 1,
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"type" : "int"
|
||||
}, {
|
||||
"id" : 2,
|
||||
"name" : "data",
|
||||
"required" : true,
|
||||
"type" : "string"
|
||||
} ]
|
||||
} ],
|
||||
"default-spec-id" : 0,
|
||||
"partition-specs" : [ {
|
||||
"spec-id" : 0,
|
||||
"fields" : [ ]
|
||||
} ],
|
||||
"last-partition-id" : 999,
|
||||
"default-sort-order-id" : 0,
|
||||
"sort-orders" : [ {
|
||||
"order-id" : 0,
|
||||
"fields" : [ ]
|
||||
} ],
|
||||
"properties" : {
|
||||
"owner" : "root",
|
||||
"write.parquet.compression-codec" : "zstd"
|
||||
},
|
||||
"current-snapshot-id" : 597133758536090507,
|
||||
"refs" : {
|
||||
"main" : {
|
||||
"snapshot-id" : 597133758536090507,
|
||||
"type" : "branch"
|
||||
}
|
||||
},
|
||||
"snapshots" : [ {
|
||||
"sequence-number" : 1,
|
||||
"snapshot-id" : 597133758536090507,
|
||||
"timestamp-ms" : 1783354456558,
|
||||
"summary" : {
|
||||
"operation" : "append",
|
||||
"spark.app.id" : "local-1783354446783",
|
||||
"added-data-files" : "1",
|
||||
"added-records" : "4",
|
||||
"added-files-size" : "684",
|
||||
"changed-partition-count" : "1",
|
||||
"total-records" : "4",
|
||||
"total-files-size" : "684",
|
||||
"total-data-files" : "1",
|
||||
"total-delete-files" : "0",
|
||||
"total-position-deletes" : "0",
|
||||
"total-equality-deletes" : "0",
|
||||
"engine-version" : "3.5.5",
|
||||
"app-id" : "local-1783354446783",
|
||||
"engine-name" : "spark",
|
||||
"iceberg-version" : "Apache Iceberg 1.8.1 (commit 9ce0fcf0af7becf25ad9fc996c3bad2afdcfd33d)"
|
||||
},
|
||||
"manifest-list" : "/out/warehouse/default/eq_deletes_required_table/metadata/snap-597133758536090507-1-183cb62a-4751-4276-93b1-989e617b5d04.avro",
|
||||
"schema-id" : 0
|
||||
} ],
|
||||
"statistics" : [ ],
|
||||
"partition-statistics" : [ ],
|
||||
"snapshot-log" : [ {
|
||||
"timestamp-ms" : 1783354456558,
|
||||
"snapshot-id" : 597133758536090507
|
||||
} ],
|
||||
"metadata-log" : [ {
|
||||
"timestamp-ms" : 1783354452257,
|
||||
"metadata-file" : "/out/warehouse/default/eq_deletes_required_table/metadata/v1.metadata.json"
|
||||
} ]
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"format-version" : 2,
|
||||
"table-uuid" : "35a5afa6-4947-4e9a-ab1a-0ae79844b538",
|
||||
"location" : "/out/warehouse/default/eq_deletes_required_table",
|
||||
"last-sequence-number" : 2,
|
||||
"last-updated-ms" : 1783354456969,
|
||||
"last-column-id" : 2,
|
||||
"current-schema-id" : 0,
|
||||
"schemas" : [ {
|
||||
"type" : "struct",
|
||||
"schema-id" : 0,
|
||||
"fields" : [ {
|
||||
"id" : 1,
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"type" : "int"
|
||||
}, {
|
||||
"id" : 2,
|
||||
"name" : "data",
|
||||
"required" : true,
|
||||
"type" : "string"
|
||||
} ]
|
||||
} ],
|
||||
"default-spec-id" : 0,
|
||||
"partition-specs" : [ {
|
||||
"spec-id" : 0,
|
||||
"fields" : [ ]
|
||||
} ],
|
||||
"last-partition-id" : 999,
|
||||
"default-sort-order-id" : 0,
|
||||
"sort-orders" : [ {
|
||||
"order-id" : 0,
|
||||
"fields" : [ ]
|
||||
} ],
|
||||
"properties" : {
|
||||
"owner" : "root",
|
||||
"write.parquet.compression-codec" : "zstd"
|
||||
},
|
||||
"current-snapshot-id" : 667782107838695408,
|
||||
"refs" : {
|
||||
"main" : {
|
||||
"snapshot-id" : 667782107838695408,
|
||||
"type" : "branch"
|
||||
}
|
||||
},
|
||||
"snapshots" : [ {
|
||||
"sequence-number" : 1,
|
||||
"snapshot-id" : 597133758536090507,
|
||||
"timestamp-ms" : 1783354456558,
|
||||
"summary" : {
|
||||
"operation" : "append",
|
||||
"spark.app.id" : "local-1783354446783",
|
||||
"added-data-files" : "1",
|
||||
"added-records" : "4",
|
||||
"added-files-size" : "684",
|
||||
"changed-partition-count" : "1",
|
||||
"total-records" : "4",
|
||||
"total-files-size" : "684",
|
||||
"total-data-files" : "1",
|
||||
"total-delete-files" : "0",
|
||||
"total-position-deletes" : "0",
|
||||
"total-equality-deletes" : "0",
|
||||
"engine-version" : "3.5.5",
|
||||
"app-id" : "local-1783354446783",
|
||||
"engine-name" : "spark",
|
||||
"iceberg-version" : "Apache Iceberg 1.8.1 (commit 9ce0fcf0af7becf25ad9fc996c3bad2afdcfd33d)"
|
||||
},
|
||||
"manifest-list" : "/out/warehouse/default/eq_deletes_required_table/metadata/snap-597133758536090507-1-183cb62a-4751-4276-93b1-989e617b5d04.avro",
|
||||
"schema-id" : 0
|
||||
}, {
|
||||
"sequence-number" : 2,
|
||||
"snapshot-id" : 667782107838695408,
|
||||
"parent-snapshot-id" : 597133758536090507,
|
||||
"timestamp-ms" : 1783354456969,
|
||||
"summary" : {
|
||||
"operation" : "delete",
|
||||
"added-equality-delete-files" : "1",
|
||||
"added-delete-files" : "1",
|
||||
"added-files-size" : "489",
|
||||
"added-equality-deletes" : "2",
|
||||
"changed-partition-count" : "1",
|
||||
"total-records" : "4",
|
||||
"total-files-size" : "1173",
|
||||
"total-data-files" : "1",
|
||||
"total-delete-files" : "1",
|
||||
"total-position-deletes" : "0",
|
||||
"total-equality-deletes" : "2",
|
||||
"engine-version" : "3.5.5",
|
||||
"app-id" : "local-1783354446783",
|
||||
"engine-name" : "spark",
|
||||
"iceberg-version" : "Apache Iceberg 1.8.1 (commit 9ce0fcf0af7becf25ad9fc996c3bad2afdcfd33d)"
|
||||
},
|
||||
"manifest-list" : "/out/warehouse/default/eq_deletes_required_table/metadata/snap-667782107838695408-1-373fb659-dd36-47eb-81a0-32bbe12c0e09.avro",
|
||||
"schema-id" : 0
|
||||
} ],
|
||||
"statistics" : [ ],
|
||||
"partition-statistics" : [ ],
|
||||
"snapshot-log" : [ {
|
||||
"timestamp-ms" : 1783354456558,
|
||||
"snapshot-id" : 597133758536090507
|
||||
}, {
|
||||
"timestamp-ms" : 1783354456969,
|
||||
"snapshot-id" : 667782107838695408
|
||||
} ],
|
||||
"metadata-log" : [ {
|
||||
"timestamp-ms" : 1783354452257,
|
||||
"metadata-file" : "/out/warehouse/default/eq_deletes_required_table/metadata/v1.metadata.json"
|
||||
}, {
|
||||
"timestamp-ms" : 1783354456558,
|
||||
"metadata-file" : "/out/warehouse/default/eq_deletes_required_table/metadata/v2.metadata.json"
|
||||
} ]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user