Skip to content

Allow using insertRangeFrom when lowCardinality numeric#80376

Closed
Ergus wants to merge 3 commits intoClickHouse:masterfrom
Ergus:fix_57243
Closed

Allow using insertRangeFrom when lowCardinality numeric#80376
Ergus wants to merge 3 commits intoClickHouse:masterfrom
Ergus:fix_57243

Conversation

@Ergus
Copy link
Copy Markdown
Member

@Ergus Ergus commented May 16, 2025

Resolves #57243

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Fix an exception with ARRAY JOINs: Function writeSlice expects same column types for GenericArraySlice and GenericArraySink which happens when lowCardinality numeric types are used (issue #57243).

@clickhouse-gh
Copy link
Copy Markdown
Contributor

clickhouse-gh bot commented May 16, 2025

Workflow [PR], commit [b844017]

@alexey-milovidov
Copy link
Copy Markdown
Member

@Ergus Ergus force-pushed the fix_57243 branch 3 times, most recently from da0bf0f to 757aaf4 Compare May 18, 2025 13:36
@clickhouse-gh clickhouse-gh bot added the pr-bugfix Pull request with bugfix, not backported by default label May 18, 2025
@Ergus Ergus added the can be tested Allows running workflows for external contributors label May 18, 2025
@Ergus Ergus force-pushed the fix_57243 branch 2 times, most recently from ecb6017 to e944979 Compare May 18, 2025 14:43
@tuanpach tuanpach self-assigned this May 19, 2025
@Ergus Ergus force-pushed the fix_57243 branch 3 times, most recently from 4f1f7f5 to 658d3a0 Compare May 20, 2025 08:39
* src/Functions/GatherUtils/Algorithms.h (writeSlice) : Extend the condition to allow use insertRangeFrom when lowCardinality numeric
types. This is a preliminar fix that works, but maybe this extension is not general enough.
* tests/queries/0_stateless/02447_join_numeric_low_cardinality.{sql,reference}: New tests with previously failing queries.

Fixes: ClickHouse#57243
{
if (slice.elements->structureEquals(sink.elements))
const bool can_insert =
// Check same type
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: // --> /// (style guide)

const bool can_insert =
// Check same type
slice.elements->structureEquals(sink.elements)
// insertRangeFrom casts numeric types if they are different, so it can insert them.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading l. 78, one wonders why l. 79 checks for low cardinality. Please either update the comment or remove sink.elements.lowCardinality() && slice.elements->lowCardinality() &&.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not okay to have this check inside a function that is called in a loop.

@rschu1ze
Copy link
Copy Markdown
Member

Let's continue with this PR (there was still an exception Function writeSlice expects same column types for GenericValueSlice and GenericArraySink in the AST fuzzer.

@Ergus
Copy link
Copy Markdown
Member Author

Ergus commented Jun 14, 2025

Let's continue with this PR (there was still an exception Function writeSlice expects same column types for GenericValueSlice and GenericArraySink in the AST fuzzer.

@rschu1ze

I suppose that this comes from a different code part (cause my error message is different now):

The only candidate so far is:

inline ALWAYS_INLINE void writeSlice(const GenericValueSlice & slice, GenericArraySink & sink)
{
if (slice.elements->structureEquals(sink.elements))
{
sink.elements.insertFrom(*slice.elements, slice.position);
++sink.current_offset;
}
else
throw Exception(ErrorCodes::LOGICAL_ERROR, "Function writeSlice expects same column types for GenericValueSlice and GenericArraySink.");
}

The code is very similar form outside, but in the inner part the function I modified relies on:

else
{
auto copy = [&](auto cur_type)
{
using CurIndexType = decltype(cur_type);
auto & positions_data = getPositionsData<CurIndexType>();
const auto & column_data = column_ptr->getData();
UInt64 size = positions_data.size();
positions_data.resize(size + limit);
for (UInt64 i = 0; i < limit; ++i)
positions_data[size + i] = static_cast<CurIndexType>(column_data[offset + i]);
};
callForType(std::move(copy), size_of_type);
}

But this function relies on:

void ColumnLowCardinality::Index::insertPosition(UInt64 position)
{
while (position > getMaxPositionForCurrentType())
expandType();
positions->insert(position);
checkSizeOfType();
}

That as you can see looks not as "elaborated".

So, in order to relax the outer condition as I did in the other function I need to add similar boundaries checks and casting code if needed. The issue here is that this looks like a single position insertion, so adding all the same checks could produce potential overhead.

WDYT?

@rschu1ze
Copy link
Copy Markdown
Member

Mergingmaster as the PR is based on some baroque code state.

@rschu1ze
Copy link
Copy Markdown
Member

@Ergus Question number 0 is: Is there a functional issue at SQL level caused by the other function

inline ALWAYS_INLINE void writeSlice(const GenericValueSlice & slice, GenericArraySink & sink)

?

There are two ways to find out:

  • Run the current SQL test in this PR on a debug binary in gdb/lldb, then adjust the SQL test a bit - basically try to make it go through above mentioned function with GenericValueSlice argument, i.e. check if there is unexpected behavior similar similar to the original issue at the SQL level.

  • Use the SELECT query generated by the AST Fuzzer for experimentation (it obviously triggered an exception but it is not clear if the exception was legitimate or not). Logs can be found here.

I would say once it is clear if there is a problem at all, we can check how to fix it.

@clickhouse-gh
Copy link
Copy Markdown
Contributor

clickhouse-gh bot commented Jul 22, 2025

Dear @tuanpach, this PR hasn't been updated for a while. You will be unassigned. Will you continue working on it? If so, please feel free to reassign yourself.

@Ergus
Copy link
Copy Markdown
Member Author

Ergus commented Jul 22, 2025

There are two ways to find out:

  • Run the current SQL test in this PR on a debug binary in gdb/lldb, then adjust the SQL test a bit - basically try to make it go through above mentioned function with GenericValueSlice argument, i.e. check if there is unexpected behavior similar similar to the original issue at the SQL level.

I tried this, but couldn't manage to force it go throw the failing function. We should try something else.

@@ -72,13 +72,21 @@ inline ALWAYS_INLINE void writeSlice(const StringSource::Slice & slice, FixedStr
/// Assuming same types of underlying columns for slice and sink if (ArraySlice, ArraySink) is (GenericArraySlice, GenericArraySink).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the assumption is stated in the comment.

@alexey-milovidov alexey-milovidov self-assigned this Dec 7, 2025
@alexey-milovidov
Copy link
Copy Markdown
Member

@Ergus, find another approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-bugfix Pull request with bugfix, not backported by default

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Array join: Function writeSlice expects same column types for GenericArraySlice and GenericArraySink

4 participants