This is an automated email from the ASF dual-hosted git repository. lide pushed a commit to branch branch-2.0 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.0 by this push: new 7b9b8c97ec6 [Feature](PreparedStatement) implement general server side prepared (#34247) 7b9b8c97ec6 is described below commit 7b9b8c97ec6956590381d93a8a04386f38781d6c Author: lihangyu <15605149...@163.com> AuthorDate: Wed May 8 09:59:26 2024 +0800 [Feature](PreparedStatement) implement general server side prepared (#34247) --- be/src/runtime/runtime_state.h | 5 + be/src/service/point_query_executor.cpp | 1 - be/src/vec/sink/vresult_sink.cpp | 12 +- .../main/java/org/apache/doris/catalog/Type.java | 4 + fe/fe-core/src/main/cup/sql_parser.cup | 8 +- .../java/org/apache/doris/analysis/Analyzer.java | 16 +- .../org/apache/doris/analysis/BinaryPredicate.java | 8 +- .../main/java/org/apache/doris/analysis/Expr.java | 2 +- .../org/apache/doris/analysis/LiteralExpr.java | 16 +- .../apache/doris/analysis/NativeInsertStmt.java | 11 ++ .../org/apache/doris/analysis/PlaceHolderExpr.java | 7 +- .../org/apache/doris/analysis/PrepareStmt.java | 131 ++++++++++------ .../org/apache/doris/analysis/StatementBase.java | 14 +- .../org/apache/doris/analysis/StringLiteral.java | 2 + .../java/org/apache/doris/catalog/OlapTable.java | 11 +- .../java/org/apache/doris/mysql/MysqlProto.java | 1 - .../org/apache/doris/planner/OlapScanNode.java | 10 +- .../java/org/apache/doris/qe/ConnectProcessor.java | 54 +++---- .../main/java/org/apache/doris/qe/Coordinator.java | 3 + .../java/org/apache/doris/qe/PointQueryExec.java | 2 +- .../java/org/apache/doris/qe/SessionVariable.java | 6 + .../java/org/apache/doris/qe/StmtExecutor.java | 44 ++++-- fe/fe-core/src/main/jflex/sql_scanner.flex | 1 - .../data/point_query_p0/test_point_query.out | 36 ----- .../data/prepared_stmt_p0/prepared_stmt.out | 55 +++++++ .../org/apache/doris/regression/suite/Suite.groovy | 6 + .../apache/doris/regression/util/JdbcUtils.groovy | 9 ++ .../suites/point_query_p0/test_point_query.groovy | 12 +- .../suites/prepared_stmt_p0/prepared_stmt.groovy | 168 +++++++++++++++++++++ 29 files changed, 470 insertions(+), 185 deletions(-) diff --git a/be/src/runtime/runtime_state.h b/be/src/runtime/runtime_state.h index 1e7375cbb97..8e57638dee6 100644 --- a/be/src/runtime/runtime_state.h +++ b/be/src/runtime/runtime_state.h @@ -142,6 +142,11 @@ public: _query_options.enable_common_expr_pushdown; } + bool mysql_row_binary_format() const { + return _query_options.__isset.mysql_row_binary_format && + _query_options.mysql_row_binary_format; + } + Status query_status() { std::lock_guard<std::mutex> l(_process_status_lock); return _process_status; diff --git a/be/src/service/point_query_executor.cpp b/be/src/service/point_query_executor.cpp index 20a111b2b38..d9a31b8c9d6 100644 --- a/be/src/service/point_query_executor.cpp +++ b/be/src/service/point_query_executor.cpp @@ -41,7 +41,6 @@ #include "vec/exprs/vexpr.h" #include "vec/exprs/vexpr_context.h" #include "vec/jsonb/serialize.h" -#include "vec/sink/vmysql_result_writer.cpp" #include "vec/sink/vmysql_result_writer.h" namespace doris { diff --git a/be/src/vec/sink/vresult_sink.cpp b/be/src/vec/sink/vresult_sink.cpp index 4c731199d06..e71176a2bfc 100644 --- a/be/src/vec/sink/vresult_sink.cpp +++ b/be/src/vec/sink/vresult_sink.cpp @@ -91,10 +91,16 @@ Status VResultSink::prepare(RuntimeState* state) { // create writer based on sink type switch (_sink_type) { - case TResultSinkType::MYSQL_PROTOCAL: - _writer.reset(new (std::nothrow) - VMysqlResultWriter(_sender.get(), _output_vexpr_ctxs, _profile)); + case TResultSinkType::MYSQL_PROTOCAL: { + if (state->mysql_row_binary_format()) { + _writer.reset(new (std::nothrow) VMysqlResultWriter<true>( + _sender.get(), _output_vexpr_ctxs, _profile)); + } else { + _writer.reset(new (std::nothrow) VMysqlResultWriter<false>( + _sender.get(), _output_vexpr_ctxs, _profile)); + } break; + } default: return Status::InternalError("Unknown result sink type"); } diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java b/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java index ea10972faa2..e5f14a58127 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java +++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java @@ -354,6 +354,10 @@ public abstract class Type { return isScalarType(PrimitiveType.INVALID_TYPE); } + public boolean isUnsupported() { + return isScalarType(PrimitiveType.UNSUPPORTED); + } + public boolean isValid() { return !isInvalid(); } diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup index d31934cc599..3cd8b66b543 100644 --- a/fe/fe-core/src/main/cup/sql_parser.cup +++ b/fe/fe-core/src/main/cup/sql_parser.cup @@ -1159,7 +1159,11 @@ stmt ::= | insert_overwrite_stmt : stmt {: RESULT = stmt; :} | update_stmt : stmt - {: RESULT = stmt; :} + {: + RESULT = stmt; + stmt.setPlaceHolders(parser.placeholder_expr_list); + parser.placeholder_expr_list.clear(); + :} | backup_stmt : stmt {: RESULT = stmt; :} | restore_stmt : stmt @@ -5340,7 +5344,7 @@ expr_or_default ::= prepare_stmt ::= KW_PREPARE variable_name:name KW_FROM select_stmt:s {: - RESULT = new PrepareStmt(s, name, false); + RESULT = new PrepareStmt(s, name); s.setPlaceHolders(parser.placeholder_expr_list); parser.placeholder_expr_list.clear(); :} diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java index 2990e924698..ea6ed101242 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java @@ -593,6 +593,14 @@ public class Analyzer { return callDepth; } + public void setPrepareStmt(PrepareStmt stmt) { + prepareStmt = stmt; + } + + public PrepareStmt getPrepareStmt() { + return prepareStmt; + } + public void setInlineView(boolean inlineView) { isInlineView = inlineView; } @@ -605,14 +613,6 @@ public class Analyzer { explicitViewAlias = alias; } - public void setPrepareStmt(PrepareStmt stmt) { - prepareStmt = stmt; - } - - public PrepareStmt getPrepareStmt() { - return prepareStmt; - } - public String getExplicitViewAlias() { return explicitViewAlias; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/BinaryPredicate.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/BinaryPredicate.java index 3a193213171..ba895061e07 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/BinaryPredicate.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/BinaryPredicate.java @@ -483,11 +483,13 @@ public class BinaryPredicate extends Predicate implements Writable { public void analyzeImpl(Analyzer analyzer) throws AnalysisException { super.analyzeImpl(analyzer); this.checkIncludeBitmap(); - // Ignore placeholder - if (getChild(0) instanceof PlaceHolderExpr || getChild(1) instanceof PlaceHolderExpr) { + // Ignore placeholder, when it type is invalid. + // Invalid type could happen when analyze prepared point query select statement, + // since the value is occupied but not assigned + if ((getChild(0) instanceof PlaceHolderExpr && getChild(0).type == Type.UNSUPPORTED) + || (getChild(1) instanceof PlaceHolderExpr && getChild(1).type == Type.UNSUPPORTED)) { return; } - for (Expr expr : children) { if (expr instanceof Subquery) { Subquery subquery = (Subquery) expr; diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java index f1c2d90270c..765d88148d1 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java @@ -1513,7 +1513,7 @@ public abstract class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl * failure to convert a string literal to a date literal */ public final Expr castTo(Type targetType) throws AnalysisException { - if (this instanceof PlaceHolderExpr && this.type.isInvalid()) { + if (this instanceof PlaceHolderExpr && this.type.isUnsupported()) { return this; } // If the targetType is NULL_TYPE then ignore the cast because NULL_TYPE diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java index 00fd4b898d4..3d8c7f4f06d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/LiteralExpr.java @@ -320,14 +320,6 @@ public abstract class LiteralExpr extends Expr implements Comparable<LiteralExpr return getStringValue(); } - // Parse from binary data, the format follows mysql binary protocal - // see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html. - // Return next offset - public void setupParamFromBinary(ByteBuffer data) { - Preconditions.checkState(false, - "should implement this in derived class. " + this.type.toSql()); - } - public static LiteralExpr getLiteralByMysqlType(int mysqlType) throws AnalysisException { switch (mysqlType) { // MYSQL_TYPE_TINY @@ -438,4 +430,12 @@ public abstract class LiteralExpr extends Expr implements Comparable<LiteralExpr default: throw new AnalysisException("Wrong type from thrift;"); } } + + // Parse from binary data, the format follows mysql binary protocal + // see https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html. + // Return next offset + public void setupParamFromBinary(ByteBuffer data) { + Preconditions.checkState(false, + "should implement this in derived class. " + this.type.toSql()); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/NativeInsertStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/NativeInsertStmt.java index c016d7bd213..7f84dc54f6f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/NativeInsertStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/NativeInsertStmt.java @@ -158,6 +158,17 @@ public class NativeInsertStmt extends InsertStmt { } } + public NativeInsertStmt(NativeInsertStmt other) { + super(other.label, null, null); + this.tblName = other.tblName; + this.targetPartitionNames = other.targetPartitionNames; + this.label = other.label; + this.queryStmt = other.queryStmt; + this.planHints = other.planHints; + this.targetColumnNames = other.targetColumnNames; + this.isValuesOrConstantSelect = other.isValuesOrConstantSelect; + } + public NativeInsertStmt(InsertTarget target, String label, List<String> cols, InsertSource source, List<String> hints) { super(new LabelName(null, label), null, null); diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/PlaceHolderExpr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/PlaceHolderExpr.java index a15e567daaa..55cf1f1bba0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/PlaceHolderExpr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/PlaceHolderExpr.java @@ -39,7 +39,7 @@ public class PlaceHolderExpr extends LiteralExpr { int mysqlTypeCode = -1; public PlaceHolderExpr() { - + type = Type.UNSUPPORTED; } public void setTypeCode(int mysqlTypeCode) { @@ -164,7 +164,10 @@ public class PlaceHolderExpr extends LiteralExpr { @Override public String toSqlImpl() { - return getStringValue(); + if (this.lExpr == null) { + return "?"; + } + return "_placeholder_(" + this.lExpr.toSqlImpl() + ")"; } @Override diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/PrepareStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/PrepareStmt.java index f9bb9e5e058..383728b6fb4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/PrepareStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/PrepareStmt.java @@ -39,41 +39,39 @@ import java.util.Map; import java.util.UUID; public class PrepareStmt extends StatementBase { + // We provide bellow types of prepared statement: + // NONE, which is not prepared + // FULL_PREPARED, which is real prepared, which will cache analyzed statement and planner + // STATEMENT, which only cache statement it self, but need to analyze each time executed. + public enum PreparedType { + NONE, FULL_PREPARED, STATEMENT + } + private static final Logger LOG = LogManager.getLogger(PrepareStmt.class); private StatementBase inner; private String stmtName; - // Cached for better CPU performance, since serialize DescriptorTable and // outputExprs are heavy work private ByteString serializedDescTable; private ByteString serializedOutputExpr; - private TDescriptorTable descTable; + private UUID id; - // whether return binary protocol mysql row or not - private boolean binaryRowFormat; - int schemaVersion = -1; - OlapTable tbl; - ConnectContext context; + private int schemaVersion = -1; + private OlapTable tbl; + private ConnectContext context; + private PreparedType preparedType = PreparedType.STATEMENT; + boolean isPointQueryShortCircuit = false; + + private TDescriptorTable descTable; // Serialized mysql Field, this could avoid serialize mysql field each time sendFields. // Since, serialize fields is too heavy when table is wide Map<String, byte[]> serializedFields = Maps.newHashMap(); - // We provide bellow types of prepared statement: - // NONE, which is not prepared - // FULL_PREPARED, which is really prepared, which will cache analyzed statement and planner - // STATEMENT, which only cache statement itself, but need to analyze each time executed - public enum PreparedType { - NONE, FULL_PREPARED, STATEMENT - } - - private PreparedType preparedType = PreparedType.STATEMENT; - - public PrepareStmt(StatementBase stmt, String name, boolean binaryRowFormat) { + public PrepareStmt(StatementBase stmt, String name) { this.inner = stmt; this.stmtName = name; this.id = UUID.randomUUID(); - this.binaryRowFormat = binaryRowFormat; } public void setContext(ConnectContext ctx) { @@ -81,7 +79,8 @@ public class PrepareStmt extends StatementBase { } public boolean needReAnalyze() { - if (preparedType == PreparedType.FULL_PREPARED && schemaVersion == tbl.getBaseSchemaVersion()) { + if (preparedType == PreparedType.FULL_PREPARED + && schemaVersion == tbl.getBaseSchemaVersion()) { return false; } reset(); @@ -96,10 +95,6 @@ public class PrepareStmt extends StatementBase { return id; } - public boolean isBinaryProtocol() { - return binaryRowFormat; - } - public byte[] getSerializedField(String colName) { return serializedFields.getOrDefault(colName, null); } @@ -142,34 +137,53 @@ public class PrepareStmt extends StatementBase { return serializedOutputExpr; } + public boolean isPointQueryShortCircuit() { + return isPointQueryShortCircuit; + } + @Override public void analyze(Analyzer analyzer) throws UserException { + // TODO support more Statement + if (!(inner instanceof SelectStmt) && !(inner instanceof NativeInsertStmt)) { + throw new UserException("Only support prepare SelectStmt or NativeInsertStmt"); + } + analyzer.setPrepareStmt(this); if (inner instanceof SelectStmt) { - // Use tmpAnalyzer since selectStmt will be reAnalyzed - Analyzer tmpAnalyzer = new Analyzer(context.getEnv(), context); + // Try to use FULL_PREPARED to increase performance SelectStmt selectStmt = (SelectStmt) inner; - inner.analyze(tmpAnalyzer); - if (!selectStmt.checkAndSetPointQuery()) { - throw new UserException("Only support prepare SelectStmt point query now"); + try { + // Use tmpAnalyzer since selectStmt will be reAnalyzed + Analyzer tmpAnalyzer = new Analyzer(context.getEnv(), context); + inner.analyze(tmpAnalyzer); + // Case 1 short circuit point query + if (selectStmt.checkAndSetPointQuery()) { + tbl = (OlapTable) selectStmt.getTableRefs().get(0).getTable(); + schemaVersion = tbl.getBaseSchemaVersion(); + preparedType = PreparedType.FULL_PREPARED; + isPointQueryShortCircuit = true; + LOG.debug("using FULL_PREPARED prepared"); + return; + } + } catch (UserException e) { + LOG.debug("fallback to STATEMENT prepared, {}", e); + } finally { + // will be reanalyzed + selectStmt.reset(); + } + // use session var to decide whether to use full prepared or let user client handle to do fail over + if (preparedType != PreparedType.FULL_PREPARED + && !ConnectContext.get().getSessionVariable().enableServeSidePreparedStatement) { + throw new UserException("Failed to prepare statement" + + "try to set enable_server_side_prepared_statement = true"); } - tbl = (OlapTable) selectStmt.getTableRefs().get(0).getTable(); - schemaVersion = tbl.getBaseSchemaVersion(); - // reset will be reAnalyzed - selectStmt.reset(); - analyzer.setPrepareStmt(this); - // tmpAnalyzer.setPrepareStmt(this); - preparedType = PreparedType.FULL_PREPARED; } else if (inner instanceof NativeInsertStmt) { LabelName label = ((NativeInsertStmt) inner).getLoadLabel(); - if (label == null || Strings.isNullOrEmpty(label.getLabelName())) { - analyzer.setPrepareStmt(this); - preparedType = PreparedType.STATEMENT; - } else { + if (label != null && !Strings.isNullOrEmpty(label.getLabelName())) { throw new UserException("Only support prepare InsertStmt without label now"); } - } else { - throw new UserException("Only support prepare SelectStmt or InsertStmt now"); } + preparedType = PreparedType.STATEMENT; + LOG.debug("using STATEMENT prepared"); } public String getName() { @@ -181,10 +195,6 @@ public class PrepareStmt extends StatementBase { return RedirectStatus.NO_FORWARD; } - public StatementBase getInnerStmt() { - return inner; - } - public List<PlaceHolderExpr> placeholders() { return inner.getPlaceHolders(); } @@ -193,6 +203,10 @@ public class PrepareStmt extends StatementBase { return inner.getPlaceHolders().size(); } + public PreparedType getPreparedType() { + return preparedType; + } + public List<Expr> getPlaceHolderExprList() { ArrayList<Expr> slots = new ArrayList<>(); for (PlaceHolderExpr pexpr : inner.getPlaceHolders()) { @@ -209,6 +223,27 @@ public class PrepareStmt extends StatementBase { return lables; } + public StatementBase getInnerStmt() { + if (preparedType == PreparedType.FULL_PREPARED) { + // For performance reason we could reuse the inner statement when FULL_PREPARED + return inner; + } + // Make a copy of Statement, since anlyze will modify the structure of Statement. + // But we should keep the original statement + if (inner instanceof SelectStmt) { + return new SelectStmt((SelectStmt) inner); + } + if (inner instanceof NativeInsertStmt) { + return new NativeInsertStmt((NativeInsertStmt) inner); + } + // Other statement could reuse the inner statement + return inner; + } + + public int argsSize() { + return inner.getPlaceHolders().size(); + } + public void asignValues(List<LiteralExpr> values) throws UserException { if (values.size() != inner.getPlaceHolders().size()) { throw new UserException("Invalid arguments size " @@ -222,10 +257,6 @@ public class PrepareStmt extends StatementBase { } } - public PreparedType getPreparedType() { - return preparedType; - } - @Override public void reset() { serializedDescTable = null; diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java index 198bf0b8972..26cacc2317c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java @@ -31,13 +31,15 @@ import org.apache.doris.thrift.TQueryOptions; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.List; public abstract class StatementBase implements ParseNode { - + private static final Logger LOG = LogManager.getLogger(StatementBase.class); private String clusterName; // Set this variable if this QueryStmt is the top level query from an EXPLAIN <query> @@ -57,7 +59,6 @@ public abstract class StatementBase implements ParseNode { private UserIdentity userInfo; private boolean isPrepared = false; - // select * from tbl where a = ? and b = ? // `?` is the placeholder private ArrayList<PlaceHolderExpr> placeholders = new ArrayList<>(); @@ -105,14 +106,15 @@ public abstract class StatementBase implements ParseNode { this.explainOptions = options; } - public boolean isExplain() { - return this.explainOptions != null; - } - public void setPlaceHolders(ArrayList<PlaceHolderExpr> placeholders) { + LOG.debug("setPlaceHolders {}", placeholders); this.placeholders = new ArrayList<PlaceHolderExpr>(placeholders); } + public boolean isExplain() { + return this.explainOptions != null; + } + public ArrayList<PlaceHolderExpr> getPlaceHolders() { return this.placeholders; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/StringLiteral.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/StringLiteral.java index 66747e0002f..57bc67fdc3e 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/StringLiteral.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/StringLiteral.java @@ -311,5 +311,7 @@ public class StringLiteral extends LiteralExpr { if (LOG.isDebugEnabled()) { LOG.debug("parsed value '{}'", value); } + // Set it's literal type from binary info + type = Type.VARCHAR; } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java index a6525831197..2595a7f9ae3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java @@ -1980,6 +1980,12 @@ public class OlapTable extends Table { return false; } + public int getBaseSchemaVersion() { + MaterializedIndexMeta baseIndexMeta = indexIdToMeta.get(baseIndexId); + return baseIndexMeta.getSchemaVersion(); + } + + public void setEnableSingleReplicaCompaction(boolean enableSingleReplicaCompaction) { if (tableProperty == null) { tableProperty = new TableProperty(new HashMap<>()); @@ -2122,11 +2128,6 @@ public class OlapTable extends Table { return null; } - public int getBaseSchemaVersion() { - MaterializedIndexMeta baseIndexMeta = indexIdToMeta.get(baseIndexId); - return baseIndexMeta.getSchemaVersion(); - } - public int getIndexSchemaVersion(long indexId) { MaterializedIndexMeta indexMeta = indexIdToMeta.get(indexId); return indexMeta.getSchemaVersion(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java index c463e8f4264..dbc49b1e3bd 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java +++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java @@ -200,7 +200,6 @@ public class MysqlProto { } if (handshakeResponse == null) { - // receive response failed. return false; } if (capability.isDeprecatedEOF()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java index 68be7541043..f7263980136 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java @@ -30,6 +30,7 @@ import org.apache.doris.analysis.InPredicate; import org.apache.doris.analysis.IntLiteral; import org.apache.doris.analysis.LiteralExpr; import org.apache.doris.analysis.PartitionNames; +import org.apache.doris.analysis.PrepareStmt; import org.apache.doris.analysis.SlotDescriptor; import org.apache.doris.analysis.SlotId; import org.apache.doris.analysis.SlotRef; @@ -204,6 +205,7 @@ public class OlapScanNode extends ScanNode { // only used in short circuit plan at present private final PartitionPruneV2ForShortCircuitPlan cachedPartitionPruner = new PartitionPruneV2ForShortCircuitPlan(); + PrepareStmt preparedStatment = null; // Constructs node to scan given data files of table 'tbl'. public OlapScanNode(PlanNodeId id, TupleDescriptor desc, String planNodeName) { @@ -544,9 +546,9 @@ public class OlapScanNode extends ScanNode { super.init(analyzer); filterDeletedRows(analyzer); - // lazy evaluation, since stmt is a prepared statment - isFromPrepareStmt = analyzer.getPrepareStmt() != null; - if (!isFromPrepareStmt) { + // point query could do lazy evaluation, since stmt is a prepared statment + preparedStatment = analyzer.getPrepareStmt(); + if (preparedStatment == null || !preparedStatment.isPointQueryShortCircuit()) { computeColumnsFilter(); computePartitionInfo(); } @@ -606,7 +608,7 @@ public class OlapScanNode extends ScanNode { } // prepare stmt evaluate lazily in Coordinator execute - if (!isFromPrepareStmt) { + if (preparedStatment == null || !preparedStatment.isPointQueryShortCircuit()) { try { createScanRangeLocations(); } catch (AnalysisException e) { 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 bc1ba7ac655..a6358675bc7 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 @@ -222,44 +222,35 @@ public class ConnectProcessor { packetBuf.get(); // iteration_count always 1, packetBuf.getInt(); - LOG.debug("execute prepared statement {}", stmtId); PrepareStmtContext prepareCtx = ctx.getPreparedStmt(String.valueOf(stmtId)); - if (prepareCtx == null) { - 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) { - ctx.getState().setIsQuery(true); - } - prepareCtx.stmt.setIsPrepared(); int paramCount = prepareCtx.stmt.getParmCount(); + LOG.debug("execute prepared statement {}, paramCount {}", stmtId, paramCount); // null bitmap - byte[] nullbitmapData = new byte[(paramCount + 7) / 8]; - packetBuf.get(nullbitmapData); String stmtStr = ""; try { - // new_params_bind_flag - if ((int) packetBuf.get() != 0) { - // parse params's types - for (int i = 0; i < paramCount; ++i) { - int typeCode = packetBuf.getChar(); - LOG.debug("code {}", typeCode); - prepareCtx.stmt.placeholders().get(i).setTypeCode(typeCode); - } - } List<LiteralExpr> realValueExprs = new ArrayList<>(); - // parse param data - for (int i = 0; i < paramCount; ++i) { - if (isNull(nullbitmapData, i)) { - realValueExprs.add(new NullLiteral()); - continue; + if (paramCount > 0) { + byte[] nullbitmapData = new byte[(paramCount + 7) / 8]; + packetBuf.get(nullbitmapData); + // new_params_bind_flag + if ((int) packetBuf.get() != 0) { + // parse params's types + for (int i = 0; i < paramCount; ++i) { + int typeCode = packetBuf.getChar(); + LOG.debug("code {}", typeCode); + prepareCtx.stmt.placeholders().get(i).setTypeCode(typeCode); + } + } + // parse param data + for (int i = 0; i < paramCount; ++i) { + if (isNull(nullbitmapData, i)) { + realValueExprs.add(new NullLiteral()); + continue; + } + LiteralExpr l = prepareCtx.stmt.placeholders().get(i).createLiteralFromType(); + l.setupParamFromBinary(packetBuf); + realValueExprs.add(l); } - LiteralExpr l = prepareCtx.stmt.placeholders().get(i).createLiteralFromType(); - l.setupParamFromBinary(packetBuf); - realValueExprs.add(l); } ExecuteStmt executeStmt = new ExecuteStmt(String.valueOf(stmtId), realValueExprs); // TODO set real origin statement @@ -571,7 +562,6 @@ public class ConnectProcessor { LOG.debug("handle command {}", command); ctx.setCommand(command); ctx.setStartTime(); - switch (command) { case COM_INIT_DB: handleInitDb(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java index 164072c0b5c..e2889310428 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java @@ -36,6 +36,7 @@ import org.apache.doris.common.util.RuntimeProfile; import org.apache.doris.common.util.TimeUtils; import org.apache.doris.load.loadv2.LoadJob; import org.apache.doris.metric.MetricRepo; +import org.apache.doris.mysql.MysqlCommand; import org.apache.doris.nereids.stats.StatsErrorEstimator; import org.apache.doris.planner.DataPartition; import org.apache.doris.planner.DataSink; @@ -373,6 +374,8 @@ public class Coordinator implements CoordInterface { this.queryOptions.setQueryTimeout(context.getExecTimeout()); this.queryOptions.setExecutionTimeout(context.getExecTimeout()); this.queryOptions.setEnableScanNodeRunSerial(context.getSessionVariable().isEnableScanRunSerial()); + this.queryOptions.setMysqlRowBinaryFormat( + context.getCommand() == MysqlCommand.COM_STMT_EXECUTE); } public long getJobId() { diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExec.java b/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExec.java index f5712d3a92b..634a4967d85 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExec.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/PointQueryExec.java @@ -113,7 +113,7 @@ public class PointQueryExec implements CoordInterface { this.cacheID = prepareStmt.getID(); this.serializedDescTable = prepareStmt.getSerializedDescTable(); this.serializedOutputExpr = prepareStmt.getSerializedOutputExprs(); - this.isBinaryProtocol = prepareStmt.isBinaryProtocol(); + this.isBinaryProtocol = true; } else { // TODO // planner.getDescTable().toThrift(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java index cfa7fc64e24..56743ba04c5 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java @@ -116,6 +116,8 @@ public class SessionVariable implements Serializable, Writable { public static final String ENABLE_INSERT_STRICT = "enable_insert_strict"; public static final String ENABLE_SPILLING = "enable_spilling"; public static final String ENABLE_EXCHANGE_NODE_PARALLEL_MERGE = "enable_exchange_node_parallel_merge"; + + public static final String ENABLE_SERVER_SIDE_PREPARED_STATEMENT = "enable_server_side_prepared_statement"; public static final String PREFER_JOIN_METHOD = "prefer_join_method"; public static final String ENABLE_FOLD_CONSTANT_BY_BE = "enable_fold_constant_by_be"; @@ -1078,6 +1080,10 @@ public class SessionVariable implements Serializable, Writable { @VariableMgr.VarAttr(name = TOPN_OPT_LIMIT_THRESHOLD) public long topnOptLimitThreshold = 1024; + @VariableMgr.VarAttr(name = ENABLE_SERVER_SIDE_PREPARED_STATEMENT, needForward = true, description = { + "是否启用开启服务端prepared statement", "Set whether to enable server side prepared statement."}) + public boolean enableServeSidePreparedStatement = false; + // Default value is false, which means the group by and having clause // should first use column name not alias. According to mysql. @VariableMgr.VarAttr(name = GROUP_BY_AND_HAVING_USE_ALIAS_FIRST) 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 6ee43bd834b..1f0e4d43d20 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 @@ -117,6 +117,7 @@ import org.apache.doris.load.loadv2.LoadManagerAdapter; import org.apache.doris.mysql.MysqlChannel; import org.apache.doris.mysql.MysqlCommand; import org.apache.doris.mysql.MysqlEofPacket; +import org.apache.doris.mysql.MysqlOkPacket; import org.apache.doris.mysql.MysqlSerializer; import org.apache.doris.mysql.ProxyMysqlChannel; import org.apache.doris.mysql.privilege.PrivPredicate; @@ -958,6 +959,7 @@ public class StmtExecutor { } // continue analyze preparedStmtReanalyzed = true; + preparedStmtCtx.stmt.reset(); preparedStmtCtx.stmt.analyze(analyzer); } @@ -973,7 +975,7 @@ public class StmtExecutor { 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()), true /*binary protocol*/); + String.valueOf(context.getEnv().getNextStmtId())); } else { prepareStmt = (PrepareStmt) parsedStmt; } @@ -1070,7 +1072,8 @@ public class StmtExecutor { throw new AnalysisException("Unexpected exception: " + e.getMessage()); } } - if (preparedStmtReanalyzed) { + if (preparedStmtReanalyzed + && preparedStmtCtx.stmt.getPreparedType() == PrepareStmt.PreparedType.FULL_PREPARED) { LOG.debug("update planner and analyzer after prepared statement reanalyzed"); preparedStmtCtx.planner = planner; preparedStmtCtx.analyzer = analyzer; @@ -1178,6 +1181,11 @@ public class StmtExecutor { Lists.newArrayList(parsedStmt.getColLabels()); // Re-analyze the stmt with a new analyzer. analyzer = new Analyzer(context.getEnv(), context); + if (prepareStmt != null) { + // Re-analyze prepareStmt with a new analyzer + prepareStmt.reset(); + prepareStmt.analyze(analyzer); + } if (prepareStmt != null) { // Re-analyze prepareStmt with a new analyzer @@ -1406,12 +1414,13 @@ public class StmtExecutor { } // handle selects that fe can do without be, so we can make sql tools happy, especially the setup step. - Optional<ResultSet> resultSet = planner.handleQueryInFe(parsedStmt); - if (resultSet.isPresent()) { - sendResultSet(resultSet.get()); - return; + if (context.getCommand() != MysqlCommand.COM_STMT_EXECUTE) { + Optional<ResultSet> resultSet = planner.handleQueryInFe(parsedStmt); + if (resultSet.isPresent()) { + sendResultSet(resultSet.get()); + return; + } } - MysqlChannel channel = context.getMysqlChannel(); boolean isOutfileQuery = queryStmt.hasOutFileClause(); @@ -2088,11 +2097,11 @@ public class StmtExecutor { private void handlePrepareStmt() throws Exception { // register prepareStmt LOG.debug("add prepared statement {}, isBinaryProtocol {}", - prepareStmt.getName(), prepareStmt.isBinaryProtocol()); + prepareStmt.getName(), context.getCommand() == MysqlCommand.COM_STMT_PREPARE); context.addPreparedStmt(prepareStmt.getName(), new PrepareStmtContext(prepareStmt, context, planner, analyzer, prepareStmt.getName())); - if (prepareStmt.isBinaryProtocol()) { + if (context.getCommand() == MysqlCommand.COM_STMT_PREPARE) { sendStmtPrepareOK(); } } @@ -2168,13 +2177,18 @@ public class StmtExecutor { serializer.writeField(colNames.get(i), Type.fromPrimitiveType(types.get(i))); context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer()); } + serializer.reset(); + if (!context.getMysqlChannel().clientDeprecatedEOF()) { + MysqlEofPacket eofPacket = new MysqlEofPacket(context.getState()); + eofPacket.writeTo(serializer); + } else { + MysqlOkPacket okPacket = new MysqlOkPacket(context.getState()); + okPacket.writeTo(serializer); + } + context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer()); } - // send EOF if nessessary - if (!context.getMysqlChannel().clientDeprecatedEOF()) { - context.getState().setEof(); - } else { - context.getState().setOk(); - } + context.getMysqlChannel().flush(); + context.getState().setNoop(); } private void sendFields(List<String> colNames, List<Type> types) throws IOException { diff --git a/fe/fe-core/src/main/jflex/sql_scanner.flex b/fe/fe-core/src/main/jflex/sql_scanner.flex index 96fc35f6d45..583fd916d20 100644 --- a/fe/fe-core/src/main/jflex/sql_scanner.flex +++ b/fe/fe-core/src/main/jflex/sql_scanner.flex @@ -557,7 +557,6 @@ import org.apache.doris.qe.SqlModeHelper; tokenIdMap.put(new Integer(SqlParserSymbols.BITXOR), "^"); tokenIdMap.put(new Integer(SqlParserSymbols.NUMERIC_OVERFLOW), "NUMERIC OVERFLOW"); tokenIdMap.put(new Integer(SqlParserSymbols.PLACEHOLDER), "?"); - } public static boolean isKeyword(Integer tokenId) { diff --git a/regression-test/data/point_query_p0/test_point_query.out b/regression-test/data/point_query_p0/test_point_query.out index d23a62474c3..ff4b1932b3a 100644 --- a/regression-test/data/point_query_p0/test_point_query.out +++ b/regression-test/data/point_query_p0/test_point_query.out @@ -71,18 +71,6 @@ -- !sql -- 6120202020646464 6C616F6F71 32.92200050354004 --- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 \N 1.111 [119181.111100000, 819019.119100000, null] \N 0 0 - --- !sql -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 false 90696620686827832.374 [1.100000000, 2.200000000, 3.300000000, 4.400000000, 5.500000000] [] 0 0 - --- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 \N 1.111 [119181.111100000, 819019.119100000, null] \N 0 0 - --- !sql -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 false 90696620686827832.374 [1.100000000, 2.200000000, 3.300000000, 4.400000000, 5.500000000] [] 0 0 - -- !sql -- 0 1 2 3 @@ -158,18 +146,6 @@ -- !sql -- 6120202020646464 6C616F6F71 32.92200050354004 --- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 \N 1.111 [119181.111100000, 819019.119100000, null] \N 0 0 - --- !sql -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 false 90696620686827832.374 [1.100000000, 2.200000000, 3.300000000, 4.400000000, 5.500000000] [] 0 0 - --- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 \N 1.111 [119181.111100000, 819019.119100000, null] \N 0 0 - --- !sql -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 false 90696620686827832.374 [1.100000000, 2.200000000, 3.300000000, 4.400000000, 5.500000000] [] 0 0 - -- !sql -- 0 1 2 3 @@ -245,18 +221,6 @@ -- !sql -- 6120202020646464 6C616F6F71 32.92200050354004 --- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 \N 1.111 [119181.111100000, 819019.119100000, null] \N 0 0 - --- !sql -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 false 90696620686827832.374 [1.100000000, 2.200000000, 3.300000000, 4.400000000, 5.500000000] [] 0 0 - --- !sql -- -1231 119291.110000000 ddd laooq \N 2020-01-01T12:36:38 \N 1022-01-01 \N 1.111 [119181.111100000, 819019.119100000, null] \N 0 0 - --- !sql -- -1237 120939.111300000 a ddd laooq 2030-01-02 2020-01-01T12:36:38 22.822 7022-01-01 false 90696620686827832.374 [1.100000000, 2.200000000, 3.300000000, 4.400000000, 5.500000000] [] 0 0 - -- !sql -- 0 1 2 3 diff --git a/regression-test/data/prepared_stmt_p0/prepared_stmt.out b/regression-test/data/prepared_stmt_p0/prepared_stmt.out new file mode 100644 index 00000000000..396ee931683 --- /dev/null +++ b/regression-test/data/prepared_stmt_p0/prepared_stmt.out @@ -0,0 +1,55 @@ +-- 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"] + +-- !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"] + +-- !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"] + +-- !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"] + +-- !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"] + +-- !select1 -- +646464 xxxx--- + +-- !select1 -- +787878 yyyy--- + +-- !select1 -- +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"] + +-- !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"] + +-- !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"] + +-- !select3 -- +1 1 user1 30 1234 12345 + +-- !select4 -- +10 + +-- !select5 -- +1 + diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy index 494cf0224b4..7575c185d54 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy @@ -672,6 +672,12 @@ class Suite implements GroovyInterceptable { return result } + List<List<Object>> exec(Object stmt) { + logger.info("Execute sql: ${stmt}".toString()) + def (result, meta )= JdbcUtils.executeToList(context.getConnection(), (PreparedStatement) stmt) + return result + } + void quickRunTest(String tag, Object arg, boolean isOrder = false) { if (context.config.generateOutputFile || context.config.forceGenerateOutputFile) { Tuple2<List<List<Object>>, ResultSetMetaData> tupleResult = null diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy index 8791dd289b3..d52bea3c514 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/JdbcUtils.groovy @@ -41,6 +41,15 @@ class JdbcUtils { return conn.prepareStatement(sql); } + static Tuple2<List<List<Object>>, ResultSetMetaData> executeToList(Connection conn, PreparedStatement stmt) { + boolean hasResultSet = stmt.execute() + if (!hasResultSet) { + return [ImmutableList.of(ImmutableList.of(stmt.getUpdateCount())), null] + } else { + return toList(stmt.resultSet) + } + } + static Tuple2<List<List<Object>>, ResultSetMetaData> executeToStringList(Connection conn, PreparedStatement stmt) { return toStringList(stmt.executeQuery()) } 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 8c01c8cc208..5be89b840b1 100644 --- a/regression-test/suites/point_query_p0/test_point_query.groovy +++ b/regression-test/suites/point_query_p0/test_point_query.groovy @@ -227,13 +227,13 @@ suite("test_point_query") { qt_sql """select /*+ SET_VAR(enable_nereids_planner=false) */ * from ${tableName} where k1 = 1237 and k2 = 120939.11130 and k3 = 'a ddd'""" qt_sql """select /*+ SET_VAR(enable_nereids_planner=false) */ hex(k3), hex(k4), k7 + 10.1 from ${tableName} where k1 = 1237 and k2 = 120939.11130 and k3 = 'a ddd'""" // prepared text - sql """ prepare stmt1 from select * from ${tableName} where k1 = % and k2 = % and k3 = % """ - qt_sql """execute stmt1 using (1231, 119291.11, 'ddd')""" - qt_sql """execute stmt1 using (1237, 120939.11130, 'a ddd')""" + // sql """ prepare stmt1 from select * from ${tableName} where k1 = % and k2 = % and k3 = % """ + // qt_sql """execute stmt1 using (1231, 119291.11, 'ddd')""" + // qt_sql """execute stmt1 using (1237, 120939.11130, 'a ddd')""" - sql """prepare stmt2 from select * from ${tableName} where k1 = % and k2 = % and k3 = %""" - qt_sql """execute stmt2 using (1231, 119291.11, 'ddd')""" - qt_sql """execute stmt2 using (1237, 120939.11130, 'a ddd')""" + // sql """prepare stmt2 from select * from ${tableName} where k1 = % and k2 = % and k3 = %""" + // qt_sql """execute stmt2 using (1231, 119291.11, 'ddd')""" + // qt_sql """execute stmt2 using (1237, 120939.11130, 'a ddd')""" tableName = "test_query" sql """DROP TABLE IF EXISTS ${tableName}""" sql """CREATE TABLE ${tableName} ( diff --git a/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy b/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy new file mode 100644 index 00000000000..7a746f41c97 --- /dev/null +++ b/regression-test/suites/prepared_stmt_p0/prepared_stmt.groovy @@ -0,0 +1,168 @@ +// 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. + +import java.math.BigDecimal; + +suite("test_prepared_stmt", "nonConcurrent") { + def tableName = "tbl_prepared_stmt" + 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<datetime> 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]" + + 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 + + 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);""" + 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 + + 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 "set global enable_server_side_prepared_statement = false" + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org