diff --git a/CHANGES b/CHANGES index 586dd992d..830f4bfe8 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,8 @@ Version 8.4.0 + - WL#15706, Add OpenTelemetry tracing. + - WL#16174, Support for VECTOR data type. - Fix for Bug#36380711, Tests failing due to removal of deprecated features. diff --git a/build.xml b/build.xml index 01f2f7723..f97eceecd 100644 --- a/build.xml +++ b/build.xml @@ -276,7 +276,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o - + diff --git a/src/main/core-api/java/com/mysql/cj/MessageBuilder.java b/src/main/core-api/java/com/mysql/cj/MessageBuilder.java index 723a37a4d..001c9e782 100644 --- a/src/main/core-api/java/com/mysql/cj/MessageBuilder.java +++ b/src/main/core-api/java/com/mysql/cj/MessageBuilder.java @@ -32,6 +32,8 @@ public interface MessageBuilder { M buildClose(); + M buildComQuery(M sharedPacket, Session sess, String query, Query callingQuery, String characterEncoding); + M buildComQuery(M sharedPacket, Session sess, PreparedQuery preparedQuery, QueryBindings bindings, String characterEncoding); } diff --git a/src/main/core-api/java/com/mysql/cj/QueryAttributesBindings.java b/src/main/core-api/java/com/mysql/cj/QueryAttributesBindings.java index 5e01d9ca3..6edf3f6e3 100644 --- a/src/main/core-api/java/com/mysql/cj/QueryAttributesBindings.java +++ b/src/main/core-api/java/com/mysql/cj/QueryAttributesBindings.java @@ -34,12 +34,19 @@ public interface QueryAttributesBindings { * * @param name * the query attribute name. - * * @param value * the query attribute value. */ void setAttribute(String name, Object value); + /** + * Removes the specified query attribute from the list of query attributes. + * + * @param name + * the query attribute name. + */ + void removeAttribute(String name); + /** * Get the count of query attributes in the list. * @@ -68,6 +75,16 @@ public interface QueryAttributesBindings { */ void runThroughAll(Consumer bindAttribute); + /** + * Checks if there's already an attribute with the specified name. + * + * @param name + * the query attribute name. + * @return + * true if the specified attribute name already exists. + */ + boolean containsAttribute(String name); + /** * Removes all query attributes from the query attributes list. */ diff --git a/src/main/core-api/java/com/mysql/cj/QueryInfo.java b/src/main/core-api/java/com/mysql/cj/QueryInfo.java index ba4ef5446..94b0f382f 100644 --- a/src/main/core-api/java/com/mysql/cj/QueryInfo.java +++ b/src/main/core-api/java/com/mysql/cj/QueryInfo.java @@ -41,6 +41,7 @@ public class QueryInfo { private static final String INSERT_STATEMENT = "INSERT"; private static final String REPLACE_STATEMENT = "REPLACE"; + private static final String MULTIPLE_QUERIES_TAG = "(multiple queries)"; private static final String VALUE_CLAUSE = "VALUE"; private static final String AS_CLAUSE = "AS"; @@ -55,6 +56,7 @@ public class QueryInfo { private int queryLength = 0; private int queryStartPos = 0; private char statementFirstChar = Character.MIN_VALUE; + private String statementKeyword = ""; private int batchCount = 1; private int numberOfPlaceholders = 0; private int numberOfQueries = 0; @@ -102,17 +104,23 @@ public QueryInfo(String sql, Session session, String encoding) { } else { this.numberOfQueries = 1; this.statementFirstChar = Character.toUpperCase(strInspector.getChar()); + + // Capture the statement keyword. + int endStatementKeyword = 0; + int nextChar = this.queryStartPos; + StringBuilder sbStatementKeyword = new StringBuilder(); + do { + sbStatementKeyword.append(Character.toUpperCase(strInspector.getChar())); + endStatementKeyword = nextChar + 1; + strInspector.incrementPosition(); + nextChar = strInspector.indexOfNextChar(); + } while (nextChar == endStatementKeyword); + this.statementKeyword = sbStatementKeyword.toString(); } // Only INSERT and REPLACE statements support multi-values clause rewriting. - boolean isInsert = strInspector.matchesIgnoreCase(INSERT_STATEMENT) != -1; - if (isInsert) { - strInspector.incrementPosition(INSERT_STATEMENT.length()); // Advance to the end of "INSERT". - } - boolean isReplace = !isInsert && strInspector.matchesIgnoreCase(REPLACE_STATEMENT) != -1; - if (isReplace) { - strInspector.incrementPosition(REPLACE_STATEMENT.length()); // Advance to the end of "REPLACE". - } + boolean isInsert = INSERT_STATEMENT.equalsIgnoreCase(this.statementKeyword); + boolean isReplace = !isInsert && REPLACE_STATEMENT.equalsIgnoreCase(this.statementKeyword); // Check if the statement has potential to be rewritten as a multi-values clause statement, i.e., if it is an INSERT or REPLACE statement and // 'rewriteBatchedStatements' is enabled. @@ -338,6 +346,10 @@ public QueryInfo(String sql, Session session, String encoding) { int length = end - begin; this.staticSqlParts[i] = StringUtils.getBytes(this.sql, begin, length, this.encoding); } + + if (this.numberOfQueries > 1) { + this.statementKeyword = MULTIPLE_QUERIES_TAG; + } } /** @@ -358,6 +370,7 @@ private QueryInfo(QueryInfo baseQueryInfo, int batchCount) { this.queryLength = 0; this.queryStartPos = this.baseQueryInfo.queryStartPos; this.statementFirstChar = this.baseQueryInfo.statementFirstChar; + this.statementKeyword = this.baseQueryInfo.statementKeyword; this.batchCount = batchCount; this.numberOfPlaceholders = this.baseQueryInfo.numberOfPlaceholders * this.batchCount; this.numberOfQueries = 1; @@ -458,6 +471,16 @@ public char getFirstStmtChar() { return this.baseQueryInfo.statementFirstChar; } + /** + * Returns the statement keyword from the query used to build this {@link QueryInfo}. + * + * @return + * the statement keyword + */ + public String getStatementKeyword() { + return this.statementKeyword; + } + /** * If this object represents a query that is re-writable as a multi-values statement and if rewriting batched statements is enabled, then returns the * length of the parsed VALUES clause section, including the placeholder characters themselves, otherwise returns -1. @@ -585,6 +608,32 @@ public static char firstCharOfStatementUc(String sql, boolean noBackslashEscapes return Character.toUpperCase(sql.charAt(statementKeywordPos)); } + /** + * Finds and returns the statement keyword from the specified SQL, skipping comments and quoted text. + * + * @param sql + * the query to search + * @param noBackslashEscapes + * whether backslash escapes are disabled or not + * @return the statement keyword of the query + */ + public static String getStatementKeyword(String sql, boolean noBackslashEscapes) { + StringInspector strInspector = new StringInspector(sql, 0, OPENING_MARKERS, CLOSING_MARKERS, OVERRIDING_MARKERS, + noBackslashEscapes ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); + int begin = strInspector.indexOfNextAlphanumericChar(); + if (begin == -1) { + return ""; + } + int end = 0; + int nextChar = begin; + do { + end = nextChar + 1; + strInspector.incrementPosition(); + nextChar = strInspector.indexOfNextChar(); + } while (nextChar == end); + return sql.substring(begin, end).toUpperCase(); + } + /** * Checks whether the given query is safe to run in a read-only session. In case of doubt it is assumed to be safe. This operation does not take into * consideration the multiplicity of queries in the specified SQL. diff --git a/src/main/core-api/java/com/mysql/cj/Session.java b/src/main/core-api/java/com/mysql/cj/Session.java index c3b0f792e..6d567e15f 100644 --- a/src/main/core-api/java/com/mysql/cj/Session.java +++ b/src/main/core-api/java/com/mysql/cj/Session.java @@ -38,6 +38,7 @@ import com.mysql.cj.protocol.ResultBuilder; import com.mysql.cj.protocol.ServerSession; import com.mysql.cj.result.Row; +import com.mysql.cj.telemetry.TelemetryHandler; /** * {@link Session} exposes logical level which user API uses internally to call {@link Protocol} methods. @@ -111,6 +112,26 @@ public interface Session { */ ProfilerEventHandler getProfilerEventHandler(); + /** + * Returns the comment that will be prepended to all statements sent to the server. + * + * @return query comment string + */ + String getQueryComment(); + + /** + * Sets the comment that will be prepended to all statements sent to the server. Do not use slash-star or star-slash tokens in the comment as these will be + * added by the driver itself. + * + * @param comment + * query comment string + */ + void setQueryComment(String comment); + + void setTelemetryHandler(TelemetryHandler telemetryHandler); + + TelemetryHandler getTelemetryHandler(); + HostInfo getHostInfo(); String getQueryTimingUnits(); diff --git a/src/main/core-api/java/com/mysql/cj/conf/PropertyDefinitions.java b/src/main/core-api/java/com/mysql/cj/conf/PropertyDefinitions.java index bd056d906..4d8c5431e 100644 --- a/src/main/core-api/java/com/mysql/cj/conf/PropertyDefinitions.java +++ b/src/main/core-api/java/com/mysql/cj/conf/PropertyDefinitions.java @@ -142,6 +142,10 @@ public enum SslMode { PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, DISABLED; } + public enum OpenTelemetry { + PREFERRED, REQUIRED, DISABLED; + } + public enum XdevapiSslMode { REQUIRED, VERIFY_CA, VERIFY_IDENTITY, DISABLED; } @@ -158,14 +162,13 @@ public enum DatabaseTerm { CATALOG, SCHEMA; } + private static String STANDARD_LOGGER_NAME = StandardLogger.class.getName(); + /** * Static unmodifiable {@link PropertyKey} -> {@link PropertyDefinition} map. */ public static final Map> PROPERTY_KEY_TO_PROPERTY_DEFINITION; - static { - String STANDARD_LOGGER_NAME = StandardLogger.class.getName(); - PropertyDefinition[] pdefs = new PropertyDefinition[] { // // CATEGORY_AUTHENTICATION @@ -801,6 +804,9 @@ public enum DatabaseTerm { new BooleanPropertyDefinition(PropertyKey.autoGenerateTestcaseScript, DEFAULT_VALUE_FALSE, RUNTIME_MODIFIABLE, Messages.getString("ConnectionProperties.autoGenerateTestcaseScript"), "3.1.9", CATEGORY_DEBUGING_PROFILING, 18), + new EnumPropertyDefinition<>(PropertyKey.openTelemetry, OpenTelemetry.PREFERRED, RUNTIME_MODIFIABLE, + Messages.getString("ConnectionProperties.openTelemetry"), "8.1.0", CATEGORY_DEBUGING_PROFILING, 19), + // // CATEGORY_EXCEPTIONS // diff --git a/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java b/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java index 2233b6cd2..296c30b2a 100644 --- a/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java +++ b/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java @@ -163,11 +163,11 @@ public enum PropertyKey { nullDatabaseMeansCurrent("nullDatabaseMeansCurrent", "nullCatalogMeansCurrent", true), // ociConfigFile("ociConfigFile", true), // ociConfigProfile("ociConfigProfile", true), // + openTelemetry("openTelemetry", true), // overrideSupportsIntegrityEnhancementFacility("overrideSupportsIntegrityEnhancementFacility", true), // packetDebugBufferSize("packetDebugBufferSize", true), // padCharsWithSpace("padCharsWithSpace", true), // paranoid("paranoid", false), // - queryInfoCacheFactory("queryInfoCacheFactory", "parseInfoCacheFactory", true), // password1("password1", true), // password2("password2", true), // password3("password3", true), // @@ -183,6 +183,7 @@ public enum PropertyKey { profileSQL("profileSQL", true), // propertiesTransform("propertiesTransform", true), // queriesBeforeRetrySource("queriesBeforeRetrySource", "queriesBeforeRetryMaster", true), // + queryInfoCacheFactory("queryInfoCacheFactory", "parseInfoCacheFactory", true), // queryInterceptors("queryInterceptors", true), // queryTimeoutKillsConnection("queryTimeoutKillsConnection", true), // readFromSourceWhenNoReplicas("readFromSourceWhenNoReplicas", "readFromMasterWhenNoSlaves", true), // diff --git a/src/main/core-api/java/com/mysql/cj/protocol/Protocol.java b/src/main/core-api/java/com/mysql/cj/protocol/Protocol.java index a910ecaa0..d4319182e 100644 --- a/src/main/core-api/java/com/mysql/cj/protocol/Protocol.java +++ b/src/main/core-api/java/com/mysql/cj/protocol/Protocol.java @@ -230,24 +230,6 @@ T read(Class requiredClass, int maxRows, b */ InputStream getLocalInfileInputStream(); - /** - * Returns the comment that will be prepended to all statements - * sent to the server. - * - * @return query comment string - */ - String getQueryComment(); - - /** - * Sets the comment that will be prepended to all statements - * sent to the server. Do not use slash-star or star-slash tokens - * in the comment as these will be added by the driver itself. - * - * @param comment - * query comment string - */ - void setQueryComment(String comment); - /** * Read messages from server and deliver them to resultBuilder. * diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetryHandler.java b/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetryHandler.java new file mode 100644 index 000000000..3a75134d7 --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetryHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * No-op implementation of {@link TelemetryHandler}. + */ +public class NoopTelemetryHandler implements TelemetryHandler { + + private static NoopTelemetryHandler INSTANCE = new NoopTelemetryHandler(); + + public static NoopTelemetryHandler getInstance() { + return INSTANCE; + } + + private NoopTelemetryHandler() { + } + + @Override + public TelemetrySpan startSpan(TelemetrySpanName spanName, Object... args) { + return NoopTelemetrySpan.getInstance(); + } + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetryScope.java b/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetryScope.java new file mode 100644 index 000000000..064a648dc --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetryScope.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * No-op implementation of {@link TelemetryScope}. + */ +public class NoopTelemetryScope implements TelemetryScope { + + private static NoopTelemetryScope INSTANCE = new NoopTelemetryScope(); + + public static NoopTelemetryScope getInstance() { + return INSTANCE; + } + + private NoopTelemetryScope() { + } + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetrySpan.java b/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetrySpan.java new file mode 100644 index 000000000..a8fec6abf --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/NoopTelemetrySpan.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * No-op implementation of {@link TelemetrySpan}. + */ +public class NoopTelemetrySpan implements TelemetrySpan { + + private static NoopTelemetrySpan INSTANCE = new NoopTelemetrySpan(); + + public static NoopTelemetrySpan getInstance() { + return INSTANCE; + } + + @Override + public TelemetryScope makeCurrent() { + return NoopTelemetryScope.getInstance(); + } + + private NoopTelemetrySpan() { + } + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryAttribute.java b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryAttribute.java new file mode 100644 index 000000000..6a8f75a08 --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryAttribute.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * List of supported telemetry attributes and operation values. + */ +public enum TelemetryAttribute { + + // Call-level attributes: + DB_NAME("db.name"), // + DB_OPERATION("db.operation"), // + DB_STATEMENT("db.statement"), // + + // Connection-level attributes: + DB_CONNECTION_STRING("db.connection_string"), // + DB_SYSTEM("db.system"), // + DB_USER("db.user"), // + NETWORK_PEER_ADDRESS("network.peer.address"), // + NETWORK_PEER_PORT("network.peer.port"), // + NETWORK_TRANSPORT("network.transport"), // + SERVER_ADDRESS("server.address"), // + SERVER_PORT("server.port"), // + + // General thread attributes: + THREAD_ID("thread.id"), // + THREAD_NAME("thread.name"); + + private String key = null; + + private TelemetryAttribute(String key) { + this.key = key; + } + + public String getKey() { + return this.key; + } + + /* + * Most common operation values. + */ + public static final String DB_SYSTEM_DEFAULT = "mysql"; + public static final String NETWORK_TRANSPORT_TCP = "tcp"; + public static final String NETWORK_TRANSPORT_UNIX = "unix"; + public static final String NETWORK_TRANSPORT_PIPE = "pipe"; + public static final String STATEMENT_SUFFIX = " (...)"; + public static final String OPERATION_BATCH = "(SQL batch)"; + public static final String OPERATION_COMMIT = "COMMIT"; + public static final String OPERATION_CREATE = "CREATE"; + public static final String OPERATION_EXPLAIN = "EXPLAIN"; + public static final String OPERATION_INIT_DB = "INIT_DB"; + public static final String OPERATION_KILL = "KILL"; + public static final String OPERATION_PING = "PING"; + public static final String OPERATION_ROLLBACK = "ROLLBACK"; + public static final String OPERATION_SELECT = "SELECT"; + public static final String OPERATION_SET = "SET"; + public static final String OPERATION_SHOW = "SHOW"; + public static final String OPERATION_SHUTDOWN = "SHUTDOWN"; + public static final String OPERATION_USE = "USE"; + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryHandler.java b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryHandler.java new file mode 100644 index 000000000..3b15fa914 --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryHandler.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +import java.util.function.BiConsumer; + +/** + * A handler for telemetry operations. Implementations must forward each operation to an underlying telemetry API or SDK. + * + * A default no-op implementation is provided so that telemetry may be turned off with minimal impact to the driver code. + */ +public interface TelemetryHandler { + + /** + * Start a telemetry span. Additionally, the returned {@link TelemetrySpan} object returned must be made current so that it gets recorded by the telemetry + * tracer. + * A {@link TelemetrySpan} object must be closed in a finally block after being made current, e.g.: + * + *
{@code
+     * TelemetrySpan span = telemetryHandler.startSpan(TelemetrySpanName.PING);
+     * try (TelemetryScope scope = span.makeCurrent()) {
+     *     // your code goes here
+     * } finally {
+     *     span.end();
+     * }
+     * }
+ * + * @param spanName + * the span name that identifies this telemetry span + * @param args + * arguments used for interpolating the specified span name via {@link String#format(String, Object...)} + * @return + * the newly created span object + */ + TelemetrySpan startSpan(TelemetrySpanName spanName, Object... args); + + /** + * Registers the specified {@link TelemetrySpan} as a link target for subsequent new spans. Spans created after will include links to all registered link + * target spans in the order they were added. + * + * @param span + * the {@link TelemetrySpan} to be registered as a link target for subsequent new spans + */ + default void addLinkTarget(TelemetrySpan span) { + // Noop. + } + + /** + * Removes the specified span from the list of registered link targets. + * + * @param span + * the {@link TelemetrySpan} to be removed from the list of registered link targets + */ + default void removeLinkTarget(TelemetrySpan span) { + // Noop. + } + + /** + * Injects telemetry context propagation information into the specified consumer. + * + * @param traceparentConsumer + * the consumer that will process the telemetry context propagation information + */ + default void propagateContext(BiConsumer traceparentConsumer) { + // Noop. + } + + /** + * The telemetry context propagation default key name. + * + * @return + * the default name of the key used in telemetry context propagation + */ + default String getContextPropagationKey() { + return "traceparent"; + } + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryScope.java b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryScope.java new file mode 100644 index 000000000..b98bcd446 --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetryScope.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * A telemetry context scope wrapper that hides all specific details from the underlying telemetry library. + * + * A default no-op implementation is provided so that telemetry may be turned off with minimal impact to the driver code. + */ +public interface TelemetryScope extends AutoCloseable { + + /** + * {@link AutoCloseable#close()} that must be used to end this context scope and, thus, make it possible to create new span within try-with-resources + * blocks. + */ + @Override + default void close() { + // Noop. + } + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/TelemetrySpan.java b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetrySpan.java new file mode 100644 index 000000000..7b4d95402 --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetrySpan.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * A telemetry span wrapper that hides all specific details from the underlying telemetry library. + * + * A default no-op implementation is provided so that telemetry may be turned off with minimal impact to the driver code. + */ +public interface TelemetrySpan extends AutoCloseable { + + /** + * Takes this telemetry span and makes it current in the global telemetry context. + * + * @return + * an {@link AutoCloseable} telemetry scope that represents the current telemetry context. + */ + TelemetryScope makeCurrent(); + + /** + * Adds the specified String attribute to this telemetry span. + * + * @param key + * the key for this attribute + * @param value + * the value for this attribute + */ + default void setAttribute(TelemetryAttribute key, String value) { + // Noop. + } + + /** + * Adds the specified long attribute to this telemetry span. + * + * @param key + * the key for this attribute + * @param value + * the value for this attribute + */ + default void setAttribute(TelemetryAttribute key, long value) { + // Noop. + } + + /** + * Sets the status code of this telemetry span as ERROR and records the stack trace of the specified exception. + * + * @param cause + * the cause for setting this span status code to ERROR + */ + default void setError(Throwable cause) { + // Noop. + } + + /** + * Marks the end of the execution of this span. + */ + default void end() { + // Noop. + } + + /** + * {@link AutoCloseable#close()} that can be used to end this span and, thus, make it possible to create new span within try-with-resources blocks. + */ + @Override + default void close() { + // Noop. + } + +} diff --git a/src/main/core-api/java/com/mysql/cj/telemetry/TelemetrySpanName.java b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetrySpanName.java new file mode 100644 index 000000000..cb279d4c7 --- /dev/null +++ b/src/main/core-api/java/com/mysql/cj/telemetry/TelemetrySpanName.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.telemetry; + +/** + * List of most common telemetry span names. + */ +public enum TelemetrySpanName { + + CANCEL_QUERY("Cancel query"), // + CHANGE_DATABASE("Change database"), // + COMMIT("Commit"), // + CONNECTION_CREATE("Create connection"), // + CONNECTION_RESET("Reset connection"), // + CREATE_DATABASE("Create database"), // + EXPLAIN_QUERY("Explain query"), // + GET_INNODB_STATUS("Get InnoDB status"), // + GET_PROCESS_HOST("Get process host"), // + GET_VARIABLE("Get variable '%s'"), // + LOAD_COLLATIONS("Load collations"), // + LOAD_VARIABLES("Load server variables"), // + PING("Ping"), // + ROLLBACK("Rollback"),// + ROUTINE_EXECUTE("Execute stored routine"), // + ROUTINE_EXECUTE_BATCH("Batch execute stored routine"), // + ROUTINE_PREPARE("Prepare stored routine"), // + SET_CHARSET("Set character set"), // + SET_OPTION_MULTI_STATEMENTS("Set multi-statements '%s'"), // + SET_TRANSACTION_ACCESS_MODE("Set transaction access mode '%s'"), // + SET_TRANSACTION_ISOLATION("Set transaction isolation"), // + SET_VARIABLE("Set variable '%s'"), // + SET_VARIABLES("Set variable(s)"), // + SHOW_WARNINGS("Show warnings"), // + SHUTDOWN("Shutdown"), // + STMT_DEALLOCATE_PREPARED("Deallocate prepared statement"), // + STMT_EXECUTE("Execute statement"), // + STMT_EXECUTE_BATCH("Batch execute statement"), // + STMT_EXECUTE_BATCH_PREPARED("Batch execute prepared statement"), // + STMT_EXECUTE_PREPARED("Execute prepared statement"), // + STMT_FETCH_PREPARED("Fetch rows for prepared statement"), // + STMT_PREPARE("Prepare statement"), // + STMT_RESET_PREPARED("Reset prepared statement"), // + STMT_SEND_LONG_DATA("Send long data for prepared statement"), // + USE_DATABASE("Use database"); + + private String name = ""; + + private TelemetrySpanName(String name) { + this.name = name; + } + + public String getName(Object... args) { + if (args.length > 0) { + return String.format(this.name, args); + } + return this.name; + } + +} diff --git a/src/main/core-impl/java/com/mysql/cj/CancelQueryTaskImpl.java b/src/main/core-impl/java/com/mysql/cj/CancelQueryTaskImpl.java index 7231a92f7..f64c28cf6 100644 --- a/src/main/core-impl/java/com/mysql/cj/CancelQueryTaskImpl.java +++ b/src/main/core-impl/java/com/mysql/cj/CancelQueryTaskImpl.java @@ -27,6 +27,10 @@ import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.OperationCancelledException; import com.mysql.cj.protocol.a.NativeMessageBuilder; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; //TODO should not be protocol-specific @@ -84,19 +88,36 @@ public void run() { NativeSession newSession = null; try { newSession = new NativeSession(hostInfo, session.getPropertySet()); - newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() { - @Override - public void transactionCompleted() { - } - - @Override - public void transactionBegun() { - } - - }); - newSession.getProtocol().sendCommand(new NativeMessageBuilder(newSession.getServerSession().supportsQueryAttributes()) - .buildComQuery(newSession.getSharedSendPacket(), "KILL QUERY " + origConnId), false, 0); + TelemetrySpan span = newSession.getTelemetryHandler().startSpan(TelemetrySpanName.CANCEL_QUERY); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, database); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_KILL); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_KILL + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, user); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() { + + @Override + public void transactionCompleted() { + } + + @Override + public void transactionBegun() { + } + + }); + newSession.getProtocol().sendCommand(new NativeMessageBuilder(newSession.getServerSession().supportsQueryAttributes()) + .buildComQuery(newSession.getSharedSendPacket(), newSession, "KILL QUERY " + origConnId), false, 0); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } finally { try { newSession.forceClose(); diff --git a/src/main/core-impl/java/com/mysql/cj/CoreSession.java b/src/main/core-impl/java/com/mysql/cj/CoreSession.java index 7e762a05c..e7a45c9bd 100644 --- a/src/main/core-impl/java/com/mysql/cj/CoreSession.java +++ b/src/main/core-impl/java/com/mysql/cj/CoreSession.java @@ -36,6 +36,7 @@ import com.mysql.cj.protocol.Message; import com.mysql.cj.protocol.Protocol; import com.mysql.cj.protocol.ServerSession; +import com.mysql.cj.telemetry.TelemetryHandler; import com.mysql.cj.util.Util; public abstract class CoreSession implements Session { @@ -70,6 +71,9 @@ public abstract class CoreSession implements Session { /** The event sink to use for profiling */ private ProfilerEventHandler eventSink; + /** The Telemetry handler to process telemetry operations */ + private TelemetryHandler telemetryHandler = null; + public CoreSession(HostInfo hostInfo, PropertySet propSet) { this.connectionCreationTimeMillis = System.currentTimeMillis(); this.hostInfo = hostInfo; @@ -175,6 +179,26 @@ public ProfilerEventHandler getProfilerEventHandler() { return this.eventSink; } + @Override + public String getQueryComment() { + throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); + } + + @Override + public void setQueryComment(String comment) { + throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); + } + + @Override + public void setTelemetryHandler(TelemetryHandler telemetryHandler) { + this.telemetryHandler = telemetryHandler; + } + + @Override + public TelemetryHandler getTelemetryHandler() { + return this.telemetryHandler; + } + @Override public boolean isSSLEstablished() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); diff --git a/src/main/core-impl/java/com/mysql/cj/NativeCharsetSettings.java b/src/main/core-impl/java/com/mysql/cj/NativeCharsetSettings.java index 7d2d319b4..4ff3f0533 100644 --- a/src/main/core-impl/java/com/mysql/cj/NativeCharsetSettings.java +++ b/src/main/core-impl/java/com/mysql/cj/NativeCharsetSettings.java @@ -49,6 +49,10 @@ import com.mysql.cj.result.Row; import com.mysql.cj.result.StringValueFactory; import com.mysql.cj.result.ValueFactory; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.StringUtils; public class NativeCharsetSettings extends CharsetMapping implements CharsetSettings { @@ -263,7 +267,6 @@ public void configurePostHandshake(boolean dontCheckServerMatch) { String sessionCollationClause = ""; try { - // connectionCollation overrides the characterEncoding value if (requiredCollation != null && (requiredCollationIndex = getCollationIndexForCollationName(requiredCollation)) != null) { if (isImpermissibleCollation(requiredCollationIndex)) { @@ -321,8 +324,9 @@ public void configurePostHandshake(boolean dontCheckServerMatch) { boolean isCollationDifferent = sessionCollationClause.length() > 0 && !requiredCollation.equalsIgnoreCase(this.serverSession.getServerVariable(COLLATION_CONNECTION)); if (dontCheckServerMatch || isCharsetDifferent || isCollationDifferent) { - this.session.getProtocol().sendCommand(getCommandBuilder().buildComQuery(null, "SET NAMES " + sessionCharsetName + sessionCollationClause), - false, 0); + String sql = "SET NAMES " + sessionCharsetName + sessionCollationClause; + telemetryWrapSendCommand(() -> this.session.getProtocol().sendCommand(getCommandBuilder().buildComQuery(null, this.session, sql), false, 0), + TelemetrySpanName.SET_CHARSET); this.serverSession.getServerVariables().put(CHARACTER_SET_CLIENT, sessionCharsetName); this.serverSession.getServerVariables().put(CHARACTER_SET_CONNECTION, sessionCharsetName); @@ -350,7 +354,9 @@ public void configurePostHandshake(boolean dontCheckServerMatch) { String characterSetResultsValue = this.characterSetResults.getValue(); if (StringUtils.isNullOrEmpty(characterSetResultsValue) || "null".equalsIgnoreCase(characterSetResultsValue)) { if (!StringUtils.isNullOrEmpty(sessionResultsCharset) && !"NULL".equalsIgnoreCase(sessionResultsCharset)) { - this.session.getProtocol().sendCommand(getCommandBuilder().buildComQuery(null, "SET character_set_results = NULL"), false, 0); + telemetryWrapSendCommand(() -> this.session.getProtocol() + .sendCommand(getCommandBuilder().buildComQuery(null, this.session, "SET character_set_results = NULL"), false, 0), + TelemetrySpanName.SET_VARIABLE, "character_set_results"); this.serverSession.getServerVariables().put(CHARACTER_SET_RESULTS, null); } @@ -368,7 +374,10 @@ public void configurePostHandshake(boolean dontCheckServerMatch) { } if (!resultsCharsetName.equalsIgnoreCase(sessionResultsCharset)) { - this.session.getProtocol().sendCommand(getCommandBuilder().buildComQuery(null, "SET character_set_results = " + resultsCharsetName), false, 0); + telemetryWrapSendCommand( + () -> this.session.getProtocol().sendCommand( + getCommandBuilder().buildComQuery(null, this.session, "SET character_set_results = " + resultsCharsetName), false, 0), + TelemetrySpanName.SET_VARIABLE, "character_set_results"); this.serverSession.getServerVariables().put(CHARACTER_SET_RESULTS, resultsCharsetName); } @@ -419,6 +428,26 @@ public void configurePostHandshake(boolean dontCheckServerMatch) { } } + private void telemetryWrapSendCommand(Runnable command, TelemetrySpanName spanName, Object... args) { + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(spanName, args); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + command.run(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } + } + private boolean characterSetNamesMatches(String mysqlEncodingName) { boolean res = false; if (mysqlEncodingName != null) { @@ -505,84 +534,101 @@ private void buildCollationMapping() { } if (customCollationIndexToCharsetName == null && this.session.getPropertySet().getBooleanProperty(PropertyKey.detectCustomCollations).getValue()) { - customCollationIndexToCollationName = new HashMap<>(); - customCollationNameToCollationIndex = new HashMap<>(); - customCollationIndexToCharsetName = new HashMap<>(); - customCharsetNameToMblen = new HashMap<>(); - customCharsetNameToJavaEncoding = new HashMap<>(); - customJavaEncodingUcToCharsetName = new HashMap<>(); - customCharsetNameToCollationIndex = new HashMap<>(); - customMultibyteEncodings = new HashSet<>(); - - String customCharsetMapping = this.session.getPropertySet().getStringProperty(PropertyKey.customCharsetMapping).getValue(); - if (customCharsetMapping != null) { - String[] pairs = customCharsetMapping.split(","); - for (String pair : pairs) { - int keyEnd = pair.indexOf(":"); - if (keyEnd > 0 && keyEnd + 1 < pair.length()) { - String charset = pair.substring(0, keyEnd); - String encoding = pair.substring(keyEnd + 1); - customCharsetNameToJavaEncoding.put(charset, encoding); - customJavaEncodingUcToCharsetName.put(encoding.toUpperCase(Locale.ENGLISH), charset); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.LOAD_COLLATIONS); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SELECT); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SELECT + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + customCollationIndexToCollationName = new HashMap<>(); + customCollationNameToCollationIndex = new HashMap<>(); + customCollationIndexToCharsetName = new HashMap<>(); + customCharsetNameToMblen = new HashMap<>(); + customCharsetNameToJavaEncoding = new HashMap<>(); + customJavaEncodingUcToCharsetName = new HashMap<>(); + customCharsetNameToCollationIndex = new HashMap<>(); + customMultibyteEncodings = new HashSet<>(); + + String customCharsetMapping = this.session.getPropertySet().getStringProperty(PropertyKey.customCharsetMapping).getValue(); + if (customCharsetMapping != null) { + String[] pairs = customCharsetMapping.split(","); + for (String pair : pairs) { + int keyEnd = pair.indexOf(":"); + if (keyEnd > 0 && keyEnd + 1 < pair.length()) { + String charset = pair.substring(0, keyEnd); + String encoding = pair.substring(keyEnd + 1); + customCharsetNameToJavaEncoding.put(charset, encoding); + customJavaEncodingUcToCharsetName.put(encoding.toUpperCase(Locale.ENGLISH), charset); + } } } - } - ValueFactory ivf = new IntegerValueFactory(this.session.getPropertySet()); - - try { - NativePacketPayload resultPacket = this.session.getProtocol().sendCommand(getCommandBuilder().buildComQuery(null, - "SELECT c.COLLATION_NAME, c.CHARACTER_SET_NAME, c.ID, cs.MAXLEN, c.IS_DEFAULT='Yes' from INFORMATION_SCHEMA.COLLATIONS AS c LEFT JOIN" - + " INFORMATION_SCHEMA.CHARACTER_SETS AS cs ON cs.CHARACTER_SET_NAME=c.CHARACTER_SET_NAME"), - false, 0); - Resultset rs = this.session.getProtocol().readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); - ValueFactory svf = new StringValueFactory(this.session.getPropertySet()); - Row r; - while ((r = rs.getRows().next()) != null) { - String collationName = r.getValue(0, svf); - String charsetName = r.getValue(1, svf); - int collationIndex = ((Number) r.getValue(2, ivf)).intValue(); - int maxlen = ((Number) r.getValue(3, ivf)).intValue(); - boolean isDefault = ((Number) r.getValue(4, ivf)).intValue() > 0; - - if (collationIndex >= MAP_SIZE // - || collationIndex != getStaticCollationIndexForCollationName(collationName) - || !charsetName.equals(getStaticMysqlCharsetNameForCollationIndex(collationIndex))) { - customCollationIndexToCollationName.put(collationIndex, collationName); - customCollationNameToCollationIndex.put(collationName, collationIndex); - customCollationIndexToCharsetName.put(collationIndex, charsetName); - if (isDefault || !customCharsetNameToCollationIndex.containsKey(charsetName) - && CharsetMapping.getStaticCollationIndexForMysqlCharsetName(charsetName) == 0) { - customCharsetNameToCollationIndex.put(charsetName, collationIndex); + ValueFactory ivf = new IntegerValueFactory(this.session.getPropertySet()); + + try { + NativePacketPayload resultPacket = this.session.getProtocol().sendCommand(getCommandBuilder().buildComQuery(null, this.session, + "SELECT c.COLLATION_NAME, c.CHARACTER_SET_NAME, c.ID, cs.MAXLEN, c.IS_DEFAULT='Yes' from INFORMATION_SCHEMA.COLLATIONS AS c LEFT JOIN" + + " INFORMATION_SCHEMA.CHARACTER_SETS AS cs ON cs.CHARACTER_SET_NAME=c.CHARACTER_SET_NAME"), + false, 0); + Resultset rs = this.session.getProtocol().readAllResults(-1, false, resultPacket, false, null, + new ResultsetFactory(Type.FORWARD_ONLY, null)); + ValueFactory svf = new StringValueFactory(this.session.getPropertySet()); + Row r; + while ((r = rs.getRows().next()) != null) { + String collationName = r.getValue(0, svf); + String charsetName = r.getValue(1, svf); + int collationIndex = ((Number) r.getValue(2, ivf)).intValue(); + int maxlen = ((Number) r.getValue(3, ivf)).intValue(); + boolean isDefault = ((Number) r.getValue(4, ivf)).intValue() > 0; + + if (collationIndex >= MAP_SIZE // + || collationIndex != getStaticCollationIndexForCollationName(collationName) + || !charsetName.equals(getStaticMysqlCharsetNameForCollationIndex(collationIndex))) { + customCollationIndexToCollationName.put(collationIndex, collationName); + customCollationNameToCollationIndex.put(collationName, collationIndex); + customCollationIndexToCharsetName.put(collationIndex, charsetName); + if (isDefault || !customCharsetNameToCollationIndex.containsKey(charsetName) + && CharsetMapping.getStaticCollationIndexForMysqlCharsetName(charsetName) == 0) { + customCharsetNameToCollationIndex.put(charsetName, collationIndex); + } } - } - // if no static map for charsetName adding to custom map - if (getStaticMysqlCharsetByName(charsetName) == null) { - customCharsetNameToMblen.put(charsetName, maxlen); - if (maxlen > 1) { - String enc = customCharsetNameToJavaEncoding.get(charsetName); - if (enc != null) { - customMultibyteEncodings.add(enc.toUpperCase(Locale.ENGLISH)); + // if no static map for charsetName adding to custom map + if (getStaticMysqlCharsetByName(charsetName) == null) { + customCharsetNameToMblen.put(charsetName, maxlen); + if (maxlen > 1) { + String enc = customCharsetNameToJavaEncoding.get(charsetName); + if (enc != null) { + customMultibyteEncodings.add(enc.toUpperCase(Locale.ENGLISH)); + } } } - } + } + } catch (IOException e) { + throw ExceptionFactory.createException(e.getMessage(), e, this.session.getExceptionInterceptor()); } - } catch (IOException e) { - throw ExceptionFactory.createException(e.getMessage(), e, this.session.getExceptionInterceptor()); - } - if (this.cacheServerConfiguration.getValue()) { - synchronized (customCollationIndexToCharsetNameByUrl) { - customCollationIndexToCollationNameByUrl.put(databaseURL, Collections.unmodifiableMap(customCollationIndexToCollationName)); - customCollationNameToCollationIndexByUrl.put(databaseURL, Collections.unmodifiableMap(customCollationNameToCollationIndex)); - customCollationIndexToCharsetNameByUrl.put(databaseURL, Collections.unmodifiableMap(customCollationIndexToCharsetName)); - customCharsetNameToMblenByUrl.put(databaseURL, Collections.unmodifiableMap(customCharsetNameToMblen)); - customCharsetNameToJavaEncodingByUrl.put(databaseURL, Collections.unmodifiableMap(customCharsetNameToJavaEncoding)); - customJavaEncodingUcToCharsetNameByUrl.put(databaseURL, Collections.unmodifiableMap(customJavaEncodingUcToCharsetName)); - customCharsetNameToCollationIndexByUrl.put(databaseURL, Collections.unmodifiableMap(customCharsetNameToCollationIndex)); - customMultibyteEncodingsByUrl.put(databaseURL, Collections.unmodifiableSet(customMultibyteEncodings)); + if (this.cacheServerConfiguration.getValue()) { + synchronized (customCollationIndexToCharsetNameByUrl) { + customCollationIndexToCollationNameByUrl.put(databaseURL, Collections.unmodifiableMap(customCollationIndexToCollationName)); + customCollationNameToCollationIndexByUrl.put(databaseURL, Collections.unmodifiableMap(customCollationNameToCollationIndex)); + customCollationIndexToCharsetNameByUrl.put(databaseURL, Collections.unmodifiableMap(customCollationIndexToCharsetName)); + customCharsetNameToMblenByUrl.put(databaseURL, Collections.unmodifiableMap(customCharsetNameToMblen)); + customCharsetNameToJavaEncodingByUrl.put(databaseURL, Collections.unmodifiableMap(customCharsetNameToJavaEncoding)); + customJavaEncodingUcToCharsetNameByUrl.put(databaseURL, Collections.unmodifiableMap(customJavaEncodingUcToCharsetName)); + customCharsetNameToCollationIndexByUrl.put(databaseURL, Collections.unmodifiableMap(customCharsetNameToCollationIndex)); + customMultibyteEncodingsByUrl.put(databaseURL, Collections.unmodifiableSet(customMultibyteEncodings)); + } } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } diff --git a/src/main/core-impl/java/com/mysql/cj/NativeQueryAttributesBindings.java b/src/main/core-impl/java/com/mysql/cj/NativeQueryAttributesBindings.java index 56dfec2e3..42829cd9b 100644 --- a/src/main/core-impl/java/com/mysql/cj/NativeQueryAttributesBindings.java +++ b/src/main/core-impl/java/com/mysql/cj/NativeQueryAttributesBindings.java @@ -20,14 +20,54 @@ package com.mysql.cj; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.function.Consumer; public class NativeQueryAttributesBindings implements QueryAttributesBindings { + // Query attributes use a different type mapping than other parameters bindings. + private static final Map, MysqlType> DEFAULT_MYSQL_TYPES = new HashMap<>(); + static { + DEFAULT_MYSQL_TYPES.put(String.class, MysqlType.CHAR); + DEFAULT_MYSQL_TYPES.put(Boolean.class, MysqlType.TINYINT); + DEFAULT_MYSQL_TYPES.put(Byte.class, MysqlType.TINYINT); + DEFAULT_MYSQL_TYPES.put(Short.class, MysqlType.SMALLINT); + DEFAULT_MYSQL_TYPES.put(Integer.class, MysqlType.INT); + DEFAULT_MYSQL_TYPES.put(Long.class, MysqlType.BIGINT); + DEFAULT_MYSQL_TYPES.put(BigInteger.class, MysqlType.BIGINT); + DEFAULT_MYSQL_TYPES.put(Float.class, MysqlType.FLOAT); + DEFAULT_MYSQL_TYPES.put(Double.class, MysqlType.DOUBLE); + DEFAULT_MYSQL_TYPES.put(BigDecimal.class, MysqlType.DOUBLE); + DEFAULT_MYSQL_TYPES.put(java.sql.Date.class, MysqlType.DATE); + DEFAULT_MYSQL_TYPES.put(LocalDate.class, MysqlType.DATE); + DEFAULT_MYSQL_TYPES.put(java.sql.Time.class, MysqlType.TIME); + DEFAULT_MYSQL_TYPES.put(LocalTime.class, MysqlType.TIME); + DEFAULT_MYSQL_TYPES.put(OffsetTime.class, MysqlType.TIME); + DEFAULT_MYSQL_TYPES.put(Duration.class, MysqlType.TIME); + DEFAULT_MYSQL_TYPES.put(LocalDateTime.class, MysqlType.DATETIME); + DEFAULT_MYSQL_TYPES.put(java.sql.Timestamp.class, MysqlType.TIMESTAMP); + DEFAULT_MYSQL_TYPES.put(Instant.class, MysqlType.TIMESTAMP); + DEFAULT_MYSQL_TYPES.put(OffsetDateTime.class, MysqlType.TIMESTAMP); + DEFAULT_MYSQL_TYPES.put(ZonedDateTime.class, MysqlType.TIMESTAMP); + DEFAULT_MYSQL_TYPES.put(java.util.Date.class, MysqlType.TIMESTAMP); + DEFAULT_MYSQL_TYPES.put(java.util.Calendar.class, MysqlType.TIMESTAMP); + } + Session session = null; private List bindAttributes = new ArrayList<>(); @@ -37,15 +77,15 @@ public NativeQueryAttributesBindings(Session sess) { @Override public void setAttribute(String name, Object value) { - MysqlType defaultMysqlType = value == null ? MysqlType.NULL : NativeQueryBindings.DEFAULT_MYSQL_TYPES.get(value.getClass()); + MysqlType defaultMysqlType = value == null ? MysqlType.NULL : DEFAULT_MYSQL_TYPES.get(value.getClass()); Object val = value; if (defaultMysqlType == null) { - Optional mysqlType = NativeQueryBindings.DEFAULT_MYSQL_TYPES.entrySet().stream() - .filter(m -> m.getKey().isAssignableFrom(value.getClass())).map(Entry::getValue).findFirst(); + Optional mysqlType = DEFAULT_MYSQL_TYPES.entrySet().stream().filter(m -> m.getKey().isAssignableFrom(value.getClass())) + .map(Entry::getValue).findFirst(); if (mysqlType.isPresent()) { defaultMysqlType = mysqlType.get(); } else { - defaultMysqlType = MysqlType.VARCHAR; + defaultMysqlType = MysqlType.CHAR; val = value.toString(); } } @@ -56,6 +96,14 @@ public void setAttribute(String name, Object value) { this.bindAttributes.add(bv); } + @Override + public void removeAttribute(String name) { + Optional bindValue = this.bindAttributes.stream().filter(b -> name.equalsIgnoreCase(b.getName())).findAny(); + if (bindValue.isPresent()) { + this.bindAttributes.remove(bindValue.get()); + } + } + @Override public int getCount() { return this.bindAttributes.size(); @@ -66,6 +114,11 @@ public BindValue getAttributeValue(int index) { return this.bindAttributes.get(index); } + @Override + public boolean containsAttribute(String name) { + return this.bindAttributes.stream().filter(b -> name.equalsIgnoreCase(b.getName())).findAny().isPresent(); + } + @Override public void runThroughAll(Consumer bindAttribute) { this.bindAttributes.forEach(bindAttribute::accept); diff --git a/src/main/core-impl/java/com/mysql/cj/NativeQueryBindValue.java b/src/main/core-impl/java/com/mysql/cj/NativeQueryBindValue.java index 90098e5ae..e3a3e040a 100644 --- a/src/main/core-impl/java/com/mysql/cj/NativeQueryBindValue.java +++ b/src/main/core-impl/java/com/mysql/cj/NativeQueryBindValue.java @@ -280,9 +280,10 @@ public int getFieldType() { case TINYINT: case TINYINT_UNSIGNED: return MysqlType.FIELD_TYPE_TINY; + case CHAR: + return MysqlType.FIELD_TYPE_STRING; case BINARY: case VARBINARY: - case CHAR: case VARCHAR: return MysqlType.FIELD_TYPE_VAR_STRING; case FLOAT: diff --git a/src/main/core-impl/java/com/mysql/cj/NativeQueryBindings.java b/src/main/core-impl/java/com/mysql/cj/NativeQueryBindings.java index e81efb89c..f3cc626c7 100644 --- a/src/main/core-impl/java/com/mysql/cj/NativeQueryBindings.java +++ b/src/main/core-impl/java/com/mysql/cj/NativeQueryBindings.java @@ -205,7 +205,7 @@ public void setFromBindValue(int parameterIndex, BindValue bv) { binding.setScaleOrLength(bv.getScaleOrLength()); } - static Map, MysqlType> DEFAULT_MYSQL_TYPES = new HashMap<>(); + private static final Map, MysqlType> DEFAULT_MYSQL_TYPES = new HashMap<>(); static { DEFAULT_MYSQL_TYPES.put(BigDecimal.class, MysqlType.DECIMAL); DEFAULT_MYSQL_TYPES.put(BigInteger.class, MysqlType.BIGINT); diff --git a/src/main/core-impl/java/com/mysql/cj/NativeSession.java b/src/main/core-impl/java/com/mysql/cj/NativeSession.java index 379aaa34d..5c70c4de2 100644 --- a/src/main/core-impl/java/com/mysql/cj/NativeSession.java +++ b/src/main/core-impl/java/com/mysql/cj/NativeSession.java @@ -37,6 +37,7 @@ import java.util.function.Supplier; import com.mysql.cj.conf.HostInfo; +import com.mysql.cj.conf.PropertyDefinitions.OpenTelemetry; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.PropertySet; import com.mysql.cj.conf.RuntimeProperty; @@ -50,6 +51,7 @@ import com.mysql.cj.exceptions.OperationCancelledException; import com.mysql.cj.interceptors.QueryInterceptor; import com.mysql.cj.log.Log; +import com.mysql.cj.otel.OpenTelemetryHandler; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.NetworkResources; import com.mysql.cj.protocol.ProtocolEntityFactory; @@ -68,6 +70,11 @@ import com.mysql.cj.result.Row; import com.mysql.cj.result.StringValueFactory; import com.mysql.cj.result.ValueFactory; +import com.mysql.cj.telemetry.NoopTelemetryHandler; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.Util; @@ -80,6 +87,9 @@ public class NativeSession extends CoreSession implements Serializable { /** When did the last query finish? */ private long lastQueryFinishedTime = 0; + /** The comment (if any) to prepend to all queries sent to the server (to show up in "SHOW PROCESSLIST") */ + private String queryComment = null; + /** Does this connection need to be tested? */ private boolean needsPing = false; @@ -97,6 +107,24 @@ public class NativeSession extends CoreSession implements Serializable { public NativeSession(HostInfo hostInfo, PropertySet propSet) { super(hostInfo, propSet); + + // Check Telemetry option. + RuntimeProperty openTelemetry = this.propertySet.getEnumProperty(PropertyKey.openTelemetry); + if (openTelemetry.getValue() == OpenTelemetry.PREFERRED || openTelemetry.getValue() == OpenTelemetry.REQUIRED) { + if (!OpenTelemetryHandler.isOpenTelemetryApiAvailable()) { + if (openTelemetry.getValue() == OpenTelemetry.REQUIRED) { + throw ExceptionFactory.createException(Messages.getString("Connection.OtelApiNotFound")); + } + if (openTelemetry.isExplicitlySet()) { + getLog().logInfo(Messages.getString("Connection.OtelApiNotFound")); + } + setTelemetryHandler(NoopTelemetryHandler.getInstance()); + } else { + setTelemetryHandler(new OpenTelemetryHandler()); + } + } else { + setTelemetryHandler(NoopTelemetryHandler.getInstance()); + } } public void connect(HostInfo hi, String user, String password, String database, int loginTimeout, TransactionEventHandler transactionManager) @@ -179,15 +207,47 @@ public void forceClose() { } public void enableMultiQueries() { - this.protocol.sendCommand(this.commandBuilder.buildComSetOption(((NativeProtocol) this.protocol).getSharedSendPacket(), 0), false, 0); - // OK_PACKET returned in previous sendCommand() was not processed so keep original transaction state. - ((NativeServerSession) getServerSession()).preserveOldTransactionState(); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.SET_OPTION_MULTI_STATEMENTS, "on"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.protocol.sendCommand(this.commandBuilder.buildComSetOption(((NativeProtocol) this.protocol).getSharedSendPacket(), 0), false, 0); + // OK_PACKET returned in previous sendCommand() was not processed so keep original transaction state. + ((NativeServerSession) getServerSession()).preserveOldTransactionState(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } public void disableMultiQueries() { - this.protocol.sendCommand(this.commandBuilder.buildComSetOption(((NativeProtocol) this.protocol).getSharedSendPacket(), 1), false, 0); - // OK_PACKET returned in previous sendCommand() was not processed so keep original transaction state. - ((NativeServerSession) getServerSession()).preserveOldTransactionState(); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.SET_OPTION_MULTI_STATEMENTS, "off"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.protocol.sendCommand(this.commandBuilder.buildComSetOption(((NativeProtocol) this.protocol).getSharedSendPacket(), 1), false, 0); + // OK_PACKET returned in previous sendCommand() was not processed so keep original transaction state. + ((NativeServerSession) getServerSession()).preserveOldTransactionState(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } @Override @@ -221,10 +281,26 @@ public boolean isServerLocal(Session sess) { * */ public void shutdownServer() { - if (versionMeetsMinimum(5, 7, 9)) { - this.protocol.sendCommand(this.commandBuilder.buildComQuery(getSharedSendPacket(), "SHUTDOWN"), false, 0); - } else { - this.protocol.sendCommand(this.commandBuilder.buildComShutdown(getSharedSendPacket()), false, 0); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.SHUTDOWN); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SHUTDOWN); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SHUTDOWN + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + if (versionMeetsMinimum(5, 7, 9)) { + this.protocol.sendCommand(this.commandBuilder.buildComQuery(getSharedSendPacket(), this, "SHUTDOWN"), false, 0); + } else { + this.protocol.sendCommand(this.commandBuilder.buildComShutdown(getSharedSendPacket()), false, 0); + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } @@ -403,66 +479,84 @@ public void loadServerVariables(Object syncMutex, String version) { this.protocol.getServerSession().setServerVariables(new HashMap<>()); - if (versionMeetsMinimum(5, 1, 0)) { - StringBuilder queryBuf = new StringBuilder(versionComment).append("SELECT"); - queryBuf.append(" @@session.auto_increment_increment AS auto_increment_increment"); - queryBuf.append(", @@character_set_client AS character_set_client"); - queryBuf.append(", @@character_set_connection AS character_set_connection"); - queryBuf.append(", @@character_set_results AS character_set_results"); - queryBuf.append(", @@character_set_server AS character_set_server"); - queryBuf.append(", @@collation_server AS collation_server"); - queryBuf.append(", @@collation_connection AS collation_connection"); - queryBuf.append(", @@init_connect AS init_connect"); - queryBuf.append(", @@interactive_timeout AS interactive_timeout"); - if (!versionMeetsMinimum(5, 5, 0)) { - queryBuf.append(", @@language AS language"); - } - queryBuf.append(", @@license AS license"); - queryBuf.append(", @@lower_case_table_names AS lower_case_table_names"); - queryBuf.append(", @@max_allowed_packet AS max_allowed_packet"); - queryBuf.append(", @@net_write_timeout AS net_write_timeout"); - queryBuf.append(", @@performance_schema AS performance_schema"); - if (!versionMeetsMinimum(8, 0, 3)) { - queryBuf.append(", @@query_cache_size AS query_cache_size"); - queryBuf.append(", @@query_cache_type AS query_cache_type"); - } - queryBuf.append(", @@sql_mode AS sql_mode"); - queryBuf.append(", @@system_time_zone AS system_time_zone"); - queryBuf.append(", @@time_zone AS time_zone"); - if (versionMeetsMinimum(8, 0, 3) || versionMeetsMinimum(5, 7, 20) && !versionMeetsMinimum(8, 0, 0)) { - queryBuf.append(", @@transaction_isolation AS transaction_isolation"); - } else { - queryBuf.append(", @@tx_isolation AS transaction_isolation"); - } - queryBuf.append(", @@wait_timeout AS wait_timeout"); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.LOAD_VARIABLES); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_SYSTEM, "MySQL"); + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SELECT); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SELECT + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + if (versionMeetsMinimum(5, 1, 0)) { + StringBuilder queryBuf = new StringBuilder(versionComment).append("SELECT"); + queryBuf.append(" @@session.auto_increment_increment AS auto_increment_increment"); + queryBuf.append(", @@character_set_client AS character_set_client"); + queryBuf.append(", @@character_set_connection AS character_set_connection"); + queryBuf.append(", @@character_set_results AS character_set_results"); + queryBuf.append(", @@character_set_server AS character_set_server"); + queryBuf.append(", @@collation_server AS collation_server"); + queryBuf.append(", @@collation_connection AS collation_connection"); + queryBuf.append(", @@init_connect AS init_connect"); + queryBuf.append(", @@interactive_timeout AS interactive_timeout"); + if (!versionMeetsMinimum(5, 5, 0)) { + queryBuf.append(", @@language AS language"); + } + queryBuf.append(", @@license AS license"); + queryBuf.append(", @@lower_case_table_names AS lower_case_table_names"); + queryBuf.append(", @@max_allowed_packet AS max_allowed_packet"); + queryBuf.append(", @@net_write_timeout AS net_write_timeout"); + queryBuf.append(", @@performance_schema AS performance_schema"); + if (!versionMeetsMinimum(8, 0, 3)) { + queryBuf.append(", @@query_cache_size AS query_cache_size"); + queryBuf.append(", @@query_cache_type AS query_cache_type"); + } + queryBuf.append(", @@sql_mode AS sql_mode"); + queryBuf.append(", @@system_time_zone AS system_time_zone"); + queryBuf.append(", @@time_zone AS time_zone"); + if (versionMeetsMinimum(8, 0, 3) || versionMeetsMinimum(5, 7, 20) && !versionMeetsMinimum(8, 0, 0)) { + queryBuf.append(", @@transaction_isolation AS transaction_isolation"); + } else { + queryBuf.append(", @@tx_isolation AS transaction_isolation"); + } + queryBuf.append(", @@wait_timeout AS wait_timeout"); + + NativePacketPayload resultPacket = (NativePacketPayload) this.protocol + .sendCommand(this.commandBuilder.buildComQuery(null, this, queryBuf.toString()), false, 0); + Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, + new ResultsetFactory(Type.FORWARD_ONLY, null)); + Field[] f = rs.getColumnDefinition().getFields(); + if (f.length > 0) { + ValueFactory vf = new StringValueFactory(this.propertySet); + Row r; + if ((r = rs.getRows().next()) != null) { + for (int i = 0; i < f.length; i++) { + String value = r.getValue(i, vf); + this.protocol.getServerSession().getServerVariables().put(f[i].getColumnLabel(), value); + } + } + } - NativePacketPayload resultPacket = (NativePacketPayload) this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, queryBuf.toString()), - false, 0); - Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, - new ResultsetFactory(Type.FORWARD_ONLY, null)); - Field[] f = rs.getColumnDefinition().getFields(); - if (f.length > 0) { + } else { + NativePacketPayload resultPacket = (NativePacketPayload) this.protocol + .sendCommand(this.commandBuilder.buildComQuery(null, this, versionComment + "SHOW VARIABLES"), false, 0); + Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, + new ResultsetFactory(Type.FORWARD_ONLY, null)); ValueFactory vf = new StringValueFactory(this.propertySet); Row r; - if ((r = rs.getRows().next()) != null) { - for (int i = 0; i < f.length; i++) { - String value = r.getValue(i, vf); - this.protocol.getServerSession().getServerVariables().put(f[i].getColumnLabel(), value); - } + while ((r = rs.getRows().next()) != null) { + this.protocol.getServerSession().getServerVariables().put(r.getValue(0, vf), r.getValue(1, vf)); } } - - } else { - NativePacketPayload resultPacket = (NativePacketPayload) this.protocol - .sendCommand(this.commandBuilder.buildComQuery(null, versionComment + "SHOW VARIABLES"), false, 0); - Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, - new ResultsetFactory(Type.FORWARD_ONLY, null)); - ValueFactory vf = new StringValueFactory(this.propertySet); - Row r; - while ((r = rs.getRows().next()) != null) { - this.protocol.getServerSession().getServerVariables().put(r.getValue(0, vf), r.getValue(1, vf)); - } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } + } catch (IOException e) { throw ExceptionFactory.createException(e.getMessage(), e); } @@ -484,56 +578,100 @@ public void setSessionVariables() { } if (!variablesToSet.isEmpty()) { - StringBuilder query = new StringBuilder("SET "); - String separator = ""; - for (String variableToSet : variablesToSet) { - if (variableToSet.length() > 0) { - query.append(separator); - if (!variableToSet.startsWith("@")) { - query.append("SESSION "); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLES); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + StringBuilder query = new StringBuilder("SET "); + String separator = ""; + for (String variableToSet : variablesToSet) { + if (variableToSet.length() > 0) { + query.append(separator); + if (!variableToSet.startsWith("@")) { + query.append("SESSION "); + } + query.append(variableToSet); + separator = ","; } - query.append(variableToSet); - separator = ","; } + this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, this, query.toString()), false, 0); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } - this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, query.toString()), false, 0); + } } } + @Override + public String getQueryComment() { + return this.queryComment; + } + + @Override + public void setQueryComment(String comment) { + this.queryComment = comment; + } + @Override public String getProcessHost() { - try { - long threadId = getThreadId(); - String processHost = findProcessHost(threadId); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.GET_PROCESS_HOST); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = TelemetryAttribute.OPERATION_SELECT + "/" + TelemetryAttribute.OPERATION_SHOW; + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - if (processHost == null) { - // http://bugs.mysql.com/bug.php?id=44167 - connection ids on the wire wrap at 4 bytes even though they're 64-bit numbers - this.log.logWarn(String.format( - "Connection id %d not found in \"SHOW PROCESSLIST\", assuming 32-bit overflow, using SELECT CONNECTION_ID() instead", threadId)); + try { + long threadId = getThreadId(); + String processHost = findProcessHost(threadId); - NativePacketPayload resultPacket = (NativePacketPayload) this.protocol - .sendCommand(this.commandBuilder.buildComQuery(null, "SELECT CONNECTION_ID()"), false, 0); - Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, - new ResultsetFactory(Type.FORWARD_ONLY, null)); + if (processHost == null) { + // http://bugs.mysql.com/bug.php?id=44167 - connection ids on the wire wrap at 4 bytes even though they're 64-bit numbers + this.log.logWarn(String.format( + "Connection id %d not found in \"SHOW PROCESSLIST\", assuming 32-bit overflow, using SELECT CONNECTION_ID() instead", threadId)); - ValueFactory lvf = new LongValueFactory(getPropertySet()); - Row r; - if ((r = rs.getRows().next()) != null) { - threadId = r.getValue(0, lvf); - processHost = findProcessHost(threadId); - } else { - this.log.logError("No rows returned for statement \"SELECT CONNECTION_ID()\", local connection check will most likely be incorrect"); + NativePacketPayload resultPacket = (NativePacketPayload) this.protocol + .sendCommand(this.commandBuilder.buildComQuery(null, this, "SELECT CONNECTION_ID()"), false, 0); + Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, + new ResultsetFactory(Type.FORWARD_ONLY, null)); + + ValueFactory lvf = new LongValueFactory(getPropertySet()); + Row r; + if ((r = rs.getRows().next()) != null) { + threadId = r.getValue(0, lvf); + processHost = findProcessHost(threadId); + } else { + this.log.logError("No rows returned for statement \"SELECT CONNECTION_ID()\", local connection check will most likely be incorrect"); + } } - } - if (processHost == null) { - this.log.logWarn(String.format( - "Cannot find process listing for connection %d in SHOW PROCESSLIST output, unable to determine if locally connected", threadId)); + if (processHost == null) { + this.log.logWarn(String.format( + "Cannot find process listing for connection %d in SHOW PROCESSLIST output, unable to determine if locally connected", threadId)); + } + return processHost; + } catch (IOException e) { + throw ExceptionFactory.createException(e.getMessage(), e); } - return processHost; - } catch (IOException e) { - throw ExceptionFactory.createException(e.getMessage(), e); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } @@ -543,13 +681,11 @@ private String findProcessHost(long threadId) { String ps = this.protocol.getServerSession().getServerVariable("performance_schema"); - NativePacketPayload resultPacket = versionMeetsMinimum(5, 6, 0) // performance_schema.threads in MySQL 5.5 does not contain PROCESSLIST_HOST column - && ps != null && ("1".contentEquals(ps) || "ON".contentEquals(ps)) - ? (NativePacketPayload) this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, - "select PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST from performance_schema.threads where PROCESSLIST_ID=" - + threadId), - false, 0) - : (NativePacketPayload) this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, "SHOW PROCESSLIST"), false, 0); + NativePacketPayload resultPacket = ps != null && ("1".contentEquals(ps) || "ON".contentEquals(ps)) + ? (NativePacketPayload) this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, this, + "SELECT PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST FROM performance_schema.threads WHERE PROCESSLIST_ID=" + threadId), + false, 0) + : (NativePacketPayload) this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, this, "SHOW PROCESSLIST"), false, 0); Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); @@ -579,25 +715,41 @@ private String findProcessHost(long threadId) { * @return server variable value */ public String queryServerVariable(String varName) { - try { + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.GET_VARIABLE, varName); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SELECT); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SELECT + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - NativePacketPayload resultPacket = (NativePacketPayload) this.protocol.sendCommand(this.commandBuilder.buildComQuery(null, "SELECT " + varName), - false, 0); - Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); + try { + NativePacketPayload resultPacket = (NativePacketPayload) this.protocol + .sendCommand(this.commandBuilder.buildComQuery(null, this, "SELECT " + varName), false, 0); + Resultset rs = ((NativeProtocol) this.protocol).readAllResults(-1, false, resultPacket, false, null, + new ResultsetFactory(Type.FORWARD_ONLY, null)); - ValueFactory svf = new StringValueFactory(this.propertySet); - Row r; - if ((r = rs.getRows().next()) != null) { - String s = r.getValue(0, svf); - if (s != null) { - return s; + ValueFactory svf = new StringValueFactory(this.propertySet); + Row r; + if ((r = rs.getRows().next()) != null) { + String s = r.getValue(0, svf); + if (s != null) { + return s; + } } - } - return null; + return null; - } catch (IOException e) { - throw ExceptionFactory.createException(e.getMessage(), e); + } catch (IOException e) { + throw ExceptionFactory.createException(e.getMessage(), e); + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } @@ -645,10 +797,10 @@ public T execSQL(Query callingQuery, String query, int max } try { - return packet == null - ? ((NativeProtocol) this.protocol).sendQueryString(callingQuery, query, this.characterEncoding.getValue(), maxRows, streamResults, - cachedMetadata, resultSetFactory) - : ((NativeProtocol) this.protocol).sendQueryPacket(callingQuery, packet, maxRows, streamResults, cachedMetadata, resultSetFactory); + if (packet == null) { + packet = this.commandBuilder.buildComQuery(null, this, query, callingQuery, this.characterEncoding.getValue()); + } + return ((NativeProtocol) this.protocol).sendQueryPacket(callingQuery, packet, maxRows, streamResults, cachedMetadata, resultSetFactory); } catch (CJException sqlE) { if (getPropertySet().getBooleanProperty(PropertyKey.dumpQueriesOnException).getValue()) { @@ -712,18 +864,34 @@ public void ping(boolean checkForClosedConnection, int timeoutMillis) { checkClosed(); } - long pingMillisLifetime = getPropertySet().getIntegerProperty(PropertyKey.selfDestructOnPingSecondsLifetime).getValue(); - int pingMaxOperations = getPropertySet().getIntegerProperty(PropertyKey.selfDestructOnPingMaxOperations).getValue(); + TelemetrySpan span = getTelemetryHandler().startSpan(TelemetrySpanName.PING); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.hostInfo.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_PING); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_PING); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.hostInfo.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + long pingMillisLifetime = getPropertySet().getIntegerProperty(PropertyKey.selfDestructOnPingSecondsLifetime).getValue(); + int pingMaxOperations = getPropertySet().getIntegerProperty(PropertyKey.selfDestructOnPingMaxOperations).getValue(); - if (pingMillisLifetime > 0 && System.currentTimeMillis() - this.connectionCreationTimeMillis > pingMillisLifetime - || pingMaxOperations > 0 && pingMaxOperations <= getCommandCount()) { + if (pingMillisLifetime > 0 && System.currentTimeMillis() - this.connectionCreationTimeMillis > pingMillisLifetime + || pingMaxOperations > 0 && pingMaxOperations <= getCommandCount()) { - invokeNormalCloseListeners(); + invokeNormalCloseListeners(); - throw ExceptionFactory.createException(Messages.getString("Connection.exceededConnectionLifetime"), - MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0, false, null, this.exceptionInterceptor); + throw ExceptionFactory.createException(Messages.getString("Connection.exceededConnectionLifetime"), + MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0, false, null, this.exceptionInterceptor); + } + this.protocol.sendCommand(this.commandBuilder.buildComPing(null), false, timeoutMillis); // it isn't safe to use a shared packet here + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } - this.protocol.sendCommand(this.commandBuilder.buildComPing(null), false, timeoutMillis); // it isn't safe to use a shared packet here } public long getConnectionCreationTimeMillis() { diff --git a/src/main/core-impl/java/com/mysql/cj/ServerPreparedQuery.java b/src/main/core-impl/java/com/mysql/cj/ServerPreparedQuery.java index 9047b6f6e..c8a7de7af 100644 --- a/src/main/core-impl/java/com/mysql/cj/ServerPreparedQuery.java +++ b/src/main/core-impl/java/com/mysql/cj/ServerPreparedQuery.java @@ -45,6 +45,10 @@ import com.mysql.cj.protocol.a.NativeMessageBuilder; import com.mysql.cj.protocol.a.NativePacketPayload; import com.mysql.cj.result.Field; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.StringUtils; // TODO should not be protocol-specific @@ -381,36 +385,53 @@ public T readExecuteResult(NativePacketPayload resultPacke */ private void serverLongData(int parameterIndex, BindValue binding) { synchronized (this) { - NativePacketPayload packet = this.session.getSharedSendPacket(); - Object value = binding.getValue(); - if (value instanceof byte[]) { - this.session.getProtocol() - .sendCommand(this.commandBuilder.buildComStmtSendLongData(packet, this.serverStatementId, parameterIndex, (byte[]) value), true, 0); - } else if (value instanceof InputStream) { - storeStreamOrReader(parameterIndex, packet, (InputStream) value); - } else if (value instanceof java.sql.Blob) { - try { - storeStreamOrReader(parameterIndex, packet, ((java.sql.Blob) value).getBinaryStream()); - } catch (Throwable t) { - throw ExceptionFactory.createException(t.getMessage(), this.session.getExceptionInterceptor()); - } - } else if (value instanceof Reader) { - if (binding.isNational() && !this.charEncoding.equalsIgnoreCase("UTF-8") && !this.charEncoding.equalsIgnoreCase("utf8")) { - throw ExceptionFactory.createException(Messages.getString("ServerPreparedStatement.31"), this.session.getExceptionInterceptor()); - } - storeStreamOrReader(parameterIndex, packet, (Reader) value); - } else if (value instanceof Clob) { - if (binding.isNational() && !this.charEncoding.equalsIgnoreCase("UTF-8") && !this.charEncoding.equalsIgnoreCase("utf8")) { - throw ExceptionFactory.createException(Messages.getString("ServerPreparedStatement.31"), this.session.getExceptionInterceptor()); - } - try { - storeStreamOrReader(parameterIndex, packet, ((Clob) value).getCharacterStream()); - } catch (Throwable t) { - throw ExceptionFactory.createException(t.getMessage(), t); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_SEND_LONG_DATA); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + NativePacketPayload packet = this.session.getSharedSendPacket(); + Object value = binding.getValue(); + if (value instanceof byte[]) { + this.session.getProtocol() + .sendCommand(this.commandBuilder.buildComStmtSendLongData(packet, this.serverStatementId, parameterIndex, (byte[]) value), true, 0); + } else if (value instanceof InputStream) { + storeStreamOrReader(parameterIndex, packet, (InputStream) value); + } else if (value instanceof java.sql.Blob) { + try { + storeStreamOrReader(parameterIndex, packet, ((java.sql.Blob) value).getBinaryStream()); + } catch (Throwable t) { + throw ExceptionFactory.createException(t.getMessage(), this.session.getExceptionInterceptor()); + } + } else if (value instanceof Reader) { + if (binding.isNational() && !this.charEncoding.equalsIgnoreCase("UTF-8") && !this.charEncoding.equalsIgnoreCase("utf8")) { + throw ExceptionFactory.createException(Messages.getString("ServerPreparedStatement.31"), this.session.getExceptionInterceptor()); + } + storeStreamOrReader(parameterIndex, packet, (Reader) value); + } else if (value instanceof Clob) { + if (binding.isNational() && !this.charEncoding.equalsIgnoreCase("UTF-8") && !this.charEncoding.equalsIgnoreCase("utf8")) { + throw ExceptionFactory.createException(Messages.getString("ServerPreparedStatement.31"), this.session.getExceptionInterceptor()); + } + try { + storeStreamOrReader(parameterIndex, packet, ((Clob) value).getCharacterStream()); + } catch (Throwable t) { + throw ExceptionFactory.createException(t.getMessage(), t); + } + } else { + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ServerPreparedStatement.18") + value.getClass().getName() + "'", this.session.getExceptionInterceptor()); } - } else { - throw ExceptionFactory.createException(WrongArgumentException.class, - Messages.getString("ServerPreparedStatement.18") + value.getClass().getName() + "'", this.session.getExceptionInterceptor()); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } } @@ -550,16 +571,33 @@ public void clearParameters(boolean clearServerParameters) { public void serverResetStatement() { this.session.checkClosed(); synchronized (this.session) { - try { - this.session.getProtocol().sendCommand(this.commandBuilder.buildComStmtReset(this.session.getSharedSendPacket(), this.serverStatementId), false, - 0); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_RESET_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + try { + this.session.getProtocol().sendCommand(this.commandBuilder.buildComStmtReset(this.session.getSharedSendPacket(), this.serverStatementId), + false, 0); + } finally { + // OK_PACKET returned in previous sendCommand() was not processed so keep original transaction state. + this.session.getProtocol().getServerSession().preserveOldTransactionState(); + this.session.clearInputStream(); + } + // Nothing to be detected after a reset... + this.queryBindings.setLongParameterSwitchDetected(false); + } catch (Throwable t) { + span.setError(t); + throw t; } finally { - // OK_PACKET returned in previous sendCommand() was not processed so keep original transaction state. - this.session.getProtocol().getServerSession().preserveOldTransactionState(); - this.session.clearInputStream(); + span.end(); } - // Nothing to be detected after a reset... - this.queryBindings.setLongParameterSwitchDetected(false); } } diff --git a/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetryHandler.java b/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetryHandler.java new file mode 100644 index 000000000..2534a206b --- /dev/null +++ b/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetryHandler.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.otel; + +import java.util.ArrayList; +import java.util.List; +import java.util.WeakHashMap; +import java.util.function.BiConsumer; + +import com.mysql.cj.Constants; +import com.mysql.cj.Messages; +import com.mysql.cj.exceptions.ExceptionFactory; +import com.mysql.cj.telemetry.TelemetryHandler; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; + +public class OpenTelemetryHandler implements TelemetryHandler { + + private static boolean otelApiAvaliable = true; + static { + try { + Class.forName("io.opentelemetry.api.GlobalOpenTelemetry"); + } catch (ClassNotFoundException e) { + otelApiAvaliable = false; + } + } + private OpenTelemetry openTelemetry = null; + private Tracer tracer = null; + private WeakHashMap spans = new WeakHashMap<>(); + private List linkTargets = new ArrayList<>(); + + public static boolean isOpenTelemetryApiAvailable() { + return otelApiAvaliable; + } + + public OpenTelemetryHandler() { + if (!isOpenTelemetryApiAvailable()) { + throw ExceptionFactory.createException(Messages.getString("Connection.OtelApiNotFound")); + } + + this.openTelemetry = GlobalOpenTelemetry.get(); + this.tracer = this.openTelemetry.getTracer(Constants.CJ_NAME, Constants.CJ_VERSION); + } + + @Override + public TelemetrySpan startSpan(TelemetrySpanName spanName, Object... args) { + SpanBuilder spanBuilder = this.tracer.spanBuilder(spanName.getName(args)).setSpanKind(SpanKind.CLIENT); + this.linkTargets.stream().map(Span::getSpanContext).forEach(spanBuilder::addLink); + Span otelSpan = spanBuilder.startSpan(); + TelemetrySpan span = new OpenTelemetrySpan(otelSpan); + this.spans.put(span, otelSpan); + return span; + } + + @Override + public void addLinkTarget(TelemetrySpan span) { + Span otelSpan = this.spans.get(span); + if (otelSpan != null) { + this.linkTargets.add(otelSpan); + } + } + + @Override + public void removeLinkTarget(TelemetrySpan span) { + Span otelSpan = this.spans.get(span); + if (otelSpan != null) { + this.linkTargets.remove(otelSpan); + } + } + + @Override + public void propagateContext(BiConsumer traceparentConsumer) { + this.openTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), traceparentConsumer, + (carrier, key, value) -> carrier.accept(key, value)); + } + +} diff --git a/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetryScope.java b/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetryScope.java new file mode 100644 index 000000000..5c1d9a9fa --- /dev/null +++ b/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetryScope.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.otel; + +import com.mysql.cj.telemetry.TelemetryScope; + +import io.opentelemetry.context.Scope; + +public class OpenTelemetryScope implements TelemetryScope { + + private Scope scope = null; + + public OpenTelemetryScope(Scope scope) { + this.scope = scope; + } + + @Override + public void close() { + this.scope.close(); + } + +} diff --git a/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetrySpan.java b/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetrySpan.java new file mode 100644 index 000000000..210216a02 --- /dev/null +++ b/src/main/core-impl/java/com/mysql/cj/otel/OpenTelemetrySpan.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by + * the Free Software Foundation. + * + * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in + * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the + * separately licensed software that they have either included with the program or referenced in the documentation. + * + * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception, + * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package com.mysql.cj.otel; + +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; + +public class OpenTelemetrySpan implements TelemetrySpan { + + private Span span = null; + private OpenTelemetryScope scope = null; + + public OpenTelemetrySpan(Span span) { + this.span = span; + } + + @Override + public TelemetryScope makeCurrent() { + this.scope = new OpenTelemetryScope(this.span.makeCurrent()); + return this.scope; + } + + @Override + public void setAttribute(TelemetryAttribute key, String value) { + this.span.setAttribute(key.getKey(), value); + } + + @Override + public void setAttribute(TelemetryAttribute key, long value) { + this.span.setAttribute(key.getKey(), value); + } + + @Override + public void setError(Throwable cause) { + this.span.setStatus(StatusCode.ERROR, cause.getMessage()).recordException(cause); + } + + @Override + public void end() { + this.span.end(); + } + + @Override + public void close() { + if (this.scope != null) { + this.scope.close(); + this.scope = null; + } + end(); + } + +} diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeMessageBuilder.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeMessageBuilder.java index e71ae9699..4c31188e2 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeMessageBuilder.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeMessageBuilder.java @@ -26,8 +26,11 @@ import com.mysql.cj.Constants; import com.mysql.cj.MessageBuilder; import com.mysql.cj.Messages; -import com.mysql.cj.NativeSession; +import com.mysql.cj.MysqlType; +import com.mysql.cj.NativeQueryAttributesBindings; +import com.mysql.cj.NativeQueryBindValue; import com.mysql.cj.PreparedQuery; +import com.mysql.cj.Query; import com.mysql.cj.QueryAttributesBindings; import com.mysql.cj.QueryBindings; import com.mysql.cj.Session; @@ -62,116 +65,234 @@ public NativePacketPayload buildClose() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } - public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, byte[] query) { + public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, Session sess, byte[] query) { NativePacketPayload packet = sharedPacket != null ? sharedPacket : new NativePacketPayload(query.length + 1); packet.writeInteger(IntegerDataType.INT1, NativeConstants.COM_QUERY); if (this.supportsQueryAttributes) { // CLIENT_QUERY_ATTRIBUTES capability has been negotiated but, since this method is used solely to run queries internally and it is not bound to any - // Statement object, no query attributes are ever set. - packet.writeInteger(IntegerDataType.INT_LENENC, 0); - packet.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + // Statement object, no query attributes are ever set. It remains to inject telemetry context propagation query attribute if telemetry is enabled. + + BindValue queryAttribute = new NativeQueryBindValue(sess); // Required for telemetry context propagation. + sess.getTelemetryHandler().propagateContext((k, v) -> { + queryAttribute.setName(k); + queryAttribute.setBinding(v, MysqlType.CHAR, 0, null); + }); + + if (queryAttribute.getName() == null) { // Telemetry context propagation attribute wasn't set. + packet.writeInteger(IntegerDataType.INT_LENENC, 0); // parameter_count (always 0) + packet.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + + } else { + packet.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_count (always 1, for telemetry context propagation) + packet.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + packet.writeInteger(IntegerDataType.INT1, 0); // null_bitmap (always 0, no nulls) + packet.writeInteger(IntegerDataType.INT1, 1); // new_params_bind_flag (always 1) + packet.writeInteger(IntegerDataType.INT2, queryAttribute.getFieldType()); // param_type_and_flag + packet.writeBytes(StringSelfDataType.STRING_LENENC, queryAttribute.getName().getBytes()); // parameter_name + queryAttribute.writeAsQueryAttribute(packet); // parameter_value + } + } packet.writeBytes(StringLengthDataType.STRING_FIXED, query); return packet; } - public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, String query) { - return buildComQuery(sharedPacket, StringUtils.getBytes(query)); + public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, Session sess, String query) { + return buildComQuery(sharedPacket, sess, StringUtils.getBytes(query)); } - public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, String query, String encoding) { - return buildComQuery(sharedPacket, StringUtils.getBytes(query, encoding)); + public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, Session sess, String query, String encoding) { + return buildComQuery(sharedPacket, sess, StringUtils.getBytes(query, encoding)); } @Override - public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, Session sess, PreparedQuery preparedQuery, QueryBindings bindings, - String characterEncoding) { - NativePacketPayload sendPacket = sharedPacket != null ? sharedPacket : new NativePacketPayload(9); - QueryAttributesBindings queryAttributesBindings = preparedQuery.getQueryAttributesBindings(); + public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, Session sess, String query, Query callingQuery, String characterEncoding) { + String statementComment = sess.getQueryComment(); + if (sess.getPropertySet().getBooleanProperty(PropertyKey.includeThreadNamesAsStatementComment).getValue()) { + statementComment = (statementComment != null ? statementComment + ", " : "") + "java thread: " + Thread.currentThread().getName(); + } + byte[] commentAsBytes = StringUtils.getBytes(statementComment, characterEncoding); - synchronized (this) { - BindValue[] bindValues = bindings.getBindValues(); + QueryAttributesBindings queryAttributesBindings = null; + if (!this.supportsQueryAttributes && callingQuery != null && callingQuery.getQueryAttributesBindings().getCount() > 0) { + sess.getLog().logWarn(Messages.getString("QueryAttributes.SetButNotSupported")); + } + if (this.supportsQueryAttributes && callingQuery != null) { + queryAttributesBindings = callingQuery.getQueryAttributesBindings(); + } else { + queryAttributesBindings = new NativeQueryAttributesBindings(sess); // Required for telemetry context propagation. + } - sendPacket.writeInteger(IntegerDataType.INT1, NativeConstants.COM_QUERY); + boolean contextPropagationAttributeWasInjected = false; + final NativePacketPayload sendPacket; + if (sharedPacket != null) { + sendPacket = sharedPacket; + } else { + // Compute packet length. It's not possible to know exactly how many bytes will be obtained from the query, but UTF-8 max encoding length is 4, so + // pad it (4 * query) + space for headers + int packLength = 1 /* COM_QUERY */ + query.length() * 4 + 2; + + if (commentAsBytes.length > 0) { + packLength += commentAsBytes.length; + packLength += 6; // for "/*[space]" + "[space]*/" + } if (this.supportsQueryAttributes) { + if (!queryAttributesBindings.containsAttribute(sess.getTelemetryHandler().getContextPropagationKey())) { + sess.getTelemetryHandler().propagateContext(queryAttributesBindings::setAttribute); + contextPropagationAttributeWasInjected = true; + } if (queryAttributesBindings.getCount() > 0) { - sendPacket.writeInteger(IntegerDataType.INT_LENENC, queryAttributesBindings.getCount()); - sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) - byte[] nullBitsBuffer = new byte[(queryAttributesBindings.getCount() + 7) / 8]; + packLength += 9 /* parameter_count */ + 1 /* parameter_set_count */; + packLength += (queryAttributesBindings.getCount() + 7) / 8 /* null_bitmap */ + 1 /* new_params_bind_flag */; for (int i = 0; i < queryAttributesBindings.getCount(); i++) { - if (queryAttributesBindings.getAttributeValue(i).isNull()) { - nullBitsBuffer[i >>> 3] |= 1 << (i & 7); - } + BindValue queryAttribute = queryAttributesBindings.getAttributeValue(i); + packLength += 2 /* parameter_type */ + queryAttribute.getName().length() /* parameter_name */ + queryAttribute.getBinaryLength(); } - sendPacket.writeBytes(StringLengthDataType.STRING_VAR, nullBitsBuffer); - sendPacket.writeInteger(IntegerDataType.INT1, 1); // new_params_bind_flag (always 1) - queryAttributesBindings.runThroughAll(a -> { - sendPacket.writeInteger(IntegerDataType.INT2, a.getFieldType()); - sendPacket.writeBytes(StringSelfDataType.STRING_LENENC, a.getName().getBytes()); - }); - queryAttributesBindings.runThroughAll(a -> { - if (!a.isNull()) { - a.writeAsQueryAttribute(sendPacket); - } - }); } else { - sendPacket.writeInteger(IntegerDataType.INT_LENENC, 0); - sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + packLength += 1 /* parameter_count */ + 1 /* parameter_set_count */; } - } else if (queryAttributesBindings.getCount() > 0) { - sess.getLog().logWarn(Messages.getString("QueryAttributes.SetButNotSupported")); } - sendPacket.setTag("QUERY"); + sendPacket = new NativePacketPayload(packLength); + } - boolean useStreamLengths = sess.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts).getValue(); + sendPacket.setPosition(0); + sendPacket.writeInteger(IntegerDataType.INT1, NativeConstants.COM_QUERY); - // - // Try and get this allocation as close as possible for BLOBs - // - int ensurePacketSize = 0; + if (this.supportsQueryAttributes) { + if (queryAttributesBindings != null && queryAttributesBindings.getCount() > 0) { + sendPacket.writeInteger(IntegerDataType.INT_LENENC, queryAttributesBindings.getCount()); + sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + byte[] nullBitsBuffer = new byte[(queryAttributesBindings.getCount() + 7) / 8]; + for (int i = 0; i < queryAttributesBindings.getCount(); i++) { + if (queryAttributesBindings.getAttributeValue(i).isNull()) { + nullBitsBuffer[i >>> 3] |= 1 << (i & 7); + } + } + sendPacket.writeBytes(StringLengthDataType.STRING_VAR, nullBitsBuffer); + sendPacket.writeInteger(IntegerDataType.INT1, 1); // new_params_bind_flag (always 1) + queryAttributesBindings.runThroughAll(a -> { + sendPacket.writeInteger(IntegerDataType.INT2, a.getFieldType()); + sendPacket.writeBytes(StringSelfDataType.STRING_LENENC, a.getName().getBytes()); + }); + queryAttributesBindings.runThroughAll(a -> { + if (!a.isNull()) { + a.writeAsQueryAttribute(sendPacket); + } + }); + } else { + sendPacket.writeInteger(IntegerDataType.INT_LENENC, 0); + sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + } + if (contextPropagationAttributeWasInjected) { + queryAttributesBindings.removeAttribute(sess.getTelemetryHandler().getContextPropagationKey()); + } + } + sendPacket.setTag("QUERY"); + + if (commentAsBytes.length > 0) { + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SLASH_STAR_SPACE_AS_BYTES); + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, commentAsBytes); + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES); + } - String statementComment = ((NativeSession) sess).getProtocol().getQueryComment(); + if (!sess.getServerSession().getCharsetSettings().doesPlatformDbCharsetMatches() && StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) { + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, StringUtils.getBytes(query)); + } else { + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, StringUtils.getBytes(query, characterEncoding)); + } + return sendPacket; + } - byte[] commentAsBytes = null; + @Override + public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, Session sess, PreparedQuery preparedQuery, QueryBindings bindings, + String characterEncoding) { + NativePacketPayload sendPacket = sharedPacket != null ? sharedPacket : new NativePacketPayload(9); + QueryAttributesBindings queryAttributesBindings = preparedQuery.getQueryAttributesBindings(); + BindValue[] bindValues = bindings.getBindValues(); - if (statementComment != null) { - commentAsBytes = StringUtils.getBytes(statementComment, characterEncoding); + sendPacket.writeInteger(IntegerDataType.INT1, NativeConstants.COM_QUERY); - ensurePacketSize += commentAsBytes.length; - ensurePacketSize += 6; // for /*[space] [space]*/ + boolean contextPropagationAttributeWasInjected = false; + if (this.supportsQueryAttributes) { + if (!queryAttributesBindings.containsAttribute(sess.getTelemetryHandler().getContextPropagationKey())) { + sess.getTelemetryHandler().propagateContext(queryAttributesBindings::setAttribute); + contextPropagationAttributeWasInjected = true; } - - for (int i = 0; i < bindValues.length; i++) { - if (bindValues[i].isStream() && useStreamLengths) { - ensurePacketSize += bindValues[i].getScaleOrLength(); + if (queryAttributesBindings.getCount() > 0) { + sendPacket.writeInteger(IntegerDataType.INT_LENENC, queryAttributesBindings.getCount()); + sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) + byte[] nullBitsBuffer = new byte[(queryAttributesBindings.getCount() + 7) / 8]; + for (int i = 0; i < queryAttributesBindings.getCount(); i++) { + if (queryAttributesBindings.getAttributeValue(i).isNull()) { + nullBitsBuffer[i >>> 3] |= 1 << (i & 7); + } } + sendPacket.writeBytes(StringLengthDataType.STRING_VAR, nullBitsBuffer); + sendPacket.writeInteger(IntegerDataType.INT1, 1); // new_params_bind_flag (always 1) + queryAttributesBindings.runThroughAll(a -> { + sendPacket.writeInteger(IntegerDataType.INT2, a.getFieldType()); + sendPacket.writeBytes(StringSelfDataType.STRING_LENENC, a.getName().getBytes()); + }); + queryAttributesBindings.runThroughAll(a -> { + if (!a.isNull()) { + a.writeAsQueryAttribute(sendPacket); + } + }); + } else { + sendPacket.writeInteger(IntegerDataType.INT_LENENC, 0); + sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) } - - if (ensurePacketSize != 0) { - sendPacket.ensureCapacity(ensurePacketSize); + if (contextPropagationAttributeWasInjected) { + queryAttributesBindings.removeAttribute(sess.getTelemetryHandler().getContextPropagationKey()); } + } else if (queryAttributesBindings.getCount() > 0) { + sess.getLog().logWarn(Messages.getString("QueryAttributes.SetButNotSupported")); + } + sendPacket.setTag("QUERY"); - if (commentAsBytes != null) { - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SLASH_STAR_SPACE_AS_BYTES); - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, commentAsBytes); - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES); - } + boolean useStreamLengths = sess.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts).getValue(); + + // Try and get this allocation as close as possible for BLOBs. + int ensurePacketSize = 0; - byte[][] staticSqlStrings = preparedQuery.getQueryInfo().getStaticSqlParts(); - for (int i = 0; i < bindValues.length; i++) { - bindings.checkParameterSet(i); - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, staticSqlStrings[i]); - bindValues[i].writeAsText(sendPacket); + String statementComment = sess.getQueryComment(); + byte[] commentAsBytes = null; + + if (statementComment != null) { + commentAsBytes = StringUtils.getBytes(statementComment, characterEncoding); + ensurePacketSize += commentAsBytes.length; + ensurePacketSize += 6; // for /*[space] [space]*/ + } + + for (int i = 0; i < bindValues.length; i++) { + if (bindValues[i].isStream() && useStreamLengths) { + ensurePacketSize += bindValues[i].getScaleOrLength(); } + } - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, staticSqlStrings[bindValues.length]); + if (ensurePacketSize != 0) { + sendPacket.ensureCapacity(ensurePacketSize); + } - return sendPacket; + if (commentAsBytes != null) { + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SLASH_STAR_SPACE_AS_BYTES); + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, commentAsBytes); + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES); } + + byte[][] staticSqlStrings = preparedQuery.getQueryInfo().getStaticSqlParts(); + for (int i = 0; i < bindValues.length; i++) { + bindings.checkParameterSet(i); + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, staticSqlStrings[i]); + bindValues[i].writeAsText(sendPacket); + } + + sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, staticSqlStrings[bindValues.length]); + return sendPacket; } public NativePacketPayload buildComInitDb(NativePacketPayload sharedPacket, byte[] dbName) { @@ -261,6 +382,7 @@ public NativePacketPayload buildComStmtExecute(NativePacketPayload sharedPacket, PreparedQuery preparedQuery) { NativePacketPayload packet = sharedPacket != null ? sharedPacket : new NativePacketPayload(5); + Session sess = preparedQuery.getSession(); int parameterCount = preparedQuery.getParameterCount(); QueryBindings queryBindings = preparedQuery.getQueryBindings(); BindValue[] parameterBindings = queryBindings.getBindValues(); @@ -271,9 +393,14 @@ public NativePacketPayload buildComStmtExecute(NativePacketPayload sharedPacket, packet.writeInteger(IntegerDataType.INT1, flags); packet.writeInteger(IntegerDataType.INT4, 1); // placeholder for parameter iterations + boolean contextPropagationAttributeWasInjected = false; int parametersAndAttributesCount = parameterCount; if (this.supportsQueryAttributes) { if (sendQueryAttributes) { + if (!queryAttributesBindings.containsAttribute(sess.getTelemetryHandler().getContextPropagationKey())) { + sess.getTelemetryHandler().propagateContext(queryAttributesBindings::setAttribute); + contextPropagationAttributeWasInjected = true; + } parametersAndAttributesCount += queryAttributesBindings.getCount(); } if (sendQueryAttributes || parametersAndAttributesCount > 0) { @@ -285,20 +412,17 @@ public NativePacketPayload buildComStmtExecute(NativePacketPayload sharedPacket, if (parametersAndAttributesCount > 0) { /* Reserve place for null-marker bytes */ int nullCount = (parametersAndAttributesCount + 7) / 8; - int nullBitsPosition = packet.getPosition(); - for (int i = 0; i < nullCount; i++) { packet.writeInteger(IntegerDataType.INT1, 0); } - byte[] nullBitsBuffer = new byte[nullCount]; // In case if buffers (type) changed or there are query attributes to send. if (queryBindings.getSendTypesToServer().get() || sendQueryAttributes && queryAttributesBindings.getCount() > 0) { packet.writeInteger(IntegerDataType.INT1, 1); - // Store types of parameters in the first package that is sent to the server. + // Store types of parameters in the first packet that is sent to the server. for (int i = 0; i < parameterCount; i++) { packet.writeInteger(IntegerDataType.INT2, parameterBindings[i].getFieldType()); if (this.supportsQueryAttributes) { @@ -348,6 +472,10 @@ public NativePacketPayload buildComStmtExecute(NativePacketPayload sharedPacket, packet.setPosition(endPosition); } + if (contextPropagationAttributeWasInjected) { + queryAttributesBindings.removeAttribute(sess.getTelemetryHandler().getContextPropagationKey()); + } + return packet; } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeProtocol.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeProtocol.java index 7547b3c5d..cbf88d757 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeProtocol.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeProtocol.java @@ -71,16 +71,13 @@ import java.util.TimeZone; import java.util.function.Supplier; -import com.mysql.cj.BindValue; import com.mysql.cj.CharsetMapping; -import com.mysql.cj.Constants; import com.mysql.cj.MessageBuilder; import com.mysql.cj.Messages; import com.mysql.cj.MysqlType; import com.mysql.cj.NativeCharsetSettings; import com.mysql.cj.NativeSession; import com.mysql.cj.Query; -import com.mysql.cj.QueryAttributesBindings; import com.mysql.cj.QueryResult; import com.mysql.cj.ServerPreparedQuery; import com.mysql.cj.ServerVersion; @@ -139,6 +136,10 @@ import com.mysql.cj.result.Row; import com.mysql.cj.result.StringValueFactory; import com.mysql.cj.result.ValueFactory; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.LazyString; import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.TestUtils; @@ -162,14 +163,13 @@ public class NativeProtocol extends AbstractProtocol implem /** Track this to manually shut down. */ protected CompressedPacketSender compressedPacketSender; - //private PacketPayload sendPacket = null; protected NativePacketPayload sharedSendPacket = null; + /** Use this when reading in rows to avoid thousands of new() calls, because the byte arrays just get copied out of the packet anyway */ protected NativePacketPayload reusablePacket = null; /** * Packet used for 'LOAD DATA LOCAL INFILE' - * * We use a SoftReference, so that we don't penalize intermittent use of this feature */ private SoftReference loadFileBufRef; @@ -182,7 +182,6 @@ public class NativeProtocol extends AbstractProtocol implem private boolean autoGenerateTestcaseScript; - /** Does the server support long column info? */ private boolean logSlowQueries = false; private boolean useAutoSlowLog; @@ -240,13 +239,7 @@ public class NativeProtocol extends AbstractProtocol implem DEFAULT_ENCODERS.put(ZonedDateTime.class, ZonedDateTimeValueEncoder::new); } - /** - * The comment (if any) that we'll prepend to all queries - * sent to the server (to show up in "SHOW PROCESSLIST") - */ - private String queryComment = null; - - private NativeMessageBuilder commandBuilder = null; + private NativeMessageBuilder nativeMessageBuilder = null; public static NativeProtocol getInstance(Session session, SocketConnection socketConnection, PropertySet propertySet, Log log, TransactionEventHandler transactionManager) { @@ -302,9 +295,13 @@ public void init(Session sess, SocketConnection phConnection, PropertySet propSe this.PROTOCOL_ENTITY_CLASS_TO_BINARY_READER = Collections.unmodifiableMap(protocolEntityClassToBinaryReader); } + public Session getSession() { + return this.session; + } + @Override public MessageBuilder getMessageBuilder() { - return getCommandBuilder(); + return getNativeMessageBuilder(); } public MessageSender getPacketSender() { @@ -315,11 +312,11 @@ public MessageReader getPacketReader() return this.packetReader; } - private NativeMessageBuilder getCommandBuilder() { - if (this.commandBuilder != null) { - return this.commandBuilder; + private NativeMessageBuilder getNativeMessageBuilder() { + if (this.nativeMessageBuilder != null) { + return this.nativeMessageBuilder; } - return this.commandBuilder = new NativeMessageBuilder(this.serverSession.supportsQueryAttributes()); + return this.nativeMessageBuilder = new NativeMessageBuilder(this.serverSession.supportsQueryAttributes()); } @Override @@ -339,8 +336,7 @@ public Supplier getValueEncoderSupplier(Object obj) { } /** - * Negotiates the SSL communications channel used when connecting - * to a MySQL server that understands SSL. + * Negotiates the SSL communications channel used when connecting to a MySQL server that understands SSL. */ @Override public void negotiateSSLConnection() { @@ -460,11 +456,8 @@ public void handlePropertyChange(RuntimeProperty prop) { case maintainTimeStats: case traceProtocol: case enablePacketDebug: - applyPacketDecorators(this.packetSender.undecorateAll(), this.packetReader.undecorateAll()); - break; - default: break; } @@ -546,18 +539,49 @@ public void changeDatabase(String database) { return; } - try { - sendCommand(getCommandBuilder().buildComInitDb(getSharedSendPacket(), database), false, 0); - } catch (CJException ex) { - if (this.getPropertySet().getBooleanProperty(PropertyKey.createDatabaseIfNotExist).getValue()) { - sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), - "CREATE DATABASE IF NOT EXISTS " + StringUtils.quoteIdentifier(database, true)), false, 0); + TelemetrySpan span1 = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.CHANGE_DATABASE); + try (TelemetryScope scope1 = span1.makeCurrent()) { + span1.setAttribute(TelemetryAttribute.DB_NAME, database); + span1.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_INIT_DB); + span1.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_INIT_DB + TelemetryAttribute.STATEMENT_SUFFIX); + span1.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span1.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span1.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span1.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - sendCommand(getCommandBuilder().buildComInitDb(getSharedSendPacket(), database), false, 0); - } else { - throw ExceptionFactory.createCommunicationsException(this.getPropertySet(), this.serverSession, this.getPacketSentTimeHolder(), - this.getPacketReceivedTimeHolder(), ex, getExceptionInterceptor()); + try { + sendCommand(getNativeMessageBuilder().buildComInitDb(getSharedSendPacket(), database), false, 0); + } catch (CJException ex) { + if (this.getPropertySet().getBooleanProperty(PropertyKey.createDatabaseIfNotExist).getValue()) { + TelemetrySpan span2 = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.CREATE_DATABASE); + try (TelemetryScope scope2 = span2.makeCurrent()) { + span2.setAttribute(TelemetryAttribute.DB_NAME, database); + span2.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_CREATE); + span2.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_CREATE + TelemetryAttribute.STATEMENT_SUFFIX); + span2.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span2.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span2.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span2.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + sendCommand(getNativeMessageBuilder().buildComQuery(getSharedSendPacket(), this.session, + "CREATE DATABASE IF NOT EXISTS " + StringUtils.quoteIdentifier(database, true)), false, 0); + } catch (Throwable t) { + span2.setError(t); + throw t; + } finally { + span2.end(); + } + sendCommand(getNativeMessageBuilder().buildComInitDb(getSharedSendPacket(), database), false, 0); + } else { + throw ExceptionFactory.createCommunicationsException(this.getPropertySet(), this.serverSession, this.getPacketSentTimeHolder(), + this.getPacketReceivedTimeHolder(), ex, getExceptionInterceptor()); + } } + } catch (Throwable t) { + span1.setError(t); + throw t; + } finally { + span1.end(); } } @@ -610,9 +634,7 @@ public final void send(Message packet, int packetLen) { this.packetSequence++; this.packetSender.send(packet.getByteBuffer(), packetLen, this.packetSequence); - // // Don't hold on to large packets - // if (packet == this.sharedSendPacket) { reclaimLargeSharedSendPacket(); } @@ -871,123 +893,6 @@ public void reclaimLargeReusablePacket() { } } - /** - * Build a query packet from the given string and send it to the server. - * - * @param - * extends {@link Resultset} - * @param callingQuery - * {@link Query} - * @param query - * query string - * @param characterEncoding - * Java encoding name - * @param maxRows - * rows limit - * @param streamResults - * whether a stream result should be created - * @param cachedMetadata - * use this metadata instead of the one provided on wire - * @param resultSetFactory - * {@link ProtocolEntityFactory} - * @return T instance - * @throws IOException - * if an i/o error occurs - */ - public final T sendQueryString(Query callingQuery, String query, String characterEncoding, int maxRows, boolean streamResults, - ColumnDefinition cachedMetadata, ProtocolEntityFactory resultSetFactory) throws IOException { - String statementComment = this.queryComment; - - if (this.propertySet.getBooleanProperty(PropertyKey.includeThreadNamesAsStatementComment).getValue()) { - statementComment = (statementComment != null ? statementComment + ", " : "") + "java thread: " + Thread.currentThread().getName(); - } - - // We don't know exactly how many bytes we're going to get from the query. Since we're dealing with UTF-8, the max is 4, so pad it - // (4 * query) + space for headers - int packLength = 1 /* com_query */ + query.length() * 4 + 2; - - byte[] commentAsBytes = null; - - if (statementComment != null) { - commentAsBytes = StringUtils.getBytes(statementComment, characterEncoding); - - packLength += commentAsBytes.length; - packLength += 6; // for /*[space] [space]*/ - } - - boolean supportsQueryAttributes = this.serverSession.supportsQueryAttributes(); - QueryAttributesBindings queryAttributes = null; - - if (!supportsQueryAttributes && callingQuery != null && callingQuery.getQueryAttributesBindings().getCount() > 0) { - this.log.logWarn(Messages.getString("QueryAttributes.SetButNotSupported")); - } - - if (supportsQueryAttributes) { - if (callingQuery != null) { - queryAttributes = callingQuery.getQueryAttributesBindings(); - } - - if (queryAttributes != null && queryAttributes.getCount() > 0) { - packLength += 9 /* parameter_count */ + 1 /* parameter_set_count */; - packLength += (queryAttributes.getCount() + 7) / 8 /* null_bitmap */ + 1 /* new_params_bind_flag */; - for (int i = 0; i < queryAttributes.getCount(); i++) { - BindValue queryAttribute = queryAttributes.getAttributeValue(i); - packLength += 2 /* parameter_type */ + queryAttribute.getName().length() /* parameter_name */ + queryAttribute.getBinaryLength(); - } - } else { - packLength += 1 /* parameter_count */ + 1 /* parameter_set_count */; - } - } - - // TODO decide how to safely use the shared this.sendPacket - NativePacketPayload sendPacket = new NativePacketPayload(packLength); - - sendPacket.setPosition(0); - sendPacket.writeInteger(IntegerDataType.INT1, NativeConstants.COM_QUERY); - - if (supportsQueryAttributes) { - if (queryAttributes != null && queryAttributes.getCount() > 0) { - sendPacket.writeInteger(IntegerDataType.INT_LENENC, queryAttributes.getCount()); - sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) - byte[] nullBitsBuffer = new byte[(queryAttributes.getCount() + 7) / 8]; - for (int i = 0; i < queryAttributes.getCount(); i++) { - if (queryAttributes.getAttributeValue(i).isNull()) { - nullBitsBuffer[i >>> 3] |= 1 << (i & 7); - } - } - sendPacket.writeBytes(StringLengthDataType.STRING_VAR, nullBitsBuffer); - sendPacket.writeInteger(IntegerDataType.INT1, 1); // new_params_bind_flag (always 1) - queryAttributes.runThroughAll(a -> { - sendPacket.writeInteger(IntegerDataType.INT2, a.getFieldType()); - sendPacket.writeBytes(StringSelfDataType.STRING_LENENC, a.getName().getBytes()); - }); - queryAttributes.runThroughAll(a -> { - if (!a.isNull()) { - a.writeAsQueryAttribute(sendPacket); - } - }); - } else { - sendPacket.writeInteger(IntegerDataType.INT_LENENC, 0); - sendPacket.writeInteger(IntegerDataType.INT_LENENC, 1); // parameter_set_count (always 1) - } - } - sendPacket.setTag("QUERY"); - - if (commentAsBytes != null) { - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SLASH_STAR_SPACE_AS_BYTES); - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, commentAsBytes); - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES); - } - - if (!this.session.getServerSession().getCharsetSettings().doesPlatformDbCharsetMatches() && StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) { - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, StringUtils.getBytes(query)); - } else { - sendPacket.writeBytes(StringLengthDataType.STRING_FIXED, StringUtils.getBytes(query, characterEncoding)); - } - - return sendQueryPacket(callingQuery, sendPacket, maxRows, streamResults, cachedMetadata, resultSetFactory); - } - /** * Send a query stored in a packet to the server. * @@ -1257,23 +1162,39 @@ public void explainSlowQuery(String query, String truncatedQuery) { if (StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery, EXPLAINABLE_STATEMENT) || versionMeetsMinimum(5, 6, 3) && StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery, EXPLAINABLE_STATEMENT_EXTENSION) != -1) { - try { - NativePacketPayload resultPacket = sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), "EXPLAIN " + query), false, 0); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.EXPLAIN_QUERY); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_EXPLAIN); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_EXPLAIN + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + try { + NativePacketPayload resultPacket = sendCommand( + getNativeMessageBuilder().buildComQuery(getSharedSendPacket(), this.session, "EXPLAIN " + query), false, 0); - Resultset rs = readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); + Resultset rs = readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); - StringBuilder explainResults = new StringBuilder(Messages.getString("Protocol.6")); - explainResults.append(truncatedQuery); - explainResults.append(Messages.getString("Protocol.7")); + StringBuilder explainResults = new StringBuilder(Messages.getString("Protocol.6")); + explainResults.append(truncatedQuery); + explainResults.append(Messages.getString("Protocol.7")); - appendResultSetSlashGStyle(explainResults, rs); + appendResultSetSlashGStyle(explainResults, rs); - this.log.logWarn(explainResults.toString()); - } catch (CJException sqlEx) { - throw sqlEx; + this.log.logWarn(explainResults.toString()); + } catch (CJException sqlEx) { + throw sqlEx; - } catch (Exception ex) { - throw ExceptionFactory.createException(ex.getMessage(), ex, getExceptionInterceptor()); + } catch (Exception ex) { + throw ExceptionFactory.createException(ex.getMessage(), ex, getExceptionInterceptor()); + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } } @@ -1317,7 +1238,7 @@ public final void quit() { this.packetSequence = -1; NativePacketPayload packet = new NativePacketPayload(1); - send(getCommandBuilder().buildComQuit(packet), packet.getPosition()); + send(getNativeMessageBuilder().buildComQuit(packet), packet.getPosition()); } finally { this.socketConnection.forceClose(); this.localInfileInputStream = null; @@ -2009,45 +1930,52 @@ public BaseMetricsHolder getMetricsHolder() { return this.metricsHolder; } - @Override - public String getQueryComment() { - return this.queryComment; - } - - @Override - public void setQueryComment(String comment) { - this.queryComment = comment; - } - private void appendDeadlockStatusInformation(Session sess, String xOpen, StringBuilder errorBuf) { if (sess.getPropertySet().getBooleanProperty(PropertyKey.includeInnodbStatusInDeadlockExceptions).getValue() && xOpen != null && (xOpen.startsWith("40") || xOpen.startsWith("41")) && getStreamingData() == null) { - try { - NativePacketPayload resultPacket = sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), "SHOW ENGINE INNODB STATUS"), false, 0); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SHOW); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SHOW + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - Resultset rs = readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); - - int colIndex = 0; - Field f = null; - for (int i = 0; i < rs.getColumnDefinition().getFields().length; i++) { - f = rs.getColumnDefinition().getFields()[i]; - if ("Status".equals(f.getName())) { - colIndex = i; - break; + try { + NativePacketPayload resultPacket = sendCommand( + getNativeMessageBuilder().buildComQuery(getSharedSendPacket(), this.session, "SHOW ENGINE INNODB STATUS"), false, 0); + + Resultset rs = readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); + + int colIndex = 0; + Field f = null; + for (int i = 0; i < rs.getColumnDefinition().getFields().length; i++) { + f = rs.getColumnDefinition().getFields()[i]; + if ("Status".equals(f.getName())) { + colIndex = i; + break; + } } - } - ValueFactory vf = new StringValueFactory(this.propertySet); + ValueFactory vf = new StringValueFactory(this.propertySet); - Row r; - if ((r = rs.getRows().next()) != null) { - errorBuf.append("\n\n").append(r.getValue(colIndex, vf)); - } else { - errorBuf.append("\n\n").append(Messages.getString("MysqlIO.NoInnoDBStatusFound")); + Row r; + if ((r = rs.getRows().next()) != null) { + errorBuf.append("\n\n").append(r.getValue(colIndex, vf)); + } else { + errorBuf.append("\n\n").append(Messages.getString("MysqlIO.NoInnoDBStatusFound")); + } + } catch (IOException | CJException ex) { + errorBuf.append("\n\n").append(Messages.getString("MysqlIO.InnoDBStatusFailed")).append("\n\n").append(Util.stackTraceToString(ex)); } - } catch (IOException | CJException ex) { - errorBuf.append("\n\n").append(Messages.getString("MysqlIO.InnoDBStatusFailed")).append("\n\n").append(Util.stackTraceToString(ex)); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } @@ -2153,65 +2081,75 @@ public SQLWarning convertShowWarningsToSQLWarnings(boolean forTruncationOnly) { SQLWarning currentWarning = null; ResultsetRows rows = null; - try { - /* - * +---------+------+---------------------------------------------+ - * | Level ..| Code | Message ....................................| - * +---------+------+---------------------------------------------+ - * | Warning | 1265 | Data truncated for column 'field1' at row 1 | - * +---------+------+---------------------------------------------+ - */ - NativePacketPayload resultPacket = sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), "SHOW WARNINGS"), false, 0); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SHOW_WARNINGS); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SHOW); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SHOW + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + try { + NativePacketPayload resultPacket = sendCommand(getNativeMessageBuilder().buildComQuery(getSharedSendPacket(), this.session, "SHOW WARNINGS"), + false, 0); - Resultset warnRs = readAllResults(-1, this.warningCount > 99 /* stream large warning counts */, resultPacket, false, null, - new ResultsetFactory(Type.FORWARD_ONLY, Concurrency.READ_ONLY)); + Resultset warnRs = readAllResults(-1, this.warningCount > 99 /* stream large warning counts */, resultPacket, false, null, + new ResultsetFactory(Type.FORWARD_ONLY, Concurrency.READ_ONLY)); - int codeFieldIndex = warnRs.getColumnDefinition().findColumn("Code", false, 1) - 1; - int messageFieldIndex = warnRs.getColumnDefinition().findColumn("Message", false, 1) - 1; + int codeFieldIndex = warnRs.getColumnDefinition().findColumn("Code", false, 1) - 1; + int messageFieldIndex = warnRs.getColumnDefinition().findColumn("Message", false, 1) - 1; - ValueFactory svf = new StringValueFactory(this.propertySet); - ValueFactory ivf = new IntegerValueFactory(this.propertySet); + ValueFactory svf = new StringValueFactory(this.propertySet); + ValueFactory ivf = new IntegerValueFactory(this.propertySet); - rows = warnRs.getRows(); - Row r; - while ((r = rows.next()) != null) { + rows = warnRs.getRows(); + Row r; + while ((r = rows.next()) != null) { - int code = r.getValue(codeFieldIndex, ivf); + int code = r.getValue(codeFieldIndex, ivf); - if (forTruncationOnly) { - if (code == MysqlErrorNumbers.ER_WARN_DATA_TRUNCATED || code == MysqlErrorNumbers.ER_WARN_DATA_OUT_OF_RANGE) { - DataTruncation newTruncation = new MysqlDataTruncation(r.getValue(messageFieldIndex, svf), 0, false, false, 0, 0, code); + if (forTruncationOnly) { + if (code == MysqlErrorNumbers.ER_WARN_DATA_TRUNCATED || code == MysqlErrorNumbers.ER_WARN_DATA_OUT_OF_RANGE) { + DataTruncation newTruncation = new MysqlDataTruncation(r.getValue(messageFieldIndex, svf), 0, false, false, 0, 0, code); + if (currentWarning == null) { + currentWarning = newTruncation; + } else { + currentWarning.setNextWarning(newTruncation); + } + } + } else { + //String level = warnRs.getString("Level"); + String message = r.getValue(messageFieldIndex, svf); + + SQLWarning newWarning = new SQLWarning(message, MysqlErrorNumbers.mysqlToSqlState(code), code); if (currentWarning == null) { - currentWarning = newTruncation; + currentWarning = newWarning; } else { - currentWarning.setNextWarning(newTruncation); + currentWarning.setNextWarning(newWarning); } } - } else { - //String level = warnRs.getString("Level"); - String message = r.getValue(messageFieldIndex, svf); + } - SQLWarning newWarning = new SQLWarning(message, MysqlErrorNumbers.mysqlToSqlState(code), code); - if (currentWarning == null) { - currentWarning = newWarning; - } else { - currentWarning.setNextWarning(newWarning); - } + if (forTruncationOnly && currentWarning != null) { + throw ExceptionFactory.createException(currentWarning.getMessage(), currentWarning); } - } - if (forTruncationOnly && currentWarning != null) { - throw ExceptionFactory.createException(currentWarning.getMessage(), currentWarning); + return currentWarning; + } catch (IOException ex) { + throw ExceptionFactory.createException(ex.getMessage(), ex); + } finally { + if (rows != null) { + rows.close(); + } } - - return currentWarning; - } catch (IOException ex) { - throw ExceptionFactory.createException(ex.getMessage(), ex); + } catch (Throwable t) { + span.setError(t); + throw t; } finally { - if (rows != null) { - rows.close(); - } + span.end(); } } @@ -2254,20 +2192,36 @@ public void configureTimeZone() { if (getPropertySet().getBooleanProperty(PropertyKey.forceConnectionTimeZoneToSession).getValue()) { // TODO don't send 'SET SESSION time_zone' if time_zone is already equal to the selectedTz (but it requires time zone detection) - StringBuilder query = new StringBuilder("SET SESSION time_zone='"); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "time_zone"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + StringBuilder query = new StringBuilder("SET SESSION time_zone='"); + + ZoneId zid = selectedTz.toZoneId().normalized(); + if (zid instanceof ZoneOffset) { + String offsetStr = ((ZoneOffset) zid).getId().replace("Z", "+00:00"); + query.append(offsetStr); + this.serverSession.getServerVariables().put("time_zone", offsetStr); + } else { + query.append(selectedTz.getID()); + this.serverSession.getServerVariables().put("time_zone", selectedTz.getID()); + } - ZoneId zid = selectedTz.toZoneId().normalized(); - if (zid instanceof ZoneOffset) { - String offsetStr = ((ZoneOffset) zid).getId().replace("Z", "+00:00"); - query.append(offsetStr); - this.serverSession.getServerVariables().put("time_zone", offsetStr); - } else { - query.append(selectedTz.getID()); - this.serverSession.getServerVariables().put("time_zone", selectedTz.getID()); + query.append("'"); + sendCommand(getNativeMessageBuilder().buildComQuery(null, this.session, query.toString()), false, 0); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } - - query.append("'"); - sendCommand(getCommandBuilder().buildComQuery(null, query.toString()), false, 0); } } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsCursor.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsCursor.java index 1320a9380..87afab8d0 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsCursor.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsCursor.java @@ -24,6 +24,7 @@ import java.util.List; import com.mysql.cj.Messages; +import com.mysql.cj.Session; import com.mysql.cj.exceptions.ExceptionFactory; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.Resultset.Concurrency; @@ -33,6 +34,10 @@ import com.mysql.cj.protocol.a.NativeMessageBuilder; import com.mysql.cj.protocol.a.NativeProtocol; import com.mysql.cj.result.Row; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; /** * Model for result set data backed by a cursor (see http://dev.mysql.com/doc/refman/5.7/en/cursors.html and @@ -194,53 +199,70 @@ private void fetchMoreRows() { } synchronized (this.owner.getSyncMutex()) { - try { - boolean oldFirstFetchCompleted = this.firstFetchCompleted; - - if (!this.firstFetchCompleted) { - this.firstFetchCompleted = true; - } + Session session = this.owner.getSession(); + TelemetrySpan span = session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_FETCH_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + try { + boolean oldFirstFetchCompleted = this.firstFetchCompleted; + + if (!this.firstFetchCompleted) { + this.firstFetchCompleted = true; + } - int numRowsToFetch = this.owner.getOwnerFetchSize(); + int numRowsToFetch = this.owner.getOwnerFetchSize(); - if (numRowsToFetch == 0) { - numRowsToFetch = this.owner.getOwningStatementFetchSize(); - } + if (numRowsToFetch == 0) { + numRowsToFetch = this.owner.getOwningStatementFetchSize(); + } - if (numRowsToFetch == Integer.MIN_VALUE) { - // Handle the case where the user used 'old' streaming result sets + if (numRowsToFetch == Integer.MIN_VALUE) { + // Handle the case where the user used 'old' streaming result sets - numRowsToFetch = 1; - } + numRowsToFetch = 1; + } - if (this.fetchedRows == null) { - this.fetchedRows = new ArrayList<>(numRowsToFetch); - } else { - this.fetchedRows.clear(); - } + if (this.fetchedRows == null) { + this.fetchedRows = new ArrayList<>(numRowsToFetch); + } else { + this.fetchedRows.clear(); + } - // TODO this is not the right place for this code, should be in protocol - this.protocol.sendCommand( - this.commandBuilder.buildComStmtFetch(this.protocol.getSharedSendPacket(), this.owner.getOwningStatementServerId(), numRowsToFetch), - true, 0); + // TODO this is not the right place for this code, should be in protocol + this.protocol.sendCommand( + this.commandBuilder.buildComStmtFetch(this.protocol.getSharedSendPacket(), this.owner.getOwningStatementServerId(), numRowsToFetch), + true, 0); - Row row = null; + Row row = null; - while ((row = this.protocol.read(ResultsetRow.class, this.rowFactory)) != null) { - this.fetchedRows.add(row); - } + while ((row = this.protocol.read(ResultsetRow.class, this.rowFactory)) != null) { + this.fetchedRows.add(row); + } - this.currentPositionInFetchedRows = BEFORE_START_OF_ROWS; + this.currentPositionInFetchedRows = BEFORE_START_OF_ROWS; - if (this.protocol.getServerSession().isLastRowSent()) { - this.lastRowFetched = true; + if (this.protocol.getServerSession().isLastRowSent()) { + this.lastRowFetched = true; - if (!oldFirstFetchCompleted && this.fetchedRows.size() == 0) { - this.wasEmpty = true; + if (!oldFirstFetchCompleted && this.fetchedRows.size() == 0) { + this.wasEmpty = true; + } } + } catch (Exception ex) { + throw ExceptionFactory.createException(ex.getMessage(), ex); } - } catch (Exception ex) { - throw ExceptionFactory.createException(ex.getMessage(), ex); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsStreaming.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsStreaming.java index 6b7bcebd4..debabfdfb 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsStreaming.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/ResultsetRowsStreaming.java @@ -21,6 +21,7 @@ package com.mysql.cj.protocol.a.result; import com.mysql.cj.Messages; +import com.mysql.cj.Session; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJException; import com.mysql.cj.exceptions.ExceptionFactory; @@ -39,6 +40,10 @@ import com.mysql.cj.protocol.a.NativeProtocol; import com.mysql.cj.protocol.a.TextRowFactory; import com.mysql.cj.result.Row; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.Util; /** @@ -114,15 +119,34 @@ public void close() { if (!this.protocol.getPropertySet().getBooleanProperty(PropertyKey.clobberStreamingResults).getValue() && this.protocol.getPropertySet().getIntegerProperty(PropertyKey.netTimeoutForStreamingResults).getValue() > 0) { - int oldValue = this.protocol.getServerSession().getServerVariable("net_write_timeout", 60); - - this.protocol.clearInputStream(); - - try { - this.protocol.sendCommand(this.commandBuilder.buildComQuery(this.protocol.getSharedSendPacket(), "SET net_write_timeout=" + oldValue, - this.protocol.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue()), false, 0); - } catch (Exception ex) { - throw ExceptionFactory.createException(ex.getMessage(), ex, this.exceptionInterceptor); + Session session = this.owner.getSession(); + TelemetrySpan span = session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "net_write_timeout"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, session.getHostInfo().getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, session.getHostInfo().getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + int oldValue = this.protocol.getServerSession().getServerVariable("net_write_timeout", 60); + + this.protocol.clearInputStream(); + + try { + this.protocol.sendCommand( + this.commandBuilder.buildComQuery(this.protocol.getSharedSendPacket(), session, "SET net_write_timeout=" + oldValue, + this.protocol.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue()), + false, 0); + } catch (Exception ex) { + throw ExceptionFactory.createException(ex.getMessage(), ex, this.exceptionInterceptor); + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XMessageBuilder.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XMessageBuilder.java index 2da131be6..792d5cd19 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XMessageBuilder.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XMessageBuilder.java @@ -40,6 +40,7 @@ import com.mysql.cj.MessageBuilder; import com.mysql.cj.Messages; import com.mysql.cj.PreparedQuery; +import com.mysql.cj.Query; import com.mysql.cj.QueryBindings; import com.mysql.cj.Session; import com.mysql.cj.exceptions.CJOperationNotSupportedException; @@ -145,7 +146,7 @@ public XMessage buildDocInsert(String schemaName, String collectionName, List updates) { Update.Builder builder = Update.newBuilder().setCollection((Collection) filterParams.getCollection()); @@ -246,14 +248,14 @@ public XMessage buildPrepareDocUpdate(int preparedStatementId, FilterParams filt } /** - * Initialize an {@link Update.Builder} for table data model with common data for prepared and non-prepared executions. + * Initialize an {@link com.mysql.cj.x.protobuf.MysqlxCrud.Update.Builder} for table data model with common data for prepared and non-prepared executions. * * @param filterParams * the filter parameters * @param updateParams * the update parameters * @return - * an initialized {@link Update.Builder} instance + * an initialized {@link com.mysql.cj.x.protobuf.MysqlxCrud.Update.Builder} instance */ @SuppressWarnings("unchecked") private Update.Builder commonRowUpdateBuilder(FilterParams filterParams, UpdateParams updateParams) { @@ -302,12 +304,12 @@ public XMessage buildPrepareRowUpdate(int preparedStatementId, FilterParams filt } /** - * Initialize a {@link Find.Builder} for collection data model with common data for prepared and non-prepared executions. + * Initialize a {@link com.mysql.cj.x.protobuf.MysqlxCrud.Find.Builder} for collection data model with common data for prepared and non-prepared executions. * * @param filterParams * the filter parameters * @return - * an initialized {@link Find.Builder} instance + * an initialized {@link com.mysql.cj.x.protobuf.MysqlxCrud.Find.Builder} instance */ @SuppressWarnings("unchecked") private Find.Builder commonFindBuilder(FilterParams filterParams) { @@ -365,12 +367,12 @@ public XMessage buildPrepareFind(int preparedStatementId, FilterParams filterPar } /** - * Initialize a {@link Delete.Builder} with common data for prepared and non-prepared executions. + * Initialize a {@link com.mysql.cj.x.protobuf.MysqlxCrud.Delete.Builder} with common data for prepared and non-prepared executions. * * @param filterParams * the filter parameters * @return - * an initialized {@link Delete.Builder} instance + * an initialized {@link com.mysql.cj.x.protobuf.MysqlxCrud.Delete.Builder} instance */ private Delete.Builder commonDeleteBuilder(FilterParams filterParams) { Delete.Builder builder = Delete.newBuilder().setCollection((Collection) filterParams.getCollection()); @@ -411,12 +413,12 @@ public XMessage buildPrepareDelete(int preparedStatementId, FilterParams filterP } /** - * Initialize a {@link StmtExecute.Builder} with common data for prepared and non-prepared executions. + * Initialize a {@link com.mysql.cj.x.protobuf.MysqlxSql.StmtExecute.Builder} with common data for prepared and non-prepared executions. * * @param statement * the SQL statement * @return - * an initialized {@link StmtExecute.Builder} instance + * an initialized {@link com.mysql.cj.x.protobuf.MysqlxSql.StmtExecute.Builder} instance */ private StmtExecute.Builder commonSqlStatementBuilder(String statement) { StmtExecute.Builder builder = StmtExecute.newBuilder(); @@ -992,6 +994,11 @@ public XMessage buildExpectOpen() { .setConditionKey(MysqlxExpect.Open.Condition.Key.EXPECT_FIELD_EXIST_VALUE).setConditionValue(ByteString.copyFromUtf8("6.1"))).build()); } + @Override + public XMessage buildComQuery(XMessage sharedPacket, Session sess, String query, Query callingQuery, String characterEncoding) { + throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); + } + @Override public XMessage buildComQuery(XMessage sharedPacket, Session sess, PreparedQuery preparedQuery, QueryBindings bindings, String characterEncoding) { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XProtocol.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XProtocol.java index 283660983..991915749 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XProtocol.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XProtocol.java @@ -1022,16 +1022,6 @@ public InputStream getLocalInfileInputStream() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } - @Override - public String getQueryComment() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void setQueryComment(String comment) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - @Override public Supplier getValueEncoderSupplier(Object obj) { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerSession.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerSession.java index faace2959..a4abe5a91 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerSession.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerSession.java @@ -31,6 +31,7 @@ import com.mysql.cj.protocol.ServerSession; public class XServerSession implements ServerSession { + XServerCapabilities serverCapabilities = null; private TimeZone defaultTimeZone = TimeZone.getDefault(); diff --git a/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties b/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties index 72b9a076e..1e88e7833 100644 --- a/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties +++ b/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties @@ -148,6 +148,7 @@ Connection.BadExceptionInterceptor=Unable to load exception interceptor. Connection.CantDetectLocalConnect=Unable to determine if hostname ''{0}'' is local to this box because of exception, assuming it''s not. Connection.NoMetadataOnSocketFactory=Configured socket factory does not implement SocketMetadata, can not determine whether server is locally-connected, assuming not" Connection.LoginTimeout=Connection attempt exceeded defined timeout. +Connection.OtelApiNotFound=The OpenTelemetry API library cannot be found or it is not installed. ConnectionGroup.0=Cannot remove host, only one configured host active. ConnectionGroup.1=Host is not configured: {0} @@ -901,11 +902,11 @@ ConnectionProperties.noDatetimeStringSync=Don''t ensure that ''ResultSet.getTime ConnectionProperties.nullCatalogMeansCurrent=In ''DatabaseMetaData'' methods that take a ''catalog'' or ''schema'' parameter, does the value "null" mean to use the current database? See also the property ''databaseTerm''. ConnectionProperties.ociConfigFile=The location of the OCI configuration file as required by the OCI SDK for Java. Default value is "~/.oci/config" for Unix-like systems and "%HOMEDRIVE%%HOMEPATH%.oci\\config" for Windows. ConnectionProperties.ociConfigProfile=The profile in the OCI configuration file specified in ''ociConfigFile'', from where the configuration to use in the ''authentication_oci_client'' authentication plugin is to be read. +ConnectionProperties.openTelemetry=Should the driver generate OpenTelemetry traces and handle context propagation to the MySQL Server? This option accepts the values "REQUIRED", "PREFERRED" and "DISABLED". If set to "REQUIRED", an OpenTelemetry library must be available at run time, or else connections to the MySQL Server will fail. Setting it to "DISABLED", turns off generating OpenTelemetry instrumentation by Connector/J. Setting it to "PREFERRED" enables generating OpenTelemetry instrumentation provided that an OpenTelemetry library is available at run time, and a warning is issued otherwise. Not setting a value for the property is equivalent to setting it as "PREFERRED" but no warning is issued when no OpenTelmetry library is available at run time. Connector/J relies entirely on the OpenTelemetry exporters configured in the calling application and does not provide any means of configuring its own exporters. ConnectionProperties.overrideSupportsIEF=Should the driver return "true" for ''DatabaseMetaData.supportsIntegrityEnhancementFacility()'' even if the database doesn''t support it to workaround applications that require this method to return "true" to signal support of foreign keys, even though the SQL specification states that this facility contains much more than just foreign key support (one such application being OpenOffice)? ConnectionProperties.packetDebugBufferSize=The maximum number of packets to retain when ''enablePacketDebug'' is "true". ConnectionProperties.padCharsWithSpace=If a result set column has the CHAR type and the value does not fill the amount of characters specified in the DDL for the column, should the driver pad the remaining characters with space (for ANSI compliance)? ConnectionProperties.paranoid=Take measures to prevent exposure sensitive information in error messages and clear data structures holding sensitive data when possible? -ConnectionProperties.queryInfoCacheFactory=Name of a class implementing ''com.mysql.cj.CacheAdapterFactory'' which will be used to create caches for the parsed representation of prepared statements. Prior to version 8.0.29, this property was named ''parseInfoCacheFactory'', which remains as an alias. ConnectionProperties.Password=The password to use when authenticating the user. ConnectionProperties.Password1=The password to use in the first phase of a Multi-Factor Authentication workflow. It is a synonym of the connection property ''password'' and can also be set with user credentials in the connection string. ConnectionProperties.Password2=The password to use in the second phase of a Multi-Factor Authentication workflow. @@ -921,6 +922,7 @@ ConnectionProperties.processEscapeCodesForPrepStmts=Should the driver process es ConnectionProperties.profilerEventHandler=Name of a class that implements the interface ''com.mysql.cj.log.ProfilerEventHandler'' that will be used to handle profiling/tracing events. ConnectionProperties.profileSQL=Trace queries and their execution/fetch times to the configured ''profilerEventHandler''. ConnectionProperties.queriesBeforeRetrySource=When using multi-host failover, the number of queries to issue before falling back to the primary host when failed over. Whichever condition is met first, ''queriesBeforeRetrySource'' or ''secondsBeforeRetrySource'' will cause an attempt to be made to reconnect to the primary host. Setting both properties to "0" disables the automatic fall back to the primary host at transaction boundaries. +ConnectionProperties.queryInfoCacheFactory=Name of a class implementing ''com.mysql.cj.CacheAdapterFactory'' which will be used to create caches for the parsed representation of prepared statements. Prior to version 8.0.29, this property was named ''parseInfoCacheFactory'', which remains as an alias. ConnectionProperties.queryInterceptors=A comma-delimited list of classes that implement ''com.mysql.cj.interceptors.QueryInterceptor'' that intercept query executions and are able influence the results. Query iterceptors are chainable: the results returned by the current interceptor will be passed on to the next in the chain, from left-to-right in the order specified in this property. ConnectionProperties.queryTimeoutKillsConnection=If the timeout given in ''Statement.setQueryTimeout()'' expires, should the driver forcibly abort the connection instead of attempting to abort the query? ConnectionProperties.readFromSourceWhenNoReplicas=Replication-aware connections distribute load by using the source hosts when in read/write state and by using the replica hosts when in read-only state. If, when setting the connection to read-only state, none of the replica hosts are available, an ''SQLException'' is thrown back. Setting this property to "true" allows to fail over to the source hosts, while setting the connection state to read-only, when no replica hosts are available at switch instant. diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/CallableStatement.java b/src/main/user-impl/java/com/mysql/cj/jdbc/CallableStatement.java index 4ac3693f7..6c3cc70ea 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/CallableStatement.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/CallableStatement.java @@ -64,6 +64,10 @@ import com.mysql.cj.result.DefaultColumnDefinition; import com.mysql.cj.result.Field; import com.mysql.cj.result.Row; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.SearchMode; import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.Util; @@ -784,15 +788,15 @@ private void determineParameterTypes() throws SQLException { java.sql.DatabaseMetaData dbmd = this.connection.getMetaData(); if (this.callingStoredFunction) { - paramTypesRs = dbIsSchema ? dbmd.getFunctionColumns(null, useDb ? this.getCurrentDatabase() : tmpDb, procName, "%") - : dbmd.getFunctionColumns(useDb ? this.getCurrentDatabase() : tmpDb, null, procName, "%"); + paramTypesRs = dbIsSchema ? dbmd.getFunctionColumns(null, useDb ? getCurrentDatabase() : tmpDb, procName, "%") + : dbmd.getFunctionColumns(useDb ? getCurrentDatabase() : tmpDb, null, procName, "%"); } else { RuntimeProperty getProcRetFuncProp = this.session.getPropertySet().getBooleanProperty(PropertyKey.getProceduresReturnsFunctions); Boolean getProcRetFuncsCurrentValue = getProcRetFuncProp.getValue(); try { getProcRetFuncProp.setValue(Boolean.FALSE); - paramTypesRs = dbIsSchema ? dbmd.getProcedureColumns(null, useDb ? this.getCurrentDatabase() : tmpDb, procName, "%") - : dbmd.getProcedureColumns(useDb ? this.getCurrentDatabase() : tmpDb, null, procName, "%"); + paramTypesRs = dbIsSchema ? dbmd.getProcedureColumns(null, useDb ? getCurrentDatabase() : tmpDb, procName, "%") + : dbmd.getProcedureColumns(useDb ? getCurrentDatabase() : tmpDb, null, procName, "%"); } finally { getProcRetFuncProp.setValue(getProcRetFuncsCurrentValue); } @@ -841,62 +845,95 @@ private void convertGetProcedureColumnsToInternalDescriptors(java.sql.ResultSet @Override public boolean execute() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - boolean returnVal = false; + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.ROUTINE_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - checkStreamability(); + boolean returnVal = false; - setInOutParamsOnServer(); - setOutParams(); + checkStreamability(); - returnVal = super.execute(); + setInOutParamsOnServer(); + setOutParams(); - if (this.callingStoredFunction) { - this.functionReturnValueResults = this.results; - this.functionReturnValueResults.next(); - this.results = null; - } + returnVal = super.execute(); - // TODO There is something strange here: - // From ResultSetRegressionTest.testBug14562(): - // - // $ CREATE TABLE testBug14562 (row_order INT, signed_field MEDIUMINT, unsigned_field MEDIUMINT UNSIGNED) - // $ INSERT INTO testBug14562 VALUES (1, -8388608, 0), (2, 8388607, 16777215) - // $ CREATE PROCEDURE sp_testBug14562_1 (OUT param_1 MEDIUMINT, OUT param_2 MEDIUMINT UNSIGNED) - // BEGIN - // SELECT signed_field, unsigned_field INTO param_1, param_2 FROM testBug14562 WHERE row_order=1; - // END - // $ CALL sp_testBug14562_1(@com_mysql_jdbc_outparam_param_1, @com_mysql_jdbc_outparam_param_2) - // $ SELECT @com_mysql_jdbc_outparam_param_1,@com_mysql_jdbc_outparam_param_2 - // - // ResultSet metadata returns BIGINT for @com_mysql_jdbc_outparam_param_1 and @com_mysql_jdbc_outparam_param_2 - // instead of expected MEDIUMINT. I wonder what happens to other types... - retrieveOutParams(); - - if (!this.callingStoredFunction) { - return returnVal; - } + if (this.callingStoredFunction) { + this.functionReturnValueResults = this.results; + this.functionReturnValueResults.next(); + this.results = null; + } - // Functions can't return results - return false; + // TODO There is something strange here: + // From ResultSetRegressionTest.testBug14562(): + // + // $ CREATE TABLE testBug14562 (row_order INT, signed_field MEDIUMINT, unsigned_field MEDIUMINT UNSIGNED) + // $ INSERT INTO testBug14562 VALUES (1, -8388608, 0), (2, 8388607, 16777215) + // $ CREATE PROCEDURE sp_testBug14562_1 (OUT param_1 MEDIUMINT, OUT param_2 MEDIUMINT UNSIGNED) + // BEGIN + // SELECT signed_field, unsigned_field INTO param_1, param_2 FROM testBug14562 WHERE row_order=1; + // END + // $ CALL sp_testBug14562_1(@com_mysql_jdbc_outparam_param_1, @com_mysql_jdbc_outparam_param_2) + // $ SELECT @com_mysql_jdbc_outparam_param_1,@com_mysql_jdbc_outparam_param_2 + // + // ResultSet metadata returns BIGINT for @com_mysql_jdbc_outparam_param_1 and @com_mysql_jdbc_outparam_param_2 + // instead of expected MEDIUMINT. I wonder what happens to other types... + retrieveOutParams(); + + if (!this.callingStoredFunction) { + return returnVal; + } + + // Functions can't return results + return false; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @Override public java.sql.ResultSet executeQuery() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.ROUTINE_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - checkStreamability(); + checkStreamability(); - java.sql.ResultSet execResults = null; + java.sql.ResultSet execResults = null; - setInOutParamsOnServer(); - setOutParams(); + setInOutParamsOnServer(); + setOutParams(); - execResults = super.executeQuery(); + execResults = super.executeQuery(); - retrieveOutParams(); + retrieveOutParams(); - return execResults; + return execResults; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -2239,7 +2276,7 @@ private boolean checkReadOnlyProcedure() throws SQLException { try { String procName = extractProcedureName(); - String db = this.getCurrentDatabase(); + String db = getCurrentDatabase(); if (procName.indexOf(".") != -1) { db = procName.substring(0, procName.indexOf(".")); @@ -2478,24 +2515,41 @@ protected byte[] s2b(String s) { @Override public long executeLargeUpdate() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - long returnVal = -1; + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.ROUTINE_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - checkStreamability(); + long returnVal = -1; - if (this.callingStoredFunction) { - execute(); + checkStreamability(); - return -1; - } + if (this.callingStoredFunction) { + execute(); + + return -1; + } - setInOutParamsOnServer(); - setOutParams(); + setInOutParamsOnServer(); + setOutParams(); - returnVal = super.executeLargeUpdate(); + returnVal = super.executeLargeUpdate(); - retrieveOutParams(); + retrieveOutParams(); - return returnVal; + return returnVal; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -2505,8 +2559,23 @@ public long[] executeLargeBatch() throws SQLException { throw SQLError.createSQLException("Can't call executeBatch() on CallableStatement with OUTPUT parameters", MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } - - return super.executeLargeBatch(); + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.ROUTINE_EXECUTE_BATCH); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_BATCH); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_BATCH); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + return super.executeLargeBatch(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ClientPreparedStatement.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ClientPreparedStatement.java index f9295230c..cca0a5f6a 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ClientPreparedStatement.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ClientPreparedStatement.java @@ -72,6 +72,10 @@ import com.mysql.cj.protocol.Message; import com.mysql.cj.protocol.a.NativePacketPayload; import com.mysql.cj.result.Field; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.Util; /** @@ -286,129 +290,160 @@ protected boolean checkReadOnlySafeStatement() throws SQLException { @Override public boolean execute() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + JdbcConnection locallyScopedConn = this.connection; + + if (!this.doPingInstead && !checkReadOnlySafeStatement()) { + throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") + Messages.getString("PreparedStatement.21"), + MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor); + } - JdbcConnection locallyScopedConn = this.connection; + ResultSetInternalMethods rs = null; - if (!this.doPingInstead && !checkReadOnlySafeStatement()) { - throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") + Messages.getString("PreparedStatement.21"), - MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor); - } + this.lastQueryIsOnDupKeyUpdate = false; - ResultSetInternalMethods rs = null; - - this.lastQueryIsOnDupKeyUpdate = false; + if (this.retrieveGeneratedKeys) { + this.lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyUpdate(); + } - if (this.retrieveGeneratedKeys) { - this.lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyUpdate(); - } + this.batchedGeneratedKeys = null; - this.batchedGeneratedKeys = null; + resetCancelledState(); - resetCancelledState(); + implicitlyCloseAllOpenResults(); - implicitlyCloseAllOpenResults(); + clearWarnings(); - clearWarnings(); + if (this.doPingInstead) { + doPingInstead(); - if (this.doPingInstead) { - doPingInstead(); + return true; + } - return true; - } + setupStreamingTimeout(locallyScopedConn); - setupStreamingTimeout(locallyScopedConn); + Message sendPacket = ((PreparedQuery) this.query).fillSendPacket(((PreparedQuery) this.query).getQueryBindings()); - Message sendPacket = ((PreparedQuery) this.query).fillSendPacket(((PreparedQuery) this.query).getQueryBindings()); + String oldDb = null; - String oldDb = null; + if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { + oldDb = locallyScopedConn.getDatabase(); + locallyScopedConn.setDatabase(getCurrentDatabase()); + } - if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) { - oldDb = locallyScopedConn.getDatabase(); - locallyScopedConn.setDatabase(this.getCurrentDatabase()); - } + // + // Check if we have cached metadata for this query... + // + CachedResultSetMetaData cachedMetadata = null; - // - // Check if we have cached metadata for this query... - // - CachedResultSetMetaData cachedMetadata = null; + boolean cacheResultSetMetadata = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue(); + if (cacheResultSetMetadata) { + cachedMetadata = locallyScopedConn.getCachedMetaData(((PreparedQuery) this.query).getOriginalSql()); + } - boolean cacheResultSetMetadata = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue(); - if (cacheResultSetMetadata) { - cachedMetadata = locallyScopedConn.getCachedMetaData(((PreparedQuery) this.query).getOriginalSql()); - } + // + // Only apply max_rows to selects + // + locallyScopedConn.setSessionMaxRows(getQueryInfo().getFirstStmtChar() == 'S' ? this.maxRows : -1); - // - // Only apply max_rows to selects - // - locallyScopedConn.setSessionMaxRows(getQueryInfo().getFirstStmtChar() == 'S' ? this.maxRows : -1); + rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), getQueryInfo().getFirstStmtChar() == 'S', cachedMetadata, false); - rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), getQueryInfo().getFirstStmtChar() == 'S', cachedMetadata, false); + if (cachedMetadata != null) { + locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery) this.query).getOriginalSql(), cachedMetadata, rs); + } else if (rs.hasRows() && cacheResultSetMetadata) { + locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery) this.query).getOriginalSql(), null /* will be created */, rs); + } - if (cachedMetadata != null) { - locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery) this.query).getOriginalSql(), cachedMetadata, rs); - } else if (rs.hasRows() && cacheResultSetMetadata) { - locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery) this.query).getOriginalSql(), null /* will be created */, rs); - } + if (this.retrieveGeneratedKeys) { + rs.setFirstCharOfQuery(getQueryInfo().getFirstStmtChar()); + } - if (this.retrieveGeneratedKeys) { - rs.setFirstCharOfQuery(getQueryInfo().getFirstStmtChar()); - } + if (oldDb != null) { + locallyScopedConn.setDatabase(oldDb); + } - if (oldDb != null) { - locallyScopedConn.setDatabase(oldDb); - } + if (rs != null) { + this.lastInsertId = rs.getUpdateID(); - if (rs != null) { - this.lastInsertId = rs.getUpdateID(); + this.results = rs; + } - this.results = rs; + return rs != null && rs.hasRows(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } - - return rs != null && rs.hasRows(); } } @Override protected long[] executeBatchInternal() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE_BATCH_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_BATCH); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_BATCH); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + if (this.connection.isReadOnly()) { + throw new SQLException(Messages.getString("PreparedStatement.25") + Messages.getString("PreparedStatement.26"), + MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT); + } - if (this.connection.isReadOnly()) { - throw new SQLException(Messages.getString("PreparedStatement.25") + Messages.getString("PreparedStatement.26"), - MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT); - } + if (this.query.getBatchedArgs() == null || this.query.getBatchedArgs().size() == 0) { + return new long[0]; + } - if (this.query.getBatchedArgs() == null || this.query.getBatchedArgs().size() == 0) { - return new long[0]; - } + // we timeout the entire batch, not individual statements + long batchTimeout = getTimeoutInMillis(); + setTimeoutInMillis(0); - // we timeout the entire batch, not individual statements - long batchTimeout = getTimeoutInMillis(); - setTimeoutInMillis(0); + resetCancelledState(); - resetCancelledState(); + try { + statementBegins(); - try { - statementBegins(); + clearWarnings(); - clearWarnings(); + if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) { - if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) { + if (getQueryInfo().isRewritableWithMultiValuesClause()) { + return executeBatchWithMultiValuesClause(batchTimeout); + } - if (getQueryInfo().isRewritableWithMultiValuesClause()) { - return executeBatchWithMultiValuesClause(batchTimeout); + if (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null + && this.query.getBatchedArgs().size() > 3 /* cost of option setting rt-wise */) { + return executePreparedBatchAsMultiStatement(batchTimeout); + } } - if (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null - && this.query.getBatchedArgs().size() > 3 /* cost of option setting rt-wise */) { - return executePreparedBatchAsMultiStatement(batchTimeout); - } - } + return executeBatchSerially(batchTimeout); + } finally { + this.query.getStatementExecuting().set(false); - return executeBatchSerially(batchTimeout); + clearBatch(); + } + } catch (Throwable t) { + span.setError(t); + throw t; } finally { - this.query.getStatementExecuting().set(false); - - clearBatch(); + span.end(); } } } @@ -906,71 +941,87 @@ protected ResultSetInternalMethods executeInternal(int maxRo @Override public java.sql.ResultSet executeQuery() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - - JdbcConnection locallyScopedConn = this.connection; - - if (!this.doPingInstead) { - QueryReturnType queryReturnType = getQueryInfo().getQueryReturnType(); - if (queryReturnType != QueryReturnType.PRODUCES_RESULT_SET && queryReturnType != QueryReturnType.MAY_PRODUCE_RESULT_SET) { - throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, - getExceptionInterceptor()); + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + JdbcConnection locallyScopedConn = this.connection; + + if (!this.doPingInstead) { + QueryReturnType queryReturnType = getQueryInfo().getQueryReturnType(); + if (queryReturnType != QueryReturnType.PRODUCES_RESULT_SET && queryReturnType != QueryReturnType.MAY_PRODUCE_RESULT_SET) { + throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, + getExceptionInterceptor()); + } } - } - this.batchedGeneratedKeys = null; + this.batchedGeneratedKeys = null; - resetCancelledState(); + resetCancelledState(); - implicitlyCloseAllOpenResults(); + implicitlyCloseAllOpenResults(); - clearWarnings(); + clearWarnings(); - if (this.doPingInstead) { - doPingInstead(); + if (this.doPingInstead) { + doPingInstead(); - return this.results; - } + return this.results; + } - setupStreamingTimeout(locallyScopedConn); + setupStreamingTimeout(locallyScopedConn); - Message sendPacket = ((PreparedQuery) this.query).fillSendPacket(((PreparedQuery) this.query).getQueryBindings()); + Message sendPacket = ((PreparedQuery) this.query).fillSendPacket(((PreparedQuery) this.query).getQueryBindings()); - String oldDb = null; + String oldDb = null; - if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) { - oldDb = locallyScopedConn.getDatabase(); - locallyScopedConn.setDatabase(this.getCurrentDatabase()); - } + if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { + oldDb = locallyScopedConn.getDatabase(); + locallyScopedConn.setDatabase(getCurrentDatabase()); + } - // - // Check if we have cached metadata for this query... - // - CachedResultSetMetaData cachedMetadata = null; - boolean cacheResultSetMetadata = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue(); + // + // Check if we have cached metadata for this query... + // + CachedResultSetMetaData cachedMetadata = null; + boolean cacheResultSetMetadata = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue(); - String origSql = ((PreparedQuery) this.query).getOriginalSql(); + String origSql = ((PreparedQuery) this.query).getOriginalSql(); - if (cacheResultSetMetadata) { - cachedMetadata = locallyScopedConn.getCachedMetaData(origSql); - } + if (cacheResultSetMetadata) { + cachedMetadata = locallyScopedConn.getCachedMetaData(origSql); + } - locallyScopedConn.setSessionMaxRows(this.maxRows); + locallyScopedConn.setSessionMaxRows(this.maxRows); - this.results = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), true, cachedMetadata, false); + this.results = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), true, cachedMetadata, false); - if (oldDb != null) { - locallyScopedConn.setDatabase(oldDb); - } + if (oldDb != null) { + locallyScopedConn.setDatabase(oldDb); + } - if (cachedMetadata != null) { - locallyScopedConn.initializeResultsMetadataFromCache(origSql, cachedMetadata, this.results); - } else if (cacheResultSetMetadata) { - locallyScopedConn.initializeResultsMetadataFromCache(origSql, null /* will be created */, this.results); - } + if (cachedMetadata != null) { + locallyScopedConn.initializeResultsMetadataFromCache(origSql, cachedMetadata, this.results); + } else if (cacheResultSetMetadata) { + locallyScopedConn.initializeResultsMetadataFromCache(origSql, null /* will be created */, this.results); + } - this.lastInsertId = this.results.getUpdateID(); + this.lastInsertId = this.results.getUpdateID(); - return this.results; + return this.results; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -1010,61 +1061,77 @@ protected long executeUpdateInternal(boolean clearBatchedGeneratedKeysAndWarning */ protected long executeUpdateInternal(QueryBindings bindings, boolean isReallyBatch) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + JdbcConnection locallyScopedConn = this.connection; + + if (locallyScopedConn.isReadOnly(false)) { + throw SQLError.createSQLException(Messages.getString("PreparedStatement.34") + Messages.getString("PreparedStatement.35"), + MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor); + } - JdbcConnection locallyScopedConn = this.connection; - - if (locallyScopedConn.isReadOnly(false)) { - throw SQLError.createSQLException(Messages.getString("PreparedStatement.34") + Messages.getString("PreparedStatement.35"), - MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor); - } - - if (!isNonResultSetProducingQuery()) { - throw SQLError.createSQLException(Messages.getString("PreparedStatement.37"), "01S03", this.exceptionInterceptor); - } + if (!isNonResultSetProducingQuery()) { + throw SQLError.createSQLException(Messages.getString("PreparedStatement.37"), "01S03", this.exceptionInterceptor); + } - resetCancelledState(); + resetCancelledState(); - implicitlyCloseAllOpenResults(); + implicitlyCloseAllOpenResults(); - ResultSetInternalMethods rs = null; + ResultSetInternalMethods rs = null; - Message sendPacket = ((PreparedQuery) this.query).fillSendPacket(bindings); + Message sendPacket = ((PreparedQuery) this.query).fillSendPacket(bindings); - String oldDb = null; + String oldDb = null; - if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) { - oldDb = locallyScopedConn.getDatabase(); - locallyScopedConn.setDatabase(this.getCurrentDatabase()); - } + if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { + oldDb = locallyScopedConn.getDatabase(); + locallyScopedConn.setDatabase(getCurrentDatabase()); + } - // - // Only apply max_rows to selects - // - locallyScopedConn.setSessionMaxRows(-1); + // + // Only apply max_rows to selects + // + locallyScopedConn.setSessionMaxRows(-1); - rs = executeInternal(-1, sendPacket, false, false, null, isReallyBatch); + rs = executeInternal(-1, sendPacket, false, false, null, isReallyBatch); - if (this.retrieveGeneratedKeys) { - rs.setFirstCharOfQuery(getQueryInfo().getFirstStmtChar()); - } + if (this.retrieveGeneratedKeys) { + rs.setFirstCharOfQuery(getQueryInfo().getFirstStmtChar()); + } - if (oldDb != null) { - locallyScopedConn.setDatabase(oldDb); - } + if (oldDb != null) { + locallyScopedConn.setDatabase(oldDb); + } - this.results = rs; + this.results = rs; - this.updateCount = rs.getUpdateCount(); + this.updateCount = rs.getUpdateCount(); - if (containsOnDuplicateKeyUpdate() && this.compensateForOnDuplicateKeyUpdate) { - if (this.updateCount == 2 || this.updateCount == 0) { - this.updateCount = 1; + if (containsOnDuplicateKeyUpdate() && this.compensateForOnDuplicateKeyUpdate) { + if (this.updateCount == 2 || this.updateCount == 0) { + this.updateCount = 1; + } } - } - this.lastInsertId = rs.getUpdateID(); + this.lastInsertId = rs.getUpdateID(); - return this.updateCount; + return this.updateCount; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -1086,7 +1153,7 @@ protected boolean containsOnDuplicateKeyUpdate() { protected ClientPreparedStatement prepareBatchedInsertSQL(JdbcConnection localConn, int numBatches) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { ClientPreparedStatement pstmt = new ClientPreparedStatement(localConn, "Rewritten batch of: " + ((PreparedQuery) this.query).getOriginalSql(), - this.getCurrentDatabase(), getQueryInfo().getQueryInfoForBatch(numBatches)); + getCurrentDatabase(), getQueryInfo().getQueryInfoForBatch(numBatches)); pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys); pstmt.rewrittenBatchSize = numBatches; @@ -1128,8 +1195,7 @@ public java.sql.ResultSetMetaData getMetaData() throws SQLException { if (this.pstmtResultMetaData == null) { try { - mdStmt = new ClientPreparedStatement(this.connection, ((PreparedQuery) this.query).getOriginalSql(), this.getCurrentDatabase(), - getQueryInfo()); + mdStmt = new ClientPreparedStatement(this.connection, ((PreparedQuery) this.query).getOriginalSql(), getCurrentDatabase(), getQueryInfo()); mdStmt.setMaxRows(1); diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionImpl.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionImpl.java index 128cd984a..7a547b343 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionImpl.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionImpl.java @@ -23,6 +23,8 @@ import java.io.Serializable; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.sql.Blob; import java.sql.Clob; import java.sql.DatabaseMetaData; @@ -81,10 +83,13 @@ import com.mysql.cj.jdbc.result.ResultSetInternalMethods; import com.mysql.cj.jdbc.result.UpdatableResultSet; import com.mysql.cj.log.ProfilerEvent; -import com.mysql.cj.log.StandardLogger; import com.mysql.cj.protocol.ServerSessionStateController; import com.mysql.cj.protocol.SocksProxySocketFactory; import com.mysql.cj.protocol.a.NativeProtocol; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.LRUCache; import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.Util; @@ -194,9 +199,6 @@ public int hashCode() { } - /** Default logger class name */ - protected static final String DEFAULT_LOGGER_CLASS = StandardLogger.class.getName(); - /** * Map mysql transaction isolation level name to * java.sql.Connection.TRANSACTION_XXX @@ -347,6 +349,8 @@ private static boolean nullSafeCompare(String s1, String s2) { protected ResultSetFactory nullStatementResultSetFactory; + TelemetrySpan connectionSpan = null; + /** * ' * For the delegate only @@ -381,70 +385,105 @@ public ConnectionImpl(HostInfo hostInfo) throws SQLException { this.nullStatementResultSetFactory = new ResultSetFactory(this, null); this.session = new NativeSession(hostInfo, this.propertySet); this.session.addListener(this); // listen for session status changes - - // we can't cache fixed values here because properties are still not initialized with user provided values - this.autoReconnectForPools = this.propertySet.getBooleanProperty(PropertyKey.autoReconnectForPools); - this.cachePrepStmts = this.propertySet.getBooleanProperty(PropertyKey.cachePrepStmts); - this.autoReconnect = this.propertySet.getBooleanProperty(PropertyKey.autoReconnect); - this.useUsageAdvisor = this.propertySet.getBooleanProperty(PropertyKey.useUsageAdvisor); - this.reconnectAtTxEnd = this.propertySet.getBooleanProperty(PropertyKey.reconnectAtTxEnd); - this.emulateUnsupportedPstmts = this.propertySet.getBooleanProperty(PropertyKey.emulateUnsupportedPstmts); - this.ignoreNonTxTables = this.propertySet.getBooleanProperty(PropertyKey.ignoreNonTxTables); - this.pedantic = this.propertySet.getBooleanProperty(PropertyKey.pedantic); - this.prepStmtCacheSqlLimit = this.propertySet.getIntegerProperty(PropertyKey.prepStmtCacheSqlLimit); - this.useLocalSessionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalSessionState); - this.useServerPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.useServerPrepStmts); - this.processEscapeCodesForPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.processEscapeCodesForPrepStmts); - this.useLocalTransactionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalTransactionState); - this.disconnectOnExpiredPasswords = this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords); - this.readOnlyPropagatesToServer = this.propertySet.getBooleanProperty(PropertyKey.readOnlyPropagatesToServer); - - String exceptionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.exceptionInterceptors).getStringValue(); - if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) { - this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses, this.props, this.session.getLog()); - } - if (this.cachePrepStmts.getValue()) { - createPreparedStatementCaches(); - } - if (this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) { - this.parsedCallableStatementCache = new LRUCache<>(this.propertySet.getIntegerProperty(PropertyKey.callableStmtCacheSize).getValue()); - } - if (this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue()) { - this.propertySet.getProperty(PropertyKey.cacheResultSetMetadata).setValue(false); // we don't handle this yet - } - if (this.propertySet.getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { - this.resultSetMetadataCache = new LRUCache<>(this.propertySet.getIntegerProperty(PropertyKey.metadataCacheSize).getValue()); - } - if (this.propertySet.getStringProperty(PropertyKey.socksProxyHost).getStringValue() != null) { - this.propertySet.getProperty(PropertyKey.socketFactory).setValue(SocksProxySocketFactory.class.getName()); - } - - this.dbmd = getMetaData(false, false); - initializeSafeQueryInterceptors(); } catch (CJException e) { throw SQLExceptionsMapping.translateException(e, getExceptionInterceptor()); } - try { - createNewIO(false); + this.connectionSpan = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.CONNECTION_CREATE); + this.session.getTelemetryHandler().addLinkTarget(this.connectionSpan); + try (TelemetryScope scope = this.connectionSpan.makeCurrent()) { + this.connectionSpan.setAttribute(TelemetryAttribute.DB_CONNECTION_STRING, getURL()); + this.connectionSpan.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + this.connectionSpan.setAttribute(TelemetryAttribute.DB_USER, getUser()); + this.connectionSpan.setAttribute(TelemetryAttribute.SERVER_ADDRESS, this.origHostToConnectTo); + this.connectionSpan.setAttribute(TelemetryAttribute.SERVER_PORT, this.origPortToConnectTo); + this.connectionSpan.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + this.connectionSpan.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + try { + String exceptionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.exceptionInterceptors).getStringValue(); + if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) { + this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses, this.props, this.session.getLog()); + } + + // we can't cache fixed values here because properties are still not initialized with user provided values + this.autoReconnectForPools = this.propertySet.getBooleanProperty(PropertyKey.autoReconnectForPools); + this.cachePrepStmts = this.propertySet.getBooleanProperty(PropertyKey.cachePrepStmts); + this.autoReconnect = this.propertySet.getBooleanProperty(PropertyKey.autoReconnect); + this.useUsageAdvisor = this.propertySet.getBooleanProperty(PropertyKey.useUsageAdvisor); + this.reconnectAtTxEnd = this.propertySet.getBooleanProperty(PropertyKey.reconnectAtTxEnd); + this.emulateUnsupportedPstmts = this.propertySet.getBooleanProperty(PropertyKey.emulateUnsupportedPstmts); + this.ignoreNonTxTables = this.propertySet.getBooleanProperty(PropertyKey.ignoreNonTxTables); + this.pedantic = this.propertySet.getBooleanProperty(PropertyKey.pedantic); + this.prepStmtCacheSqlLimit = this.propertySet.getIntegerProperty(PropertyKey.prepStmtCacheSqlLimit); + this.useLocalSessionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalSessionState); + this.useServerPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.useServerPrepStmts); + this.processEscapeCodesForPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.processEscapeCodesForPrepStmts); + this.useLocalTransactionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalTransactionState); + this.disconnectOnExpiredPasswords = this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords); + this.readOnlyPropagatesToServer = this.propertySet.getBooleanProperty(PropertyKey.readOnlyPropagatesToServer); - unSafeQueryInterceptors(); + if (this.cachePrepStmts.getValue()) { + createPreparedStatementCaches(); + } + if (this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) { + this.parsedCallableStatementCache = new LRUCache<>(this.propertySet.getIntegerProperty(PropertyKey.callableStmtCacheSize).getValue()); + } + if (this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue()) { + this.propertySet.getProperty(PropertyKey.cacheResultSetMetadata).setValue(false); // we don't handle this yet + } + if (this.propertySet.getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { + this.resultSetMetadataCache = new LRUCache<>(this.propertySet.getIntegerProperty(PropertyKey.metadataCacheSize).getValue()); + } + if (this.propertySet.getStringProperty(PropertyKey.socksProxyHost).getStringValue() != null) { + this.propertySet.getProperty(PropertyKey.socketFactory).setValue(SocksProxySocketFactory.class.getName()); + } + + this.dbmd = getMetaData(false, false); + initializeSafeQueryInterceptors(); + } catch (CJException e) { + throw SQLExceptionsMapping.translateException(e, getExceptionInterceptor()); + } + + try { + createNewIO(false); - AbandonedConnectionCleanupThread.trackConnection(this, this.getSession().getNetworkResources()); - } catch (SQLException ex) { - cleanup(ex); + unSafeQueryInterceptors(); + + AbandonedConnectionCleanupThread.trackConnection(this, this.getSession().getNetworkResources()); + + SocketAddress socketAddress = this.session.getRemoteSocketAddress(); + if (InetSocketAddress.class.isInstance(socketAddress)) { + InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress; + this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_PEER_ADDRESS, inetSocketAddress.getHostName()); + this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_PEER_PORT, inetSocketAddress.getPort()); + this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_TRANSPORT, TelemetryAttribute.NETWORK_TRANSPORT_TCP); + } + if (this.propertySet.getStringProperty(PropertyKey.socketFactory).getValue().equalsIgnoreCase("com.mysql.cj.protocol.NamedPipeSocketFactory")) { + this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_TRANSPORT, TelemetryAttribute.NETWORK_TRANSPORT_PIPE); + } else if (StringUtils.indexOfIgnoreCase(socketAddress.getClass().getName(), "UNIXSocket") >= 0) { + this.connectionSpan.setAttribute(TelemetryAttribute.NETWORK_TRANSPORT, TelemetryAttribute.NETWORK_TRANSPORT_UNIX); + } - // don't clobber SQL exceptions - throw ex; - } catch (Exception ex) { - cleanup(ex); + } catch (SQLException ex) { + cleanup(ex); - throw SQLError - .createSQLException( - this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() ? Messages.getString("Connection.0") - : Messages.getString("Connection.1", - new Object[] { this.session.getHostInfo().getHost(), this.session.getHostInfo().getPort() }), - MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE, ex, getExceptionInterceptor()); + // don't clobber SQL exceptions + throw ex; + } catch (Exception ex) { + cleanup(ex); + + throw SQLError.createSQLException( + this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() ? Messages.getString("Connection.0") + : Messages.getString("Connection.1", + new Object[] { this.session.getHostInfo().getHost(), this.session.getHostInfo().getPort() }), + MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE, ex, getExceptionInterceptor()); + } + } catch (Throwable t) { + this.connectionSpan.setError(t); + throw t; + } finally { + this.connectionSpan.end(); } } @@ -769,7 +808,23 @@ void forEach(ConnectionLifecycleInterceptor each) throws SQLException { } } - this.session.execSQL(null, "commit", -1, null, false, this.nullStatementResultSetFactory, null, false); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.ROLLBACK); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_ROLLBACK); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_ROLLBACK); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.session.execSQL(null, "COMMIT", -1, null, false, this.nullStatementResultSetFactory, null, false); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } catch (SQLException sqlException) { if (MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE.equals(sqlException.getSQLState())) { throw SQLError.createSQLException(Messages.getString("Connection.4"), MysqlErrorNumbers.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, @@ -921,7 +976,6 @@ private void connectOneTryOnly(boolean isForReconnect) throws SQLException { Exception connectionNotEstablishedBecause = null; try { - JdbcConnection c = getProxy(); this.session.connect(this.origHostInfo, this.user, this.password, this.database, getLoginTimeout(), c); @@ -1224,8 +1278,7 @@ public boolean hasTriedMaster() { } /** - * Sets varying properties that depend on server information. Called once we - * have connected to the server. + * Sets varying properties that depend on server information. Called once we have connected to the server. * * @throws SQLException * if a database access error occurs @@ -1466,35 +1519,56 @@ public java.sql.CallableStatement prepareCall(String sql) throws SQLException { @Override public java.sql.CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { - CallableStatement cStmt = null; - - if (!this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) { + synchronized (getConnectionMutex()) { + checkClosed(); - cStmt = parseCallableStatement(sql); - } else { - synchronized (this.parsedCallableStatementCache) { - CompoundCacheKey key = new CompoundCacheKey(getDatabase(), sql); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.ROUTINE_PREPARE); + try (TelemetryScope scope = span.makeCurrent()) { + CallableStatement cStmt = null; - CallableStatement.CallableStatementParamInfo cachedParamInfo = this.parsedCallableStatementCache.get(key); + if (!this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) { - if (cachedParamInfo != null) { - cStmt = CallableStatement.getInstance(getMultiHostSafeProxy(), cachedParamInfo); - } else { cStmt = parseCallableStatement(sql); + } else { + synchronized (this.parsedCallableStatementCache) { + CompoundCacheKey key = new CompoundCacheKey(getDatabase(), sql); - synchronized (cStmt) { - cachedParamInfo = cStmt.paramInfo; - } + CallableStatement.CallableStatementParamInfo cachedParamInfo = this.parsedCallableStatementCache.get(key); + + if (cachedParamInfo != null) { + cStmt = CallableStatement.getInstance(getMultiHostSafeProxy(), cachedParamInfo); + } else { + cStmt = parseCallableStatement(sql); - this.parsedCallableStatementCache.put(key, cachedParamInfo); + synchronized (cStmt) { + cachedParamInfo = cStmt.paramInfo; + } + + this.parsedCallableStatementCache.put(key, cachedParamInfo); + } + } } - } - } - cStmt.setResultSetType(resultSetType); - cStmt.setResultSetConcurrency(resultSetConcurrency); + cStmt.setResultSetType(resultSetType); + cStmt.setResultSetConcurrency(resultSetConcurrency); - return cStmt; + String dbOperation = cStmt.getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + return cStmt; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } + } } @Override @@ -1529,75 +1603,92 @@ public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType synchronized (getConnectionMutex()) { checkClosed(); - // - // FIXME: Create warnings if can't create results of the given type or concurrency - // - ClientPreparedStatement pStmt = null; - - boolean canServerPrepare = true; - - String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_PREPARE); + try (TelemetryScope scope = span.makeCurrent()) { + // + // FIXME: Create warnings if can't create results of the given type or concurrency + // + ClientPreparedStatement pStmt = null; - if (this.useServerPrepStmts.getValue() && this.emulateUnsupportedPstmts.getValue()) { - canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); - } + boolean canServerPrepare = true; - if (this.useServerPrepStmts.getValue() && canServerPrepare) { - if (this.cachePrepStmts.getValue()) { - synchronized (this.serverSideStatementCache) { - pStmt = this.serverSideStatementCache.remove(new CompoundCacheKey(this.database, sql)); + String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql; - if (pStmt != null) { - ((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).setClosed(false); - pStmt.clearParameters(); - pStmt.setResultSetType(resultSetType); - pStmt.setResultSetConcurrency(resultSetConcurrency); - } + if (this.useServerPrepStmts.getValue() && this.emulateUnsupportedPstmts.getValue()) { + canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); + } - if (pStmt == null) { - try { - pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, - resultSetConcurrency); - if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) { - ((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).isCacheable = true; - } + if (this.useServerPrepStmts.getValue() && canServerPrepare) { + if (this.cachePrepStmts.getValue()) { + synchronized (this.serverSideStatementCache) { + pStmt = this.serverSideStatementCache.remove(new CompoundCacheKey(this.database, sql)); + if (pStmt != null) { + ((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).setClosed(false); + pStmt.clearParameters(); pStmt.setResultSetType(resultSetType); pStmt.setResultSetConcurrency(resultSetConcurrency); - } catch (SQLException sqlEx) { - // Punt, if necessary - if (this.emulateUnsupportedPstmts.getValue()) { - pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); + } + if (pStmt == null) { + try { + pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, + resultSetConcurrency); if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) { - this.serverSideStatementCheckCache.put(sql, Boolean.FALSE); + ((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).isCacheable = true; + } + + pStmt.setResultSetType(resultSetType); + pStmt.setResultSetConcurrency(resultSetConcurrency); + } catch (SQLException sqlEx) { + // Punt, if necessary + if (this.emulateUnsupportedPstmts.getValue()) { + pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); + + if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) { + this.serverSideStatementCheckCache.put(sql, Boolean.FALSE); + } + } else { + throw sqlEx; } - } else { - throw sqlEx; } } } - } - } else { - try { - pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); - - pStmt.setResultSetType(resultSetType); - pStmt.setResultSetConcurrency(resultSetConcurrency); - } catch (SQLException sqlEx) { - // Punt, if necessary - if (this.emulateUnsupportedPstmts.getValue()) { - pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); - } else { - throw sqlEx; + } else { + try { + pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); + + pStmt.setResultSetType(resultSetType); + pStmt.setResultSetConcurrency(resultSetConcurrency); + } catch (SQLException sqlEx) { + // Punt, if necessary + if (this.emulateUnsupportedPstmts.getValue()) { + pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); + } else { + throw sqlEx; + } } } + } else { + pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } - } else { - pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); - } - return pStmt; + String dbOperation = pStmt.getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + return pStmt; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -1690,6 +1781,7 @@ public void realClose(boolean calledExplicitly, boolean issueRollback, boolean s this.queryInterceptors = null; this.exceptionInterceptor = null; this.nullStatementResultSetFactory = null; + this.session.getTelemetryHandler().removeLinkTarget(this.connectionSpan); } if (sqlEx != null) { @@ -1752,12 +1844,28 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException { @Override public void resetServerState() throws SQLException { if (!this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() && this.session != null) { - this.session.getServerSession().getCharsetSettings().configurePreHandshake(true); - this.session.resetSessionState(); - this.session.getServerSession().getCharsetSettings().configurePostHandshake(true); - this.session.setSessionVariables(); - handleAutoCommitDefaults(); - setupServerForTruncationChecks(); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.CONNECTION_RESET); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_CONNECTION_STRING, getURL()); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.SERVER_ADDRESS, this.origHostToConnectTo); + span.setAttribute(TelemetryAttribute.SERVER_PORT, this.origPortToConnectTo); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.session.getServerSession().getCharsetSettings().configurePreHandshake(true); + this.session.resetSessionState(); + this.session.getServerSession().getCharsetSettings().configurePostHandshake(true); + this.session.setSessionVariables(); + handleAutoCommitDefaults(); + setupServerForTruncationChecks(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -1887,7 +1995,23 @@ private void rollbackNoChecks() throws SQLException { } } - this.session.execSQL(null, "rollback", -1, null, false, this.nullStatementResultSetFactory, null, false); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.ROLLBACK); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_ROLLBACK); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_ROLLBACK); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.session.execSQL(null, "ROLLBACK", -1, null, false, this.nullStatementResultSetFactory, null, false); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -1993,8 +2117,24 @@ void forEach(ConnectionLifecycleInterceptor each) throws SQLException { this.session.getServerSession().setAutoCommit(autoCommitFlag); if (needsSetOnServer) { - this.session.execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, false, this.nullStatementResultSetFactory, - null, false); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "autocommit"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.session.execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, false, + this.nullStatementResultSetFactory, null, false); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } catch (CJCommunicationsException e) { throw e; @@ -2059,14 +2199,30 @@ void forEach(ConnectionLifecycleInterceptor each) throws SQLException { } } - String quotedId = this.session.getIdentifierQuoteString(); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.USE_DATABASE); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, db); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_USE); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_USE + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + String quotedId = this.session.getIdentifierQuoteString(); - StringBuilder query = new StringBuilder("USE "); - query.append(StringUtils.quoteIdentifier(db, quotedId, this.pedantic.getValue())); + StringBuilder query = new StringBuilder("USE "); + query.append(StringUtils.quoteIdentifier(db, quotedId, this.pedantic.getValue())); - this.session.execSQL(null, query.toString(), -1, null, false, this.nullStatementResultSetFactory, null, false); + this.session.execSQL(null, query.toString(), -1, null, false, this.nullStatementResultSetFactory, null, false); - this.database = db; + this.database = db; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -2103,8 +2259,26 @@ public void setReadOnlyInternal(boolean readOnlyFlag) throws SQLException { // note this this is safe even inside a transaction if (this.readOnlyPropagatesToServer.getValue() && versionMeetsMinimum(5, 6, 5)) { if (!this.useLocalSessionState.getValue() || readOnlyFlag != this.readOnly) { - this.session.execSQL(null, "set session transaction " + (readOnlyFlag ? "read only" : "read write"), -1, null, false, - this.nullStatementResultSetFactory, null, false); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_TRANSACTION_ACCESS_MODE, + readOnlyFlag ? "read-only" : "read-write"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.session.execSQL(null, "SET SESSION TRANSACTION " + (readOnlyFlag ? "READ ONLY" : "READ WRITE"), -1, null, false, + this.nullStatementResultSetFactory, null, false); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } + } } @@ -2164,38 +2338,55 @@ public void setTransactionIsolation(int level) throws SQLException { } if (shouldSendSet) { - switch (level) { - case java.sql.Connection.TRANSACTION_NONE: - throw SQLError.createSQLException(Messages.getString("Connection.24"), getExceptionInterceptor()); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_TRANSACTION_ISOLATION); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - case java.sql.Connection.TRANSACTION_READ_COMMITTED: - sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"; + switch (level) { + case java.sql.Connection.TRANSACTION_NONE: + throw SQLError.createSQLException(Messages.getString("Connection.24"), getExceptionInterceptor()); - break; + case java.sql.Connection.TRANSACTION_READ_COMMITTED: + sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"; - case java.sql.Connection.TRANSACTION_READ_UNCOMMITTED: - sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; + break; - break; + case java.sql.Connection.TRANSACTION_READ_UNCOMMITTED: + sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; - case java.sql.Connection.TRANSACTION_REPEATABLE_READ: - sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"; + break; - break; + case java.sql.Connection.TRANSACTION_REPEATABLE_READ: + sql = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"; - case java.sql.Connection.TRANSACTION_SERIALIZABLE: - sql = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE"; + break; - break; + case java.sql.Connection.TRANSACTION_SERIALIZABLE: + sql = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE"; - default: - throw SQLError.createSQLException(Messages.getString("Connection.25", new Object[] { level }), - MysqlErrorNumbers.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); - } + break; + + default: + throw SQLError.createSQLException(Messages.getString("Connection.25", new Object[] { level }), + MysqlErrorNumbers.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); + } + + this.session.execSQL(null, sql, -1, null, false, this.nullStatementResultSetFactory, null, false); - this.session.execSQL(null, sql, -1, null, false, this.nullStatementResultSetFactory, null, false); + this.isolationLevel = level; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } - this.isolationLevel = level; } } } @@ -2216,18 +2407,34 @@ private void setupServerForTruncationChecks() throws SQLException { boolean strictTransTablesIsSet = StringUtils.indexOfIgnoreCase(currentSqlMode, "STRICT_TRANS_TABLES") != -1; if (currentSqlMode == null || currentSqlMode.length() == 0 || !strictTransTablesIsSet) { - StringBuilder commandBuf = new StringBuilder("SET sql_mode='"); - - if (currentSqlMode != null && currentSqlMode.length() > 0) { - commandBuf.append(currentSqlMode); - commandBuf.append(","); - } + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "sql_mode"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + StringBuilder commandBuf = new StringBuilder("SET sql_mode='"); + + if (currentSqlMode != null && currentSqlMode.length() > 0) { + commandBuf.append(currentSqlMode); + commandBuf.append(","); + } - commandBuf.append("STRICT_TRANS_TABLES'"); + commandBuf.append("STRICT_TRANS_TABLES'"); - this.session.execSQL(null, commandBuf.toString(), -1, null, false, this.nullStatementResultSetFactory, null, false); + this.session.execSQL(null, commandBuf.toString(), -1, null, false, this.nullStatementResultSetFactory, null, false); - jdbcCompliantTruncation.setValue(false); // server's handling this for us now + jdbcCompliantTruncation.setValue(false); // server's handling this for us now + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } else if (strictTransTablesIsSet) { // We didn't set it, but someone did, so we piggy back on it jdbcCompliantTruncation.setValue(false); // server's handling this for us now @@ -2300,12 +2507,12 @@ public void initializeResultsMetadataFromCache(String sql, CachedResultSetMetaDa @Override public String getStatementComment() { - return this.session.getProtocol().getQueryComment(); + return this.session.getQueryComment(); } @Override public void setStatementComment(String comment) { - this.session.getProtocol().setQueryComment(comment); + this.session.setQueryComment(comment); } @Override @@ -2362,9 +2569,26 @@ public void setSessionMaxRows(int max) throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); if (this.session.getSessionMaxRows() != max) { - this.session.setSessionMaxRows(max); - this.session.execSQL(null, "SET SQL_SELECT_LIMIT=" + (this.session.getSessionMaxRows() == -1 ? "DEFAULT" : this.session.getSessionMaxRows()), - -1, null, false, this.nullStatementResultSetFactory, null, false); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "sql_select_limit"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + this.session.setSessionMaxRows(max); + this.session.execSQL(null, + "SET sql_select_limit=" + (this.session.getSessionMaxRows() == -1 ? "DEFAULT" : this.session.getSessionMaxRows()), -1, null, false, + this.nullStatementResultSetFactory, null, false); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaData.java b/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaData.java index 22cc07e6b..e0e0631cd 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaData.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaData.java @@ -47,6 +47,7 @@ import com.mysql.cj.Messages; import com.mysql.cj.MysqlType; import com.mysql.cj.NativeSession; +import com.mysql.cj.QueryInfo; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.RuntimeProperty; @@ -63,6 +64,10 @@ import com.mysql.cj.result.DefaultColumnDefinition; import com.mysql.cj.result.Field; import com.mysql.cj.result.Row; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.SearchMode; import com.mysql.cj.util.StringUtils; @@ -4948,16 +4953,33 @@ public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { * if a database access error occurs */ protected java.sql.PreparedStatement prepareMetaDataSafeStatement(String sql) throws SQLException { - // Can't use server-side here as we coerce a lot of types to match the spec. - java.sql.PreparedStatement pStmt = this.conn.clientPrepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); - - if (pStmt.getMaxRows() != 0) { - pStmt.setMaxRows(0); - } + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_PREPARE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = QueryInfo.getStatementKeyword(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + span.setAttribute(TelemetryAttribute.DB_NAME, this.conn.getDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.conn.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + // Can't use server-side here as we coerce a lot of types to match the spec. + java.sql.PreparedStatement pStmt = this.conn.clientPrepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + + if (pStmt.getMaxRows() != 0) { + pStmt.setMaxRows(0); + } - ((com.mysql.cj.jdbc.JdbcStatement) pStmt).setHoldResultsOpenOverClose(true); + ((com.mysql.cj.jdbc.JdbcStatement) pStmt).setHoldResultsOpenOverClose(true); - return pStmt; + return pStmt; + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } @Override diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ServerPreparedStatement.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ServerPreparedStatement.java index a2c405a70..7cdb924fa 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ServerPreparedStatement.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ServerPreparedStatement.java @@ -36,6 +36,7 @@ import com.mysql.cj.QueryBindings; import com.mysql.cj.QueryInfo; import com.mysql.cj.ServerPreparedQuery; +import com.mysql.cj.Session; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJException; import com.mysql.cj.exceptions.ExceptionFactory; @@ -50,6 +51,10 @@ import com.mysql.cj.jdbc.result.ResultSetMetaData; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.Message; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; /** * JDBC Interface for MySQL-4.1 and newer server-side PreparedStatements. @@ -110,7 +115,7 @@ protected ServerPreparedStatement(JdbcConnection conn, String sql, String db, in super(conn, db); checkNullOrEmptyQuery(sql); - String statementComment = this.session.getProtocol().getQueryComment(); + String statementComment = this.session.getQueryComment(); PreparedQuery prepQuery = (PreparedQuery) this.query; prepQuery.setOriginalSql(statementComment == null ? sql : "/* " + statementComment + " */ " + sql); prepQuery.setQueryInfo(new QueryInfo(prepQuery.getOriginalSql(), this.session, this.charEncoding)); @@ -438,6 +443,10 @@ public void realClose(boolean calledExplicitly, boolean closeOpenResults) throws this.isCached = false; } + // Make a copy of variables needed for telemetry work before they get nulled. + Session sessionLocalCopy = this.session; + String user = this.connection.getUser(); + super.realClose(calledExplicitly, closeOpenResults); ((ServerPreparedQuery) this.query).clearParameters(false); @@ -445,11 +454,28 @@ public void realClose(boolean calledExplicitly, boolean closeOpenResults) throws // Finally deallocate the prepared statement. if (calledExplicitly && !locallyScopedConn.isClosed()) { synchronized (locallyScopedConn.getConnectionMutex()) { - try { - ((NativeSession) locallyScopedConn.getSession()).getProtocol().sendCommand( - this.commandBuilder.buildComStmtClose(null, ((ServerPreparedQuery) this.query).getServerStatementId()), true, 0); - } catch (CJException sqlEx) { - exceptionDuringClose = sqlEx; + TelemetrySpan span = sessionLocalCopy.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_DEALLOCATE_PREPARED); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, user); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + try { + ((NativeSession) locallyScopedConn.getSession()).getProtocol().sendCommand( + this.commandBuilder.buildComStmtClose(null, ((ServerPreparedQuery) this.query).getServerStatementId()), true, 0); + } catch (CJException sqlEx) { + exceptionDuringClose = sqlEx; + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } } @@ -470,41 +496,59 @@ public void realClose(boolean calledExplicitly, boolean closeOpenResults) throws */ protected void rePrepare() { synchronized (checkClosed().getConnectionMutex()) { - this.invalidationException = null; - try { - serverPrepare(((PreparedQuery) this.query).getOriginalSql()); - } catch (Exception ex) { - this.invalidationException = ExceptionFactory.createException(ex.getMessage(), ex); - } + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.STMT_PREPARE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = getQueryInfo().getStatementKeyword(); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - if (this.invalidationException != null) { - this.invalid = true; + this.invalidationException = null; - this.query.closeQuery(); + try { + serverPrepare(((PreparedQuery) this.query).getOriginalSql()); + } catch (Exception ex) { + this.invalidationException = ExceptionFactory.createException(ex.getMessage(), ex); + } - if (this.results != null) { - try { - this.results.close(); - } catch (Exception ex) { + if (this.invalidationException != null) { + this.invalid = true; + + this.query.closeQuery(); + + if (this.results != null) { + try { + this.results.close(); + } catch (Exception ex) { + } } - } - if (this.generatedKeysResults != null) { - try { - this.generatedKeysResults.close(); - } catch (Exception ex) { + if (this.generatedKeysResults != null) { + try { + this.generatedKeysResults.close(); + } catch (Exception ex) { + } } - } - try { - closeAllOpenResults(); - } catch (Exception e) { - } + try { + closeAllOpenResults(); + } catch (Exception e) { + } - if (this.connection != null && !this.dontTrackOpenResources.getValue()) { - this.connection.unregisterStatement(this); + if (this.connection != null && !this.dontTrackOpenResources.getValue()) { + this.connection.unregisterStatement(this); + } } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/StatementImpl.java b/src/main/user-impl/java/com/mysql/cj/jdbc/StatementImpl.java index ef92b41c9..673daf247 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/StatementImpl.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/StatementImpl.java @@ -79,6 +79,10 @@ import com.mysql.cj.result.DefaultColumnDefinition; import com.mysql.cj.result.Field; import com.mysql.cj.result.Row; +import com.mysql.cj.telemetry.TelemetryAttribute; +import com.mysql.cj.telemetry.TelemetryScope; +import com.mysql.cj.telemetry.TelemetrySpan; +import com.mysql.cj.telemetry.TelemetrySpanName; import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.Util; @@ -290,20 +294,37 @@ public void cancel() throws SQLException { String user = hostInfo.getUser(); String password = hostInfo.getPassword(); newSession = new NativeSession(this.session.getHostInfo(), this.session.getPropertySet()); - newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() { - @Override - public void transactionCompleted() { - } + TelemetrySpan span = newSession.getTelemetryHandler().startSpan(TelemetrySpanName.CANCEL_QUERY); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, database); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_KILL); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_KILL + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, user); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - @Override - public void transactionBegun() { - } + newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() { + + @Override + public void transactionCompleted() { + } - }); - newSession.getProtocol().sendCommand(new NativeMessageBuilder(newSession.getServerSession().supportsQueryAttributes()) - .buildComQuery(newSession.getSharedSendPacket(), "KILL QUERY " + this.session.getThreadId()), false, 0); - setCancelStatus(CancelStatus.CANCELED_BY_USER); + @Override + public void transactionBegun() { + } + + }); + newSession.getProtocol().sendCommand(new NativeMessageBuilder(newSession.getServerSession().supportsQueryAttributes()) + .buildComQuery(newSession.getSharedSendPacket(), newSession, "KILL QUERY " + this.session.getThreadId()), false, 0); + setCancelStatus(CancelStatus.CANCELED_BY_USER); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } catch (IOException e) { throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); } finally { @@ -625,7 +646,23 @@ protected void setupStreamingTimeout(JdbcConnection con) throws SQLException { int netTimeoutForStreamingResults = this.session.getPropertySet().getIntegerProperty(PropertyKey.netTimeoutForStreamingResults).getValue(); if (createStreamingResultSet() && netTimeoutForStreamingResults > 0) { - executeSimpleNonQuery(con, "SET net_write_timeout=" + netTimeoutForStreamingResults); + TelemetrySpan span = this.session.getTelemetryHandler().startSpan(TelemetrySpanName.SET_VARIABLE, "net_write_timeout"); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, this.getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_SET); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_SET + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + executeSimpleNonQuery(con, "SET net_write_timeout=" + netTimeoutForStreamingResults); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } } } @@ -644,119 +681,150 @@ public boolean execute(String sql) throws SQLException { return executeInternal(sql, false); } + @Override + public boolean execute(String sql, int returnGeneratedKeys) throws SQLException { + return executeInternal(sql, returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS); + } + + @Override + public boolean execute(String sql, int[] generatedKeyIndices) throws SQLException { + return executeInternal(sql, generatedKeyIndices != null && generatedKeyIndices.length > 0); + } + + @Override + public boolean execute(String sql, String[] generatedKeyNames) throws SQLException { + return executeInternal(sql, generatedKeyNames != null && generatedKeyNames.length > 0); + } + private boolean executeInternal(String sql, boolean returnGeneratedKeys) throws SQLException { JdbcConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { checkClosed(); - checkNullOrEmptyQuery(sql); + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = QueryInfo.getStatementKeyword(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + checkNullOrEmptyQuery(sql); - resetCancelledState(); + resetCancelledState(); - implicitlyCloseAllOpenResults(); + implicitlyCloseAllOpenResults(); - clearWarnings(); + clearWarnings(); - if (sql.charAt(0) == '/') { - if (sql.startsWith(PING_MARKER)) { - doPingInstead(); + if (sql.charAt(0) == '/') { + if (sql.startsWith(PING_MARKER)) { + doPingInstead(); - return true; + return true; + } } - } - this.retrieveGeneratedKeys = returnGeneratedKeys; + this.retrieveGeneratedKeys = returnGeneratedKeys; - this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys - && QueryInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet()) == 'I' - && containsOnDuplicateKeyInString(sql); + this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys + && QueryInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet()) == 'I' + && containsOnDuplicateKeyInString(sql); - if (!QueryInfo.isReadOnlySafeQuery(sql, this.session.getServerSession().isNoBackslashEscapesSet()) && locallyScopedConn.isReadOnly()) { - throw SQLError.createSQLException(Messages.getString("Statement.27") + Messages.getString("Statement.28"), - MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); - } + if (!QueryInfo.isReadOnlySafeQuery(sql, this.session.getServerSession().isNoBackslashEscapesSet()) && locallyScopedConn.isReadOnly()) { + throw SQLError.createSQLException(Messages.getString("Statement.27") + Messages.getString("Statement.28"), + MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); + } - try { - setupStreamingTimeout(locallyScopedConn); + try { + setupStreamingTimeout(locallyScopedConn); - if (this.doEscapeProcessing) { - Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getSessionTimeZone(), - this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), - this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); - sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; - } + if (this.doEscapeProcessing) { + Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getSessionTimeZone(), + this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), + this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); + sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; + } - CachedResultSetMetaData cachedMetaData = null; + CachedResultSetMetaData cachedMetaData = null; - ResultSetInternalMethods rs = null; + ResultSetInternalMethods rs = null; - this.batchedGeneratedKeys = null; + this.batchedGeneratedKeys = null; - if (useServerFetch()) { - rs = createResultSetUsingServerFetch(sql); - } else { - CancelQueryTask timeoutTask = null; + if (useServerFetch()) { + rs = createResultSetUsingServerFetch(sql); + } else { + CancelQueryTask timeoutTask = null; - String oldDb = null; + String oldDb = null; - try { - timeoutTask = startQueryTimer(this, getTimeoutInMillis()); + try { + timeoutTask = startQueryTimer(this, getTimeoutInMillis()); - if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { - oldDb = locallyScopedConn.getDatabase(); - locallyScopedConn.setDatabase(getCurrentDatabase()); - } + if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { + oldDb = locallyScopedConn.getDatabase(); + locallyScopedConn.setDatabase(getCurrentDatabase()); + } - // Check if we have cached metadata for this query... - if (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { - cachedMetaData = locallyScopedConn.getCachedMetaData(sql); - } + // Check if we have cached metadata for this query... + if (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { + cachedMetaData = locallyScopedConn.getCachedMetaData(sql); + } - // Only apply max_rows to selects - locallyScopedConn.setSessionMaxRows(isResultSetProducingQuery(sql) ? this.maxRows : -1); + // Only apply max_rows to selects + locallyScopedConn.setSessionMaxRows(isResultSetProducingQuery(sql) ? this.maxRows : -1); - statementBegins(); + statementBegins(); - rs = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, null, createStreamingResultSet(), - getResultSetFactory(), cachedMetaData, false); + rs = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, null, createStreamingResultSet(), + getResultSetFactory(), cachedMetaData, false); - if (timeoutTask != null) { - stopQueryTimer(timeoutTask, true, true); - timeoutTask = null; - } + if (timeoutTask != null) { + stopQueryTimer(timeoutTask, true, true); + timeoutTask = null; + } - } catch (CJTimeoutException | OperationCancelledException e) { - throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); + } catch (CJTimeoutException | OperationCancelledException e) { + throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); - } finally { - stopQueryTimer(timeoutTask, false, false); + } finally { + stopQueryTimer(timeoutTask, false, false); - if (oldDb != null) { - locallyScopedConn.setDatabase(oldDb); + if (oldDb != null) { + locallyScopedConn.setDatabase(oldDb); + } } } - } - if (rs != null) { - this.lastInsertId = rs.getUpdateID(); + if (rs != null) { + this.lastInsertId = rs.getUpdateID(); - this.results = rs; + this.results = rs; - rs.setFirstCharOfQuery(QueryInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet())); + rs.setFirstCharOfQuery(QueryInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet())); - if (rs.hasRows()) { - if (cachedMetaData != null) { - locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); - } else if (this.session.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { - locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); + if (rs.hasRows()) { + if (cachedMetaData != null) { + locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); + } else if (this.session.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { + locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); + } } } - } - return rs != null && rs.hasRows(); + return rs != null && rs.hasRows(); + } finally { + this.query.getStatementExecuting().set(false); + } + } catch (Throwable t) { + span.setError(t); + throw t; } finally { - this.query.getStatementExecuting().set(false); + span.end(); } } } @@ -773,21 +841,6 @@ public void resetCancelledState() { } } - @Override - public boolean execute(String sql, int returnGeneratedKeys) throws SQLException { - return executeInternal(sql, returnGeneratedKeys == java.sql.Statement.RETURN_GENERATED_KEYS); - } - - @Override - public boolean execute(String sql, int[] generatedKeyIndices) throws SQLException { - return executeInternal(sql, generatedKeyIndices != null && generatedKeyIndices.length > 0); - } - - @Override - public boolean execute(String sql, String[] generatedKeyNames) throws SQLException { - return executeInternal(sql, generatedKeyNames != null && generatedKeyNames.length > 0); - } - @Override public int[] executeBatch() throws SQLException { return Util.truncateAndConvertToInt(executeBatchInternal()); @@ -797,118 +850,134 @@ protected long[] executeBatchInternal() throws SQLException { JdbcConnection locallyScopedConn = checkClosed(); synchronized (locallyScopedConn.getConnectionMutex()) { - if (locallyScopedConn.isReadOnly()) { - throw SQLError.createSQLException(Messages.getString("Statement.34") + Messages.getString("Statement.35"), - MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); - } + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE_BATCH); + try (TelemetryScope scope = span.makeCurrent()) { + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, TelemetryAttribute.OPERATION_BATCH); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, TelemetryAttribute.OPERATION_BATCH); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + if (locallyScopedConn.isReadOnly()) { + throw SQLError.createSQLException(Messages.getString("Statement.34") + Messages.getString("Statement.35"), + MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); + } - implicitlyCloseAllOpenResults(); + implicitlyCloseAllOpenResults(); - List batchedArgs = this.query.getBatchedArgs(); + List batchedArgs = this.query.getBatchedArgs(); - if (batchedArgs == null || batchedArgs.size() == 0) { - return new long[0]; - } + if (batchedArgs == null || batchedArgs.size() == 0) { + return new long[0]; + } - // we timeout the entire batch, not individual statements - long individualStatementTimeout = getTimeoutInMillis(); - setTimeoutInMillis(0); + // we timeout the entire batch, not individual statements + long individualStatementTimeout = getTimeoutInMillis(); + setTimeoutInMillis(0); - CancelQueryTask timeoutTask = null; + CancelQueryTask timeoutTask = null; - try { - resetCancelledState(); + try { + resetCancelledState(); - statementBegins(); + statementBegins(); - clearWarnings(); + clearWarnings(); - try { - this.retrieveGeneratedKeys = true; // The JDBC spec doesn't forbid this, but doesn't provide for it either...we do.. + try { + this.retrieveGeneratedKeys = true; // The JDBC spec doesn't forbid this, but doesn't provide for it either...we do.. - long[] updateCounts = null; + long[] updateCounts = null; - if (batchedArgs != null) { - int nbrCommands = batchedArgs.size(); + if (batchedArgs != null) { + int nbrCommands = batchedArgs.size(); - this.batchedGeneratedKeys = new ArrayList<>(batchedArgs.size()); + this.batchedGeneratedKeys = new ArrayList<>(batchedArgs.size()); - boolean multiQueriesEnabled = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.allowMultiQueries).getValue(); + boolean multiQueriesEnabled = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.allowMultiQueries).getValue(); - if (multiQueriesEnabled || this.rewriteBatchedStatements.getValue() && nbrCommands > 4) { - return executeBatchUsingMultiQueries(multiQueriesEnabled, nbrCommands, individualStatementTimeout); - } - - timeoutTask = startQueryTimer(this, individualStatementTimeout); + if (multiQueriesEnabled || this.rewriteBatchedStatements.getValue() && nbrCommands > 4) { + return executeBatchUsingMultiQueries(multiQueriesEnabled, nbrCommands, individualStatementTimeout); + } - updateCounts = new long[nbrCommands]; + timeoutTask = startQueryTimer(this, individualStatementTimeout); - for (int i = 0; i < nbrCommands; i++) { - updateCounts[i] = -3; - } + updateCounts = new long[nbrCommands]; - SQLException sqlEx = null; + for (int i = 0; i < nbrCommands; i++) { + updateCounts[i] = -3; + } - int commandIndex = 0; + SQLException sqlEx = null; - for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { - try { - String sql = (String) batchedArgs.get(commandIndex); - updateCounts[commandIndex] = executeUpdateInternal(sql, true, true); + int commandIndex = 0; - if (timeoutTask != null) { - // we need to check the cancel state on each iteration to generate timeout exception if needed - checkCancelTimeout(); - } + for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { + try { + String sql = (String) batchedArgs.get(commandIndex); + updateCounts[commandIndex] = executeUpdateInternal(sql, true, true); - // limit one generated key per OnDuplicateKey statement - getBatchedGeneratedKeys(this.results.getFirstCharOfQuery() == 'I' && containsOnDuplicateKeyInString(sql) ? 1 : 0); - } catch (SQLException ex) { - updateCounts[commandIndex] = EXECUTE_FAILED; + if (timeoutTask != null) { + // we need to check the cancel state on each iteration to generate timeout exception if needed + checkCancelTimeout(); + } - if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) - && !hasDeadlockOrTimeoutRolledBackTx(ex)) { - sqlEx = ex; - } else { - long[] newUpdateCounts = new long[commandIndex]; + // limit one generated key per OnDuplicateKey statement + getBatchedGeneratedKeys(this.results.getFirstCharOfQuery() == 'I' && containsOnDuplicateKeyInString(sql) ? 1 : 0); + } catch (SQLException ex) { + updateCounts[commandIndex] = EXECUTE_FAILED; - if (hasDeadlockOrTimeoutRolledBackTx(ex)) { - for (int i = 0; i < newUpdateCounts.length; i++) { - newUpdateCounts[i] = java.sql.Statement.EXECUTE_FAILED; - } + if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) + && !hasDeadlockOrTimeoutRolledBackTx(ex)) { + sqlEx = ex; } else { - System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex); - } + long[] newUpdateCounts = new long[commandIndex]; + + if (hasDeadlockOrTimeoutRolledBackTx(ex)) { + for (int i = 0; i < newUpdateCounts.length; i++) { + newUpdateCounts[i] = java.sql.Statement.EXECUTE_FAILED; + } + } else { + System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex); + } - sqlEx = ex; - break; - //throw SQLError.createBatchUpdateException(ex, newUpdateCounts, getExceptionInterceptor()); + sqlEx = ex; + break; + //throw SQLError.createBatchUpdateException(ex, newUpdateCounts, getExceptionInterceptor()); + } } } + + if (sqlEx != null) { + throw SQLError.createBatchUpdateException(sqlEx, updateCounts, getExceptionInterceptor()); + } } - if (sqlEx != null) { - throw SQLError.createBatchUpdateException(sqlEx, updateCounts, getExceptionInterceptor()); + if (timeoutTask != null) { + stopQueryTimer(timeoutTask, true, true); + timeoutTask = null; } - } - if (timeoutTask != null) { - stopQueryTimer(timeoutTask, true, true); - timeoutTask = null; + return updateCounts != null ? updateCounts : new long[0]; + } finally { + this.query.getStatementExecuting().set(false); } - - return updateCounts != null ? updateCounts : new long[0]; } finally { - this.query.getStatementExecuting().set(false); - } - } finally { - stopQueryTimer(timeoutTask, false, false); - resetCancelledState(); + stopQueryTimer(timeoutTask, false, false); + resetCancelledState(); - setTimeoutInMillis(individualStatementTimeout); + setTimeoutInMillis(individualStatementTimeout); - clearBatch(); + clearBatch(); + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } } } @@ -1102,97 +1171,115 @@ protected SQLException handleExceptionForBatch(int endOfBatchIndex, int numValue @Override public java.sql.ResultSet executeQuery(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - JdbcConnection locallyScopedConn = this.connection; + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = QueryInfo.getStatementKeyword(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); + + JdbcConnection locallyScopedConn = this.connection; - this.retrieveGeneratedKeys = false; + this.retrieveGeneratedKeys = false; - checkNullOrEmptyQuery(sql); + checkNullOrEmptyQuery(sql); - resetCancelledState(); + resetCancelledState(); - implicitlyCloseAllOpenResults(); + implicitlyCloseAllOpenResults(); - clearWarnings(); + clearWarnings(); - if (sql.charAt(0) == '/') { - if (sql.startsWith(PING_MARKER)) { - doPingInstead(); + if (sql.charAt(0) == '/') { + if (sql.startsWith(PING_MARKER)) { + doPingInstead(); - return this.results; + return this.results; + } } - } - setupStreamingTimeout(locallyScopedConn); + setupStreamingTimeout(locallyScopedConn); - if (this.doEscapeProcessing) { - Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getSessionTimeZone(), - this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), - getExceptionInterceptor()); - sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; - } + if (this.doEscapeProcessing) { + Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getSessionTimeZone(), + this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), + this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); + sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; + } - if (!isResultSetProducingQuery(sql)) { - throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); - } + if (!isResultSetProducingQuery(sql)) { + throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, + getExceptionInterceptor()); + } - CachedResultSetMetaData cachedMetaData = null; + CachedResultSetMetaData cachedMetaData = null; - if (useServerFetch()) { - this.results = createResultSetUsingServerFetch(sql); + if (useServerFetch()) { + this.results = createResultSetUsingServerFetch(sql); - return this.results; - } + return this.results; + } - CancelQueryTask timeoutTask = null; + CancelQueryTask timeoutTask = null; - String oldDb = null; + String oldDb = null; - try { - timeoutTask = startQueryTimer(this, getTimeoutInMillis()); + try { + timeoutTask = startQueryTimer(this, getTimeoutInMillis()); - if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { - oldDb = locallyScopedConn.getDatabase(); - locallyScopedConn.setDatabase(getCurrentDatabase()); - } + if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { + oldDb = locallyScopedConn.getDatabase(); + locallyScopedConn.setDatabase(getCurrentDatabase()); + } - // - // Check if we have cached metadata for this query... - // - if (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { - cachedMetaData = locallyScopedConn.getCachedMetaData(sql); - } + // + // Check if we have cached metadata for this query... + // + if (locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { + cachedMetaData = locallyScopedConn.getCachedMetaData(sql); + } - locallyScopedConn.setSessionMaxRows(this.maxRows); + locallyScopedConn.setSessionMaxRows(this.maxRows); - statementBegins(); + statementBegins(); - this.results = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, null, createStreamingResultSet(), - getResultSetFactory(), cachedMetaData, false); + this.results = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, null, createStreamingResultSet(), + getResultSetFactory(), cachedMetaData, false); - if (timeoutTask != null) { - stopQueryTimer(timeoutTask, true, true); - timeoutTask = null; - } + if (timeoutTask != null) { + stopQueryTimer(timeoutTask, true, true); + timeoutTask = null; + } - } catch (CJTimeoutException | OperationCancelledException e) { - throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); + } catch (CJTimeoutException | OperationCancelledException e) { + throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); - } finally { - this.query.getStatementExecuting().set(false); + } finally { + this.query.getStatementExecuting().set(false); - stopQueryTimer(timeoutTask, false, false); + stopQueryTimer(timeoutTask, false, false); - if (oldDb != null) { - locallyScopedConn.setDatabase(oldDb); + if (oldDb != null) { + locallyScopedConn.setDatabase(oldDb); + } } - } - this.lastInsertId = this.results.getUpdateID(); + this.lastInsertId = this.results.getUpdateID(); - if (cachedMetaData != null) { - locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); - } else if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { - locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); + if (cachedMetaData != null) { + locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results); + } else if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) { + locallyScopedConn.initializeResultsMetadataFromCache(sql, null /* will be created */, this.results); + } + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); } return this.results; @@ -1244,112 +1331,129 @@ public int executeUpdate(String sql) throws SQLException { return Util.truncateAndConvertToInt(executeLargeUpdate(sql)); } + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + return Util.truncateAndConvertToInt(executeLargeUpdate(sql, autoGeneratedKeys)); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + return Util.truncateAndConvertToInt(executeLargeUpdate(sql, columnIndexes)); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + return Util.truncateAndConvertToInt(executeLargeUpdate(sql, columnNames)); + } + protected long executeUpdateInternal(String sql, boolean isBatch, boolean returnGeneratedKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - JdbcConnection locallyScopedConn = this.connection; + TelemetrySpan span = getSession().getTelemetryHandler().startSpan(TelemetrySpanName.STMT_EXECUTE); + try (TelemetryScope scope = span.makeCurrent()) { + String dbOperation = QueryInfo.getStatementKeyword(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + span.setAttribute(TelemetryAttribute.DB_NAME, getCurrentDatabase()); + span.setAttribute(TelemetryAttribute.DB_OPERATION, dbOperation); + span.setAttribute(TelemetryAttribute.DB_STATEMENT, dbOperation + TelemetryAttribute.STATEMENT_SUFFIX); + span.setAttribute(TelemetryAttribute.DB_SYSTEM, TelemetryAttribute.DB_SYSTEM_DEFAULT); + span.setAttribute(TelemetryAttribute.DB_USER, this.connection.getUser()); + span.setAttribute(TelemetryAttribute.THREAD_ID, Thread.currentThread().getId()); + span.setAttribute(TelemetryAttribute.THREAD_NAME, Thread.currentThread().getName()); - checkNullOrEmptyQuery(sql); + JdbcConnection locallyScopedConn = this.connection; - resetCancelledState(); + checkNullOrEmptyQuery(sql); - clearWarnings(); + resetCancelledState(); - char firstStatementChar = QueryInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet()); - if (!isNonResultSetProducingQuery(sql)) { - throw SQLError.createSQLException(Messages.getString("Statement.46"), "01S03", getExceptionInterceptor()); - } + clearWarnings(); - this.retrieveGeneratedKeys = returnGeneratedKeys; + char firstStatementChar = QueryInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + if (!isNonResultSetProducingQuery(sql)) { + throw SQLError.createSQLException(Messages.getString("Statement.46"), "01S03", getExceptionInterceptor()); + } - this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys && firstStatementChar == 'I' && containsOnDuplicateKeyInString(sql); + this.retrieveGeneratedKeys = returnGeneratedKeys; - ResultSetInternalMethods rs = null; + this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys && firstStatementChar == 'I' && containsOnDuplicateKeyInString(sql); - if (this.doEscapeProcessing) { - Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getSessionTimeZone(), - this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), - getExceptionInterceptor()); - sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; - } + ResultSetInternalMethods rs = null; - if (locallyScopedConn.isReadOnly(false)) { - throw SQLError.createSQLException(Messages.getString("Statement.42") + Messages.getString("Statement.43"), - MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); - } + if (this.doEscapeProcessing) { + Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getSessionTimeZone(), + this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), + this.session.getServerSession().isServerTruncatesFracSecs(), getExceptionInterceptor()); + sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; + } - implicitlyCloseAllOpenResults(); + if (locallyScopedConn.isReadOnly(false)) { + throw SQLError.createSQLException(Messages.getString("Statement.42") + Messages.getString("Statement.43"), + MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); + } - // The checking and changing of databases must happen in sequence, so synchronize on the same mutex that _conn is using + implicitlyCloseAllOpenResults(); - CancelQueryTask timeoutTask = null; + // The checking and changing of databases must happen in sequence, so synchronize on the same mutex that _conn is using - String oldDb = null; + CancelQueryTask timeoutTask = null; - try { - timeoutTask = startQueryTimer(this, getTimeoutInMillis()); + String oldDb = null; - if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { - oldDb = locallyScopedConn.getDatabase(); - locallyScopedConn.setDatabase(getCurrentDatabase()); - } + try { + timeoutTask = startQueryTimer(this, getTimeoutInMillis()); - // - // Only apply max_rows to selects - // - locallyScopedConn.setSessionMaxRows(-1); + if (!locallyScopedConn.getDatabase().equals(getCurrentDatabase())) { + oldDb = locallyScopedConn.getDatabase(); + locallyScopedConn.setDatabase(getCurrentDatabase()); + } - statementBegins(); + // + // Only apply max_rows to selects + // + locallyScopedConn.setSessionMaxRows(-1); - // null database: force read of field info on DML - rs = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, -1, null, false, getResultSetFactory(), null, isBatch); + statementBegins(); - if (timeoutTask != null) { - stopQueryTimer(timeoutTask, true, true); - timeoutTask = null; - } + // null database: force read of field info on DML + rs = ((NativeSession) locallyScopedConn.getSession()).execSQL(this, sql, -1, null, false, getResultSetFactory(), null, isBatch); - } catch (CJTimeoutException | OperationCancelledException e) { - throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); + if (timeoutTask != null) { + stopQueryTimer(timeoutTask, true, true); + timeoutTask = null; + } - } finally { - stopQueryTimer(timeoutTask, false, false); + } catch (CJTimeoutException | OperationCancelledException e) { + throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); - if (oldDb != null) { - locallyScopedConn.setDatabase(oldDb); - } + } finally { + stopQueryTimer(timeoutTask, false, false); - if (!isBatch) { - this.query.getStatementExecuting().set(false); + if (oldDb != null) { + locallyScopedConn.setDatabase(oldDb); + } + + if (!isBatch) { + this.query.getStatementExecuting().set(false); + } } - } - this.results = rs; + this.results = rs; - rs.setFirstCharOfQuery(firstStatementChar); + rs.setFirstCharOfQuery(firstStatementChar); - this.updateCount = rs.getUpdateCount(); + this.updateCount = rs.getUpdateCount(); - this.lastInsertId = rs.getUpdateID(); + this.lastInsertId = rs.getUpdateID(); + } catch (Throwable t) { + span.setError(t); + throw t; + } finally { + span.end(); + } return this.updateCount; } } - @Override - public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { - return Util.truncateAndConvertToInt(executeLargeUpdate(sql, autoGeneratedKeys)); - } - - @Override - public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { - return Util.truncateAndConvertToInt(executeLargeUpdate(sql, columnIndexes)); - } - - @Override - public int executeUpdate(String sql, String[] columnNames) throws SQLException { - return Util.truncateAndConvertToInt(executeLargeUpdate(sql, columnNames)); - } - @Override public java.sql.Connection getConnection() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { diff --git a/src/test/java/testsuite/regression/ConnectionRegressionTest.java b/src/test/java/testsuite/regression/ConnectionRegressionTest.java index 698037507..6809c1844 100644 --- a/src/test/java/testsuite/regression/ConnectionRegressionTest.java +++ b/src/test/java/testsuite/regression/ConnectionRegressionTest.java @@ -10821,7 +10821,6 @@ public void testBug70677() throws Exception { } /** - * @SuppressWarnings("javadoc") * Test fix for Bug#98445 (30832513), Connection option clientInfoProvider=ClientInfoProviderSP causes NPE. * * @throws Exception diff --git a/src/test/java/testsuite/simple/ConnectionTest.java b/src/test/java/testsuite/simple/ConnectionTest.java index 50c0d1b77..926cd40bc 100644 --- a/src/test/java/testsuite/simple/ConnectionTest.java +++ b/src/test/java/testsuite/simple/ConnectionTest.java @@ -1139,10 +1139,10 @@ public void testUseLocalSessionStateRollback() throws Exception { // space is important here, we don't want to count occurrences in stack traces while (rollbackPos != -1) { - rollbackPos = searchIn.indexOf(" rollback", rollbackPos); + rollbackPos = searchIn.indexOf(" ROLLBACK", rollbackPos); if (rollbackPos != -1) { - rollbackPos += "rollback".length(); + rollbackPos += "ROLLBACK".length(); rollbackCount++; } } @@ -1154,10 +1154,10 @@ public void testUseLocalSessionStateRollback() throws Exception { // space is important here, we don't want to count "autocommit" nor occurrences in stack traces while (commitPos != -1) { - commitPos = searchIn.indexOf(" commit", commitPos); + commitPos = searchIn.indexOf(" COMMIT", commitPos); if (commitPos != -1) { - commitPos += " commit".length(); + commitPos += " COMMIT".length(); commitCount++; } } @@ -1449,16 +1449,16 @@ public void testReadOnly56() throws Exception { for (int i = 0; i < 2; i++) { BufferingLogger.startLoggingToBuffer(); notLocalState.setReadOnly(true); - assertTrue(BufferingLogger.getBuffer().toString().indexOf("set session transaction read only") != -1); - notLocalState.createStatement().execute("set session transaction read write"); + assertTrue(BufferingLogger.getBuffer().toString().indexOf("SET SESSION TRANSACTION READ ONLY") != -1); + notLocalState.createStatement().execute("SET SESSION TRANSACTION READ WRITE"); assertFalse(notLocalState.isReadOnly()); } for (int i = 0; i < 2; i++) { BufferingLogger.startLoggingToBuffer(); notLocalState.setReadOnly(false); - assertTrue(BufferingLogger.getBuffer().toString().indexOf("set session transaction read write") != -1); - notLocalState.createStatement().execute("set session transaction read only"); + assertTrue(BufferingLogger.getBuffer().toString().indexOf("SET SESSION TRANSACTION READ WRITE") != -1); + notLocalState.createStatement().execute("SET SESSION TRANSACTION READ ONLY"); assertTrue(notLocalState.isReadOnly()); } @@ -1471,9 +1471,9 @@ public void testReadOnly56() throws Exception { BufferingLogger.startLoggingToBuffer(); localState.setReadOnly(true); if (i == 0) { - assertTrue(BufferingLogger.getBuffer().toString().indexOf("set session transaction read only") != -1); + assertTrue(BufferingLogger.getBuffer().toString().indexOf("SET SESSION TRANSACTION READ ONLY") != -1); } else { - assertTrue(BufferingLogger.getBuffer().toString().indexOf("set session transaction read only") == -1); + assertTrue(BufferingLogger.getBuffer().toString().indexOf("SET SESSION TRANSACTION READ ONLY") == -1); } BufferingLogger.startLoggingToBuffer(); localState.isReadOnly(); @@ -1487,7 +1487,7 @@ public void testReadOnly56() throws Exception { for (int i = 0; i < 2; i++) { BufferingLogger.startLoggingToBuffer(); noOptimization.setReadOnly(true); - assertTrue(BufferingLogger.getBuffer().toString().indexOf("set session transaction read only") == -1); + assertTrue(BufferingLogger.getBuffer().toString().indexOf("SET SESSION TRANSACTION READ ONLY") == -1); BufferingLogger.startLoggingToBuffer(); noOptimization.isReadOnly(); assertTrue(BufferingLogger.getBuffer().toString().indexOf("select @@session." + s) == -1);