Repository navigation
Push down type casts to file readers - #22788
Conversation
7004997 to
b18f5c8
Compare
b95394b to
71abd21
Compare
|
You can verify the PR this way: ~/duckdb duckdb -c "explain select list_sum(GT) from '/home/myrrc/vortex/vortex-bench/data/statpopgen/100000/parquet/gnomad.genomes.v3.1.2.hgdp_tgp.chr21.parquet'"Before: ┌─────────────────────────────┐
│┌───────────────────────────┐│
││ Physical Plan ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ list_aggr(CAST(GT AS │
│ HUGEINT[]), 'sum') │
│ │
│ ~100,000 rows │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ PARQUET_SCAN │
│ ──────────────────── │
│ Function: │
│ PARQUET_SCAN │
│ │
│ Projections: GT │
│ │
│ ~100,000 rows │
└───────────────────────────┘After: |
Mytherin
left a comment
There was a problem hiding this comment.
Thanks - extending the usage of type_pushdown sounds like a good idea to me - but I think this is perhaps better done as an optimizer pass rather than as part of the binder. It would be great if this also replaces the existing implementation in CastLogicalOperatorToTypes which does a more limited version of this.
As for the actual implementation - we need to find all casts that occur for a given column, not just all casts that occur in the operator above the GET. The current implementation leaves any casts that occur prior to the current operator alone which causes issues, e.g. this now crashes because we don't detect the conflict in casts as we only find the cast in the LogicalFilter, missing the cast in LogicalProjection.
COPY (SELECT '2020-01-01 02:20:30' ts) TO 'file.csv';
SELECT ts::TIMESTAMP FROM 'file.csv' WHERE ts::DATE = '2020-01-01';It's also not sufficient just to find conflicts in casts, if the column is at any point used in a place where it is not directly cast to a given type there is a conflict. For example:
COPY (SELECT '2020-01-01 02:20:30' ts) TO 'file.csv';
SELECT ts, ts::DATE FROM 'file.csv';In general these kinds of changes are complex and it is easy to introduce bugs so it would be great if you could add more tests as well.
As a side-note, I think the MultiFile type pushdown for Parquet is not having any effect as these queries work with Parquet somehow. Likely the type pushdown is just being silently ignored by the Parquet reader and the original type is emitted.
|
Thanks, I'll definitely add more tests today. In the plan filter and projection are decoupled, so reader function can return casted timestamp. |
|
I think you might be conflating filter pushdown and type pushdown. What In the given example if we are telling the scan |
No, I'm saying if a file reader supports expression pushdown (cast pushdown specifically), we can request "ts AS TIMESTAMP" as projection, because "ts AS DATE" will be pushed as a filter, and there are no casted or direct usages of ts. And I also believe the reason Parquet/Arrow readers doesn't support type pushdown is because of UHUGEINT/IHUGEINT. This is the case for Vortex as well. So if you're open to breaking changes for 2.0, I'd suggest type_pushdown should either behave how pushdown_complex_filter does (get a list of output types, push down those supported, let duckdb handle the rest), or how expression pushdown does (separate function can_pushdown_type). |
584f34f to
19c9305
Compare
|
I've pushed an updated version with tests. |
4e87713 to
46fe386
Compare
46fe386 to
e13db4f
Compare
Mytherin
left a comment
There was a problem hiding this comment.
Thanks! Looks good - I've left some comments.
| TypePushdown::TypePushdown(ClientContext &context) : context(context) { | ||
| } | ||
|
|
||
| using GetCastMap = unordered_map<column_t, LogicalType>; |
There was a problem hiding this comment.
Are all of these top-level using clauses necessary? Looking at the code I think almost all of them can be replaced with auto & instead?
There was a problem hiding this comment.
Just a matter of taste, I prefer AAAA (always almost avoid auto), but I can remove them, not a big deal)
There was a problem hiding this comment.
For sure, it's a nit, but in general we prefer to have a consistent code-style as it simplifies reading / understanding code in the code base without having different dialects / styles in different modules.
| if (expr.GetExpressionClass() != ExpressionClass::BOUND_CAST) { | ||
| return collect_children(); | ||
| } | ||
| const auto &bound_cast = expr.Cast<BoundCastExpression>(); |
There was a problem hiding this comment.
We should check try_cast here as well - can you do this + add a test with try_cast and a mix of cast + try_cast on the same column?
There was a problem hiding this comment.
Does try_cast need any special handling? If a cast fails, it's on the extension to propagate this correctly since cast pushdown is already an extension point. +1 for tests
There was a problem hiding this comment.
The extension currently does not know a TRY_CAST was requested, so this will likely turn from working and returning NULL into an error:
COPY (SELECT 'hello world' varchar_col) TO 'varchar.parquet';
SELECT TRY_CAST(varchar_col AS DATE) FROM read_parquet('varchar.parquet');|
@Mytherin I've pushed an updated version. The question about type casts which are not supported in extension i.e. HUGEINT for Vortex still remains - we won't be able to use pushdown if there's no way to decline the cast. Maybe this PR should also use expression_pushdown TableFunction callback to check whether this is supported? |
|
We could redesign the That would be more flexible going forward - then we just wouldn't accept anything other than casts for now. |
|
If we redesign the function we should also move towards using a struct for input parameters (similar to |
|
What if multiple projections for a column were requested e.g. I've thought about this for a while in vortex-data/vortex#8190. If we go this route, Duckdb can request multiple same columns in projections i.e. for the query above However, if we want to further expand this to aggregate functions, we should also be notified of the output types. This shouldn't be hard, though |
|
I would say we can skip projection pushdown in that case similar to how we currently skip cast pushdown if the same column is scanned with different types - but it's an interesting angle to consider perhaps going forward. |
a46849b to
b2ad139
Compare
|
Rebased, thanks |
a397c20 to
366c814
Compare
|
Thanks! |
This a fix for duckdb#22788. Previously, we allowed to push CAST and TRY_CAST at once if type for column was the same. It doesn't work for readers like Vortex which have cast() but not try_cast(). After this PR, CAST+TRY_CAST is registered as a conflict
- Backport of duckdb/duckdb#22788 to push down casts, optimizer pass. Extract both passes common parts to optimizer.cpp - Map 8- and 16-bit types in sqllogictest runner to INTEGER. - Add tracing subscriber logging to sqllogictest runner to support RUST_LOG=debug. - Support of CAST (not TRY_CAST) over primitives pushdown on Rust side. U128/I128 are not pushed. We are overly restrictive here because we don't want clients to face issues like `No CastReduce to cast constant array from vortex.date[days](i32) to vortex.timestamp[µs](i64?)`. We can lift the restriction later. Signed-off-by: Mikhail Kot <[email protected]>
As reported in #22738, type casts are usually not propagated to file readers. For Vortex, this means list aggregation routines like
list_sum(GT)(executed aslist_sum(CAST(GT AS UHUGEINT[]))) do upcasting per-Vector instead of doing it once in Vortex.This PR solves this by transforming
CAST(col, T) -> GETnodes intoGET (col as T)if there are no conflicting casts.This PR replaces type_pushdown function with projection_expression_pushdown which can choose to push down specific projection expressions. This only uses cast pushdown as for now.
This PR also adds MultiFileInfo projection expression (casts only) pushdown. support for Parquet.
Resolves: #22738