diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/MysqlColType.java b/fe/fe-common/src/main/java/org/apache/doris/catalog/MysqlColType.java index 0008f16ab10101..cd48b0afd94611 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/catalog/MysqlColType.java +++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/MysqlColType.java @@ -17,6 +17,9 @@ package org.apache.doris.catalog; +import java.util.HashMap; +import java.util.Map; + // MySQL column type // TYPE codes are defined in the file 'mysql/include/mysql_com.h' enum enum_field_types // which is also demostrated in @@ -55,6 +58,14 @@ public enum MysqlColType { MYSQL_TYPE_GEOMETRY(255, "GEOMETRY", "GEOMETRY"), MYSQL_TYPE_MAP(400, "MAP", "MAP"); + private static final Map CODE_MAP = new HashMap<>(); + + static { + for (MysqlColType type : MysqlColType.values()) { + CODE_MAP.put(type.code, type); + } + } + private MysqlColType(int code, String desc, String jdbcColumnTypeName) { this.code = code; this.desc = desc; @@ -77,6 +88,10 @@ public int getCode() { return code; } + public static MysqlColType fromCode(int code) { + return CODE_MAP.get(code); + } + public String getJdbcColumnTypeName() { return jdbcColumnTypeName; } diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 index ba6d908d7cacbc..49c794640eb3c8 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4 @@ -408,6 +408,7 @@ PERCENT: 'PERCENT'; PERIOD: 'PERIOD'; PERMISSIVE: 'PERMISSIVE'; PHYSICAL: 'PHYSICAL'; +PLACEHOLDER: '?'; PLAN: 'PLAN'; PROCESS: 'PROCESS'; PLUGIN: 'PLUGIN'; diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 index a57df0dbea7f0f..58791189739b0b 100644 --- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 +++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 @@ -880,6 +880,7 @@ constant | LEFT_BRACE (items+=constant COLON items+=constant)? (COMMA items+=constant COLON items+=constant)* RIGHT_BRACE #mapLiteral | LEFT_BRACE items+=constant (COMMA items+=constant)* RIGHT_BRACE #structLiteral + | PLACEHOLDER #placeholder ; comparisonOperator diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java index 0d658b9e74fa71..ab8b99c51a3be4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java @@ -30,10 +30,12 @@ import org.apache.doris.nereids.trees.expressions.CTEId; import org.apache.doris.nereids.trees.expressions.ExprId; import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.Placeholder; import org.apache.doris.nereids.trees.expressions.Slot; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator; import org.apache.doris.nereids.trees.plans.ObjectId; +import org.apache.doris.nereids.trees.plans.PlaceholderId; import org.apache.doris.nereids.trees.plans.RelationId; import org.apache.doris.nereids.trees.plans.TableId; import org.apache.doris.nereids.trees.plans.algebra.Relation; @@ -119,6 +121,11 @@ public class StatementContext implements Closeable { private final Set viewDdlSqlSet = Sets.newHashSet(); private final SqlCacheContext sqlCacheContext; + // generate for next id for prepared statement's placeholders, which is connection level + private final IdGenerator placeHolderIdGenerator = PlaceholderId.createGenerator(); + // relation id to placeholders for prepared statement + private final Map idToPlaceholderRealExpr = new HashMap<>(); + // collect all hash join conditions to compute node connectivity in join graph private final List joinFilters = new ArrayList<>(); @@ -144,6 +151,9 @@ public class StatementContext implements Closeable { // table locks private final Stack plannerResources = new Stack<>(); + // placeholder params for prepared statement + private List placeholders; + // for create view support in nereids // key is the start and end position of the sql substring that needs to be replaced, // and value is the new string used for replacement. @@ -370,6 +380,14 @@ public Map> getConsumerIdToFilters() { return consumerIdToFilters; } + public PlaceholderId getNextPlaceholderId() { + return placeHolderIdGenerator.getNextId(); + } + + public Map getIdToPlaceholderRealExpr() { + return idToPlaceholderRealExpr; + } + public Map, Group>>> getCteIdToConsumerGroup() { return cteIdToConsumerGroup; } @@ -490,6 +508,14 @@ public void close() { releasePlannerResources(); } + public List getPlaceholders() { + return placeholders; + } + + public void setPlaceholders(List placeholders) { + this.placeholders = placeholders; + } + private static class CloseableResource implements Closeable { public final String resourceName; public final String threadName; diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index 79272d0e75a336..5ac16f926844e3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -262,6 +262,7 @@ import org.apache.doris.nereids.trees.expressions.NullSafeEqual; import org.apache.doris.nereids.trees.expressions.Or; import org.apache.doris.nereids.trees.expressions.OrderExpression; +import org.apache.doris.nereids.trees.expressions.Placeholder; import org.apache.doris.nereids.trees.expressions.Properties; import org.apache.doris.nereids.trees.expressions.Regexp; import org.apache.doris.nereids.trees.expressions.ScalarSubquery; @@ -494,6 +495,16 @@ public class LogicalPlanBuilder extends DorisParserBaseVisitor { private final boolean forCreateView; + // Sort the parameters with token position to keep the order with original placeholders + // in prepared statement.Otherwise, the order maybe broken + private final Map tokenPosToParameters = Maps.newTreeMap((pos1, pos2) -> { + int line = pos1.getLine() - pos2.getLine(); + if (line != 0) { + return line; + } + return pos1.getCharPositionInLine() - pos2.getCharPositionInLine(); + }); + public LogicalPlanBuilder() { forCreateView = false; } @@ -1010,6 +1021,9 @@ public List> visitMultiStatements(MultiState } logicalPlans.add(Pair.of( ParserUtils.withOrigin(ctx, () -> (LogicalPlan) visit(statement)), statementContext)); + List params = new ArrayList<>(tokenPosToParameters.values()); + statementContext.setPlaceholders(params); + tokenPosToParameters.clear(); } return logicalPlans; } @@ -2322,6 +2336,13 @@ public Literal visitStringLiteral(StringLiteralContext ctx) { return new VarcharLiteral(s, strLength); } + @Override + public Expression visitPlaceholder(DorisParser.PlaceholderContext ctx) { + Placeholder parameter = new Placeholder(ConnectContext.get().getStatementContext().getNextPlaceholderId()); + tokenPosToParameters.put(ctx.start, parameter); + return parameter; + } + /** * cast all items to same types. * TODO remove this function after we refactor type coercion. diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java index 691e4aca1a337d..0f4641cc425f6b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java @@ -56,6 +56,7 @@ import org.apache.doris.nereids.trees.expressions.Match; import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.expressions.Not; +import org.apache.doris.nereids.trees.expressions.Placeholder; import org.apache.doris.nereids.trees.expressions.Slot; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.TimestampArithmetic; @@ -541,6 +542,13 @@ public Expression visitNot(Not not, ExpressionRewriteContext context) { return expr; } + @Override + public Expression visitPlaceholder(Placeholder placeholder, ExpressionRewriteContext context) { + Expression realExpr = context.cascadesContext.getStatementContext() + .getIdToPlaceholderRealExpr().get(placeholder.getPlaceholderId()); + return visit(realExpr, context); + } + @Override public Expression visitComparisonPredicate(ComparisonPredicate cp, ExpressionRewriteContext context) { Expression left = cp.left().accept(this, context); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java new file mode 100644 index 00000000000000..9ae054d82a6598 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Placeholder.java @@ -0,0 +1,78 @@ +// 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; + +import org.apache.doris.catalog.MysqlColType; +import org.apache.doris.nereids.exceptions.UnboundException; +import org.apache.doris.nereids.trees.expressions.shape.LeafExpression; +import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; +import org.apache.doris.nereids.trees.plans.PlaceholderId; +import org.apache.doris.nereids.types.DataType; +import org.apache.doris.nereids.types.NullType; + +import java.util.Optional; + +/** + * Placeholder for prepared statement + */ +public class Placeholder extends Expression implements LeafExpression { + private final PlaceholderId placeholderId; + private final Optional mysqlColType; + + public Placeholder(PlaceholderId placeholderId) { + this.placeholderId = placeholderId; + this.mysqlColType = Optional.empty(); + } + + public Placeholder(PlaceholderId placeholderId, MysqlColType mysqlColType) { + this.placeholderId = placeholderId; + this.mysqlColType = Optional.of(mysqlColType); + } + + public PlaceholderId getPlaceholderId() { + return placeholderId; + } + + @Override + public R accept(ExpressionVisitor visitor, C context) { + return visitor.visitPlaceholder(this, context); + } + + @Override + public boolean nullable() { + return true; + } + + @Override + public String toSql() { + return "?"; + } + + @Override + public DataType getDataType() throws UnboundException { + return NullType.INSTANCE; + } + + public Placeholder withNewMysqlColType(MysqlColType mysqlColType) { + return new Placeholder(getPlaceholderId(), mysqlColType); + } + + public MysqlColType getMysqlColType() { + return mysqlColType.get(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java index c3369fc28389ac..9c1096799baeae 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Literal.java @@ -19,8 +19,10 @@ import org.apache.doris.analysis.BoolLiteral; import org.apache.doris.analysis.LiteralExpr; +import org.apache.doris.catalog.MysqlColType; import org.apache.doris.catalog.Type; import org.apache.doris.common.Config; +import org.apache.doris.mysql.MysqlProto; import org.apache.doris.nereids.exceptions.AnalysisException; import org.apache.doris.nereids.exceptions.UnboundException; import org.apache.doris.nereids.trees.expressions.Expression; @@ -28,6 +30,7 @@ import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; import org.apache.doris.nereids.types.CharType; import org.apache.doris.nereids.types.DataType; +import org.apache.doris.nereids.types.DateTimeType; import org.apache.doris.nereids.types.DateTimeV2Type; import org.apache.doris.nereids.types.DecimalV2Type; import org.apache.doris.nereids.types.DecimalV3Type; @@ -40,6 +43,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -396,4 +400,169 @@ public boolean isZero() { } return false; } + + /** + ** get paramter length, port from mysql get_param_length + **/ + public static int getParmLen(ByteBuffer data) { + int maxLen = data.remaining(); + if (maxLen < 1) { + return 0; + } + // get and advance 1 byte + int len = MysqlProto.readInt1(data); + if (len == 252) { + if (maxLen < 3) { + return 0; + } + // get and advance 2 bytes + return MysqlProto.readInt2(data); + } else if (len == 253) { + if (maxLen < 4) { + return 0; + } + // get and advance 3 bytes + return MysqlProto.readInt3(data); + } else if (len == 254) { + /* + In our client-server protocol all numbers bigger than 2^24 + stored as 8 bytes with uint8korr. Here we always know that + parameter length is less than 2^4 so we don't look at the second + 4 bytes. But still we need to obey the protocol hence 9 in the + assignment below. + */ + if (maxLen < 9) { + return 0; + } + len = MysqlProto.readInt4(data); + MysqlProto.readFixedString(data, 4); + return len; + } else if (len == 255) { + return 0; + } else { + return len; + } + } + + /** + * Retrieves a Literal object based on the MySQL type and the data provided. + * + * @param mysqlType the MySQL type identifier + * @param data the ByteBuffer containing the data + * @return a Literal object corresponding to the MySQL type + * @throws AnalysisException if the MySQL type is unsupported or if data conversion fails + * @link .... + */ + public static Literal getLiteralByMysqlType(MysqlColType mysqlType, ByteBuffer data) throws AnalysisException { + switch (mysqlType) { + case MYSQL_TYPE_TINY: + return new TinyIntLiteral(data.get()); + case MYSQL_TYPE_SHORT: + return new SmallIntLiteral((short) data.getChar()); + case MYSQL_TYPE_LONG: + return new IntegerLiteral(data.getInt()); + case MYSQL_TYPE_LONGLONG: + return new BigIntLiteral(data.getLong()); + case MYSQL_TYPE_FLOAT: + return new FloatLiteral(data.getFloat()); + case MYSQL_TYPE_DOUBLE: + return new DoubleLiteral(data.getDouble()); + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + return handleDecimalLiteral(data); + case MYSQL_TYPE_DATE: + return handleDateLiteral(data); + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: + return handleDateTimeLiteral(data); + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VARSTRING: + return handleStringLiteral(data); + case MYSQL_TYPE_VARCHAR: + return handleVarcharLiteral(data); + default: + throw new AnalysisException("Unsupported MySQL type: " + mysqlType); + } + } + + private static Literal handleDecimalLiteral(ByteBuffer data) throws AnalysisException { + int len = getParmLen(data); + byte[] bytes = new byte[len]; + data.get(bytes); + try { + String value = new String(bytes); + BigDecimal v = new BigDecimal(value); + if (Config.enable_decimal_conversion) { + return new DecimalV3Literal(v); + } + return new DecimalLiteral(v); + } catch (NumberFormatException e) { + throw new AnalysisException("Invalid decimal literal", e); + } + } + + private static Literal handleDateLiteral(ByteBuffer data) { + int len = getParmLen(data); + if (len >= 4) { + int year = (int) data.getChar(); + int month = (int) data.get(); + int day = (int) data.get(); + if (Config.enable_date_conversion) { + return new DateV2Literal(year, month, day); + } + return new DateLiteral(year, month, day); + } else { + if (Config.enable_date_conversion) { + return new DateV2Literal(0, 1, 1); + } + return new DateLiteral(0, 1, 1); + } + } + + private static Literal handleDateTimeLiteral(ByteBuffer data) { + int len = getParmLen(data); + if (len >= 4) { + int year = (int) data.getChar(); + int month = (int) data.get(); + int day = (int) data.get(); + int hour = 0; + int minute = 0; + int second = 0; + int microsecond = 0; + if (len > 4) { + hour = (int) data.get(); + minute = (int) data.get(); + second = (int) data.get(); + } + if (len > 7) { + microsecond = data.getInt(); + } + if (Config.enable_date_conversion) { + return new DateTimeV2Literal(year, month, day, hour, minute, second, microsecond); + } + return new DateTimeLiteral(DateTimeType.INSTANCE, year, month, day, hour, minute, second, microsecond); + } else { + if (Config.enable_date_conversion) { + return new DateTimeV2Literal(0, 1, 1, 0, 0, 0); + } + return new DateTimeLiteral(0, 1, 1, 0, 0, 0); + } + } + + private static Literal handleStringLiteral(ByteBuffer data) { + int strLen = getParmLen(data); + strLen = Math.min(strLen, data.remaining()); + byte[] bytes = new byte[strLen]; + data.get(bytes); + return new StringLiteral(new String(bytes)); + } + + private static Literal handleVarcharLiteral(ByteBuffer data) { + int strLen = getParmLen(data); + strLen = Math.min(strLen, data.remaining()); + byte[] bytes = new byte[strLen]; + data.get(bytes); + return new VarcharLiteral(new String(bytes)); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java index feea6cfe7d5b2a..294e96319e5c55 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionVisitor.java @@ -69,6 +69,7 @@ import org.apache.doris.nereids.trees.expressions.NullSafeEqual; import org.apache.doris.nereids.trees.expressions.Or; import org.apache.doris.nereids.trees.expressions.OrderExpression; +import org.apache.doris.nereids.trees.expressions.Placeholder; import org.apache.doris.nereids.trees.expressions.Properties; import org.apache.doris.nereids.trees.expressions.ScalarSubquery; import org.apache.doris.nereids.trees.expressions.Slot; @@ -505,6 +506,10 @@ public R visitMatchPhraseEdge(MatchPhraseEdge matchPhraseEdge, C context) { return visitMatch(matchPhraseEdge, context); } + public R visitPlaceholder(Placeholder placeholder, C context) { + return visit(placeholder, context); + } + public R visitAny(Any any, C context) { return visit(any, context); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlaceholderId.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlaceholderId.java new file mode 100644 index 00000000000000..f1d410100e16fa --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlaceholderId.java @@ -0,0 +1,58 @@ +// 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.plans; + +import org.apache.doris.common.Id; +import org.apache.doris.common.IdGenerator; + +/** + * placeholder id for prepared statement parameters + */ +public class PlaceholderId extends Id { + + public PlaceholderId(int id) { + super(id); + } + + /** + * Should be only called by {@link org.apache.doris.nereids.StatementContext}. + */ + public static IdGenerator createGenerator() { + return new IdGenerator() { + @Override + public PlaceholderId getNextId() { + return new PlaceholderId(nextId++); + } + }; + } + + @Override + public String toString() { + return "PlaceholderId#" + id; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java index 210bab09c676cf..13fd64a8798851 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java @@ -157,5 +157,8 @@ public enum PlanType { ALTER_VIEW_COMMAND, DROP_CATALOG_RECYCLE_BIN_COMMAND, UNSUPPORTED_COMMAND, - CREATE_TABLE_LIKE_COMMAND + CREATE_TABLE_LIKE_COMMAND, + + PREPARED_COMMAND, + EXECUTE_COMMAND } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java new file mode 100644 index 00000000000000..b098f883647b8f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ExecuteCommand.java @@ -0,0 +1,83 @@ +// 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.plans.commands; + +import org.apache.doris.nereids.StatementContext; +import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.glue.LogicalPlanAdapter; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.PreparedStatementContext; +import org.apache.doris.qe.StmtExecutor; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Prepared Statement + */ +public class ExecuteCommand extends Command { + private final String stmtName; + private final PrepareCommand prepareCommand; + private final StatementContext statementContext; + + public ExecuteCommand(String stmtName, PrepareCommand prepareCommand, StatementContext statementContext) { + super(PlanType.EXECUTE_COMMAND); + this.stmtName = stmtName; + this.prepareCommand = prepareCommand; + this.statementContext = statementContext; + } + + public String getStmtName() { + return stmtName; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visit(this, context); + } + + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + PreparedStatementContext preparedStmtCtx = ctx.getPreparedStementContext(stmtName); + if (null == preparedStmtCtx) { + throw new AnalysisException( + "prepare statement " + stmtName + " not found, maybe expired"); + } + PrepareCommand prepareCommand = (PrepareCommand) preparedStmtCtx.command; + LogicalPlanAdapter planAdapter = new LogicalPlanAdapter(prepareCommand.getLogicalPlan(), executor.getContext() + .getStatementContext()); + executor.setParsedStmt(planAdapter); + // execute real statement + executor.execute(); + } + + /** + * return the sql representation contains real expr instead of placeholders + */ + public String toSql() { + // maybe slow + List realValueExpr = prepareCommand.getPlaceholders().stream() + .map(placeholder -> statementContext.getIdToPlaceholderRealExpr().get(placeholder.getPlaceholderId())) + .collect(Collectors.toList()); + return "EXECUTE `" + stmtName + "`" + + realValueExpr.stream().map(Expression::toSql).collect(Collectors.joining(", ", " USING ", "")); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/PrepareCommand.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/PrepareCommand.java new file mode 100644 index 00000000000000..958fc4702836dc --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/PrepareCommand.java @@ -0,0 +1,120 @@ +// 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.plans.commands; + +import org.apache.doris.mysql.MysqlCommand; +import org.apache.doris.nereids.trees.expressions.Placeholder; +import org.apache.doris.nereids.trees.plans.PlanType; +import org.apache.doris.nereids.trees.plans.logical.LogicalPlan; +import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.OriginStatement; +import org.apache.doris.qe.PreparedStatementContext; +import org.apache.doris.qe.StmtExecutor; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +/** + * Prepared Statement + */ +public class PrepareCommand extends Command { + private static final Logger LOG = LogManager.getLogger(StmtExecutor.class); + + private final List placeholders = new ArrayList<>(); + private final LogicalPlan logicalPlan; + + private final String name; + + private final OriginStatement originalStmt; + + /** + * constructor + * @param name the statement name which represents statement id for prepared statement + * @param plan the inner statement + * @param placeholders the parameters for this prepared statement + * @param originalStmt original statement from StmtExecutor + */ + public PrepareCommand(String name, LogicalPlan plan, List placeholders, + OriginStatement originalStmt) { + super(PlanType.PREPARED_COMMAND); + this.logicalPlan = plan; + this.placeholders.addAll(placeholders); + this.name = name; + this.originalStmt = originalStmt; + } + + public String getName() { + return name; + } + + public List getPlaceholders() { + return placeholders; + } + + public int placeholderCount() { + return placeholders.size(); + } + + public LogicalPlan getLogicalPlan() { + return logicalPlan; + } + + public OriginStatement getOriginalStmt() { + return originalStmt; + } + + /** + * return the labels of parameters + */ + public List getLabels() { + List labels = new ArrayList<>(); + for (Placeholder parameter : placeholders) { + labels.add("$" + parameter.getPlaceholderId().asInt()); + } + return labels; + } + + // register prepared statement with attached statement id + @Override + public void run(ConnectContext ctx, StmtExecutor executor) throws Exception { + List labels = getLabels(); + // register prepareStmt + if (LOG.isDebugEnabled()) { + LOG.debug("add prepared statement {}, isBinaryProtocol {}", + name, ctx.getCommand() == MysqlCommand.COM_STMT_PREPARE); + } + ctx.addPreparedStatementContext(name, + new PreparedStatementContext(this, ctx, ctx.getStatementContext(), name)); + if (ctx.getCommand() == MysqlCommand.COM_STMT_PREPARE) { + executor.sendStmtPrepareOK((int) ctx.getStmtId(), labels); + } + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visit(this, context); + } + + public PrepareCommand withPlaceholders(List placeholders) { + return new PrepareCommand(this.name, this.logicalPlan, placeholders, this.originalStmt); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java index f970d9b406e824..b166de7691c08d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java @@ -251,8 +251,13 @@ public void setUserInsertTimeout(int insertTimeout) { } private StatementContext statementContext; + + // legacy planner private Map preparedStmtCtxs = Maps.newHashMap(); + // new planner + private Map preparedStatementContextMap = Maps.newHashMap(); + private List tables = null; private Map totalColumnStatisticMap = new HashMap<>(); @@ -385,6 +390,10 @@ public void addPreparedStmt(String stmtName, PrepareStmtContext ctx) { this.preparedStmtCtxs.put(stmtName, ctx); } + public void addPreparedStatementContext(String stmtName, PreparedStatementContext ctx) { + this.preparedStatementContextMap.put(stmtName, ctx); + } + public void removePrepareStmt(String stmtName) { this.preparedStmtCtxs.remove(stmtName); } @@ -393,6 +402,10 @@ public PrepareStmtContext getPreparedStmt(String stmtName) { return this.preparedStmtCtxs.get(stmtName); } + public PreparedStatementContext getPreparedStementContext(String stmtName) { + return this.preparedStatementContextMap.get(stmtName); + } + public List getTables() { return tables; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java index 0113d9d1d5ecf9..d7053e9631a1ce 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java @@ -257,8 +257,11 @@ public void executeQuery(MysqlCommand mysqlCommand, String originStmt) throws Ex Exception nereidsSyntaxException = null; long parseSqlStartTime = System.currentTimeMillis(); List cachedStmts = null; - // Nereids do not support prepare and execute now, so forbid prepare command, only process query command - if (mysqlCommand == MysqlCommand.COM_QUERY && sessionVariable.isEnableNereidsPlanner()) { + // Currently we add a config to decide whether using PREPARED/EXECUTE command for nereids + // TODO: after implemented full prepared, we could remove this flag + boolean nereidsUseServerPrep = sessionVariable.enableServeSidePreparedStatement + || mysqlCommand == MysqlCommand.COM_QUERY; + if (nereidsUseServerPrep && sessionVariable.isEnableNereidsPlanner()) { if (wantToParseSqlFromSqlCache) { cachedStmts = parseFromSqlCache(originStmt); if (cachedStmts != null) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/MysqlConnectProcessor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/MysqlConnectProcessor.java index d117eeba9493c6..6c1507722d7b65 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/MysqlConnectProcessor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/MysqlConnectProcessor.java @@ -18,15 +18,24 @@ package org.apache.doris.qe; import org.apache.doris.analysis.ExecuteStmt; -import org.apache.doris.analysis.InsertStmt; import org.apache.doris.analysis.LiteralExpr; import org.apache.doris.analysis.NullLiteral; +import org.apache.doris.analysis.PrepareStmt; import org.apache.doris.analysis.QueryStmt; +import org.apache.doris.analysis.StatementBase; +import org.apache.doris.catalog.MysqlColType; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.mysql.MysqlChannel; import org.apache.doris.mysql.MysqlCommand; import org.apache.doris.mysql.MysqlProto; +import org.apache.doris.nereids.StatementContext; +import org.apache.doris.nereids.glue.LogicalPlanAdapter; +import org.apache.doris.nereids.trees.expressions.Placeholder; +import org.apache.doris.nereids.trees.expressions.literal.Literal; +import org.apache.doris.nereids.trees.plans.PlaceholderId; +import org.apache.doris.nereids.trees.plans.commands.ExecuteCommand; +import org.apache.doris.nereids.trees.plans.commands.PrepareCommand; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -81,35 +90,12 @@ private void debugPacket() { } } - // process COM_EXECUTE, parse binary row data - // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_execute.html - private void handleExecute() { - // debugPacket(); - packetBuf = packetBuf.order(ByteOrder.LITTLE_ENDIAN); - // parse stmt_id, flags, params - int stmtId = packetBuf.getInt(); - // flag - packetBuf.get(); - // iteration_count always 1, - packetBuf.getInt(); - if (LOG.isDebugEnabled()) { - LOG.debug("execute prepared statement {}", stmtId); - } - PrepareStmtContext prepareCtx = ctx.getPreparedStmt(String.valueOf(stmtId)); - if (prepareCtx == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("No such statement in context, stmtId:{}", stmtId); - } - ctx.getState().setError(ErrorCode.ERR_UNKNOWN_COM_ERROR, - "msg: Not supported such prepared statement"); - return; - } - ctx.setStartTime(); - if (prepareCtx.stmt.getInnerStmt() instanceof QueryStmt) { + private void handleExecute(PrepareStmt prepareStmt, long stmtId) { + if (prepareStmt.getInnerStmt() instanceof QueryStmt) { ctx.getState().setIsQuery(true); } - prepareCtx.stmt.setIsPrepared(); - int paramCount = prepareCtx.stmt.getParmCount(); + prepareStmt.setIsPrepared(); + int paramCount = prepareStmt.getParmCount(); LOG.debug("execute prepared statement {}, paramCount {}", stmtId, paramCount); // null bitmap String stmtStr = ""; @@ -124,7 +110,7 @@ private void handleExecute() { for (int i = 0; i < paramCount; ++i) { int typeCode = packetBuf.getChar(); LOG.debug("code {}", typeCode); - prepareCtx.stmt.placeholders().get(i).setTypeCode(typeCode); + prepareStmt.placeholders().get(i).setTypeCode(typeCode); } } // parse param data @@ -133,7 +119,7 @@ private void handleExecute() { realValueExprs.add(new NullLiteral()); continue; } - LiteralExpr l = prepareCtx.stmt.placeholders().get(i).createLiteralFromType(); + LiteralExpr l = prepareStmt.placeholders().get(i).createLiteralFromType(); l.setupParamFromBinary(packetBuf); realValueExprs.add(l); } @@ -149,7 +135,7 @@ private void handleExecute() { ctx.setExecutor(executor); executor.execute(); PrepareStmtContext preparedStmtContext = ConnectContext.get().getPreparedStmt(String.valueOf(stmtId)); - if (preparedStmtContext != null && !(preparedStmtContext.stmt.getInnerStmt() instanceof InsertStmt)) { + if (preparedStmtContext != null) { stmtStr = executeStmt.toSql(); } } catch (Throwable e) { @@ -159,8 +145,101 @@ private void handleExecute() { ctx.getState().setError(ErrorCode.ERR_UNKNOWN_ERROR, e.getClass().getSimpleName() + ", msg: " + e.getMessage()); } - if (!stmtStr.isEmpty()) { - auditAfterExec(stmtStr, prepareCtx.stmt.getInnerStmt(), null, false); + auditAfterExec(stmtStr, executor.getParsedStmt(), executor.getQueryStatisticsForAuditLog(), true); + } + + private void handleExecute(PrepareCommand prepareCommand, long stmtId, PreparedStatementContext prepCtx) { + int paramCount = prepareCommand.placeholderCount(); + LOG.debug("execute prepared statement {}, paramCount {}", stmtId, paramCount); + // null bitmap + String stmtStr = ""; + try { + StatementContext statementContext = prepCtx.statementContext; + if (paramCount > 0) { + byte[] nullbitmapData = new byte[(paramCount + 7) / 8]; + packetBuf.get(nullbitmapData); + // new_params_bind_flag + if ((int) packetBuf.get() != 0) { + List typedPlaceholders = new ArrayList<>(); + // parse params's types + for (int i = 0; i < paramCount; ++i) { + int typeCode = packetBuf.getChar(); + LOG.debug("code {}", typeCode); + // assign type to placeholders + typedPlaceholders.add( + prepareCommand.getPlaceholders().get(i) + .withNewMysqlColType(MysqlColType.fromCode(typeCode))); + } + // rewrite with new prepared statment with type info in placeholders + prepCtx.command = prepareCommand.withPlaceholders(typedPlaceholders); + prepareCommand = (PrepareCommand) prepCtx.command; + } + // parse param data + for (int i = 0; i < paramCount; ++i) { + PlaceholderId exprId = prepareCommand.getPlaceholders().get(i).getPlaceholderId(); + if (isNull(nullbitmapData, i)) { + statementContext.getIdToPlaceholderRealExpr().put(exprId, + new org.apache.doris.nereids.trees.expressions.literal.NullLiteral()); + continue; + } + MysqlColType type = prepareCommand.getPlaceholders().get(i).getMysqlColType(); + Literal l = Literal.getLiteralByMysqlType(type, packetBuf); + statementContext.getIdToPlaceholderRealExpr().put(exprId, l); + } + } + ExecuteCommand executeStmt = new ExecuteCommand(String.valueOf(stmtId), prepareCommand, statementContext); + // TODO set real origin statement + if (LOG.isDebugEnabled()) { + LOG.debug("executeStmt {}", executeStmt); + } + StatementBase stmt = new LogicalPlanAdapter(executeStmt, statementContext); + stmt.setOrigStmt(prepareCommand.getOriginalStmt()); + executor = new StmtExecutor(ctx, stmt); + ctx.setExecutor(executor); + executor.execute(); + stmtStr = executeStmt.toSql(); + } catch (Throwable e) { + // Catch all throwable. + // If reach here, maybe doris bug. + LOG.warn("Process one query failed because unknown reason: ", e); + ctx.getState().setError(ErrorCode.ERR_UNKNOWN_ERROR, + e.getClass().getSimpleName() + ", msg: " + e.getMessage()); + } + auditAfterExec(stmtStr, executor.getParsedStmt(), executor.getQueryStatisticsForAuditLog(), true); + } + + // process COM_EXECUTE, parse binary row data + // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_execute.html + private void handleExecute() { + // debugPacket(); + packetBuf = packetBuf.order(ByteOrder.LITTLE_ENDIAN); + // parse stmt_id, flags, params + int stmtId = packetBuf.getInt(); + // flag + packetBuf.get(); + // iteration_count always 1, + packetBuf.getInt(); + if (LOG.isDebugEnabled()) { + LOG.debug("execute prepared statement {}", stmtId); + } + + PrepareStmtContext prepareCtx = ctx.getPreparedStmt(String.valueOf(stmtId)); + ctx.setStartTime(); + if (prepareCtx != null) { + // get from lagacy planner context, to be removed + handleExecute((PrepareStmt) prepareCtx.stmt, stmtId); + } else { + // nererids + PreparedStatementContext preparedStatementContext = ctx.getPreparedStementContext(String.valueOf(stmtId)); + if (preparedStatementContext == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No such statement in context, stmtId:{}", stmtId); + } + ctx.getState().setError(ErrorCode.ERR_UNKNOWN_COM_ERROR, + "msg: Not supported such prepared statement"); + return; + } + handleExecute(preparedStatementContext.command, stmtId, preparedStatementContext); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/PrepareStmtContext.java b/fe/fe-core/src/main/java/org/apache/doris/qe/PrepareStmtContext.java index 400f5047b53ac7..3c3707e8b66c3c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/PrepareStmtContext.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/PrepareStmtContext.java @@ -18,17 +18,15 @@ package org.apache.doris.qe; import org.apache.doris.analysis.Analyzer; -import org.apache.doris.analysis.PrepareStmt; -import org.apache.doris.planner.OriginalPlanner; +import org.apache.doris.analysis.StatementBase; import org.apache.doris.planner.Planner; -import com.google.common.base.Preconditions; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class PrepareStmtContext { private static final Logger LOG = LogManager.getLogger(PrepareStmtContext.class); - public PrepareStmt stmt; + public StatementBase stmt; public ConnectContext ctx; public Planner planner; public Analyzer analyzer; @@ -37,15 +35,11 @@ public class PrepareStmtContext { // Timestamp in millisecond last command starts at protected volatile long startTime; - public PrepareStmtContext(PrepareStmt stmt, ConnectContext ctx, Planner planner, + public PrepareStmtContext(StatementBase stmt, ConnectContext ctx, Planner planner, Analyzer analyzer, String stmtString) { this.stmt = stmt; this.ctx = ctx; this.planner = planner; - // Only support OriginalPlanner for now - if (planner != null) { - Preconditions.checkState(planner instanceof OriginalPlanner); - } this.analyzer = analyzer; this.stmtString = stmtString; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/PreparedStatementContext.java b/fe/fe-core/src/main/java/org/apache/doris/qe/PreparedStatementContext.java new file mode 100644 index 00000000000000..d54b2e5291c9cc --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/PreparedStatementContext.java @@ -0,0 +1,47 @@ +// 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.qe; + +import org.apache.doris.nereids.StatementContext; +import org.apache.doris.nereids.trees.plans.commands.PrepareCommand; + +public class PreparedStatementContext { + public PrepareCommand command; + public ConnectContext ctx; + StatementContext statementContext; + public String stmtString; + + // Timestamp in millisecond last command starts at + protected volatile long startTime; + + public PreparedStatementContext(PrepareCommand command, + ConnectContext ctx, StatementContext statementContext, String stmtString) { + this.command = command; + this.ctx = ctx; + this.statementContext = statementContext; + this.stmtString = stmtString; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime() { + startTime = System.currentTimeMillis(); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java index db14ff1d8e6533..2a7421616f7b46 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java @@ -152,6 +152,7 @@ import org.apache.doris.nereids.trees.plans.commands.DeleteFromUsingCommand; import org.apache.doris.nereids.trees.plans.commands.Forward; import org.apache.doris.nereids.trees.plans.commands.NotAllowFallback; +import org.apache.doris.nereids.trees.plans.commands.PrepareCommand; import org.apache.doris.nereids.trees.plans.commands.UpdateCommand; import org.apache.doris.nereids.trees.plans.commands.insert.BatchInsertIntoTableCommand; import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand; @@ -278,7 +279,7 @@ public class StmtExecutor { private Data.PQueryStatistics.Builder statisticsForAuditLog; private boolean isCached; private String stmtName; - private PrepareStmt prepareStmt = null; + private StatementBase prepareStmt = null; private String mysqlLoadId; // Distinguish from prepare and execute command private boolean isExecuteStmt = false; @@ -680,6 +681,14 @@ private void executeByNereids(TUniqueId queryId) throws Exception { "Nereids only process LogicalPlanAdapter, but parsedStmt is " + parsedStmt.getClass().getName()); context.getState().setNereids(true); LogicalPlan logicalPlan = ((LogicalPlanAdapter) parsedStmt).getLogicalPlan(); + if (context.getCommand() == MysqlCommand.COM_STMT_PREPARE) { + if (isForwardToMaster()) { + throw new UserException("Forward master command is not supported for prepare statement"); + } + logicalPlan = new PrepareCommand(String.valueOf(context.getStmtId()), + logicalPlan, statementContext.getPlaceholders(), originStmt); + + } // when we in transaction mode, we only support insert into command and transaction command if (context.isTxnModel()) { if (!(logicalPlan instanceof BatchInsertIntoTableCommand || logicalPlan instanceof InsertIntoTableCommand @@ -1187,8 +1196,8 @@ public void analyze(TQueryOptions tQueryOptions) throws UserException, Interrupt throw new UserException("Could not execute, since `" + execStmt.getName() + "` not exist"); } // parsedStmt may already by set when constructing this StmtExecutor(); - preparedStmtCtx.stmt.asignValues(execStmt.getArgs()); - parsedStmt = preparedStmtCtx.stmt.getInnerStmt(); + ((PrepareStmt) preparedStmtCtx.stmt).asignValues(execStmt.getArgs()); + parsedStmt = ((PrepareStmt) preparedStmtCtx.stmt).getInnerStmt(); planner = preparedStmtCtx.planner; analyzer = preparedStmtCtx.analyzer; prepareStmt = preparedStmtCtx.stmt; @@ -1196,7 +1205,7 @@ public void analyze(TQueryOptions tQueryOptions) throws UserException, Interrupt LOG.debug("already prepared stmt: {}", preparedStmtCtx.stmtString); } isExecuteStmt = true; - if (!preparedStmtCtx.stmt.needReAnalyze()) { + if (!((PrepareStmt) preparedStmtCtx.stmt).needReAnalyze()) { // Return directly to bypass analyze and plan return; } @@ -1218,15 +1227,15 @@ public void analyze(TQueryOptions tQueryOptions) throws UserException, Interrupt if (parsedStmt instanceof PrepareStmt || context.getCommand() == MysqlCommand.COM_STMT_PREPARE) { if (context.getCommand() == MysqlCommand.COM_STMT_PREPARE) { prepareStmt = new PrepareStmt(parsedStmt, - String.valueOf(context.getEnv().getNextStmtId())); + String.valueOf(String.valueOf(context.getStmtId()))); } else { prepareStmt = (PrepareStmt) parsedStmt; } - prepareStmt.setContext(context); + ((PrepareStmt) prepareStmt).setContext(context); prepareStmt.analyze(analyzer); // Need analyze inner statement - parsedStmt = prepareStmt.getInnerStmt(); - if (prepareStmt.getPreparedType() == PrepareStmt.PreparedType.STATEMENT) { + parsedStmt = ((PrepareStmt) prepareStmt).getInnerStmt(); + if (((PrepareStmt) prepareStmt).getPreparedType() == PrepareStmt.PreparedType.STATEMENT) { // Skip analyze, do it lazy return; } @@ -1344,15 +1353,15 @@ && hasCloudClusterPriv()) { } } if (preparedStmtReanalyzed - && preparedStmtCtx.stmt.getPreparedType() == PrepareStmt.PreparedType.FULL_PREPARED) { - prepareStmt.asignValues(execStmt.getArgs()); + && ((PrepareStmt) preparedStmtCtx.stmt).getPreparedType() == PrepareStmt.PreparedType.FULL_PREPARED) { + ((PrepareStmt) prepareStmt).asignValues(execStmt.getArgs()); if (LOG.isDebugEnabled()) { LOG.debug("update planner and analyzer after prepared statement reanalyzed"); } preparedStmtCtx.planner = planner; preparedStmtCtx.analyzer = analyzer; Preconditions.checkNotNull(preparedStmtCtx.stmt); - preparedStmtCtx.analyzer.setPrepareStmt(preparedStmtCtx.stmt); + preparedStmtCtx.analyzer.setPrepareStmt(((PrepareStmt) preparedStmtCtx.stmt)); } } @@ -1410,9 +1419,9 @@ private void analyzeAndGenerateQueryPlan(TQueryOptions tQueryOptions) throws Use } } if (prepareStmt != null) { - analyzer.setPrepareStmt(prepareStmt); - if (execStmt != null && prepareStmt.getPreparedType() != PreparedType.FULL_PREPARED) { - prepareStmt.asignValues(execStmt.getArgs()); + analyzer.setPrepareStmt(((PrepareStmt) prepareStmt)); + if (execStmt != null && ((PrepareStmt) prepareStmt).getPreparedType() != PreparedType.FULL_PREPARED) { + ((PrepareStmt) prepareStmt).asignValues(execStmt.getArgs()); } } parsedStmt.analyze(analyzer); @@ -1480,9 +1489,10 @@ private void analyzeAndGenerateQueryPlan(TQueryOptions tQueryOptions) throws Use // query re-analyze parsedStmt.reset(); if (prepareStmt != null) { - analyzer.setPrepareStmt(prepareStmt); - if (execStmt != null && prepareStmt.getPreparedType() != PreparedType.FULL_PREPARED) { - prepareStmt.asignValues(execStmt.getArgs()); + analyzer.setPrepareStmt(((PrepareStmt) prepareStmt)); + if (execStmt != null + && ((PrepareStmt) prepareStmt).getPreparedType() != PreparedType.FULL_PREPARED) { + ((PrepareStmt) prepareStmt).asignValues(execStmt.getArgs()); } } analyzer.setReAnalyze(true); @@ -2584,16 +2594,17 @@ private void handleSwitchStmt() throws AnalysisException { } private void handlePrepareStmt() throws Exception { + List labels = ((PrepareStmt) prepareStmt).getColLabelsOfPlaceHolders(); // register prepareStmt if (LOG.isDebugEnabled()) { LOG.debug("add prepared statement {}, isBinaryProtocol {}", - prepareStmt.getName(), context.getCommand() == MysqlCommand.COM_STMT_PREPARE); + prepareStmt.toSql(), context.getCommand() == MysqlCommand.COM_STMT_PREPARE); } - context.addPreparedStmt(prepareStmt.getName(), + context.addPreparedStmt(String.valueOf(context.getStmtId()), new PrepareStmtContext(prepareStmt, - context, planner, analyzer, prepareStmt.getName())); + context, planner, analyzer, String.valueOf(context.getStmtId()))); if (context.getCommand() == MysqlCommand.COM_STMT_PREPARE) { - sendStmtPrepareOK(); + sendStmtPrepareOK((int) context.getStmtId(), labels); } } @@ -2697,19 +2708,19 @@ private List exprToStringType(List exprs) { return exprs.stream().map(e -> PrimitiveType.STRING).collect(Collectors.toList()); } - private void sendStmtPrepareOK() throws IOException { + public void sendStmtPrepareOK(int stmtId, List labels) throws IOException { Preconditions.checkState(context.getConnectType() == ConnectType.MYSQL); // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_prepare.html#sect_protocol_com_stmt_prepare_response serializer.reset(); // 0x00 OK serializer.writeInt1(0); // statement_id - serializer.writeInt4(Integer.valueOf(prepareStmt.getName())); + serializer.writeInt4(stmtId); // num_columns int numColumns = 0; serializer.writeInt2(numColumns); // num_params - int numParams = prepareStmt.getColLabelsOfPlaceHolders().size(); + int numParams = labels.size(); serializer.writeInt2(numParams); // reserved_1 serializer.writeInt1(0); @@ -2718,14 +2729,12 @@ private void sendStmtPrepareOK() throws IOException { // send field one by one // TODO use real type instead of string, for JDBC client it's ok // but for other client, type should be correct - List types = exprToStringType(prepareStmt.getPlaceHolderExprList()); - List colNames = prepareStmt.getColLabelsOfPlaceHolders(); - if (LOG.isDebugEnabled()) { - LOG.debug("sendFields {}, {}", colNames, types); - } + // List types = exprToStringType(labels); + List colNames = labels; for (int i = 0; i < colNames.size(); ++i) { serializer.reset(); - serializer.writeField(colNames.get(i), Type.fromPrimitiveType(types.get(i))); + // serializer.writeField(colNames.get(i), Type.fromPrimitiveType(types.get(i))); + serializer.writeField(colNames.get(i), Type.STRING); context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer()); } serializer.reset(); @@ -2754,14 +2763,15 @@ private void sendFields(List colNames, List types) throws IOExcept // send field one by one for (int i = 0; i < colNames.size(); ++i) { serializer.reset(); - if (prepareStmt != null && isExecuteStmt) { + if (prepareStmt != null && prepareStmt instanceof PrepareStmt + && context.getCommand() == MysqlCommand.COM_STMT_EXECUTE) { // Using PreparedStatment pre serializedField to avoid serialize each time // we send a field - byte[] serializedField = prepareStmt.getSerializedField(colNames.get(i)); + byte[] serializedField = ((PrepareStmt) prepareStmt).getSerializedField(colNames.get(i)); if (serializedField == null) { serializer.writeField(colNames.get(i), types.get(i)); serializedField = serializer.toArray(); - prepareStmt.setSerializedField(colNames.get(i), serializedField); + ((PrepareStmt) prepareStmt).setSerializedField(colNames.get(i), serializedField); } context.getMysqlChannel().sendOnePacket(ByteBuffer.wrap(serializedField)); } else { diff --git a/regression-test/data/prepared_stmt_p0/prepared_stmt.out b/regression-test/data/prepared_stmt_p0/prepared_stmt.out index 396ee931683c9e..bd01fbf2c59582 100644 --- a/regression-test/data/prepared_stmt_p0/prepared_stmt.out +++ b/regression-test/data/prepared_stmt_p0/prepared_stmt.out @@ -1,30 +1,30 @@ -- This file is automatically generated. You should know what you did if you want to edit this -- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01 12:36:38 \N 1022-01-01 ["2022-01-01 11:30:38", "2022-01-01 11:30:38", "2022-01-01 11:30:38"] -1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01 12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] -1233 1.392932911 yyy laooq 2024-01-02 2020-01-01 12:36:38 52.862 3022-01-01 ["2024-01-01 11:30:38", "2024-01-01 11:30:38", "2024-01-01 11:30:38"] -1234 12919291.129191137 xxddd laooq 2025-01-02 2020-01-01 12:36:38 552.872 4022-01-01 ["2025-01-01 11:30:38", "2025-01-01 11:30:38", "2025-01-01 11:30:38"] -1235 991129292901.111380000 dd \N 2120-01-02 2020-01-01 12:36:38 652.692 5022-01-01 [] -1236 100320.111390000 laa ddd laooq 2220-01-02 2020-01-01 12:36:38 2.7692 6022-01-01 [null] -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] +1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 ["2022-01-01 11:30:38", "2022-01-01 11:30:38", "2022-01-01 11:30:38"] +1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01T12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] +1233 1.392932911 yyy laooq 2024-01-02 2020-01-01T12:36:38 52.862 3022-01-01 ["2024-01-01 11:30:38", "2024-01-01 11:30:38", "2024-01-01 11:30:38"] +1234 12919291.129191137 xxddd laooq 2025-01-02 2020-01-01T12:36:38 552.872 4022-01-01 ["2025-01-01 11:30:38", "2025-01-01 11:30:38", "2025-01-01 11:30:38"] +1235 991129292901.111380000 dd \N 2120-01-02 2020-01-01T12:36:38 652.692 5022-01-01 [] +1236 100320.111390000 laa ddd laooq 2220-01-02 2020-01-01T12:36:38 2.7692 6022-01-01 [null] +1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] -- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01 12:36:38 \N 1022-01-01 ["2022-01-01 11:30:38", "2022-01-01 11:30:38", "2022-01-01 11:30:38"] -1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01 12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] -1233 1.392932911 yyy laooq 2024-01-02 2020-01-01 12:36:38 52.862 3022-01-01 ["2024-01-01 11:30:38", "2024-01-01 11:30:38", "2024-01-01 11:30:38"] -1234 12919291.129191137 xxddd laooq 2025-01-02 2020-01-01 12:36:38 552.872 4022-01-01 ["2025-01-01 11:30:38", "2025-01-01 11:30:38", "2025-01-01 11:30:38"] -1235 991129292901.111380000 dd \N 2120-01-02 2020-01-01 12:36:38 652.692 5022-01-01 [] -1236 100320.111390000 laa ddd laooq 2220-01-02 2020-01-01 12:36:38 2.7692 6022-01-01 [null] -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] +1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 ["2022-01-01 11:30:38", "2022-01-01 11:30:38", "2022-01-01 11:30:38"] +1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01T12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] +1233 1.392932911 yyy laooq 2024-01-02 2020-01-01T12:36:38 52.862 3022-01-01 ["2024-01-01 11:30:38", "2024-01-01 11:30:38", "2024-01-01 11:30:38"] +1234 12919291.129191137 xxddd laooq 2025-01-02 2020-01-01T12:36:38 552.872 4022-01-01 ["2025-01-01 11:30:38", "2025-01-01 11:30:38", "2025-01-01 11:30:38"] +1235 991129292901.111380000 dd \N 2120-01-02 2020-01-01T12:36:38 652.692 5022-01-01 [] +1236 100320.111390000 laa ddd laooq 2220-01-02 2020-01-01T12:36:38 2.7692 6022-01-01 [null] +1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] -- !select0 -- -1231 119291.110000000 ddd laooq \N 2020-01-01 12:36:38 \N 1022-01-01 ["2022-01-01 11:30:38", "2022-01-01 11:30:38", "2022-01-01 11:30:38"] +1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 ["2022-01-01 11:30:38", "2022-01-01 11:30:38", "2022-01-01 11:30:38"] -- !select0 -- -1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01 12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] +1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01T12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] -- !select0 -- -1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01 12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] +1232 12222.991211350 xxx laooq 2023-01-02 2020-01-01T12:36:38 522.762 2022-01-01 ["2023-01-01 11:30:38", "2023-01-01 11:30:38"] -- !select1 -- 646464 xxxx--- @@ -36,13 +36,13 @@ 787878 yyyy--- -- !select2 -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] 1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] +1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] 1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] -- !select2 -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] 1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] +1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] 1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] -- !select2 -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] 1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01 12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] +1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] 1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 ["2025-01-01 11:30:38"] -- !select3 -- 1 1 user1 30 1234 12345 @@ -53,3 +53,16 @@ -- !select5 -- 1 +-- !select6 -- +2 1 user1 \N 1234.1111 xxxlalala + +-- !select7 -- +2 1 user1 \N 1111111 1111111 + +-- !select8 -- +1 +1 + +-- !select9 -- +2 + diff --git a/regression-test/suites/point_query_p0/test_point_query.groovy b/regression-test/suites/point_query_p0/test_point_query.groovy index 8d1b62108bc2c1..7bea03fc9f5eed 100644 --- a/regression-test/suites/point_query_p0/test_point_query.groovy +++ b/regression-test/suites/point_query_p0/test_point_query.groovy @@ -17,7 +17,7 @@ import java.math.BigDecimal; -suite("test_point_query") { +suite("test_point_query", "nonConcurrent") { def backendId_to_backendIP = [:] def backendId_to_backendHttpPort = [:] getBackendIpHttpPort(backendId_to_backendIP, backendId_to_backendHttpPort); @@ -30,7 +30,7 @@ suite("test_point_query") { try { set_be_config.call("disable_storage_row_cache", "false") // nereids do not support point query now - sql """set enable_nereids_planner=false""" + sql """set global enable_nereids_planner=false""" def user = context.config.jdbcUser def password = context.config.jdbcPassword @@ -274,5 +274,6 @@ suite("test_point_query") { sql "select /*+ SET_VAR(enable_nereids_planner=false) */ substr(RPTNO,2,5) from test_ODS_EBA_LLREPORT where RPTNO = '567890'" } finally { set_be_config.call("disable_storage_row_cache", "true") + sql """set global enable_nereids_planner=true""" } } \ No newline at end of file diff --git a/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy b/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy index 46f8d7629caf10..f4c13fd04b06f3 100644 --- a/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy +++ b/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy @@ -22,158 +22,117 @@ suite("test_prepared_stmt", "nonConcurrent") { def user = context.config.jdbcUser def password = context.config.jdbcPassword def url = context.config.jdbcUrl + "&useServerPrepStmts=true" - // def url = context.config.jdbcUrl - def result1 = connect(user=user, password=password, url=url) { sql "set global enable_server_side_prepared_statement = true" - def insert_prepared = { stmt, k1 , k2, k3, k4, k5, k6, k7, k8, k9 -> - java.text.SimpleDateFormat formater = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - if (k1 == null) { - stmt.setNull(1, java.sql.Types.INTEGER); - } else { - stmt.setInt(1, k1) - } - if (k2 == null) { - stmt.setNull(2, java.sql.Types.DECIMAL); - } else { - stmt.setBigDecimal(2, k2) - } - if (k3 == null) { - stmt.setNull(3, java.sql.Types.VARCHAR); - } else { - stmt.setString(3, k3) - } - if (k4 == null) { - stmt.setNull(4, java.sql.Types.VARCHAR); - } else { - stmt.setString(4, k4) - } - if (k5 == null) { - stmt.setNull(5, java.sql.Types.DATE); - } else { - stmt.setDate(5, java.sql.Date.valueOf(k5)) - } - if (k6 == null) { - stmt.setNull(6, java.sql.Types.TIMESTAMP); - } else { - stmt.setTimestamp(6, new java.sql.Timestamp(formater.parse(k6).getTime())) - } - if (k7 == null) { - stmt.setNull(7, java.sql.Types.FLOAT); - } else { - stmt.setFloat(7, k7) - } - if (k8 == null) { - stmt.setNull(8, java.sql.Types.DATE); - } else { - stmt.setTimestamp(8, new java.sql.Timestamp(formater.parse(k8).getTime())) - } - if (k9 == null) { - stmt.setNull(9, java.sql.Types.VARCHAR); - } else { - stmt.setString(9, k9) - } - exec stmt - } - - sql """DROP TABLE IF EXISTS ${tableName} """ - sql """ - CREATE TABLE IF NOT EXISTS ${tableName} ( - `k1` int(11) NULL COMMENT "", - `k2` decimalv3(27, 9) NULL COMMENT "", - `k3` varchar(30) NULL COMMENT "", - `k4` varchar(30) NULL COMMENT "", - `k5` date NULL COMMENT "", - `k6` datetime NULL COMMENT "", - `k7` float NULL COMMENT "", - `k8` datev2 NULL COMMENT "", - `k9` array NULL COMMENT "" - ) ENGINE=OLAP - DUPLICATE KEY(`k1`, `k2`, `k3`) - DISTRIBUTED BY HASH(`k1`, k2, k3) BUCKETS 1 - PROPERTIES ( - "replication_allocation" = "tag.location.default: 1", - "light_schema_change" = "true", - "storage_format" = "V2" - ) - """ - - // def insert_stmt = prepareStatement """ INSERT INTO ${tableName} VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) """ - // assertEquals(insert_stmt.class, com.mysql.cj.jdbc.ServerPreparedStatement); - // insert_prepared insert_stmt, 1231, 119291.11, "ddd", "laooq", null, "2020-01-01 12:36:38", null, "1022-01-01 11:30:38", "[2022-01-01 11:30:38, 2022-01-01 11:30:38, 2022-01-01 11:30:38]" - // insert_prepared insert_stmt, 1232, 12222.99121135, "xxx", "laooq", "2023-01-02", "2020-01-01 12:36:38", 522.762, "2022-01-01 11:30:38", "[2023-01-01 11:30:38, 2023-01-01 11:30:38]" - // insert_prepared insert_stmt, 1233, 1.392932911136, "yyy", "laooq", "2024-01-02", "2020-01-01 12:36:38", 52.862, "3022-01-01 11:30:38", "[2024-01-01 11:30:38, 2024-01-01 11:30:38, 2024-01-01 11:30:38]" - // insert_prepared insert_stmt, 1234, 12919291.129191137, "xxddd", "laooq", "2025-01-02", "2020-01-01 12:36:38", 552.872, "4022-01-01 11:30:38", "[2025-01-01 11:30:38, 2025-01-01 11:30:38, 2025-01-01 11:30:38]" - // insert_prepared insert_stmt, 1235, 991129292901.11138, "dd", null, "2120-01-02", "2020-01-01 12:36:38", 652.692, "5022-01-01 11:30:38", "[]" - // insert_prepared insert_stmt, 1236, 100320.11139, "laa ddd", "laooq", "2220-01-02", "2020-01-01 12:36:38", 2.7692, "6022-01-01 11:30:38", "[null]" - // insert_prepared insert_stmt, 1237, 120939.11130, "a ddd", "laooq", "2030-01-02", "2020-01-01 12:36:38", 22.822, "7022-01-01 11:30:38", "[2025-01-01 11:30:38]" + def result1 = connect(user=user, password=password, url=url) { + sql """DROP TABLE IF EXISTS ${tableName} """ + sql """ + CREATE TABLE IF NOT EXISTS ${tableName} ( + `k1` int(11) NULL COMMENT "", + `k2` decimalv3(27, 9) NULL COMMENT "", + `k3` varchar(30) NULL COMMENT "", + `k4` varchar(30) NULL COMMENT "", + `k5` date NULL COMMENT "", + `k6` datetime NULL COMMENT "", + `k7` float NULL COMMENT "", + `k8` datev2 NULL COMMENT "", + `k9` array NULL COMMENT "" + ) ENGINE=OLAP + DUPLICATE KEY(`k1`, `k2`, `k3`) + DISTRIBUTED BY HASH(`k1`, k2, k3) BUCKETS 1 + PROPERTIES ( + "replication_allocation" = "tag.location.default: 1", + "light_schema_change" = "true", + "storage_format" = "V2" + ) + """ - sql """ INSERT INTO ${tableName} VALUES(1231, 119291.11, "ddd", "laooq", null, "2020-01-01 12:36:38", null, "1022-01-01 11:30:38", "[2022-01-01 11:30:38, 2022-01-01 11:30:38, 2022-01-01 11:30:38]") """ - sql """ INSERT INTO ${tableName} VALUES(1232, 12222.99121135, "xxx", "laooq", "2023-01-02", "2020-01-01 12:36:38", 522.762, "2022-01-01 11:30:38", "[2023-01-01 11:30:38, 2023-01-01 11:30:38]") """ - sql """ INSERT INTO ${tableName} VALUES(1233, 1.392932911136, "yyy", "laooq", "2024-01-02", "2020-01-01 12:36:38", 52.862, "3022-01-01 11:30:38", "[2024-01-01 11:30:38, 2024-01-01 11:30:38, 2024-01-01 11:30:38]") """ - sql """ INSERT INTO ${tableName} VALUES(1234, 12919291.129191137, "xxddd", "laooq", "2025-01-02", "2020-01-01 12:36:38", 552.872, "4022-01-01 11:30:38", "[2025-01-01 11:30:38, 2025-01-01 11:30:38, 2025-01-01 11:30:38]") """ - sql """ INSERT INTO ${tableName} VALUES(1235, 991129292901.11138, "dd", null, "2120-01-02", "2020-01-01 12:36:38", 652.692, "5022-01-01 11:30:38", "[]") """ - sql """ INSERT INTO ${tableName} VALUES(1236, 100320.11139, "laa ddd", "laooq", "2220-01-02", "2020-01-01 12:36:38", 2.7692, "6022-01-01 11:30:38", "[null]") """ - sql """ INSERT INTO ${tableName} VALUES(1237, 120939.11130, "a ddd", "laooq", "2030-01-02", "2020-01-01 12:36:38", 22.822, "7022-01-01 11:30:38", "[2025-01-01 11:30:38]") """ - sql """sync""" + sql """ INSERT INTO ${tableName} VALUES(1231, 119291.11, "ddd", "laooq", null, "2020-01-01 12:36:38", null, "1022-01-01 11:30:38", "[2022-01-01 11:30:38, 2022-01-01 11:30:38, 2022-01-01 11:30:38]") """ + sql """ INSERT INTO ${tableName} VALUES(1232, 12222.99121135, "xxx", "laooq", "2023-01-02", "2020-01-01 12:36:38", 522.762, "2022-01-01 11:30:38", "[2023-01-01 11:30:38, 2023-01-01 11:30:38]") """ + sql """ INSERT INTO ${tableName} VALUES(1233, 1.392932911136, "yyy", "laooq", "2024-01-02", "2020-01-01 12:36:38", 52.862, "3022-01-01 11:30:38", "[2024-01-01 11:30:38, 2024-01-01 11:30:38, 2024-01-01 11:30:38]") """ + sql """ INSERT INTO ${tableName} VALUES(1234, 12919291.129191137, "xxddd", "laooq", "2025-01-02", "2020-01-01 12:36:38", 552.872, "4022-01-01 11:30:38", "[2025-01-01 11:30:38, 2025-01-01 11:30:38, 2025-01-01 11:30:38]") """ + sql """ INSERT INTO ${tableName} VALUES(1235, 991129292901.11138, "dd", null, "2120-01-02", "2020-01-01 12:36:38", 652.692, "5022-01-01 11:30:38", "[]") """ + sql """ INSERT INTO ${tableName} VALUES(1236, 100320.11139, "laa ddd", "laooq", "2220-01-02", "2020-01-01 12:36:38", 2.7692, "6022-01-01 11:30:38", "[null]") """ + sql """ INSERT INTO ${tableName} VALUES(1237, 120939.11130, "a ddd", "laooq", "2030-01-02", "2020-01-01 12:36:38", 22.822, "7022-01-01 11:30:38", "[2025-01-01 11:30:38]") """ + sql """sync""" - qt_sql """select * from ${tableName} order by 1, 2, 3""" - qt_sql """select * from ${tableName} order by 1, 2, 3""" + qt_sql """select * from ${tableName} order by 1, 2, 3""" + qt_sql """select * from ${tableName} order by 1, 2, 3""" - def stmt_read = prepareStatement "select * from ${tableName} where k1 = ? order by k1" - assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); - stmt_read.setInt(1, 1231) - qe_select0 stmt_read - stmt_read.setInt(1, 1232) - qe_select0 stmt_read - qe_select0 stmt_read - def stmt_read1 = prepareStatement "select hex(k3), ? from ${tableName} where k1 = ? order by 1" - assertEquals(stmt_read1.class, com.mysql.cj.jdbc.ServerPreparedStatement); - stmt_read1.setString(1, "xxxx---") - stmt_read1.setInt(2, 1231) - qe_select1 stmt_read1 - stmt_read1.setString(1, "yyyy---") - stmt_read1.setInt(2, 1232) - qe_select1 stmt_read1 - qe_select1 stmt_read1 - def stmt_read2 = prepareStatement "select * from ${tableName} as t1 join ${tableName} as t2 on t1.`k1` = t2.`k1` where t1.`k1` >= ? and t1.`k2` >= ? and size(t1.`k9`) > ? order by 1, 2, 3" - assertEquals(stmt_read2.class, com.mysql.cj.jdbc.ServerPreparedStatement); - stmt_read2.setInt(1, 1237) - stmt_read2.setBigDecimal(2, new BigDecimal("120939.11130")) - stmt_read2.setInt(3, 0) - qe_select2 stmt_read2 - qe_select2 stmt_read2 - qe_select2 stmt_read2 + def stmt_read = prepareStatement "select * from ${tableName} where k1 = ? order by k1" + assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); + stmt_read.setInt(1, 1231) + qe_select0 stmt_read + stmt_read.setInt(1, 1232) + qe_select0 stmt_read + qe_select0 stmt_read + def stmt_read1 = prepareStatement "select hex(k3), ? \n from ${tableName} where k1 = ? order by 1" + assertEquals(stmt_read1.class, com.mysql.cj.jdbc.ServerPreparedStatement); + stmt_read1.setString(1, "xxxx---") + stmt_read1.setInt(2, 1231) + qe_select1 stmt_read1 + stmt_read1.setString(1, "yyyy---") + stmt_read1.setInt(2, 1232) + qe_select1 stmt_read1 + qe_select1 stmt_read1 + def stmt_read2 = prepareStatement "select * from ${tableName} as t1 join ${tableName} as t2 on t1.`k1` = t2.`k1` where t1.`k1` >= ? and t1.`k2` >= ? and size(t1.`k9`) > ? order by 1, 2, 3" + assertEquals(stmt_read2.class, com.mysql.cj.jdbc.ServerPreparedStatement); + stmt_read2.setInt(1, 1237) + stmt_read2.setBigDecimal(2, new BigDecimal("120939.11130")) + stmt_read2.setInt(3, 0) + qe_select2 stmt_read2 + qe_select2 stmt_read2 + qe_select2 stmt_read2 - sql "DROP TABLE IF EXISTS mytable1" - sql """ - CREATE TABLE mytable1 - ( - siteid INT DEFAULT '10', - citycode SMALLINT, - username VARCHAR(32) DEFAULT '', - pv BIGINT SUM DEFAULT '0' - ) - AGGREGATE KEY(siteid, citycode, username) - DISTRIBUTED BY HASH(siteid) BUCKETS 10 - PROPERTIES("replication_num" = "1"); - """ + sql "DROP TABLE IF EXISTS mytable1" + sql """ + CREATE TABLE mytable1 + ( + siteid INT DEFAULT '10', + citycode SMALLINT, + username VARCHAR(32) DEFAULT '', + pv BIGINT SUM DEFAULT '0' + ) + AGGREGATE KEY(siteid, citycode, username) + DISTRIBUTED BY HASH(siteid) BUCKETS 10 + PROPERTIES("replication_num" = "1"); + """ - sql """insert into mytable1 values(1,1,'user1',10);""" - sql """insert into mytable1 values(1,1,'user1',10);""" - sql """insert into mytable1 values(1,1,'user1',10);""" - sql "sync" - stmt_read = prepareStatement "SELECT *, ? FROM (select *, ? from mytable1 where citycode = ?) AS `SpotfireCustomQuery1` WHERE 1 = 1" - stmt_read.setInt(1, 12345) - stmt_read.setInt(2, 1234) - stmt_read.setInt(3, 1) - qe_select3 stmt_read + sql """insert into mytable1 values(1,1,'user1',10);""" + sql """insert into mytable1 values(1,1,'user1',10);""" + sql """insert into mytable1 values(1,1,'user1',10);""" + sql "sync" + stmt_read = prepareStatement "SELECT *, ? FROM (select *, ? from mytable1 where citycode = ?) AS `SpotfireCustomQuery1` WHERE 1 = 1" + assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); + stmt_read.setInt(1, 12345) + stmt_read.setInt(2, 1234) + stmt_read.setInt(3, 1) + qe_select3 stmt_read + + stmt_read = prepareStatement "SELECT 10" + assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); + qe_select4 stmt_read + stmt_read = prepareStatement "SELECT 1" + assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); + qe_select5 stmt_read + + sql """insert into mytable1 values(2,1,'user1',null);""" + stmt_read = prepareStatement "SELECT *, ? FROM (select *, ? from mytable1 where pv is null) AS `SpotfireCustomQuery1` WHERE 1 = 1" + assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); + stmt_read.setString(1, "xxxlalala") + stmt_read.setDouble(2, 1234.1111) + qe_select6 stmt_read + stmt_read.setString(1, "1111111") + stmt_read.setString(2, "1111111") + qe_select7 stmt_read + + stmt_read = prepareStatement "SELECT COUNT() from mytable1 WHERE citycode = ? GROUP BY siteid" + stmt_read.setString(1, "1") + qe_select8 stmt_read - stmt_read = prepareStatement "SELECT 10" - assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); - qe_select4 stmt_read - stmt_read = prepareStatement "SELECT 1" - assertEquals(stmt_read.class, com.mysql.cj.jdbc.ServerPreparedStatement); - qe_select5 stmt_read + stmt_read = prepareStatement "SELECT COUNT() from mytable1 WHERE citycode = ? GROUP BY ?" + stmt_read.setString(1, "1") + stmt_read.setString(2, "1") + qe_select9 stmt_read } sql "set global enable_server_side_prepared_statement = false"