Repository: ClickHouse/ClickHouse (2026-03-24T12:42:25Z)

Issue:
Logical error: 'Inconsistent AST formatting: the query: (STID: 1941-1bfa)
_Important: This issue was automatically generated and is used by CI for matching failures. DO NOT modify the body content. DO NOT remove labels._

Test name: Logical error: 'Inconsistent AST formatting: the query: (STID: 1941-1bfa)
CI report: [BuzzHouse (amd_debug)](https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=99941&sha=b30ad6217288d26cc144c24e9f5fe5cda67a2769&name_0=PR&name_1=BuzzHouse%20%28amd_debug%29)
Failing test history: [cidb](https://play.clickhouse.com/play?user=play&run=1#V0lUSAogICAgOTAgQVMgaW50ZXJ2YWxfZGF5cwpTRUxFQ1QKICAgIHRvU3RhcnRPZkRheShjaGVja19zdGFydF90aW1lKSBBUyBkYXksCiAgICBjb3VudCgpIEFTIGZhaWx1cmVzLAogICAgZ3JvdXBVbmlxQXJyYXkocHVsbF9yZXF1ZXN0X251bWJlcikgQVMgcHJzLAogICAgYW55KHJlcG9ydF91cmwpIEFTIHJlcG9ydF91cmwKRlJPTSBjaGVja3MKV0hFUkUgKG5vdygpIC0gdG9JbnRlcnZhbERheShpbnRlcnZhbF9kYXlzKSkgPD0gY2hlY2tfc3RhcnRfdGltZQogICAgQU5EIHRlc3RfbmFtZSA9ICdMb2dpY2FsIGVycm9yOiAnJ0luY29uc2lzdGVudCBBU1QgZm9ybWF0dGluZzogdGhlIHF1ZXJ5OiAoU1RJRDogMTk0MS0xYmZhKScKICAgIC0tIEFORCBjaGVja19uYW1lID0gJ0J1enpIb3VzZSAoYW1kX2RlYnVnKScKICAgIEFORCB0ZXN0X3N0YXR1cyBJTiAoJ0ZBSUwnLCAnRVJST1InKQogICAgQU5EIChwdWxsX3JlcXVlc3RfbnVtYmVyID0gMCBPUiBiYXNlX3JlZiBJTiAoJ21hc3RlcicpKQogICAgQU5EIHRlc3RfY29udGV4dF9yYXcgTElLRSAnJUV4Y2VwdGlvbjolJwpHUk9VUCBCWSBkYXkKT1JERVIgQlkgZGF5IERFU0MK)

Test output:
```
Error:
Logical error: 'Inconsistent AST formatting: the query:
WITH cte0d0 AS (SELECT c0, c1 FROM (SELECT 82, t0d1.number AS a0 FROM numbers('2149-06-06', 36704, CAST('ffffffff-ffff-ffff-ffff-ffffffffffff', 'UUID')) AS t0d1 QUALIFY and(NOT (t0d1.number IS NOT NULL), mapContains(a0[0], '叫')) LIMIT -100 BY t0d1.number SETTINGS cast_ipv4_ipv6_default_on_conversion_error = 0, remerge_sort_lowered_memory_bytes_ratio = 3.200698, check_table_dependencies = 1, s3_skip_empty_files = 1, delta_lake_insert_max_bytes_in_data_file = 0, input_format_csv_try_infer_strings_from_quoted_tuples = 0, mutations_execute_nondeterministic_on_initiator = 0, input_format_parquet_use_offset_index = 1, optimize_skip_unused_shards_rewrite_in = 1, parallel_replicas_mark_segment_size = 1024, max_final_threads = 10, allow_simdjson = 0, apply_deleted_mask = 0, max_concurrent_queries_for_all_users = 0, use_text_index_postings_cache = 1, distributed_plan_max_rows_to_broadcast = 0, prefer_global_in_and_join = 1, compile_expressions = 1, join_to_sort_maximum_table_rows = 0, max_read_buffer_size_local_fs = 7915634, max_streams_for_merge_tree_reading = 65536, query_plan_read_in_order_through_join = 1, apply_deleted_mask = 1, output_format_json_quote_64bit_integers = 1)(c0, c1)), t0d0.c0 AS a1 SELECT CAST('86', 'UInt128'), CAST('1900-01-01 00:00:00', 'DateTime64') FROM (SELECT c0 FROM (SELECT CAST('dbe916fe-0202-34e6-e421-da38d5d11941', 'UUID') AS a1 FROM d12.t67 AS t0d1 SAMPLE 171000 / 1000000 OFFSET CAST('-10', 'Int256')[-3] SETTINGS cloud_mode_engine = 3, input_format_csv_try_infer_strings_from_quoted_tuples = 1, split_parts_ranges_into_intersecting_and_non_intersecting_final = 1, input_format_csv_empty_as_default = 0, apply_prewhere_after_final = 1, async_insert_max_data_size = 10485760, max_dynamic_subcolumns_in_json_type_parsing = 2, cluster_table_function_buckets_batch_size = 1938, throw_on_error_from_cache_on_write_operations = 0, describe_include_subcolumns = 1, input_format_values_deduce_templates_of_expressions = 1, opentelemetry_trace_cpu_scheduling = 1, iceberg_snapshot_id = -1)(c0)) AS t0d0 PREWHERE 1 GROUP BY t0d0.c0 AS a2 WITH CUBE ORDER BY t0d0.c0 AS a2 ASC NULLS FIRST COLLATE 'eo' OFFSET 915.6682925973521 SETTINGS enable_multiple_prewhere_read_steps = 1, output_format_parquet_date_as_uint16 = 1, enable_shared_storage_snapshot_in_query = 1, optimize_aggregators_of_group_by_keys = 1, distributed_cache_read_alignment = 0, input_format_csv_allow_variable_number_of_columns = 1, max_bytes_before_external_sort = 0, input_format_json_ignore_unknown_keys_in_named_tuple = 1, use_page_cache_with_distributed_cache = 1, query_cache_max_size_in_bytes = 1312424, merge_tree_use_v1_object_and_dynamic_serialization = 1, enable_reads_from_query_cache = 1, max_bytes_before_external_sort = 1310242, cluster_for_parallel_replicas = 'test_cluster_one_shard_two_replicas' INTO OUTFILE '/tmp/file.data' TRUNCATE FORMAT `Null`
cannot parse query back from WITH cte0d0 AS (SELECT `c0`, `c1` FROM (SELECT 82, `t0d1`.`number` AS `a0` FROM numbers('2149-06-06', 36704, 'ffffffff-ffff-ffff-ffff-ffffffffffff'::UUID) AS t0d1 QUALIFY and((NOT `t0d1`.`number` IS NOT NULL), mapContains(`a0`[0], '\xE5\x8F\xAB')) LIMIT -100 BY `t0d1`.`number` SETTINGS cast_ipv4_ipv6_default_on_conversion_error = 0, remerge_sort_lowered_memory_bytes_ratio = 3.200698, check_table_dependencies = 1, s3_skip_empty_files = 1, delta_lake_insert_max_bytes_in_data_file = 0, input_format_csv_try_infer_strings_from_quoted_tuples = 0, mutations_execute_nondeterministic_on_initiator = 0, input_format_parquet_use_offset_index = 1, optimize_skip_unused_shards_rewrite_in = 1, parallel_replicas_mark_segment_size = 1024, max_final_threads = 10, allow_simdjson = 0, apply_deleted_mask = 0, max_concurrent_queries_for_all_users = 0, use_text_index_postings_cache = 1, distributed_plan_max_rows_to_broadcast = 0, prefer_global_in_and_join = 1, compile_expressions = 1, join_to_sort_maximum_table_rows = 0, max_read_buffer_size_local_fs = 7915634, max_streams_for_merge_tree_reading = 65536, query_plan_read_in_order_through_join = 1, apply_deleted_mask = 1, output_format_json_quote_64bit_integers = 1)(c0, c1)), `t0d0`.`c0` AS `a1` FROM (SELECT `c0` FROM (FROM d12.`t67` AS t0d1 SAMPLE 0.171000 SELECT ALL 'dbe916fe-0202-34e6-e421-da38d5d11941'::UUID AS `a1` OFFSET -10::Int256[-3] SETTINGS cloud_mode_engine = 3, input_format_csv_try_infer_strings_from_quoted_tuples = 1, split_parts_ranges_into_intersecting_and_non_intersecting_final = 1, input_format_csv_empty_as_default = 0, apply_prewhere_after_final = 1, async_insert_max_data_size = 10485760, max_dynamic_subcolumns_in_json_type_parsing = 2, cluster_table_function_buckets_batch_size = 1938, throw_on_error_from_cache_on_write_operations = 0, describe_include_subcolumns = 1, input_format_values_deduce_templates_of_expressions = 1, opentelemetry_trace_cpu_scheduling = 1, iceberg_snapshot_id = -1)(c0)) AS t0d0 SELECT 86::UInt128, ('1900-01-01 00:00:00'::DateTime64) PREWHERE (1) GROUP BY CUBE((`t0d0`.`c0` AS `a2`)) ORDER BY (`t0d0`.`c0` AS `a2`) ASC NULLS FIRST COLLATE 'eo' OFFSET 915.66829259735209096850352334417 SETTINGS enable_multiple_prewhere_read_steps = 1, output_format_parquet_date_as_uint16 = 1, enable_shared_storage_snapshot_in_query = 1, optimize_aggregators_of_group_by_keys = 1, distributed_cache_read_alignment = 0, input_format_csv_allow_variable_number_of_columns = 1, max_bytes_before_external_sort = 0, input_format_json_ignore_unknown_keys_in_named_tuple = 1, use_page_cache_with_distributed_cache = 1, query_cache_max_size_in_bytes = 1312424, merge_tree_use_v1_object_and_dynamic_serialization = 1, enable_reads_from_query_cache = 1, max_bytes_before_external_sort = 1310242, cluster_for_parallel_replicas = 'test_cluster_one_shard_two_replicas' INTO OUTFILE '/tmp/file.data' TRUNCATE FORMAT Null;'.
---

Failed query:
WITH cte0d0 AS (SELECT c0, c1 FROM (SELECT 82, t0d1.number AS a0 FROM numbers('2149-06-06', 36704, CAST('ffffffff-ffff-ffff-ffff-ffffffffffff', 'UUID')) AS t0d1 QUALIFY and(NOT (t0d1.number IS NOT NULL), mapContains(a0[0], '叫')) LIMIT -100 BY t0d1.number SETTINGS cast_ipv4_ipv6_default_on_conversion_error = 0, remerge_sort_lowered_memory_bytes_ratio = 3.200698, check_table_dependencies = 1, s3_skip_empty_files = 1, delta_lake_insert_max_bytes_in_data_file = 0, input_format_csv_try_infer_strings_from_quoted_tuples = 0, mutations_execute_nondeterministic_on_initiator = 0, input_format_parquet_use_offset_index = 1, optimize_skip_unused_shards_rewrite_in = 1, parallel_replicas_mark_segment_size = 1024, max_final_threads = 10, allow_simdjson = 0, apply_deleted_mask = 0, max_concurrent_queries_for_all_users = 0, use_text_index_postings_cache = 1, distributed_plan_max_rows_to_broadcast = 0, prefer_global_in_and_join = 1, compile_expressions = 1, join_to_sort_maximum_table_rows = 0, max_read_buffer_size_local_fs = 7915634, max_streams_for_merge_tree_reading = 65536, query_plan_read_in_order_through_join = 1, apply_deleted_mask = 1, output_format_json_quote_64bit_integers = 1)(c0, c1)), t0d0.c0 AS a1 SELECT CAST('86', 'UInt128'), CAST('1900-01-01 00:00:00', 'DateTime64') FROM (SELECT c0 FROM (SELECT CAST('dbe916fe-0202-34e6-e421-da38d5d11941', 'UUID') AS a1 FROM d12.t67 AS t0d1 SAMPLE 171000 / 1000000 OFFSET CAST('-10', 'Int256')[-3] SETTINGS cloud_mode_engine = 3, input_format_csv_try_infer_strings_from_quoted_tuples = 1, split_parts_ranges_into_intersecting_and_non_intersecting_final = 1, input_format_csv_empty_as_default = 0, apply_prewhere_after_final = 1, async_insert_max_data_size = 10485760, max_dynamic_subcolumns_in_json_type_parsing = 2, cluster_table_function_buckets_batch_size = 1938, throw_on_error_from_cache_on_write_operations = 0, describe_include_subcolumns = 1, input_format_values_deduce_templates_of_expressions = 1, opentelemetry_trace_cpu_scheduling = 1, iceberg_snapshot_id = -1)(c0)) AS t0d0 PREWHERE 1 GROUP BY t0d0.c0 AS a2 WITH CUBE ORDER BY t0d0.c0 AS a2 ASC NULLS FIRST COLLATE 'eo' OFFSET 915.6682925973521 SETTINGS enable_multiple_prewhere_read_steps = 1, output_format_parquet_date_as_uint16 = 1, enable_shared_storage_snapshot_in_query = 1, optimize_aggregators_of_group_by_keys = 1, distributed_cache_read_alignment = 0, input_format_csv_allow_variable_number_of_columns = 1, max_bytes_before_external_sort = 0, input_format_json_ignore_unknown_keys_in_named_tuple = 1, use_page_cache_with_distributed_cache = 1, query_cache_max_size_in_bytes = 1312424, merge_tree_use_v1_object_and_dynamic_serialization = 1, enable_reads_from_query_cache = 1, max_bytes_before_external_sort = 1310242, cluster_for_parallel_replicas = 'test_cluster_one_shard_two_replicas' INTO OUTFILE '/tmp/file.data' TRUNCATE FORMAT `Null`
---

Stack trace:
pthread_kill@@GLIBC_2.34 @ 0x00000000000969fd
__GI_raise @ 0x0000000000042476
__lgamma_r_finite@GLIBC_2.15 @ 0x00000000000287f3
src/Common/Exception.cpp:60:5: DB::abortOnFailedAssertion(String const&, std::basic_string_view<char, std::char_traits<char>>, void* const*, unsigned long, unsigned long) @ 0x00000000165b385d
src/Common/Exception.cpp:93:13: DB::handle_error_code(String const&, std::basic_string_view<char, std::char_traits<char>>, int, bool, std::vector<void*, std::allocator<void*>> const&) @ 0x00000000165b4c4d
src/Common/Exception.cpp:146:19: DB::Exception::Exception(DB::Exception::MessageMasked&&, int, bool) @ 0x00000000165b4f39
src/Common/Exception.h:171:100: DB::Exception::Exception(String&&, int, String, bool) @ 0x000000000e5f83d4
src/Common/Exception.h:57:54: DB::Exception::Exception(PreformattedMessage&&, int) @ 0x000000000e5f7ed1
src/Common/Exception.h:189:77: DB::Exception::Exception<String&, std::basic_string_view<char, std::char_traits<char>>&>(int, FormatStringHelperImpl<std::type_identity<String&>::type, std::type_identity<std::basic_string_view<char, std::char_traits<char>>&>::type>, String&, std::basic_string_view<char, std::char_traits<char>>&) @ 0x000000001cd8b9c6
src/Interpreters/executeQuery.cpp:1240:31: DB::executeQueryImpl(char const*, char const*, std::shared_ptr<DB::Context>, DB::QueryFlags, DB::QueryProcessingStage::Enum, std::unique_ptr<DB::ReadBuffer, std::default_delete<DB::ReadBuffer>>&, boost::intrusive_ptr<DB::IAST>&, std::shared_ptr<DB::ImplicitTransactionControlExecutor>, std::function<void ()>, DB::QueryResultDetails&) @ 0x000000001cd7f1aa
src/Interpreters/executeQuery.cpp:2174:11: DB::executeQuery(String const&, std::shared_ptr<DB::Context>, DB::QueryFlags, DB::QueryProcessingStage::Enum) @ 0x000000001cd752e3
src/Server/TCPHandler.cpp:821:68: DB::TCPHandler::runImpl() @ 0x0000000020519298
src/Server/TCPHandler.cpp:2959:9: DB::TCPHandler::run() @ 0x0000000020535ae4
base/poco/Net/src/TCPServerConnection.cpp:40:3: Poco::Net::TCPServerConnection::start() @ 0x0000000027bdbb0e
base/poco/Net/src/TCPServerDispatcher.cpp:115:42: Poco::Net::TCPServerDispatcher::run() @ 0x0000000027bdc152
base/poco/Foundation/src/ThreadPool.cpp:205:14: Poco::PooledThread::run() @ 0x0000000027b7be3f
base/poco/Foundation/src/Thread_POSIX.cpp:341:27: Poco::ThreadImpl::runnableEntry(void*) @ 0x0000000027b794cf
start_thread @ 0x0000000000094ac3
clone3 @ 0x00000000001268d0
```



Discussion:

Patch:
diff --git a/src/Parsers/ASTSelectQuery.cpp b/src/Parsers/ASTSelectQuery.cpp
index 38de21c9778d..903a3c3d76f3 100644
--- a/src/Parsers/ASTSelectQuery.cpp
+++ b/src/Parsers/ASTSelectQuery.cpp
@@ -77,13 +77,29 @@ void ASTSelectQuery::formatImpl(WriteBuffer & ostr, const FormatSettings & s, Fo
         ostr << s.nl_or_ws;
     }
 
-    ostr << indent_str << "SELECT" << (distinct ? " DISTINCT" : "");
+    /// When the table has a SAMPLE clause and the query has a standalone OFFSET
+    /// (without LIMIT), use FROM-first syntax so that SELECT separates SAMPLE
+    /// from OFFSET.  Otherwise, the formatted "... SAMPLE r OFFSET n ..." is
+    /// ambiguous: the parser would consume OFFSET as the SAMPLE offset instead
+    /// of a query-level OFFSET.
+    bool format_from_first = sampleSize() && limitOffset() && !limitLength();
+
+    if (format_from_first && tables())
+    {
+        ostr << indent_str << "FROM";
+        tables()->format(ostr, s, state, frame);
+        ostr << s.nl_or_ws << indent_str << "SELECT" << (distinct ? " DISTINCT" : "");
+    }
+    else
+    {
+        ostr << indent_str << "SELECT" << (distinct ? " DISTINCT" : "");
+    }
 
     s.one_line
         ? select()->format(ostr, s, state, frame)
         : select()->as<ASTExpressionList &>().formatImplMultiline(ostr, s, state, frame);
 
-    if (tables())
+    if (!format_from_first && tables())
     {
         ostr << s.nl_or_ws << indent_str << "FROM";
         tables()->format(ostr, s, state, frame);


Test patch:
diff --git a/tests/queries/0_stateless/04054_sample_offset_formatting_ambiguity.reference b/tests/queries/0_stateless/04054_sample_offset_formatting_ambiguity.reference
new file mode 100644
index 000000000000..d24602fa0f04
--- /dev/null
+++ b/tests/queries/0_stateless/04054_sample_offset_formatting_ambiguity.reference
@@ -0,0 +1,13 @@
+SelectWithUnionQuery (children 1)
+ ExpressionList (children 1)
+  SelectQuery (children 3)
+   ExpressionList (children 1)
+    Literal UInt64_1
+   TablesInSelectQuery (children 1)
+    TablesInSelectQueryElement (children 1)
+     TableExpression (children 2)
+      TableIdentifier system.one
+      SampleRatio 5 / 10
+   Literal UInt64_10
+FROM system.one\nSAMPLE 5 / 10\nSELECT 1\nOFFSET 10
+SELECT 1\nFROM system.one\nSAMPLE 5 / 10 OFFSET 10
diff --git a/tests/queries/0_stateless/04054_sample_offset_formatting_ambiguity.sql b/tests/queries/0_stateless/04054_sample_offset_formatting_ambiguity.sql
new file mode 100644
index 000000000000..bb663473346c
--- /dev/null
+++ b/tests/queries/0_stateless/04054_sample_offset_formatting_ambiguity.sql
@@ -0,0 +1,14 @@
+-- Verify that a query with both SAMPLE and a query-level OFFSET roundtrips
+-- through formatting without the OFFSET being confused with SAMPLE OFFSET.
+
+-- FROM-first: OFFSET is after SELECT, so it is a query-level OFFSET.
+-- The AST must show Literal UInt64_10 as a child of SelectQuery (query-level offset),
+-- NOT as a child of TableExpression (which would mean SAMPLE offset).
+EXPLAIN AST FROM system.one SAMPLE 0.5 SELECT 1 OFFSET 10;
+
+-- Verify `formatQuery` produces FROM-first syntax when SAMPLE + query-level OFFSET
+-- coexist, so that re-parsing is unambiguous.
+SELECT formatQuery('FROM system.one SAMPLE 0.5 SELECT 1 OFFSET 10');
+
+-- When the OFFSET is a SAMPLE offset (SELECT-first), formatting should stay SELECT-first.
+SELECT formatQuery('SELECT 1 FROM system.one SAMPLE 0.5 OFFSET 10');