-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[fix](user_var)fix integer typing and prefer Variable.realExpression for argument/type resolution #62524
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
base: master
Are you sure you want to change the base?
[fix](user_var)fix integer typing and prefer Variable.realExpression for argument/type resolution #62524
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import org.apache.doris.nereids.exceptions.UnboundException; | ||
| import org.apache.doris.nereids.trees.TreeNode; | ||
| import org.apache.doris.nereids.trees.expressions.Expression; | ||
| import org.apache.doris.nereids.trees.expressions.Variable; | ||
| import org.apache.doris.nereids.types.DataType; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
|
|
@@ -46,23 +47,58 @@ default void checkLegalityBeforeTypeCoercion() {} | |
| @Developing | ||
| default void checkLegalityAfterRewrite() {} | ||
|
|
||
| /** | ||
| * getArguments. | ||
| */ | ||
| default List<Expression> getArguments() { | ||
| return children(); | ||
| ImmutableList.Builder<Expression> arguments = ImmutableList.builder(); | ||
| for (Expression arg : children()) { | ||
| if (arg instanceof Variable && ((Variable) arg).getRealExpression() != null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This only fixes callers that go through |
||
| arguments.add(((Variable) arg).getRealExpression()); | ||
| } else { | ||
| arguments.add(arg); | ||
| } | ||
| } | ||
| return arguments.build(); | ||
| } | ||
|
|
||
| /** | ||
| * getArgument. | ||
| */ | ||
| default Expression getArgument(int index) { | ||
| return child(index); | ||
| Expression arg = child(index); | ||
| if (arg instanceof Variable && ((Variable) arg).getRealExpression() != null) { | ||
| return ((Variable) arg).getRealExpression(); | ||
| } else { | ||
| return arg; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * getArgumentsTypes. | ||
| */ | ||
| default List<DataType> getArgumentsTypes() { | ||
| return getArguments() | ||
| .stream() | ||
| .map(Expression::getDataType) | ||
| .collect(ImmutableList.toImmutableList()); | ||
| ImmutableList.Builder<DataType> dataTypes = ImmutableList.builder(); | ||
| for (Expression arg : children()) { | ||
| if (arg instanceof Variable && ((Variable) arg).getRealExpression() != null) { | ||
| dataTypes.add(((Variable) arg).getRealExpression().getDataType()); | ||
| } else { | ||
| dataTypes.add(arg.getDataType()); | ||
| } | ||
| } | ||
| return dataTypes.build(); | ||
| } | ||
|
|
||
| /** | ||
| * getArgumentType. | ||
| */ | ||
| default DataType getArgumentType(int index) { | ||
| return child(index).getDataType(); | ||
| Expression arg = child(index); | ||
| if (arg instanceof Variable && ((Variable) arg).getRealExpression() != null) { | ||
| return ((Variable) arg).getRealExpression().getDataType(); | ||
| } else { | ||
| return arg.getDataType(); | ||
| } | ||
| } | ||
|
|
||
| default DataType getDataType() throws UnboundException { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import org.apache.doris.analysis.DecimalLiteral; | ||
| import org.apache.doris.analysis.FloatLiteral; | ||
| import org.apache.doris.analysis.IntLiteral; | ||
| import org.apache.doris.analysis.LargeIntLiteral; | ||
| import org.apache.doris.analysis.LiteralExpr; | ||
| import org.apache.doris.analysis.NullLiteral; | ||
| import org.apache.doris.analysis.RedirectStatus; | ||
|
|
@@ -88,6 +89,7 @@ | |
| import org.xnio.StreamConnection; | ||
|
|
||
| import java.io.IOException; | ||
| import java.math.BigInteger; | ||
| import java.nio.ByteBuffer; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: this
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still widens |
||
|
|
@@ -587,10 +589,23 @@ public void setUserVar(String name, LiteralExpr value) { | |
| LiteralExpr literalExpr = userVars.get(varName); | ||
| if (literalExpr instanceof BoolLiteral) { | ||
| return Literal.of(((BoolLiteral) literalExpr).getValue()); | ||
| } else if (literalExpr instanceof IntLiteral) { | ||
| } else if (literalExpr instanceof IntLiteral || literalExpr instanceof LargeIntLiteral) { | ||
| // the value in the IntLiteral should be int, but now is long in old planner literalExpr | ||
| // so type coercion to generate right new planner int Literal | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fix still misses the forwarded-FE path. |
||
| return Literal.of((int) ((IntLiteral) literalExpr).getValue()); | ||
| switch (literalExpr.getType().getPrimitiveType()) { | ||
| case LARGEINT: | ||
| return Literal.of(new BigInteger(literalExpr.getStringValue())); | ||
| case BIGINT: | ||
| return Literal.of(((IntLiteral) literalExpr).getValue()); | ||
| case INT: | ||
| return Literal.of((int) ((IntLiteral) literalExpr).getValue()); | ||
| case SMALLINT: | ||
| return Literal.of((short) ((IntLiteral) literalExpr).getValue()); | ||
| case TINYINT: | ||
| return Literal.of((byte) ((IntLiteral) literalExpr).getValue()); | ||
| default: | ||
| return Literal.of((int) ((IntLiteral) literalExpr).getValue()); | ||
| } | ||
| } else if (literalExpr instanceof FloatLiteral) { | ||
| return Literal.of(((FloatLiteral) literalExpr).getValue()); | ||
| } else if (literalExpr instanceof DecimalLiteral) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Licensed to the Apache Software Foundation (ASF) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The ASF licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| package org.apache.doris.nereids.rules.analysis; | ||
|
|
||
| import org.apache.doris.analysis.IntLiteral; | ||
| import org.apache.doris.analysis.LargeIntLiteral; | ||
| import org.apache.doris.nereids.types.BigIntType; | ||
| import org.apache.doris.nereids.types.IntegerType; | ||
| import org.apache.doris.nereids.types.LargeIntType; | ||
| import org.apache.doris.nereids.types.SmallIntType; | ||
| import org.apache.doris.nereids.types.TinyIntType; | ||
| import org.apache.doris.nereids.util.MemoTestUtils; | ||
| import org.apache.doris.qe.ConnectContext; | ||
|
|
||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| /** Tests for user variable handling in expression analysis. */ | ||
| public class UserVariableAnalysisTest { | ||
|
|
||
| @Test | ||
| public void testUserVarIntegerType() { | ||
| ConnectContext ctx = MemoTestUtils.createConnectContext(); | ||
| // set user var @a = TINY_INT_MAX (tiny int) | ||
| ctx.setUserVar("a", new IntLiteral(Byte.MAX_VALUE)); | ||
| // set user var @a = SMALL_INT_MAX (small int) | ||
| ctx.setUserVar("b", new IntLiteral(Short.MAX_VALUE)); | ||
| // set user var @b = Long.MAX_VALUE (bigint) | ||
| ctx.setUserVar("c", new IntLiteral(Integer.MAX_VALUE)); | ||
| // set user var @b = Long.MAX_VALUE (bigint) | ||
| ctx.setUserVar("d", new IntLiteral(Long.MAX_VALUE)); | ||
| // set user var @b = Long.MAX_VALUE (bigint) | ||
| ctx.setUserVar("e", new LargeIntLiteral(LargeIntLiteral.LARGE_INT_MAX)); | ||
|
|
||
| Assertions.assertEquals(TinyIntType.INSTANCE, ConnectContext.get().getLiteralForUserVar("a").getDataType()); | ||
| Assertions.assertEquals(SmallIntType.INSTANCE, ConnectContext.get().getLiteralForUserVar("b").getDataType()); | ||
| Assertions.assertEquals(IntegerType.INSTANCE, ConnectContext.get().getLiteralForUserVar("c").getDataType()); | ||
| Assertions.assertEquals(BigIntType.INSTANCE, ConnectContext.get().getLiteralForUserVar("d").getDataType()); | ||
| Assertions.assertEquals(LargeIntType.INSTANCE, ConnectContext.get().getLiteralForUserVar("e").getDataType()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // Licensed to the Apache Software Foundation (ASF) under one | ||
| // or more contributor license agreements. See the NOTICE file | ||
| // distributed with this work for additional information | ||
| // regarding copyright ownership. The ASF licenses this file | ||
| // to you under the Apache License, Version 2.0 (the | ||
| // "License"); you may not use this file except in compliance | ||
| // with the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, | ||
| // software distributed under the License is distributed on an | ||
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| // KIND, either express or implied. See the License for the | ||
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| package org.apache.doris.nereids.trees.expressions.functions; | ||
|
|
||
| import org.apache.doris.nereids.analyzer.UnboundVariable.VariableType; | ||
| import org.apache.doris.nereids.trees.expressions.Expression; | ||
| import org.apache.doris.nereids.trees.expressions.Variable; | ||
| import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral; | ||
| import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; | ||
| import org.apache.doris.nereids.types.DataType; | ||
|
|
||
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * Tests for ExpressionTrait behaviors when children are `Variable`. | ||
| */ | ||
| public class ExpressionTraitTest { | ||
|
|
||
| static class DummyFunction extends Expression { | ||
| protected DummyFunction(List<Expression> children) { | ||
| super(children); | ||
| } | ||
|
|
||
| protected DummyFunction(Expression... children) { | ||
| super(children); | ||
| } | ||
|
|
||
| @Override | ||
| public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public Expression withChildren(List<Expression> children) { | ||
| return new DummyFunction(children); | ||
| } | ||
|
|
||
| @Override | ||
| protected String computeToSql() { | ||
| return "dummy"; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean nullable() { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testVariableWithRealExpression() { | ||
| IntegerLiteral lit = new IntegerLiteral(42); | ||
| Variable var = new Variable("v", VariableType.USER, lit); | ||
|
|
||
| DummyFunction func = new DummyFunction(var); | ||
|
|
||
| List<Expression> args = func.getArguments(); | ||
| Assertions.assertEquals(1, args.size()); | ||
| Assertions.assertEquals(lit, args.get(0)); | ||
|
|
||
| Assertions.assertEquals(lit, func.getArgument(0)); | ||
|
|
||
| List<DataType> types = func.getArgumentsTypes(); | ||
| Assertions.assertEquals(1, types.size()); | ||
| Assertions.assertEquals(lit.getDataType(), types.get(0)); | ||
|
|
||
| Assertions.assertEquals(lit.getDataType(), func.getArgumentType(0)); | ||
| } | ||
|
|
||
| @Test | ||
| public void testVariableWithoutRealExpression() { | ||
| IntegerLiteral lit = new IntegerLiteral(100); | ||
| // create a Variable but override getRealExpression to simulate a missing real expression | ||
| Variable var = new Variable("v", VariableType.USER, lit) { | ||
| @Override | ||
| public Expression getRealExpression() { | ||
| return null; | ||
| } | ||
| }; | ||
|
|
||
| DummyFunction func = new DummyFunction(var); | ||
|
|
||
| List<Expression> args = func.getArguments(); | ||
| Assertions.assertEquals(1, args.size()); | ||
| // when Variable.getRealExpression() returns null, ExpressionTrait should return the Variable itself | ||
| Assertions.assertSame(var, args.get(0)); | ||
|
|
||
| Assertions.assertSame(var, func.getArgument(0)); | ||
|
|
||
| List<DataType> types = func.getArgumentsTypes(); | ||
| Assertions.assertEquals(1, types.size()); | ||
| // fallback to variable.getDataType() | ||
| Assertions.assertEquals(var.getDataType(), types.get(0)); | ||
|
|
||
| Assertions.assertEquals(var.getDataType(), func.getArgumentType(0)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,3 +20,6 @@ true false | |
| -- !select7 -- | ||
| 1 | ||
|
|
||
| -- !select_user_var -- | ||
| 2 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unwrapping
Variablehere only fixes callers that already usegetArgument*()/getArguments*(). A lot of function legality/signature code still reads rawchild()nodes before the laterVariableToLiteralanalyzer rule runs, so the same user-variable bug is still present there. Concrete example:SELECT UTC_TIMESTAMP(@p)with@p = 3still fails, becausevisitUnboundVariable()returnsVariable(realExpression=literal),visitUnboundFunction()buildsUtcTimestamp(Variable), and thenTypeCoercionUtils.processBoundFunction()immediately callsUtcTimestamp.checkLegalityBeforeTypeCoercion(), which rejects!child(0).isLiteral(). The same pattern exists inutc_time,uniform,width_bucket, etc. I think this needs to be fixed earlier in the bind/type-check path, or those raw-child checks need to be updated as well; otherwise this patch only fixes thewindow_funnel-style subset of functions.