Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unparsing implicit lateral UNNEST plan to SQL text #13824

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

goldmedal
Copy link
Contributor

@goldmedal goldmedal commented Dec 18, 2024

Which issue does this PR close?

Closes #13793

Rationale for this change

Support to unparse the implicit lateral unnest plan. Given a SQL

SELECT * FROM unnest_table u, UNNEST(u.array_col)

It can be planned to the logical plan and unparsed back to the SQL

SELECT * FROM unnest_table AS u CROSS JOIN LATERAL (SELECT UNNEST(u.array_col) AS "UNNEST(outer_ref(u.array_col))")

If with_unnest_as_table_factor is enabled, it will be unparsed to

SELECT * FROM unnest_table AS u CROSS JOIN UNNEST(u.array_col)

What changes are included in this PR?

Are these changes tested?

yes

Are there any user-facing changes?

new unparsing behavior

@github-actions github-actions bot added the sql SQL Planner label Dec 18, 2024
@goldmedal goldmedal marked this pull request as ready for review December 18, 2024 15:22
@alamb
Copy link
Contributor

alamb commented Dec 19, 2024

@phillipleblanc or @sgrebnov I wonder if you have interest/time to help review this PR?

Copy link
Contributor

@phillipleblanc phillipleblanc left a comment

Choose a reason for hiding this comment

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

Overall looks good, I have a couple of questions

/// Try to match the pattern `Expr::Alias(Expr::Column("__unnest_placeholder(outer_ref(...)))")`
///
/// `outer_ref` is the display result of [Expr::OuterReferenceColumn]
fn is_unnest_placeholder_with_outer_ref(expr: &Expr) -> (bool, bool) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we define an enum that we return here? That is clearer to read than two booleans and we can limit it to reference only the possible states (i.e. it doesn't look like (false, true) can happen, but its something a caller would need to handle from the type system)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

enum sounds like a good idea. I will try it. Thanks 👍

if let Expr::Alias(Alias { expr, .. }) = expr {
if let Expr::Column(Column { name, .. }) = expr.as_ref() {
return name.starts_with(UNNEST_PLACEHOLDER);
if let Some(prefix) = name.strip_prefix(UNNEST_PLACEHOLDER) {
return (true, prefix.starts_with("(outer_ref("));
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a huge fan of this string matching. At least the UNNEST_PLACEHOLDER is a shared const, but the outer_ref could potentially change and we would miss it here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't feel very strongly about this since it does seem unlikely to change though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not a huge fan of this string matching.

I agree that string matching isn't a good way but I think it's the only way to recognize if the outer reference column is exits in the unnest plan currently. 🤔

At least the UNNEST_PLACEHOLDER is a shared const, but the outer_ref could potentially change and we would miss it here.

Maybe we can use something like UNNEST_PLACEHOLDER to create a global constant for outer_ref. At least then, we won't miss it when someone needs to change it.

Expr::OuterReferenceColumn(_, c) => write!(f, "outer_ref({c})"),

return (plan, None);
}
if let Some(Expr::Alias(alias)) = projection.expr.first() {
if alias.expr.schema_name().to_string().starts_with("UNNEST(") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this not an expression of type Unnest? I don't think we need to do this string allocation here just to check that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's not an Expr::Unnest. The full plan is

///     SubqueryAlias: u
///       Subquery:
///         Projection: UNNEST(outer_ref(t1.c1)) AS c1
///           Projection: __unnest_placeholder(outer_ref(t1.c1),depth=1) AS UNNEST(outer_ref(t1.c1))
///             Unnest: lists[__unnest_placeholder(outer_ref(t1.c1))|depth=1] structs[]
///               Projection: outer_ref(t1.c1) AS __unnest_placeholder(outer_ref(t1.c1))
///                 EmptyRelation

It's a column pointing to an alias of its child. The alias is built from an expression schema_name.

let post_unnest_expr = col(post_unnest_name.clone()).alias(alias_name);

I think there is a similar issue for #13824 (comment).
Maybe we can have a constant for the display result of UNNEST.

@github-actions github-actions bot added the logical-expr Logical plan and expressions label Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
logical-expr Logical plan and expressions sql SQL Planner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support unparsing implicit lateral UNNEST plan to SQL text
3 participants