diff --git a/CHANGES b/CHANGES index e53c1a2bb..45cc8c2a1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,99 @@ # Changelog # https://dev.mysql.com/doc/relnotes/connector-j/8.0/en/ +Version 8.0.28 + + - Fix for Bug#99260 (31189960), statement.setQueryTimeout,creates a database connection and does not close. + + - Fix for Bug#103324 (32770013), X DevAPI Collection.replaceOne() missing matching _id check. + + - Fix for Bug#105197 (33461744), Statement.executeQuery() may return non-navigable ResultSet. + + - Fix for Bug#105323 (33507321), README.md contains broken links. + + - Fix for Bug#96900 (30355150), STATEMENT.CANCEL()CREATE A DATABASE CONNECTION BUT DOES NOT CLOSE THE CONNECTION. + + - Fix for Bug#104067 (33054827), No reset autoCommit after unknown issue occurs. + Thanks to Tingyu Wei for his contribution. + + - Fix for Bug#85223 (25656020), MYSQLSQLXML SETSTRING CRASH. + + - Fix for Bug#84365 (33425867), INSERT..VALUE with VALUES function lead to a StringIndexOutOfBoundsException. + + - Fix for Bug#105211 (33468860), class java.time.LocalDate cannot be cast to class java.sql.Date. + + - Fix for Bug#101389 (32089018), GETWARNINGS SHOULD CHECK WARNING COUNT BEFORE SENDING SHOW. + + - Fix for Bug#33488091, Remove all references to xdevapi.useAsyncProtocol from properties and code. + + - WL#14805, Remove support for TLS 1.0 and 1.1. + + - WL#14650, Support for MFA (multi factor authentication) authentication. + +Version 8.0.27 + + - Fix for Bug#103612 (32902019), Incorrectly identified WITH...SELECT as unsafe for read-only connections. + + - Fix for Bug#71929 (18346501), Prefixing query with double comments cancels query DML validation. + + - Fix for Bug#23204652, CURSOR POSITIONING API'S DOESNOT CHECK THE VALIDITY OF RESULTSET. + + - Fix for Bug#28725534, MULTI HOST CONNECTION WOULD BLOCK IN CONNECTION POOLING. + + - Fix for Bug#95139 (29807572), CACHESERVERCONFIGURATION APPEARS TO THWART CHARSET DETECTION. + + - Fix for Bug#104641 (33237255), DatabaseMetaData.getImportedKeys can return duplicated foreign keys. + + - Fix for Bug#33185116, Have method ResultSet.getBoolean() supporting conversion of 'T' and 'F' in a VARCHAR to True/False (boolean). + + - Fix for Bug#31117686, PROTOCOL ALLOWLIST NOT COMPATIBLE WITH IBM JAVA. + + - Fix for Bug#104559 (33232419), ResultSet.getObject(i, java.util.Date.class) throws NPE when the value is null. + + - WL#14707, Support OCI IAM authentication. + + - WL#14660, Testsuite with support for single MySQL server instance. + + - Fix for Bug#103878 (32954449), CONNECTOR/J 8 : QUERY WITH 'SHOW XXX' WILL GET EXCEPTION WHEN USE CURSOR. + + - Fix for Bug#103796 (32922715), CONNECTOR/J 8 STMT SETQUERYTIMEOUT CAN NOT WORK. + Thanks to Hong Wang for his contribution. + + - Fix for Bug#104170 (33064455), CONTRIBUTION: CLIENTPREPAREDSTMT: LEAVE CALENDAR UNTOUCHED. + Thanks to Björn Michael for his contribution. + + - Fix for Bug#95564 (29894324), createDatabaseIfNotExist is not working for databases with hyphen in name. + Thanks to Lukasz Sanek for his contribution. + +Version 8.0.26 + + - Fix for Bug#32954396, EXECUTEQUERY HANGS WITH USECURSORFETCH=TRUE & SETFETCHSIZE. + + - Fix for Bug#102372 (32459408), v8.0.23 unusable in OSGi. + + - Fix for Bug#25554464, CONNECT FAILS WITH NPE WHEN THE SERVER STARTED WITH CUSTOM COLLATION. + + - Fix for Bug#100606 (31818423), UNECESARY CALL TO "SET NAMES 'UTF8' COLLATE 'UTF8_GENERAL_CI'". + Thanks to Marc Fletcher for his contribution. + + - Fix for Bug#102404 (32435618), CONTRIBUTION: ADD TRACK SESSION STATE CHANGE. + Thanks to William Lee for his contribution. + + - Fix for Bug#95280 (29757140), DATABASEMETADATA.GETIMPORTEDKEYS RETURNS DOUBLE THE ROWS. + Thanks to Miron Balcerzak for his contribution. + + - Fix for Bug#97269 (30438500), POSSIBLE BUG IN COM.MYSQL.CJ.XDEVAPI.STREAMINGDOCRESULTBUILDER. + + - Fix for Bug#103303 (32766143), JAVA.LANG.CLASSCASTEXCEPTION WHEN INSERTING BLOB WITH SERVER PREPARED STATEMENT. + + - WL#14205, Support query attributes. + + - WL#14411, Support for authentication_kerberos_client authentication plugin. + + - WL#14559, Deprecate TLS 1.0 and 1.1. + + - WL#14391, Migrate QA tests to main repo. + Version 8.0.25 - This release contains no functional changes and is published to align the version number with the MySQL Server 8.0.25 release. diff --git a/LICENSE b/LICENSE index e66b83f4a..83e60a354 100644 --- a/LICENSE +++ b/LICENSE @@ -10,7 +10,7 @@ Introduction third-party software which may be included in this distribution of MySQL Connector/J 8.0. - Last updated: April 2021 + Last updated: September 2021 Licensing Information @@ -428,6 +428,7 @@ The Universal FOSS Exception, Version 1.0 Software with Other FOSS, and the constants, function signatures, data structures and other invocation methods used to run or interact with each of them (as to each, such software's "Interfaces"): + i. The Software's Interfaces may, to the extent permitted by the license of the Other FOSS, be copied into, used and distributed in the Other FOSS in order to enable interoperability, without @@ -437,6 +438,7 @@ The Universal FOSS Exception, Version 1.0 including without limitation as used in the Other FOSS (which upon any such use also then contains a portion of the Software under the Software License). + ii. The Other FOSS's Interfaces may, to the extent permitted by the license of the Other FOSS, be copied into, used and distributed in the Software in order to enable interoperability, without requiring @@ -444,6 +446,7 @@ The Universal FOSS Exception, Version 1.0 License or otherwise altering their original terms, if this does not require any portion of the Software other than such Interfaces to be licensed under the terms other than the Software License. + iii. If only Interfaces and no other code is copied between the Software and the Other FOSS in either direction, the use and/or distribution of the Software with the Other FOSS shall not be @@ -479,6 +482,7 @@ c3p0 JDBC Library The MySQL Connector/J implements interfaces that are included in c3p0, although no part of c3p0 is included or distributed with MySQL. + Copyright (C) 2019 Machinery For Change, Inc. * This library is free software; you can redistribute it and/or modify @@ -1121,6 +1125,65 @@ Apache License Version 2.0, January 2004 + ====================================================================== + ====================================================================== + +Oracle OCI SDK for Java + + Oracle OCI SDK for Java + +Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. +This software is dual-licensed to you under the Universal Permissive License +(UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl +or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. +You may choose either license. +____________________________ +The Universal Permissive License (UPL), Version 1.0 +Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or +data (collectively the "Software"), free of charge and under any and all +copyright rights in the Software, and any and all patent rights owned or +freely licensable by each licensor hereunder covering either (i) the +unmodified Software as contributed to or provided by such licensor, or (ii) +the Larger Works (as defined below), to deal in both +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at a +minimum a reference to the UPL must be included in all copies or substantial +portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The Apache Software License, Version 2.0 +Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); You may not +use this product except in compliance with the License. You may obtain a +copy of the License at http://www.apache.org/licenses/LICENSE-2.0. A copy of +the license is also reproduced below. Unless required by applicable law or +agreed to in writing, software distributed under the License is distributed +on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +express or implied. See the License for the specific language governing +permissions and limitations under the License. + +Apache License Version 2.0, January 2004 + +Oracle's use of OCI SDK for Java in MySQL Community Edition is solely under +the UPL + ====================================================================== ====================================================================== @@ -1161,6 +1224,7 @@ OR OTHER DEALINGS IN THE SOFTWARE. Unicode Data Files Unicode Data Files + COPYRIGHT AND PERMISSION NOTICE Copyright (c) 1991-2014 Unicode, Inc. All rights reserved. Distributed under @@ -1950,6 +2014,7 @@ Written Offer for Source Code request to the address listed below or by sending an email to Oracle using the following link: http://www.oracle.com/goto/opensourcecode/request. + Oracle America, Inc. Attn: Senior Vice President Development and Engineering Legal @@ -1957,20 +2022,30 @@ Written Offer for Source Code Redwood Shores, CA 94065 Your request should include: + * The name of the binary for which you are requesting the source code + * The name and version number of the Oracle product containing the binary + * The date you received the Oracle product + * Your name + * Your company name (if applicable) + * Your return mailing address and email, and + * A telephone number in the event we need to reach you. + We may charge you a fee to cover the cost of physical media and processing. Your request must be sent + a. within three (3) years of the date you received the Oracle product that included the binary that is the subject of your request, or + b. in the case of code licensed under the GPL v3 for as long as Oracle offers spare parts or customer support for that product model. diff --git a/README b/README index 952630f6b..0114ce35c 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Copyright (c) 2000, 2021, Oracle and/or its affiliates. +Copyright (c) 2000, 2022, Oracle and/or its affiliates. This is a release of MySQL Connector/J, a JDBC Type 4 driver for MySQL that also supports the new X DevAPI. diff --git a/README.md b/README.md index d41ac09e8..46a3f00b7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MySQL Connector/J -[![GitHub top language](https://img.shields.io/github/languages/top/mysql/mysql-connector-j.svg?color=5382a1)](https://github.com/mysql/mysql-connector-j/tree/release/8.0/src) [![License: GPLv2 with FOSS exception](https://img.shields.io/badge/license-GPLv2_with_FOSS_exception-c30014.svg)](LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/mysql/mysql-connector-java.svg)](https://search.maven.org/artifact/mysql/mysql-connector-java/8.0.25/jar) +[![GitHub top language](https://img.shields.io/github/languages/top/mysql/mysql-connector-j.svg?color=5382a1)](https://github.com/mysql/mysql-connector-j/tree/release/8.0/src) [![License: GPLv2 with FOSS exception](https://img.shields.io/badge/license-GPLv2_with_FOSS_exception-c30014.svg)](LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/mysql/mysql-connector-java.svg)](https://search.maven.org/artifact/mysql/mysql-connector-java/8.0.28/jar) MySQL provides connectivity for client applications developed in the Java programming language with MySQL Connector/J, a driver that implements the [Java Database Connectivity (JDBC) API](https://www.oracle.com/technetwork/java/javase/jdbc/) and also [MySQL X DevAPI](https://dev.mysql.com/doc/x-devapi-userguide/en/). @@ -30,7 +30,7 @@ Alternatively, Connector/J can be obtained automatically via [Maven's dependency mysql mysql-connector-java - 8.0.25 + 8.0.28 ``` @@ -53,8 +53,8 @@ There are a few ways to contribute to the Connector/J code. Please refer to the * [MySQL Connector/J, JDBC and Java forum](https://forums.mysql.com/list.php?39). * [`#connectors` channel in MySQL Community Slack](https://mysqlcommunity.slack.com/messages/connectors). ([Sign-up](https://lefred.be/mysql-community-on-slack/) required if you do not have an Oracle account.) * [@MySQL on Twitter](https://twitter.com/MySQL). -* [MySQL and Java Mailing Lists](https://lists.mysql.com/java). -* [InsideMySQL.com Connectors Blog](https://insidemysql.com/category/mysql-development/connectors/). +* [MySQL Blog](https://blogs.oracle.com/mysql/). +* [MySQL Connectors Blog archive](https://dev.mysql.com/blog-archive/?cat=Connectors%20%2F%20Languages). * [MySQL Bugs Database](https://bugs.mysql.com/). For more information about this and other MySQL products, please visit [MySQL Contact & Questions](https://www.mysql.com/about/contact/). diff --git a/build.xml b/build.xml index d1b1ab953..c65df5250 100644 --- a/build.xml +++ b/build.xml @@ -1,6 +1,6 @@ @@ -1189,7 +1189,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o overview="src/main/doc/mysqlx-overview.html" windowtitle="MySQL Connector/J X DevAPI Reference" header="<b>MySQL Connector/J X DevAPI Reference<br>v1</b>" - bottom="<center>Copyright &copy; 2016, 2021, Oracle and/or its affiliates.</center>" + bottom="<center>Copyright &copy; 2016, 2022, Oracle and/or its affiliates.</center>" linkoffline="https://docs.oracle.com/javase/8/docs/api ${com.mysql.cj.docs.jdk8pkg}"> @@ -1236,11 +1236,30 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o + + + + + + + + + + + + + + + + depends="-testsuite-jvm-check, compile-testsuite, -init-custom-xslt"> @@ -1283,6 +1302,9 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o + + + @@ -1295,7 +1317,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o - + @@ -1309,7 +1331,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o - + @@ -1324,7 +1346,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o - + @@ -1334,7 +1356,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o - + @@ -1353,9 +1375,7 @@ See also com.mysql.cj.conf.PropertyDefinitions.SYSP_* variables for other test o - + diff --git a/src/build/java/instrumentation/TranslateExceptions.java b/src/build/java/instrumentation/TranslateExceptions.java index 6df6eac54..cc94aab8d 100644 --- a/src/build/java/instrumentation/TranslateExceptions.java +++ b/src/build/java/instrumentation/TranslateExceptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -292,7 +292,7 @@ public static void main(String[] args) throws Exception { /* * java.sql.PreparedStatement extends java.sql.Statement (java.sql.Statement extends java.sql.Wrapper) */ - // com.mysql.cj.jdbc.PreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements java.sql.PreparedStatement + // com.mysql.cj.jdbc.ClientPreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements java.sql.PreparedStatement clazz = pool.get(ClientPreparedStatement.class.getName()); instrumentJdbcMethods(clazz, java.sql.PreparedStatement.class, false, EXCEPTION_INTERCEPTOR_GETTER); instrumentJdbcMethods(clazz, JdbcStatement.class, true, EXCEPTION_INTERCEPTOR_GETTER); @@ -318,7 +318,6 @@ public static void main(String[] args) throws Exception { catchRuntimeException(clazz, clazz.getDeclaredMethod("getParameterBindings", new CtClass[] {}), EXCEPTION_INTERCEPTOR_GETTER); catchRuntimeException(clazz, clazz.getDeclaredMethod("initializeFromParseInfo", new CtClass[] {}), EXCEPTION_INTERCEPTOR_GETTER); catchRuntimeException(clazz, clazz.getDeclaredMethod("isNull", new CtClass[] { CtClass.intType }), EXCEPTION_INTERCEPTOR_GETTER); - catchRuntimeException(clazz, clazz.getDeclaredMethod("isSelectQuery", new CtClass[] {}), EXCEPTION_INTERCEPTOR_GETTER); catchRuntimeException(clazz, clazz.getDeclaredMethod("prepareBatchedInsertSQL", new CtClass[] { ctJdbcConnection, CtClass.intType }), EXCEPTION_INTERCEPTOR_GETTER); catchRuntimeException(clazz, @@ -328,7 +327,7 @@ public static void main(String[] args) throws Exception { clazz.writeFile(args[0]); /* - * com.mysql.cj.jdbc.ServerPreparedStatement extends PreparedStatement + * com.mysql.cj.jdbc.ServerPreparedStatement extends ClientPreparedStatement */ clazz = pool.get(ServerPreparedStatement.class.getName()); instrumentJdbcMethods(clazz, java.sql.PreparedStatement.class, false, EXCEPTION_INTERCEPTOR_GETTER); diff --git a/src/build/misc/debian.in/compat b/src/build/misc/debian.in/compat index ec635144f..b4de39476 100644 --- a/src/build/misc/debian.in/compat +++ b/src/build/misc/debian.in/compat @@ -1 +1 @@ -9 +11 diff --git a/src/build/misc/pom.xml b/src/build/misc/pom.xml index 0b8b2cf97..6b756545c 100644 --- a/src/build/misc/pom.xml +++ b/src/build/misc/pom.xml @@ -1,6 +1,6 @@ Client\n"); + packetDump.append("\nPacket payload:\n\n"); + packetDump.append(this.lastHeaderPayload); + packetDump.append(PacketPayloadImpl); + + if (bytesToDump == MAX_PACKET_DUMP_LENGTH) { + packetDump.append("\nNote: Packet of " + packetLength + " bytes truncated to " + MAX_PACKET_DUMP_LENGTH + " bytes.\n"); + } + + if ((this.packetDebugBuffer.size() + 1) > this.packetDebugBufferSize.getValue()) { + this.packetDebugBuffer.removeFirst(); + } + + this.packetDebugBuffer.addLast(packetDump); + + return buf; + } + @Override public byte getMessageSequence() { return this.packetReader.getMessageSequence(); diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/MultiPacketReader.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/MultiPacketReader.java index d7ea9440a..c10ed06b7 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/MultiPacketReader.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/MultiPacketReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -54,6 +54,11 @@ public NativePacketHeader readHeader() throws IOException { return this.packetReader.readHeader(); } + @Override + public NativePacketHeader probeHeader() throws IOException { + return this.packetReader.probeHeader(); + } + @Override public NativePacketPayload readMessage(Optional reuse, NativePacketHeader header) throws IOException { @@ -93,6 +98,45 @@ public NativePacketPayload readMessage(Optional reuse, Nati return buf; } + @Override + public NativePacketPayload probeMessage(Optional reuse, NativePacketHeader header) throws IOException { + + int packetLength = header.getMessageSize(); + NativePacketPayload buf = this.packetReader.probeMessage(reuse, header); + + if (packetLength == NativeConstants.MAX_PACKET_SIZE) { // it's a multi-packet + + buf.setPosition(NativeConstants.MAX_PACKET_SIZE); + + NativePacketPayload multiPacket = null; + int multiPacketLength = -1; + byte multiPacketSeq = getMessageSequence(); + + do { + NativePacketHeader hdr = readHeader(); + multiPacketLength = hdr.getMessageSize(); + + if (multiPacket == null) { + multiPacket = new NativePacketPayload(multiPacketLength); + } + + multiPacketSeq++; + if (multiPacketSeq != hdr.getMessageSequence()) { + throw new IOException(Messages.getString("PacketReader.10")); + } + + this.packetReader.probeMessage(Optional.of(multiPacket), hdr); + + buf.writeBytes(StringLengthDataType.STRING_FIXED, multiPacket.getByteBuffer(), 0, multiPacketLength); + + } while (multiPacketLength == NativeConstants.MAX_PACKET_SIZE); + + buf.setPosition(0); + } + + return buf; + } + @Override public byte getMessageSequence() { return this.packetReader.getMessageSequence(); diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeAuthenticationProvider.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeAuthenticationProvider.java index af48c520a..e1b591822 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeAuthenticationProvider.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeAuthenticationProvider.java @@ -55,7 +55,9 @@ import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType; import com.mysql.cj.protocol.a.NativeConstants.StringLengthDataType; import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType; +import com.mysql.cj.protocol.a.authentication.AuthenticationKerberosClient; import com.mysql.cj.protocol.a.authentication.AuthenticationLdapSaslClientPlugin; +import com.mysql.cj.protocol.a.authentication.AuthenticationOciClient; import com.mysql.cj.protocol.a.authentication.CachingSha2PasswordPlugin; import com.mysql.cj.protocol.a.authentication.MysqlClearPasswordPlugin; import com.mysql.cj.protocol.a.authentication.MysqlNativePasswordPlugin; @@ -120,8 +122,6 @@ public void init(Protocol prot, PropertySet propSet, Except * Initialize communications with the MySQL server. Handles logging on, and * handling initial connection errors. * - * @param sessState - * The session state object. It's intended to be updated from the handshake * @param user * user name * @param pass @@ -130,7 +130,8 @@ public void init(Protocol prot, PropertySet propSet, Except * database name */ @Override - public void connect(ServerSession sessState, String user, String pass, String db) { + public void connect(String user, String pass, String db) { + ServerSession sessState = this.protocol.getServerSession(); this.username = user; this.password = pass; this.database = db; @@ -151,7 +152,6 @@ public void connect(ServerSession sessState, String user, String pass, String db throw ExceptionFactory.createException(UnableToConnectException.class, "CLIENT_PLUGIN_AUTH is required", getExceptionInterceptor()); } - sessState.setServerDefaultCollationIndex(capabilities.getServerDefaultCollationIndex()); // read character set (1 byte) sessState.setStatusFlags(capabilities.getStatusFlags()); // read status flags (2 bytes) int authPluginDataLength = capabilities.getAuthPluginDataLength(); @@ -164,37 +164,38 @@ public void connect(ServerSession sessState, String user, String pass, String db this.useConnectWithDb = (this.database != null) && (this.database.length() > 0) && !this.propertySet.getBooleanProperty(PropertyKey.createDatabaseIfNotExist).getValue(); - long clientParam = NativeServerSession.CLIENT_SECURE_CONNECTION | NativeServerSession.CLIENT_PLUGIN_AUTH - | (capabilityFlags & NativeServerSession.CLIENT_LONG_PASSWORD) // - | (capabilityFlags & NativeServerSession.CLIENT_PROTOCOL_41) // - | (capabilityFlags & NativeServerSession.CLIENT_TRANSACTIONS) // Need this to get server status values - | (capabilityFlags & NativeServerSession.CLIENT_MULTI_RESULTS) // We always allow multiple result sets - | (capabilityFlags & NativeServerSession.CLIENT_PS_MULTI_RESULTS) // We always allow multiple result sets for SSPS - | (capabilityFlags & NativeServerSession.CLIENT_LONG_FLAG) // - | (capabilityFlags & NativeServerSession.CLIENT_DEPRECATE_EOF) // - | (capabilityFlags & NativeServerSession.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) - | (this.propertySet.getBooleanProperty(PropertyKey.useCompression).getValue() ? (capabilityFlags & NativeServerSession.CLIENT_COMPRESS) : 0) - | (this.useConnectWithDb ? (capabilityFlags & NativeServerSession.CLIENT_CONNECT_WITH_DB) : 0) - | (this.propertySet.getBooleanProperty(PropertyKey.useAffectedRows).getValue() ? 0 : (capabilityFlags & NativeServerSession.CLIENT_FOUND_ROWS)) + long clientParam = capabilityFlags & NativeServerSession.CLIENT_LONG_PASSWORD // + | (this.propertySet.getBooleanProperty(PropertyKey.useAffectedRows).getValue() ? // + 0 : capabilityFlags & NativeServerSession.CLIENT_FOUND_ROWS) // + | capabilityFlags & NativeServerSession.CLIENT_LONG_FLAG // + | (this.useConnectWithDb ? capabilityFlags & NativeServerSession.CLIENT_CONNECT_WITH_DB : 0) // + | (this.propertySet.getBooleanProperty(PropertyKey.useCompression).getValue() ? // + capabilityFlags & NativeServerSession.CLIENT_COMPRESS : 0) // | (this.propertySet.getBooleanProperty(PropertyKey.allowLoadLocalInfile).getValue() - || this.propertySet.getStringProperty(PropertyKey.allowLoadLocalInfileInPath).isExplicitlySet() - ? (capabilityFlags & NativeServerSession.CLIENT_LOCAL_FILES) - : 0) - | (this.propertySet.getBooleanProperty(PropertyKey.interactiveClient).getValue() ? (capabilityFlags & NativeServerSession.CLIENT_INTERACTIVE) - : 0) - | (this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue() - ? (capabilityFlags & NativeServerSession.CLIENT_MULTI_STATEMENTS) - : 0) - | (this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords).getValue() ? 0 - : (capabilityFlags & NativeServerSession.CLIENT_CAN_HANDLE_EXPIRED_PASSWORD)) - | (NONE.equals(this.propertySet.getStringProperty(PropertyKey.connectionAttributes).getValue()) ? 0 - : (capabilityFlags & NativeServerSession.CLIENT_CONNECT_ATTRS)) - | (this.propertySet.getEnumProperty(PropertyKey.sslMode).getValue() != SslMode.DISABLED - ? (capabilityFlags & NativeServerSession.CLIENT_SSL) - : 0); - - // TODO MYSQLCONNJ-437 - // clientParam |= (capabilityFlags & NativeServerSession.CLIENT_SESSION_TRACK); + || this.propertySet.getStringProperty(PropertyKey.allowLoadLocalInfileInPath).isExplicitlySet() ? // + capabilityFlags & NativeServerSession.CLIENT_LOCAL_FILES : 0) // + | capabilityFlags & NativeServerSession.CLIENT_PROTOCOL_41 // + | (this.propertySet.getBooleanProperty(PropertyKey.interactiveClient).getValue() ? // + capabilityFlags & NativeServerSession.CLIENT_INTERACTIVE : 0) // + | (this.propertySet.getEnumProperty(PropertyKey.sslMode).getValue() != SslMode.DISABLED ? // + capabilityFlags & NativeServerSession.CLIENT_SSL : 0) // + | capabilityFlags & NativeServerSession.CLIENT_TRANSACTIONS // Required to get server status values. + | NativeServerSession.CLIENT_SECURE_CONNECTION // + | (this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue() ? // + capabilityFlags & NativeServerSession.CLIENT_MULTI_STATEMENTS : 0) // + | capabilityFlags & NativeServerSession.CLIENT_MULTI_RESULTS // Always allow multiple result sets. + | capabilityFlags & NativeServerSession.CLIENT_PS_MULTI_RESULTS // Always allow multiple result sets for SSPS. + | NativeServerSession.CLIENT_PLUGIN_AUTH // + | (NONE.equals(this.propertySet.getStringProperty(PropertyKey.connectionAttributes).getValue()) ? // + 0 : capabilityFlags & NativeServerSession.CLIENT_CONNECT_ATTRS) // + | capabilityFlags & NativeServerSession.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA // + | (this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords).getValue() ? // + 0 : capabilityFlags & NativeServerSession.CLIENT_CAN_HANDLE_EXPIRED_PASSWORD) // + | (this.propertySet.getBooleanProperty(PropertyKey.trackSessionState).getValue() ? // + capabilityFlags & NativeServerSession.CLIENT_SESSION_TRACK : 0) // + | capabilityFlags & NativeServerSession.CLIENT_DEPRECATE_EOF // + | capabilityFlags & NativeServerSession.CLIENT_QUERY_ATTRIBUTES // + | capabilityFlags & NativeServerSession.CLIENT_MULTI_FACTOR_AUTHENTICATION; sessState.setClientParam(clientParam); @@ -207,7 +208,7 @@ public void connect(ServerSession sessState, String user, String pass, String db throw ExceptionFactory.createException(Messages.getString("AuthenticationProvider.UnexpectedAuthenticationApproval"), getExceptionInterceptor()); } - proceedHandshakeWithPluggableAuthentication(sessState, buf); + proceedHandshakeWithPluggableAuthentication(buf); this.password = null; } @@ -253,6 +254,8 @@ private void loadAuthenticationPlugins() { pluginsToInit.add(new CachingSha2PasswordPlugin()); pluginsToInit.add(new MysqlOldPasswordPlugin()); pluginsToInit.add(new AuthenticationLdapSaslClientPlugin()); + pluginsToInit.add(new AuthenticationKerberosClient()); + pluginsToInit.add(new AuthenticationOciClient()); // plugins from authenticationPluginClasses connection parameter String authenticationPluginClasses = this.propertySet.getStringProperty(PropertyKey.authenticationPlugins).getValue(); @@ -322,7 +325,11 @@ private void loadAuthenticationPlugins() { private AuthenticationPlugin getAuthenticationPlugin(String pluginName) { AuthenticationPlugin plugin = this.authenticationPlugins.get(pluginName); - if (plugin != null && !plugin.isReusable()) { + if (plugin == null) { + return null; + } + + if (!plugin.isReusable()) { try { plugin = plugin.getClass().newInstance(); } catch (Throwable t) { @@ -357,14 +364,14 @@ private void checkConfidentiality(AuthenticationPlugin plugin) { * * This method will use registered authentication plugins as requested by the server. * - * @param serverSession - * The current state of the session * @param challenge * the Auth Challenge Packet received from server if * this method is used during the initial connection. * Otherwise null. */ - private void proceedHandshakeWithPluggableAuthentication(ServerSession serverSession, final NativePacketPayload challenge) { + private void proceedHandshakeWithPluggableAuthentication(final NativePacketPayload challenge) { + ServerSession serverSession = this.protocol.getServerSession(); + if (this.authenticationPlugins == null) { loadAuthenticationPlugins(); } @@ -375,6 +382,8 @@ private void proceedHandshakeWithPluggableAuthentication(ServerSession serverSes forChangeUser = false; } + serverSession.getCharsetSettings().configurePreHandshake(forChangeUser); + /* * Select the initial plugin: * Choose the client-side default authentication plugin, if explicitely specified, otherwise choose the server-side default authentication plugin. @@ -416,64 +425,84 @@ private void proceedHandshakeWithPluggableAuthentication(ServerSession serverSes NativePacketPayload fromServer = new NativePacketPayload(StringUtils.getBytes(this.seed)); String sourceOfAuthData = this.serverDefaultAuthenticationPluginName; - boolean old_raw_challenge = false; - NativePacketPayload last_sent = null; - NativePacketPayload last_received = challenge; + NativePacketPayload lastSent = null; + NativePacketPayload lastReceived = challenge; ArrayList toServer = new ArrayList<>(); + boolean firstPacket = true; + + // MFA authentication factor + int mfaNthFactor = 1; + /* Max iterations number */ int counter = 100; while (0 < counter--) { /* * call plugin */ - plugin.setAuthenticationParameters(this.username, skipPassword ? null : this.password); + plugin.setAuthenticationParameters(this.username, skipPassword ? null : getNthFactorPassword(mfaNthFactor)); plugin.setSourceOfAuthData(sourceOfAuthData); plugin.nextAuthenticationStep(fromServer, toServer); /* * send response to server */ - if (toServer.size() > 0) { + if (firstPacket) { + NativePacketPayload authData = toServer.isEmpty() ? new NativePacketPayload(0) : toServer.get(0); if (forChangeUser) { // write COM_CHANGE_USER Packet - last_sent = createChangeUserPacket(serverSession, plugin.getProtocolPluginName(), toServer); - this.protocol.send(last_sent, last_sent.getPosition()); - forChangeUser = false; // this branch should be executed only once - - } else if (last_received.isAuthMethodSwitchRequestPacket() || last_received.isAuthMoreData() || old_raw_challenge) { - // write AuthSwitchResponse packet or raw packet(s) - for (NativePacketPayload buffer : toServer) { - this.protocol.send(buffer, buffer.getPayloadLength()); - } - + lastSent = createChangeUserPacket(serverSession, plugin.getProtocolPluginName(), authData); + this.protocol.send(lastSent, lastSent.getPosition()); } else { // write HandshakeResponse packet - last_sent = createHandshakeResponsePacket(serverSession, plugin.getProtocolPluginName(), toServer); - this.protocol.send(last_sent, last_sent.getPosition()); + lastSent = createHandshakeResponsePacket(serverSession, plugin.getProtocolPluginName(), authData); + this.protocol.send(lastSent, lastSent.getPosition()); } + firstPacket = false; + } else if (!toServer.isEmpty()) { + // write AuthSwitchResponse packet or raw packet(s) + toServer.forEach(b -> this.protocol.send(b, b.getPayloadLength())); } /* * read packet from server */ - last_received = this.protocol.checkErrorMessage(); - old_raw_challenge = false; + lastReceived = this.protocol.checkErrorMessage(); - if (last_received.isOKPacket()) { + if (lastReceived.isOKPacket()) { // read OK packet - OkPacket ok = OkPacket.parse(last_received, null); + OkPacket ok = OkPacket.parse(lastReceived, null); serverSession.setStatusFlags(ok.getStatusFlags(), true); + serverSession.getServerSessionStateController().setSessionStateChanges(ok.getSessionStateChanges()); - // if OK packet then finish handshake + // authentication complete plugin.destroy(); break; - } else if (last_received.isAuthMethodSwitchRequestPacket()) { + } else if (lastReceived.isAuthMethodSwitchRequestPacket()) { // read AuthSwitchRequest Packet skipPassword = false; + pluginName = lastReceived.readString(StringSelfDataType.STRING_TERM, "ASCII"); + if (plugin.getProtocolPluginName().equals(pluginName)) { + plugin.reset(); // just reset the current one + } else { + // get new plugin + plugin.destroy(); + plugin = getAuthenticationPlugin(pluginName); + if (plugin == null) { + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("AuthenticationProvider.BadAuthenticationPlugin", new Object[] { pluginName }), getExceptionInterceptor()); + } + } - pluginName = last_received.readString(StringSelfDataType.STRING_TERM, "ASCII"); + checkConfidentiality(plugin); + fromServer = new NativePacketPayload(lastReceived.readBytes(StringSelfDataType.STRING_EOF)); + + } else if (lastReceived.isAuthNextFactorPacket()) { + // authentication not done yet, there's another MFA iteration + mfaNthFactor++; + skipPassword = false; + pluginName = lastReceived.readString(StringSelfDataType.STRING_TERM, "ASCII"); if (plugin.getProtocolPluginName().equals(pluginName)) { plugin.reset(); // just reset the current one } else { @@ -487,15 +516,14 @@ private void proceedHandshakeWithPluggableAuthentication(ServerSession serverSes } checkConfidentiality(plugin); - fromServer = new NativePacketPayload(StringUtils.getBytes(last_received.readString(StringSelfDataType.STRING_TERM, "ASCII"))); + fromServer = new NativePacketPayload(lastReceived.readBytes(StringSelfDataType.STRING_EOF)); } else { - // read raw packet + // read raw (from AuthMoreData) packet if (!this.protocol.versionMeetsMinimum(5, 5, 16)) { - old_raw_challenge = true; - last_received.setPosition(last_received.getPosition() - 1); + lastReceived.setPosition(lastReceived.getPosition() - 1); } - fromServer = new NativePacketPayload(last_received.readBytes(StringSelfDataType.STRING_EOF)); + fromServer = new NativePacketPayload(lastReceived.readBytes(StringSelfDataType.STRING_EOF)); } sourceOfAuthData = pluginName; @@ -513,6 +541,19 @@ private void proceedHandshakeWithPluggableAuthentication(ServerSession serverSes } } + private String getNthFactorPassword(int nthFactor) { + switch (nthFactor) { + case 1: + return this.password == null ? this.protocol.getPropertySet().getStringProperty(PropertyKey.password1).getValue() : this.password; + case 2: + return this.protocol.getPropertySet().getStringProperty(PropertyKey.password2).getValue(); + case 3: + return this.protocol.getPropertySet().getStringProperty(PropertyKey.password3).getValue(); + default: + return null; + } + } + private Map getConnectionAttributesMap(String attStr) { Map attMap = new HashMap<>(); @@ -553,20 +594,6 @@ private void appendConnectionAttributes(NativePacketPayload buf, String attribut buf.writeBytes(StringLengthDataType.STRING_FIXED, lb.getByteBuffer(), 0, lb.getPosition()); } - /** - * Get the Java encoding to be used for the handshake - * response. Defaults to UTF-8. - * - * @return encoding name - */ - public String getEncodingForHandshake() { - String enc = this.propertySet.getStringProperty(PropertyKey.characterEncoding).getValue(); - if (enc == null) { - enc = "UTF-8"; - } - return enc; - } - public ExceptionInterceptor getExceptionInterceptor() { return this.exceptionInterceptor; } @@ -574,8 +601,6 @@ public ExceptionInterceptor getExceptionInterceptor() { /** * Re-authenticates as the given user and password * - * @param serverSession - * current {@link ServerSession} * @param user * user name * @param pass @@ -584,18 +609,19 @@ public ExceptionInterceptor getExceptionInterceptor() { * database name */ @Override - public void changeUser(ServerSession serverSession, String user, String pass, String db) { + public void changeUser(String user, String pass, String db) { this.username = user; this.password = pass; this.database = db; - proceedHandshakeWithPluggableAuthentication(serverSession, null); + proceedHandshakeWithPluggableAuthentication(null); this.password = null; } - private NativePacketPayload createHandshakeResponsePacket(ServerSession serverSession, String pluginName, ArrayList toServer) { + private NativePacketPayload createHandshakeResponsePacket(ServerSession serverSession, String pluginName, NativePacketPayload authData) { long clientParam = serverSession.getClientParam(); - String enc = getEncodingForHandshake(); + int collationIndex = serverSession.getCharsetSettings().configurePreHandshake(false); + String enc = serverSession.getCharsetSettings().getPasswordCharacterEncoding(); int userLength = this.username == null ? 0 : this.username.length(); NativePacketPayload last_sent = new NativePacketPayload(AUTH_411_OVERHEAD + 7 // @@ -605,18 +631,18 @@ private NativePacketPayload createHandshakeResponsePacket(ServerSession serverSe ); last_sent.writeInteger(IntegerDataType.INT4, clientParam); last_sent.writeInteger(IntegerDataType.INT4, NativeConstants.MAX_PACKET_SIZE); - last_sent.writeInteger(IntegerDataType.INT1, AuthenticationProvider.getCharsetForHandshake(enc, serverSession.getCapabilities().getServerVersion())); + last_sent.writeInteger(IntegerDataType.INT1, collationIndex); last_sent.writeBytes(StringLengthDataType.STRING_FIXED, new byte[23]); // Set of bytes reserved for future use. // User/Password data last_sent.writeBytes(StringSelfDataType.STRING_TERM, StringUtils.getBytes(this.username, enc)); if ((clientParam & NativeServerSession.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) != 0) { // send lenenc-int length of auth-response and string[n] auth-response - last_sent.writeBytes(StringSelfDataType.STRING_LENENC, toServer.get(0).readBytes(StringSelfDataType.STRING_EOF)); + last_sent.writeBytes(StringSelfDataType.STRING_LENENC, authData.readBytes(StringSelfDataType.STRING_EOF)); } else { // send 1 byte length of auth-response and string[n] auth-response - last_sent.writeInteger(IntegerDataType.INT1, toServer.get(0).getPayloadLength()); - last_sent.writeBytes(StringSelfDataType.STRING_EOF, toServer.get(0).getByteBuffer()); + last_sent.writeInteger(IntegerDataType.INT1, authData.getPayloadLength()); + last_sent.writeBytes(StringSelfDataType.STRING_EOF, authData.getByteBuffer()); } if (this.useConnectWithDb) { @@ -632,10 +658,11 @@ private NativePacketPayload createHandshakeResponsePacket(ServerSession serverSe return last_sent; } - private NativePacketPayload createChangeUserPacket(ServerSession serverSession, String pluginName, ArrayList toServer) { + private NativePacketPayload createChangeUserPacket(ServerSession serverSession, String pluginName, NativePacketPayload authData) { // write Auth Response Packet long clientParam = serverSession.getClientParam(); - String enc = getEncodingForHandshake(); + int collationIndex = serverSession.getCharsetSettings().configurePreHandshake(false); + String enc = serverSession.getCharsetSettings().getPasswordCharacterEncoding(); NativePacketPayload last_sent = new NativePacketPayload(AUTH_411_OVERHEAD + 7 // + 48 // passwordLength @@ -647,10 +674,10 @@ private NativePacketPayload createChangeUserPacket(ServerSession serverSession, // User/Password data last_sent.writeBytes(StringSelfDataType.STRING_TERM, StringUtils.getBytes(this.username, enc)); // 'auth-response-len' is limited to one Byte but, in case of success, COM_CHANGE_USER will be followed by an AuthSwitchRequest anyway - if (toServer.get(0).getPayloadLength() < 256) { + if (authData.getPayloadLength() < 256) { // non-mysql servers may use this information to authenticate without requiring another round-trip - last_sent.writeInteger(IntegerDataType.INT1, toServer.get(0).getPayloadLength()); - last_sent.writeBytes(StringSelfDataType.STRING_EOF, toServer.get(0).getByteBuffer(), 0, toServer.get(0).getPayloadLength()); + last_sent.writeInteger(IntegerDataType.INT1, authData.getPayloadLength()); + last_sent.writeBytes(StringSelfDataType.STRING_EOF, authData.getByteBuffer(), 0, authData.getPayloadLength()); } else { last_sent.writeInteger(IntegerDataType.INT1, 0); } @@ -662,7 +689,7 @@ private NativePacketPayload createChangeUserPacket(ServerSession serverSession, last_sent.writeInteger(IntegerDataType.INT1, 0); } - last_sent.writeInteger(IntegerDataType.INT1, AuthenticationProvider.getCharsetForHandshake(enc, serverSession.getCapabilities().getServerVersion())); + last_sent.writeInteger(IntegerDataType.INT1, collationIndex); last_sent.writeInteger(IntegerDataType.INT1, 0); // two (little-endian) bytes for charset in this packet // plugin name diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeCapabilities.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeCapabilities.java index 4bf751552..73d916133 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeCapabilities.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeCapabilities.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -52,27 +52,20 @@ public class NativeCapabilities implements ServerCapabilities { private int authPluginDataLength = 0; private boolean serverHasFracSecsSupport = true; - public NativeCapabilities() { - } - - public NativePacketPayload getInitialHandshakePacket() { - return this.initialHandshakePacket; - } - - public void setInitialHandshakePacket(NativePacketPayload initialHandshakePacket) { + public NativeCapabilities(NativePacketPayload initialHandshakePacket) { this.initialHandshakePacket = initialHandshakePacket; // Get the protocol version - setProtocolVersion((byte) initialHandshakePacket.readInteger(IntegerDataType.INT1)); + this.protocolVersion = (byte) initialHandshakePacket.readInteger(IntegerDataType.INT1); try { - setServerVersion(ServerVersion.parseVersion(initialHandshakePacket.readString(StringSelfDataType.STRING_TERM, "ASCII"))); + this.serverVersion = ServerVersion.parseVersion(initialHandshakePacket.readString(StringSelfDataType.STRING_TERM, "ASCII")); // read connection id - setThreadId(initialHandshakePacket.readInteger(IntegerDataType.INT4)); + this.threadId = initialHandshakePacket.readInteger(IntegerDataType.INT4); // read auth-plugin-data-part-1 (string[8]) - setSeed(initialHandshakePacket.readString(StringLengthDataType.STRING_FIXED, "ASCII", 8)); + this.seed = initialHandshakePacket.readString(StringLengthDataType.STRING_FIXED, "ASCII", 8); // read filler ([00]) initialHandshakePacket.readInteger(IntegerDataType.INT1); @@ -85,9 +78,9 @@ public void setInitialHandshakePacket(NativePacketPayload initialHandshakePacket } // read character set (1 byte) - setServerDefaultCollationIndex((int) initialHandshakePacket.readInteger(IntegerDataType.INT1)); + this.serverDefaultCollationIndex = (int) initialHandshakePacket.readInteger(IntegerDataType.INT1); // read status flags (2 bytes) - setStatusFlags((int) initialHandshakePacket.readInteger(IntegerDataType.INT2)); + this.statusFlags = (int) initialHandshakePacket.readInteger(IntegerDataType.INT2); // read capability flags (upper 2 bytes) flags |= (int) initialHandshakePacket.readInteger(IntegerDataType.INT2) << 16; @@ -117,6 +110,10 @@ public void setInitialHandshakePacket(NativePacketPayload initialHandshakePacket } } + public NativePacketPayload getInitialHandshakePacket() { + return this.initialHandshakePacket; + } + @Override public int getCapabilityFlags() { return this.capabilityFlags; @@ -127,22 +124,10 @@ public void setCapabilityFlags(int capabilityFlags) { this.capabilityFlags = capabilityFlags; } - public byte getProtocolVersion() { - return this.protocolVersion; - } - - public void setProtocolVersion(byte protocolVersion) { - this.protocolVersion = protocolVersion; - } - public ServerVersion getServerVersion() { return this.serverVersion; } - public void setServerVersion(ServerVersion serverVersion) { - this.serverVersion = serverVersion; - } - public long getThreadId() { return this.threadId; } @@ -155,10 +140,6 @@ public String getSeed() { return this.seed; } - public void setSeed(String seed) { - this.seed = seed; - } - /** * * @return Collation index which server provided in handshake greeting packet @@ -167,32 +148,14 @@ public int getServerDefaultCollationIndex() { return this.serverDefaultCollationIndex; } - /** - * Stores collation index which server provided in handshake greeting packet. - * - * @param serverDefaultCollationIndex - * server default collation index - */ - public void setServerDefaultCollationIndex(int serverDefaultCollationIndex) { - this.serverDefaultCollationIndex = serverDefaultCollationIndex; - } - public int getStatusFlags() { return this.statusFlags; } - public void setStatusFlags(int statusFlags) { - this.statusFlags = statusFlags; - } - public int getAuthPluginDataLength() { return this.authPluginDataLength; } - public void setAuthPluginDataLength(int authPluginDataLength) { - this.authPluginDataLength = authPluginDataLength; - } - @Override public boolean serverSupportsFracSecs() { return this.serverHasFracSecsSupport; diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeConstants.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeConstants.java index a30e15de6..2aa52ec08 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeConstants.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -57,6 +57,7 @@ public class NativeConstants { public static final int BIN_LEN_DATE = 4; public static final int BIN_LEN_TIMESTAMP_NO_FRAC = 7; public static final int BIN_LEN_TIMESTAMP_WITH_MICROS = 11; + public static final int BIN_LEN_TIMESTAMP_WITH_TZ = 13; public static final int BIN_LEN_TIME_NO_FRAC = 8; public static final int BIN_LEN_TIME_WITH_MICROS = 12; 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 4acb4e584..2817e8b46 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. + * Copyright (c) 2017, 2021, 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 @@ -39,6 +39,11 @@ import com.mysql.cj.util.StringUtils; public class NativeMessageBuilder implements MessageBuilder { + private boolean supportsQueryAttributes = true; + + public NativeMessageBuilder(boolean supportsQueryAttributes) { + this.supportsQueryAttributes = supportsQueryAttributes; + } @Override public NativePacketPayload buildSqlStatement(String statement) { @@ -58,6 +63,14 @@ public NativePacketPayload buildClose() { public NativePacketPayload buildComQuery(NativePacketPayload sharedPacket, 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) + } + packet.writeBytes(StringLengthDataType.STRING_FIXED, query); return packet; } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativePacketPayload.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativePacketPayload.java index 07e0e7d4f..e44151518 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativePacketPayload.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativePacketPayload.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -29,6 +29,9 @@ package com.mysql.cj.protocol.a; +import java.util.HashMap; +import java.util.Map; + import com.mysql.cj.Constants; import com.mysql.cj.Messages; import com.mysql.cj.exceptions.ExceptionFactory; @@ -49,7 +52,6 @@ * A position is maintained for reading/writing data. A payload length is * maintained allowing the PacketPayload to be decoupled from the size of * the underlying buffer. - * */ public class NativePacketPayload implements Message { static final int NO_LENGTH_LIMIT = -1; @@ -62,6 +64,8 @@ public class NativePacketPayload implements Message { public static final short TYPE_ID_AUTH_SWITCH = 0xFE; public static final short TYPE_ID_LOCAL_INFILE = 0xFB; public static final short TYPE_ID_OK = 0; + public static final short TYPE_ID_AUTH_MORE_DATA = 0x01; + public static final short TYPE_ID_AUTH_NEXT_FACTOR = 0x02; private int payloadLength = 0; @@ -71,6 +75,8 @@ public class NativePacketPayload implements Message { static final int MAX_BYTES_TO_DUMP = 1024; + private Map tags = new HashMap<>(); + @Override public String toString() { int numBytes = this.position <= this.payloadLength ? this.position : this.payloadLength; @@ -208,17 +214,17 @@ public boolean isErrorPacket() { /** * Is it a EOF packet. - * See http://dev.mysql.com/doc/internals/en/packet-EOF_Packet.html + * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html * * @return true if it is a EOF packet */ public final boolean isEOFPacket() { - return (this.byteBuffer[0] & 0xff) == TYPE_ID_EOF && (getPayloadLength() <= 5); + return (this.byteBuffer[0] & 0xff) == TYPE_ID_EOF && (this.payloadLength <= 5); } /** * Is it a Protocol::AuthSwitchRequest packet. - * See http://dev.mysql.com/doc/internals/en/connection-phase-packets.html + * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html * * @return true if it is a Protocol::AuthSwitchRequest packet */ @@ -228,7 +234,7 @@ public final boolean isAuthMethodSwitchRequestPacket() { /** * Is it an OK packet. - * See http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html + * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html * * @return true if it is an OK packet */ @@ -238,22 +244,32 @@ public final boolean isOKPacket() { /** * Is it an OK packet for ResultSet. Unlike usual 0x00 signature it has 0xfe signature. - * See http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html + * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html * * @return true if it is an OK packet for ResultSet */ public final boolean isResultSetOKPacket() { - return (this.byteBuffer[0] & 0xff) == TYPE_ID_EOF && (getPayloadLength() < 16777215); + return (this.byteBuffer[0] & 0xff) == TYPE_ID_EOF && (this.payloadLength > 5) && (this.payloadLength < 16777215); } /** * Is it a Protocol::AuthMoreData packet. - * See http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthMoreData + * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_more_data.html * * @return true if it is a Protocol::AuthMoreData packet */ - public final boolean isAuthMoreData() { - return ((this.byteBuffer[0] & 0xff) == 1); + public final boolean isAuthMoreDataPacket() { + return (this.byteBuffer[0] & 0xff) == TYPE_ID_AUTH_MORE_DATA; + } + + /** + * Is it a Protocol::AuthNextFactor packet. + * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_next_factor_request.html + * + * @return true if it is a Protocol::AuthNextFactor packet + */ + public final boolean isAuthNextFactorPacket() { + return (this.byteBuffer[0] & 0xff) == TYPE_ID_AUTH_NEXT_FACTOR; } /** @@ -659,4 +675,29 @@ public static String extractSqlFromPacket(String possibleSqlQuery, NativePacketP return extractedSql; } + /** + * Tag current position with the given key for future reference. + * + * @param key + * the position tag key name. + * @return + * the previous value of this tag, if there was one, or -1. + */ + public int setTag(String key) { + Integer pos = this.tags.put(key, getPosition()); + return pos == null ? -1 : pos; + } + + /** + * Gets the value of the position tag for the given key. + * + * @param key + * the position tag key name. + * @return + * the position value of this tag, if there was one, or -1. + */ + public int getTag(String key) { + Integer pos = this.tags.get(key); + return pos == null ? -1 : pos; + } } 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 a6d0d978d..e7fa0622a 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -30,11 +30,9 @@ package com.mysql.cj.protocol.a; import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStreamWriter; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; @@ -68,7 +66,11 @@ 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.QueryAttributesBindValue; +import com.mysql.cj.QueryAttributesBindings; import com.mysql.cj.QueryResult; import com.mysql.cj.ServerPreparedQuery; import com.mysql.cj.ServerVersion; @@ -97,7 +99,6 @@ import com.mysql.cj.log.ProfilerEvent; import com.mysql.cj.log.ProfilerEventHandler; import com.mysql.cj.protocol.AbstractProtocol; -import com.mysql.cj.protocol.AuthenticationProvider; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.ExportControlled; import com.mysql.cj.protocol.FullReadInputStream; @@ -187,12 +188,6 @@ public class NativeProtocol extends AbstractProtocol implem protected Map, ProtocolEntityReader> PROTOCOL_ENTITY_CLASS_TO_TEXT_READER; protected Map, ProtocolEntityReader> PROTOCOL_ENTITY_CLASS_TO_BINARY_READER; - /** - * Does the character set of this connection match the character set of the - * platform - */ - protected boolean platformDbCharsetMatches = true; // changed once we've connected. - private int statementExecutionDepth = 0; private List queryInterceptors; @@ -209,33 +204,7 @@ public class NativeProtocol extends AbstractProtocol implem */ private String queryComment = null; - /** - * We store the platform 'encoding' here, only used to avoid munging filenames for LOAD DATA LOCAL INFILE... - */ - private static String jvmPlatformCharset = null; - - private NativeMessageBuilder commandBuilder = new NativeMessageBuilder(); // TODO use shared builder - - static { - OutputStreamWriter outWriter = null; - - // - // Use the I/O system to get the encoding (if possible), to avoid security restrictions on System.getProperty("file.encoding") in applets (why is that - // restricted?) - // - try { - outWriter = new OutputStreamWriter(new ByteArrayOutputStream()); - jvmPlatformCharset = outWriter.getEncoding(); - } finally { - try { - if (outWriter != null) { - outWriter.close(); - } - } catch (IOException ioEx) { - // ignore - } - } - } + private NativeMessageBuilder commandBuilder = null; public static NativeProtocol getInstance(Session session, SocketConnection socketConnection, PropertySet propertySet, Log log, TransactionEventHandler transactionManager) { @@ -308,6 +277,13 @@ public MessageReader getPacketReader() return this.packetReader; } + private NativeMessageBuilder getCommandBuilder() { + if (this.commandBuilder != null) { + return this.commandBuilder; + } + return this.commandBuilder = new NativeMessageBuilder(this.serverSession.supportsQueryAttributes()); + } + /** * Negotiates the SSL communications channel used when connecting * to a MySQL server that understands SSL. @@ -323,14 +299,13 @@ public void negotiateSSLConnection() { NativePacketPayload packet = new NativePacketPayload(SSL_REQUEST_LENGTH); packet.writeInteger(IntegerDataType.INT4, clientParam); packet.writeInteger(IntegerDataType.INT4, NativeConstants.MAX_PACKET_SIZE); - packet.writeInteger(IntegerDataType.INT1, AuthenticationProvider.getCharsetForHandshake(this.authProvider.getEncodingForHandshake(), - this.serverSession.getCapabilities().getServerVersion())); + packet.writeInteger(IntegerDataType.INT1, this.serverSession.getCharsetSettings().configurePreHandshake(false)); packet.writeBytes(StringLengthDataType.STRING_FIXED, new byte[23]); // Set of bytes reserved for future use. send(packet, packet.getPosition()); try { - this.socketConnection.performTlsHandshake(this.serverSession); + this.socketConnection.performTlsHandshake(this.serverSession, this.log); // i/o streams were replaced, build new packet sender/reader this.packetSender = new SimplePacketSender(this.socketConnection.getMysqlOutput()); @@ -381,6 +356,8 @@ public void beforeHandshake() { // Create session state this.serverSession = new NativeServerSession(this.propertySet); + this.serverSession.setCharsetSettings(new NativeCharsetSettings((NativeSession) this.session)); + // Read the first packet this.serverSession.setCapabilities(readServerCapabilities()); @@ -502,11 +479,7 @@ public NativeCapabilities readServerCapabilities() { rejectProtocol(buf); } - NativeCapabilities serverCapabilities = new NativeCapabilities(); - serverCapabilities.setInitialHandshakePacket(buf); - - return serverCapabilities; - + return new NativeCapabilities(buf); } @Override @@ -521,12 +494,13 @@ public void changeDatabase(String database) { } try { - sendCommand(this.commandBuilder.buildComInitDb(getSharedSendPacket(), database), false, 0); + sendCommand(getCommandBuilder().buildComInitDb(getSharedSendPacket(), database), false, 0); } catch (CJException ex) { if (this.getPropertySet().getBooleanProperty(PropertyKey.createDatabaseIfNotExist).getValue()) { - sendCommand(this.commandBuilder.buildComQuery(getSharedSendPacket(), "CREATE DATABASE IF NOT EXISTS " + database), false, 0); + sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), + "CREATE DATABASE IF NOT EXISTS " + StringUtils.quoteIdentifier(database, true)), false, 0); - sendCommand(this.commandBuilder.buildComInitDb(getSharedSendPacket(), database), false, 0); + sendCommand(getCommandBuilder().buildComInitDb(getSharedSendPacket(), database), false, 0); } else { throw ExceptionFactory.createCommunicationsException(this.getPropertySet(), this.serverSession, this.getPacketSentTimeHolder(), this.getPacketReceivedTimeHolder(), ex, getExceptionInterceptor()); @@ -551,6 +525,22 @@ public final NativePacketPayload readMessage(NativePacketPayload reuse) { } } + public final NativePacketPayload probeMessage(NativePacketPayload reuse) { + try { + NativePacketHeader header = this.packetReader.probeHeader(); + NativePacketPayload buf = this.packetReader.probeMessage(Optional.ofNullable(reuse), header); + this.packetSequence = header.getMessageSequence(); + return buf; + + } catch (IOException ioEx) { + throw ExceptionFactory.createCommunicationsException(this.propertySet, this.serverSession, this.getPacketSentTimeHolder(), + this.getPacketReceivedTimeHolder(), ioEx, getExceptionInterceptor()); + } catch (OutOfMemoryError oom) { + throw ExceptionFactory.createException(oom.getMessage(), MysqlErrorNumbers.SQL_STATE_MEMORY_ALLOCATION_ERROR, 0, false, oom, + this.exceptionInterceptor); + } + } + /** * @param packet * {@link Message} @@ -735,7 +725,7 @@ public void checkErrorMessage(NativePacketPayload resultPacket) { String xOpen = null; - serverErrorMessage = resultPacket.readString(StringSelfDataType.STRING_TERM, this.serverSession.getErrorMessageEncoding()); + serverErrorMessage = resultPacket.readString(StringSelfDataType.STRING_TERM, this.serverSession.getCharsetSettings().getErrorMessageEncoding()); if (serverErrorMessage.charAt(0) == '#') { @@ -862,7 +852,7 @@ public final T sendQueryString(Query callingQuery, String // 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 + (query.length() * 4) + 2; + int packLength = 1 /* com_query */ + (query.length() * 4) + 2; byte[] commentAsBytes = null; @@ -873,22 +863,68 @@ public final T sendQueryString(Query callingQuery, String 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++) { + QueryAttributesBindValue queryAttribute = queryAttributes.getAttributeValue(i); + packLength += 2 /* parameter_type */ + queryAttribute.getName().length() /* parameter_name */ + queryAttribute.getBoundLength(); + } + } else { + packLength += 1 /* parameter_count */ + 1 /* parameter_set_count */; + } + } + // TODO decide how to safely use the shared this.sendPacket - //if (this.sendPacket == null) { 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.getType()); + sendPacket.writeBytes(StringSelfDataType.STRING_LENENC, a.getName().getBytes()); + }); + ValueEncoder valueEncoder = new ValueEncoder(sendPacket, characterEncoding, this.serverSession.getDefaultTimeZone()); + queryAttributes.runThroughAll(a -> valueEncoder.encodeValue(a.getValue(), a.getType())); + } 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.platformDbCharsetMatches && StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) { + 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)); @@ -927,8 +963,8 @@ public final T sendQueryPacket(Query callingQuery, NativeP byte[] queryBuf = queryPacket.getByteBuffer(); int oldPacketPosition = queryPacket.getPosition(); // save the packet position - - LazyString query = new LazyString(queryBuf, 1, (oldPacketPosition - 1)); + int queryPosition = queryPacket.getTag("QUERY"); + LazyString query = new LazyString(queryBuf, queryPosition, (oldPacketPosition - queryPosition)); try { @@ -967,9 +1003,9 @@ public final T sendQueryPacket(Query callingQuery, NativeP long fetchEndTime = this.profileSQL ? getCurrentTimeNanosOrMillis() : 0L; // Extract the actual query from the network packet - boolean truncated = oldPacketPosition > this.maxQuerySizeToLog.getValue(); - int extractPosition = truncated ? this.maxQuerySizeToLog.getValue() + 1 : oldPacketPosition; - String extractedQuery = StringUtils.toString(queryBuf, 1, (extractPosition - 1)); + boolean truncated = oldPacketPosition - queryPosition > this.maxQuerySizeToLog.getValue(); + int extractPosition = truncated ? this.maxQuerySizeToLog.getValue() + queryPosition : oldPacketPosition; + String extractedQuery = StringUtils.toString(queryBuf, queryPosition, (extractPosition - queryPosition)); if (truncated) { extractedQuery += Messages.getString("Protocol.2"); } @@ -984,8 +1020,8 @@ public final T sendQueryPacket(Query callingQuery, NativeP this.queryTimingUnits, Long.valueOf(queryDuration), extractedQuery })); if (this.propertySet.getBooleanProperty(PropertyKey.explainSlowQueries).getValue()) { - if (oldPacketPosition < MAX_QUERY_SIZE_TO_EXPLAIN) { - queryPacket.setPosition(1); // skip first byte + if (oldPacketPosition - queryPosition < MAX_QUERY_SIZE_TO_EXPLAIN) { + queryPacket.setPosition(queryPosition); // skip until the query is located in the packet explainSlowQuery(query.toString(), extractedQuery); } else { this.log.logWarn(Messages.getString("Protocol.3", new Object[] { MAX_QUERY_SIZE_TO_EXPLAIN })); @@ -1170,7 +1206,7 @@ public void explainSlowQuery(String query, String truncatedQuery) { || (versionMeetsMinimum(5, 6, 3) && StringUtils.startsWithIgnoreCaseAndWs(truncatedQuery, EXPLAINABLE_STATEMENT_EXTENSION) != -1)) { try { - NativePacketPayload resultPacket = sendCommand(this.commandBuilder.buildComQuery(getSharedSendPacket(), "EXPLAIN " + query), false, 0); + NativePacketPayload resultPacket = sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), "EXPLAIN " + query), false, 0); Resultset rs = readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); @@ -1233,7 +1269,7 @@ public final void quit() { this.packetSequence = -1; NativePacketPayload packet = new NativePacketPayload(1); - send(this.commandBuilder.buildComQuit(packet), packet.getPosition()); + send(getCommandBuilder().buildComQuit(packet), packet.getPosition()); } finally { this.socketConnection.forceClose(); this.localInfileInputStream = null; @@ -1285,27 +1321,7 @@ public void changeUser(String user, String password, String database) { this.packetSender = this.packetSender.undecorateAll(); this.packetReader = this.packetReader.undecorateAll(); - this.authProvider.changeUser(this.serverSession, user, password, database); - } - - /** - * Determines if the database charset is the same as the platform charset - */ - public void checkForCharsetMismatch() { - String characterEncoding = this.propertySet.getStringProperty(PropertyKey.characterEncoding).getValue(); - if (characterEncoding != null) { - String encodingToCheck = jvmPlatformCharset; - - if (encodingToCheck == null) { - encodingToCheck = Constants.PLATFORM_ENCODING; - } - - if (encodingToCheck == null) { - this.platformDbCharsetMatches = false; - } else { - this.platformDbCharsetMatches = encodingToCheck.equals(characterEncoding); - } - } + this.authProvider.changeUser(user, password, database); } protected boolean useNanosForElapsedTime() { @@ -1350,8 +1366,7 @@ public void connect(String user, String password, String database) { beforeHandshake(); - this.authProvider.connect(this.serverSession, user, password, database); - + this.authProvider.connect(user, password, database); } protected boolean isDataAvailable() { @@ -1393,22 +1408,6 @@ public void dumpPacketRingBuffer() { } } - public boolean doesPlatformDbCharsetMatches() { - return this.platformDbCharsetMatches; - } - - public String getPasswordCharacterEncoding() { - String encoding; - if ((encoding = this.propertySet.getStringProperty(PropertyKey.passwordCharacterEncoding).getStringValue()) != null) { - return encoding; - } - if ((encoding = this.propertySet.getStringProperty(PropertyKey.characterEncoding).getValue()) != null) { - return encoding; - } - return "UTF-8"; - - } - public boolean versionMeetsMinimum(int major, int minor, int subminor) { return this.serverSession.getServerVersion().meetsMinimum(new ServerVersion(major, minor, subminor)); } @@ -1679,7 +1678,7 @@ public final T readServerStatusForResultSets(NativePacketPayload rowPacket, T result = null; if (rowPacket.isEOFPacket()) { // read EOF packet - rowPacket.readInteger(IntegerDataType.INT1); // skips the 'last packet' flag (packet signature) + rowPacket.setPosition(1); // skip the packet signature header this.warningCount = (int) rowPacket.readInteger(IntegerDataType.INT2); if (this.warningCount > 0) { this.hadWarnings = true; // this is a 'latch', it's reset by sendCommand() @@ -1689,10 +1688,11 @@ public final T readServerStatusForResultSets(NativePacketPayload rowPacket, checkTransactionState(); } else { // read OK packet - OkPacket ok = OkPacket.parse(rowPacket, this.serverSession.getErrorMessageEncoding()); + OkPacket ok = OkPacket.parse(rowPacket, this.serverSession.getCharsetSettings().getErrorMessageEncoding()); result = (T) ok; this.serverSession.setStatusFlags(ok.getStatusFlags(), saveOldStatus); + this.serverSession.getServerSessionStateController().setSessionStateChanges(ok.getSessionStateChanges()); checkTransactionState(); this.warningCount = ok.getWarningCount(); @@ -1935,7 +1935,7 @@ public void unsetStreamingData(ResultsetRows streamer) { public void scanForAndThrowDataTruncation() { if (this.streamingData == null && this.propertySet.getBooleanProperty(PropertyKey.jdbcCompliantTruncation).getValue() && getWarningCount() > 0) { int warningCountOld = getWarningCount(); - convertShowWarningsToSQLWarnings(getWarningCount(), true); + convertShowWarningsToSQLWarnings(true); setWarningCount(warningCountOld); } } @@ -1969,7 +1969,7 @@ private void appendDeadlockStatusInformation(Session sess, String xOpen, StringB && (xOpen.startsWith("40") || xOpen.startsWith("41")) && getStreamingData() == null) { try { - NativePacketPayload resultPacket = sendCommand(this.commandBuilder.buildComQuery(getSharedSendPacket(), "SHOW ENGINE INNODB STATUS"), false, 0); + NativePacketPayload resultPacket = sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), "SHOW ENGINE INNODB STATUS"), false, 0); Resultset rs = readAllResults(-1, false, resultPacket, false, null, new ResultsetFactory(Type.FORWARD_ONLY, null)); @@ -2085,14 +2085,16 @@ private StringBuilder appendResultSetSlashGStyle(StringBuilder appendTo, Results * If 'forTruncationOnly' is true, only looks for truncation warnings, and * actually throws DataTruncation as an exception. * - * @param warningCountIfKnown - * the warning count (if known), otherwise set it to 0. * @param forTruncationOnly * if this method should only scan for data truncation warnings * * @return the SQLWarning chain (or null if no warnings) */ - public SQLWarning convertShowWarningsToSQLWarnings(int warningCountIfKnown, boolean forTruncationOnly) { + public SQLWarning convertShowWarningsToSQLWarnings(boolean forTruncationOnly) { + if (this.warningCount == 0) { + return null; + } + SQLWarning currentWarning = null; ResultsetRows rows = null; @@ -2104,9 +2106,9 @@ public SQLWarning convertShowWarningsToSQLWarnings(int warningCountIfKnown, bool * | Warning | 1265 | Data truncated for column 'field1' at row 1 | * +---------+------+---------------------------------------------+ */ - NativePacketPayload resultPacket = sendCommand(this.commandBuilder.buildComQuery(getSharedSendPacket(), "SHOW WARNINGS"), false, 0); + NativePacketPayload resultPacket = sendCommand(getCommandBuilder().buildComQuery(getSharedSendPacket(), "SHOW WARNINGS"), false, 0); - Resultset warnRs = readAllResults(-1, warningCountIfKnown > 99 /* stream large warning counts */, resultPacket, false, null, + 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; @@ -2209,7 +2211,7 @@ public void configureTimeZone() { } query.append("'"); - sendCommand(this.commandBuilder.buildComQuery(null, query.toString()), false, 0); + sendCommand(getCommandBuilder().buildComQuery(null, query.toString()), false, 0); } } @@ -2217,8 +2219,8 @@ public void configureTimeZone() { public void initServerSession() { configureTimeZone(); - if (this.session.getServerSession().getServerVariables().containsKey("max_allowed_packet")) { - int serverMaxAllowedPacket = this.session.getServerSession().getServerVariable("max_allowed_packet", -1); + if (this.serverSession.getServerVariables().containsKey("max_allowed_packet")) { + int serverMaxAllowedPacket = this.serverSession.getServerVariable("max_allowed_packet", -1); // use server value if maxAllowedPacket hasn't been given, or max_allowed_packet is smaller if (serverMaxAllowedPacket != -1 && (!this.maxAllowedPacket.isExplicitlySet() || serverMaxAllowedPacket < this.maxAllowedPacket.getValue())) { @@ -2241,5 +2243,7 @@ public void initServerSession() { blobSendChunkSize.setValue(allowedBlobSendChunkSize); } } + + this.serverSession.getCharsetSettings().configurePostHandshake(false); } } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSession.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSession.java index 2f51630b7..be5efa266 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSession.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -33,8 +33,7 @@ import java.util.Map; import java.util.TimeZone; -import com.mysql.cj.CharsetMapping; -import com.mysql.cj.Messages; +import com.mysql.cj.CharsetSettings; import com.mysql.cj.ServerVersion; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.PropertySet; @@ -43,7 +42,7 @@ import com.mysql.cj.exceptions.WrongArgumentException; import com.mysql.cj.protocol.ServerCapabilities; import com.mysql.cj.protocol.ServerSession; -import com.mysql.cj.util.StringUtils; +import com.mysql.cj.protocol.ServerSessionStateController; import com.mysql.cj.util.TimeUtil; public class NativeServerSession implements ServerSession { @@ -56,12 +55,13 @@ public class NativeServerSession implements ServerSession { public static final int SERVER_STATUS_CURSOR_EXISTS = 64; public static final int SERVER_STATUS_LAST_ROW_SENT = 128; // The server status for 'last-row-sent' public static final int SERVER_QUERY_WAS_SLOW = 2048; + public static final int SERVER_SESSION_STATE_CHANGED = 1 << 14; // 16384 public static final int CLIENT_LONG_PASSWORD = 0x00000001; /* new more secure passwords */ public static final int CLIENT_FOUND_ROWS = 0x00000002; public static final int CLIENT_LONG_FLAG = 0x00000004; /* Get all column flags */ public static final int CLIENT_CONNECT_WITH_DB = 0x00000008; - public static final int CLIENT_COMPRESS = 0x00000020; /* Can use compression protcol */ + public static final int CLIENT_COMPRESS = 0x00000020; /* Can use compression protocol */ public static final int CLIENT_LOCAL_FILES = 0x00000080; /* Can use LOAD DATA LOCAL */ public static final int CLIENT_PROTOCOL_41 = 0x00000200; // for > 4.1.1 public static final int CLIENT_INTERACTIVE = 0x00000400; @@ -78,38 +78,20 @@ public class NativeServerSession implements ServerSession { public static final int CLIENT_CAN_HANDLE_EXPIRED_PASSWORD = 0x00400000; public static final int CLIENT_SESSION_TRACK = 0x00800000; public static final int CLIENT_DEPRECATE_EOF = 0x01000000; + public static final int CLIENT_QUERY_ATTRIBUTES = 0x08000000; + public static final int CLIENT_MULTI_FACTOR_AUTHENTICATION = 0x10000000; private PropertySet propertySet; private NativeCapabilities capabilities; private int oldStatusFlags = 0; private int statusFlags = 0; - private int serverDefaultCollationIndex; private long clientParam = 0; + private NativeServerSessionStateController serverSessionStateController; /** The map of server variables that we retrieve at connection init. */ private Map serverVariables = new HashMap<>(); - public Map indexToCustomMysqlCharset = null; - - public Map mysqlCharsetToCustomMblen = null; - - /** - * What character set is the metadata returned in? - */ - private String characterSetMetadata = null; - private int metadataCollationIndex; - - /** - * The character set we want results and result metadata returned in (null == - * results in any charset, metadata in UTF-8). - */ - private String characterSetResultsOnServer = null; - - /** - * The (Java) encoding used to interpret error messages received from the server. - * We use character_set_results (since MySQL 5.5) if it is not null or UTF-8 otherwise. - */ - private String errorMessageEncoding = "Cp1252"; // to begin with, changes after we talk to the server + private CharsetSettings charsetSettings; /** Are we in autoCommit mode? */ private boolean autoCommit = true; @@ -124,9 +106,7 @@ public class NativeServerSession implements ServerSession { public NativeServerSession(PropertySet propertySet) { this.propertySet = propertySet; this.cacheDefaultTimeZone = this.propertySet.getBooleanProperty(PropertyKey.cacheDefaultTimeZone); - - // preconfigure some server variables which are consulted before their initialization from server - this.serverVariables.put("character_set_server", "utf8"); + this.serverSessionStateController = new NativeServerSessionStateController(); } @Override @@ -232,27 +212,23 @@ public void setClientParam(long clientParam) { } @Override - public boolean useMultiResults() { - return (this.clientParam & CLIENT_MULTI_RESULTS) != 0 || (this.clientParam & CLIENT_PS_MULTI_RESULTS) != 0; - } - - public boolean isEOFDeprecated() { - return (this.clientParam & CLIENT_DEPRECATE_EOF) != 0; + public boolean hasLongColumnInfo() { + return (this.clientParam & CLIENT_LONG_FLAG) != 0; } @Override - public int getServerDefaultCollationIndex() { - return this.serverDefaultCollationIndex; + public boolean useMultiResults() { + return (this.clientParam & CLIENT_MULTI_RESULTS) != 0 || (this.clientParam & CLIENT_PS_MULTI_RESULTS) != 0; } @Override - public void setServerDefaultCollationIndex(int serverDefaultCollationIndex) { - this.serverDefaultCollationIndex = serverDefaultCollationIndex; + public boolean isEOFDeprecated() { + return (this.clientParam & CLIENT_DEPRECATE_EOF) != 0; } @Override - public boolean hasLongColumnInfo() { - return (this.clientParam & CLIENT_LONG_FLAG) != 0; + public boolean supportsQueryAttributes() { + return (this.clientParam & CLIENT_QUERY_ATTRIBUTES) != 0; } @Override @@ -281,12 +257,6 @@ public void setServerVariables(Map serverVariables) { this.serverVariables = serverVariables; } - public boolean characterSetNamesMatches(String mysqlEncodingName) { - // set names is equivalent to character_set_client ..._results and ..._connection, but we set _results later, so don't check it here. - return (mysqlEncodingName != null && mysqlEncodingName.equalsIgnoreCase(getServerVariable("character_set_client")) - && mysqlEncodingName.equalsIgnoreCase(getServerVariable("character_set_connection"))); - } - public final ServerVersion getServerVersion() { return this.capabilities.getServerVersion(); } @@ -323,159 +293,6 @@ public boolean isSetNeededForAutoCommitMode(boolean autoCommitFlag, boolean elid return true; } - @Override - public String getErrorMessageEncoding() { - return this.errorMessageEncoding; - } - - @Override - public void setErrorMessageEncoding(String errorMessageEncoding) { - this.errorMessageEncoding = errorMessageEncoding; - } - - public String getServerDefaultCharset() { - String charset = null; - if (this.indexToCustomMysqlCharset != null) { - charset = this.indexToCustomMysqlCharset.get(getServerDefaultCollationIndex()); - } - if (charset == null) { - charset = CharsetMapping.getMysqlCharsetNameForCollationIndex(getServerDefaultCollationIndex()); - } - return charset != null ? charset : getServerVariable("character_set_server"); - } - - public int getMaxBytesPerChar(String javaCharsetName) { - return getMaxBytesPerChar(null, javaCharsetName); - } - - public int getMaxBytesPerChar(Integer charsetIndex, String javaCharsetName) { - - String charset = null; - int res = 1; - - // if we can get it by charsetIndex just doing it - - // getting charset name from dynamic maps in connection; we do it before checking against static maps because custom charset on server can be mapped - // to index from our static map key's diapason - if (this.indexToCustomMysqlCharset != null) { - charset = this.indexToCustomMysqlCharset.get(charsetIndex); - } - // checking against static maps if no custom charset found - if (charset == null) { - charset = CharsetMapping.getMysqlCharsetNameForCollationIndex(charsetIndex); - } - - // if we didn't find charset name by index - if (charset == null) { - charset = CharsetMapping.getMysqlCharsetForJavaEncoding(javaCharsetName, getServerVersion()); - } - - // checking against dynamic maps in connection - Integer mblen = null; - if (this.mysqlCharsetToCustomMblen != null) { - mblen = this.mysqlCharsetToCustomMblen.get(charset); - } - - // checking against static maps - if (mblen == null) { - mblen = CharsetMapping.getMblen(charset); - } - - if (mblen != null) { - res = mblen.intValue(); - } - - return res; // we don't know - } - - public String getEncodingForIndex(int charsetIndex) { - String javaEncoding = null; - - String characterEncoding = this.propertySet.getStringProperty(PropertyKey.characterEncoding).getValue(); - - if (charsetIndex != NativeConstants.NO_CHARSET_INFO) { - try { - // getting charset name from dynamic maps in connection; we do it before checking against static maps because custom charset on server can be mapped - // to index from our static map key's diapason - if (this.indexToCustomMysqlCharset != null) { - String cs = this.indexToCustomMysqlCharset.get(charsetIndex); - if (cs != null) { - javaEncoding = CharsetMapping.getJavaEncodingForMysqlCharset(cs, characterEncoding); - } - } - // checking against static maps if no custom charset found - if (javaEncoding == null) { - javaEncoding = CharsetMapping.getJavaEncodingForCollationIndex(charsetIndex, characterEncoding); - } - - } catch (ArrayIndexOutOfBoundsException outOfBoundsEx) { - throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.11", new Object[] { charsetIndex })); - } - - // Punt - if (javaEncoding == null) { - javaEncoding = characterEncoding; - } - } else { - javaEncoding = characterEncoding; - } - - return javaEncoding; - } - - public void configureCharacterSets() { - // - // We need to figure out what character set metadata and error messages will be returned in, and then map them to Java encoding names - // - // We've already set it, and it might be different than what was originally on the server, which is why we use the "special" key to retrieve it - String characterSetResultsOnServerMysql = getServerVariable(LOCAL_CHARACTER_SET_RESULTS); - - if (characterSetResultsOnServerMysql == null || StringUtils.startsWithIgnoreCaseAndWs(characterSetResultsOnServerMysql, "NULL") - || characterSetResultsOnServerMysql.length() == 0) { - String defaultMetadataCharsetMysql = getServerVariable("character_set_system"); - String defaultMetadataCharset = null; - - if (defaultMetadataCharsetMysql != null) { - defaultMetadataCharset = CharsetMapping.getJavaEncodingForMysqlCharset(defaultMetadataCharsetMysql); - } else { - defaultMetadataCharset = "UTF-8"; - } - - this.characterSetMetadata = defaultMetadataCharset; - setErrorMessageEncoding("UTF-8"); - } else { - this.characterSetResultsOnServer = CharsetMapping.getJavaEncodingForMysqlCharset(characterSetResultsOnServerMysql); - this.characterSetMetadata = this.characterSetResultsOnServer; - setErrorMessageEncoding(this.characterSetResultsOnServer); - } - - this.metadataCollationIndex = CharsetMapping.getCollationIndexForJavaEncoding(this.characterSetMetadata, getServerVersion()); - } - - public String getCharacterSetMetadata() { - return this.characterSetMetadata; - } - - public void setCharacterSetMetadata(String characterSetMetadata) { - this.characterSetMetadata = characterSetMetadata; - } - - public int getMetadataCollationIndex() { - return this.metadataCollationIndex; - } - - public void setMetadataCollationIndex(int metadataCollationIndex) { - this.metadataCollationIndex = metadataCollationIndex; - } - - public String getCharacterSetResultsOnServer() { - return this.characterSetResultsOnServer; - } - - public void setCharacterSetResultsOnServer(String characterSetResultsOnServer) { - this.characterSetResultsOnServer = characterSetResultsOnServer; - } - public void preserveOldTransactionState() { this.statusFlags |= this.oldStatusFlags & SERVER_STATUS_IN_TRANS; } @@ -517,16 +334,6 @@ public boolean isServerTruncatesFracSecs() { return sqlModeAsString != null && sqlModeAsString.indexOf("TIME_TRUNCATE_FRACTIONAL") != -1; } - @Override - public long getThreadId() { - return this.capabilities.getThreadId(); - } - - @Override - public void setThreadId(long threadId) { - this.capabilities.setThreadId(threadId); - } - public boolean isAutoCommit() { return this.autoCommit; } @@ -563,4 +370,19 @@ public TimeZone getDefaultTimeZone() { } return TimeZone.getDefault(); } + + @Override + public ServerSessionStateController getServerSessionStateController() { + return this.serverSessionStateController; + } + + @Override + public CharsetSettings getCharsetSettings() { + return this.charsetSettings; + } + + @Override + public void setCharsetSettings(CharsetSettings charsetSettings) { + this.charsetSettings = charsetSettings; + } } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSessionStateController.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSessionStateController.java new file mode 100644 index 000000000..bb8a4991e --- /dev/null +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeServerSessionStateController.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2021, 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 also distributed with certain software (including but not + * limited to OpenSSL) 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 included with MySQL. + * + * 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.protocol.a; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import com.mysql.cj.protocol.ServerSessionStateController; +import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType; +import com.mysql.cj.protocol.a.NativeConstants.StringLengthDataType; +import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType; + +public class NativeServerSessionStateController implements ServerSessionStateController { + private NativeServerSessionStateChanges sessionStateChanges; + private List> listeners; + + @Override + public void setSessionStateChanges(ServerSessionStateChanges changes) { + this.sessionStateChanges = (NativeServerSessionStateChanges) changes; + if (this.listeners != null) { + for (WeakReference wr : this.listeners) { + SessionStateChangesListener l = wr.get(); + if (l != null) { + l.handleSessionStateChanges(changes); + } else { + this.listeners.remove(wr); + } + } + } + } + + @Override + public NativeServerSessionStateChanges getSessionStateChanges() { + return this.sessionStateChanges; + } + + @Override + public void addSessionStateChangesListener(SessionStateChangesListener l) { + if (this.listeners == null) { + this.listeners = new ArrayList<>(); + } + for (WeakReference wr : this.listeners) { + if (l.equals(wr.get())) { + return; + } + } + this.listeners.add(new WeakReference<>(l)); + } + + @Override + public void removeSessionStateChangesListener(SessionStateChangesListener listener) { + if (this.listeners != null) { + for (WeakReference wr : this.listeners) { + SessionStateChangesListener l = wr.get(); + if (l == null || l.equals(listener)) { + this.listeners.remove(wr); + break; + } + } + } + } + + public static class NativeServerSessionStateChanges implements ServerSessionStateChanges { + private List sessionStateChanges = new ArrayList<>(); + + @Override + public List getSessionStateChangesList() { + return this.sessionStateChanges; + } + + public NativeServerSessionStateChanges() { + } + + public NativeServerSessionStateChanges init(NativePacketPayload buf, String encoding) { + int totalLen = (int) buf.readInteger(IntegerDataType.INT_LENENC); + int start = buf.getPosition(); + int end = start + totalLen; + while (totalLen > 0 && end > start) { + int type = (int) buf.readInteger(IntegerDataType.INT1); + NativePacketPayload b = new NativePacketPayload(buf.readBytes(StringSelfDataType.STRING_LENENC)); + switch (type) { + case SESSION_TRACK_SYSTEM_VARIABLES: + this.sessionStateChanges.add(new SessionStateChange(type) // + .addValue(b.readString(StringSelfDataType.STRING_LENENC, encoding)) // + .addValue(b.readString(StringSelfDataType.STRING_LENENC, encoding))); + break; + case SESSION_TRACK_SCHEMA: + case SESSION_TRACK_TRANSACTION_CHARACTERISTICS: + case SESSION_TRACK_TRANSACTION_STATE: + this.sessionStateChanges.add(new SessionStateChange(type) // + .addValue(b.readString(StringSelfDataType.STRING_LENENC, encoding))); + break; + case SESSION_TRACK_GTIDS: + b.readInteger(IntegerDataType.INT1); // skip the byte reserved for the encoding specification, see WL#6128 + this.sessionStateChanges.add(new SessionStateChange(type) // + .addValue(b.readString(StringSelfDataType.STRING_LENENC, encoding))); + break; + case SESSION_TRACK_STATE_CHANGE: + default: + // store the payload as it is + this.sessionStateChanges.add(new SessionStateChange(type) // + .addValue(b.readString(StringLengthDataType.STRING_FIXED, encoding, b.getPayloadLength()))); + } + start = buf.getPosition(); + } + return this; + } + + } +} diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeSocketConnection.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeSocketConnection.java index cfcd9e9ac..84de83057 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeSocketConnection.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeSocketConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -93,8 +93,12 @@ public void connect(String hostName, int portNumber, PropertySet propSet, Except @Override public void performTlsHandshake(ServerSession serverSession) throws SSLParamsException, FeatureNotAvailableException, IOException { + performTlsHandshake(serverSession, null); + } - this.mysqlSocket = this.socketFactory.performTlsHandshake(this, serverSession); + @Override + public void performTlsHandshake(ServerSession serverSession, Log log) throws SSLParamsException, FeatureNotAvailableException, IOException { + this.mysqlSocket = this.socketFactory.performTlsHandshake(this, serverSession, log); this.mysqlInput = new FullReadInputStream( this.propertySet.getBooleanProperty(PropertyKey.useUnbufferedInput).getValue() ? getMysqlSocket().getInputStream() diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/SimplePacketReader.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/SimplePacketReader.java index 2c129ee7c..26efed3cf 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/SimplePacketReader.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/SimplePacketReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -49,6 +49,9 @@ public class SimplePacketReader implements MessageReader maxAllowedPacket) { this.socketConnection = socketConnection; this.maxAllowedPacket = maxAllowedPacket; @@ -56,18 +59,30 @@ public SimplePacketReader(SocketConnection socketConnection, RuntimeProperty this.maxAllowedPacket.getValue()) { throw new CJPacketTooBigException(packetLength, this.maxAllowedPacket.getValue()); } - } catch (IOException | CJPacketTooBigException e) { try { this.socketConnection.forceClose(); @@ -78,38 +93,52 @@ public NativePacketHeader readHeader() throws IOException { } this.readPacketSequence = hdr.getMessageSequence(); - return hdr; } @Override public NativePacketPayload readMessage(Optional reuse, NativePacketHeader header) throws IOException { + if (this.lastMessage == null) { + return readMessageLocal(reuse, header); + } + NativePacketPayload buf = this.lastMessage; + this.lastMessage = null; + return buf; + } + + @Override + public NativePacketPayload probeMessage(Optional reuse, NativePacketHeader header) throws IOException { + this.lastMessage = readMessageLocal(reuse, header); + return this.lastMessage; + } + + private NativePacketPayload readMessageLocal(Optional reuse, NativePacketHeader header) throws IOException { try { int packetLength = header.getMessageSize(); - NativePacketPayload buf; + NativePacketPayload message; if (reuse.isPresent()) { - buf = reuse.get(); + message = reuse.get(); // Set the Buffer to it's original state - buf.setPosition(0); + message.setPosition(0); // Do we need to re-alloc the byte buffer? - if (buf.getByteBuffer().length < packetLength) { + if (message.getByteBuffer().length < packetLength) { // Note: We actually check the length of the buffer, rather than getBufLength(), because getBufLength() // is not necessarily the actual length of the byte array used as the buffer - buf.setByteBuffer(new byte[packetLength]); + message.setByteBuffer(new byte[packetLength]); } // Set the new length - buf.setPayloadLength(packetLength); + message.setPayloadLength(packetLength); } else { - buf = new NativePacketPayload(new byte[packetLength]); + message = new NativePacketPayload(new byte[packetLength]); } // Read the data from the server - int numBytesRead = this.socketConnection.getMysqlInput().readFully(buf.getByteBuffer(), 0, packetLength); + int numBytesRead = this.socketConnection.getMysqlInput().readFully(message.getByteBuffer(), 0, packetLength); if (numBytesRead != packetLength) { throw new IOException(Messages.getString("PacketReader.1", new Object[] { packetLength, numBytesRead })); } - return buf; + return message; } catch (IOException e) { try { diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TextResultsetReader.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TextResultsetReader.java index 43e447b7c..d71512747 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TextResultsetReader.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TextResultsetReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -103,7 +103,8 @@ public Resultset read(int maxRows, boolean streamResults, NativePacketPayload re // check for file request if (columnCount == NativePacketPayload.NULL_LENGTH) { String charEncoding = this.protocol.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(); - String fileName = resultPacket.readString(StringSelfDataType.STRING_TERM, this.protocol.doesPlatformDbCharsetMatches() ? charEncoding : null); + String fileName = resultPacket.readString(StringSelfDataType.STRING_TERM, + this.protocol.getServerSession().getCharsetSettings().doesPlatformDbCharsetMatches() ? charEncoding : null); resultPacket = this.protocol.sendFileToServer(fileName); } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TimeTrackingPacketReader.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TimeTrackingPacketReader.java index c3baec407..f063d9a8a 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TimeTrackingPacketReader.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TimeTrackingPacketReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -52,6 +52,11 @@ public NativePacketHeader readHeader() throws IOException { return this.packetReader.readHeader(); } + @Override + public NativePacketHeader probeHeader() throws IOException { + return this.packetReader.probeHeader(); + } + @Override public NativePacketPayload readMessage(Optional reuse, NativePacketHeader header) throws IOException { NativePacketPayload buf = this.packetReader.readMessage(reuse, header); @@ -59,6 +64,13 @@ public NativePacketPayload readMessage(Optional reuse, Nati return buf; } + @Override + public NativePacketPayload probeMessage(Optional reuse, NativePacketHeader header) throws IOException { + NativePacketPayload buf = this.packetReader.probeMessage(reuse, header); + this.lastPacketReceivedTimeMs = System.currentTimeMillis(); + return buf; + } + @Override public long getLastPacketReceivedTime() { return this.lastPacketReceivedTimeMs; diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TracingPacketReader.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TracingPacketReader.java index 785150529..980148c82 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TracingPacketReader.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/TracingPacketReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -55,8 +55,15 @@ public TracingPacketReader(MessageReader reuse, NativePacketHeader header) throws IOException { - int packetLength = header.getMessageSize(); - NativePacketPayload buf = this.packetReader.readMessage(reuse, header); + return traceMessage(this.packetReader.readMessage(reuse, header), header.getMessageSize(), reuse.isPresent()); + } + + @Override + public NativePacketPayload probeMessage(Optional reuse, NativePacketHeader header) throws IOException { + return traceMessage(this.packetReader.probeMessage(reuse, header), header.getMessageSize(), reuse.isPresent()); + } + private NativePacketPayload traceMessage(NativePacketPayload buf, int packetLength, boolean reuse) throws IOException { StringBuilder traceMessageBuf = new StringBuilder(); - traceMessageBuf.append(Messages.getString(reuse.isPresent() ? "PacketReader.5" : "PacketReader.6")); + traceMessageBuf.append(Messages.getString(reuse ? "PacketReader.5" : "PacketReader.6")); traceMessageBuf.append(StringUtils.dumpAsHex(buf.getByteBuffer(), packetLength < MAX_PACKET_DUMP_LENGTH ? packetLength : MAX_PACKET_DUMP_LENGTH)); if (packetLength > MAX_PACKET_DUMP_LENGTH) { diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/ValueEncoder.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/ValueEncoder.java new file mode 100644 index 000000000..ce9dc9299 --- /dev/null +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/ValueEncoder.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2021, 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 also distributed with certain software (including but not + * limited to OpenSSL) 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 included with MySQL. + * + * 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.protocol.a; + +import static com.mysql.cj.util.StringUtils.getBytes; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +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.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import com.mysql.cj.Messages; +import com.mysql.cj.MysqlType; +import com.mysql.cj.exceptions.ExceptionFactory; +import com.mysql.cj.exceptions.WrongArgumentException; +import com.mysql.cj.protocol.InternalDate; +import com.mysql.cj.protocol.InternalTime; +import com.mysql.cj.protocol.InternalTimestamp; +import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType; +import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType; + +public class ValueEncoder { + private NativePacketPayload packet; + private String characterEncoding; + private TimeZone timezone; + + public ValueEncoder(NativePacketPayload packet, String characterEncoding, TimeZone timezone) { + this.packet = packet; + this.characterEncoding = characterEncoding; + this.timezone = timezone; + } + + public void encodeValue(Object value, int fieldType) { + if (value == null) { + return; + } + switch (fieldType) { + case MysqlType.FIELD_TYPE_TINY: + encodeInt1(asByte(value)); + return; + case MysqlType.FIELD_TYPE_SHORT: + encodeInt2(asShort(value)); + return; + case MysqlType.FIELD_TYPE_LONG: + case MysqlType.FIELD_TYPE_FLOAT: + encodeInt4(asInteger(value)); + return; + case MysqlType.FIELD_TYPE_LONGLONG: + case MysqlType.FIELD_TYPE_DOUBLE: + encodeInt8(asLong(value)); + return; + case MysqlType.FIELD_TYPE_DATE: + encodeDate(asInternalDate(value)); + return; + case MysqlType.FIELD_TYPE_TIME: + encodeTime(asInternalTime(value)); + return; + case MysqlType.FIELD_TYPE_DATETIME: + encodeDateTime(asInternalTimestampNoTz(value)); + return; + case MysqlType.FIELD_TYPE_TIMESTAMP: + encodeTimeStamp(asInternalTimestampTz(value)); + return; + case MysqlType.FIELD_TYPE_STRING: + encodeString(asString(value)); + return; + } + } + + public void encodeInt1(Byte value) { + this.packet.writeInteger(IntegerDataType.INT1, value.longValue()); + } + + public void encodeInt2(Short value) { + this.packet.writeInteger(IntegerDataType.INT2, value.longValue()); + } + + public void encodeInt4(Integer value) { + this.packet.writeInteger(IntegerDataType.INT4, value.longValue()); + } + + public void encodeInt8(Long value) { + this.packet.writeInteger(IntegerDataType.INT8, value.longValue()); + } + + public void encodeDate(InternalDate date) { + this.packet.ensureCapacity(NativeConstants.BIN_LEN_DATE + 1); + this.packet.writeInteger(IntegerDataType.INT1, NativeConstants.BIN_LEN_DATE); + this.packet.writeInteger(IntegerDataType.INT2, date.getYear()); + this.packet.writeInteger(IntegerDataType.INT1, date.getMonth()); + this.packet.writeInteger(IntegerDataType.INT1, date.getDay()); + } + + public void encodeTime(InternalTime time) { + boolean hasFractionalSeconds = time.getNanos() > 0; + this.packet.ensureCapacity((hasFractionalSeconds ? NativeConstants.BIN_LEN_TIME_WITH_MICROS : NativeConstants.BIN_LEN_TIME_NO_FRAC) + 1); + this.packet.writeInteger(IntegerDataType.INT1, hasFractionalSeconds ? NativeConstants.BIN_LEN_TIME_WITH_MICROS : NativeConstants.BIN_LEN_TIME_NO_FRAC); + this.packet.writeInteger(IntegerDataType.INT1, time.isNegative() ? 1 : 0); + this.packet.writeInteger(IntegerDataType.INT4, time.getHours() / 24); + this.packet.writeInteger(IntegerDataType.INT1, time.getHours() % 24); + this.packet.writeInteger(IntegerDataType.INT1, time.getMinutes()); + this.packet.writeInteger(IntegerDataType.INT1, time.getSeconds()); + if (hasFractionalSeconds) { + this.packet.writeInteger(IntegerDataType.INT4, TimeUnit.NANOSECONDS.toMicros(time.getNanos())); + } + } + + public void encodeDateTime(InternalTimestamp timestamp) { + boolean hasFractionalSeconds = timestamp.getNanos() > 0; + this.packet.ensureCapacity((hasFractionalSeconds ? NativeConstants.BIN_LEN_TIMESTAMP_WITH_MICROS : NativeConstants.BIN_LEN_TIMESTAMP_NO_FRAC) + 1); + this.packet.writeInteger(IntegerDataType.INT1, + hasFractionalSeconds ? NativeConstants.BIN_LEN_TIMESTAMP_WITH_MICROS : NativeConstants.BIN_LEN_TIMESTAMP_NO_FRAC); // length + this.packet.writeInteger(IntegerDataType.INT2, timestamp.getYear()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getMonth()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getDay()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getHours()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getMinutes()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getSeconds()); + if (hasFractionalSeconds) { + this.packet.writeInteger(IntegerDataType.INT4, TimeUnit.NANOSECONDS.toMicros(timestamp.getNanos())); + } + } + + public void encodeTimeStamp(InternalTimestamp timestamp) { + this.packet.ensureCapacity(NativeConstants.BIN_LEN_TIMESTAMP_WITH_TZ + 1); + this.packet.writeInteger(IntegerDataType.INT1, NativeConstants.BIN_LEN_TIMESTAMP_WITH_TZ); + this.packet.writeInteger(IntegerDataType.INT2, timestamp.getYear()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getMonth()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getDay()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getHours()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getMinutes()); + this.packet.writeInteger(IntegerDataType.INT1, timestamp.getSeconds()); + this.packet.writeInteger(IntegerDataType.INT4, TimeUnit.NANOSECONDS.toMicros(timestamp.getNanos())); + this.packet.writeInteger(IntegerDataType.INT2, timestamp.getOffset()); + } + + public void encodeString(String value) { + this.packet.writeBytes(StringSelfDataType.STRING_LENENC, getBytes(value, this.characterEncoding)); + } + + private Byte asByte(Object value) { + if (Boolean.class.isInstance(value)) { + return (Boolean) value ? new Byte((byte) 1) : new Byte((byte) 0); + } + + if (Byte.class.isInstance(value)) { + return (Byte) value; + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongTinyIntValueType", new Object[] { value.getClass() })); + } + + private Short asShort(Object value) { + if (Short.class.isInstance(value)) { + return (Short) value; + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongSmallIntValueType", new Object[] { value.getClass() })); + } + + private Integer asInteger(Object value) { + if (Integer.class.isInstance(value)) { + return (Integer) value; + } + + if (Float.class.isInstance(value)) { + return Float.floatToIntBits((Float) value); + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongIntValueType", new Object[] { value.getClass() })); + } + + private Long asLong(Object value) { + if (Long.class.isInstance(value)) { + return (Long) value; + } + + if (Double.class.isInstance(value)) { + return Double.doubleToLongBits((Double) value); + } + + if (BigInteger.class.isInstance(value)) { + return ((BigInteger) value).longValue(); + } + + if (BigDecimal.class.isInstance(value)) { + return Double.doubleToLongBits(((BigDecimal) value).doubleValue()); + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongBigIntValueType", new Object[] { value.getClass() })); + } + + private InternalDate asInternalDate(Object value) { + if (LocalDate.class.isInstance(value)) { + LocalDate localDate = (LocalDate) value; + + InternalDate internalDate = new InternalDate(); + internalDate.setYear(localDate.getYear()); + internalDate.setMonth(localDate.getMonthValue()); + internalDate.setDay(localDate.getDayOfMonth()); + return internalDate; + } + + if (Date.class.isInstance(value)) { + Calendar calendar = Calendar.getInstance(this.timezone); + calendar.setTime((Date) value); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + + InternalDate internalDate = new InternalDate(); + internalDate.setYear(calendar.get(Calendar.YEAR)); + internalDate.setMonth(calendar.get(Calendar.MONTH) + 1); + internalDate.setDay(calendar.get(Calendar.DAY_OF_MONTH)); + return internalDate; + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongDateValueType", new Object[] { value.getClass() })); + } + + private InternalTime asInternalTime(Object value) { + if (LocalTime.class.isInstance(value)) { + LocalTime localTime = (LocalTime) value; + + InternalTime internalTime = new InternalTime(); + internalTime.setHours(localTime.getHour()); + internalTime.setMinutes(localTime.getMinute()); + internalTime.setSeconds(localTime.getSecond()); + internalTime.setNanos(localTime.getNano()); + return internalTime; + } + + if (OffsetTime.class.isInstance(value)) { + OffsetTime offsetTime = (OffsetTime) value; + + InternalTime internalTime = new InternalTime(); + internalTime.setHours(offsetTime.getHour()); + internalTime.setMinutes(offsetTime.getMinute()); + internalTime.setSeconds(offsetTime.getSecond()); + internalTime.setNanos(offsetTime.getNano()); + + return internalTime; + } + + if (Duration.class.isInstance(value)) { + Duration duration = (Duration) value; + Duration durationAbs = duration.abs(); + + long fullSeconds = durationAbs.getSeconds(); + int seconds = (int) (fullSeconds % 60); + long fullMinutes = fullSeconds / 60; + int minutes = (int) (fullMinutes % 60); + long fullHours = fullMinutes / 60; + + InternalTime internalTime = new InternalTime(); + internalTime.setNegative(duration.isNegative()); + internalTime.setHours((int) fullHours); + internalTime.setMinutes(minutes); + internalTime.setSeconds(seconds); + internalTime.setNanos(durationAbs.getNano()); + return internalTime; + } + + if (Time.class.isInstance(value)) { + Time time = (Time) value; + + Calendar calendar = Calendar.getInstance(this.timezone); + calendar.setTime(time); + + InternalTime internalTime = new InternalTime(); + internalTime.setHours(calendar.get(Calendar.HOUR_OF_DAY)); + internalTime.setMinutes(calendar.get(Calendar.MINUTE)); + internalTime.setSeconds(calendar.get(Calendar.SECOND)); + internalTime.setNanos((int) TimeUnit.MILLISECONDS.toNanos(calendar.get(Calendar.MILLISECOND))); + return internalTime; + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongTimeValueType", new Object[] { value.getClass() })); + } + + private InternalTimestamp asInternalTimestampNoTz(Object value) { + if (LocalDateTime.class.isInstance(value)) { + LocalDateTime localDateTime = (LocalDateTime) value; + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(localDateTime.getYear()); + internalTimestamp.setMonth(localDateTime.getMonthValue()); + internalTimestamp.setDay(localDateTime.getDayOfMonth()); + internalTimestamp.setHours(localDateTime.getHour()); + internalTimestamp.setMinutes(localDateTime.getMinute()); + internalTimestamp.setSeconds(localDateTime.getSecond()); + internalTimestamp.setNanos(localDateTime.getNano()); + return internalTimestamp; + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongDatetimeValueType", new Object[] { value.getClass() })); + } + + private InternalTimestamp asInternalTimestampTz(Object value) { + if (Instant.class.isInstance(value)) { + Instant instant = (Instant) value; + OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC); + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(offsetDateTime.getYear()); + internalTimestamp.setMonth(offsetDateTime.getMonthValue()); + internalTimestamp.setDay(offsetDateTime.getDayOfMonth()); + internalTimestamp.setHours(offsetDateTime.getHour()); + internalTimestamp.setMinutes(offsetDateTime.getMinute()); + internalTimestamp.setSeconds(offsetDateTime.getSecond()); + internalTimestamp.setNanos(offsetDateTime.getNano()); + internalTimestamp.setOffset(0); // UTC + return internalTimestamp; + } + + if (OffsetDateTime.class.isInstance(value)) { + OffsetDateTime offsetDateTime = (OffsetDateTime) value; + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(offsetDateTime.getYear()); + internalTimestamp.setMonth(offsetDateTime.getMonthValue()); + internalTimestamp.setDay(offsetDateTime.getDayOfMonth()); + internalTimestamp.setHours(offsetDateTime.getHour()); + internalTimestamp.setMinutes(offsetDateTime.getMinute()); + internalTimestamp.setSeconds(offsetDateTime.getSecond()); + internalTimestamp.setNanos(offsetDateTime.getNano()); + internalTimestamp.setOffset((int) TimeUnit.SECONDS.toMinutes(offsetDateTime.getOffset().getTotalSeconds())); + return internalTimestamp; + } + + if (ZonedDateTime.class.isInstance(value)) { + ZonedDateTime zonedDateTime = (ZonedDateTime) value; + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(zonedDateTime.getYear()); + internalTimestamp.setMonth(zonedDateTime.getMonthValue()); + internalTimestamp.setDay(zonedDateTime.getDayOfMonth()); + internalTimestamp.setHours(zonedDateTime.getHour()); + internalTimestamp.setMinutes(zonedDateTime.getMinute()); + internalTimestamp.setSeconds(zonedDateTime.getSecond()); + internalTimestamp.setNanos(zonedDateTime.getNano()); + internalTimestamp.setOffset((int) TimeUnit.SECONDS.toMinutes(zonedDateTime.getOffset().getTotalSeconds())); + return internalTimestamp; + + } + + if (Calendar.class.isInstance(value)) { + Calendar calendar = (Calendar) value; + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(calendar.get(Calendar.YEAR)); + internalTimestamp.setMonth(calendar.get(Calendar.MONTH) + 1); + internalTimestamp.setDay(calendar.get(Calendar.DAY_OF_MONTH)); + internalTimestamp.setHours(calendar.get(Calendar.HOUR_OF_DAY)); + internalTimestamp.setMinutes(calendar.get(Calendar.MINUTE)); + internalTimestamp.setSeconds(calendar.get(Calendar.SECOND)); + internalTimestamp.setNanos((int) TimeUnit.MILLISECONDS.toNanos(calendar.get(Calendar.MILLISECOND))); + internalTimestamp.setOffset((int) TimeUnit.MILLISECONDS.toMinutes(calendar.getTimeZone().getOffset(calendar.getTimeInMillis()))); + return internalTimestamp; + + } + + if (Timestamp.class.isInstance(value)) { // must be checked before java.util.Date. + Timestamp timestamp = (Timestamp) value; + + Calendar calendar = Calendar.getInstance(this.timezone); + calendar.setTime(timestamp); + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(calendar.get(Calendar.YEAR)); + internalTimestamp.setMonth(calendar.get(Calendar.MONTH) + 1); + internalTimestamp.setDay(calendar.get(Calendar.DAY_OF_MONTH)); + internalTimestamp.setHours(calendar.get(Calendar.HOUR_OF_DAY)); + internalTimestamp.setMinutes(calendar.get(Calendar.MINUTE)); + internalTimestamp.setSeconds(calendar.get(Calendar.SECOND)); + internalTimestamp.setNanos(timestamp.getNanos()); + internalTimestamp.setOffset((int) TimeUnit.MILLISECONDS.toMinutes(calendar.getTimeZone().getOffset(calendar.getTimeInMillis()))); + return internalTimestamp; + } + + if (java.util.Date.class.isInstance(value)) { + java.util.Date date = (java.util.Date) value; + + Calendar calendar = Calendar.getInstance(this.timezone); + calendar.setTime(date); + + InternalTimestamp internalTimestamp = new InternalTimestamp(); + internalTimestamp.setYear(calendar.get(Calendar.YEAR)); + internalTimestamp.setMonth(calendar.get(Calendar.MONTH) + 1); + internalTimestamp.setDay(calendar.get(Calendar.DAY_OF_MONTH)); + internalTimestamp.setHours(calendar.get(Calendar.HOUR_OF_DAY)); + internalTimestamp.setMinutes(calendar.get(Calendar.MINUTE)); + internalTimestamp.setSeconds(calendar.get(Calendar.SECOND)); + internalTimestamp.setNanos((int) TimeUnit.MILLISECONDS.toNanos(calendar.get(Calendar.MILLISECOND))); + internalTimestamp.setOffset((int) TimeUnit.MILLISECONDS.toMinutes(calendar.getTimeZone().getOffset(date.getTime()))); + return internalTimestamp; + } + + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ValueEncoder.WrongTimestampValueType", new Object[] { value.getClass() })); + } + + private String asString(Object value) { + if (String.class.isInstance(value)) { + return (String) value; + } + + return value.toString(); + } +} diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationKerberosClient.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationKerberosClient.java new file mode 100644 index 000000000..c8ad00ba0 --- /dev/null +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationKerberosClient.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2021, 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 also distributed with certain software (including but not + * limited to OpenSSL) 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 included with MySQL. + * + * 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.protocol.a.authentication; + +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +import com.mysql.cj.Messages; +import com.mysql.cj.callback.MysqlCallbackHandler; +import com.mysql.cj.callback.UsernameCallback; +import com.mysql.cj.exceptions.CJException; +import com.mysql.cj.exceptions.ExceptionFactory; +import com.mysql.cj.protocol.AuthenticationPlugin; +import com.mysql.cj.protocol.Protocol; +import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType; +import com.mysql.cj.protocol.a.NativeConstants.StringLengthDataType; +import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType; +import com.mysql.cj.protocol.a.NativePacketPayload; +import com.mysql.cj.util.StringUtils; + +/** + * MySQL 'authentication_kerberos_client' authentication plugin. + */ +public class AuthenticationKerberosClient implements AuthenticationPlugin { + public static String PLUGIN_NAME = "authentication_kerberos_client"; + + private static final String LOGIN_CONFIG_ENTRY = "MySQLConnectorJ"; + private static final String AUTHENTICATION_MECHANISM = "GSSAPI"; + + private String sourceOfAuthData = PLUGIN_NAME; + + private MysqlCallbackHandler usernameCallbackHandler = null; + private String user = null; + private String password = null; + private String userPrincipalName = null; + + private Subject subject = null; + private String cachedPrincipalName = null; + + private CallbackHandler credentialsCallbackHandler = (cbs) -> { + for (Callback cb : cbs) { + if (NameCallback.class.isAssignableFrom(cb.getClass())) { + ((NameCallback) cb).setName(this.userPrincipalName); + } else if (PasswordCallback.class.isAssignableFrom(cb.getClass())) { + ((PasswordCallback) cb).setPassword(this.password == null ? new char[0] : this.password.toCharArray()); + } else { + throw new UnsupportedCallbackException(cb, cb.getClass().getName()); + } + } + }; + + private SaslClient saslClient = null; + + @Override + public void init(Protocol prot, MysqlCallbackHandler cbh) { + this.usernameCallbackHandler = cbh; + } + + @Override + public void reset() { + if (this.saslClient != null) { + try { + this.saslClient.dispose(); + } catch (SaslException e) { + // Ignore exception. + } + } + this.user = null; + this.password = null; + this.saslClient = null; + } + + @Override + public void destroy() { + reset(); + this.userPrincipalName = null; + this.subject = null; + this.cachedPrincipalName = null; + } + + @Override + public String getProtocolPluginName() { + return PLUGIN_NAME; + } + + @Override + public boolean requiresConfidentiality() { + return false; + } + + @Override + public boolean isReusable() { + return false; + } + + @Override + public void setAuthenticationParameters(String user, String password) { + this.user = user; + this.password = password; + + if (this.user == null) { + try { + // Try to obtain the user name from a cached TGT. + initializeAuthentication(); + int pos = this.cachedPrincipalName.indexOf('@'); + if (pos >= 0) { + this.user = this.cachedPrincipalName.substring(0, pos); + } else { + this.user = this.cachedPrincipalName; + } + } catch (CJException e) { + // Fall-back to system login user. + this.user = System.getProperty("user.name"); + } + if (this.usernameCallbackHandler != null) { + this.usernameCallbackHandler.handle(new UsernameCallback(this.user)); + } + } + } + + @Override + public void setSourceOfAuthData(String sourceOfAuthData) { + this.sourceOfAuthData = sourceOfAuthData; + } + + @Override + public boolean nextAuthenticationStep(NativePacketPayload fromServer, List toServer) { + toServer.clear(); + + if (!this.sourceOfAuthData.equals(PLUGIN_NAME) || fromServer.getPayloadLength() == 0) { + // Cannot do anything with whatever payload comes from the server, so just skip this iteration and wait for a Protocol::AuthSwitchRequest or a + // Protocol::AuthNextFactor. + return true; + } + + if (this.saslClient == null) { + try { + // Protocol::AuthSwitchRequest plugin data contains: + // int<2> SPN string length + // string SPN string + // int<2> User Principal Name realm string length + // string User Principal Name realm string + int servicePrincipalNameLength = (int) fromServer.readInteger(IntegerDataType.INT2); + String servicePrincipalName = fromServer.readString(StringLengthDataType.STRING_VAR, "ASCII", servicePrincipalNameLength); + // A typical Kerberos V5 principal has the structure "primary/instance@REALM". + String primary = ""; + String instance = ""; + // Being a bit lenient here: the spec allows escaping characters (https://tools.ietf.org/html/rfc1964#section-2.1.1). + int posAt = servicePrincipalName.indexOf('@'); + if (posAt < 0) { + posAt = servicePrincipalName.length(); + } + int posSlash = servicePrincipalName.lastIndexOf('/', posAt); + if (posSlash >= 0) { + primary = servicePrincipalName.substring(0, posSlash); + instance = servicePrincipalName.substring(posSlash + 1, posAt); + } else { + primary = servicePrincipalName.substring(0, posAt); + } + + int userPrincipalRealmLength = (int) fromServer.readInteger(IntegerDataType.INT2); + String userPrincipalRealm = fromServer.readString(StringLengthDataType.STRING_VAR, "ASCII", userPrincipalRealmLength); + this.userPrincipalName = this.user + "@" + userPrincipalRealm; + + initializeAuthentication(); + + // Create a GSSAPI SASL client using the credentials stored in this thread's Subject. + try { + final String localPrimary = primary; + final String localInstance = instance; + this.saslClient = Subject.doAs(this.subject, (PrivilegedExceptionAction) () -> Sasl + .createSaslClient(new String[] { AUTHENTICATION_MECHANISM }, null, localPrimary, localInstance, null, null)); + } catch (PrivilegedActionException e) { + // SaslException is the only checked exception that can be thrown. + throw (SaslException) e.getException(); + } + + } catch (SaslException e) { + throw ExceptionFactory.createException( + Messages.getString("AuthenticationKerberosClientPlugin.FailCreateSaslClient", new Object[] { AUTHENTICATION_MECHANISM }), e); + } + + if (this.saslClient == null) { + throw ExceptionFactory.createException( + Messages.getString("AuthenticationKerberosClientPlugin.FailCreateSaslClient", new Object[] { AUTHENTICATION_MECHANISM })); + } + } + + if (!this.saslClient.isComplete()) { + // All packets: send payload to the SASL client. + try { + Subject.doAs(this.subject, (PrivilegedExceptionAction) () -> { + byte[] response = this.saslClient.evaluateChallenge(fromServer.readBytes(StringSelfDataType.STRING_EOF)); + if (response != null) { + NativePacketPayload bresp = new NativePacketPayload(response); + bresp.setPosition(0); + toServer.add(bresp); + } + return null; + }); + } catch (PrivilegedActionException e) { + throw ExceptionFactory.createException( + Messages.getString("AuthenticationKerberosClientPlugin.ErrProcessingAuthIter", new Object[] { AUTHENTICATION_MECHANISM }), + e.getException()); + } + } + return true; + } + + private void initializeAuthentication() { + if (this.subject != null && this.cachedPrincipalName != null && this.cachedPrincipalName.equals(this.userPrincipalName)) { + // Already initialized with the right user. + return; + } + + // In-memory login configuration. Used only if system property 'java.security.auth.login.config' is not set. + String loginConfigFile = System.getProperty("java.security.auth.login.config"); + Configuration loginConfig = null; + if (StringUtils.isNullOrEmpty(loginConfigFile)) { + final String localUser = this.userPrincipalName; + final boolean debug = Boolean.getBoolean("sun.security.jgss.debug"); + loginConfig = new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + Map options = new HashMap<>(); + options.put("useTicketCache", "true"); + options.put("renewTGT", "false"); + if (localUser != null) { + options.put("principal", localUser); + } + options.put("debug", Boolean.toString(debug)); // Hook debugging on system property 'sun.security.jgss.debug'. + return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; + } + }; + } + + // Login into Kerberos service and obtain the user subject/credentials. + LoginContext loginContext; + try { + loginContext = new LoginContext(LOGIN_CONFIG_ENTRY, null, this.credentialsCallbackHandler, loginConfig); + loginContext.login(); + this.subject = loginContext.getSubject(); + this.cachedPrincipalName = this.subject.getPrincipals().iterator().next().getName(); + } catch (LoginException e) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationKerberosClientPlugin.FailAuthenticateUser"), e); + } + } +} diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationLdapSaslClientPlugin.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationLdapSaslClientPlugin.java index 89f14582d..046f41960 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationLdapSaslClientPlugin.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/AuthenticationLdapSaslClientPlugin.java @@ -52,6 +52,8 @@ import javax.security.sasl.SaslException; import com.mysql.cj.Messages; +import com.mysql.cj.callback.MysqlCallbackHandler; +import com.mysql.cj.callback.UsernameCallback; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJException; import com.mysql.cj.exceptions.ExceptionFactory; @@ -78,8 +80,8 @@ private enum AuthenticationMechanisms { SCRAM_SHA_256(ScramSha256SaslClient.IANA_MECHANISM_NAME, ScramSha256SaslClient.MECHANISM_NAME), // GSSAPI("GSSAPI", "GSSAPI"); - private String mechName; - private String saslServiceName; + private String mechName = null; + private String saslServiceName = null; private AuthenticationMechanisms(String mechName, String serviceName) { this.mechName = mechName; @@ -105,11 +107,12 @@ String getSaslServiceName() { } private Protocol protocol = null; - private String user; - private String password; + private MysqlCallbackHandler usernameCallbackHandler = null; + private String user = null; + private String password = null; - private AuthenticationMechanisms authMech; - private SaslClient saslClient; + private AuthenticationMechanisms authMech = null; + private SaslClient saslClient = null; private Subject subject = null; private boolean firstPass = true; @@ -135,6 +138,12 @@ public void init(Protocol prot) { Security.addProvider(new ScramShaSaslProvider()); } + @Override + public void init(Protocol prot, MysqlCallbackHandler cbh) { + init(prot); + this.usernameCallbackHandler = cbh; + } + @Override public void reset() { if (this.saslClient != null) { @@ -177,6 +186,14 @@ public boolean isReusable() { public void setAuthenticationParameters(String user, String password) { this.user = user; this.password = password; + + if (this.user == null) { + // Fall-back to system login user. + this.user = System.getProperty("user.name"); + if (this.usernameCallbackHandler != null) { + this.usernameCallbackHandler.handle(new UsernameCallback(this.user)); + } + } } @Override @@ -192,8 +209,7 @@ public boolean nextAuthenticationStep(NativePacketPayload fromServer, List { + public static String PLUGIN_NAME = "authentication_oci_client"; + + private String sourceOfAuthData = PLUGIN_NAME; + + protected Protocol protocol = null; + private MysqlCallbackHandler usernameCallbackHandler = null; + private String fingerprint = null; + private RSAPrivateKey privateKey = null; + + @Override + public void init(Protocol prot, MysqlCallbackHandler cbh) { + this.protocol = prot; + this.usernameCallbackHandler = cbh; + } + + @Override + public void reset() { + this.fingerprint = null; + this.privateKey = null; + } + + @Override + public void destroy() { + reset(); + } + + @Override + public String getProtocolPluginName() { + return PLUGIN_NAME; + } + + @Override + public boolean requiresConfidentiality() { + return false; + } + + @Override + public boolean isReusable() { + return false; + } + + @Override + public void setAuthenticationParameters(String user, String password) { + if (user == null && this.usernameCallbackHandler != null) { + // Fall-back to system login user. + this.usernameCallbackHandler.handle(new UsernameCallback(System.getProperty("user.name"))); + } + } + + @Override + public void setSourceOfAuthData(String sourceOfAuthData) { + this.sourceOfAuthData = sourceOfAuthData; + } + + @Override + public boolean nextAuthenticationStep(NativePacketPayload fromServer, List toServer) { + toServer.clear(); + + if (!this.sourceOfAuthData.equals(PLUGIN_NAME) || fromServer.getPayloadLength() == 0) { + // Cannot do anything with whatever payload comes from the server, so just skip this iteration and wait for a Protocol::AuthSwitchRequest or a + // Protocol::AuthNextFactor. + toServer.add(new NativePacketPayload(0)); + return true; + } + + initializePrivateKey(); + + byte[] nonce = fromServer.readBytes(StringSelfDataType.STRING_EOF); + byte[] signature = ExportControlled.sign(nonce, this.privateKey); + if (signature == null) { + signature = new byte[0]; + } + String payload = String.format("{\"fingerprint\":\"%s\", \"signature\":\"%s\"}", this.fingerprint, Base64.getEncoder().encodeToString(signature)); + toServer.add(new NativePacketPayload(payload.getBytes(Charset.defaultCharset()))); + return true; + } + + private void initializePrivateKey() { + if (this.privateKey != null) { + // Already initialized. + return; + } + + ConfigFile configFile; + try { + String configFilePath = this.protocol.getPropertySet().getStringProperty(PropertyKey.ociConfigFile.getKeyName()).getStringValue(); + if (StringUtils.isNullOrEmpty(configFilePath)) { + configFile = ConfigFileReader.parseDefault(); + } else if (Files.exists(Paths.get(configFilePath))) { + configFile = ConfigFileReader.parse(configFilePath); + } else { + throw ExceptionFactory.createException("configuration file does not exist"); + } + } catch (NoClassDefFoundError e) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationOciClientPlugin.SdkNotFound"), e); + } catch (IOException e) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationOciClientPlugin.OciConfigFileError"), e); + } + this.fingerprint = configFile.get("fingerprint"); + if (StringUtils.isNullOrEmpty(this.fingerprint)) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationOciClientPlugin.OciConfigFileMissingEntry")); + } + String keyFilePath = configFile.get("key_file"); + if (StringUtils.isNullOrEmpty(keyFilePath)) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationOciClientPlugin.OciConfigFileMissingEntry")); + } + + try { + String key = new String(Files.readAllBytes(Paths.get(keyFilePath)), Charset.defaultCharset()); + this.privateKey = ExportControlled.decodeRSAPrivateKey(key); + } catch (IOException e) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationOciClientPlugin.PrivateKeyNotFound"), e); + } catch (RSAException | IllegalArgumentException e) { + throw ExceptionFactory.createException(Messages.getString("AuthenticationOciClientPlugin.PrivateKeyNotValid"), e); + } + } +} diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/CachingSha2PasswordPlugin.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/CachingSha2PasswordPlugin.java index 9e1947f99..ba94b5759 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/CachingSha2PasswordPlugin.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/CachingSha2PasswordPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. + * Copyright (c) 2017, 2021, 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 @@ -91,8 +91,9 @@ public boolean nextAuthenticationStep(NativePacketPayload fromServer, List NativeConstants.SEED_LENGTH) { + if (this.publicKeyRequested && fromServer.getPayloadLength() > NativeConstants.SEED_LENGTH + 1) { // auth data is null terminated // Servers affected by Bug#70865 could send Auth Switch instead of key after Public Key Retrieval, // so we check payload length to detect that. diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/MysqlClearPasswordPlugin.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/MysqlClearPasswordPlugin.java index 39cf026d9..6428386e7 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/MysqlClearPasswordPlugin.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/authentication/MysqlClearPasswordPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020, Oracle and/or its affiliates. + * Copyright (c) 2012, 2021, 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 @@ -45,8 +45,8 @@ public class MysqlClearPasswordPlugin implements AuthenticationPlugin { public static String PLUGIN_NAME = "mysql_clear_password"; - private Protocol protocol; - private MysqlCallbackHandler usernameCallbackHandler; + private Protocol protocol = null; + private MysqlCallbackHandler usernameCallbackHandler = null; private String password = null; @Override @@ -73,7 +73,7 @@ public boolean isReusable() { public void setAuthenticationParameters(String user, String password) { this.password = password; - if (user == null) { + if (user == null && this.usernameCallbackHandler != null) { // Fall-back to system login user. this.usernameCallbackHandler.handle(new UsernameCallback(System.getProperty("user.name"))); } @@ -82,7 +82,7 @@ public void setAuthenticationParameters(String user, String password) { public boolean nextAuthenticationStep(NativePacketPayload fromServer, List toServer) { toServer.clear(); - String encoding = this.protocol.versionMeetsMinimum(5, 7, 6) ? this.protocol.getPasswordCharacterEncoding() : "UTF-8"; + String encoding = this.protocol.getServerSession().getCharsetSettings().getPasswordCharacterEncoding(); NativePacketPayload bresp = new NativePacketPayload(StringUtils.getBytes(this.password != null ? this.password : "", encoding)); bresp.setPosition(bresp.getPayloadLength()); @@ -92,5 +92,4 @@ public boolean nextAuthenticationStep(NativePacketPayload fromServer, List { public static String PLUGIN_NAME = "mysql_native_password"; - private Protocol protocol; - private MysqlCallbackHandler usernameCallbackHandler; + private Protocol protocol = null; + private MysqlCallbackHandler usernameCallbackHandler = null; private String password = null; @Override @@ -73,14 +73,13 @@ public boolean isReusable() { public void setAuthenticationParameters(String user, String password) { this.password = password; - if (user == null) { + if (user == null && this.usernameCallbackHandler != null) { // Fall-back to system login user. this.usernameCallbackHandler.handle(new UsernameCallback(System.getProperty("user.name"))); } } public boolean nextAuthenticationStep(NativePacketPayload fromServer, List toServer) { - toServer.clear(); NativePacketPayload bresp = null; @@ -90,12 +89,11 @@ public boolean nextAuthenticationStep(NativePacketPayload fromServer, List { public static String PLUGIN_NAME = "mysql_old_password"; - private Protocol protocol; - private MysqlCallbackHandler usernameCallbackHandler; + private Protocol protocol = null; + private MysqlCallbackHandler usernameCallbackHandler = null; private String password = null; @Override @@ -75,7 +75,7 @@ public boolean isReusable() { public void setAuthenticationParameters(String user, String password) { this.password = password; - if (user == null) { + if (user == null && this.usernameCallbackHandler != null) { // Fall-back to system login user. this.usernameCallbackHandler.handle(new UsernameCallback(System.getProperty("user.name"))); } @@ -92,8 +92,8 @@ public boolean nextAuthenticationStep(NativePacketPayload fromServer, List { public static String PLUGIN_NAME = "sha256_password"; - protected Protocol protocol; - protected MysqlCallbackHandler usernameCallbackHandler; + protected Protocol protocol = null; + protected MysqlCallbackHandler usernameCallbackHandler = null; protected String password = null; protected String seed = null; protected boolean publicKeyRequested = false; @@ -68,9 +68,9 @@ public class Sha256PasswordPlugin implements AuthenticationPlugin serverRSAPublicKeyFile = null; @Override - public void init(Protocol prot, MysqlCallbackHandler mch) { + public void init(Protocol prot, MysqlCallbackHandler cbh) { this.protocol = prot; - this.usernameCallbackHandler = mch; + this.usernameCallbackHandler = cbh; this.serverRSAPublicKeyFile = this.protocol.getPropertySet().getStringProperty(PropertyKey.serverRSAPublicKeyFile); String pkURL = this.serverRSAPublicKeyFile.getValue(); @@ -99,7 +99,7 @@ public boolean isReusable() { public void setAuthenticationParameters(String user, String password) { this.password = password; - if (user == null) { + if (user == null && this.usernameCallbackHandler != null) { // Fall-back to system login user. this.usernameCallbackHandler.handle(new UsernameCallback(System.getProperty("user.name"))); } @@ -117,7 +117,8 @@ public boolean nextAuthenticationStep(NativePacketPayload fromServer, List NativeConstants.SEED_LENGTH) { + if (this.publicKeyRequested && fromServer.getPayloadLength() > NativeConstants.SEED_LENGTH + 1) { // auth data is null terminated // Servers affected by Bug#70865 could send Auth Switch instead of key after Public Key Retrieval, // so we check payload length to detect that. @@ -167,7 +168,9 @@ protected byte[] encryptPassword() { protected byte[] encryptPassword(String transformation) { byte[] input = null; - input = this.password != null ? StringUtils.getBytesNullTerminated(this.password, this.protocol.getPasswordCharacterEncoding()) : new byte[] { 0 }; + input = this.password != null + ? StringUtils.getBytesNullTerminated(this.password, this.protocol.getServerSession().getCharsetSettings().getPasswordCharacterEncoding()) + : new byte[] { 0 }; byte[] mysqlScrambleBuff = new byte[input.length]; Security.xorString(input, mysqlScrambleBuff, this.seed.getBytes(), input.length); return ExportControlled.encryptWithRSAPublicKey(mysqlScrambleBuff, ExportControlled.decodeRSAPublicKey(this.publicKeyString), transformation); diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/OkPacket.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/OkPacket.java index 87d292ccc..6924902b2 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/OkPacket.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/a/result/OkPacket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -29,20 +29,24 @@ package com.mysql.cj.protocol.a.result; +import static com.mysql.cj.protocol.a.NativeServerSession.SERVER_SESSION_STATE_CHANGED; + import com.mysql.cj.protocol.ProtocolEntity; import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType; import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType; import com.mysql.cj.protocol.a.NativePacketPayload; +import com.mysql.cj.protocol.a.NativeServerSessionStateController.NativeServerSessionStateChanges; public class OkPacket implements ProtocolEntity { - private long updateCount = -1; private long updateID = -1; private int statusFlags = 0; private int warningCount = 0; private String info = null; + private NativeServerSessionStateChanges sessionStateChanges; - public OkPacket() { + private OkPacket() { + this.sessionStateChanges = new NativeServerSessionStateChanges(); } public static OkPacket parse(NativePacketPayload buf, String errorMessageEncoding) { @@ -56,6 +60,11 @@ public static OkPacket parse(NativePacketPayload buf, String errorMessageEncodin ok.setStatusFlags((int) buf.readInteger(IntegerDataType.INT2)); ok.setWarningCount((int) buf.readInteger(IntegerDataType.INT2)); ok.setInfo(buf.readString(StringSelfDataType.STRING_TERM, errorMessageEncoding)); // info + + // read session state changes info + if ((ok.getStatusFlags() & SERVER_SESSION_STATE_CHANGED) > 0) { + ok.sessionStateChanges.init(buf, errorMessageEncoding); + } return ok; } @@ -98,4 +107,9 @@ public int getWarningCount() { public void setWarningCount(int warningCount) { this.warningCount = warningCount; } -} + + public NativeServerSessionStateChanges getSessionStateChanges() { + return this.sessionStateChanges; + } + +} \ No newline at end of file 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 a244682d9..a0fd6035e 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -76,7 +76,7 @@ public class ResultsetRowsCursor extends AbstractResultsetRows implements Result */ private boolean firstFetchCompleted = false; - protected NativeMessageBuilder commandBuilder = new NativeMessageBuilder(); // TODO use shared builder + protected NativeMessageBuilder commandBuilder = null; /** * Creates a new cursor-backed row provider. @@ -91,6 +91,7 @@ public ResultsetRowsCursor(NativeProtocol ioChannel, ColumnDefinition columnDefi this.metadata = columnDefinition; this.protocol = ioChannel; this.rowFactory = new BinaryRowFactory(this.protocol, this.metadata, Concurrency.READ_ONLY, false); + this.commandBuilder = new NativeMessageBuilder(this.protocol.getServerSession().supportsQueryAttributes()); } @Override 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 db3cd3aaf..9cba95947 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 @@ -77,7 +77,7 @@ public class ResultsetRowsStreaming extends AbstractRe private ProtocolEntityFactory resultSetFactory; - private NativeMessageBuilder commandBuilder = new NativeMessageBuilder(); // TODO use shared builder + private NativeMessageBuilder commandBuilder = null; /** * Creates a new RowDataDynamic object. @@ -100,6 +100,7 @@ public ResultsetRowsStreaming(NativeProtocol io, ColumnDefinition columnDefiniti this.resultSetFactory = resultSetFactory; this.rowFactory = this.isBinaryEncoded ? new BinaryRowFactory(this.protocol, this.metadata, Concurrency.READ_ONLY, true) : new TextRowFactory(this.protocol, this.metadata, Concurrency.READ_ONLY, true); + this.commandBuilder = new NativeMessageBuilder(this.protocol.getServerSession().supportsQueryAttributes()); } @Override diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/FieldFactory.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/FieldFactory.java index 319591b5d..7e156489c 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/FieldFactory.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/FieldFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. + * Copyright (c) 2018, 2021, 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 @@ -100,7 +100,7 @@ private Field columnMetaDataToField(ColumnMetaData col, String characterSet) { collationIndex = (int) col.getCollation(); } - String encoding = CharsetMapping.getJavaEncodingForCollationIndex(collationIndex); + String encoding = CharsetMapping.getStaticJavaEncodingForCollationIndex(collationIndex); MysqlType mysqlType = findMysqlType(col.getType(), col.getContentType(), col.getFlags(), collationIndex); int mysqlTypeId = xProtocolTypeToMysqlType(col.getType(), col.getContentType()); @@ -174,7 +174,7 @@ private MysqlType findMysqlType(FieldType type, int contentType, int flags, int case XPROTOCOL_COLUMN_BYTES_CONTENT_TYPE_JSON: return MysqlType.JSON; default: - if (collationIndex == 33) { + if (collationIndex == 33) { // TODO what if other utf8 or utf8mb4 collation ? return MysqlType.VARBINARY; } return MysqlType.VARCHAR; diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/Notice.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/Notice.java index ab10533f3..322e917fc 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/Notice.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/Notice.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. + * Copyright (c) 2018, 2021, 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 @@ -50,23 +50,21 @@ public class Notice implements ProtocolEntity { public static Notice getInstance(XMessage message) { Frame notice = (Frame) message.getMessage(); - if (notice.getScope() != Frame.Scope.GLOBAL) { // TODO should we handle global notices somehow? What frame types are applicable there? - switch (notice.getType()) { - case Frame.Type.WARNING_VALUE: - return new XWarning(notice); - - case Frame.Type.SESSION_VARIABLE_CHANGED_VALUE: - return new XSessionVariableChanged(notice); - - case Frame.Type.SESSION_STATE_CHANGED_VALUE: - return new XSessionStateChanged(notice); - - case Frame.Type.GROUP_REPLICATION_STATE_CHANGED_VALUE: - // TODO - break; - default: - break; - } + switch (notice.getType()) { + case Frame.Type.WARNING_VALUE: + return new XWarning(notice); + + case Frame.Type.SESSION_VARIABLE_CHANGED_VALUE: + return new XSessionVariableChanged(notice); + + case Frame.Type.SESSION_STATE_CHANGED_VALUE: + return new XSessionStateChanged(notice); + + case Frame.Type.GROUP_REPLICATION_STATE_CHANGED_VALUE: + // TODO + break; + default: + break; } return new Notice(notice); } diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XAuthenticationProvider.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XAuthenticationProvider.java index 56a965c44..0af197f65 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XAuthenticationProvider.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. + * Copyright (c) 2018, 2021, 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 @@ -43,7 +43,6 @@ import com.mysql.cj.exceptions.WrongArgumentException; import com.mysql.cj.protocol.AuthenticationProvider; import com.mysql.cj.protocol.Protocol; -import com.mysql.cj.protocol.ServerSession; import com.mysql.cj.util.StringUtils; import com.mysql.cj.xdevapi.XDevAPIError; @@ -59,12 +58,12 @@ public void init(Protocol prot, PropertySet propertySet, ExceptionInte } @Override - public void connect(ServerSession serverSession, String userName, String password, String database) { - changeUser(serverSession, userName, password, database); + public void connect(String userName, String password, String database) { + changeUser(userName, password, database); } @Override - public void changeUser(ServerSession serverSession, String userName, String password, String database) { + public void changeUser(String userName, String password, String database) { boolean overTLS = ((XServerCapabilities) this.protocol.getServerSession().getCapabilities()).getTls(); RuntimeProperty authMechProp = this.protocol.getPropertySet().getEnumProperty(PropertyKey.xdevapiAuth); List tryAuthMech; @@ -139,8 +138,4 @@ public void changeUser(ServerSession serverSession, String userName, String pass this.protocol.afterHandshake(); } - @Override - public String getEncodingForHandshake() { - return null; // TODO - } } 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 330bb807d..722ddc22e 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -49,7 +49,6 @@ import java.util.function.Consumer; import com.google.protobuf.GeneratedMessageV3; -import com.mysql.cj.CharsetMapping; import com.mysql.cj.Constants; import com.mysql.cj.Messages; import com.mysql.cj.QueryResult; @@ -74,6 +73,8 @@ import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.exceptions.SSLParamsException; import com.mysql.cj.exceptions.WrongArgumentException; +import com.mysql.cj.log.Log; +import com.mysql.cj.log.LogFactory; import com.mysql.cj.protocol.AbstractProtocol; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.ExportControlled; @@ -144,22 +145,6 @@ public class XProtocol extends AbstractProtocol implements Protocol, ProtocolEntityFactory> messageToProtocolEntityFactory = new HashMap<>(); - public XProtocol(String host, int port, String defaultSchema, PropertySet propertySet) { - - this.defaultSchemaName = defaultSchema; - - // Override common connectTimeout with xdevapi.connect-timeout to provide unified logic in StandardSocketFactory - RuntimeProperty connectTimeout = propertySet.getIntegerProperty(PropertyKey.connectTimeout); - RuntimeProperty xdevapiConnectTimeout = propertySet.getIntegerProperty(PropertyKey.xdevapiConnectTimeout); - if (xdevapiConnectTimeout.isExplicitlySet() || !connectTimeout.isExplicitlySet()) { - connectTimeout.setValue(xdevapiConnectTimeout.getValue()); - } - - SocketConnection socketConn = new NativeSocketConnection(); - socketConn.connect(host, port, propertySet, null, null, 0); - init(null, socketConn, propertySet, null); - } - public XProtocol(HostInfo hostInfo, PropertySet propertySet) { String host = hostInfo.getHost(); if (host == null || StringUtils.isEmptyOrWhitespaceOnly(host)) { @@ -187,6 +172,9 @@ public XProtocol(HostInfo hostInfo, PropertySet propertySet) { public void init(Session sess, SocketConnection socketConn, PropertySet propSet, TransactionEventHandler trManager) { super.init(sess, socketConn, propSet, trManager); + // Session is not kept, so we need to do this + this.log = LogFactory.getLogger(getPropertySet().getStringProperty(PropertyKey.logger).getStringValue(), Log.LOGGER_INSTANCE_NAME); + this.messageBuilder = new XMessageBuilder(); this.authProvider = new XAuthenticationProvider(); @@ -237,7 +225,7 @@ public void negotiateSSLConnection() { sendCapabilities(tlsCapabilities); try { - this.socketConnection.performTlsHandshake(null); //(this.serverSession); + this.socketConnection.performTlsHandshake(null, this.log); } catch (SSLParamsException | FeatureNotAvailableException | IOException e) { throw new CJCommunicationsException(e); } @@ -378,28 +366,21 @@ public void beforeHandshake() { } RuntimeProperty xdevapiTlsVersions = this.propertySet.getStringProperty(PropertyKey.xdevapiTlsVersions); - RuntimeProperty jdbcEnabledTlsProtocols = this.propertySet.getStringProperty(PropertyKey.enabledTLSProtocols); + RuntimeProperty jdbcEnabledTlsProtocols = this.propertySet.getStringProperty(PropertyKey.tlsVersions); if (xdevapiTlsVersions.isExplicitlySet()) { if (sslMode.getValue() == SslMode.DISABLED) { throw ExceptionFactory.createException(WrongArgumentException.class, "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled."); } - if (xdevapiTlsVersions.getValue().trim().isEmpty()) { - throw ExceptionFactory.createException(WrongArgumentException.class, - "At least one TLS protocol version must be specified in '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' list."); - } String[] tlsVersions = xdevapiTlsVersions.getValue().split("\\s*,\\s*"); List tryProtocols = Arrays.asList(tlsVersions); ExportControlled.checkValidProtocols(tryProtocols); jdbcEnabledTlsProtocols.setValue(xdevapiTlsVersions.getValue()); - - } else if (!jdbcEnabledTlsProtocols.isExplicitlySet()) { - jdbcEnabledTlsProtocols.setValue(xdevapiTlsVersions.getValue()); } RuntimeProperty xdevapiTlsCiphersuites = this.propertySet.getStringProperty(PropertyKey.xdevapiTlsCiphersuites); - RuntimeProperty jdbcEnabledSslCipherSuites = this.propertySet.getStringProperty(PropertyKey.enabledSSLCipherSuites); + RuntimeProperty jdbcEnabledSslCipherSuites = this.propertySet.getStringProperty(PropertyKey.tlsCiphersuites); if (xdevapiTlsCiphersuites.isExplicitlySet()) { if (sslMode.getValue() == SslMode.DISABLED) { throw ExceptionFactory.createException(WrongArgumentException.class, @@ -407,9 +388,6 @@ public void beforeHandshake() { } jdbcEnabledSslCipherSuites.setValue(xdevapiTlsCiphersuites.getValue()); - - } else if (!jdbcEnabledSslCipherSuites.isExplicitlySet()) { - jdbcEnabledSslCipherSuites.setValue(xdevapiTlsCiphersuites.getValue()); } boolean verifyServerCert = sslMode.getValue() == SslMode.VERIFY_CA || sslMode.getValue() == SslMode.VERIFY_IDENTITY; @@ -437,7 +415,7 @@ public void beforeHandshake() { } } - if (xdevapiSslMode.getValue() != XdevapiSslMode.DISABLED) { + if (jdbcSslMode.getValue() != SslMode.DISABLED) { negotiateSSLConnection(); } @@ -519,7 +497,7 @@ public void connect(String user, String password, String database) { this.currDatabase = database; beforeHandshake(); - this.authProvider.connect(null, user, password, database); + this.authProvider.connect(user, password, database); } public void changeUser(String user, String password, String database) { @@ -527,7 +505,7 @@ public void changeUser(String user, String password, String database) { this.currPassword = password; this.currDatabase = database; - this.authProvider.changeUser(null, user, password, database); + this.authProvider.changeUser(user, password, database); } public void afterHandshake() { @@ -577,7 +555,7 @@ public void readAuthenticateOk() { if (notice instanceof XSessionStateChanged) { switch (((XSessionStateChanged) notice).getParamType()) { case Notice.SessionStateChanged_CLIENT_ID_ASSIGNED: - this.getServerSession().setThreadId(((XSessionStateChanged) notice).getValue().getVUnsignedInt()); + this.getServerSession().getCapabilities().setThreadId(((XSessionStateChanged) notice).getValue().getVUnsignedInt()); break; case Notice.SessionStateChanged_ACCOUNT_EXPIRED: // TODO @@ -683,21 +661,20 @@ public void drainRows() { } } - // TODO: put this in CharsetMapping.. - public static Map COLLATION_NAME_TO_COLLATION_INDEX = new java.util.HashMap<>(); - - static { - for (int i = 0; i < CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME.length; ++i) { - COLLATION_NAME_TO_COLLATION_INDEX.put(CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[i], i); - } + public ColumnDefinition readMetadata() { + return readMetadata(null); } - public ColumnDefinition readMetadata() { + public ColumnDefinition readMetadata(Consumer noticeConsumer) { try { + List notices; List fromServer = new LinkedList<>(); do { // use this construct to read at least one - fromServer.add((ColumnMetaData) this.reader.readMessage(null, ServerMessages.Type.RESULTSET_COLUMN_META_DATA_VALUE).getMessage()); - // TODO put notices somewhere like it's done eg. in readStatementExecuteOk(): builder.addNotice(this.reader.read(Frame.class)); + XMessage mess = this.reader.readMessage(null, ServerMessages.Type.RESULTSET_COLUMN_META_DATA_VALUE); + if (noticeConsumer != null && (notices = mess.getNotices()) != null) { + notices.stream().forEach(noticeConsumer::accept); + } + fromServer.add((ColumnMetaData) mess.getMessage()); } while (((SyncMessageReader) this.reader).getNextNonNoticeMessageType() == ServerMessages.Type.RESULTSET_COLUMN_META_DATA_VALUE); ArrayList metadata = new ArrayList<>(fromServer.size()); @SuppressWarnings("unchecked") @@ -999,7 +976,7 @@ public void reset() { } } - this.authProvider.changeUser(null, this.currUser, this.currPassword, this.currDatabase); + this.authProvider.changeUser(this.currUser, this.currPassword, this.currDatabase); } // No prepared statements survived to Mysqlx.Session.Reset. Reset all related control structures. @@ -1021,10 +998,6 @@ public void changeDatabase(String database) { // TODO: Figure out how this is relevant for X Protocol client Session } - public String getPasswordCharacterEncoding() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - public boolean versionMeetsMinimum(int major, int minor, int subminor) { //TODO: expose this via ServerVersion so calls look like x.getServerVersion().meetsMinimum(major, minor, subminor) throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); diff --git a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerCapabilities.java b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerCapabilities.java index ede5e68dd..c8730c72e 100644 --- a/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerCapabilities.java +++ b/src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerCapabilities.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. + * Copyright (c) 2018, 2021, 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 @@ -55,6 +55,9 @@ public class XServerCapabilities implements ServerCapabilities { static String SUBKEY_COMPRESSION_SERVER_COMBINE_MIXED_MESSAGES = "server_combine_mixed_messages"; static String SUBKEY_COMPRESSION_SERVER_MAX_COMBINE_MESSAGES = "server_max_combine_messages"; + /** Server-assigned client-id. */ + private long clientId = -1; + public XServerCapabilities(Map capabilities) { this.capabilities = capabilities; } @@ -118,13 +121,23 @@ public ServerVersion getServerVersion() { } @Override - public void setServerVersion(ServerVersion serverVersion) { + public boolean serverSupportsFracSecs() { + return true; + } + + @Override + public int getServerDefaultCollationIndex() { // TODO Auto-generated method stub + return 0; + } + @Override + public long getThreadId() { + return this.clientId; } @Override - public boolean serverSupportsFracSecs() { - return true; + public void setThreadId(long threadId) { + this.clientId = threadId; } } 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 019c2ee59..61461b63a 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. + * Copyright (c) 2018, 2021, 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 @@ -32,6 +32,7 @@ import java.util.Map; import java.util.TimeZone; +import com.mysql.cj.CharsetSettings; import com.mysql.cj.ServerVersion; import com.mysql.cj.exceptions.CJOperationNotSupportedException; import com.mysql.cj.exceptions.ExceptionFactory; @@ -41,8 +42,6 @@ public class XServerSession implements ServerSession { XServerCapabilities serverCapabilities = null; - /** Server-assigned client-id. */ - private long clientId = -1; private TimeZone defaultTimeZone = TimeZone.getDefault(); @@ -54,7 +53,6 @@ public ServerCapabilities getCapabilities() { @Override public void setCapabilities(ServerCapabilities capabilities) { this.serverCapabilities = (XServerCapabilities) capabilities; - } @Override @@ -82,16 +80,6 @@ public void setOldStatusFlags(int statusFlags) { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } - @Override - public int getServerDefaultCollationIndex() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void setServerDefaultCollationIndex(int serverDefaultCollationIndex) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - @Override public int getTransactionState() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); @@ -147,6 +135,11 @@ public void setClientParam(long clientParam) { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } + @Override + public boolean hasLongColumnInfo() { + throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); + } + @Override public boolean useMultiResults() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); @@ -158,7 +151,7 @@ public boolean isEOFDeprecated() { } @Override - public boolean hasLongColumnInfo() { + public boolean supportsQueryAttributes() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } @@ -182,11 +175,6 @@ public void setServerVariables(Map serverVariables) { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } - @Override - public boolean characterSetNamesMatches(String mysqlEncodingName) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - @Override public ServerVersion getServerVersion() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); @@ -197,71 +185,6 @@ public boolean isVersion(ServerVersion version) { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } - @Override - public String getServerDefaultCharset() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public String getErrorMessageEncoding() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void setErrorMessageEncoding(String errorMessageEncoding) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public int getMaxBytesPerChar(String javaCharsetName) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public int getMaxBytesPerChar(Integer charsetIndex, String javaCharsetName) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public String getEncodingForIndex(int collationIndex) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void configureCharacterSets() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public String getCharacterSetMetadata() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void setCharacterSetMetadata(String characterSetMetadata) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public int getMetadataCollationIndex() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void setMetadataCollationIndex(int metadataCollationIndex) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public String getCharacterSetResultsOnServer() { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - - @Override - public void setCharacterSetResultsOnServer(String characterSetResultsOnServer) { - throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); - } - @Override public boolean isLowerCaseTableNames() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); @@ -292,16 +215,6 @@ public boolean isServerTruncatesFracSecs() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); } - @Override - public long getThreadId() { - return this.clientId; - } - - @Override - public void setThreadId(long threadId) { - this.clientId = threadId; - } - @Override public boolean isAutoCommit() { throw ExceptionFactory.createException(CJOperationNotSupportedException.class, "Not supported"); @@ -323,4 +236,16 @@ public void setSessionTimeZone(TimeZone sessionTimeZone) { public TimeZone getDefaultTimeZone() { return this.defaultTimeZone; } + + @Override + public CharsetSettings getCharsetSettings() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setCharsetSettings(CharsetSettings charsetSettings) { + // TODO Auto-generated method stub + + } } diff --git a/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties b/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties index b0cf3c9ed..2b9704d90 100644 --- a/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties +++ b/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties @@ -35,11 +35,21 @@ Milliseconds=ms # # Classes # +AuthenticationKerberosClientPlugin.FailAuthenticateUser=No cached TGT found in the system or failed authenticating the user in the Kerberos server. +AuthenticationKerberosClientPlugin.FailCreateSaslClient=Failed creating a SASL client for the authentication mechanism ''{0}''. +AuthenticationKerberosClientPlugin.ErrProcessingAuthIter=Error while processing an authentication iteration for the authentication mechanism ''{0}''. + AuthenticationLdapSaslClientPlugin.UnsupportedAuthMech=Unsupported SASL authentication mechanism ''{0}''. AuthenticationLdapSaslClientPlugin.MissingLdapServerHostname=An LDAP Server hostname could not be acquired. One must be provided by either using the connection option ''ldapServerHostname'' or by setting the system property ''java.security.krb5.kdc''. AuthenticationLdapSaslClientPlugin.FailCreateSaslClient=Failed creating a SASL client for the authentication mechanism ''{0}''. AuthenticationLdapSaslClientPlugin.ErrProcessingAuthIter=Error while processing an authentication iteration for the authentication mechanism ''{0}''. +AuthenticationOciClientPlugin.SdkNotFound=The OCI SDK could not be found or is not installed. +AuthenticationOciClientPlugin.OciConfigFileError=OCI configuration file could not be read. +AuthenticationOciClientPlugin.OciConfigFileMissingEntry=OCI configuration file does not contain a ''fingerprint'' or ''key_file'' entry. +AuthenticationOciClientPlugin.PrivateKeyNotFound=Private key could not be found at location given by OCI configuration entry ''key_file''. +AuthenticationOciClientPlugin.PrivateKeyNotValid=OCI configuration entry ''key_file'' does not reference a valid key file. + AuthenticationProvider.BadAuthenticationPlugin=Unable to load authentication plugin ''{0}''. AuthenticationProvider.BadDefaultAuthenticationPlugin=Improper value "{0}" for property ''defaultAuthenticationPlugin''. AuthenticationProvider.DefaultAuthenticationPluginIsNotListed=Default authentication plugin "{0}" is neither one of the built-in plugins nor one of the plugins listed in ''authenticationPlugins''. @@ -93,6 +103,8 @@ Clob.11=Cannot truncate CLOB of length Clob.12=\ to length of Clob.13=. +Collection.DocIdMismatch=Replacement document has an _id that is different than the matched document. + ColumnDefinition.0={0} is not applicable to the {1} type of column ''{2}''. ColumnDefinition.1=Length must be specified before decimals for column ''{0}''. @@ -101,13 +113,12 @@ Connection.1=Cannot connect to MySQL server on {0}:{1}.\n\nMake sure that there Connection.2=No operations allowed after connection closed. Connection.3=Can''t call commit when autocommit=true Connection.4=Communications link failure during commit(). Transaction resolution unknown. -Connection.5=Java does not support the MySQL character encoding ''{0}''. -Connection.6=Unknown initial character set index ''{0}'' received from server. Initial client character set can be forced via the ''characterEncoding'' property. +Connection.5=Unknown Java encoding for the character set with index ''{0}''. Use the ''customCharsetMapping'' property to force it. +Connection.6=Unknown character set index ''{0}'' received from server. The appropriate client character set can be forced via the ''characterEncoding'' property. Connection.7=Can''t map {0} given for characterSetResults to a supported MySQL encoding. Connection.8=Unable to use encoding: {0} Connection.9=No timezone mapping entry for ''{0}'' Connection.10=Illegal connection port value ''{0}'' -Connection.11=Unknown character set index ''{0}'' was received from server. Connection.12=Could not map transaction isolation ''{0}'' to a valid JDBC level. Connection.13=Could not retrieve transaction isolation level from server Connection.15=Connection setting too low for ''maxAllowedPacket''. When ''useServerPrepStmts=true'', ''maxAllowedPacket'' must be higher than {0}. Check also ''max_allowed_packet'' in MySQL configuration files. @@ -291,15 +302,13 @@ MysqlIO.105=Negative skip length not allowed MysqlIO.106=Value ''0000-00-00'' can not be represented as java.sql.Date MysqlIO.107=Value ''0000-00-00'' can not be represented as java.sql.Timestamp MysqlIO.111=Could not allocate packet of {0} bytes required for "LOAD DATA LOCAL INFILE" operation. Try increasing max heap allocation for JVM or decreasing server variable ''max_allowed_packet'' -MysqlIO.113=Invalid character set index for encoding: {0} +MysqlIO.113=Invalid character set index {0} for handshake, only values 1-255 are allowed. MysqlIO.EOF=Can not read response from server. Expected to read {0} bytes, read {1} bytes before connection was unexpectedly lost. MysqlIO.NoInnoDBStatusFound=No InnoDB status output returned by server. MysqlIO.InnoDBStatusFailed=Couldn''t retrieve InnoDB status due to underlying exception: MysqlIO.LoadDataLocalNotAllowed=Server asked for stream in response to "LOAD DATA LOCAL INFILE" but functionality is not enabled at client by setting "allowLoadLocalInfile=true" or specifying a path with ''allowLoadLocalInfileInPath''. MysqlIo.BadQueryInterceptor=Unable to load query interceptor ''{0}''. -MysqlNativePasswordPlugin.1=Unsupported character encoding ''{0}'' for ''passwordCharacterEncoding'' or ''characterEncoding''. - MysqlParameterMetadata.0=Parameter metadata not available for the given statement MysqlParameterMetadata.1=Parameter index of ''{0}'' is invalid. MysqlParameterMetadata.2=Parameter index of ''{0}'' is greater than number of parameters, which is ''{1}''. @@ -363,7 +372,7 @@ PreparedStatement.25=Connection is read-only. PreparedStatement.26=Queries leading to data modification are not allowed PreparedStatement.34=Connection is read-only. PreparedStatement.35=Queries leading to data modification are not allowed -PreparedStatement.37=Can not issue executeUpdate() or executeLargeUpdate() for SELECTs +PreparedStatement.37=Can not issue executeUpdate() or executeLargeUpdate() with statements that produce result sets PreparedStatement.40=No value specified for parameter PreparedStatement.43=PreparedStatement created, but used 1 or fewer times. It is more efficient to prepare statements once, and re-use them many times PreparedStatement.48=PreparedStatement has been closed. No further operations allowed. @@ -442,7 +451,7 @@ ResultSet.Operation_not_allowed_after_ResultSet_closed_144=Operation not allowed ResultSet.Before_start_of_result_set_146=Before start of result set ResultSet.After_end_of_result_set_148=After end of result set ResultSet.Query_generated_no_fields_for_ResultSet_133=Query generated no fields for ResultSet -ResultSet.ResultSet_is_from_UPDATE._No_Data_115=ResultSet is from UPDATE. No Data. +ResultSet.ResultSet_is_from_UPDATE._No_Data_115=Not a navigable ResultSet. ResultSet.Invalid_value_for_getFloat()_-____68=Invalid value for getFloat() - '' ResultSet.Invalid_value_for_getInt()_-____74=Invalid value for getInt() - '' @@ -484,6 +493,15 @@ ResultSet.UnknownSourceType=Cannot decode value of unknown source type ResultSet.InvalidTimeValue=The value ''{0}'' is an invalid TIME value. JDBC Time objects represent a wall-clock time and not a duration as MySQL treats them. If you are treating this type as a duration, consider retrieving this value as a string and dealing with it according to your requirements. ResultSet.InvalidZeroDate=Zero date value prohibited +ValueEncoder.WrongTinyIntValueType=Type ''{0}'' cannot be encoded into a MySQL TINYINT value. +ValueEncoder.WrongSmallIntValueType=Type ''{0}'' cannot be encoded into a MySQL SMALLINT value. +ValueEncoder.WrongIntValueType=Type ''{0}'' cannot be encoded into a MySQL INT value. +ValueEncoder.WrongBigIntValueType=Type ''{0}'' cannot be encoded into a MySQL BIGINT value. +ValueEncoder.WrongTimeValueType=Type ''{0}'' cannot be encoded into a MySQL TIME value. +ValueEncoder.WrongDateValueType=Type ''{0}'' cannot be encoded into a MySQL DATE value. +ValueEncoder.WrongDateTimeValueType=Type ''{0}'' cannot be encoded into a MySQL DATETIME value. +ValueEncoder.WrongTimestampValueType=Type ''{0}'' cannot be encoded into a MySQL TIMESTAMP value. + # # Usage advisor messages for ResultSets # @@ -579,7 +597,6 @@ Session.Create.Failover.0=Unable to connect to any of the target hosts. Sha256PasswordPlugin.0=Unable to read public key {0} Sha256PasswordPlugin.1=Unable to close public key file Sha256PasswordPlugin.2=Public Key Retrieval is not allowed -Sha256PasswordPlugin.3=Unsupported character encoding ''{0}'' for ''passwordCharacterEncoding'' or ''characterEncoding''. Schema.CreateCollection=The server doesn't support the requested operation. Please update the MySQL Server and or Client library @@ -608,9 +625,9 @@ Statement.35=Queries leading to data modification are not allowed. Statement.40=Can not issue INSERT/UPDATE/DELETE with executeQuery(). Statement.42=Connection is read-only. Statement.43=Queries leading to data modification are not allowed. -Statement.46=Can not issue SELECT via executeUpdate() or executeLargeUpdate(). +Statement.46=Statement.executeUpdate() or Statement.executeLargeUpdate() cannot issue statements that produce result sets. Statement.AlreadyClosed=No operations allowed after statement closed. -Statement.57=Can not issue data manipulation statements with executeQuery(). +Statement.57=Statement.executeQuery() cannot issue statements that do not produce result sets. Statement.59=Can not issue NULL query. Statement.61=Can not issue empty query. Statement.63=Statement not closed explicitly. You should call close() on created Statement instances from your code to be more efficient. @@ -619,10 +636,16 @@ Statement.GeneratedKeysNotRequested=Generated keys not requested. You need to sp Statement.ConnectionKilledDueToTimeout=Connection closed to due to statement timeout being reached and "queryTimeoutKillsConnection" being set to "true". Statement.UnsupportedSQLType=Unsupported SQL type: +StringInspector.1=The source string must not be null. +StringInspector.2=Illegal argument value {0} for openingMarkers and/or {1} for closingMarkers. These cannot be null and must have the same length. +StringInspector.3=Illegal argument value {0} for overridingMarkers. These cannot be null and must be a sub-set of openingMarkers {1}. +StringInspector.4=The start position must be zero or a positive number. +StringInspector.5=The start position must must not be higher than the stop position. +StringInspector.6=The stop position must be zero or a positive number. +StringInspector.7=The stop position must must not be higher than the length of the source string. +StringInspector.8=The delimiter string must not be null. StringUtils.0=Unsupported character encoding ''{0}'' -StringUtils.15=Illegal argument value {0} for openingMarkers and/or {1} for closingMarkers. These cannot be null and must have the same length. -StringUtils.16=Illegal argument value {0} for overridingMarkers. These cannot be null and must be a sub-set of openingMarkers {1}. StringUtils.badIntFormat=Invalid integer format for value ''{0}'' TimeUtil.0=Illegal hour value ''{0}'' for java.sql.Time type in value ''{1}''. @@ -742,6 +765,12 @@ SQLError.67=Invalid argument value SQLError.68=Driver not capable SQLError.69=Timeout expired +# +# Log messages +# + +QueryAttributes.SetButNotSupported=Query attributes have been set but the server does not support them. + # # ConnectionProperty Categories # @@ -770,15 +799,15 @@ ConnectionProperties.categoryUserDefined=User-defined properties # ConnectionProperty Descriptions # -ConnectionProperties.loadDataLocal=Should the driver allow use of "LOAD DATA LOCAL INFILE ..."?[CR]Setting to "true" overrides whatever path is set in ''allowLoadLocalInfileInPath'', allowing uploading files from any location. -ConnectionProperties.loadDataLocalInPath=Enables "LOAD DATA LOCAL INFILE ..." statements, but only allows loading files from the specified path. Files within sub-directories are also allowed, but relative paths or symlinks that fall outside this path are forbidden. -ConnectionProperties.allowSourceDownConnections=By default, a replication-aware connection will fail to connect when configured source hosts are all unavailable at initial connection. Setting this property to ''true'' allows to establish the initial connection, by failing over to the replica servers, in read-only state. It won''t prevent subsequent failures when switching back to the source hosts i.e. by setting the replication connection to read/write state. -ConnectionProperties.allowReplicaDownConnections=By default, a replication-aware connection will fail to connect when configured replica hosts are all unavailable at initial connection. Setting this property to ''true'' allows to establish the initial connection. It won''t prevent failures when switching to replicas i.e. by setting the replication connection to read-only state. The property ''readFromSourceWhenNoReplicas'' should be used for this purpose. -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. ConnectionProperties.allowMultiQueries=Allow the use of '';'' to delimit multiple queries during one statement (true/false). Default is ''false'', and it does not affect the addBatch() and executeBatch() methods, which rely on rewriteBatchStatements instead. ConnectionProperties.allowNANandINF=Should the driver allow NaN or +/- INF values in PreparedStatement.setDouble()? +ConnectionProperties.allowPublicKeyRetrieval=Allows special handshake round-trip to get an RSA public key directly from server. +ConnectionProperties.allowReplicaDownConnections=By default, a replication-aware connection will fail to connect when configured replica hosts are all unavailable at initial connection. Setting this property to ''true'' allows to establish the initial connection. It won''t prevent failures when switching to replicas i.e. by setting the replication connection to read-only state. The property ''readFromSourceWhenNoReplicas'' should be used for this purpose. +ConnectionProperties.allowSourceDownConnections=By default, a replication-aware connection will fail to connect when configured source hosts are all unavailable at initial connection. Setting this property to ''true'' allows to establish the initial connection, by failing over to the replica servers, in read-only state. It won''t prevent subsequent failures when switching back to the source hosts i.e. by setting the replication connection to read/write state. ConnectionProperties.allowUrlInLoadLocal=Should the driver allow URLs in "LOAD DATA LOCAL INFILE ..." statements? +ConnectionProperties.allVersions=all versions ConnectionProperties.alwaysSendSetIsolation=Should the driver always communicate with the database when Connection.setTransactionIsolation() is called? If set to false, the driver will only communicate with the database when the requested transaction isolation is different than the whichever is newer, the last value that was set via Connection.setTransactionIsolation(), or the value that was read from the server when the connection was established. Note that useLocalSessionState=true will force the same behavior as alwaysSendSetIsolation=false, regardless of how alwaysSendSetIsolation is set. +ConnectionProperties.authenticationPlugins=Comma-delimited list of classes that implement the interface com.mysql.cj.protocol.AuthenticationPlugin. These plugins will be loaded at connection initialization and can be used together with their sever-side counterparts for authenticating users, unless they are also disabled in the connection property ''disabledAuthenticationPlugins''. ConnectionProperties.autoClosePstmtStreams=Should the driver automatically call .close() on streams/readers passed as arguments via set*() methods? ConnectionProperties.autoDeserialize=Should the driver automatically detect and de-serialize objects stored in BLOB fields? ConnectionProperties.autoGenerateTestcaseScript=Should the driver dump the SQL it is executing, including server-side prepared statements to STDERR? @@ -786,38 +815,59 @@ ConnectionProperties.autoReconnect=Should the driver try to re-establish stale a ConnectionProperties.autoReconnectForPools=Use a reconnection strategy appropriate for connection pools (defaults to ''false'') ConnectionProperties.autoSlowLog=Instead of using slowQueryThreshold* to determine if a query is slow enough to be logged, maintain statistics that allow the driver to determine queries that are outside the 99th percentile? ConnectionProperties.blobsAreStrings=Should the driver always treat BLOBs as Strings - specifically to work around dubious metadata returned by the server for GROUP BY clauses? -ConnectionProperties.functionsNeverReturnBlobs=Should the driver always treat data from functions returning BLOBs as Strings - specifically to work around dubious metadata returned by the server for GROUP BY clauses? ConnectionProperties.blobSendChunkSize=Chunk size to use when sending BLOB/CLOBs via ServerPreparedStatements. Note that this value cannot exceed the value of "maxAllowedPacket" and, if that is the case, then this value will be corrected automatically. ConnectionProperties.cacheCallableStatements=Should the driver cache the parsing stage of CallableStatements +ConnectionProperties.cacheDefaultTimeZone=Caches client's default time zone. This results in better performance when dealing with time zone conversions in Date and Time data types, however it won't be aware of time zone changes if they happen at runtime. ConnectionProperties.cachePrepStmts=Should the driver cache the parsing stage of PreparedStatements of client-side prepared statements, the "check" for suitability of server-side prepared and server-side prepared statements themselves? ConnectionProperties.cacheRSMetadata=Should the driver cache ResultSetMetaData for Statements and PreparedStatements? (Req. JDK-1.4+, true/false, default ''false'') ConnectionProperties.cacheServerConfiguration=Should the driver cache the results of ''SHOW VARIABLES'' and ''SHOW COLLATION'' on a per-URL basis? ConnectionProperties.callableStmtCacheSize=If ''cacheCallableStmts'' is enabled, how many callable statements should be cached? -ConnectionProperties.characterEncoding=What character encoding should the driver use when dealing with strings? (defaults is to ''autodetect'') -ConnectionProperties.characterSetResults=Character set to tell the server to return results as. +ConnectionProperties.characterEncoding=Instructs the server to set session system variables ''character_set_client'' and ''character_set_connection'' to the default character set for the specified Java encoding and set ''collation_connection'' to the default collation for this character set. If neither this property nor the property ''connectionCollation'' is set:[CR]For Connector/J 8.0.25 and earlier, the driver will try to use the server default character set;[CR]For Connector/J 8.0.26 and later, the driver will use "utf8mb4". +ConnectionProperties.characterSetResults=Instructs the server to return the data encoded with the default character set for the specified Java encoding. If not set or set to "null", the server will send data in its original character set and the driver will decode it according to the result metadata. +ConnectionProperties.clientCertificateKeyStorePassword=Password for the client certificates key store. +ConnectionProperties.clientCertificateKeyStoreType=Key store type for client certificates.[CR]NULL or empty means use the default, which is "JKS". Standard key store types supported by the JVM are "JKS" and "PKCS12", your environment may have more available depending on what security products are installed and available to the JVM. +ConnectionProperties.clientCertificateKeyStoreUrl=URL for the client certificate KeyStore[CR]If not specified, the property ''fallbackToSystemKeyStore'' determines if system-wide key store is used. ConnectionProperties.clientInfoProvider=The name of a class that implements the com.mysql.cj.jdbc.ClientInfoProvider interface in order to support JDBC-4.0''s Connection.get/setClientInfo() methods ConnectionProperties.clobberStreamingResults=This will cause a ''streaming'' ResultSet to be automatically closed, and any outstanding data still streaming from the server to be discarded if another query is executed before all the data has been read from the server. ConnectionProperties.clobCharacterEncoding=The character encoding to use for sending and retrieving TEXT, MEDIUMTEXT and LONGTEXT values instead of the configured connection characterEncoding ConnectionProperties.compensateOnDuplicateKeyUpdateCounts=Should the driver compensate for the update counts of "ON DUPLICATE KEY" INSERT statements (2 = 1, 0 = 1) when using prepared statements? -ConnectionProperties.connectionCollation=If set, tells the server to use this collation in SET NAMES charset COLLATE connectionCollation. Also overrides the characterEncoding with those corresponding to the character set of this collation. +ConnectionProperties.connectionAttributes=A comma-delimited list of user-defined key:value pairs (in addition to standard MySQL-defined key:value pairs) to be passed to MySQL Server for display as connection attributes in the PERFORMANCE_SCHEMA.SESSION_CONNECT_ATTRS table. Example usage: connectionAttributes=key1:value1,key2:value2 This functionality is available for use with MySQL Server version 5.6 or later only. Earlier versions of MySQL Server do not support connection attributes, causing this configuration option to be ignored. Setting connectionAttributes=none will cause connection attribute processing to be bypassed, for situations where Connection creation/initialization speed is critical. +ConnectionProperties.connectionCollation=Instructs the server to set session system variable ''collation_connection'' to the specified collation name and set ''character_set_client'' and ''character_set_connection'' to the corresponding character set. This property overrides the value of ''characterEncoding'' with the character set this collation belongs to. If neither this property nor the property ''characterEncoding'' is set:[CR]For Connector/J 8.0.25 and earlier, the driver will try to use the server default character set;[CR]For Connector/J 8.0.26 and later, the driver will use "utf8mb4" default collation. ConnectionProperties.connectionLifecycleInterceptors=A comma-delimited list of classes that implement "com.mysql.cj.jdbc.interceptors.ConnectionLifecycleInterceptor" that should notified of connection lifecycle events (creation, destruction, commit, rollback, setting the current database and changing the autocommit mode) and potentially alter the execution of these commands. ConnectionLifecycleInterceptors are "stackable", more than one interceptor may be specified via the configuration property as a comma-delimited list, with the interceptors executed in order from left to right. +ConnectionProperties.connectionPropertiesTransform=An implementation of com.mysql.cj.conf.ConnectionPropertiesTransform that the driver will use to modify URL properties passed to the driver before attempting a connection +ConnectionProperties.connectionTimeZone=Configures the connection time zone which is used by Connector/J if conversion between the JVM default and a target time zone is needed when preserving instant temporal values.[CR]Accepts a geographic time zone name or a time zone offset from Greenwich/UTC, using a syntax ''java.time.ZoneId'' is able to parse, or one of the two logical values "LOCAL" and "SERVER". Default is "LOCAL". If set to an explicit time zone then it must be one that either the JVM or both the JVM and MySQL support. If set to "LOCAL" then the driver assumes that the connection time zone is the same as the JVM default time zone. If set to "SERVER" then the driver attempts to detect the session time zone from the values configured on the MySQL server session variables ''time_zone'' or ''system_time_zone''. The time zone detection and subsequent mapping to a Java time zone may fail due to several reasons, mostly because of time zone abbreviations being used, in which case an explicit time zone must be set or a different time zone must be configured on the server.[CR]This option itself does not set MySQL server session variable ''time_zone'' to the given value. To do that the ''forceConnectionTimeZoneToSession'' connection option must be set to "true".[CR]Please note that setting a value to ''connectionTimeZone'' in conjunction with ''forceConnectionTimeZoneToSession=false'' and ''preserveInstants=false'' has no effect since, in this case, neither this option is used to change the session time zone nor used for time zone conversions of time-based data.[CR]Former connection option ''serverTimezone'' is still valid as an alias of this one but may be deprecated in the future.[CR]See also ''forceConnectionTimeZoneToSession'' and ''preserveInstants'' for more details. ConnectionProperties.connectTimeout=Timeout for socket connect (in milliseconds), with 0 being no timeout. Only works on JDK-1.4 or newer. Defaults to ''0''. ConnectionProperties.continueBatchOnError=Should the driver continue processing batch commands if one statement fails. The JDBC spec allows either way (defaults to ''true''). ConnectionProperties.createDatabaseIfNotExist=Creates the database given in the URL if it doesn''t yet exist. Assumes the configured user has permissions to create databases. +ConnectionProperties.customCharsetMapping=A comma-delimited list of custom "charset:java encoding" pairs.[CR]In case the MySQL server is configured with custom character sets and ''detectCustomCollations=true'', Connector/J needs to know which Java character encoding to use for the data represented by these character sets. Example usage: ''customCharsetMapping=charset1:UTF-8,charset2:Cp1252''. +ConnectionProperties.databaseTerm=MySQL uses the term "schema" as a synonym of the term "database," while Connector/J historically takes the JDBC term "catalog" as synonymous to "database". This property sets for Connector/J which of the JDBC terms "catalog" and "schema" is used in an application to refer to a database. The property takes one of the two values CATALOG or SCHEMA and uses it to determine (1) which Connection methods can be used to set/get the current database (e.g. setCatalog() or setSchema()?), (2) which arguments can be used within the various DatabaseMetaData methods to filter results (e.g. the catalog or schemaPattern argument of getColumns()?), and (3) which fields in the ResultSet returned by DatabaseMetaData methods contain the database identification information (i.e., the TABLE_CAT or TABLE_SCHEM field in the ResultSet returned by getTables()?).[CR]If databaseTerm=CATALOG, schemaPattern for searches are ignored and calls of schema methods (like setSchema() or get Schema()) become no-ops, and vice versa. +ConnectionProperties.defaultAuthenticationPlugin=The default authentication plugin client-side protocol name or a fully qualified name of a class that implements the interface com.mysql.cj.protocol.AuthenticationPlugin. The specified authentication plugin must be either one of the built-in authentication plugins or one of the plugins listed in the property ''authenticationPlugins''. Additionally, the default authentication plugin cannot be disabled with the property ''disabledAuthenticationPlugins''. Neither an empty nor unknown plugin name or class can be set for this property.[CR]By default, Connector/J honors the server-side default authentication plugin, which is known after receiving the initial handshake packet, and falls back to this property's default value if that plugin cannot be used. However, when a value is explicitly provided to this property, Connector/J then overrides the server-side default authentication plugin and always tries first the plugin specified with this property. ConnectionProperties.defaultFetchSize=The driver will call setFetchSize(n) with this value on all newly-created Statements -ConnectionProperties.useServerPrepStmts=Use server-side prepared statements if the server supports them? +ConnectionProperties.detectCustomCollations=Should the driver detect custom charsets/collations installed on server (true/false, defaults to ''false''). If this option set to ''true'' driver gets actual charsets/collations from server each time connection establishes. This could slow down connection initialization significantly. +ConnectionProperties.disabledAuthenticationPlugins=Comma-delimited list of authentication plugins client-side protocol names or classes implementing the interface com.mysql.cj.protocol.AuthenticationPlugin. The authentication plugins listed will not be used for authenticating users and, if anyone of them is required during the authentication exchange, the connection fails. The default authentication plugin specified in the property ''defaultAuthenticationPlugin'' cannot be disabled. +ConnectionProperties.disconnectOnExpiredPasswords=If "disconnectOnExpiredPasswords" is set to "false" and password is expired then server enters "sandbox" mode and sends ERR(08001, ER_MUST_CHANGE_PASSWORD) for all commands that are not needed to set a new password until a new password is set. +ConnectionProperties.dnsSrv=Should the driver use the given host name to lookup for DNS SRV records and use the resulting list of hosts in a multi-host failover connection? Note that a single host name and no port must be provided when this option is enabled. +ConnectionProperties.dontCheckOnDuplicateKeyUpdateInSQL=Stops checking if every INSERT statement contains the "ON DUPLICATE KEY UPDATE" clause. As a side effect, obtaining the statement''s generated keys information will return a list where normally it wouldn''t. Also be aware that, in this case, the list of generated keys returned may not be accurate. The effect of this property is canceled if set simultaneously with ''rewriteBatchedStatements=true''. ConnectionProperties.dontTrackOpenResources=The JDBC specification requires the driver to automatically track and close resources, however if your application doesn''t do a good job of explicitly calling close() on statements or result sets, this can cause memory leakage. Setting this property to true relaxes this constraint, and can be more memory efficient for some applications. Also the automatic closing of the Statement and current ResultSet in Statement.closeOnCompletion() and Statement.getMoreResults ([Statement.CLOSE_CURRENT_RESULT | Statement.CLOSE_ALL_RESULTS]), respectively, ceases to happen. This property automatically sets holdResultsOpenOverStatementClose=true. ConnectionProperties.dumpQueriesOnException=Should the driver dump the contents of the query sent to the server in the message for SQLExceptions? ConnectionProperties.eliseSetAutoCommit=If using MySQL-4.1 or newer, should the driver only issue ''set autocommit=n'' queries when the server''s state doesn''t match the requested state by Connection.setAutoCommit(boolean)? ConnectionProperties.emptyStringsConvertToZero=Should the driver allow conversions from empty string fields to numeric values of '''0''? ConnectionProperties.emulateLocators=Should the driver emulate java.sql.Blobs with locators? With this feature enabled, the driver will delay loading the actual Blob data until the one of the retrieval methods (getInputStream(), getBytes(), and so forth) on the blob data stream has been accessed. For this to work, you must use a column alias with the value of the column to the actual name of the Blob. The feature also has the following restrictions: The SELECT that created the result set must reference only one table, the table must have a primary key; the SELECT must alias the original blob column name, specified as a string, to an alternate name; the SELECT must cover all columns that make up the primary key. ConnectionProperties.emulateUnsupportedPstmts=Should the driver detect prepared statements that are not supported by the server, and replace them with client-side emulated versions? +ConnectionProperties.enableEscapeProcessing=Sets the default escape processing behavior for Statement objects. The method Statement.setEscapeProcessing() can be used to specify the escape processing behavior for an individual Statement object. Default escape processing behavior in prepared statements must be defined with the property ''processEscapeCodesForPrepStmts''. ConnectionProperties.enablePacketDebug=When enabled, a ring-buffer of ''packetDebugBufferSize'' packets will be kept, and dumped when exceptions are thrown in key areas in the driver''s code ConnectionProperties.enableQueryTimeouts=When enabled, query timeouts set via Statement.setQueryTimeout() use a shared java.util.Timer instance for scheduling. Even if the timeout doesn''t expire before the query is processed, there will be memory used by the TimerTask for the given timeout which won''t be reclaimed until the time the timeout would have expired if it hadn''t been cancelled by the driver. High-load environments might want to consider disabling this functionality. +ConnectionProperties.exceptionInterceptors=Comma-delimited list of classes that implement com.mysql.cj.exceptions.ExceptionInterceptor. These classes will be instantiated one per Connection instance, and all SQLExceptions thrown by the driver will be allowed to be intercepted by these interceptors, in a chained fashion, with the first class listed as the head of the chain. ConnectionProperties.explainSlowQueries=If ''logSlowQueries'' is enabled, should the driver automatically issue an ''EXPLAIN'' on the server and send the results to the configured logger at a WARN level? ConnectionProperties.failoverReadOnly=When failing over in autoReconnect mode, should the connection be set to ''read-only''? +ConnectionProperties.fallbackToSystemKeyStore=Whether the absence of setting a value for ''clientCertificateKeyStoreUrl'' falls back to using the system-wide key store defined through the system properties ''javax.net.ssl.keyStore*''. +ConnectionProperties.fallbackToSystemTrustStore=Whether the absence of setting a value for ''trustCertificateKeyStoreUrl'' falls back to using the system-wide default trust store or one defined through the system properties ''javax.net.ssl.trustStore*''. +ConnectionProperties.forceConnectionTimeZoneToSession=If enabled, sets the time zone value determined by ''connectionTimeZone'' connection property to the current server session ''time_zone'' variable. If the time zone value is given as a geographical time zone, then Connector/J sets this value as-is in the server session, in which case the time zone system tables must be populated beforehand (consult the MySQL Server documentation for further details); but, if the value is given as an offset from Greenwich/UTC in any of the supported syntaxes, then the server session time zone is set as a numeric offset from UTC.[CR]With that no intermediate conversion between JVM default time zone and connection time zone is needed to store correct milliseconds value of instant Java objects such as java.sql.Timestamp or java.time.OffsetDateTime when stored in TIMESTAMP columns.[CR]Note that it also affects the result of MySQL functions such as ''NOW()'', ''CURTIME()'' or ''CURDATE()''.[CR]This option has no effect if used in conjunction with ''connectionTimeZone=SERVER'' since, in this case, the session is already set with the required time zone.[CR]See also ''connectionTimeZone'' and ''preserveInstants'' for more details. +ConnectionProperties.functionsNeverReturnBlobs=Should the driver always treat data from functions returning BLOBs as Strings - specifically to work around dubious metadata returned by the server for GROUP BY clauses? ConnectionProperties.gatherPerfMetrics=Should the driver gather performance metrics, and report them via the configured logger every ''reportMetricsIntervalMillis'' milliseconds? ConnectionProperties.generateSimpleParameterMetadata=Should the driver generate simplified parameter metadata for PreparedStatements when no metadata is available either because the server couldn''t support preparing the statement, or server-side prepared statements are disabled? +ConnectionProperties.getProceduresReturnsFunctions=Pre-JDBC4 DatabaseMetaData API has only the getProcedures() and getProcedureColumns() methods, so they return metadata info for both stored procedures and functions. JDBC4 was extended with the getFunctions() and getFunctionColumns() methods and the expected behaviours of previous methods are not well defined. For JDBC4 and higher, default ''true'' value of the option means that calls of DatabaseMetaData.getProcedures() and DatabaseMetaData.getProcedureColumns() return metadata for both procedures and functions as before, keeping backward compatibility. Setting this property to ''false'' decouples Connector/J from its pre-JDBC4 behaviours for DatabaseMetaData.getProcedures() and DatabaseMetaData.getProcedureColumns(), forcing them to return metadata for procedures only. +ConnectionProperties.ha.enableJMX=Enables JMX-based management of load-balanced connection groups, including live addition/removal of hosts from load-balancing pool. Enables JMX-based management of replication connection groups, including live replica promotion, addition of new replicas and removal of source or replica hosts from load-balanced source and replica connection pools. ConnectionProperties.holdRSOpenOverStmtClose=Should the driver close result sets on Statement.close() as required by the JDBC specification? ConnectionProperties.ignoreNonTxTables=Ignore non-transactional table warning for rollback? (defaults to ''false''). ConnectionProperties.includeInnodbStatusInDeadlockExceptions=Include the output of "SHOW ENGINE INNODB STATUS" in exception messages when deadlock exceptions are detected? @@ -828,50 +878,61 @@ ConnectionProperties.interactiveClient=Set the CLIENT_INTERACTIVE flag, which te ConnectionProperties.jdbcCompliantTruncation=Should the driver throw java.sql.DataTruncation exceptions when data is truncated as is required by the JDBC specification when connected to a server that supports warnings (MySQL 4.1.0 and newer)? This property has no effect if the server sql-mode includes STRICT_TRANS_TABLES. ConnectionProperties.largeRowSizeThreshold=What size result set row should the JDBC driver consider "large", and thus use a more memory-efficient way of representing the row internally? ConnectionProperties.ldapServerHostname=When using MySQL''s LDAP pluggable authentication with GSSAPI/Kerberos authentication method, allows setting the LDAP service principal hostname as configured in the Kerberos KDC. If this property is not set, Connector/J takes the system property ''java.security.krb5.kdc'' and extracts the hostname (short name) from its value and uses it. If neither is set, the connection fails with an exception. -ConnectionProperties.loadBalanceStrategy=If using a load-balanced connection to connect to SQL nodes in a MySQL Cluster/NDB configuration (by using the URL prefix "jdbc:mysql:loadbalance://"), which load balancing algorithm should the driver use: (1) "random" - the driver will pick a random host for each request. This tends to work better than round-robin, as the randomness will somewhat account for spreading loads where requests vary in response time, while round-robin can sometimes lead to overloaded nodes if there are variations in response times across the workload. (2) "bestResponseTime" - the driver will route the request to the host that had the best response time for the previous transaction. (3) "serverAffinity" - the driver initially attempts to enforce server affinity while still respecting and benefiting from the fault tolerance aspects of the load-balancing implementation. The server affinity ordered list is provided using the property ''serverAffinityOrder''. If none of the servers listed in the affinity list is responsive, the driver then refers to the "random" strategy to proceed with choosing the next server. -ConnectionProperties.serverAffinityOrder=A comma separated list containing the host/port pairs that are to be used in load-balancing "serverAffinity" strategy. Only the sub-set of the hosts enumerated in the main hosts section in this URL will be used and they must be identical in case and type, i.e., can''t use an IP address in one place and the corresponding host name in the other. +ConnectionProperties.loadBalanceAutoCommitStatementRegex=When load-balancing is enabled for auto-commit statements (via loadBalanceAutoCommitStatementThreshold), the statement counter will only increment when the SQL matches the regular expression. By default, every statement issued matches. +ConnectionProperties.loadBalanceAutoCommitStatementThreshold=When auto-commit is enabled, the number of statements which should be executed before triggering load-balancing to rebalance. Default value of 0 causes load-balanced connections to only rebalance when exceptions are encountered, or auto-commit is disabled and transactions are explicitly committed or rolled back. ConnectionProperties.loadBalanceBlocklistTimeout=Time in milliseconds between checks of servers which are unavailable, by controlling how long a server lives in the global blocklist. -ConnectionProperties.loadBalancePingTimeout=Time in milliseconds to wait for ping response from each of load-balanced physical connections when using load-balanced Connection. -ConnectionProperties.loadBalanceValidateConnectionOnSwapServer=Should the load-balanced Connection explicitly check whether the connection is live when swapping to a new physical connection at commit/rollback? ConnectionProperties.loadBalanceConnectionGroup=Logical group of load-balanced connections within a classloader, used to manage different groups independently. If not specified, live management of load-balanced connections is disabled. ConnectionProperties.loadBalanceExceptionChecker=Fully-qualified class name of custom exception checker. The class must implement com.mysql.cj.jdbc.ha.LoadBalanceExceptionChecker interface, and is used to inspect SQLExceptions and determine whether they should trigger fail-over to another host in a load-balanced deployment. -ConnectionProperties.loadBalanceSQLStateFailover=Comma-delimited list of SQLState codes used by default load-balanced exception checker to determine whether a given SQLException should trigger failover. The SQLState of a given SQLException is evaluated to determine whether it begins with any value in the comma-delimited list. -ConnectionProperties.loadBalanceSQLExceptionSubclassFailover=Comma-delimited list of classes/interfaces used by default load-balanced exception checker to determine whether a given SQLException should trigger failover. The comparison is done using Class.isInstance(SQLException) using the thrown SQLException. -ConnectionProperties.ha.enableJMX=Enables JMX-based management of load-balanced connection groups, including live addition/removal of hosts from load-balancing pool. Enables JMX-based management of replication connection groups, including live replica promotion, addition of new replicas and removal of source or replica hosts from load-balanced source and replica connection pools. ConnectionProperties.loadBalanceHostRemovalGracePeriod=Sets the grace period to wait for a host being removed from a load-balanced connection, to be released when it is currently the active host. -ConnectionProperties.loadBalanceAutoCommitStatementThreshold=When auto-commit is enabled, the number of statements which should be executed before triggering load-balancing to rebalance. Default value of 0 causes load-balanced connections to only rebalance when exceptions are encountered, or auto-commit is disabled and transactions are explicitly committed or rolled back. -ConnectionProperties.loadBalanceAutoCommitStatementRegex=When load-balancing is enabled for auto-commit statements (via loadBalanceAutoCommitStatementThreshold), the statement counter will only increment when the SQL matches the regular expression. By default, every statement issued matches. +ConnectionProperties.loadBalancePingTimeout=Time in milliseconds to wait for ping response from each of load-balanced physical connections when using load-balanced Connection. +ConnectionProperties.loadBalanceSQLExceptionSubclassFailover=Comma-delimited list of classes/interfaces used by default load-balanced exception checker to determine whether a given SQLException should trigger failover. The comparison is done using Class.isInstance(SQLException) using the thrown SQLException. +ConnectionProperties.loadBalanceSQLStateFailover=Comma-delimited list of SQLState codes used by default load-balanced exception checker to determine whether a given SQLException should trigger failover. The SQLState of a given SQLException is evaluated to determine whether it begins with any value in the comma-delimited list. +ConnectionProperties.loadBalanceStrategy=If using a load-balanced connection to connect to SQL nodes in a MySQL Cluster/NDB configuration (by using the URL prefix "jdbc:mysql:loadbalance://"), which load balancing algorithm should the driver use: (1) "random" - the driver will pick a random host for each request. This tends to work better than round-robin, as the randomness will somewhat account for spreading loads where requests vary in response time, while round-robin can sometimes lead to overloaded nodes if there are variations in response times across the workload. (2) "bestResponseTime" - the driver will route the request to the host that had the best response time for the previous transaction. (3) "serverAffinity" - the driver initially attempts to enforce server affinity while still respecting and benefiting from the fault tolerance aspects of the load-balancing implementation. The server affinity ordered list is provided using the property ''serverAffinityOrder''. If none of the servers listed in the affinity list is responsive, the driver then refers to the "random" strategy to proceed with choosing the next server. +ConnectionProperties.loadBalanceValidateConnectionOnSwapServer=Should the load-balanced Connection explicitly check whether the connection is live when swapping to a new physical connection at commit/rollback? +ConnectionProperties.loadDataLocal=Should the driver allow use of "LOAD DATA LOCAL INFILE ..."?[CR]Setting to "true" overrides whatever path is set in ''allowLoadLocalInfileInPath'', allowing uploading files from any location. +ConnectionProperties.loadDataLocalInPath=Enables "LOAD DATA LOCAL INFILE ..." statements, but only allows loading files from the specified path. Files within sub-directories are also allowed, but relative paths or symlinks that fall outside this path are forbidden. ConnectionProperties.localSocketAddress=Hostname or IP address given to explicitly configure the interface that the driver will bind the client side of the TCP/IP connection to when connecting. ConnectionProperties.locatorFetchBufferSize=If ''emulateLocators'' is configured to ''true'', what size buffer should be used when fetching BLOB data for getBinaryInputStream? ConnectionProperties.logger=The name of a class that implements \"{0}\" that will be used to log messages to. (default is \"{1}\", which logs to STDERR) ConnectionProperties.logSlowQueries=Should queries that take longer than ''slowQueryThresholdMillis'' or detected by the ''autoSlowLog'' monitoring be reported to the registered ''profilerEventHandler''? ConnectionProperties.logXaCommands=Should the driver log XA commands sent by MysqlXaConnection to the server, at the DEBUG level of logging? ConnectionProperties.maintainTimeStats=Should the driver maintain various internal timers to enable idle time calculations as well as more verbose error messages when the connection to the server fails? Setting this property to false removes at least two calls to System.getCurrentTimeMillis() per query. +ConnectionProperties.maxAllowedPacket=Maximum allowed packet size to send to server. If not set, the value of system variable ''max_allowed_packet'' will be used to initialize this upon connecting. This value will not take effect if set larger than the value of ''max_allowed_packet''. Also, due to an internal dependency with the property "blobSendChunkSize", this setting has a minimum value of "8203" if "useServerPrepStmts" is set to "true". ConnectionProperties.maxQuerySizeToLog=Controls the maximum length of the part of a query that will get logged when profiling or tracing ConnectionProperties.maxReconnects=Maximum number of reconnects to attempt if autoReconnect is true, default is ''3''. ConnectionProperties.maxRows=The maximum number of rows to return (0, the default means return all rows). -ConnectionProperties.allVersions=all versions ConnectionProperties.metadataCacheSize=The number of queries to cache ResultSetMetadata for if cacheResultSetMetaData is set to ''true'' (default 50) ConnectionProperties.netTimeoutForStreamingResults=What value should the driver automatically set the server setting ''net_write_timeout'' to when the streaming result sets feature is in use? (value has unit of seconds, the value ''0'' means the driver will not try and adjust this value) ConnectionProperties.noAccessToProcedureBodies=When determining procedure parameter types for CallableStatements, and the connected user can''t access procedure bodies through "SHOW CREATE PROCEDURE" or select on mysql.proc should the driver instead create basic metadata (all parameters reported as INOUT VARCHARs) instead of throwing an exception? ConnectionProperties.noDatetimeStringSync=Don''t ensure that ResultSet.getDatetimeType().toString().equals(ResultSet.getString()) -ConnectionProperties.cacheDefaultTimeZone=Caches client's default time zone. This results in better performance when dealing with time zone conversions in Date and Time data types, however it won't be aware of time zone changes if they happen at runtime. ConnectionProperties.nullCatalogMeansCurrent=When DatabaseMetadata methods ask for a ''catalog'' or ''schema'' parameter, does the value null mean use the current database? See also property ''databaseTerm''. -ConnectionProperties.databaseTerm=MySQL uses the term "schema" as a synonym of the term "database," while Connector/J historically takes the JDBC term "catalog" as synonymous to "database". This property sets for Connector/J which of the JDBC terms "catalog" and "schema" is used in an application to refer to a database. The property takes one of the two values CATALOG or SCHEMA and uses it to determine (1) which Connection methods can be used to set/get the current database (e.g. setCatalog() or setSchema()?), (2) which arguments can be used within the various DatabaseMetaData methods to filter results (e.g. the catalog or schemaPattern argument of getColumns()?), and (3) which fields in the ResultSet returned by DatabaseMetaData methods contain the database identification information (i.e., the TABLE_CAT or TABLE_SCHEM field in the ResultSet returned by getTables()?).[CR]If databaseTerm=CATALOG, schemaPattern for searches are ignored and calls of schema methods (like setSchema() or get Schema()) become no-ops, and vice versa. +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.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? (defaults to ''false'') +ConnectionProperties.parseInfoCacheFactory=Name of a class implementing com.mysql.cj.CacheAdapterFactory, which will be used to create caches for the parsed representation of client-side prepared statements. +ConnectionProperties.Password=The password to use when connecting. +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. +ConnectionProperties.Password3=The password to use in the third phase of a Multi-Factor Authentication workflow. +ConnectionProperties.passwordCharacterEncoding=Instructs the server to use the default character set for the specified Java encoding during the authentication phase. If this property is not set, Connector/J falls back to the collation name specified in the property ''connectionCollation'' or to the Java encoding specified in the property ''characterEncoding'', in that order of priority. The "utf8mb4" default collation is used if none of the properties is set. ConnectionProperties.pedantic=Follow the JDBC spec to the letter. ConnectionProperties.pinGlobalTxToPhysicalConnection=When using XAConnections, should the driver ensure that operations on a given XID are always routed to the same physical connection? This allows the XAConnection to support "XA START ... JOIN" after "XA END" has been called ConnectionProperties.populateInsertRowWithDefaultValues=When using ResultSets that are CONCUR_UPDATABLE, should the driver pre-populate the "insert" row with default values from the DDL for the table used in the query so those values are immediately available for ResultSet accessors? This functionality requires a call to the database for metadata each time a result set of this type is created. If disabled (the default), the default values will be populated by the an internal call to refreshRow() which pulls back default values and/or values changed by triggers. ConnectionProperties.prepStmtCacheSize=If prepared statement caching is enabled, how many prepared statements should be cached? ConnectionProperties.prepStmtCacheSqlLimit=If prepared statement caching is enabled, what''s the largest SQL the driver will cache the parsing for? +ConnectionProperties.preserveInstants=If enabled, Connector/J does its best to preserve the instant point on the time-line for Java instant-based objects such as java.sql.Timestamp or java.time.OffsetDateTime instead of their original visual form. Otherwise, the driver always uses the JVM default time zone for rendering the values it sends to the server and for constructing the Java objects from the fetched data.[CR]MySQL uses implied time zone conversion for TIMESTAMP values: they are converted from the session time zone to UTC for storage, and back from UTC to the session time zone for retrieval. So, to store the correct correct UTC value internally, the driver converts the value from the original time zone to the session time zone before sending to the server. On retrieval, Connector/J converts the received value from the session time zone to the JVM default one.[CR]When storing, the conversion is performed only if the target SQLType, either the explicit one or the default one, is TIMESTAMP. When retrieving, the conversion is performed only if the source column has the TIMESTAMP, DATETIME or character type and the target class is an instant-based one, like java.sql.Timestamp or java.time.OffsetDateTime.[CR]Note that this option has no effect if used in conjunction with ''connectionTimeZone=LOCAL'' since, in this case, the source and target time zones are the same. Though, in this case, it's still possible to store a correct instant value if set ''forceConnectionTimeZoneToSession=true''.[CR]See also ''connectionTimeZone'' and ''forceConnectionTimeZoneToSession'' for more details. ConnectionProperties.processEscapeCodesForPrepStmts=Should the driver process escape codes in queries that are prepared? Default escape processing behavior in non-prepared statements must be defined with the property ''enableEscapeProcessing''. 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.connectionPropertiesTransform=An implementation of com.mysql.cj.conf.ConnectionPropertiesTransform that the driver will use to modify URL properties passed to the driver before attempting a connection ConnectionProperties.queriesBeforeRetrySource=Number of queries to issue before falling back to the primary host when failed over (when using multi-host failover). 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. Defaults to 50. +ConnectionProperties.queryInterceptors=A comma-delimited list of classes that implement "com.mysql.cj.interceptors.QueryInterceptor" that should be placed "in between" query execution to influence the results. QueryInterceptors are "chainable", the results returned by the "current" interceptor will be passed on to the next in in the chain, from left-to-right order, as 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. +ConnectionProperties.readOnlyPropagatesToServer=Should the driver issue appropriate statements to implicitly set the transaction access mode on server side when Connection.setReadOnly() is called? Setting this property to ''true'' enables InnoDB read-only potential optimizations but also requires an extra roundtrip to set the right transaction state. Even if this property is set to ''false'', the driver will do its best effort to prevent the execution of database-state-changing queries. Requires minimum of MySQL 5.6. ConnectionProperties.reconnectAtTxEnd=If autoReconnect is set to true, should the driver attempt reconnections at the end of every transaction? +ConnectionProperties.replicationConnectionGroup=Logical group of replication connections within a classloader, used to manage different groups independently. If not specified, live management of replication connections is disabled. ConnectionProperties.reportMetricsIntervalMillis=If ''gatherPerfMetrics'' is enabled, how often should they be logged (in ms)? ConnectionProperties.requireSSL=For 8.0.12 and earlier: Require server support of SSL connection if useSSL=true? (defaults to ''false'').[CR] For 8.0.13 and later: DEPRECATED. See sslMode property description for details. ConnectionProperties.resourceId=A globally unique name that identifies the resource that this datasource or connection is connected to, used for XAResource.isSameRM() when the driver can''t determine this value based on hostnames used in the URL @@ -879,13 +940,15 @@ ConnectionProperties.resultSetSizeThreshold=If ''useUsageAdvisor'' is true, how ConnectionProperties.retriesAllDown=When using loadbalancing or failover, the number of times the driver should cycle through available hosts, attempting to connect. Between cycles, the driver will pause for 250ms if no servers are available. ConnectionProperties.rewriteBatchedStatements=Should the driver use multiqueries (regardless of the setting of "allowMultiQueries") as well as rewriting of prepared statements for INSERT into multi-value inserts when executeBatch() is called? Notice that this has the potential for SQL injection if using plain java.sql.Statements and your code doesn''t sanitize input correctly. Notice that for prepared statements, server-side prepared statements can not currently take advantage of this rewrite option, and that if you don''t specify stream lengths when using PreparedStatement.set*Stream(), the driver won''t be able to determine the optimum number of parameters per batch and you might receive an error from the driver that the resultant packet is too large. Statement.getGeneratedKeys() for these rewritten statements only works when the entire batch includes INSERT statements. Please be aware using rewriteBatchedStatements=true with INSERT .. ON DUPLICATE KEY UPDATE that for rewritten statement server returns only one value as sum of all affected (or found) rows in batch and it isn''t possible to map it correctly to initial statements; in this case driver returns 0 as a result of each batch statement if total count was 0, and the Statement.SUCCESS_NO_INFO as a result of each batch statement if total count was > 0. ConnectionProperties.rollbackOnPooledClose=Should the driver issue a rollback() when the logical connection in a pool is closed? +ConnectionProperties.scrollTolerantForwardOnly=Should the driver contradict the JDBC API and tolerate and support backward and absolute cursor movement on result sets of type ''ResultSet.TYPE_FORWARD_ONLY''?[CR]Regardless of this setting, cursor-based and row streaming result sets cannot be navigated in the prohibited directions. ConnectionProperties.secondsBeforeRetrySource=How long should the driver wait, when failed over, before attempting to reconnect to the primary host? Whichever condition is met first, ''queriesBeforeRetrySource'' or ''secondsBeforeRetrySource'' will cause an attempt to be made to reconnect to the source host. Setting both properties to 0 disables the automatic fall back to the primary host at transaction boundaries. Time in seconds, defaults to 30 -ConnectionProperties.selfDestructOnPingSecondsLifetime=If set to a non-zero value, the driver will close the connection and report failure when Connection.ping() or Connection.isValid(int) is called if the connection''s lifetime exceeds this value (in milliseconds). ConnectionProperties.selfDestructOnPingMaxOperations=If set to a non-zero value, the driver will report close the connection and report failure when Connection.ping() or Connection.isValid(int) is called if the connection''s count of commands sent to the server exceeds this value. -ConnectionProperties.connectionTimeZone=Configures the connection time zone which is used by Connector/J if conversion between the JVM default and a target time zone is needed when preserving instant temporal values.[CR]Accepts a geographic time zone name or a time zone offset from Greenwich/UTC, using a syntax ''java.time.ZoneId'' is able to parse, or one of the two logical values "LOCAL" and "SERVER". Default is "LOCAL". If set to an explicit time zone then it must be one that either the JVM or both the JVM and MySQL support. If set to "LOCAL" then the driver assumes that the connection time zone is the same as the JVM default time zone. If set to "SERVER" then the driver attempts to detect the session time zone from the values configured on the MySQL server session variables ''time_zone'' or ''system_time_zone''. The time zone detection and subsequent mapping to a Java time zone may fail due to several reasons, mostly because of time zone abbreviations being used, in which case an explicit time zone must be set or a different time zone must be configured on the server.[CR]This option itself does not set MySQL server session variable ''time_zone'' to the given value. To do that the ''forceConnectionTimeZoneToSession'' connection option must be set to "true".[CR]Please note that setting a value to ''connectionTimeZone'' in conjunction with ''forceConnectionTimeZoneToSession=false'' and ''preserveInstants=false'' has no effect since, in this case, neither this option is used to change the session time zone nor used for time zone conversions of time-based data.[CR]Former connection option ''serverTimezone'' is still valid as an alias of this one but may be deprecated in the future.[CR]See also ''forceConnectionTimeZoneToSession'' and ''preserveInstants'' for more details. -ConnectionProperties.forceConnectionTimeZoneToSession=If enabled, sets the time zone value determined by ''connectionTimeZone'' connection property to the current server session ''time_zone'' variable. If the time zone value is given as a geographical time zone, then Connector/J sets this value as-is in the server session, in which case the time zone system tables must be populated beforehand (consult the MySQL Server documentation for further details); but, if the value is given as an offset from Greenwich/UTC in any of the supported syntaxes, then the server session time zone is set as a numeric offset from UTC.[CR]With that no intermediate conversion between JVM default time zone and connection time zone is needed to store correct milliseconds value of instant Java objects such as java.sql.Timestamp or java.time.OffsetDateTime when stored in TIMESTAMP columns.[CR]Note that it also affects the result of MySQL functions such as ''NOW()'', ''CURTIME()'' or ''CURDATE()''.[CR]This option has no effect if used in conjunction with ''connectionTimeZone=SERVER'' since, in this case, the session is already set with the required time zone.[CR]See also ''connectionTimeZone'' and ''preserveInstants'' for more details. -ConnectionProperties.preserveInstants=If enabled, Connector/J does its best to preserve the instant point on the time-line for Java instant-based objects such as java.sql.Timestamp or java.time.OffsetDateTime instead of their original visual form. Otherwise, the driver always uses the JVM default time zone for rendering the values it sends to the server and for constructing the Java objects from the fetched data.[CR]MySQL uses implied time zone conversion for TIMESTAMP values: they are converted from the session time zone to UTC for storage, and back from UTC to the session time zone for retrieval. So, to store the correct correct UTC value internally, the driver converts the value from the original time zone to the session time zone before sending to the server. On retrieval, Connector/J converts the received value from the session time zone to the JVM default one.[CR]When storing, the conversion is performed only if the target SQLType, either the explicit one or the default one, is TIMESTAMP. When retrieving, the conversion is performed only if the source column has the TIMESTAMP, DATETIME or character type and the target class is an instant-based one, like java.sql.Timestamp or java.time.OffsetDateTime.[CR]Note that this option has no effect if used in conjunction with ''connectionTimeZone=LOCAL'' since, in this case, the source and target time zones are the same. Though, in this case, it's still possible to store a correct instant value if set ''forceConnectionTimeZoneToSession=true''.[CR]See also ''connectionTimeZone'' and ''forceConnectionTimeZoneToSession'' for more details. -ConnectionProperties.scrollTolerantForwardOnly=Should the driver contradict the JDBC API and tolerate and support backward and absolute cursor movement on result sets of type ''ResultSet.TYPE_FORWARD_ONLY''?[CR]Regardless of this setting, cursor-based and row streaming result sets cannot be navigated in the prohibited directions. +ConnectionProperties.selfDestructOnPingSecondsLifetime=If set to a non-zero value, the driver will close the connection and report failure when Connection.ping() or Connection.isValid(int) is called if the connection''s lifetime exceeds this value (in milliseconds). +ConnectionProperties.sendFractionalSeconds=If set to "false", the fractional seconds will always be truncated before sending any data to the server. This option applies only to prepared statements, callable statements or updatable result sets. +ConnectionProperties.sendFractionalSecondsForTime=If set to "false", the fractional seconds of java.sql.Time will be ignored as required by JDBC specification. If set to "true", it's value is rendered with fractional seconds allowing to store milliseconds into MySQL TIME column. This option applies only to prepared statements, callable statements or updatable result sets. It has no effect if sendFractionalSeconds=false. +ConnectionProperties.serverAffinityOrder=A comma separated list containing the host/port pairs that are to be used in load-balancing "serverAffinity" strategy. Only the sub-set of the hosts enumerated in the main hosts section in this URL will be used and they must be identical in case and type, i.e., can''t use an IP address in one place and the corresponding host name in the other. +ConnectionProperties.serverConfigCacheFactory=Name of a class implementing com.mysql.cj.CacheAdapterFactory>, which will be used to create caches for MySQL server configuration values +ConnectionProperties.serverRSAPublicKeyFile=File path to the server RSA public key file for sha256_password authentication. If not specified, the public key will be retrieved from the server. ConnectionProperties.sessionVariables=A comma or semicolon separated list of name=value pairs to be sent as SET [SESSION] ... to the server when the driver connects. ConnectionProperties.slowQueryThresholdMillis=If ''logSlowQueries'' is enabled, how long should a query take (in ms) before it is logged as slow? ConnectionProperties.slowQueryThresholdNanos=If ''logSlowQueries'' is enabled, ''useNanosForElapsedTime'' is set to true, and this property is set to a non-zero value, the driver will use this threshold (in nanosecond units) to determine if a query was slow. @@ -893,18 +956,26 @@ ConnectionProperties.socketFactory=The name of the class that the driver should ConnectionProperties.socketTimeout=Timeout (in milliseconds) on network socket operations (0, the default means no timeout). ConnectionProperties.socksProxyHost=Name or IP address of SOCKS host to connect through. ConnectionProperties.socksProxyPort=Port of SOCKS server. -ConnectionProperties.queryInterceptors=A comma-delimited list of classes that implement "com.mysql.cj.interceptors.QueryInterceptor" that should be placed "in between" query execution to influence the results. QueryInterceptors are "chainable", the results returned by the "current" interceptor will be passed on to the next in in the chain, from left-to-right order, as specified in this property. +ConnectionProperties.sslMode=By default, network connections are SSL encrypted; this property permits secure connections to be turned off, or a different levels of security to be chosen. The following values are allowed: "DISABLED" - Establish unencrypted connections; "PREFERRED" - (default) Establish encrypted connections if the server enabled them, otherwise fall back to unencrypted connections; "REQUIRED" - Establish secure connections if the server enabled them, fail otherwise; "VERIFY_CA" - Like "REQUIRED" but additionally verify the server TLS certificate against the configured Certificate Authority (CA) certificates; "VERIFY_IDENTITY" - Like "VERIFY_CA", but additionally verify that the server certificate matches the host to which the connection is attempted.[CR] This property replaced the deprecated legacy properties "useSSL", "requireSSL", and "verifyServerCertificate", which are still accepted but translated into a value for "sslMode" if "sslMode" is not explicitly set: "useSSL=false" is translated to "sslMode=DISABLED"; '{'"useSSL=true", "requireSSL=false", "verifyServerCertificate=false"'}' is translated to "sslMode=PREFERRED"; '{'"useSSL=true", "requireSSL=true", "verifyServerCertificate=false"'}' is translated to "sslMode=REQUIRED"; '{'"useSSL=true" AND "verifyServerCertificate=true"'}' is translated to "sslMode=VERIFY_CA". There is no equivalent legacy settings for "sslMode=VERIFY_IDENTITY". Note that, for ALL server versions, the default setting of "sslMode" is "PREFERRED", and it is equivalent to the legacy settings of "useSSL=true", "requireSSL=false", and "verifyServerCertificate=false", which are different from their default settings for Connector/J 8.0.12 and earlier in some situations. Applications that continue to use the legacy properties and rely on their old default settings should be reviewed.[CR] The legacy properties are ignored if "sslMode" is set explicitly. If none of "sslMode" or "useSSL" is set explicitly, the default setting of "sslMode=PREFERRED" applies. ConnectionProperties.strictUpdates=Should the driver do strict checking (all primary keys selected) of updatable result sets (true, false, defaults to ''true'')? -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.tcpNoDelay=If connecting using TCP/IP, should the driver set SO_TCP_NODELAY (disabling the Nagle Algorithm)? ConnectionProperties.tcpKeepAlive=If connecting using TCP/IP, should the driver set SO_KEEPALIVE? +ConnectionProperties.tcpNoDelay=If connecting using TCP/IP, should the driver set SO_TCP_NODELAY (disabling the Nagle Algorithm)? ConnectionProperties.tcpSoRcvBuf=If connecting using TCP/IP, should the driver set SO_RCV_BUF to the given value? The default value of ''0'', means use the platform default value for this property) ConnectionProperties.tcpSoSndBuf=If connecting using TCP/IP, should the driver set SO_SND_BUF to the given value? The default value of ''0'', means use the platform default value for this property) ConnectionProperties.tcpTrafficClass=If connecting using TCP/IP, should the driver set traffic class or type-of-service fields ?See the documentation for java.net.Socket.setTrafficClass() for more information. ConnectionProperties.tinyInt1isBit=Should the driver treat the datatype TINYINT(1) as the BIT type (because the server silently converts BIT -> TINYINT(1) when creating tables)? +ConnectionProperties.tlsCiphersuites=When establishing secure connections, overrides the cipher suites enabled for use on the underlying SSL sockets. This may be required when using external JSSE providers or to specify cipher suites compatible with both MySQL server and used JVM. +ConnectionProperties.tlsVersions=List of TLS protocols to allow when establishing secure connections. Overrides the TLS protocols enabled in the underlying SSL sockets. This can be used to restrict connections to specific TLS versions and, by doing that, avoid TLS negotiation fallback. Allowed and default values are TLSv1.2, TLSv1.3. ConnectionProperties.traceProtocol=Should the network protocol be logged at the TRACE level? -ConnectionProperties.treatUtilDateAsTimestamp=Should the driver treat java.util.Date as a TIMESTAMP for the purposes of PreparedStatement.setObject()? +ConnectionProperties.trackSessionState=Receive server session state changes on query results. These changes are accessible via MysqlConnection.getServerSessionStateController(). ConnectionProperties.transformedBitIsBoolean=If the driver converts TINYINT(1) to a different type, should it use BOOLEAN instead of BIT for future compatibility with MySQL-5.0, as MySQL-5.0 has a BIT type? +ConnectionProperties.treatUtilDateAsTimestamp=Should the driver treat java.util.Date as a TIMESTAMP for the purposes of PreparedStatement.setObject()? +ConnectionProperties.trustCertificateKeyStorePassword=Password for the trusted root certificates key store. +ConnectionProperties.trustCertificateKeyStoreType=Key store type for trusted root certificates.[CR]NULL or empty means use the default, which is "JKS". Standard key store types supported by the JVM are "JKS" and "PKCS12", your environment may have more available depending on what security products are installed and available to the JVM. +ConnectionProperties.trustCertificateKeyStoreUrl=URL for the trusted root certificates key store.[CR]If not specified, the property ''fallbackToSystemTrustStore'' determines if system-wide trust store is used. +ConnectionProperties.ultraDevHack=Create PreparedStatements for prepareCall() when required, because UltraDev is broken and issues a prepareCall() for _all_ statements? (true/false, defaults to ''false'') +ConnectionProperties.useAffectedRows=Don''t set the CLIENT_FOUND_ROWS flag when connecting to the server (not JDBC-compliant, will break most applications that rely on "found" rows vs. "affected rows" for DML statements), but does cause "correct" update counts from "INSERT ... ON DUPLICATE KEY UPDATE" statements to be returned by the server. +ConnectionProperties.useColumnNamesInFindColumn=Prior to JDBC-4.0, the JDBC specification had a bug related to what could be given as a "column name" to ResultSet methods like findColumn(), or getters that took a String property. JDBC-4.0 clarified "column name" to mean the label, as given in an "AS" clause and returned by ResultSetMetaData.getColumnLabel(), and if no AS clause, the column name. Setting this property to "true" will give behavior that is congruent to JDBC-3.0 and earlier versions of the JDBC specification, but which because of the specification bug could give unexpected results. This property is preferred over "useOldAliasMetadataBehavior" unless you need the specific behavior that it provides with respect to ResultSetMetadata. ConnectionProperties.useCompression=Use zlib compression when communicating with the server (true/false)? ConnectionProperties.useConfigs=Load the comma-delimited list of configuration properties before parsing the URL or applying user-specified properties. These configurations are explained in the ''Configurations'' of the documentation. ConnectionProperties.useCursorFetch=Should the driver use cursor-based fetching to retrieve rows? If set to "true" and "defaultFetchSize" > 0 (or setFetchSize() > 0 is called on a statement) then the cursor-based result set will be used. Please note that "useServerPrepStmts" is automatically set to "true" in this case because cursor functionality is available only for server-side prepared statements. @@ -916,56 +987,19 @@ ConnectionProperties.useNanosForElapsedTime=For profiling/debugging functionalit ConnectionProperties.useOldAliasMetadataBehavior=Should the driver use the legacy behavior for "AS" clauses on columns and tables, and only return aliases (if any) for ResultSetMetaData.getColumnName() or ResultSetMetaData.getTableName() rather than the original column/table name? In 5.0.x, the default value was true. ConnectionProperties.useOnlyServerErrorMessages=Don''t prepend ''standard'' SQLState error messages to error messages returned by the server. ConnectionProperties.useReadAheadInput=Use newer, optimized non-blocking, buffered input stream when reading from the server? +ConnectionProperties.Username=The user to connect as +ConnectionProperties.useServerPrepStmts=Use server-side prepared statements if the server supports them? ConnectionProperties.useSqlStateCodes=Use SQL Standard state codes instead of ''legacy'' X/Open/SQL state codes (true/false), default is ''true'' ConnectionProperties.useSSL=For 8.0.12 and earlier: Use SSL when communicating with the server (true/false), default is ''true'' when connecting to MySQL 5.5.45+, 5.6.26+ or 5.7.6+, otherwise default is ''false''.[CR] For 8.0.13 and later: Default is ''true''. DEPRECATED. See sslMode property description for details. ConnectionProperties.useStreamLengthsInPrepStmts=Honor stream length parameter in PreparedStatement/ResultSet.setXXXStream() method calls (true/false, defaults to ''true'')? -ConnectionProperties.ultraDevHack=Create PreparedStatements for prepareCall() when required, because UltraDev is broken and issues a prepareCall() for _all_ statements? (true/false, defaults to ''false'') ConnectionProperties.useUnbufferedInput=Don''t use BufferedInputStream for reading data from the server ConnectionProperties.useUsageAdvisor=Should the driver issue ''usage'' warnings advising proper and efficient usage of JDBC and MySQL Connector/J to the ''profilerEventHandler''? ConnectionProperties.verifyServerCertificate=For 8.0.12 and earlier: If "useSSL" is set to "true", should the driver verify the server''s certificate? When using this feature, the key store parameters should be specified by the "clientCertificateKeyStore*" properties, rather than system properties. Default is ''false'' when connecting to MySQL 5.5.45+, 5.6.26+ or 5.7.6+ and "useSSL" was not explicitly set to "true". Otherwise default is ''true''.[CR] For 8.0.13 and later: Default is ''false''. DEPRECATED. See sslMode property description for details. ConnectionProperties.yearIsDateType=Should the JDBC driver treat the MySQL type "YEAR" as a java.sql.Date, or as a SHORT? ConnectionProperties.zeroDateTimeBehavior=What should happen when the driver encounters DATETIME values that are composed entirely of zeros (used by MySQL to represent invalid dates)? Valid values are \"{0}\", \"{1}\" and \"{2}\". -ConnectionProperties.clientCertificateKeyStoreUrl=URL for the client certificate KeyStore[CR]If not specified, the property ''fallbackToSystemKeyStore'' determines if system-wide key store is used. -ConnectionProperties.clientCertificateKeyStoreType=Key store type for client certificates.[CR]NULL or empty means use the default, which is "JKS". Standard key store types supported by the JVM are "JKS" and "PKCS12", your environment may have more available depending on what security products are installed and available to the JVM. -ConnectionProperties.clientCertificateKeyStorePassword=Password for the client certificates key store. -ConnectionProperties.fallbackToSystemKeyStore=Whether the absence of setting a value for ''clientCertificateKeyStoreUrl'' falls back to using the system-wide key store defined through the system properties ''javax.net.ssl.keyStore*''. -ConnectionProperties.trustCertificateKeyStoreUrl=URL for the trusted root certificates key store.[CR]If not specified, the property ''fallbackToSystemTrustStore'' determines if system-wide trust store is used. -ConnectionProperties.trustCertificateKeyStoreType=Key store type for trusted root certificates.[CR]NULL or empty means use the default, which is "JKS". Standard key store types supported by the JVM are "JKS" and "PKCS12", your environment may have more available depending on what security products are installed and available to the JVM. -ConnectionProperties.trustCertificateKeyStorePassword=Password for the trusted root certificates key store. -ConnectionProperties.fallbackToSystemTrustStore=Whether the absence of setting a value for ''trustCertificateKeyStoreUrl'' falls back to using the system-wide default trust store or one defined through the system properties ''javax.net.ssl.trustStore*''. -ConnectionProperties.serverRSAPublicKeyFile=File path to the server RSA public key file for sha256_password authentication. If not specified, the public key will be retrieved from the server. -ConnectionProperties.allowPublicKeyRetrieval=Allows special handshake round-trip to get an RSA public key directly from server. -ConnectionProperties.Username=The user to connect as -ConnectionProperties.Password=The password to use when connecting -ConnectionProperties.sendFractionalSeconds=If set to "false", the fractional seconds will always be truncated before sending any data to the server. This option applies only to prepared statements, callable statements or updatable result sets. -ConnectionProperties.sendFractionalSecondsForTime=If set to "false", the fractional seconds of java.sql.Time will be ignored as required by JDBC specification. If set to "true", it's value is rendered with fractional seconds allowing to store milliseconds into MySQL TIME column. This option applies only to prepared statements, callable statements or updatable result sets. It has no effect if sendFractionalSeconds=false. -ConnectionProperties.useColumnNamesInFindColumn=Prior to JDBC-4.0, the JDBC specification had a bug related to what could be given as a "column name" to ResultSet methods like findColumn(), or getters that took a String property. JDBC-4.0 clarified "column name" to mean the label, as given in an "AS" clause and returned by ResultSetMetaData.getColumnLabel(), and if no AS clause, the column name. Setting this property to "true" will give behavior that is congruent to JDBC-3.0 and earlier versions of the JDBC specification, but which because of the specification bug could give unexpected results. This property is preferred over "useOldAliasMetadataBehavior" unless you need the specific behavior that it provides with respect to ResultSetMetadata. -ConnectionProperties.useAffectedRows=Don''t set the CLIENT_FOUND_ROWS flag when connecting to the server (not JDBC-compliant, will break most applications that rely on "found" rows vs. "affected rows" for DML statements), but does cause "correct" update counts from "INSERT ... ON DUPLICATE KEY UPDATE" statements to be returned by the server. -ConnectionProperties.passwordCharacterEncoding=What character encoding is used for passwords? Leaving this set to the default value (null), uses the value set in "characterEncoding" if there is one, otherwise uses UTF-8 as default encoding. If the password contains non-ASCII characters, the password encoding must match what server encoding was set to when the password was created. For passwords in other character encodings, the encoding will have to be specified with this property (or with "characterEncoding"), as it''s not possible for the driver to auto-detect this. -ConnectionProperties.exceptionInterceptors=Comma-delimited list of classes that implement com.mysql.cj.exceptions.ExceptionInterceptor. These classes will be instantiated one per Connection instance, and all SQLExceptions thrown by the driver will be allowed to be intercepted by these interceptors, in a chained fashion, with the first class listed as the head of the chain. -ConnectionProperties.maxAllowedPacket=Maximum allowed packet size to send to server. If not set, the value of system variable ''max_allowed_packet'' will be used to initialize this upon connecting. This value will not take effect if set larger than the value of ''max_allowed_packet''. Also, due to an internal dependency with the property "blobSendChunkSize", this setting has a minimum value of "8203" if "useServerPrepStmts" is set to "true". -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.authenticationPlugins=Comma-delimited list of classes that implement the interface com.mysql.cj.protocol.AuthenticationPlugin. These plugins will be loaded at connection initialization and can be used together with their sever-side counterparts for authenticating users, unless they are also disabled in the connection property ''disabledAuthenticationPlugins''. -ConnectionProperties.disabledAuthenticationPlugins=Comma-delimited list of authentication plugins client-side protocol names or classes implementing the interface com.mysql.cj.protocol.AuthenticationPlugin. The authentication plugins listed will not be used for authenticating users and, if anyone of them is required during the authentication exchange, the connection fails. The default authentication plugin specified in the property ''defaultAuthenticationPlugin'' cannot be disabled. -ConnectionProperties.defaultAuthenticationPlugin=The default authentication plugin client-side protocol name or a fully qualified name of a class that implements the interface com.mysql.cj.protocol.AuthenticationPlugin. The specified authentication plugin must be either one of the built-in authentication plugins or one of the plugins listed in the property ''authenticationPlugins''. Additionally, the default authentication plugin cannot be disabled with the property ''disabledAuthenticationPlugins''. Neither an empty nor unknown plugin name or class can be set for this property.[CR]By default, Connector/J honors the server-side default authentication plugin, which is known after receiving the initial handshake packet, and falls back to this property's default value if that plugin cannot be used. However, when a value is explicitly provided to this property, Connector/J then overrides the server-side default authentication plugin and always tries first the plugin specified with this property. -ConnectionProperties.parseInfoCacheFactory=Name of a class implementing com.mysql.cj.CacheAdapterFactory, which will be used to create caches for the parsed representation of client-side prepared statements. -ConnectionProperties.serverConfigCacheFactory=Name of a class implementing com.mysql.cj.CacheAdapterFactory>, which will be used to create caches for MySQL server configuration values -ConnectionProperties.disconnectOnExpiredPasswords=If "disconnectOnExpiredPasswords" is set to "false" and password is expired then server enters "sandbox" mode and sends ERR(08001, ER_MUST_CHANGE_PASSWORD) for all commands that are not needed to set a new password until a new password is set. -ConnectionProperties.connectionAttributes=A comma-delimited list of user-defined key:value pairs (in addition to standard MySQL-defined key:value pairs) to be passed to MySQL Server for display as connection attributes in the PERFORMANCE_SCHEMA.SESSION_CONNECT_ATTRS table. Example usage: connectionAttributes=key1:value1,key2:value2 This functionality is available for use with MySQL Server version 5.6 or later only. Earlier versions of MySQL Server do not support connection attributes, causing this configuration option to be ignored. Setting connectionAttributes=none will cause connection attribute processing to be bypassed, for situations where Connection creation/initialization speed is critical. -ConnectionProperties.getProceduresReturnsFunctions=Pre-JDBC4 DatabaseMetaData API has only the getProcedures() and getProcedureColumns() methods, so they return metadata info for both stored procedures and functions. JDBC4 was extended with the getFunctions() and getFunctionColumns() methods and the expected behaviours of previous methods are not well defined. For JDBC4 and higher, default ''true'' value of the option means that calls of DatabaseMetaData.getProcedures() and DatabaseMetaData.getProcedureColumns() return metadata for both procedures and functions as before, keeping backward compatibility. Setting this property to ''false'' decouples Connector/J from its pre-JDBC4 behaviours for DatabaseMetaData.getProcedures() and DatabaseMetaData.getProcedureColumns(), forcing them to return metadata for procedures only. -ConnectionProperties.detectCustomCollations=Should the driver detect custom charsets/collations installed on server (true/false, defaults to ''false''). If this option set to ''true'' driver gets actual charsets/collations from server each time connection establishes. This could slow down connection initialization significantly. -ConnectionProperties.dontCheckOnDuplicateKeyUpdateInSQL=Stops checking if every INSERT statement contains the "ON DUPLICATE KEY UPDATE" clause. As a side effect, obtaining the statement''s generated keys information will return a list where normally it wouldn''t. Also be aware that, in this case, the list of generated keys returned may not be accurate. The effect of this property is canceled if set simultaneously with ''rewriteBatchedStatements=true''. -ConnectionProperties.readOnlyPropagatesToServer=Should the driver issue appropriate statements to implicitly set the transaction access mode on server side when Connection.setReadOnly() is called? Setting this property to ''true'' enables InnoDB read-only potential optimizations but also requires an extra roundtrip to set the right transaction state. Even if this property is set to ''false'', the driver will do its best effort to prevent the execution of database-state-changing queries. Requires minimum of MySQL 5.6. -ConnectionProperties.enabledSSLCipherSuites=If "useSSL" is set to "true", overrides the cipher suites enabled for use on the underlying SSL sockets. This may be required when using external JSSE providers or to specify cipher suites compatible with both MySQL server and used JVM. -ConnectionProperties.enabledTLSProtocols=If "useSSL" is set to "true", overrides the TLS protocols enabled for use on the underlying SSL sockets. This may be used to restrict connections to specific TLS versions. -ConnectionProperties.enableEscapeProcessing=Sets the default escape processing behavior for Statement objects. The method Statement.setEscapeProcessing() can be used to specify the escape processing behavior for an individual Statement object. Default escape processing behavior in prepared statements must be defined with the property ''processEscapeCodesForPrepStmts''. -ConnectionProperties.replicationConnectionGroup=Logical group of replication connections within a classloader, used to manage different groups independently. If not specified, live management of replication connections is disabled. -ConnectionProperties.dnsSrv=Should the driver use the given host name to lookup for DNS SRV records and use the resulting list of hosts in a multi-host failover connection? Note that a single host name and no port must be provided when this option is enabled. -ConnectionProperties.sslMode=By default, network connections are SSL encrypted; this property permits secure connections to be turned off, or a different levels of security to be chosen. The following values are allowed: "DISABLED" - Establish unencrypted connections; "PREFERRED" - (default) Establish encrypted connections if the server enabled them, otherwise fall back to unencrypted connections; "REQUIRED" - Establish secure connections if the server enabled them, fail otherwise; "VERIFY_CA" - Like "REQUIRED" but additionally verify the server TLS certificate against the configured Certificate Authority (CA) certificates; "VERIFY_IDENTITY" - Like "VERIFY_CA", but additionally verify that the server certificate matches the host to which the connection is attempted.[CR] This property replaced the deprecated legacy properties "useSSL", "requireSSL", and "verifyServerCertificate", which are still accepted but translated into a value for "sslMode" if "sslMode" is not explicitly set: "useSSL=false" is translated to "sslMode=DISABLED"; '{'"useSSL=true", "requireSSL=false", "verifyServerCertificate=false"'}' is translated to "sslMode=PREFERRED"; '{'"useSSL=true", "requireSSL=true", "verifyServerCertificate=false"'}' is translated to "sslMode=REQUIRED"; '{'"useSSL=true" AND "verifyServerCertificate=true"'}' is translated to "sslMode=VERIFY_CA". There is no equivalent legacy settings for "sslMode=VERIFY_IDENTITY". Note that, for ALL server versions, the default setting of "sslMode" is "PREFERRED", and it is equivalent to the legacy settings of "useSSL=true", "requireSSL=false", and "verifyServerCertificate=false", which are different from their default settings for Connector/J 8.0.12 and earlier in some situations. Applications that continue to use the legacy properties and rely on their old default settings should be reviewed.[CR] The legacy properties are ignored if "sslMode" is set explicitly. If none of "sslMode" or "useSSL" is set explicitly, the default setting of "sslMode=PREFERRED" applies. - ConnectionProperties.xdevapiSslMode=X DevAPI-specific SSL mode setting. If not specified, use ''sslMode''. Because the "PREFERRED" mode is not applicable to X Protocol, if ''xdevapi.ssl-mode'' is not set and ''sslMode'' is set to "PREFERRED", ''xdevapi.ssl-mode'' is set to "REQUIRED". ConnectionProperties.xdevapiTlsCiphersuites=X DevAPI-specific property overriding the cipher suites enabled for use on the underlying SSL sockets. If not specified, the value of ''enabledSSLCipherSuites'' is used. -ConnectionProperties.xdevapiTlsVersions=X DevAPI-specific property overriding the TLS protocols enabled for use on the underlying SSL sockets. If not specified, the value of ''enabledTLSProtocols'' is used. +ConnectionProperties.xdevapiTlsVersions=X DevAPI-specific property that takes a list of TLS protocols to allow when creating secure sessions. Overrides the TLS protocols enabled in the underlying SSL socket. If not specified, then the value of ''tlsVersions'' is used instead. Allowed and default values are TLSv1.2, TLSv1.3. ConnectionProperties.xdevapiSslKeyStoreUrl=X DevAPI-specific URL for the client certificate key store. If not specified, use ''clientCertificateKeyStoreUrl'' value. ConnectionProperties.xdevapiSslKeyStoreType=X DevAPI-specific type of the client certificate key store. If not specified, use ''clientCertificateKeyStoreType'' value. ConnectionProperties.xdevapiSslKeyStorePassword=X DevAPI-specific password for the client certificate key store. If not specified, use ''clientCertificateKeyStorePassword'' value. @@ -975,13 +1009,12 @@ ConnectionProperties.xdevapiSslTrustStoreType=X DevAPI-specific type of the trus ConnectionProperties.xdevapiSslTrustStorePassword=X DevAPI-specific password for the trusted CA certificates key store. If not specified, use ''trustCertificateKeyStorePassword'' value. ConnectionProperties.xdevapiFallbackToSystemTrustStore=X DevAPI-specific switch to specify whether in the absence of a set value for ''xdevapi.ssl-truststore'' (or ''trustCertificateKeyStoreUrl''), Connector/J falls back to using the system-wide default trust store or one defined through the system properties ''javax.net.ssl.trustStore*''. If not specified, the value of ''fallbackToSystemTrustStore'' is used. ConnectionProperties.auth=Authentication mechanism to use with the X Protocol. Allowed values are "SHA256_MEMORY", "MYSQL41", "PLAIN", and "EXTERNAL". Value is case insensitive. If the property is not set, the mechanism is chosen depending on the connection type: "PLAIN" is used for TLS connections and "SHA256_MEMORY" or "MYSQL41" is used for unencrypted connections. -ConnectionProperties.xdevapiConnectTimeout=X DevAPI-specific timeout for socket connect (in milliseconds), with "0" being no timeout. Defaults to "10000". If ''xdevapi.connect-timeout'' is not set explicitly and ''connectTimeout'' is, ''xdevapi.connect-timeout'' takes up the value of ''connectTimeout''. If ''xdevapi.useAsyncProtocol=true'', both ''xdevapi.connect-timeout'' and ''connectTimeout'' are ignored. +ConnectionProperties.xdevapiConnectTimeout=X DevAPI-specific timeout for socket connect (in milliseconds), with "0" being no timeout. Defaults to "10000". If ''xdevapi.connect-timeout'' is not set explicitly and ''connectTimeout'' is, ''xdevapi.connect-timeout'' takes up the value of ''connectTimeout''. ConnectionProperties.xdevapiConnectionAttributes=An X DevAPI-specific comma-delimited list of user-defined key=value pairs (in addition to standard X Protocol-defined key=value pairs) to be passed to MySQL Server for display as connection attributes in PERFORMANCE_SCHEMA tables session_account_connect_attrs and session_connect_attrs. Example usage: xdevapi.connection-attributes=key1=value1,key2=value2 or xdevapi.connection-attributes=[key1=value1,key2=value2]. This functionality is available for use with MySQL Server version 8.0.16 or later only. Earlier versions of X Protocol do not support connection attributes, causing this configuration option to be ignored. For situations where Session creation/initialization speed is critical, setting xdevapi.connection-attributes=false will cause connection attribute processing to be bypassed. ConnectionProperties.xdevapiDnsSrv=X DevAPI-specific option for instructing the driver use the given host name to lookup for DNS SRV records and use the resulting list of hosts in a multi-host failover connection. Note that a single host name and no port must be provided when this option is enabled. ConnectionProperties.xdevapiCompression=X DevAPI-specific network traffic compression. This option accepts one of the three values: "PREFERRED", "REQUIRED", and "DISABLED". Setting this option to "PREFERRED" or "REQUIRED" enables compression algorithm negotiation between Connector and Server, and turns on compression of large X Protocol packets, as long as a consensus is reached between client and server regarding the compression algorithm to use. If a consensus cannot be reached, connection fails if the option is set to "REQUIRED" and continues without compression if the option is set to "PREFERRED". Setting this option as "DISABLED" skips the compression negotiation phase and forbids the interchange of compressed messages between client and server. ConnectionProperties.xdevapiCompressionAlgorithms=A comma-delimited list of compression algorithms, each one identified by its name and operating mode (e.g. "lz4_message" -- consult the description for the MySQL global variable ''mysqlx_compression_algorithms'' for a list of supported and enabled algorithms), that defines the order and which algorithms will be attempted when negotiating connection compression with the server.[CR]The compression algorithm ''deflate_stream'' is supported natively. Additional compression algorithms require using third-party libraries and enabling them with the connection property ''xdevapi.compression-extensions''.[CR]This option is meaningful only when network traffic compression is enabled using the connection property ''xdevapi.compression''.[CR]As an alternative to the default algorithm names, that contain a reference to the compression operation mode, the aliases "zstd", "lz4", and "deflate" can be used instead of "zstd_stream", "lz4_message", and "deflate_stream". ConnectionProperties.xdevapiCompressionExtensions=A comma-delimited list of triplets, with their elements delimited by colon, that enables the support for additional compression algorithms. Each triplet must contain: first, an algorithm name and operating mode (e.g. "lz4_message" -- consult the description for the MySQL global variable ''mysqlx_compression_algorithms'' for a list of supported and enabled algorithms); second, a fully-qualified class name of a class implementing the interface java.io.InputStream that will be used to inflate data compressed with the named algorithm; third, a fully-qualified class name of a class implementing the interface java.io.OutputStream that will be used to deflate data using the named algorithm. Along with this setting, the library containing implementations of the designated classes must be available in the application's class path.[CR]Any number of triplets defining compression algorithms and their inflater and deflater implementations can be provided but only the ones supported and enabled on the MySQL Server can be used.[CR]The compression algorithm ''deflate_stream'' is supported natively. Additional compression algorithms require using third-party libraries.[CR]This option is meaningful only when network traffic compression is enabled using the connection property ''xdevapi.compression''.[CR]As an alternative to the default algorithm names, that contain a reference to the compression operation mode, the aliases "zstd", "lz4", and "deflate" can be used instead of "zstd_stream", "lz4_message", and "deflate_stream". -ConnectionProperties.useAsyncProtocol=For 8.0.21 and earlier: Use asynchronous variant of X Protocol.[CR]For 8.0.22 and later: DEPRECATED; has no effect. ConnectionProperties.asyncResponseTimeout=For 8.0.21 and earlier: Timeout (in seconds) for getting server response via X Protocol.[CR]For 8.0.22 and later: DEPRECATED; has no effect. ConnectionProperties.unknown=Property is not defined in Connector/J but used in connection URL. diff --git a/src/main/resources/com/mysql/cj/TlsSettings.properties b/src/main/resources/com/mysql/cj/TlsSettings.properties index 1e6847830..df6f68bab 100644 --- a/src/main/resources/com/mysql/cj/TlsSettings.properties +++ b/src/main/resources/com/mysql/cj/TlsSettings.properties @@ -1,4 +1,4 @@ -# Copyright (c) 2019, 2020, Oracle and/or its affiliates. +# Copyright (c) 2019, 2021, 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 @@ -27,84 +27,100 @@ # Mandatory TLS Ciphers TLSCiphers.Mandatory=\ - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\ - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,\ - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\ + ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,\ + ECDHE_RSA_WITH_AES_128_GCM_SHA256 # Approved TLS Ciphers TLSCiphers.Approved=\ - TLS_AES_128_GCM_SHA256,\ - TLS_AES_256_GCM_SHA384,\ - TLS_CHACHA20_POLY1305_SHA256,\ - TLS_AES_128_CCM_SHA256,\ - TLS_AES_128_CCM_8_SHA256,\ - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,\ - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,\ - TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,\ - TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,\ - TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,\ - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,\ - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,\ - TLS_DH_DSS_WITH_AES_128_GCM_SHA256,\ - TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,\ - TLS_DH_DSS_WITH_AES_256_GCM_SHA384,\ - TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,\ - TLS_DH_RSA_WITH_AES_128_GCM_SHA256,\ - TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,\ - TLS_DH_RSA_WITH_AES_256_GCM_SHA384,\ - TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 + AES_128_GCM_SHA256,\ + AES_256_GCM_SHA384,\ + CHACHA20_POLY1305_SHA256,\ + AES_128_CCM_SHA256,\ + AES_128_CCM_8_SHA256,\ + ECDHE_RSA_WITH_AES_256_GCM_SHA384,\ + DHE_RSA_WITH_AES_128_GCM_SHA256,\ + DHE_DSS_WITH_AES_128_GCM_SHA256,\ + DHE_DSS_WITH_AES_256_GCM_SHA384,\ + DHE_RSA_WITH_AES_256_GCM_SHA384,\ + ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,\ + ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,\ + ECDHE_ECDSA_WITH_AES_256_CCM,\ + ECDHE_ECDSA_WITH_AES_128_CCM,\ + DHE_RSA_WITH_AES_256_CCM,\ + DHE_RSA_WITH_AES_128_CCM,\ + DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,\ + ECDHE_ECDSA_WITH_AES_256_CCM_8,\ + ECDHE_ECDSA_WITH_AES_128_CCM_8,\ + DHE_RSA_WITH_AES_256_CCM_8,\ + DHE_RSA_WITH_AES_128_CCM_8 # Deprecated TLS Ciphers TLSCiphers.Deprecated=\ - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,\ - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,\ - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,\ - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,\ - TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,\ - TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,\ - TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,\ - TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,\ - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,\ - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,\ - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,\ - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,\ - TLS_DHE_DSS_WITH_AES_128_CBC_SHA,\ - TLS_DHE_RSA_WITH_AES_128_CBC_SHA,\ - TLS_DHE_DSS_WITH_AES_256_CBC_SHA,\ - TLS_DHE_RSA_WITH_AES_256_CBC_SHA,\ - TLS_DH_RSA_WITH_AES_128_CBC_SHA256,\ - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,\ - TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,\ - TLS_DH_RSA_WITH_AES_256_CBC_SHA256,\ - TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,\ - TLS_DH_DSS_WITH_AES_128_CBC_SHA256,\ - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,\ - TLS_DH_DSS_WITH_AES_128_CBC_SHA,\ - TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,\ - TLS_DH_DSS_WITH_AES_256_CBC_SHA,\ - TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,\ - TLS_DH_DSS_WITH_AES_256_CBC_SHA256,\ - TLS_DH_RSA_WITH_AES_128_CBC_SHA,\ - TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,\ - TLS_DH_RSA_WITH_AES_256_CBC_SHA,\ - TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,\ - TLS_RSA_WITH_AES_128_GCM_SHA256,\ - TLS_RSA_WITH_AES_256_GCM_SHA384,\ - TLS_RSA_WITH_AES_128_CBC_SHA256,\ - TLS_RSA_WITH_AES_256_CBC_SHA256,\ - TLS_RSA_WITH_AES_128_CBC_SHA,\ - TLS_RSA_WITH_AES_256_CBC_SHA,\ - TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,\ - TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,\ - TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,\ - TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,\ - TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,\ - TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,\ - TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,\ - TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,\ - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,\ - TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,\ - TLS_RSA_WITH_3DES_EDE_CBC_SHA + ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,\ + ECDHE_RSA_WITH_AES_128_CBC_SHA256,\ + ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,\ + ECDHE_RSA_WITH_AES_256_CBC_SHA384,\ + DHE_DSS_WITH_AES_128_CBC_SHA256,\ + DHE_DSS_WITH_AES_256_CBC_SHA256,\ + DHE_RSA_WITH_AES_256_CBC_SHA256,\ + DHE_RSA_WITH_AES_128_CBC_SHA256,\ + DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256,\ + DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,\ + ECDHE_RSA_WITH_AES_128_CBC_SHA,\ + ECDHE_ECDSA_WITH_AES_128_CBC_SHA,\ + ECDHE_RSA_WITH_AES_256_CBC_SHA,\ + ECDHE_ECDSA_WITH_AES_256_CBC_SHA,\ + DHE_DSS_WITH_AES_128_CBC_SHA,\ + DHE_RSA_WITH_AES_128_CBC_SHA,\ + DHE_RSA_WITH_AES_256_CBC_SHA,\ + DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,\ + RSA_WITH_CAMELLIA_128_CBC_SHA,\ + DH_RSA_WITH_AES_128_CBC_SHA256,\ + ECDH_ECDSA_WITH_AES_128_CBC_SHA256,\ + ECDH_RSA_WITH_AES_128_CBC_SHA256,\ + DH_RSA_WITH_AES_256_CBC_SHA256,\ + ECDH_RSA_WITH_AES_256_CBC_SHA384,\ + DH_DSS_WITH_AES_128_CBC_SHA256,\ + ECDH_ECDSA_WITH_AES_256_CBC_SHA384,\ + DH_DSS_WITH_AES_128_CBC_SHA,\ + ECDH_ECDSA_WITH_AES_128_CBC_SHA,\ + DH_DSS_WITH_AES_256_CBC_SHA,\ + ECDH_ECDSA_WITH_AES_256_CBC_SHA,\ + DH_DSS_WITH_AES_256_CBC_SHA256,\ + DH_RSA_WITH_AES_128_CBC_SHA,\ + ECDH_RSA_WITH_AES_128_CBC_SHA,\ + DH_RSA_WITH_AES_256_CBC_SHA,\ + ECDH_RSA_WITH_AES_256_CBC_SHA,\ + RSA_WITH_AES_128_GCM_SHA256,\ + RSA_WITH_AES_128_CCM,\ + RSA_WITH_AES_128_CCM_8,\ + RSA_WITH_AES_256_GCM_SHA384,\ + RSA_WITH_AES_256_CCM,\ + RSA_WITH_AES_256_CCM_8,\ + RSA_WITH_AES_128_CBC_SHA256,\ + RSA_WITH_AES_256_CBC_SHA256,\ + RSA_WITH_AES_128_CBC_SHA,\ + RSA_WITH_AES_256_CBC_SHA,\ + RSA_WITH_CAMELLIA_256_CBC_SHA,\ + RSA_WITH_CAMELLIA_128_CBC_SHA,\ + DH_DSS_WITH_AES_128_GCM_SHA256,\ + ECDH_ECDSA_WITH_AES_128_GCM_SHA256,\ + DH_DSS_WITH_AES_256_GCM_SHA384,\ + ECDH_ECDSA_WITH_AES_256_GCM_SHA384,\ + DH_RSA_WITH_AES_128_GCM_SHA256,\ + ECDH_RSA_WITH_AES_128_GCM_SHA256,\ + DH_RSA_WITH_AES_256_GCM_SHA384,\ + ECDH_RSA_WITH_AES_256_GCM_SHA384,\ + DH_DSS_WITH_3DES_EDE_CBC_SHA,\ + DH_RSA_WITH_3DES_EDE_CBC_SHA,\ + DHE_DSS_WITH_3DES_EDE_CBC_SHA,\ + DHE_RSA_WITH_3DES_EDE_CBC_SHA,\ + ECDH_RSA_WITH_3DES_EDE_CBC_SHA,\ + ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,\ + ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,\ + ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,\ + RSA_WITH_3DES_EDE_CBC_SHA # Unacceptable TLS Ciphers TLSCiphers.Unacceptable.Mask=_ANON_,_NULL_,_EXPORT,_MD5,_DES,_RC2_,_RC4_,_PSK_ diff --git a/src/main/user-api/java/com/mysql/cj/jdbc/JdbcStatement.java b/src/main/user-api/java/com/mysql/cj/jdbc/JdbcStatement.java index d80b877ad..ff3ccf89f 100644 --- a/src/main/user-api/java/com/mysql/cj/jdbc/JdbcStatement.java +++ b/src/main/user-api/java/com/mysql/cj/jdbc/JdbcStatement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. + * Copyright (c) 2007, 2021, 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 @@ -123,4 +123,8 @@ public interface JdbcStatement extends java.sql.Statement, Query { void setHoldResultsOpenOverClose(boolean holdResultsOpenOverClose); Query getQuery(); + + void setAttribute(String name, Object value); + + void clearAttributes(); } 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 9b4fce5eb..251008992 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -58,6 +58,7 @@ import com.mysql.cj.Messages; import com.mysql.cj.MysqlType; +import com.mysql.cj.ParseInfo; import com.mysql.cj.PreparedQuery; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; import com.mysql.cj.conf.PropertyKey; @@ -71,6 +72,7 @@ import com.mysql.cj.result.DefaultColumnDefinition; import com.mysql.cj.result.Field; import com.mysql.cj.result.Row; +import com.mysql.cj.util.SearchMode; import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.Util; @@ -530,7 +532,7 @@ private void generateParameterMap() throws SQLException { int parenOpenPos = q.getOriginalSql().indexOf('(', startPos + 4); if (parenOpenPos != -1) { - int parenClosePos = StringUtils.indexOfIgnoreCase(parenOpenPos, q.getOriginalSql(), ")", "'", "'", StringUtils.SEARCH_MODE__ALL); + int parenClosePos = StringUtils.indexOfIgnoreCase(parenOpenPos, q.getOriginalSql(), ")", "'", "'", SearchMode.__FULL); if (parenClosePos != -1) { List parsedParameters = StringUtils.split(q.getOriginalSql().substring(parenOpenPos + 1, parenClosePos), ",", "'\"", "'\"", @@ -705,8 +707,8 @@ public void clearParameters() throws SQLException { */ private void fakeParameterTypes(boolean isReallyProcedure) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - String encoding = this.connection.getSession().getServerSession().getCharacterSetMetadata(); - int collationIndex = this.connection.getSession().getServerSession().getMetadataCollationIndex(); + String encoding = this.connection.getSession().getServerSession().getCharsetSettings().getMetadataEncoding(); + int collationIndex = this.connection.getSession().getServerSession().getCharsetSettings().getMetadataCollationIndex(); Field[] fields = new Field[13]; fields[0] = new Field("", "PROCEDURE_CAT", collationIndex, encoding, MysqlType.CHAR, 0); @@ -900,7 +902,8 @@ public int executeUpdate() throws SQLException { } private String extractProcedureName() throws SQLException { - String sanitizedSql = StringUtils.stripComments(((PreparedQuery) this.query).getOriginalSql(), "`\"'", "`\"'", true, false, true, true); + String sanitizedSql = StringUtils.stripCommentsAndHints(((PreparedQuery) this.query).getOriginalSql(), "`'\"", "`'\"", + !this.session.getServerSession().isNoBackslashEscapesSet()); // TODO: Do this with less memory allocation int endCallIndex = StringUtils.indexOfIgnoreCase(sanitizedSql, "CALL "); @@ -2330,7 +2333,20 @@ private boolean checkReadOnlyProcedure() throws SQLException { @Override protected boolean checkReadOnlySafeStatement() throws SQLException { - return (super.checkReadOnlySafeStatement() || this.checkReadOnlyProcedure()); + if (ParseInfo.isReadOnlySafeQuery(((PreparedQuery) this.query).getOriginalSql(), this.session.getServerSession().isNoBackslashEscapesSet())) { + String sql = ((PreparedQuery) this.query).getOriginalSql(); + int statementKeywordPos = ParseInfo.indexOfStatementKeyword(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + if (StringUtils.startsWithIgnoreCaseAndWs(sql, "CALL", statementKeywordPos) + || StringUtils.startsWithIgnoreCaseAndWs(sql, "SELECT", statementKeywordPos)) { + // "CALL" and "SELECT" are read-only safe but the routine they call may not be. + if (!this.connection.isReadOnly()) { // Only proceed with checking the routine body if really needed. + return true; + } + return checkReadOnlyProcedure(); + } + return true; + } + return !this.connection.isReadOnly(); } @Override 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 4127b0495..c2fa0832a 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 @@ -63,6 +63,7 @@ import com.mysql.cj.PreparedQuery; import com.mysql.cj.Query; import com.mysql.cj.QueryBindings; +import com.mysql.cj.QueryReturnType; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJException; import com.mysql.cj.exceptions.FeatureNotAvailableException; @@ -80,7 +81,6 @@ import com.mysql.cj.protocol.Message; import com.mysql.cj.protocol.a.NativePacketPayload; import com.mysql.cj.result.Field; -import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.Util; /** @@ -304,7 +304,8 @@ public void clearParameters() throws SQLException { */ protected boolean checkReadOnlySafeStatement() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - return ((PreparedQuery) this.query).getParseInfo().getFirstStmtChar() == 'S' || !this.connection.isReadOnly(); + return ParseInfo.isReadOnlySafeQuery(((PreparedQuery) this.query).getOriginalSql(), this.session.getServerSession().isNoBackslashEscapesSet()) + || !this.connection.isReadOnly(); } } @@ -365,10 +366,9 @@ public boolean execute() throws SQLException { // // Only apply max_rows to selects // - locallyScopedConn.setSessionMaxRows(((PreparedQuery) this.query).getParseInfo().getFirstStmtChar() == 'S' ? this.maxRows : -1); + locallyScopedConn.setSessionMaxRows(getParseInfo().getFirstStmtChar() == 'S' ? this.maxRows : -1); - rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), - (((PreparedQuery) this.query).getParseInfo().getFirstStmtChar() == 'S'), cachedMetadata, false); + rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), (getParseInfo().getFirstStmtChar() == 'S'), cachedMetadata, false); if (cachedMetadata != null) { locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery) this.query).getOriginalSql(), cachedMetadata, rs); @@ -379,7 +379,7 @@ public boolean execute() throws SQLException { } if (this.retrieveGeneratedKeys) { - rs.setFirstCharOfQuery(((PreparedQuery) this.query).getParseInfo().getFirstStmtChar()); + rs.setFirstCharOfQuery(getParseInfo().getFirstStmtChar()); } if (oldDb != null) { @@ -422,7 +422,7 @@ protected long[] executeBatchInternal() throws SQLException { if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) { - if (((PreparedQuery) this.query).getParseInfo().canRewriteAsMultiValueInsertAtSqlLevel()) { + if (getParseInfo().canRewriteAsMultiValueInsertAtSqlLevel()) { return executeBatchedInserts(batchTimeout); } @@ -486,7 +486,7 @@ protected long[] executePreparedBatchAsMultiStatement(int batchTimeout) throws S int numberToExecuteAsMultiValue = 0; int batchCounter = 0; int updateCountCounter = 0; - long[] updateCounts = new long[numBatchedArgs * ((PreparedQuery) this.query).getParseInfo().numberOfQueries]; + long[] updateCounts = new long[numBatchedArgs * getParseInfo().getNumberOfQueries()]; SQLException sqlEx = null; try { @@ -653,7 +653,7 @@ private String generateMultiStatementForBatch(int numBatches) throws SQLExceptio */ protected long[] executeBatchedInserts(int batchTimeout) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - String valuesClause = ((PreparedQuery) this.query).getParseInfo().getValuesClause(); + String valuesClause = getParseInfo().getValuesClause(); JdbcConnection locallyScopedConn = this.connection; @@ -959,7 +959,13 @@ public java.sql.ResultSet executeQuery() throws SQLException { JdbcConnection locallyScopedConn = this.connection; - checkForDml(((PreparedQuery) this.query).getOriginalSql(), ((PreparedQuery) this.query).getParseInfo().getFirstStmtChar()); + if (!this.doPingInstead) { + QueryReturnType queryReturnType = getParseInfo().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; @@ -1065,7 +1071,7 @@ protected long executeUpdateInternal(QueryBindings bindings, boolean isReally MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor); } - if ((((PreparedQuery) this.query).getParseInfo().getFirstStmtChar() == 'S') && isSelectQuery()) { + if (!isNonResultSetProducingQuery()) { throw SQLError.createSQLException(Messages.getString("PreparedStatement.37"), "01S03", this.exceptionInterceptor); } @@ -1092,7 +1098,7 @@ protected long executeUpdateInternal(QueryBindings bindings, boolean isReally rs = executeInternal(-1, sendPacket, false, false, null, isReallyBatch); if (this.retrieveGeneratedKeys) { - rs.setFirstCharOfQuery(((PreparedQuery) this.query).getParseInfo().getFirstStmtChar()); + rs.setFirstCharOfQuery(getParseInfo().getFirstStmtChar()); } if (oldDb != null) { @@ -1116,7 +1122,7 @@ protected long executeUpdateInternal(QueryBindings bindings, boolean isReally } protected boolean containsOnDuplicateKeyUpdateInSQL() { - return ((PreparedQuery) this.query).getParseInfo().containsOnDuplicateKeyUpdateInSQL(); + return getParseInfo().containsOnDuplicateKeyUpdateInSQL(); } /** @@ -1133,10 +1139,12 @@ protected boolean containsOnDuplicateKeyUpdateInSQL() { 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(), ((PreparedQuery) this.query).getParseInfo().getParseInfoForBatch(numBatches)); + this.getCurrentDatabase(), getParseInfo().getParseInfoForBatch(numBatches)); pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys); pstmt.rewrittenBatchSize = numBatches; + getQueryAttributesBindings().runThroughAll(a -> ((JdbcStatement) pstmt).setAttribute(a.getName(), a.getValue())); + return pstmt; } } @@ -1172,7 +1180,7 @@ public java.sql.ResultSetMetaData getMetaData() throws SQLException { // CALL's are trapped further up and you end up with a CallableStatement anyway. // - if (!isSelectQuery()) { + if (!isResultSetProducingQuery()) { return null; } @@ -1182,7 +1190,7 @@ public java.sql.ResultSetMetaData getMetaData() throws SQLException { if (this.pstmtResultMetaData == null) { try { mdStmt = new ClientPreparedStatement(this.connection, ((PreparedQuery) this.query).getOriginalSql(), this.getCurrentDatabase(), - ((PreparedQuery) this.query).getParseInfo()); + getParseInfo()); mdStmt.setMaxRows(1); @@ -1236,11 +1244,26 @@ public java.sql.ResultSetMetaData getMetaData() throws SQLException { } } - protected boolean isSelectQuery() throws SQLException { - synchronized (checkClosed().getConnectionMutex()) { - return StringUtils.startsWithIgnoreCaseAndWs( - StringUtils.stripComments(((PreparedQuery) this.query).getOriginalSql(), "'\"", "'\"", true, false, true, true), "SELECT"); - } + /** + * Checks if the given SQL query is a result set producing query. + * + * @return + * true if the query produces a result set, false otherwise. + */ + protected boolean isResultSetProducingQuery() { + QueryReturnType queryReturnType = getParseInfo().getQueryReturnType(); + return queryReturnType == QueryReturnType.PRODUCES_RESULT_SET || queryReturnType == QueryReturnType.MAY_PRODUCE_RESULT_SET; + } + + /** + * Checks if the given SQL query does not return a result set. + * + * @return + * true if the query does not produce a result set, false otherwise. + */ + private boolean isNonResultSetProducingQuery() { + QueryReturnType queryReturnType = getParseInfo().getQueryReturnType(); + return queryReturnType == QueryReturnType.DOES_NOT_PRODUCE_RESULT_SET || queryReturnType == QueryReturnType.MAY_PRODUCE_RESULT_SET; } @Override @@ -1268,10 +1291,10 @@ public ParseInfo getParseInfo() { private void initializeFromParseInfo() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - int parameterCount = ((PreparedQuery) this.query).getParseInfo().getStaticSql().length - 1; + int parameterCount = getParseInfo().getStaticSql().length - 1; ((PreparedQuery) this.query).setParameterCount(parameterCount); ((PreparedQuery) this.query).setQueryBindings(new ClientPreparedQueryBindings(parameterCount, this.session)); - ((ClientPreparedQuery) this.query).getQueryBindings().setLoadDataQuery(((PreparedQuery) this.query).getParseInfo().isFoundLoadData()); + ((ClientPreparedQuery) this.query).getQueryBindings().setLoadDataQuery(getParseInfo().isLoadData()); clearParameters(); } @@ -1323,7 +1346,7 @@ public String getPreparedSql() { } try { - return ((PreparedQuery) this.query).getParseInfo().getSqlForBatch(); + return getParseInfo().getSqlForBatch(); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } 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 b8aa34781..2a149e0a4 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 @@ -70,6 +70,7 @@ import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.RuntimeProperty; +import com.mysql.cj.exceptions.CJCommunicationsException; import com.mysql.cj.exceptions.CJException; import com.mysql.cj.exceptions.ExceptionFactory; import com.mysql.cj.exceptions.ExceptionInterceptor; @@ -89,6 +90,7 @@ 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.util.LRUCache; import com.mysql.cj.util.StringUtils; @@ -201,12 +203,6 @@ public int hashCode() { } } - /** - * The mapping between MySQL charset names and Java charset names. - * Initialized by loadCharacterSetMapping() - */ - public static Map charsetMap; - /** Default logger class name */ protected static final String DEFAULT_LOGGER_CLASS = StandardLogger.class.getName(); @@ -560,7 +556,7 @@ public void changeUser(String userName, String newPassword) throws SQLException this.user = userName; this.password = newPassword; - this.session.configureClientCharacterSet(true); + this.session.getServerSession().getCharsetSettings().configurePostHandshake(true); this.session.setSessionVariables(); @@ -1133,7 +1129,7 @@ public String getCatalog() throws SQLException { @Override public String getCharacterSetMetadata() { synchronized (getConnectionMutex()) { - return this.session.getServerSession().getCharacterSetMetadata(); + return this.session.getServerSession().getCharsetSettings().getMetadataEncoding(); } } @@ -1176,8 +1172,8 @@ private java.sql.DatabaseMetaData getMetaData(boolean checkClosed, boolean check this.nullStatementResultSetFactory); if (getSession() != null && getSession().getProtocol() != null) { - dbmeta.setMetadataEncoding(getSession().getServerSession().getCharacterSetMetadata()); - dbmeta.setMetadataCollationIndex(getSession().getServerSession().getMetadataCollationIndex()); + dbmeta.setMetadataEncoding(getSession().getServerSession().getCharsetSettings().getMetadataEncoding()); + dbmeta.setMetadataCollationIndex(getSession().getServerSession().getCharsetSettings().getMetadataCollationIndex()); } return dbmeta; @@ -1304,8 +1300,6 @@ private void initializePropsFromServer() throws SQLException { this.autoIncrementIncrement = this.session.getServerSession().getServerVariable("auto_increment_increment", 1); - this.session.buildCollationMapping(); - try { LicenseConfiguration.checkLicenseType(this.session.getServerSession().getServerVariables()); } catch (CJException e) { @@ -1316,20 +1310,11 @@ private void initializePropsFromServer() throws SQLException { checkTransactionIsolationLevel(); - this.session.checkForCharsetMismatch(); - - this.session.configureClientCharacterSet(false); - handleAutoCommitDefaults(); - // - // We need to figure out what character set metadata and error messages will be returned in, and then map them to Java encoding names - // - // We've already set it, and it might be different than what was originally on the server, which is why we use the "special" key to retrieve it - this.session.getServerSession().configureCharacterSets(); - - ((com.mysql.cj.jdbc.DatabaseMetaData) this.dbmd).setMetadataEncoding(getSession().getServerSession().getCharacterSetMetadata()); - ((com.mysql.cj.jdbc.DatabaseMetaData) this.dbmd).setMetadataCollationIndex(getSession().getServerSession().getMetadataCollationIndex()); + ((com.mysql.cj.jdbc.DatabaseMetaData) this.dbmd).setMetadataEncoding(this.session.getServerSession().getCharsetSettings().getMetadataEncoding()); + ((com.mysql.cj.jdbc.DatabaseMetaData) this.dbmd) + .setMetadataCollationIndex(this.session.getServerSession().getCharsetSettings().getMetadataCollationIndex()); // // Server can do this more efficiently for us @@ -2033,10 +2018,10 @@ void forEach(ConnectionLifecycleInterceptor each) throws SQLException { this.autoReconnect.setValue(true); } + boolean isAutocommit = this.session.getServerSession().isAutocommit(); try { boolean needsSetOnServer = true; - - if (this.useLocalSessionState.getValue() && this.session.getServerSession().isAutoCommit() == autoCommitFlag) { + if (this.useLocalSessionState.getValue() && isAutocommit == autoCommitFlag) { needsSetOnServer = false; } else if (!this.autoReconnect.getValue()) { needsSetOnServer = getSession().isSetNeededForAutoCommitMode(autoCommitFlag); @@ -2051,6 +2036,13 @@ void forEach(ConnectionLifecycleInterceptor each) throws SQLException { this.session.execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, false, this.nullStatementResultSetFactory, null, false); } + } catch (CJCommunicationsException e) { + throw e; + } catch (CJException e) { + // Reset to current autocommit value in case of an exception different than a communication exception occurs. + this.session.getServerSession().setAutoCommit(isAutocommit); + // Update the stacktrace. + throw SQLError.createSQLException(e.getMessage(), e.getSQLState(), e.getVendorCode(), e.isTransient(), e, getExceptionInterceptor()); } finally { if (this.autoReconnectForPools.getValue()) { this.autoReconnect.setValue(false); @@ -2700,4 +2692,9 @@ public void handleCleanup(Throwable whyCleanedUp) { cleanup(whyCleanedUp); } + @Override + public ServerSessionStateController getServerSessionStateController() { + return this.session.getServerSession().getServerSessionStateController(); + } + } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionWrapper.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionWrapper.java index 367cdb197..4b0202058 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionWrapper.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ConnectionWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -59,6 +59,7 @@ import com.mysql.cj.jdbc.exceptions.SQLError; import com.mysql.cj.jdbc.result.CachedResultSetMetaData; import com.mysql.cj.jdbc.result.ResultSetInternalMethods; +import com.mysql.cj.protocol.ServerSessionStateController; /** * This class serves as a wrapper for the connection object. It is returned to the application server which may wrap it again and then return it to the @@ -1243,4 +1244,8 @@ public void cleanup(Throwable whyCleanedUp) { this.mc.cleanup(whyCleanedUp); } + @Override + public ServerSessionStateController getServerSessionStateController() { + return this.mc.getServerSessionStateController(); + } } 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 02309a52b..bade0ecfd 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 @@ -72,6 +72,7 @@ import com.mysql.cj.result.DefaultColumnDefinition; import com.mysql.cj.result.Field; import com.mysql.cj.result.Row; +import com.mysql.cj.util.SearchMode; import com.mysql.cj.util.StringUtils; /** @@ -1060,13 +1061,14 @@ public List extractForeignKeyForTable(ArrayList rows, java.sql.ResultS if (indexOfFK != -1) { int afterFk = indexOfFK + "FOREIGN KEY".length(); - int indexOfRef = StringUtils.indexOfIgnoreCase(afterFk, line, "REFERENCES", this.quotedId, this.quotedId, StringUtils.SEARCH_MODE__ALL); + int indexOfRef = StringUtils.indexOfIgnoreCase(afterFk, line, "REFERENCES", this.quotedId, this.quotedId, + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfRef != -1) { int indexOfParenOpen = line.indexOf('(', afterFk); int indexOfParenClose = StringUtils.indexOfIgnoreCase(indexOfParenOpen, line, ")", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__ALL); + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfParenOpen == -1 || indexOfParenClose == -1) { // throw SQLError.createSQLException(); @@ -1077,20 +1079,20 @@ public List extractForeignKeyForTable(ArrayList rows, java.sql.ResultS int afterRef = indexOfRef + "REFERENCES".length(); int referencedColumnBegin = StringUtils.indexOfIgnoreCase(afterRef, line, "(", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__ALL); + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (referencedColumnBegin != -1) { referencedTableName = line.substring(afterRef, referencedColumnBegin); int referencedColumnEnd = StringUtils.indexOfIgnoreCase(referencedColumnBegin + 1, line, ")", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__ALL); + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (referencedColumnEnd != -1) { referencedColumnName = line.substring(referencedColumnBegin + 1, referencedColumnEnd); } int indexOfDbSep = StringUtils.indexOfIgnoreCase(0, referencedTableName, ".", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__ALL); + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfDbSep != -1) { referencedDbName = referencedTableName.substring(0, indexOfDbSep); @@ -1435,7 +1437,7 @@ private void getCallStmtParameterTypes(String db, String quotedProcName, Procedu int dotIndex = " ".equals(this.quotedId) ? quotedProcName.indexOf(".") : StringUtils.indexOfIgnoreCase(0, quotedProcName, ".", this.quotedId, this.quotedId, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); String dbName = null; @@ -1495,10 +1497,11 @@ private void getCallStmtParameterTypes(String db, String quotedProcName, Procedu if (procedureDef != null && procedureDef.length() != 0) { // sanitize/normalize by stripping out comments - procedureDef = StringUtils.stripComments(procedureDef, identifierAndStringMarkers, identifierAndStringMarkers, true, false, true, true); + procedureDef = StringUtils.stripCommentsAndHints(procedureDef, identifierAndStringMarkers, identifierAndStringMarkers, + !this.session.getServerSession().isNoBackslashEscapesSet()); int openParenIndex = StringUtils.indexOfIgnoreCase(0, procedureDef, "(", this.quotedId, this.quotedId, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__FULL); int endOfParamDeclarationIndex = 0; endOfParamDeclarationIndex = endPositionOfParameterDeclaration(openParenIndex, procedureDef, this.quotedId); @@ -1508,7 +1511,7 @@ private void getCallStmtParameterTypes(String db, String quotedProcName, Procedu // Grab the return column since it needs // to go first in the output result set int returnsIndex = StringUtils.indexOfIgnoreCase(0, procedureDef, " RETURNS ", this.quotedId, this.quotedId, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__FULL); int endReturnsDef = findEndOfReturnsClause(procedureDef, returnsIndex); @@ -1691,11 +1694,11 @@ private int endPositionOfParameterDeclaration(int beginIndex, String procedureDe while (parenDepth > 0 && currentPos < procedureDef.length()) { int closedParenIndex = StringUtils.indexOfIgnoreCase(currentPos, procedureDef, ")", quoteChar, quoteChar, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (closedParenIndex != -1) { int nextOpenParenIndex = StringUtils.indexOfIgnoreCase(currentPos, procedureDef, "(", quoteChar, quoteChar, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (nextOpenParenIndex != -1 && nextOpenParenIndex < closedParenIndex) { parenDepth++; @@ -1743,7 +1746,7 @@ private int findEndOfReturnsClause(String procedureDefn, int positionOfReturnKey for (int i = 0; i < tokens.length; i++) { int nextEndOfReturn = StringUtils.indexOfIgnoreCase(startLookingAt, procedureDefn, tokens[i], openingMarkers, closingMarkers, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (nextEndOfReturn != -1) { if (endOfReturn == -1 || (nextEndOfReturn < endOfReturn)) { @@ -1758,7 +1761,7 @@ private int findEndOfReturnsClause(String procedureDefn, int positionOfReturnKey // Label? endOfReturn = StringUtils.indexOfIgnoreCase(startLookingAt, procedureDefn, ":", openingMarkers, closingMarkers, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (endOfReturn != -1) { // seek back until whitespace @@ -3167,7 +3170,7 @@ protected java.sql.ResultSet getProcedureOrFunctionColumns(Field[] fields, Strin //Continuing from above (database_name.sp_name) if (!" ".equals(this.quotedId)) { idx = StringUtils.indexOfIgnoreCase(0, procName, ".", this.quotedId, this.quotedId, - this.session.getServerSession().isNoBackslashEscapesSet() ? StringUtils.SEARCH_MODE__MRK_COM_WS : StringUtils.SEARCH_MODE__ALL); + this.session.getServerSession().isNoBackslashEscapesSet() ? SearchMode.__MRK_COM_MYM_HNT_WS : SearchMode.__BSE_MRK_COM_MYM_HNT_WS); } else { idx = procName.indexOf("."); } @@ -4331,7 +4334,8 @@ protected LocalAndReferencedColumns parseTableStatusIntoLocalAndReferencedColumn String columnsDelimitter = ","; // what version did this change in? - int indexOfOpenParenLocalColumns = StringUtils.indexOfIgnoreCase(0, keysComment, "(", this.quotedId, this.quotedId, StringUtils.SEARCH_MODE__ALL); + int indexOfOpenParenLocalColumns = StringUtils.indexOfIgnoreCase(0, keysComment, "(", this.quotedId, this.quotedId, + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfOpenParenLocalColumns == -1) { throw SQLError.createSQLException(Messages.getString("DatabaseMetaData.14"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); @@ -4343,7 +4347,7 @@ protected LocalAndReferencedColumns parseTableStatusIntoLocalAndReferencedColumn String keysCommentTrimmed = keysComment.trim(); int indexOfCloseParenLocalColumns = StringUtils.indexOfIgnoreCase(0, keysCommentTrimmed, ")", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__ALL); + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfCloseParenLocalColumns == -1) { throw SQLError.createSQLException(Messages.getString("DatabaseMetaData.15"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); @@ -4351,14 +4355,14 @@ protected LocalAndReferencedColumns parseTableStatusIntoLocalAndReferencedColumn String localColumnNamesString = keysCommentTrimmed.substring(1, indexOfCloseParenLocalColumns); - int indexOfRefer = StringUtils.indexOfIgnoreCase(0, keysCommentTrimmed, "REFER ", this.quotedId, this.quotedId, StringUtils.SEARCH_MODE__ALL); + int indexOfRefer = StringUtils.indexOfIgnoreCase(0, keysCommentTrimmed, "REFER ", this.quotedId, this.quotedId, SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfRefer == -1) { throw SQLError.createSQLException(Messages.getString("DatabaseMetaData.16"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } int indexOfOpenParenReferCol = StringUtils.indexOfIgnoreCase(indexOfRefer, keysCommentTrimmed, "(", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__MRK_COM_WS); + SearchMode.__MRK_COM_MYM_HNT_WS); if (indexOfOpenParenReferCol == -1) { throw SQLError.createSQLException(Messages.getString("DatabaseMetaData.17"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); @@ -4366,7 +4370,7 @@ protected LocalAndReferencedColumns parseTableStatusIntoLocalAndReferencedColumn String referDbTableString = keysCommentTrimmed.substring(indexOfRefer + "REFER ".length(), indexOfOpenParenReferCol); - int indexOfSlash = StringUtils.indexOfIgnoreCase(0, referDbTableString, "/", this.quotedId, this.quotedId, StringUtils.SEARCH_MODE__MRK_COM_WS); + int indexOfSlash = StringUtils.indexOfIgnoreCase(0, referDbTableString, "/", this.quotedId, this.quotedId, SearchMode.__MRK_COM_MYM_HNT_WS); if (indexOfSlash == -1) { throw SQLError.createSQLException(Messages.getString("DatabaseMetaData.18"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); @@ -4376,7 +4380,7 @@ protected LocalAndReferencedColumns parseTableStatusIntoLocalAndReferencedColumn String referTable = StringUtils.unQuoteIdentifier(referDbTableString.substring(indexOfSlash + 1).trim(), this.quotedId); int indexOfCloseParenRefer = StringUtils.indexOfIgnoreCase(indexOfOpenParenReferCol, keysCommentTrimmed, ")", this.quotedId, this.quotedId, - StringUtils.SEARCH_MODE__ALL); + SearchMode.__BSE_MRK_COM_MYM_HNT_WS); if (indexOfCloseParenRefer == -1) { throw SQLError.createSQLException(Messages.getString("DatabaseMetaData.19"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaDataUsingInfoSchema.java b/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaDataUsingInfoSchema.java index 4334444a8..8f1d3e19f 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaDataUsingInfoSchema.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/DatabaseMetaDataUsingInfoSchema.java @@ -299,9 +299,9 @@ public java.sql.ResultSet getCrossReference(String primaryCatalog, String primar primaryDb = this.pedantic ? primaryDb : StringUtils.unQuoteIdentifier(primaryDb, this.quotedId); foreignDb = this.pedantic ? foreignDb : StringUtils.unQuoteIdentifier(foreignDb, this.quotedId); - StringBuilder sqlBuf = new StringBuilder( - this.databaseTerm.getValue() == DatabaseTerm.SCHEMA ? "SELECT A.CONSTRAINT_CATALOG AS PKTABLE_CAT, A.REFERENCED_TABLE_SCHEMA AS PKTABLE_SCHEM," - : "SELECT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT,NULL AS PKTABLE_SCHEM,"); + StringBuilder sqlBuf = new StringBuilder(this.databaseTerm.getValue() == DatabaseTerm.SCHEMA + ? "SELECT DISTINCT A.CONSTRAINT_CATALOG AS PKTABLE_CAT, A.REFERENCED_TABLE_SCHEMA AS PKTABLE_SCHEM," + : "SELECT DISTINCT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT,NULL AS PKTABLE_SCHEM,"); sqlBuf.append(" A.REFERENCED_TABLE_NAME AS PKTABLE_NAME, A.REFERENCED_COLUMN_NAME AS PKCOLUMN_NAME,"); sqlBuf.append(this.databaseTerm.getValue() == DatabaseTerm.SCHEMA ? " A.TABLE_CATALOG AS FKTABLE_CAT, A.TABLE_SCHEMA AS FKTABLE_SCHEM," : " A.TABLE_SCHEMA AS FKTABLE_CAT, NULL AS FKTABLE_SCHEM,"); @@ -309,13 +309,14 @@ public java.sql.ResultSet getCrossReference(String primaryCatalog, String primar sqlBuf.append(generateUpdateRuleClause()); sqlBuf.append(" AS UPDATE_RULE,"); sqlBuf.append(generateDeleteRuleClause()); - sqlBuf.append(" AS DELETE_RULE, A.CONSTRAINT_NAME AS FK_NAME,"); - sqlBuf.append(" (SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = A.REFERENCED_TABLE_SCHEMA"); - sqlBuf.append(" AND TABLE_NAME = A.REFERENCED_TABLE_NAME AND CONSTRAINT_TYPE IN ('UNIQUE','PRIMARY KEY') LIMIT 1) AS PK_NAME, "); + sqlBuf.append(" AS DELETE_RULE, A.CONSTRAINT_NAME AS FK_NAME, TC.CONSTRAINT_NAME AS PK_NAME,"); sqlBuf.append(importedKeyNotDeferrable); sqlBuf.append(" AS DEFERRABILITY FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE A"); sqlBuf.append(" JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS B USING (TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_NAME) "); sqlBuf.append(generateOptionalRefContraintsJoin()); + sqlBuf.append(" LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC ON (A.REFERENCED_TABLE_SCHEMA = TC.TABLE_SCHEMA"); + sqlBuf.append(" AND A.REFERENCED_TABLE_NAME = TC.TABLE_NAME"); + sqlBuf.append(" AND TC.CONSTRAINT_TYPE IN ('UNIQUE', 'PRIMARY KEY'))"); sqlBuf.append("WHERE B.CONSTRAINT_TYPE = 'FOREIGN KEY'"); if (primaryDb != null) { sqlBuf.append(" AND A.REFERENCED_TABLE_SCHEMA=?"); @@ -325,7 +326,7 @@ public java.sql.ResultSet getCrossReference(String primaryCatalog, String primar sqlBuf.append(" AND A.TABLE_SCHEMA = ?"); } sqlBuf.append(" AND A.TABLE_NAME=?"); - sqlBuf.append(" ORDER BY A.TABLE_SCHEMA, A.TABLE_NAME, A.ORDINAL_POSITION"); + sqlBuf.append(" ORDER BY FKTABLE_NAME, FKTABLE_NAME, KEY_SEQ"); java.sql.PreparedStatement pStmt = null; @@ -363,9 +364,9 @@ public java.sql.ResultSet getExportedKeys(String catalog, String schema, String db = this.pedantic ? db : StringUtils.unQuoteIdentifier(db, this.quotedId); - StringBuilder sqlBuf = new StringBuilder( - this.databaseTerm.getValue() == DatabaseTerm.SCHEMA ? "SELECT A.CONSTRAINT_CATALOG AS PKTABLE_CAT, A.REFERENCED_TABLE_SCHEMA AS PKTABLE_SCHEM," - : "SELECT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT,NULL AS PKTABLE_SCHEM,"); + StringBuilder sqlBuf = new StringBuilder(this.databaseTerm.getValue() == DatabaseTerm.SCHEMA + ? "SELECT DISTINCT A.CONSTRAINT_CATALOG AS PKTABLE_CAT, A.REFERENCED_TABLE_SCHEMA AS PKTABLE_SCHEM," + : "SELECT DISTINCT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT,NULL AS PKTABLE_SCHEM,"); sqlBuf.append(" A.REFERENCED_TABLE_NAME AS PKTABLE_NAME, A.REFERENCED_COLUMN_NAME AS PKCOLUMN_NAME,"); sqlBuf.append(this.databaseTerm.getValue() == DatabaseTerm.SCHEMA ? " A.TABLE_CATALOG AS FKTABLE_CAT, A.TABLE_SCHEMA AS FKTABLE_SCHEM," : " A.TABLE_SCHEMA AS FKTABLE_CAT, NULL AS FKTABLE_SCHEM,"); @@ -373,19 +374,20 @@ public java.sql.ResultSet getExportedKeys(String catalog, String schema, String sqlBuf.append(generateUpdateRuleClause()); sqlBuf.append(" AS UPDATE_RULE,"); sqlBuf.append(generateDeleteRuleClause()); - sqlBuf.append(" AS DELETE_RULE, A.CONSTRAINT_NAME AS FK_NAME, (SELECT CONSTRAINT_NAME FROM"); - sqlBuf.append(" INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = A.REFERENCED_TABLE_SCHEMA AND"); - sqlBuf.append(" TABLE_NAME = A.REFERENCED_TABLE_NAME AND CONSTRAINT_TYPE IN ('UNIQUE','PRIMARY KEY') LIMIT 1) AS PK_NAME,"); + sqlBuf.append(" AS DELETE_RULE, A.CONSTRAINT_NAME AS FK_NAME, TC.CONSTRAINT_NAME AS PK_NAME,"); sqlBuf.append(importedKeyNotDeferrable); sqlBuf.append(" AS DEFERRABILITY FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE A"); sqlBuf.append(" JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS B USING (TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_NAME) "); sqlBuf.append(generateOptionalRefContraintsJoin()); + sqlBuf.append(" LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC ON (A.REFERENCED_TABLE_SCHEMA = TC.TABLE_SCHEMA"); + sqlBuf.append(" AND A.REFERENCED_TABLE_NAME = TC.TABLE_NAME"); + sqlBuf.append(" AND TC.CONSTRAINT_TYPE IN ('UNIQUE', 'PRIMARY KEY'))"); sqlBuf.append(" WHERE B.CONSTRAINT_TYPE = 'FOREIGN KEY'"); if (db != null) { sqlBuf.append(" AND A.REFERENCED_TABLE_SCHEMA = ?"); } sqlBuf.append(" AND A.REFERENCED_TABLE_NAME=?"); - sqlBuf.append(" ORDER BY A.TABLE_SCHEMA, A.TABLE_NAME, A.ORDINAL_POSITION"); + sqlBuf.append(" ORDER BY FKTABLE_NAME, FKTABLE_NAME, KEY_SEQ"); java.sql.PreparedStatement pStmt = null; @@ -441,9 +443,9 @@ public java.sql.ResultSet getImportedKeys(String catalog, String schema, String db = this.pedantic ? db : StringUtils.unQuoteIdentifier(db, this.quotedId); - StringBuilder sqlBuf = new StringBuilder( - this.databaseTerm.getValue() == DatabaseTerm.SCHEMA ? "SELECT A.CONSTRAINT_CATALOG AS PKTABLE_CAT, A.REFERENCED_TABLE_SCHEMA AS PKTABLE_SCHEM," - : "SELECT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT,NULL AS PKTABLE_SCHEM,"); + StringBuilder sqlBuf = new StringBuilder(this.databaseTerm.getValue() == DatabaseTerm.SCHEMA + ? "SELECT DISTINCT A.CONSTRAINT_CATALOG AS PKTABLE_CAT, A.REFERENCED_TABLE_SCHEMA AS PKTABLE_SCHEM," + : "SELECT DISTINCT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT,NULL AS PKTABLE_SCHEM,"); sqlBuf.append(" A.REFERENCED_TABLE_NAME AS PKTABLE_NAME, A.REFERENCED_COLUMN_NAME AS PKCOLUMN_NAME,"); sqlBuf.append(this.databaseTerm.getValue() == DatabaseTerm.SCHEMA ? " A.TABLE_CATALOG AS FKTABLE_CAT, A.TABLE_SCHEMA AS FKTABLE_SCHEM," : " A.TABLE_SCHEMA AS FKTABLE_CAT, NULL AS FKTABLE_SCHEM,"); @@ -451,9 +453,7 @@ public java.sql.ResultSet getImportedKeys(String catalog, String schema, String sqlBuf.append(generateUpdateRuleClause()); sqlBuf.append(" AS UPDATE_RULE,"); sqlBuf.append(generateDeleteRuleClause()); - sqlBuf.append(" AS DELETE_RULE, A.CONSTRAINT_NAME AS FK_NAME, (SELECT CONSTRAINT_NAME FROM"); - sqlBuf.append(" INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = A.REFERENCED_TABLE_SCHEMA AND"); - sqlBuf.append(" TABLE_NAME = A.REFERENCED_TABLE_NAME AND CONSTRAINT_TYPE IN ('UNIQUE','PRIMARY KEY') LIMIT 1) AS PK_NAME,"); + sqlBuf.append(" AS DELETE_RULE, A.CONSTRAINT_NAME AS FK_NAME, R.UNIQUE_CONSTRAINT_NAME AS PK_NAME,"); sqlBuf.append(importedKeyNotDeferrable); sqlBuf.append(" AS DEFERRABILITY FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE A"); sqlBuf.append(" JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS B USING (CONSTRAINT_NAME, TABLE_NAME) "); diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/MysqlSQLXML.java b/src/main/user-impl/java/com/mysql/cj/jdbc/MysqlSQLXML.java index 41da76e99..27d7cd6ab 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/MysqlSQLXML.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/MysqlSQLXML.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -42,6 +42,7 @@ import java.sql.SQLException; import java.sql.SQLXML; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.stream.XMLInputFactory; @@ -65,7 +66,9 @@ import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; import com.mysql.cj.Messages; import com.mysql.cj.exceptions.ExceptionInterceptor; @@ -199,56 +202,54 @@ public T getSource(Class clazz) throws SQLException { if (clazz == null || clazz.equals(SAXSource.class)) { - InputSource inputSource = null; - - if (this.fromResultSet) { - inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml)); - } else { - inputSource = new InputSource(new StringReader(this.stringRep)); + try { + XMLReader reader = XMLReaderFactory.createXMLReader(); + // According to https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + reader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + setFeature(reader, "http://apache.org/xml/features/disallow-doctype-decl", true); + setFeature(reader, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + setFeature(reader, "http://xml.org/sax/features/external-general-entities", false); + setFeature(reader, "http://xml.org/sax/features/external-parameter-entities", false); + + return (T) new SAXSource(reader, this.fromResultSet ? new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml)) + : new InputSource(new StringReader(this.stringRep))); + } catch (SAXException ex) { + SQLException sqlEx = SQLError.createSQLException(ex.getMessage(), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, ex, this.exceptionInterceptor); + throw sqlEx; } - return (T) new SAXSource(inputSource); } else if (clazz.equals(DOMSource.class)) { try { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); builderFactory.setNamespaceAware(true); - DocumentBuilder builder = builderFactory.newDocumentBuilder(); - InputSource inputSource = null; + // According to https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + setFeature(builderFactory, XMLConstants.FEATURE_SECURE_PROCESSING, true); + setFeature(builderFactory, "http://apache.org/xml/features/disallow-doctype-decl", true); + setFeature(builderFactory, "http://xml.org/sax/features/external-general-entities", false); + setFeature(builderFactory, "http://xml.org/sax/features/external-parameter-entities", false); + setFeature(builderFactory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + builderFactory.setXIncludeAware(false); + builderFactory.setExpandEntityReferences(false); - if (this.fromResultSet) { - inputSource = new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml)); - } else { - inputSource = new InputSource(new StringReader(this.stringRep)); - } + builderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + DocumentBuilder builder = builderFactory.newDocumentBuilder(); - return (T) new DOMSource(builder.parse(inputSource)); + return (T) new DOMSource(builder.parse(this.fromResultSet ? new InputSource(this.owningResultSet.getCharacterStream(this.columnIndexOfXml)) + : new InputSource(new StringReader(this.stringRep)))); } catch (Throwable t) { SQLException sqlEx = SQLError.createSQLException(t.getMessage(), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, t, this.exceptionInterceptor); throw sqlEx; } } else if (clazz.equals(StreamSource.class)) { - Reader reader = null; + return (T) new StreamSource(this.fromResultSet ? this.owningResultSet.getCharacterStream(this.columnIndexOfXml) : new StringReader(this.stringRep)); - if (this.fromResultSet) { - reader = this.owningResultSet.getCharacterStream(this.columnIndexOfXml); - } else { - reader = new StringReader(this.stringRep); - } - - return (T) new StreamSource(reader); } else if (clazz.equals(StAXSource.class)) { try { - Reader reader = null; - - if (this.fromResultSet) { - reader = this.owningResultSet.getCharacterStream(this.columnIndexOfXml); - } else { - reader = new StringReader(this.stringRep); - } - - return (T) new StAXSource(this.inputFactory.createXMLStreamReader(reader)); + return (T) new StAXSource(this.inputFactory.createXMLStreamReader( + this.fromResultSet ? this.owningResultSet.getCharacterStream(this.columnIndexOfXml) : new StringReader(this.stringRep))); } catch (XMLStreamException ex) { SQLException sqlEx = SQLError.createSQLException(ex.getMessage(), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, ex, this.exceptionInterceptor); throw sqlEx; @@ -259,6 +260,18 @@ public T getSource(Class clazz) throws SQLException { } } + private static void setFeature(Object factory, String name, boolean value) { + try { + if (factory instanceof DocumentBuilderFactory) { + ((DocumentBuilderFactory) factory).setFeature(name, value); + } else if (factory instanceof XMLReader) { + ((XMLReader) factory).setFeature(name, value); + } + } catch (Exception ignore) { + // no-op + } + } + @Override public synchronized OutputStream setBinaryStream() throws SQLException { checkClosed(); @@ -391,7 +404,7 @@ protected String readerToString(Reader reader) throws SQLException { protected synchronized Reader serializeAsCharacterStream() throws SQLException { checkClosed(); - if (this.workingWithResult) { + if (this.workingWithResult || this.owningResultSet == null) { // figure out what kind of result if (this.stringRep != null) { return new StringReader(this.stringRep); diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ParameterBindingsImpl.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ParameterBindingsImpl.java index 489c8a625..b601339c0 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ParameterBindingsImpl.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ParameterBindingsImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * Copyright (c) 2019, 2021, 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 @@ -107,7 +107,7 @@ public class ParameterBindingsImpl implements ParameterBindings { break; default: try { - charsetIndex = CharsetMapping.getCollationIndexForJavaEncoding( + charsetIndex = session.getServerSession().getCharsetSettings().getCollationIndexForJavaEncoding( this.propertySet.getStringProperty(PropertyKey.characterEncoding).getValue(), session.getServerSession().getServerVersion()); } catch (RuntimeException ex) { throw SQLError.createSQLException(ex.toString(), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, ex, null); 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 f1d64fac0..de2e09652 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 @@ -34,7 +34,6 @@ import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.URL; -import java.sql.Date; import java.sql.ParameterMetaData; import java.sql.SQLException; import java.sql.Time; @@ -272,6 +271,7 @@ public void close() throws SQLException { if (this.isCacheable && isPoolable()) { clearParameters(); + clearAttributes(); this.isClosed = true; @@ -719,10 +719,21 @@ protected int setOneBatchedParameterSet(java.sql.PreparedStatement batchedStatem if (paramArg[j].isStream()) { Object value = paramArg[j].value; - if (value instanceof InputStream) { + if (value instanceof byte[]) { + batchedStatement.setBytes(batchedParamIndex++, (byte[]) value); + } else if (value instanceof InputStream) { batchedStatement.setBinaryStream(batchedParamIndex++, (InputStream) value, paramArg[j].getStreamLength()); - } else { + } else if (value instanceof java.sql.Blob) { + try { + batchedStatement.setBinaryStream(batchedParamIndex++, ((java.sql.Blob) value).getBinaryStream(), paramArg[j].getStreamLength()); + } catch (Throwable t) { + throw ExceptionFactory.createException(t.getMessage(), this.session.getExceptionInterceptor()); + } + } else if (value instanceof Reader) { batchedStatement.setCharacterStream(batchedParamIndex++, (Reader) value, paramArg[j].getStreamLength()); + } else { + throw ExceptionFactory.createException(WrongArgumentException.class, + Messages.getString("ServerPreparedStatement.18") + value.getClass().getName() + "'", this.session.getExceptionInterceptor()); } } else { switch (paramArg[j].bufferType) { @@ -748,7 +759,7 @@ protected int setOneBatchedParameterSet(java.sql.PreparedStatement batchedStatem batchedStatement.setTime(batchedParamIndex++, (Time) paramArg[j].value); break; case MysqlType.FIELD_TYPE_DATE: - batchedStatement.setDate(batchedParamIndex++, (Date) paramArg[j].value); + batchedStatement.setObject(batchedParamIndex++, paramArg[j].value, MysqlType.DATE); break; case MysqlType.FIELD_TYPE_DATETIME: batchedStatement.setObject(batchedParamIndex++, paramArg[j].value); @@ -802,6 +813,8 @@ protected ClientPreparedStatement prepareBatchedInsertSQL(JdbcConnection localCo this.resultSetConcurrency, this.query.getResultType().getIntValue())).unwrap(ClientPreparedStatement.class); pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys); + getQueryAttributesBindings().runThroughAll(a -> ((JdbcStatement) pstmt).setAttribute(a.getName(), a.getValue())); + return pstmt; } catch (UnsupportedEncodingException e) { SQLException sqlEx = SQLError.createSQLException(Messages.getString("ServerPreparedStatement.27"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, 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 d340e2f7c..d841fe1aa 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -43,13 +43,14 @@ import java.util.concurrent.atomic.AtomicBoolean; import com.mysql.cj.CancelQueryTask; -import com.mysql.cj.CharsetMapping; import com.mysql.cj.Messages; import com.mysql.cj.MysqlType; import com.mysql.cj.NativeSession; import com.mysql.cj.ParseInfo; import com.mysql.cj.PingTarget; import com.mysql.cj.Query; +import com.mysql.cj.QueryAttributesBindings; +import com.mysql.cj.QueryReturnType; import com.mysql.cj.Session; import com.mysql.cj.SimpleQuery; import com.mysql.cj.TransactionEventHandler; @@ -99,14 +100,14 @@ public class StatementImpl implements JdbcStatement { protected static final String PING_MARKER = "/* ping */"; - protected NativeMessageBuilder commandBuilder = new NativeMessageBuilder(); // TODO use shared builder - public final static byte USES_VARIABLES_FALSE = 0; public final static byte USES_VARIABLES_TRUE = 1; public final static byte USES_VARIABLES_UNKNOWN = -1; + protected NativeMessageBuilder commandBuilder = null; // TODO use shared builder + /** The character encoding to use (if available) */ protected String charEncoding = null; @@ -211,6 +212,8 @@ public StatementImpl(JdbcConnection c, String db) throws SQLException { this.session = (NativeSession) c.getSession(); this.exceptionInterceptor = c.getExceptionInterceptor(); + this.commandBuilder = new NativeMessageBuilder(this.session.getServerSession().supportsQueryAttributes()); + try { initQuery(); } catch (CJException e) { @@ -286,15 +289,14 @@ public void cancel() throws SQLException { } if (!this.isClosed && this.connection != null) { - JdbcConnection cancelConn = null; - java.sql.Statement cancelStmt = null; + NativeSession newSession = null; try { HostInfo hostInfo = this.session.getHostInfo(); String database = hostInfo.getDatabase(); String user = hostInfo.getUser(); String password = hostInfo.getPassword(); - NativeSession newSession = new NativeSession(this.session.getHostInfo(), this.session.getPropertySet()); + newSession = new NativeSession(this.session.getHostInfo(), this.session.getPropertySet()); newSession.connect(hostInfo, user, password, database, 30000, new TransactionEventHandler() { @Override public void transactionCompleted() { @@ -304,18 +306,14 @@ public void transactionCompleted() { public void transactionBegun() { } }); - newSession.sendCommand(new NativeMessageBuilder().buildComQuery(newSession.getSharedSendPacket(), "KILL QUERY " + this.session.getThreadId()), - false, 0); + newSession.sendCommand(new NativeMessageBuilder(newSession.getServerSession().supportsQueryAttributes()) + .buildComQuery(newSession.getSharedSendPacket(), "KILL QUERY " + this.session.getThreadId()), false, 0); setCancelStatus(CancelStatus.CANCELED_BY_USER); } catch (IOException e) { throw SQLExceptionsMapping.translateException(e, this.exceptionInterceptor); } finally { - if (cancelStmt != null) { - cancelStmt.close(); - } - - if (cancelConn != null) { - cancelConn.close(); + if (newSession != null) { + newSession.forceClose(); } } @@ -342,29 +340,29 @@ protected JdbcConnection checkClosed() { } /** - * Checks if the given SQL query with the given first non-ws char is a DML - * statement. Throws an exception if it is. + * Checks if the given SQL query is a result set producing query. * * @param sql * the SQL to check - * @param firstStatementChar - * the UC first non-ws char of the statement + * @return + * true if the query produces a result set, false otherwise. + */ + protected boolean isResultSetProducingQuery(String sql) { + QueryReturnType queryReturnType = ParseInfo.getQueryReturnType(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + return queryReturnType == QueryReturnType.PRODUCES_RESULT_SET || queryReturnType == QueryReturnType.MAY_PRODUCE_RESULT_SET; + } + + /** + * Checks if the given SQL query does not return a result set. * - * @throws SQLException - * if the statement contains DML + * @param sql + * the SQL to check + * @return + * true if the query does not produce a result set, false otherwise. */ - protected void checkForDml(String sql, char firstStatementChar) throws SQLException { - if ((firstStatementChar == 'I') || (firstStatementChar == 'U') || (firstStatementChar == 'D') || (firstStatementChar == 'A') - || (firstStatementChar == 'C') || (firstStatementChar == 'T') || (firstStatementChar == 'R')) { - String noCommentSql = StringUtils.stripComments(sql, "'\"", "'\"", true, false, true, true); - - if (StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "INSERT") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "UPDATE") - || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DELETE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "DROP") - || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "CREATE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "ALTER") - || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "TRUNCATE") || StringUtils.startsWithIgnoreCaseAndWs(noCommentSql, "RENAME")) { - throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); - } - } + protected boolean isNonResultSetProducingQuery(String sql) { + QueryReturnType queryReturnType = ParseInfo.getQueryReturnType(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + return queryReturnType == QueryReturnType.DOES_NOT_PRODUCE_RESULT_SET || queryReturnType == QueryReturnType.MAY_PRODUCE_RESULT_SET; } /** @@ -554,6 +552,10 @@ private ResultSetInternalMethods createResultSetUsingServerFetch(String sql) thr pStmt.setFetchSize(this.query.getResultFetchSize()); + if (this.getQueryTimeout() > 0) { + pStmt.setQueryTimeout(this.getQueryTimeout()); + } + if (this.maxRows > -1) { pStmt.setMaxRows(this.maxRows); } @@ -666,14 +668,13 @@ private boolean executeInternal(String sql, boolean returnGeneratedKeys) throws } } - char firstNonWsChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); - boolean maybeSelect = firstNonWsChar == 'S'; - this.retrieveGeneratedKeys = returnGeneratedKeys; - this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys && firstNonWsChar == 'I' && containsOnDuplicateKeyInString(sql); + this.lastQueryIsOnDupKeyUpdate = returnGeneratedKeys + && ParseInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet()) == 'I' + && containsOnDuplicateKeyInString(sql); - if (!maybeSelect && locallyScopedConn.isReadOnly()) { + if (!ParseInfo.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()); } @@ -715,7 +716,7 @@ private boolean executeInternal(String sql, boolean returnGeneratedKeys) throws } // Only apply max_rows to selects - locallyScopedConn.setSessionMaxRows(maybeSelect ? this.maxRows : -1); + locallyScopedConn.setSessionMaxRows(isResultSetProducingQuery(sql) ? this.maxRows : -1); statementBegins(); @@ -744,7 +745,7 @@ private boolean executeInternal(String sql, boolean returnGeneratedKeys) throws this.results = rs; - rs.setFirstCharOfQuery(firstNonWsChar); + rs.setFirstCharOfQuery(ParseInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet())); if (rs.hasRows()) { if (cachedMetaData != null) { @@ -968,6 +969,8 @@ private long[] executeBatchUsingMultiQueries(boolean multiQueriesEnabled, int nb StringBuilder queryBuf = new StringBuilder(); batchStmt = locallyScopedConn.createStatement(); + JdbcStatement jdbcBatchedStmt = (JdbcStatement) batchStmt; + getQueryAttributesBindings().runThroughAll(a -> jdbcBatchedStmt.setAttribute(a.getName(), a.getValue())); timeoutTask = startQueryTimer((StatementImpl) batchStmt, individualStatementTimeout); @@ -976,14 +979,14 @@ private long[] executeBatchUsingMultiQueries(boolean multiQueriesEnabled, int nb String connectionEncoding = locallyScopedConn.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(); int numberOfBytesPerChar = StringUtils.startsWithIgnoreCase(connectionEncoding, "utf") ? 3 - : (CharsetMapping.isMultibyteCharset(connectionEncoding) ? 2 : 1); + : (this.session.getServerSession().getCharsetSettings().isMultibyteCharset(connectionEncoding) ? 2 : 1); int escapeAdjust = 1; batchStmt.setEscapeProcessing(this.doEscapeProcessing); if (this.doEscapeProcessing) { - escapeAdjust = 2; // We assume packet _could_ grow by this amount, as we're not sure how big statement will end up after escape processing + escapeAdjust = 2; // We assume packet _could_ grow by this amount, as we're not sure how big statement will end up after escape processing } SQLException sqlEx = null; @@ -1128,9 +1131,9 @@ public java.sql.ResultSet executeQuery(String sql) throws SQLException { sql = escapedSqlResult instanceof String ? (String) escapedSqlResult : ((EscapeProcessorResult) escapedSqlResult).escapedSql; } - char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); - - checkForDml(sql, firstStatementChar); + if (!isResultSetProducingQuery(sql)) { + throw SQLError.createSQLException(Messages.getString("Statement.57"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); + } CachedResultSetMetaData cachedMetaData = null; @@ -1219,8 +1222,8 @@ protected void doPingInstead() throws SQLException { protected ResultSetInternalMethods generatePingResultSet() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - String encoding = this.session.getServerSession().getCharacterSetMetadata(); - int collationIndex = this.session.getServerSession().getMetadataCollationIndex(); + String encoding = this.session.getServerSession().getCharsetSettings().getMetadataEncoding(); + int collationIndex = this.session.getServerSession().getCharsetSettings().getMetadataCollationIndex(); Field[] fields = { new Field(null, "1", collationIndex, encoding, MysqlType.BIGINT, 1) }; ArrayList rows = new ArrayList<>(); byte[] colVal = new byte[] { (byte) '1' }; @@ -1251,7 +1254,10 @@ protected long executeUpdateInternal(String sql, boolean isBatch, boolean return resetCancelledState(); - char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql)); + char firstStatementChar = ParseInfo.firstCharOfStatementUc(sql, this.session.getServerSession().isNoBackslashEscapesSet()); + if (!isNonResultSetProducingQuery(sql)) { + throw SQLError.createSQLException(Messages.getString("Statement.46"), "01S03", getExceptionInterceptor()); + } this.retrieveGeneratedKeys = returnGeneratedKeys; @@ -1271,10 +1277,6 @@ protected long executeUpdateInternal(String sql, boolean isBatch, boolean return MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } - if (StringUtils.startsWithIgnoreCaseAndWs(sql, "select")) { - throw SQLError.createSQLException(Messages.getString("Statement.46"), "01S03", getExceptionInterceptor()); - } - implicitlyCloseAllOpenResults(); // The checking and changing of databases must happen in sequence, so synchronize on the same mutex that _conn is using @@ -1382,8 +1384,8 @@ public java.sql.ResultSet getGeneratedKeys() throws SQLException { return this.generatedKeysResults = getGeneratedKeysInternal(); } - String encoding = this.session.getServerSession().getCharacterSetMetadata(); - int collationIndex = this.session.getServerSession().getMetadataCollationIndex(); + String encoding = this.session.getServerSession().getCharsetSettings().getMetadataEncoding(); + int collationIndex = this.session.getServerSession().getCharsetSettings().getMetadataCollationIndex(); Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", collationIndex, encoding, MysqlType.BIGINT_UNSIGNED, 20); @@ -1406,8 +1408,8 @@ protected ResultSetInternalMethods getGeneratedKeysInternal() throws SQLExceptio protected ResultSetInternalMethods getGeneratedKeysInternal(long numKeys) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - String encoding = this.session.getServerSession().getCharacterSetMetadata(); - int collationIndex = this.session.getServerSession().getMetadataCollationIndex(); + String encoding = this.session.getServerSession().getCharsetSettings().getMetadataEncoding(); + int collationIndex = this.session.getServerSession().getCharsetSettings().getMetadataCollationIndex(); Field[] fields = new Field[1]; fields[0] = new Field("", "GENERATED_KEY", collationIndex, encoding, MysqlType.BIGINT_UNSIGNED, 20); @@ -1728,7 +1730,7 @@ public java.sql.SQLWarning getWarnings() throws SQLException { return null; } - SQLWarning pendingWarningsFromServer = this.session.getProtocol().convertShowWarningsToSQLWarnings(0, false); + SQLWarning pendingWarningsFromServer = this.session.getProtocol().convertShowWarningsToSQLWarnings(false); if (this.warningChain != null) { this.warningChain.setNextWarning(pendingWarningsFromServer); @@ -1794,6 +1796,8 @@ protected void realClose(boolean calledExplicitly, boolean closeOpenResults) thr closeAllOpenResults(); } + clearAttributes(); + this.isClosed = true; closeQuery(); @@ -2029,32 +2033,6 @@ public T unwrap(Class iface) throws SQLException { } } - protected static int findStartOfStatement(String sql) { - int statementStartPos = 0; - - if (StringUtils.startsWithIgnoreCaseAndWs(sql, "/*")) { - statementStartPos = sql.indexOf("*/"); - - if (statementStartPos == -1) { - statementStartPos = 0; - } else { - statementStartPos += 2; - } - } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "--") || StringUtils.startsWithIgnoreCaseAndWs(sql, "#")) { - statementStartPos = sql.indexOf('\n'); - - if (statementStartPos == -1) { - statementStartPos = sql.indexOf('\r'); - - if (statementStartPos == -1) { - statementStartPos = 0; - } - } - } - - return statementStartPos; - } - @Override public InputStream getLocalInfileInputStream() { return this.session.getLocalInfileInputStream(); @@ -2269,4 +2247,19 @@ public void setClearWarningsCalled(boolean clearWarningsCalled) { public Query getQuery() { return this.query; } + + @Override + public QueryAttributesBindings getQueryAttributesBindings() { + return this.query.getQueryAttributesBindings(); + } + + @Override + public void setAttribute(String name, Object value) { + getQueryAttributesBindings().setAttribute(name, value); + } + + @Override + public void clearAttributes() { + getQueryAttributesBindings().clearAttributes(); + } } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/FailoverConnectionProxy.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/FailoverConnectionProxy.java index 7ad584957..635aad8e5 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/FailoverConnectionProxy.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/FailoverConnectionProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020, Oracle and/or its affiliates. + * Copyright (c) 2010, 2021, 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 @@ -499,7 +499,7 @@ synchronized void doAbort(Executor executor) throws SQLException { * This is the continuation of MultiHostConnectionProxy#invoke(Object, Method, Object[]). */ @Override - public synchronized Object invokeMore(Object proxy, Method method, Object[] args) throws Throwable { + public Object invokeMore(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (METHOD_SET_READ_ONLY.equals(methodName)) { diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/LoadBalancedConnectionProxy.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/LoadBalancedConnectionProxy.java index c9db2bdde..eb3c1c2c1 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/LoadBalancedConnectionProxy.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/LoadBalancedConnectionProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. + * Copyright (c) 2007, 2021, 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 @@ -536,7 +536,7 @@ synchronized void doAbort(Executor executor) { * This is the continuation of MultiHostConnectionProxy#invoke(Object, Method, Object[]). */ @Override - public synchronized Object invokeMore(Object proxy, Method method, Object[] args) throws Throwable { + Object invokeMore(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (this.isClosed && !allowedOnClosedConnection(method) && method.getExceptionTypes().length > 0) { // TODO remove method.getExceptionTypes().length ? diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostConnectionProxy.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostConnectionProxy.java index f9bf0800a..ab7d8184b 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostConnectionProxy.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostConnectionProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -54,7 +54,6 @@ public abstract class MultiHostConnectionProxy implements InvocationHandler { private static final String METHOD_GET_MULTI_HOST_SAFE_PROXY = "getMultiHostSafeProxy"; private static final String METHOD_EQUALS = "equals"; - private static final String METHOD_HASH_CODE = "hashCode"; private static final String METHOD_CLOSE = "close"; private static final String METHOD_ABORT_INTERNAL = "abortInternal"; private static final String METHOD_ABORT = "abort"; @@ -466,7 +465,7 @@ void syncSessionState(JdbcConnection source, JdbcConnection target, boolean read * if an error occurs */ @Override - public synchronized Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (METHOD_GET_MULTI_HOST_SAFE_PROXY.equals(methodName)) { @@ -478,50 +477,53 @@ public synchronized Object invoke(Object proxy, Method method, Object[] args) th return args[0].equals(this); } - if (METHOD_HASH_CODE.equals(methodName)) { - return this.hashCode(); + // Execute remaining ubiquitous methods right away. + if (method.getDeclaringClass().equals(Object.class)) { + return method.invoke(this, args); } - if (METHOD_CLOSE.equals(methodName)) { - doClose(); - this.isClosed = true; - this.closedReason = "Connection explicitly closed."; - this.closedExplicitly = true; - return null; - } + synchronized (this) { + if (METHOD_CLOSE.equals(methodName)) { + doClose(); + this.isClosed = true; + this.closedReason = "Connection explicitly closed."; + this.closedExplicitly = true; + return null; + } - if (METHOD_ABORT_INTERNAL.equals(methodName)) { - doAbortInternal(); - this.currentConnection.abortInternal(); - this.isClosed = true; - this.closedReason = "Connection explicitly closed."; - return null; - } + if (METHOD_ABORT_INTERNAL.equals(methodName)) { + doAbortInternal(); + this.currentConnection.abortInternal(); + this.isClosed = true; + this.closedReason = "Connection explicitly closed."; + return null; + } - if (METHOD_ABORT.equals(methodName) && args.length == 1) { - doAbort((Executor) args[0]); - this.isClosed = true; - this.closedReason = "Connection explicitly closed."; - return null; - } + if (METHOD_ABORT.equals(methodName) && args.length == 1) { + doAbort((Executor) args[0]); + this.isClosed = true; + this.closedReason = "Connection explicitly closed."; + return null; + } - if (METHOD_IS_CLOSED.equals(methodName)) { - return this.isClosed; - } + if (METHOD_IS_CLOSED.equals(methodName)) { + return this.isClosed; + } - try { - return invokeMore(proxy, method, args); - } catch (InvocationTargetException e) { - throw e.getCause() != null ? e.getCause() : e; - } catch (Exception e) { - // Check if the captured exception must be wrapped by an unchecked exception. - Class[] declaredException = method.getExceptionTypes(); - for (Class declEx : declaredException) { - if (declEx.isAssignableFrom(e.getClass())) { - throw e; + try { + return invokeMore(proxy, method, args); + } catch (InvocationTargetException e) { + throw e.getCause() != null ? e.getCause() : e; + } catch (Exception e) { + // Check if the captured exception must be wrapped by an unchecked exception. + Class[] declaredException = method.getExceptionTypes(); + for (Class declEx : declaredException) { + if (declEx.isAssignableFrom(e.getClass())) { + throw e; + } } + throw new IllegalStateException(e.getMessage(), e); } - throw new IllegalStateException(e.getMessage(), e); } } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostMySQLConnection.java b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostMySQLConnection.java index c379c4f2d..7b1042b15 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostMySQLConnection.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/ha/MultiHostMySQLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -60,6 +60,7 @@ import com.mysql.cj.jdbc.exceptions.SQLError; import com.mysql.cj.jdbc.result.CachedResultSetMetaData; import com.mysql.cj.jdbc.result.ResultSetInternalMethods; +import com.mysql.cj.protocol.ServerSessionStateController; /** * Each instance of MultiHostMySQLConnection is coupled with a MultiHostConnectionProxy instance. @@ -753,4 +754,9 @@ public void normalClose() { public void cleanup(Throwable whyCleanedUp) { getActiveMySQLConnection().cleanup(whyCleanedUp); } + + @Override + public ServerSessionStateController getServerSessionStateController() { + return getActiveMySQLConnection().getServerSessionStateController(); + } } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetImpl.java b/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetImpl.java index 10606d9cd..ff45a5592 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetImpl.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetImpl.java @@ -371,6 +371,11 @@ public void initializeWithMetadata() throws SQLException { @Override public boolean absolute(int row) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } @@ -419,6 +424,11 @@ public boolean absolute(int row) throws SQLException { @Override public void afterLast() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } @@ -435,6 +445,11 @@ public void afterLast() throws SQLException { @Override public void beforeFirst() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } @@ -560,18 +575,6 @@ public void deleteRow() throws SQLException { throw new NotUpdatable(Messages.getString("NotUpdatable.0")); } - /* - * /** - * TODO: Required by JDBC spec - */ - /* - * protected void finalize() throws Throwable { - * if (!this.isClosed) { - * realClose(false); - * } - * } - */ - @Override public int findColumn(String columnName) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { @@ -590,6 +593,11 @@ public int findColumn(String columnName) throws SQLException { @Override public boolean first() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } @@ -875,7 +883,7 @@ public String getString(int columnIndex) throws SQLException { String stringVal = this.thisRow.getValue(columnIndex - 1, vf); if (this.padCharsWithSpace && stringVal != null && f.getMysqlTypeId() == MysqlType.FIELD_TYPE_STRING) { - int maxBytesPerChar = this.session.getServerSession().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding()); + int maxBytesPerChar = this.session.getServerSession().getCharsetSettings().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding()); int fieldLength = (int) f.getLength() /* safe, bytes in a CHAR <= 1024 */ / maxBytesPerChar; /* safe, this will never be 0 */ return StringUtils.padString(stringVal, fieldLength); } @@ -1343,7 +1351,8 @@ public T getObject(int columnIndex, Class type) throws SQLException { return (T) getTimestamp(columnIndex); } else if (type.equals(java.util.Date.class)) { - return (T) java.util.Date.from(getTimestamp(columnIndex).toInstant()); + Timestamp ts = getTimestamp(columnIndex); + return ts == null ? null : (T) java.util.Date.from(ts.toInstant()); } else if (type.equals(java.util.Calendar.class)) { return (T) getUtilCalendar(columnIndex); @@ -1589,6 +1598,11 @@ public java.sql.Ref getRef(String colName) throws SQLException { public int getRow() throws SQLException { checkClosed(); + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, + getExceptionInterceptor()); + } + int currentRowNumber = this.rowData.getPosition(); int row = 0; @@ -1691,15 +1705,22 @@ public void insertRow() throws SQLException { @Override public boolean isAfterLast() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - boolean b = this.rowData.isAfterLast(); - - return b; + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + return this.rowData.isAfterLast(); } } @Override public boolean isBeforeFirst() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + return this.rowData.isBeforeFirst(); } } @@ -1707,6 +1728,11 @@ public boolean isBeforeFirst() throws SQLException { @Override public boolean isFirst() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + return this.rowData.isFirst(); } } @@ -1714,6 +1740,11 @@ public boolean isFirst() throws SQLException { @Override public boolean isLast() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + return this.rowData.isLast(); } } @@ -1732,6 +1763,11 @@ protected boolean isStrictlyForwardOnly() { @Override public boolean last() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } @@ -1764,14 +1800,13 @@ public void moveToInsertRow() throws SQLException { @Override public boolean next() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { - - boolean b; - if (!hasRows()) { throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } + boolean b; + if (this.rowData.size() == 0) { b = false; } else { @@ -1838,6 +1873,11 @@ public boolean prev() throws java.sql.SQLException { @Override public boolean previous() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } @@ -1960,6 +2000,11 @@ public void refreshRow() throws SQLException { @Override public boolean relative(int rows) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { + if (!hasRows()) { + throw SQLError.createSQLException(Messages.getString("ResultSet.ResultSet_is_from_UPDATE._No_Data_115"), + MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); + } + if (isStrictlyForwardOnly()) { throw ExceptionFactory.createException(Messages.getString("ResultSet.ForwardOnly")); } diff --git a/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetMetaData.java b/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetMetaData.java index b89172e32..155e98d2a 100644 --- a/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetMetaData.java +++ b/src/main/user-impl/java/com/mysql/cj/jdbc/result/ResultSetMetaData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -31,10 +31,8 @@ import java.sql.SQLException; -import com.mysql.cj.CharsetMapping; import com.mysql.cj.Messages; import com.mysql.cj.MysqlType; -import com.mysql.cj.NativeSession; import com.mysql.cj.Session; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; import com.mysql.cj.conf.PropertyKey; @@ -127,18 +125,7 @@ public String getColumnCharacterEncoding(int column) throws SQLException { * if an invalid column index is given. */ public String getColumnCharacterSet(int column) throws SQLException { - int index = getField(column).getCollationIndex(); - - String charsetName = null; - - if (((NativeSession) this.session).getProtocol().getServerSession().indexToCustomMysqlCharset != null) { - charsetName = ((NativeSession) this.session).getProtocol().getServerSession().indexToCustomMysqlCharset.get(index); - } - if (charsetName == null) { - charsetName = CharsetMapping.getMysqlCharsetNameForCollationIndex(index); - } - - return charsetName; + return this.session.getServerSession().getCharsetSettings().getMysqlCharsetNameForCollationIndex(getField(column).getCollationIndex()); } @Override @@ -169,7 +156,7 @@ public int getColumnDisplaySize(int column) throws SQLException { int lengthInBytes = clampedGetLength(f); - return lengthInBytes / this.session.getServerSession().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding()); + return lengthInBytes / this.session.getServerSession().getCharsetSettings().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding()); } @Override @@ -241,7 +228,7 @@ public int getPrecision(int column) throws SQLException { default: return f.getMysqlType().isDecimal() ? clampedGetLength(f) - : clampedGetLength(f) / this.session.getServerSession().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding()); + : clampedGetLength(f) / this.session.getServerSession().getCharsetSettings().getMaxBytesPerChar(f.getCollationIndex(), f.getEncoding()); } } @@ -315,7 +302,7 @@ public boolean isCaseSensitive(int column) throws java.sql.SQLException { case JSON: case ENUM: case SET: - String collationName = CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[field.getCollationIndex()]; + String collationName = this.session.getServerSession().getCharsetSettings().getCollationNameForCollationIndex(field.getCollationIndex()); return ((collationName != null) && !collationName.endsWith("_ci")); default: diff --git a/src/main/user-impl/java/com/mysql/cj/xdevapi/CollectionImpl.java b/src/main/user-impl/java/com/mysql/cj/xdevapi/CollectionImpl.java index 5883498d3..43eae5edb 100644 --- a/src/main/user-impl/java/com/mysql/cj/xdevapi/CollectionImpl.java +++ b/src/main/user-impl/java/com/mysql/cj/xdevapi/CollectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -188,6 +188,10 @@ public Result replaceOne(String id, DbDoc doc) { if (doc == null) { throw new XDevAPIError(Messages.getString("CreateTableStatement.0", new String[] { "doc" })); } + JsonValue docId = doc.get("_id"); + if (docId != null && (!JsonString.class.isInstance(docId) || !id.equals(((JsonString) docId).getString()))) { + throw new XDevAPIError(Messages.getString("Collection.DocIdMismatch")); + } return modify("_id = :id").set("$", doc).bind("id", id).execute(); } @@ -214,10 +218,11 @@ public Result addOrReplaceOne(String id, DbDoc doc) { if (doc == null) { throw new XDevAPIError(Messages.getString("CreateTableStatement.0", new String[] { "doc" })); } - if (doc.get("_id") == null) { + JsonValue docId = doc.get("_id"); + if (docId == null) { doc.add("_id", new JsonString().setValue(id)); - } else if (!id.equals(((JsonString) doc.get("_id")).getString())) { - throw new XDevAPIError("Document already has an _id that doesn't match to id parameter"); + } else if (!JsonString.class.isInstance(docId) || !id.equals(((JsonString) docId).getString())) { + throw new XDevAPIError(Messages.getString("Collection.DocIdMismatch")); } return add(doc).setUpsert(true).execute(); } diff --git a/src/main/user-impl/java/com/mysql/cj/xdevapi/ColumnImpl.java b/src/main/user-impl/java/com/mysql/cj/xdevapi/ColumnImpl.java index 902017707..8cccf54c6 100644 --- a/src/main/user-impl/java/com/mysql/cj/xdevapi/ColumnImpl.java +++ b/src/main/user-impl/java/com/mysql/cj/xdevapi/ColumnImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -151,11 +151,11 @@ public boolean isNumberSigned() { } public String getCollationName() { - return CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[this.field.getCollationIndex()]; + return CharsetMapping.getStaticCollationNameForCollationIndex(this.field.getCollationIndex()); // TODO use CharsetSettings method } public String getCharacterSetName() { - return CharsetMapping.getMysqlCharsetNameForCollationIndex(this.field.getCollationIndex()); + return CharsetMapping.getStaticMysqlCharsetNameForCollationIndex(this.field.getCollationIndex()); // TODO use CharsetSettings method } public boolean isPadded() { diff --git a/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingDocResultBuilder.java b/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingDocResultBuilder.java index fc443bf9c..825a11766 100644 --- a/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingDocResultBuilder.java +++ b/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingDocResultBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * Copyright (c) 2019, 2021, 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 @@ -70,6 +70,7 @@ public boolean addProtocolEntity(ProtocolEntity entity) { } else if (entity instanceof Notice) { this.statementExecuteOkBuilder.addProtocolEntity(entity); + return false; } if (this.metadata == null) { diff --git a/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingSqlResultBuilder.java b/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingSqlResultBuilder.java index d36ff5e65..bd8a210a0 100644 --- a/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingSqlResultBuilder.java +++ b/src/main/user-impl/java/com/mysql/cj/xdevapi/StreamingSqlResultBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * Copyright (c) 2019, 2021, 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 @@ -102,7 +102,7 @@ public boolean addProtocolEntity(ProtocolEntity entity) { }); this.lastEntity = null; } else { - cd = this.protocol.readMetadata(); + cd = this.protocol.readMetadata(this.statementExecuteOkBuilder::addProtocolEntity); } return new SqlSingleResult(cd, this.protocol.getServerSession().getDefaultTimeZone(), new XProtocolRowInputStream(cd, this.protocol, (n) -> { this.statementExecuteOkBuilder.addProtocolEntity(n); diff --git a/src/test/java/com/mysql/cj/CharsetMappingWrapper.java b/src/test/java/com/mysql/cj/CharsetMappingWrapper.java new file mode 100644 index 000000000..074ab2c2a --- /dev/null +++ b/src/test/java/com/mysql/cj/CharsetMappingWrapper.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, 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 also distributed with certain software (including but not + * limited to OpenSSL) 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 included with MySQL. + * + * 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; + +/** + * Exposes protected {@link CharsetMapping} methods for use in tests. + */ +public class CharsetMappingWrapper { + + public static boolean isStaticMultibyteCharset(String javaEncodingName) { + return CharsetMapping.isStaticMultibyteCharset(javaEncodingName); + } + + public static int getStaticCollationIndexForJavaEncoding(String javaEncoding, ServerVersion version) { + return CharsetMapping.getStaticCollationIndexForJavaEncoding(javaEncoding, version); + } + + public static String getStaticCollationNameForCollationIndex(Integer collationIndex) { + return CharsetMapping.getStaticCollationNameForCollationIndex(collationIndex); + } + + public static String getStaticJavaEncodingForCollationIndex(Integer collationIndex) { + return CharsetMapping.getStaticJavaEncodingForCollationIndex(collationIndex); + } + + public static String getStaticJavaEncodingForMysqlCharset(String mysqlCharsetName) { + return CharsetMapping.getStaticJavaEncodingForMysqlCharset(mysqlCharsetName); + } + + public static String getStaticMysqlCharsetByName(String mysqlCharsetName) { + Object cs = CharsetMapping.getStaticMysqlCharsetByName(mysqlCharsetName); + return cs == null ? null : cs.toString(); + } + + public static String getStaticMysqlCharsetForJavaEncoding(String javaEncoding, ServerVersion version) { + return CharsetMapping.getStaticMysqlCharsetForJavaEncoding(javaEncoding, version); + } + + public static String getStaticMysqlCharsetNameForCollationIndex(Integer collationIndex) { + return CharsetMapping.getStaticMysqlCharsetNameForCollationIndex(collationIndex); + } + +} diff --git a/src/test/java/com/mysql/cj/ConnectionUrlTest.java b/src/test/java/com/mysql/cj/ConnectionUrlTest.java index 62ee30b06..38ab7d0b9 100644 --- a/src/test/java/com/mysql/cj/ConnectionUrlTest.java +++ b/src/test/java/com/mysql/cj/ConnectionUrlTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -96,14 +96,10 @@ protected static EX assertThrows(String message, Class { System.out.println(ConnectionUrl.getConnectionUrlInstance(cs, null)); - fail(cs + ": expected to throw a " + WrongArgumentException.class.getName()); - } catch (Exception e) { - assertTrue(WrongArgumentException.class.isAssignableFrom(e.getClass()), cs + ": expected to throw a " + WrongArgumentException.class.getName()); - } + return null; + }); } } diff --git a/src/test/java/com/mysql/cj/MessagesTest.java b/src/test/java/com/mysql/cj/MessagesTest.java index 183fb21fe..745aca9cb 100644 --- a/src/test/java/com/mysql/cj/MessagesTest.java +++ b/src/test/java/com/mysql/cj/MessagesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -104,9 +104,10 @@ public void testLocalizedErrorMessages() throws Exception { assertEquals("Illegal starting position for search, '10'", Messages.getString("Clob.8", new Object[] { 10 })); - assertEquals("Java does not support the MySQL character encoding 'Test'.", Messages.getString("Connection.5", new Object[] { "Test" })); + assertEquals("Unknown Java encoding for the character set with index '1234'. Use the 'customCharsetMapping' property to force it.", + Messages.getString("Connection.5", new Object[] { "1234" })); assertEquals( - "Unknown initial character set index 'Test' received from server. Initial client character set can be forced via the 'characterEncoding' property.", + "Unknown character set index 'Test' received from server. The appropriate client character set can be forced via the 'characterEncoding' property.", Messages.getString("Connection.6", new Object[] { "Test" })); assertEquals("Can't map Test given for characterSetResults to a supported MySQL encoding.", Messages.getString("Connection.7", new Object[] { "Test" })); diff --git a/src/test/java/com/mysql/cj/protocol/a/MysqlTextValueDecoderTest.java b/src/test/java/com/mysql/cj/protocol/a/MysqlTextValueDecoderTest.java index 70c00f463..ff866f190 100644 --- a/src/test/java/com/mysql/cj/protocol/a/MysqlTextValueDecoderTest.java +++ b/src/test/java/com/mysql/cj/protocol/a/MysqlTextValueDecoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -31,8 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; @@ -96,12 +96,10 @@ public void testIntValues() { assertEquals(String.valueOf(Integer.MAX_VALUE), this.valueDecoder.decodeUInt4(String.valueOf(Integer.MAX_VALUE).getBytes(), 0, 10, vf)); assertEquals("2147483648", this.valueDecoder.decodeUInt4(Constants.BIG_INTEGER_MAX_INTEGER_VALUE.add(Constants.BIG_INTEGER_ONE).toString().getBytes(), 0, 10, vf)); - try { + + assertThrows(NumberOutOfRange.class, () -> { this.valueDecoder.decodeInt4(Constants.BIG_INTEGER_MAX_INTEGER_VALUE.add(Constants.BIG_INTEGER_ONE).toString().getBytes(), 0, 10, vf); - fail("Exception should be thrown for decodeInt4(Integer.MAX_VALUE + 1)"); - } catch (NumberOutOfRange ex) { - // expected - } + }, "Exception should be thrown for decodeInt4(Integer.MAX_VALUE + 1)"); byte[] uint8LessThanMaxLong = "8223372036854775807".getBytes(); ValueFactory fromLongOnly = new DefaultValueFactory(new DefaultPropertySet()) { diff --git a/src/test/java/com/mysql/cj/protocol/a/SimplePacketReaderTest.java b/src/test/java/com/mysql/cj/protocol/a/SimplePacketReaderTest.java index 269877166..b041b210e 100644 --- a/src/test/java/com/mysql/cj/protocol/a/SimplePacketReaderTest.java +++ b/src/test/java/com/mysql/cj/protocol/a/SimplePacketReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -241,8 +241,6 @@ public void connect(String host, int port, PropertySet propertySet, ExceptionInt @Override public void performTlsHandshake(ServerSession serverSession) throws SSLParamsException, FeatureNotAvailableException, IOException { - // TODO Auto-generated method stub - } public void forceClose() { diff --git a/src/test/java/com/mysql/cj/protocol/x/SyncMessageWriterTest.java b/src/test/java/com/mysql/cj/protocol/x/SyncMessageWriterTest.java index 09219e014..41b9f3990 100644 --- a/src/test/java/com/mysql/cj/protocol/x/SyncMessageWriterTest.java +++ b/src/test/java/com/mysql/cj/protocol/x/SyncMessageWriterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -30,8 +30,8 @@ package com.mysql.cj.protocol.x; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -87,13 +87,10 @@ public void testCompleteWriteMessage() throws IOException { @Test public void testBadMessageClass() { - try { + assertThrows(WrongArgumentException.class, () -> { // try sending "Ok" which is a server-sent message. should fail with exception this.writer.send(new XMessage(Ok.getDefaultInstance())); - fail("Writing OK message should fail"); - } catch (WrongArgumentException ex) { - // expected - } + }, "Writing OK message should fail"); } @Test diff --git a/src/test/java/com/mysql/cj/result/CommonAsserts.java b/src/test/java/com/mysql/cj/result/CommonAsserts.java index 45ffbebec..123eb3381 100644 --- a/src/test/java/com/mysql/cj/result/CommonAsserts.java +++ b/src/test/java/com/mysql/cj/result/CommonAsserts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. + * Copyright (c) 2018, 2021, 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 @@ -29,6 +29,7 @@ package com.mysql.cj.result; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.concurrent.Callable; @@ -38,14 +39,9 @@ protected static EX assertThrows(Class throwable, Str try { testRoutine.call(); } catch (Throwable t) { - if (!throwable.isAssignableFrom(t.getClass())) { - fail("Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown."); - } - - if (!t.getMessage().matches(msgMatchesRegex)) { - fail("The error message [" + t.getMessage() + "] was expected to match [" + msgMatchesRegex + "]."); - } - + assertTrue(throwable.isAssignableFrom(t.getClass()), + "Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown."); + assertTrue(t.getMessage().matches(msgMatchesRegex), "The error message [" + t.getMessage() + "] was expected to match [" + msgMatchesRegex + "]."); return throwable.cast(t); } fail("Expected exception of type '" + throwable.getName() + "'."); diff --git a/src/test/java/testsuite/simple/StringUtilsTest.java b/src/test/java/com/mysql/cj/util/StringUtilsTest.java similarity index 82% rename from src/test/java/testsuite/simple/StringUtilsTest.java rename to src/test/java/com/mysql/cj/util/StringUtilsTest.java index 8094a982a..b58c94451 100644 --- a/src/test/java/testsuite/simple/StringUtilsTest.java +++ b/src/test/java/com/mysql/cj/util/StringUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -27,7 +27,7 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -package testsuite.simple; +package com.mysql.cj.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -44,10 +44,6 @@ import org.junit.jupiter.api.Test; -import com.mysql.cj.util.LazyString; -import com.mysql.cj.util.StringUtils; -import com.mysql.cj.util.StringUtils.SearchMode; - import testsuite.BaseTestCase; public class StringUtilsTest extends BaseTestCase { @@ -112,21 +108,27 @@ public void testIndexOfIgnoreCase() throws Exception { /* * C. test indexOfIgnoreCase(int startingPosition, String searchIn, String searchFor, String openingMarkers, String closingMarkers, Set - * searchMode) using search modes SEARCH_MODE__BSESC_MRK_WS or SEARCH_MODE__MRK_WS + * searchMode) using search modes SearchMode.__MRK_WS or SearchMode.__BSE_MRK_WS */ // basic test set - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, null, (String) null, markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, null, "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", (String) null, markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "bcd", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(6, StringUtils.indexOfIgnoreCase(0, "ab -- abc", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(5, StringUtils.indexOfIgnoreCase(0, "ab # abc", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/*/* /c", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/**/c", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(5, StringUtils.indexOfIgnoreCase(0, "ab/* abc */c", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(0, StringUtils.indexOfIgnoreCase(0, "abc", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); - assertEquals(3, StringUtils.indexOfIgnoreCase(0, "abc d efg", " d ", markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS)); + assertThrows(IllegalArgumentException.class, "The source string must not be null.", () -> { + StringUtils.indexOfIgnoreCase(0, null, (String) null, markerStart, markerEnd, SearchMode.__BSE_MRK_WS); + return null; + }); + assertThrows(IllegalArgumentException.class, "The source string must not be null.", () -> { + StringUtils.indexOfIgnoreCase(0, null, "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS); + return null; + }); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", (String) null, markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "bcd", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(6, StringUtils.indexOfIgnoreCase(0, "ab -- abc", "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(5, StringUtils.indexOfIgnoreCase(0, "ab # abc", "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/*/* /c", "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/**/c", "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(5, StringUtils.indexOfIgnoreCase(0, "ab/* abc */c", "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(0, StringUtils.indexOfIgnoreCase(0, "abc", "abc", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); + assertEquals(3, StringUtils.indexOfIgnoreCase(0, "abc d efg", " d ", markerStart, markerEnd, SearchMode.__BSE_MRK_WS)); // exhaustive test set searchInMulti = new String[] { "A \"strange \"STRONG SsStRiNg to be searched in", "A 'strange 'STRONG SsStRiNg to be searched in", @@ -136,18 +138,17 @@ public void testIndexOfIgnoreCase() throws Exception { for (int i = 0; i < searchForMulti.length; i++) { for (int j = 0; j < searchInMulti.length; j++) { // multiple markers - assertEquals(expectedIdx[i], - StringUtils.indexOfIgnoreCase(0, searchInMulti[j], searchForMulti[i], markerStart, markerEnd, StringUtils.SEARCH_MODE__MRK_WS), + assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchInMulti[j], searchForMulti[i], markerStart, markerEnd, SearchMode.__MRK_WS), "Test C." + j + "." + i); assertEquals(expectedIdx[i], - StringUtils.indexOfIgnoreCase(0, searchInMulti[j], searchForMulti[i], markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS), + StringUtils.indexOfIgnoreCase(0, searchInMulti[j], searchForMulti[i], markerStart, markerEnd, SearchMode.__BSE_MRK_WS), "Test C." + j + "." + i); // single marker assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchInMulti[j], searchForMulti[i], markerStart.substring(j, j + 1), - markerEnd.substring(j, j + 1), StringUtils.SEARCH_MODE__MRK_WS), "Test C." + j + "." + i); + markerEnd.substring(j, j + 1), SearchMode.__MRK_WS), "Test C." + j + "." + i); assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchInMulti[j], searchForMulti[i], markerStart.substring(j, j + 1), - markerEnd.substring(j, j + 1), StringUtils.SEARCH_MODE__BSESC_MRK_WS), "Test C." + j + "." + i); + markerEnd.substring(j, j + 1), SearchMode.__BSE_MRK_WS), "Test C." + j + "." + i); } } @@ -156,12 +157,10 @@ public void testIndexOfIgnoreCase() throws Exception { expectedIdx = new int[] { 18, 26, -1, -1, 48, 37 }; for (int i = 0; i < searchForMulti.length; i++) { // multiple markers - assertEquals(expectedIdx[i], - StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS), + assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, SearchMode.__BSE_MRK_WS), "Test C.4." + i); // single marker - assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], "'", "'", StringUtils.SEARCH_MODE__BSESC_MRK_WS), - "Test C.5." + i); + assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], "'", "'", SearchMode.__BSE_MRK_WS), "Test C.5." + i); } searchIn = "A 'strange \\''STRONG \\`SsSTRING\\\" to be searched in"; @@ -169,12 +168,10 @@ public void testIndexOfIgnoreCase() throws Exception { expectedIdx = new int[] { 14, 24, -1, -1, 48, 37 }; for (int i = 0; i < searchForMulti.length; i++) { // multiple markers - assertEquals(expectedIdx[i], - StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, StringUtils.SEARCH_MODE__BSESC_MRK_WS), + assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, SearchMode.__BSE_MRK_WS), "Test C.6." + i); // single marker - assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], "'", "'", StringUtils.SEARCH_MODE__BSESC_MRK_WS), - "Test C.7." + i); + assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], "'", "'", SearchMode.__BSE_MRK_WS), "Test C.7." + i); } /* @@ -182,122 +179,165 @@ public void testIndexOfIgnoreCase() throws Exception { * searchMode) using combined and single search modes */ // basic test set - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, null, (String) null, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, null, "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", (String) null, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "bcd", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab -- abc", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab # abc", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/*/* /c", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/**/c", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/* abc */c", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(0, StringUtils.indexOfIgnoreCase(0, "abc", "abc", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(3, StringUtils.indexOfIgnoreCase(0, "abc d efg", " d ", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); + assertThrows(IllegalArgumentException.class, "The source string must not be null.", () -> { + StringUtils.indexOfIgnoreCase(0, null, (String) null, markerStart, markerEnd, SearchMode.__FULL); + return null; + }); + assertThrows(IllegalArgumentException.class, "The source string must not be null.", () -> { + StringUtils.indexOfIgnoreCase(0, null, "abc", markerStart, markerEnd, SearchMode.__FULL); + return null; + }); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", (String) null, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", "bcd", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab -- abc", "abc", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab # abc", "abc", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/*/* /c", "abc", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/**/c", "abc", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "ab/* abc */c", "abc", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(0, StringUtils.indexOfIgnoreCase(0, "abc", "abc", markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(3, StringUtils.indexOfIgnoreCase(0, "abc d efg", " d ", markerStart, markerEnd, SearchMode.__FULL)); // exhaustive test set searchIn = "/* MySQL01 *//* MySQL02 */ \"MySQL03\" /* MySQL04 */-- MySQL05\n/* MySQL06 *//* MySQL07 */ 'MySQL08' /* MySQL09 */-- # MySQL10\r\n" + "/* MySQL11 *//* MySQL12 */ `MySQL13` /* MySQL14 */# MySQL15\r\n/* MySQL16 *//* MySQL17 */ (MySQL18) /* MySQL19 */# -- MySQL20 \n" - + "/* MySQL21 *//* MySQL22 */ \\MySQL23--;/*! MySQL24 */ MySQL25 --"; + + "/* MySQL21 *//* MySQL22 */ \\MySQL23--;/*!12345 MySQL24 */ /*+ MySQL25 */ MySQL26 --"; searchFor = "mYSql"; - // 1. different markers in method arguments - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, null, null, StringUtils.SEARCH_MODE__BSESC_COM_WS); + // D.1. different markers in method arguments + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, null, null, SearchMode.__BSE_COM_MYM_HNT_WS); assertEquals(3, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "", "", StringUtils.SEARCH_MODE__ALL); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "", "", SearchMode.__FULL); assertEquals(3, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "'`(", "'`)", StringUtils.SEARCH_MODE__ALL); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "'`(", "'`)", SearchMode.__FULL); assertEquals(3, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "\"`(", "\"`)", StringUtils.SEARCH_MODE__ALL); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "\"`(", "\"`)", SearchMode.__FULL); assertEquals(8, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "\"'(", "\"')", StringUtils.SEARCH_MODE__ALL); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "\"'(", "\"')", SearchMode.__FULL); assertEquals(13, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "\"'`", "\"'`", StringUtils.SEARCH_MODE__ALL); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, "`'\"", "`'\"", SearchMode.__FULL); assertEquals(18, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - // 2a. search mode: all but skip markers - searchMode = StringUtils.SEARCH_MODE__BSESC_COM_WS; + // D.2a. search mode: all but SearchMode.SKIP_BETWEEN_MARKERS + searchMode = SearchMode.__BSE_COM_MYM_HNT_WS; pos = 0; - expectedIdx = new int[] { 3, 8, 13, 18, 24, 25, -1 }; + expectedIdx = new int[] { 3, 8, 13, 18, 23, 24, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.2a." + i); } - // 2b. search mode: only skip markers + // D.2b. search mode: only SearchMode.SKIP_BETWEEN_MARKERS searchMode = EnumSet.of(SearchMode.SKIP_BETWEEN_MARKERS); pos = 0; - expectedIdx = new int[] { 1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, -1 }; + expectedIdx = new int[] { 1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.2b." + i); } - // 3a. search mode: all but skip line comments - searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_BLOCK_COMMENTS, - SearchMode.SKIP_WHITE_SPACE); + // D.3a. search mode: all but SearchMode.SKIP_BLOCK_COMMENTS + searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_LINE_COMMENTS, + SearchMode.SKIP_MYSQL_MARKERS, SearchMode.SKIP_HINT_BLOCKS, SearchMode.SKIP_WHITE_SPACE); pos = 0; - expectedIdx = new int[] { 5, 10, 15, 20, 24, 25, -1 }; + expectedIdx = new int[] { 1, 2, 4, 6, 7, 9, 11, 12, 14, 16, 17, 19, 21, 22, 23, 24, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.3a." + i); } - // 3b. search mode: only skip line comments - searchMode = EnumSet.of(SearchMode.SKIP_LINE_COMMENTS); + // D.3b. search mode: only SearchMode.SKIP_BLOCK_COMMENTS + searchMode = EnumSet.of(SearchMode.SKIP_BLOCK_COMMENTS); pos = 0; - expectedIdx = new int[] { 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24, 25, -1 }; + expectedIdx = new int[] { 3, 5, 8, 10, 13, 15, 18, 20, 23, 24, 25, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.3b." + i); } - // 4a. search mode: all but skip block comments - searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_LINE_COMMENTS, SearchMode.SKIP_WHITE_SPACE); + // D.4a. search mode: all but SearchMode.SKIP_LINE_COMMENTS + searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_BLOCK_COMMENTS, + SearchMode.SKIP_MYSQL_MARKERS, SearchMode.SKIP_HINT_BLOCKS, SearchMode.SKIP_WHITE_SPACE); pos = 0; - expectedIdx = new int[] { 1, 2, 4, 6, 7, 9, 11, 12, 14, 16, 17, 19, 21, 22, 24, 25, -1 }; + expectedIdx = new int[] { 5, 10, 15, 20, 23, 24, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.4a." + i); } - // 4b. search mode: only skip block comments - searchMode = EnumSet.of(SearchMode.SKIP_BLOCK_COMMENTS); + // D.4b. search mode: only SearchMode.SKIP_LINE_COMMENTS + searchMode = EnumSet.of(SearchMode.SKIP_LINE_COMMENTS); pos = 0; - expectedIdx = new int[] { 3, 5, 8, 10, 13, 15, 18, 20, 23, 24, 25, -1 }; + expectedIdx = new int[] { 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.4b." + i); } - // 5a. search mode: all but allow backslash escape - pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, markerStart, markerEnd, StringUtils.SEARCH_MODE__MRK_COM_WS); + // D.5a. search mode: all but SearchMode.SKIP_MYSQL_MARKERS + searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_BLOCK_COMMENTS, + SearchMode.SKIP_LINE_COMMENTS, SearchMode.SKIP_HINT_BLOCKS, SearchMode.SKIP_WHITE_SPACE); + pos = 0; + expectedIdx = new int[] { 23, 24, 26, -1 }; + for (int i = 0; i < expectedIdx.length; i++, pos++) { + pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); + assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.5a." + i); + } + // D.5b. search mode: only SearchMode.SKIP_MYSQL_MARKERS + searchMode = EnumSet.of(SearchMode.SKIP_MYSQL_MARKERS); + pos = 0; + expectedIdx = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1 }; + for (int i = 0; i < expectedIdx.length; i++, pos++) { + pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); + assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.5b." + i); + } + + // D.6a. search mode: all but SearchMode.SKIP_HINT_BLOCKS + searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_BLOCK_COMMENTS, + SearchMode.SKIP_LINE_COMMENTS, SearchMode.SKIP_MYSQL_MARKERS, SearchMode.SKIP_WHITE_SPACE); + pos = 0; + expectedIdx = new int[] { 23, 24, 25, 26, -1 }; + for (int i = 0; i < expectedIdx.length; i++, pos++) { + pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); + assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.6a." + i); + } + // D.6b. search mode: only SearchMode.SKIP_HINT_BLOCKS + searchMode = EnumSet.of(SearchMode.SKIP_HINT_BLOCKS); + pos = 0; + expectedIdx = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, -1 }; + for (int i = 0; i < expectedIdx.length; i++, pos++) { + pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); + assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.6b." + i); + } + + // D.7a. search mode: all but SearchMode.ALLOW_BACKSLASH_ESCAPE + pos = StringUtils.indexOfIgnoreCase(0, searchIn, searchFor, markerStart, markerEnd, SearchMode.__MRK_COM_MYM_HNT_WS); assertEquals(23, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - // 5b. search mode: only allow backslash escape + // D.7b. search mode: only allow backslash escape searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE); pos = 0; - expectedIdx = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, -1 }; + expectedIdx = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, searchMode); - assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.5b." + i); + assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.7b." + i); } - // 6. all together + // D.8. all together pos = 0; - expectedIdx = new int[] { 24, 25, -1 }; + expectedIdx = new int[] { 23, 24, 26, -1 }; for (int i = 0; i < expectedIdx.length; i++, pos++) { - pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL); - assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.6." + i); + pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, SearchMode.__FULL); + assertEquals(expectedIdx[i], testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.8." + i); } - pos = StringUtils.indexOfIgnoreCase(0, searchIn, "YourSQL", markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, "YourSQL", markerStart, markerEnd, SearchMode.__FULL); assertEquals(-1, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - // 7. none + // D.9. none pos = 0; - for (int i = 1; i <= 25; i++, pos++) { - pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, StringUtils.SEARCH_MODE__NONE); - assertEquals(i, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.7." + i); + for (int i = 1; i <= 26; i++, pos++) { + pos = StringUtils.indexOfIgnoreCase(pos, searchIn, searchFor, markerStart, markerEnd, SearchMode.__NONE); + assertEquals(i, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos), "Test D.9." + i); } - pos = StringUtils.indexOfIgnoreCase(pos + 1, searchIn, searchFor, markerStart, markerEnd, StringUtils.SEARCH_MODE__NONE); + pos = StringUtils.indexOfIgnoreCase(pos + 1, searchIn, searchFor, markerStart, markerEnd, SearchMode.__NONE); assertEquals(-1, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); - pos = StringUtils.indexOfIgnoreCase(0, searchIn, "YourSQL", markerStart, markerEnd, StringUtils.SEARCH_MODE__NONE); + pos = StringUtils.indexOfIgnoreCase(0, searchIn, "YourSQL", markerStart, markerEnd, SearchMode.__NONE); assertEquals(-1, testIndexOfIgnoreCaseMySQLIndexMarker(searchIn, pos)); /* @@ -377,7 +417,7 @@ public Void call() throws Exception { for (int i = 0; i < searchForMulti.length; i++) { assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, searchMode), "Test F.3." + i); } - searchMode = StringUtils.SEARCH_MODE__BSESC_COM_WS; + searchMode = SearchMode.__BSE_COM_MYM_HNT_WS; expectedIdx = new int[] { 0, 5, 12, 21 }; for (int i = 0; i < searchForMulti.length; i++) { assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, searchMode), "Test F.4." + i); @@ -391,7 +431,7 @@ public Void call() throws Exception { for (int i = 0; i < searchForMulti.length; i++) { assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, searchMode), "Test F.5." + i); } - searchMode = StringUtils.SEARCH_MODE__BSESC_COM_WS; + searchMode = SearchMode.__BSE_COM_MYM_HNT_WS; expectedIdx = new int[] { 0, 5, 12, 24 }; for (int i = 0; i < searchForMulti.length; i++) { assertEquals(expectedIdx[i], StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti[i], markerStart, markerEnd, searchMode), "Test F.6." + i); @@ -402,57 +442,48 @@ public Void call() throws Exception { * searchMode) using all combined search modes */ // basic test set - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, null, (String[]) null, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, null, new String[] { "abc" }, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", (String[]) null, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", new String[] {}, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", new String[] { "", "" }, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc -- d", new String[] { "c", "d" }, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(0, StringUtils.indexOfIgnoreCase(0, "abc", new String[] { "abc" }, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, - StringUtils.indexOfIgnoreCase(0, "abc d efg h", new String[] { " d ", " efg" }, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(3, StringUtils.indexOfIgnoreCase(0, "abc d efg h", new String[] { " d ", "efg" }, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); + assertThrows(IllegalArgumentException.class, "The source string must not be null.", () -> { + StringUtils.indexOfIgnoreCase(0, null, (String[]) null, markerStart, markerEnd, SearchMode.__FULL); + return null; + }); + assertThrows(IllegalArgumentException.class, "The source string must not be null.", () -> { + StringUtils.indexOfIgnoreCase(0, null, new String[] { "abc" }, markerStart, markerEnd, SearchMode.__FULL); + return null; + }); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", (String[]) null, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", new String[] {}, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc", new String[] { "", "" }, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc -- d", new String[] { "c", "d" }, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(0, StringUtils.indexOfIgnoreCase(0, "abc", new String[] { "abc" }, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "abc d efg h", new String[] { " d ", " efg" }, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(3, StringUtils.indexOfIgnoreCase(0, "abc d efg h", new String[] { " d ", "efg" }, markerStart, markerEnd, SearchMode.__FULL)); // exhaustive test set searchForMulti = new String[] { "ONE", "two", "ThrEE" }; // 1. simple strings - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "onetwothee", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "one one one one two", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(11, StringUtils.indexOfIgnoreCase(0, "onetwothee one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(20, - StringUtils.indexOfIgnoreCase(0, "/* one two three */ one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "onetwothee", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(-1, StringUtils.indexOfIgnoreCase(0, "one one one one two", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(11, StringUtils.indexOfIgnoreCase(0, "onetwothee one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(20, StringUtils.indexOfIgnoreCase(0, "/* one two three */ one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); assertEquals(38, StringUtils.indexOfIgnoreCase(0, "/* one two three *//* one two three */one two three", searchForMulti, markerStart, markerEnd, - StringUtils.SEARCH_MODE__ALL)); - assertEquals(7, - StringUtils.indexOfIgnoreCase(0, "/*one*/one/*two*/two/*three*/three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(0, - StringUtils.indexOfIgnoreCase(0, "one/*one*/two/*two*/three/*three*/", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(16, - StringUtils.indexOfIgnoreCase(0, "# one two three\none two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(17, - StringUtils.indexOfIgnoreCase(0, "-- one two three\none two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(22, - StringUtils.indexOfIgnoreCase(0, "/* one two three */--;one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(4, - StringUtils.indexOfIgnoreCase(0, "/*! one two three */--;one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(9, StringUtils.indexOfIgnoreCase(0, "/*!50616 one two three */--;one two three", searchForMulti, markerStart, markerEnd, - StringUtils.SEARCH_MODE__ALL)); - assertEquals(16, - StringUtils.indexOfIgnoreCase(0, "\"one two three\" one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(16, - StringUtils.indexOfIgnoreCase(0, "'one two three' one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(16, - StringUtils.indexOfIgnoreCase(0, "`one two three` one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - assertEquals(16, - StringUtils.indexOfIgnoreCase(0, "(one two three) one two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); - - assertEquals(3, StringUtils.indexOfIgnoreCase(0, "/* one two three */ one two three", searchForMulti, markerStart, markerEnd, - StringUtils.SEARCH_MODE__NONE)); - assertEquals(2, - StringUtils.indexOfIgnoreCase(0, "# one two three\none two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__NONE)); - assertEquals(3, - StringUtils.indexOfIgnoreCase(0, "-- one two three\none two three", searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__NONE)); + SearchMode.__FULL)); + assertEquals(7, StringUtils.indexOfIgnoreCase(0, "/*one*/one/*two*/two/*three*/three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(0, StringUtils.indexOfIgnoreCase(0, "one/*one*/two/*two*/three/*three*/", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(16, StringUtils.indexOfIgnoreCase(0, "# one two three\none two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(17, StringUtils.indexOfIgnoreCase(0, "-- one two three\none two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(22, StringUtils.indexOfIgnoreCase(0, "/* one two three */--;one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(4, StringUtils.indexOfIgnoreCase(0, "/*! one two three */--;one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(9, + StringUtils.indexOfIgnoreCase(0, "/*!50616 one two three */--;one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(16, StringUtils.indexOfIgnoreCase(0, "\"one two three\" one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(16, StringUtils.indexOfIgnoreCase(0, "'one two three' one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(16, StringUtils.indexOfIgnoreCase(0, "`one two three` one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + assertEquals(16, StringUtils.indexOfIgnoreCase(0, "(one two three) one two three", searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); + + assertEquals(3, StringUtils.indexOfIgnoreCase(0, "/* one two three */ one two three", searchForMulti, markerStart, markerEnd, SearchMode.__NONE)); + assertEquals(2, StringUtils.indexOfIgnoreCase(0, "# one two three\none two three", searchForMulti, markerStart, markerEnd, SearchMode.__NONE)); + assertEquals(3, StringUtils.indexOfIgnoreCase(0, "-- one two three\none two three", searchForMulti, markerStart, markerEnd, SearchMode.__NONE)); // 2. complex string searchIn = "/* one two three *//* one two three */ one 'two' three -- \"one/* one */two three\" one owt three\n" @@ -461,11 +492,11 @@ public Void call() throws Exception { printRuler(searchIn); // 2.1. skip all - assertEquals(159, StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__ALL)); + assertEquals(159, StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti, markerStart, markerEnd, SearchMode.__FULL)); // 2.2. search within block comments searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_LINE_COMMENTS, SearchMode.SKIP_WHITE_SPACE); assertEquals(3, StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti, markerStart, markerEnd, searchMode)); - assertEquals(3, StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti, markerStart, markerEnd, StringUtils.SEARCH_MODE__NONE)); + assertEquals(3, StringUtils.indexOfIgnoreCase(0, searchIn, searchForMulti, markerStart, markerEnd, SearchMode.__NONE)); // 2.3. search within line comments and unidentified markers searchMode = EnumSet.of(SearchMode.ALLOW_BACKSLASH_ESCAPE, SearchMode.SKIP_BETWEEN_MARKERS, SearchMode.SKIP_BLOCK_COMMENTS, SearchMode.SKIP_WHITE_SPACE); @@ -1358,4 +1389,70 @@ public void testQuoteUnquoteBytes() throws Exception { assertEquals(origBytes[i], unquotedBytes[i]); } } + + @Test + public void testStringUtils001() throws Exception { + char[] cdata = new char[26]; + byte[] bdata = new byte[26]; + String s1 = "String Consist of some small Sample Data"; + for (int i = 0; i < 26; i++) { + cdata[i] = (char) ('A' + i); + } + + byte[] bdata3 = new byte[26]; + for (int i = 0; i < 26; i++) { + bdata3[i] = (byte) ('a' + i % 6); + } + + System.out.print("\n StringUtils.escapeQuote(XabXcX) :" + StringUtils.escapeQuote("XXabcX", "X")); + System.out.print("\n StringUtils.escapeQuote('ab'c') :" + StringUtils.escapeQuote("'ab'c'", "'")); + System.out.print("\n StringUtils.escapeQuote('select \\1\\, \\6\\') :" + StringUtils.escapeQuote("select \\1\\, \\6\\", "\\")); + + assertEquals("''abc", StringUtils.escapeQuote("''abc'", "'"), "escapeQuote() returns Wrong result"); + assertEquals('R', StringUtils.firstNonWsCharUc(" \t Raj"), "firstNonWsCharUc() returns Wrong result"); + assertEquals("select \\\\1\\\\, \\\\6\\\\", StringUtils.escapeQuote("select \\1\\, \\6\\", "\\"), "escapeQuote() returns Wrong result"); + String s2 = null; + assertEquals(null, StringUtils.escapeQuote(s2, "'"), "escapeQuote() returns Wrong result"); + bdata = StringUtils.getBytes(cdata, 5, 5); + + System.out.print("\n indexOf(bdata2,'F') (0):" + StringUtils.indexOf(bdata, 'F')); + System.out.print("\n indexOf(bdata2,'I') (3):" + StringUtils.indexOf(bdata, 'I')); + System.out.print("\n isValidIdChar('+') (false) : " + StringUtils.isValidIdChar('+')); + System.out.print("\n isValidIdChar('X')(true) : " + StringUtils.isValidIdChar('X')); + System.out.print("\n lastIndexOf(bdata3,'d')(21) :" + StringUtils.lastIndexOf(bdata3, 'd')); + System.out.print("\n indexOf(bdata3,'d') (3):" + StringUtils.indexOf(bdata3, 'd')); + System.out.print("\n wildCompareIgnoreCase(s1,'some%mal') (false):" + StringUtils.wildCompareIgnoreCase(s1, "some%mal")); + System.out.print("\n wildCompareIgnoreCase(s1,'some_s_al_') (false):" + StringUtils.wildCompareIgnoreCase(s1, "some_s_al_")); + System.out.print("\n wildCompareIgnoreCase(s1,'some') (false):" + StringUtils.wildCompareIgnoreCase(s1, "some")); + System.out.print("\n wildCompareIgnoreCase(s1,'S_%s_%S_%Da%'):" + StringUtils.wildCompareIgnoreCase(s1, "S_%s_%S_%Da%")); + System.out.print("\n wildCompareIgnoreCase(s1,'S_r_n_ C_n_i_t%'):" + StringUtils.wildCompareIgnoreCase(s1, "S_r_n_ C_n_i_t%")); + assertEquals(0, StringUtils.indexOf(bdata, 'F'), "indexOf() returns Wrong result"); + assertEquals(false, StringUtils.isValidIdChar('+'), "isValidIdChar() returns Wrong result"); + assertEquals(true, StringUtils.isValidIdChar('X'), "isValidIdChar() returns Wrong result"); + assertEquals(21, StringUtils.lastIndexOf(bdata3, 'd'), "lastIndexOf() returns Wrong result"); + assertEquals(3, StringUtils.indexOf(bdata3, 'd'), "indexOf() returns Wrong result"); + + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "some"), "wildCompareIgnoreCase() returns Wrong result"); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "small"), "wildCompareIgnoreCase() returns Wrong result"); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "Data"), "wildCompareIgnoreCase() returns Wrong result"); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "amp"), "wildCompareIgnoreCase() returns Wrong result"); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, " "), "wildCompareIgnoreCase() returns Wrong result"); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "some_s_al_"), "wildCompareIgnoreCase() returns Wrong result"); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "some%mal"), "wildCompareIgnoreCase() returns Wrong result"); + + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "some_s_al_"), "wildCompareIgnoreCase() returns Wrong result"); + assertTrue(StringUtils.wildCompareIgnoreCase(s1, "S_r_n_ C_n_i_t%"), "wildCompareIgnoreCase() returns Wrong result"); + + System.out.print("\n firstNonWsCharUc(' \t Raj') : " + StringUtils.firstNonWsCharUc(" \t Raj")); + System.out.print("\n escapeQuote('select '1', '6'') : " + StringUtils.escapeQuote("select '1', '6'", "'")); + } + + @Test + public void testStripComments() throws Exception { + String testString = "-- 1st comment\nthis\nis/* 2nd comment */not # 3rd \"comment\"\n-- 4th comment\r\n /* 5th comment */a 'comment'" + + "--neither this! nor '# this is a comment'"; + String expected = "this\nis not a 'comment'--neither this! nor '# this is a comment'"; + + assertEquals(expected, StringUtils.stripCommentsAndHints(testString, "\"'", "\"'", true)); + } } diff --git a/src/test/java/com/mysql/cj/xdevapi/ExprParserTest.java b/src/test/java/com/mysql/cj/xdevapi/ExprParserTest.java index d935f1cbd..a7e341c90 100644 --- a/src/test/java/com/mysql/cj/xdevapi/ExprParserTest.java +++ b/src/test/java/com/mysql/cj/xdevapi/ExprParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -31,8 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.util.Arrays; import java.util.Iterator; @@ -62,13 +62,10 @@ public class ExprParserTest { * @param s */ private void checkBadParse(String s) { - try { + assertThrows(WrongArgumentException.class, () -> { Expr e = new ExprParser(s).parse(); System.err.println("Parsed as: " + e); - fail("Expected exception while parsing: '" + s + "'"); - } catch (WrongArgumentException ex) { - // expected - } + }, "Expected exception while parsing: '" + s + "'"); } @Test diff --git a/src/test/java/com/mysql/cj/xdevapi/JsonDocTest.java b/src/test/java/com/mysql/cj/xdevapi/JsonDocTest.java index 2390d2e0b..9783da79c 100644 --- a/src/test/java/com/mysql/cj/xdevapi/JsonDocTest.java +++ b/src/test/java/com/mysql/cj/xdevapi/JsonDocTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -719,10 +719,8 @@ protected static EX assertThrows(Class throwable, Cal try { testRoutine.call(); } catch (Throwable t) { - if (!throwable.isAssignableFrom(t.getClass())) { - fail("Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown."); - } - + assertTrue(throwable.isAssignableFrom(t.getClass()), + "Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown."); return throwable.cast(t); } fail("Expected exception of type '" + throwable.getName() + "'."); @@ -735,14 +733,9 @@ protected static EX assertThrows(Class throwable, Str try { testRoutine.call(); } catch (Throwable t) { - if (!throwable.isAssignableFrom(t.getClass())) { - fail("Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown."); - } - - if (!t.getMessage().matches(msgMatchesRegex)) { - fail("The error message [" + t.getMessage() + "] was expected to match [" + msgMatchesRegex + "]."); - } - + assertTrue(throwable.isAssignableFrom(t.getClass()), + "Expected exception of type '" + throwable.getName() + "' but instead a exception of type '" + t.getClass().getName() + "' was thrown."); + assertTrue(t.getMessage().matches(msgMatchesRegex), "The error message [" + t.getMessage() + "] was expected to match [" + msgMatchesRegex + "]."); return throwable.cast(t); } fail("Expected exception of type '" + throwable.getName() + "'."); diff --git a/src/test/java/testsuite/BaseTestCase.java b/src/test/java/testsuite/BaseTestCase.java index eb81c7c22..4dc2a943b 100644 --- a/src/test/java/testsuite/BaseTestCase.java +++ b/src/test/java/testsuite/BaseTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -31,6 +31,7 @@ import static com.mysql.cj.util.StringUtils.isNullOrEmpty; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -70,6 +71,7 @@ import com.mysql.cj.conf.ConnectionUrlParser; import com.mysql.cj.conf.HostInfo; import com.mysql.cj.conf.PropertyDefinitions; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.jdbc.JdbcConnection; import com.mysql.cj.jdbc.NonRegisteringDriver; @@ -81,14 +83,6 @@ * Base class for all test cases. Creates connections, statements, etc. and closes them. */ public abstract class BaseTestCase { - // next variables disable some tests - protected boolean DISABLED_testBug15121 = true; // TODO needs to be fixed on server - protected boolean DISABLED_testBug7033 = true; // TODO disabled for unknown reason - protected boolean DISABLED_testBug2654 = true; // TODO check if it's still a server-level bug - protected boolean DISABLED_testBug5136 = true; // TODO disabled for unknown reason - protected boolean DISABLED_testBug65503 = true; // TODO disabled for unknown reason - protected boolean DISABLED_testContention = true; // TODO disabled for unknown reason - protected boolean DISABLED_testBug3620new = true; // TODO this test is working in c/J 5.1 but fails here; disabled for later analysis /** * JDBC URL, initialized from com.mysql.cj.testsuite.url system property, or defaults to jdbc:mysql:///test and its connection URL. @@ -96,12 +90,7 @@ public abstract class BaseTestCase { public static String dbUrl = "jdbc:mysql:///test"; public static String timeZoneFreeDbUrl = "jdbc:mysql:///test"; protected static ConnectionUrl mainConnectionUrl = null; - - /** - * JDBC URL, initialized from com.mysql.cj.testsuite.url.openssl system property and its connection URL - */ - protected static String sha256Url = null; - protected static ConnectionUrl sha256ConnectionUrl = null; + protected boolean isOpenSSL = false; /** Instance counter */ private static int instanceCount = 1; @@ -109,8 +98,6 @@ public abstract class BaseTestCase { /** Connection to server, initialized in setUp() Cleaned up in tearDown(). */ protected Connection conn = null; - protected Connection sha256Conn = null; - /** Server version `this.conn' is connected to. */ protected ServerVersion serverVersion; @@ -129,7 +116,7 @@ public abstract class BaseTestCase { /** * Default catalog. */ - protected final String dbName; + protected String dbName; /** * PreparedStatement to be used in tests, not initialized. Cleaned up in @@ -142,16 +129,12 @@ public abstract class BaseTestCase { */ protected ResultSet rs = null; - protected ResultSet sha256Rs = null; - /** * Statement to be used in tests, initialized in setUp(). Cleaned up in * tearDown(). */ protected Statement stmt = null; - protected Statement sha256Stmt = null; - private boolean isOnCSFS = true; /** @@ -163,19 +146,32 @@ public BaseTestCase() { String newDbUrl = System.getProperty(PropertyDefinitions.SYSP_testsuite_url); if ((newDbUrl != null) && (newDbUrl.trim().length() != 0)) { - dbUrl = newDbUrl; + dbUrl = sanitizeDbName(newDbUrl); } - timeZoneFreeDbUrl = dbUrl.replaceAll(PropertyKey.connectionTimeZone.getKeyName() + "=", PropertyKey.connectionTimeZone.getKeyName() + "VOID=") - .replaceAll("serverTimezone=", "serverTimezoneVOID="); mainConnectionUrl = ConnectionUrl.getConnectionUrlInstance(dbUrl, null); this.dbName = mainConnectionUrl.getDatabase(); - String defaultSha256Url = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_openssl); + timeZoneFreeDbUrl = dbUrl.replaceAll(PropertyKey.connectionTimeZone.getKeyName() + "=", PropertyKey.connectionTimeZone.getKeyName() + "VOID=") + .replaceAll("serverTimezone=", "serverTimezoneVOID="); + } - if ((defaultSha256Url != null) && (defaultSha256Url.trim().length() != 0)) { - sha256Url = defaultSha256Url; - sha256ConnectionUrl = ConnectionUrl.getConnectionUrlInstance(sha256Url, null); + private String sanitizeDbName(String url) { + ConnectionUrl parsedUrl = ConnectionUrl.getConnectionUrlInstance(url, null); + if (StringUtils.isNullOrEmpty(parsedUrl.getDatabase())) { + List splitUp = StringUtils.split(url, "\\?", true); + StringBuilder value = new StringBuilder(); + for (int i = 0; i < splitUp.size(); i++) { + value.append(splitUp.get(i)); + if (i == 0) { + if (!splitUp.get(i).endsWith("/")) { + value.append("/"); + } + value.append("cjtest_8_0?"); + } + } + url = value.toString(); } + return url; } protected void createSchemaObject(String objectType, String objectName, String columnsAndOtherStuff) throws SQLException { @@ -334,19 +330,6 @@ protected void dropSchemaObject(Statement st, String objectType, String objectNa } } - protected Connection getAdminConnection() throws SQLException { - return getAdminConnectionWithProps(new Properties()); - } - - protected Connection getAdminConnectionWithProps(Properties props) throws SQLException { - String adminUrl = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_admin); - - if (adminUrl != null) { - return DriverManager.getConnection(adminUrl, props); - } - return null; - } - public Connection getConnectionWithProps(String propsList) throws SQLException { return getConnectionWithProps(dbUrl, propsList); } @@ -397,16 +380,10 @@ protected Connection getConnectionWithProps(String url, Properties props) throws } protected Connection getNewConnection() throws SQLException { - return DriverManager.getConnection(dbUrl); - } - - protected Connection getNewSha256Connection() throws SQLException { - if (sha256Url != null) { - Properties props = new Properties(); - props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - return DriverManager.getConnection(sha256Url, props); - } - return null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + return DriverManager.getConnection(dbUrl, props); } /** @@ -599,10 +576,6 @@ protected Object getSingleValueWithQuery(String query) throws SQLException { return getSingleIndexedValueWithQuery(1, query); } - protected boolean isAdminConnectionConfigured() { - return System.getProperty(PropertyDefinitions.SYSP_testsuite_url_admin) != null; - } - protected boolean isServerRunningOnWindows() throws SQLException { return (getMysqlVariable("datadir").indexOf('\\') != -1); } @@ -645,14 +618,9 @@ protected final boolean runLongTests() { */ protected boolean isSysPropDefined(String propName) { String prop = System.getProperty(propName); - return (prop != null) && (prop.length() > 0); } - protected boolean runMultiHostTests() { - return !isSysPropDefined(PropertyDefinitions.SYSP_testsuite_disable_multihost_tests); - } - /** * Creates resources used by all tests. * @@ -670,12 +638,14 @@ public void setUpBase(TestInfo testInfo) throws Exception { this.createdObjects = new ArrayList<>(); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); // testsuite is built upon non-SSL default connection + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); // testsuite is built upon non-SSL default connection props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.createDatabaseIfNotExist.getKeyName(), "true"); + if (StringUtils.isNullOrEmpty(mainConnectionUrl.getDatabase())) { + props.setProperty(PropertyKey.DBNAME.getKeyName(), this.dbName); + } this.conn = DriverManager.getConnection(dbUrl, props); - this.sha256Conn = sha256Url == null ? null : DriverManager.getConnection(sha256Url, props); - this.serverVersion = ((JdbcConnection) this.conn).getServerVersion(); this.stmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); @@ -685,6 +655,25 @@ public void setUpBase(TestInfo testInfo) throws Exception { this.rs = this.stmt.executeQuery("SELECT VERSION()"); this.rs.next(); logDebug("Connected to " + this.rs.getString(1)); + this.rs.close(); + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'auto_generate_certs'"); + if (this.rs.next()) { + this.isOpenSSL = true; + } + + // ensure max_connections value is enough to run tests + this.rs.close(); + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_connections'"); + this.rs.next(); + int maxConnections = this.rs.getInt(2); + + this.rs = this.stmt.executeQuery("show status like 'threads_connected'"); + this.rs.next(); + int usedConnections = this.rs.getInt(2); + + if (maxConnections - usedConnections < 200) { + this.stmt.executeUpdate("SET GLOBAL max_connections=" + (maxConnections + 200)); + } } else { logDebug("Connected to " + this.conn.getMetaData().getDatabaseProductName() + " / " + this.conn.getMetaData().getDatabaseProductVersion()); } @@ -696,26 +685,6 @@ public void setUpBase(TestInfo testInfo) throws Exception { } this.isOnCSFS = !this.conn.getMetaData().storesLowerCaseIdentifiers(); - - if (this.sha256Conn != null) { - this.sha256Stmt = this.sha256Conn.createStatement(); - - try { - if (sha256Url.indexOf("mysql") != -1) { - this.sha256Rs = this.sha256Stmt.executeQuery("SELECT VERSION()"); - this.sha256Rs.next(); - logDebug("Connected to " + this.sha256Rs.getString(1)); - } else { - logDebug("Connected to " + this.sha256Conn.getMetaData().getDatabaseProductName() + " / " - + this.sha256Conn.getMetaData().getDatabaseProductVersion()); - } - } finally { - if (this.sha256Rs != null) { - this.sha256Rs.close(); - this.sha256Rs = null; - } - } - } } /** @@ -732,22 +701,8 @@ public void tearDownBase() throws Exception { } } - if (this.sha256Rs != null) { - try { - this.sha256Rs.close(); - } catch (SQLException SQLE) { - } - } - if (System.getProperty(PropertyDefinitions.SYSP_testsuite_retainArtifacts) == null) { - Statement st = this.conn == null || this.conn.isClosed() ? getNewConnection().createStatement() : this.conn.createStatement(); - Statement sha256st; - if (this.sha256Conn == null || this.sha256Conn.isClosed()) { - Connection c = getNewSha256Connection(); - sha256st = c == null ? null : c.createStatement(); - } else { - sha256st = this.sha256Conn.createStatement(); - } + Statement st = (this.conn == null || this.conn.isClosed() ? getNewConnection() : this.conn).createStatement(); for (int i = 0; i < this.createdObjects.size(); i++) { String[] objectInfo = this.createdObjects.get(i); @@ -756,16 +711,8 @@ public void tearDownBase() throws Exception { dropSchemaObject(st, objectInfo[0], objectInfo[1]); } catch (SQLException SQLE) { } - - try { - dropSchemaObject(sha256st, objectInfo[0], objectInfo[1]); - } catch (SQLException SQLE) { - } } st.close(); - if (sha256st != null) { - sha256st.close(); - } } if (this.stmt != null) { @@ -775,13 +722,6 @@ public void tearDownBase() throws Exception { } } - if (this.sha256Stmt != null) { - try { - this.sha256Stmt.close(); - } catch (SQLException SQLE) { - } - } - if (this.pstmt != null) { try { this.pstmt.close(); @@ -795,13 +735,6 @@ public void tearDownBase() throws Exception { } catch (SQLException SQLE) { } } - - if (this.sha256Conn != null) { - try { - this.sha256Conn.close(); - } catch (SQLException SQLE) { - } - } } /** @@ -1009,15 +942,10 @@ protected static EX assertThrows(String message, Class 0; + } + + protected boolean supportsLoadLocalInfile(Statement st) throws Exception { + ResultSet rs1 = st.executeQuery("SHOW VARIABLES LIKE 'local_infile'"); + return rs1.next() && "ON".equalsIgnoreCase(rs1.getString(2)); + } + + protected boolean supportsTestCertificates(Statement st) throws Exception { + ResultSet rs1 = st.executeQuery("SHOW VARIABLES LIKE 'ssl_ca'"); + return rs1.next() && rs1.getString(2).contains("ssl-test-certs"); + } + + protected boolean supportsTestSha256PasswordKeys(Statement st) throws Exception { + ResultSet rs1 = st.executeQuery("SHOW VARIABLES LIKE 'sha256_password_public_key_path'"); + return rs1.next() && rs1.getString(2).contains("ssl-test-certs"); + } + + protected boolean supportsTestCachingSha2PasswordKeys(Statement st) throws Exception { + ResultSet rs1 = st.executeQuery("SHOW VARIABLES LIKE 'caching_sha2_password_private_key_path'"); + return rs1.next() && rs1.getString(2).contains("ssl-test-certs"); + } + + protected boolean supportsTLSv1_2(ServerVersion version) throws Exception { + return version.meetsMinimum(new ServerVersion(5, 7, 28)) + || version.meetsMinimum(new ServerVersion(5, 6, 46)) && !version.meetsMinimum(new ServerVersion(5, 7, 0)) + || version.meetsMinimum(new ServerVersion(5, 6, 0)) && Util.isEnterpriseEdition(version.toString()); + + } + + protected void assertSessionStatusEquals(Statement st, String statusVariable, String expected) throws Exception { + ResultSet rs1 = st.executeQuery("SHOW SESSION STATUS LIKE '" + statusVariable + "'"); + assertTrue(rs1.next()); + assertEquals(expected, rs1.getString(2)); + } + } diff --git a/src/test/java/testsuite/InjectedSocketFactory.java b/src/test/java/testsuite/InjectedSocketFactory.java index 3b6459844..bbab2f5e3 100644 --- a/src/test/java/testsuite/InjectedSocketFactory.java +++ b/src/test/java/testsuite/InjectedSocketFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. + * Copyright (c) 2020, 2021, 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 @@ -29,7 +29,7 @@ package testsuite; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.Closeable; import java.io.IOException; @@ -363,9 +363,7 @@ public int read(byte[] b, int off, int len) throws IOException { return readCount; } catch (SocketTimeoutException e) { this.loopCount++; - if (this.loopCount > 10) { - fail("Probable infinite loop at MySQLIO.clearInputStream()."); - } + assertFalse(this.loopCount > 10, "Probable infinite loop at MySQLIO.clearInputStream()."); return -1; } } diff --git a/src/test/java/testsuite/perf/RetrievalPerfTest.java b/src/test/java/testsuite/perf/RetrievalPerfTest.java index 88ab23c0f..3c11fdcad 100644 --- a/src/test/java/testsuite/perf/RetrievalPerfTest.java +++ b/src/test/java/testsuite/perf/RetrievalPerfTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -30,6 +30,7 @@ package testsuite.perf; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -66,9 +67,9 @@ public void setUp() throws Exception { */ @Test public void testRetrievalCached() throws Exception { - if (!((MysqlConnection) this.conn).getSession().getServerSession().isQueryCacheEnabled()) { - return; - } + assumeTrue(((MysqlConnection) this.conn).getSession().getServerSession().isQueryCacheEnabled(), + "This test requires the server with enabled query cache."); + this.stmt.executeUpdate("SET QUERY_CACHE_TYPE = DEMAND"); double fullBegin = System.currentTimeMillis(); diff --git a/src/test/java/testsuite/regression/BlobRegressionTest.java b/src/test/java/testsuite/regression/BlobRegressionTest.java index cbead9eab..e0fc0c6dc 100644 --- a/src/test/java/testsuite/regression/BlobRegressionTest.java +++ b/src/test/java/testsuite/regression/BlobRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.io.ByteArrayInputStream; import java.io.File; @@ -50,6 +50,7 @@ import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.util.StringUtils; @@ -107,18 +108,44 @@ public void testBug2670() throws Exception { */ @Test public void testUpdateLongBlobGT16M() throws Exception { - byte[] blobData = new byte[18 * 1024 * 1024]; // 18M blob - - createTable("testUpdateLongBlob", "(blobField LONGBLOB)"); - this.stmt.executeUpdate("INSERT INTO testUpdateLongBlob (blobField) VALUES (NULL)"); + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); + this.rs.next(); + long len = 4 + 1024 * 1024 * 36 + "UPDATE testUpdateLongBlob SET blobField=".length(); + long defaultMaxAllowedPacket = this.rs.getInt(2); + boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < len; - this.pstmt = this.conn.prepareStatement("UPDATE testUpdateLongBlob SET blobField=?"); - this.pstmt.setBytes(1, blobData); + Connection con1 = null; + Connection con2 = null; try { - this.pstmt.executeUpdate(); - } catch (SQLException sqlEx) { - if (sqlEx.getMessage().indexOf("max_allowed_packet") != -1) { - fail("You need to increase max_allowed_packet to at least 18M before running this test!"); + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 37); + } + byte[] blobData = new byte[18 * 1024 * 1024]; // 18M blob + + createTable("testUpdateLongBlob", "(blobField LONGBLOB)"); + this.stmt.executeUpdate("INSERT INTO testUpdateLongBlob (blobField) VALUES (NULL)"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + con1 = getConnectionWithProps(props); + this.pstmt = con1.prepareStatement("UPDATE testUpdateLongBlob SET blobField=?"); + this.pstmt.setBytes(1, blobData); + try { + this.pstmt.executeUpdate(); + } catch (SQLException sqlEx) { + assertTrue(sqlEx.getMessage().indexOf("max_allowed_packet") == -1, + "You need to increase max_allowed_packet to at least 18M before running this test!"); + } + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket); + } + if (con1 != null) { + con1.close(); + } + if (con2 != null) { + con2.close(); } } } @@ -202,6 +229,8 @@ public void testBug8096() throws Exception { int dataSize = 256; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.emulateLocators.getKeyName(), "true"); Connection locatorConn = getConnectionWithProps(props); @@ -406,25 +435,60 @@ public Void call() throws Exception { @Test public void testBug23535571() throws Exception { + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); + this.rs.next(); + long len = 4 + 1024 * 1024 * 36 + "UPDATE testBug23535571 SET blobField=".length(); + long defaultMaxAllowedPacket = this.rs.getInt(2); + boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < len; + + if (!versionMeetsMinimum(5, 7)) { + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'innodb_log_file_size'"); + this.rs.next(); + long defaultInnodbLogFileSize = this.rs.getInt(2); + assumeFalse(defaultInnodbLogFileSize < 1024 * 1024 * 36 * 10, "This test requires innodb_log_file_size > " + (1024 * 1024 * 36 * 10)); + } + createTable("testBug23535571", "(blobField LONGBLOB)"); this.stmt.executeUpdate("INSERT INTO testBug23535571 (blobField) VALUES (NULL)"); - // Insert 1 record with 18M data - byte[] blobData = new byte[18 * 1024 * 1024]; - this.pstmt = this.conn.prepareStatement("UPDATE testBug23535571 SET blobField=?"); - this.pstmt.setBytes(1, blobData); - this.pstmt.executeUpdate(); + Connection con1 = null; + Connection con2 = null; + try { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 37); + } - Properties props = new Properties(); - props.setProperty(PropertyKey.enablePacketDebug.getKeyName(), "true"); - Connection con = getConnectionWithProps(props); - - for (int i = 0; i < 100; i++) { - this.pstmt = con.prepareStatement("select * from testBug23535571"); - this.rs = this.pstmt.executeQuery(); - this.rs.close(); - this.pstmt.close(); - Thread.sleep(100); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + con1 = getConnectionWithProps(props); + + // Insert 1 record with 18M data + byte[] blobData = new byte[18 * 1024 * 1024]; + this.pstmt = con1.prepareStatement("UPDATE testBug23535571 SET blobField=?"); + this.pstmt.setBytes(1, blobData); + this.pstmt.executeUpdate(); + + props.setProperty(PropertyKey.enablePacketDebug.getKeyName(), "true"); + con2 = getConnectionWithProps(props); + + for (int i = 0; i < 100; i++) { + this.pstmt = con2.prepareStatement("select * from testBug23535571"); + this.rs = this.pstmt.executeQuery(); + this.rs.close(); + this.pstmt.close(); + Thread.sleep(100); + } + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket); + } + if (con1 != null) { + con1.close(); + } + if (con2 != null) { + con2.close(); + } } } @@ -440,6 +504,8 @@ public void testBug95210() throws Exception { this.stmt.executeUpdate("INSERT INTO testBug95210 (ID, DATA) VALUES (1, '111')"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); // testsuite is built upon non-SSL default connection + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.emulateLocators.getKeyName(), "true"); Connection locatorConn = getSourceReplicaReplicationConnection(props); diff --git a/src/test/java/testsuite/regression/CachedRowsetTest.java b/src/test/java/testsuite/regression/CachedRowsetTest.java index 1bce398b7..1767ab44e 100644 --- a/src/test/java/testsuite/regression/CachedRowsetTest.java +++ b/src/test/java/testsuite/regression/CachedRowsetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.lang.reflect.Method; import java.sql.ResultSet; @@ -54,13 +55,12 @@ public class CachedRowsetTest extends BaseTestCase { @Test public void testBug5188() throws Exception { String implClass = "com.sun.rowset.CachedRowSetImpl"; - Class c; + Class c = null; Method populate; try { c = Class.forName(implClass); } catch (ClassNotFoundException e) { - System.out.println("skipping testBug5188. Requires: " + implClass); - return; + assumeFalse(true, "Requires: " + implClass); } populate = c.getMethod("populate", new Class[] { ResultSet.class }); diff --git a/src/test/java/testsuite/regression/CallableStatementRegressionTest.java b/src/test/java/testsuite/regression/CallableStatementRegressionTest.java index 7d4d59ace..519dc26aa 100644 --- a/src/test/java/testsuite/regression/CallableStatementRegressionTest.java +++ b/src/test/java/testsuite/regression/CallableStatementRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -54,6 +54,7 @@ import org.junit.jupiter.api.Test; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.jdbc.JdbcConnection; @@ -88,7 +89,11 @@ public void testBug3539() throws Exception { public void testBug3540() throws Exception { createProcedure("testBug3540", "(x int, out y int)\nBEGIN\nSELECT 1;end\n"); - Connection con = getConnectionWithProps("nullCatalogMeansCurrent=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); + Connection con = getConnectionWithProps(props); try { this.rs = con.getMetaData().getProcedureColumns(null, null, "testBug3540%", "%"); @@ -117,7 +122,11 @@ public void testBug3540() throws Exception { public void testBug7026() throws Exception { createProcedure("testBug7026", "(x int, out y int)\nBEGIN\nSELECT 1;end\n"); - Connection con = getConnectionWithProps("nullCatalogMeansCurrent=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); + Connection con = getConnectionWithProps(props); try { // // Should be found this time. @@ -162,101 +171,103 @@ public void testBug7026() throws Exception { */ @Test public void testBug9319() throws Exception { - boolean doASelect = true; // SELECT currently causes the server to hang on the last execution of this testcase, filed as BUG#9405 - - if (isAdminConnectionConfigured()) { - Connection db2Connection = null; - Connection db1Connection = null; - - db2Connection = getAdminConnection(); - db1Connection = getAdminConnection(); - - Statement db1st = db1Connection.createStatement(); - Statement db2st = db2Connection.createStatement(); - - createDatabase(db2st, "db_9319_2"); - db2Connection.setCatalog("db_9319_2"); - createProcedure(db2st, "COMPROVAR_USUARI", - "(IN p_CodiUsuari VARCHAR(10),\nIN p_contrasenya VARCHAR(10),\nOUT p_userId INTEGER," - + "\nOUT p_userName VARCHAR(30),\nOUT p_administrador VARCHAR(1),\nOUT p_idioma VARCHAR(2))\nBEGIN" - + (doASelect ? "\nselect 2;" : "\nSELECT 2 INTO p_administrador;") + "\nEND"); - - createDatabase(db1st, "db_9319_1"); - db1Connection.setCatalog("db_9319_1"); - createProcedure(db1st, "COMPROVAR_USUARI", - "(IN p_CodiUsuari VARCHAR(10),\nIN p_contrasenya VARCHAR(10),\nOUT p_userId INTEGER," - + "\nOUT p_userName VARCHAR(30),\nOUT p_administrador VARCHAR(1))\nBEGIN" - + (doASelect ? "\nselect 1;" : "\nSELECT 1 INTO p_administrador;") + "\nEND"); - - CallableStatement cstmt = db2Connection.prepareCall("{ call COMPROVAR_USUARI(?, ?, ?, ?, ?, ?) }"); - cstmt.setString(1, "abc"); - cstmt.setString(2, "def"); - cstmt.registerOutParameter(3, java.sql.Types.INTEGER); - cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); - cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); + boolean doASelect = true; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - cstmt.registerOutParameter(6, java.sql.Types.VARCHAR); + Connection db2Connection = null; + Connection db1Connection = null; - cstmt.execute(); + db2Connection = getConnectionWithProps(props); + db1Connection = getConnectionWithProps(props); - if (doASelect) { - this.rs = cstmt.getResultSet(); - assertTrue(this.rs.next()); - assertEquals(2, this.rs.getInt(1)); - } else { - assertEquals(2, cstmt.getInt(5)); - } + Statement db1st = db1Connection.createStatement(); + Statement db2st = db2Connection.createStatement(); - cstmt = db1Connection.prepareCall("{ call COMPROVAR_USUARI(?, ?, ?, ?, ?, ?) }"); - cstmt.setString(1, "abc"); - cstmt.setString(2, "def"); - cstmt.registerOutParameter(3, java.sql.Types.INTEGER); - cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); - cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); + createDatabase(db2st, "db_9319_2"); + db2Connection.setCatalog("db_9319_2"); - try { - cstmt.registerOutParameter(6, java.sql.Types.VARCHAR); - fail("Should've thrown an exception"); - } catch (SQLException sqlEx) { - assertEquals(MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, sqlEx.getSQLState()); - } + createProcedure(db2st, "db_9319_2.COMPROVAR_USUARI", + "(IN p_CodiUsuari VARCHAR(10),\nIN p_contrasenya VARCHAR(10),\nOUT p_userId INTEGER," + + "\nOUT p_userName VARCHAR(30),\nOUT p_administrador VARCHAR(1),\nOUT p_idioma VARCHAR(2))\nBEGIN" + + (doASelect ? "\nselect 2;" : "\nSELECT 2 INTO p_administrador;") + "\nEND"); - cstmt = db1Connection.prepareCall("{ call COMPROVAR_USUARI(?, ?, ?, ?, ?) }"); - cstmt.setString(1, "abc"); - cstmt.setString(2, "def"); - cstmt.registerOutParameter(3, java.sql.Types.INTEGER); - cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); - cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); + createDatabase(db1st, "db_9319_1"); + db1Connection.setCatalog("db_9319_1"); + createProcedure(db1st, "db_9319_1.COMPROVAR_USUARI", + "(IN p_CodiUsuari VARCHAR(10),\nIN p_contrasenya VARCHAR(10),\nOUT p_userId INTEGER," + + "\nOUT p_userName VARCHAR(30),\nOUT p_administrador VARCHAR(1))\nBEGIN" + + (doASelect ? "\nselect 1;" : "\nSELECT 1 INTO p_administrador;") + "\nEND"); - cstmt.execute(); + CallableStatement cstmt = db2Connection.prepareCall("{ call COMPROVAR_USUARI(?, ?, ?, ?, ?, ?) }"); + cstmt.setString(1, "abc"); + cstmt.setString(2, "def"); + cstmt.registerOutParameter(3, java.sql.Types.INTEGER); + cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); + cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); - if (doASelect) { - this.rs = cstmt.getResultSet(); - assertTrue(this.rs.next()); - assertEquals(1, this.rs.getInt(1)); - } else { - assertEquals(1, cstmt.getInt(5)); - } + cstmt.registerOutParameter(6, java.sql.Types.VARCHAR); - String quoteChar = db2Connection.getMetaData().getIdentifierQuoteString(); + cstmt.execute(); - cstmt = db2Connection.prepareCall( - "{ call " + quoteChar + db1Connection.getCatalog() + quoteChar + "." + quoteChar + "COMPROVAR_USUARI" + quoteChar + "(?, ?, ?, ?, ?) }"); - cstmt.setString(1, "abc"); - cstmt.setString(2, "def"); - cstmt.registerOutParameter(3, java.sql.Types.INTEGER); - cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); - cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); + if (doASelect) { + this.rs = cstmt.getResultSet(); + assertTrue(this.rs.next()); + assertEquals(2, this.rs.getInt(1)); + } else { + assertEquals(2, cstmt.getInt(5)); + } - cstmt.execute(); + cstmt = db1Connection.prepareCall("{ call COMPROVAR_USUARI(?, ?, ?, ?, ?, ?) }"); + cstmt.setString(1, "abc"); + cstmt.setString(2, "def"); + cstmt.registerOutParameter(3, java.sql.Types.INTEGER); + cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); + cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); - if (doASelect) { - this.rs = cstmt.getResultSet(); - assertTrue(this.rs.next()); - assertEquals(1, this.rs.getInt(1)); - } else { - assertEquals(1, cstmt.getInt(5)); - } + try { + cstmt.registerOutParameter(6, java.sql.Types.VARCHAR); + fail("Should've thrown an exception"); + } catch (SQLException sqlEx) { + assertEquals(MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, sqlEx.getSQLState()); + } + + cstmt = db1Connection.prepareCall("{ call COMPROVAR_USUARI(?, ?, ?, ?, ?) }"); + cstmt.setString(1, "abc"); + cstmt.setString(2, "def"); + cstmt.registerOutParameter(3, java.sql.Types.INTEGER); + cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); + cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); + + cstmt.execute(); + + if (doASelect) { + this.rs = cstmt.getResultSet(); + assertTrue(this.rs.next()); + assertEquals(1, this.rs.getInt(1)); + } else { + assertEquals(1, cstmt.getInt(5)); + } + + String quoteChar = db2Connection.getMetaData().getIdentifierQuoteString(); + + cstmt = db2Connection.prepareCall( + "{ call " + quoteChar + db1Connection.getCatalog() + quoteChar + "." + quoteChar + "COMPROVAR_USUARI" + quoteChar + "(?, ?, ?, ?, ?) }"); + cstmt.setString(1, "abc"); + cstmt.setString(2, "def"); + cstmt.registerOutParameter(3, java.sql.Types.INTEGER); + cstmt.registerOutParameter(4, java.sql.Types.VARCHAR); + cstmt.registerOutParameter(5, java.sql.Types.VARCHAR); + + cstmt.execute(); + + if (doASelect) { + this.rs = cstmt.getResultSet(); + assertTrue(this.rs.next()); + assertEquals(1, this.rs.getInt(1)); + } else { + assertEquals(1, cstmt.getInt(5)); } } @@ -432,31 +443,28 @@ public void testBug12417() throws Exception { @Test public void testBug15121() throws Exception { - if (!this.DISABLED_testBug15121 /* needs to be fixed on server */) { - createProcedure("p_testBug15121", "()\nBEGIN\nSELECT * from idonotexist;\nEND"); + createProcedure("p_testBug15121", "()\nBEGIN\nSELECT * from idonotexist;\nEND"); - Properties props = new Properties(); - props.setProperty(PropertyKey.DBNAME.getKeyName(), ""); - - Connection noDbConn = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.DBNAME.getKeyName(), ""); - try { - noDbConn = getConnectionWithProps(props); + Connection noDbConn = getConnectionWithProps(props); - StringBuilder queryBuf = new StringBuilder("{call "); - String quotedId = this.conn.getMetaData().getIdentifierQuoteString(); - queryBuf.append(quotedId); - queryBuf.append(this.conn.getCatalog()); - queryBuf.append(quotedId); - queryBuf.append(".p_testBug15121()}"); + StringBuilder queryBuf = new StringBuilder("{call "); + String quotedId = this.conn.getMetaData().getIdentifierQuoteString(); + queryBuf.append(quotedId); + queryBuf.append(this.conn.getCatalog()); + queryBuf.append(quotedId); + queryBuf.append(".p_testBug15121()}"); + assertThrows(SQLException.class, "Table '" + this.conn.getCatalog() + ".idonotexist' doesn't exist", new Callable() { + public Void call() throws Exception { noDbConn.prepareCall(queryBuf.toString()).execute(); - } finally { - if (noDbConn != null) { - noDbConn.close(); - } + return null; } - } + }); } /** @@ -872,7 +880,11 @@ public void testBug28689() throws Exception { createProcedure("sp_testBug28689", "(tid INT)\nBEGIN\nUPDATE testBug28689 SET usuario = 'BBBBBB' WHERE id = tid;\nEND"); - Connection noProcedureBodiesConn = getConnectionWithProps("noAccessToProcedureBodies=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.noAccessToProcedureBodies.getKeyName(), "true"); + Connection noProcedureBodiesConn = getConnectionWithProps(props); CallableStatement cStmt = null; try { @@ -1120,7 +1132,14 @@ public void testBug49831() throws Exception { execProcBug49831(this.conn); this.stmt.execute("TRUNCATE TABLE testBug49831"); assertEquals(0, getRowCount("testBug49831")); - Connection noBodiesConn = getConnectionWithProps("noAccessToProcedureBodies=true,jdbcCompliantTruncation=false,characterEncoding=utf8"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.noAccessToProcedureBodies.getKeyName(), "true"); + props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); + Connection noBodiesConn = getConnectionWithProps(props); try { execProcBug49831(noBodiesConn); } finally { @@ -1183,6 +1202,8 @@ public void testBug43576() throws Exception { + "\nOUT fdoc VARCHAR(100))\nBEGIN\nSET nfact = 'ncfact string';\nSET ffact = 'ffact string';\nSET fdoc = 'fdoc string';\nEND"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); Connection conn1 = null; @@ -1484,7 +1505,9 @@ public void testBug26259384() throws Exception { createProcedure("testBug26259384", "(IN p1 int,INOUT p2 int)\nBEGIN\nSET p2=p1+100;\nEND"); Properties props = new Properties(); - props.setProperty("autoReconnect", "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); Connection conn1 = getConnectionWithProps(props); conn1.prepareCall("{ call testBug26259384(?+?,?) }"); @@ -1501,7 +1524,7 @@ public void testBug87704() throws Exception { "(IN PARAMIN BIGINT, OUT PARAM_OUT_LONG BIGINT, OUT PARAM_OUT_STR VARCHAR(100))\nBEGIN\nSET PARAM_OUT_LONG = PARAMIN + 100000;\nSET PARAM_OUT_STR = concat('STR' ,PARAM_OUT_LONG);end\n"); final Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); @@ -1536,4 +1559,88 @@ public void testBug87704() throws Exception { } } } + + /** + * Tests fix for Bug#20279641, CANNOT CALL A PROCEDURE USING `DATABASE`.PROCNAME FORMAT. + * + * @throws Exception + */ + @Test + public void testBug20279641() throws Exception { + createDatabase("`abc1`"); + createProcedure("`abc1`.procBug20279641", "(IN c1 int, INOUT c2 int, OUT c3 int)" + " BEGIN Set c3=c2+c1; END"); + + CallableStatement cstmt = this.conn.prepareCall("{ call `abc1`.procBug20279641(?, ?, ?) }"); + cstmt.registerOutParameter(2, java.sql.Types.INTEGER); + cstmt.registerOutParameter(3, java.sql.Types.INTEGER); + cstmt.setInt(1, 113); + cstmt.setInt(2, 123); + cstmt.setNull(3, java.sql.Types.INTEGER); + cstmt.execute(); + + assertEquals("123", cstmt.getString(2)); + assertEquals("236", cstmt.getString(3)); + } + + /** + * Tests fix for Bug#19857166, SET FUNCTIONS ON CALLABLESTATEMENT RETURNS EXCEPTION WHEN CALLED WITH PARAM NAME. + * + * @throws Exception + */ + @Test + public void testBug19857166() throws Exception { + createProcedure("testBug19857166p", "(IN inp1 VARCHAR(10),INOUT inp2 VARCHAR(10)) begin" + " set inp2 = 'data'; END"); + createFunction("testBug19857166f", "(a char(10),b varchar(10)) RETURNS CHAR(50) COMMENT 'Returns string' DETERMINISTIC BEGIN RETURN CONCAT(a, b); END"); + + Connection con = null; + try { + for (boolean getProcRetFuncs : new boolean[] { false, true }) { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.getProceduresReturnsFunctions.getKeyName(), "" + getProcRetFuncs); + con = getConnectionWithProps(props); + + CallableStatement callSt1 = con.prepareCall(" call testBug19857166p(?, ?) "); + assertThrows(SQLException.class, "No parameter named 'iNp1'", () -> { + callSt1.setString("iNp1", "xxx"); + return null; + }); + assertThrows(SQLException.class, "No parameter named 'inP2'", () -> { + callSt1.setString("inP2", "xxx"); + return null; + }); + callSt1.setString("inp1", "xxx"); + callSt1.registerOutParameter(2, java.sql.Types.VARCHAR); + callSt1.execute(); + assertEquals("data", callSt1.getString(2)); + callSt1.close(); + + CallableStatement callSt2 = con.prepareCall("{? = CALL testBug19857166f(?,?)}"); + callSt2.registerOutParameter(1, java.sql.Types.VARCHAR); + if (getProcRetFuncs) { + callSt2.setString("a", "abcd"); + callSt2.setString("b", "rr"); + } else { + assertThrows(SQLException.class, "No parameter named 'a'", () -> { + callSt2.setString("a", "abcd"); + return null; + }); + assertThrows(SQLException.class, "No parameter named 'b'", () -> { + callSt2.setString("b", "rr"); + return null; + }); + callSt2.setString(2, "abcd"); + callSt2.setString(3, "rr"); + } + callSt2.execute(); + assertEquals("abcdrr", callSt2.getString(1), "Data Comparison failed"); + callSt2.close(); + } + } finally { + if (con != null) { + con.close(); + } + } + } } diff --git a/src/test/java/testsuite/regression/CharsetRegressionTest.java b/src/test/java/testsuite/regression/CharsetRegressionTest.java index d9ea76423..5338d0a91 100644 --- a/src/test/java/testsuite/regression/CharsetRegressionTest.java +++ b/src/test/java/testsuite/regression/CharsetRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020, Oracle and/or its affiliates. + * Copyright (c) 2014, 2021, 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 @@ -32,28 +32,615 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; import java.util.function.Supplier; import org.junit.jupiter.api.Test; -import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CacheAdapter; +import com.mysql.cj.CharsetMappingWrapper; +import com.mysql.cj.CharsetSettings; import com.mysql.cj.MysqlConnection; +import com.mysql.cj.NativeSession; import com.mysql.cj.Query; +import com.mysql.cj.ServerVersion; +import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.ExceptionFactory; +import com.mysql.cj.exceptions.MysqlErrorNumbers; +import com.mysql.cj.interceptors.QueryInterceptor; +import com.mysql.cj.jdbc.JdbcConnection; +import com.mysql.cj.log.Log; +import com.mysql.cj.protocol.Message; import com.mysql.cj.protocol.Resultset; +import com.mysql.cj.util.StringUtils; import testsuite.BaseQueryInterceptor; import testsuite.BaseTestCase; public class CharsetRegressionTest extends BaseTestCase { + + /** + * Tests fix for BUG#7607 - MS932, SHIFT_JIS and Windows_31J not recog. as aliases for sjis. + * + * @throws Exception + */ + @Test + public void testBug7607() throws Exception { + Connection ms932Conn = null, cp943Conn = null, shiftJisConn = null, windows31JConn = null; + + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "MS932"); + + ms932Conn = getConnectionWithProps(props); + + this.rs = ms932Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); + assertTrue(this.rs.next()); + String encoding = this.rs.getString(2); + assertEquals("cp932", encoding.toLowerCase(Locale.ENGLISH)); + + this.rs = ms932Conn.createStatement().executeQuery("SELECT 'abc'"); + assertTrue(this.rs.next()); + + String charsetToCheck = "ms932"; + + assertEquals(charsetToCheck, + ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toLowerCase(Locale.ENGLISH)); + + try { + ms932Conn.createStatement().executeUpdate("drop table if exists testBug7607"); + ms932Conn.createStatement().executeUpdate("create table testBug7607 (sortCol int, col1 varchar(100) ) character set sjis"); + ms932Conn.createStatement().executeUpdate("insert into testBug7607 values(1, 0x835C)"); // standard + // sjis + ms932Conn.createStatement().executeUpdate("insert into testBug7607 values(2, 0x878A)"); // NEC + // kanji + + this.rs = ms932Conn.createStatement().executeQuery("SELECT col1 FROM testBug7607 ORDER BY sortCol ASC"); + assertTrue(this.rs.next()); + String asString = this.rs.getString(1); + assertTrue("\u30bd".equals(asString)); + + assertTrue(this.rs.next()); + asString = this.rs.getString(1); + assertEquals("\u3231", asString); + } finally { + ms932Conn.createStatement().executeUpdate("drop table if exists testBug7607"); + } + + props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "SHIFT_JIS"); + + shiftJisConn = getConnectionWithProps(props); + + this.rs = shiftJisConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); + assertTrue(this.rs.next()); + encoding = this.rs.getString(2); + assertTrue("sjis".equalsIgnoreCase(encoding)); + + this.rs = shiftJisConn.createStatement().executeQuery("SELECT 'abc'"); + assertTrue(this.rs.next()); + + String charSetUC = ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toUpperCase(Locale.US); + + props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "WINDOWS-31J"); + + windows31JConn = getConnectionWithProps(props); + + this.rs = windows31JConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); + assertTrue(this.rs.next()); + encoding = this.rs.getString(2); + + assertEquals("cp932", encoding.toLowerCase(Locale.ENGLISH)); + + this.rs = windows31JConn.createStatement().executeQuery("SELECT 'abc'"); + assertTrue(this.rs.next()); + + assertEquals("windows-31j".toLowerCase(Locale.ENGLISH), + ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toLowerCase(Locale.ENGLISH)); + + props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "CP943"); + + cp943Conn = getConnectionWithProps(props); + + this.rs = cp943Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); + assertTrue(this.rs.next()); + encoding = this.rs.getString(2); + assertTrue("sjis".equalsIgnoreCase(encoding)); + + this.rs = cp943Conn.createStatement().executeQuery("SELECT 'abc'"); + assertTrue(this.rs.next()); + + charSetUC = ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toUpperCase(Locale.US); + + assertEquals("CP943", charSetUC); + + } finally { + if (ms932Conn != null) { + ms932Conn.close(); + } + + if (shiftJisConn != null) { + shiftJisConn.close(); + } + + if (windows31JConn != null) { + windows31JConn.close(); + } + + if (cp943Conn != null) { + cp943Conn.close(); + } + } + } + + /** + * Tests fix for BUG#9206, can not use 'UTF-8' for characterSetResults configuration property. + * + * @throws Exception + */ + @Test + public void testBug9206() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "UTF-8"); + getConnectionWithProps(props).close(); + } + + /** + * Tests fix for BUG#10496 - SQLException is thrown when using property "characterSetResults" + * + * @throws Exception + */ + @Test + public void testBug10496() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "WINDOWS-31J"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "WINDOWS-31J"); + getConnectionWithProps(props).close(); + + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "EUC_JP"); + getConnectionWithProps(props).close(); + } + + /** + * Tests fix for BUG#12752 - Cp1251 incorrectly mapped to win1251 for servers newer than 4.0.x. + * + * @throws Exception + */ + @Test + public void testBug12752() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1251"); + getConnectionWithProps(props).close(); + } + + /** + * Tests fix for BUG#15544, no "dos" character set in MySQL > 4.1.0 + * + * @throws Exception + */ + @Test + public void testBug15544() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp437"); + Connection dosConn = null; + + try { + dosConn = getConnectionWithProps(props); + } finally { + if (dosConn != null) { + dosConn.close(); + } + } + } + + @Test + public void testBug37931() throws Exception { + Connection _conn = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "ISO88591"); + + try { + _conn = getConnectionWithProps(props); + assertTrue(false, "This point should not be reached."); + } catch (Exception e) { + assertEquals("Unsupported character encoding 'ISO88591'", e.getMessage()); + } finally { + if (_conn != null) { + _conn.close(); + } + } + + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "null"); + + try { + _conn = getConnectionWithProps(props); + + Statement _stmt = _conn.createStatement(); + ResultSet _rs = _stmt.executeQuery("show variables where variable_name='character_set_results'"); + if (_rs.next()) { + String res = _rs.getString(2); + if (res == null || "NULL".equalsIgnoreCase(res) || res.length() == 0) { + assertTrue(true); + } else { + assertTrue(false); + } + } + } finally { + if (_conn != null) { + _conn.close(); + } + } + } + + /** + * Tests fix for Bug#64205 (13702427), Connected through Connector/J 5.1 to MySQL 5.5, the error message is garbled. + * + * @throws Exception + */ + @Test + public void testBug64205() throws Exception { + Properties props = getPropertiesFromTestsuiteUrl(); + String dbname = props.getProperty(PropertyKey.DBNAME.getKeyName()); + if (dbname == null) { + assertTrue(false, "No database selected"); + } + + props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP"); + + Connection testConn = null; + Statement testSt = null; + ResultSet testRs = null; + try { + testConn = getConnectionWithProps(props); + testSt = testConn.createStatement(); + testRs = testSt.executeQuery("SELECT * FROM `" + dbname + "`.`\u307b\u3052\u307b\u3052`"); + } catch (SQLException e1) { + if (e1.getClass().getName().endsWith("SQLSyntaxErrorException")) { + assertEquals("Table '" + dbname + ".\u307B\u3052\u307b\u3052' doesn't exist", e1.getMessage()); + } else if (e1.getErrorCode() == MysqlErrorNumbers.ER_FILE_NOT_FOUND) { + // this could happen on Windows with 5.5 and 5.6 servers where BUG#14642248 exists + assertTrue(e1.getMessage().contains("Can't find file")); + } else { + throw e1; + } + + testSt.close(); + testConn.close(); + + try { + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "SJIS"); + testConn = getConnectionWithProps(props); + testSt = testConn.createStatement(); + testSt.execute("SET lc_messages = 'ru_RU'"); + testRs = testSt.executeQuery("SELECT * FROM `" + dbname + "`.`\u307b\u3052\u307b\u3052`"); + } catch (SQLException e2) { + if (e2.getClass().getName().endsWith("SQLSyntaxErrorException")) { + assertEquals("\u0422\u0430\u0431\u043b\u0438\u0446\u0430 '" + dbname + + ".\u307b\u3052\u307b\u3052' \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442", e2.getMessage()); + } else if (e2.getErrorCode() == MysqlErrorNumbers.ER_FILE_NOT_FOUND) { + // this could happen on Windows with 5.5 and 5.6 servers where BUG#14642248 exists + assertTrue(e2.getMessage().indexOf("\u0444\u0430\u0439\u043b") > -1, + "File not found error message should be russian but is this one: " + e2.getMessage()); + } else { + throw e2; + } + } + + } finally { + if (testRs != null) { + testRs.close(); + } + if (testSt != null) { + testSt.close(); + } + if (testConn != null) { + testConn.close(); + } + } + + // also test with explicit characterSetResults and cacheServerConfiguration + try { + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "EUC_JP"); + props.setProperty(PropertyKey.cacheServerConfiguration.getKeyName(), "true"); + testConn = getConnectionWithProps(props); + testSt = testConn.createStatement(); + testRs = testSt.executeQuery("SELECT * FROM `" + dbname + "`.`\u307b\u3052\u307b\u3052`"); + fail("Exception should be thrown for attemping to query non-existing table"); + } catch (SQLException e1) { + if (e1.getClass().getName().endsWith("SQLSyntaxErrorException")) { + assertEquals("Table '" + dbname + ".\u307B\u3052\u307b\u3052' doesn't exist", e1.getMessage()); + } else if (e1.getErrorCode() == MysqlErrorNumbers.ER_FILE_NOT_FOUND) { + // this could happen on Windows with 5.5 and 5.6 servers where BUG#14642248 exists + assertTrue(e1.getMessage().contains("Can't find file")); + } else { + throw e1; + } + } finally { + testConn.close(); + } + props.remove(PropertyKey.cacheServerConfiguration.getKeyName()); + + // Error messages may also be received after the handshake but before connection initialization is complete. This tests the interpretation of + // errors thrown during this time window using a SatementInterceptor that throws an Exception while setting the session variables. + // Start by getting the Latin1 version of the error to compare later. + String latin1ErrorMsg = ""; + int latin1ErrorLen = 0; + try { + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Latin1"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "Latin1"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "lc_messages=ru_RU"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug64205QueryInterceptor.class.getName()); + testConn = getConnectionWithProps(props); + fail("Exception should be trown for syntax error, caused by the exception interceptor"); + } catch (Exception e) { + latin1ErrorMsg = e.getMessage(); + latin1ErrorLen = latin1ErrorMsg.length(); + } + // Now compare with results when using a proper encoding. + try { + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "EUC_JP"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "lc_messages=ru_RU"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug64205QueryInterceptor.class.getName()); + testConn = getConnectionWithProps(props); + fail("Exception should be trown for syntax error, caused by the exception interceptor"); + } catch (SQLException e) { + // There should be the Russian version of this error message, correctly encoded. A mis-interpretation, e.g. decoding as latin1, would return a + // wrong message with the wrong size. + assertEquals(29 + dbname.length(), e.getMessage().length()); + assertFalse(latin1ErrorMsg.equals(e.getMessage())); + assertFalse(latin1ErrorLen == e.getMessage().length()); + } finally { + testConn.close(); + } + } + + public static class TestBug64205QueryInterceptor extends BaseQueryInterceptor { + private JdbcConnection connection; + + @Override + public QueryInterceptor init(MysqlConnection conn, Properties props, Log log) { + this.connection = (JdbcConnection) conn; + return super.init(conn, props, log); + } + + @Override + public M postProcess(M queryPacket, M originalResponsePacket) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); + if (sql.contains("lc_messages=ru_RU")) { + try { + this.connection.createStatement() + .executeQuery("SELECT * FROM `" + + (this.connection.getPropertySet().getEnumProperty(PropertyKey.databaseTerm) + .getValue() == DatabaseTerm.SCHEMA ? this.connection.getSchema() : this.connection.getCatalog()) + + "`.`\u307b\u3052\u307b\u3052`"); + } catch (Exception e) { + throw ExceptionFactory.createException(e.getMessage(), e); + } + } + return originalResponsePacket; + } + } + + /** + * Bug #41730 - SQL Injection when using U+00A5 and SJIS/Windows-31J + * + * @throws Exception + */ + @Test + public void testBug41730() throws Exception { + try { + "".getBytes("sjis"); + } catch (UnsupportedEncodingException ex) { + assumeFalse(true, "Test requires JVM with sjis support."); + } + + Connection conn2 = null; + PreparedStatement pstmt2 = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "sjis"); + conn2 = getConnectionWithProps(props); + pstmt2 = conn2.prepareStatement("select ?"); + pstmt2.setString(1, "\u00A5'"); + // this will throw an exception with a syntax error if it fails + this.rs = pstmt2.executeQuery(); + } finally { + try { + if (pstmt2 != null) { + pstmt2.close(); + } + } catch (SQLException ex) { + } + try { + if (conn2 != null) { + conn2.close(); + } + } catch (SQLException ex) { + } + } + } + + /** + * Tests character conversion bug. + * + * @throws Exception + */ + @Test + public void testAsciiCharConversion() throws Exception { + byte[] buf = new byte[10]; + buf[0] = (byte) '?'; + buf[1] = (byte) 'S'; + buf[2] = (byte) 't'; + buf[3] = (byte) 'a'; + buf[4] = (byte) 't'; + buf[5] = (byte) 'e'; + buf[6] = (byte) '-'; + buf[7] = (byte) 'b'; + buf[8] = (byte) 'o'; + buf[9] = (byte) 't'; + + String testString = "?State-bot"; + String convertedString = StringUtils.toAsciiString(buf); + + for (int i = 0; i < convertedString.length(); i++) { + System.out.println((byte) convertedString.charAt(i)); + } + + assertTrue(testString.equals(convertedString), "Converted string != test string"); + } + + /** + * Tests for regression of encoding forced by user, reported by Jive Software + * + * @throws Exception + */ + @Test + public void testEncodingRegression() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + DriverManager.getConnection(dbUrl, props).close(); + } + + /** + * Tests fix for BUG#879 + * + * @throws Exception + */ + @Test + public void testEscapeSJISDoubleEscapeBug() throws Exception { + String testString = "'It\\'s a boy!'"; + + //byte[] testStringAsBytes = testString.getBytes("SJIS"); + + byte[] origByteStream = new byte[] { (byte) 0x95, (byte) 0x5c, (byte) 0x8e, (byte) 0x96, (byte) 0x5c, (byte) 0x62, (byte) 0x5c }; + + //String origString = "\u955c\u8e96\u5c62\\"; + + origByteStream = new byte[] { (byte) 0x8d, (byte) 0xb2, (byte) 0x93, (byte) 0x91, (byte) 0x81, (byte) 0x40, (byte) 0x8c, (byte) 0x5c }; + + testString = new String(origByteStream, "SJIS"); + + Properties connProps = new Properties(); + connProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + connProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + connProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "sjis"); + + Connection sjisConn = getConnectionWithProps(connProps); + Statement sjisStmt = sjisConn.createStatement(); + + try { + sjisStmt.executeUpdate("DROP TABLE IF EXISTS doubleEscapeSJISTest"); + sjisStmt.executeUpdate("CREATE TABLE doubleEscapeSJISTest (field1 BLOB)"); + + PreparedStatement sjisPStmt = sjisConn.prepareStatement("INSERT INTO doubleEscapeSJISTest VALUES (?)"); + sjisPStmt.setString(1, testString); + sjisPStmt.executeUpdate(); + + this.rs = sjisStmt.executeQuery("SELECT * FROM doubleEscapeSJISTest"); + + this.rs.next(); + + String retrString = this.rs.getString(1); + + System.out.println(retrString.equals(testString)); + } finally { + sjisStmt.executeUpdate("DROP TABLE IF EXISTS doubleEscapeSJISTest"); + } + } + + @Test + public void testGreekUtf8411() throws Exception { + Properties newProps = new Properties(); + newProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + newProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + newProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + + Connection utf8Conn = this.getConnectionWithProps(newProps); + + Statement utfStmt = utf8Conn.createStatement(); + + createTable("greekunicode", "(ID INTEGER NOT NULL AUTO_INCREMENT,UpperCase VARCHAR (30),LowerCase VARCHAR (30),Accented " + + " VARCHAR (30),Special VARCHAR (30),PRIMARY KEY(ID)) DEFAULT CHARACTER SET utf8", "InnoDB"); + + String upper = "\u0394\u930F\u039A\u0399\u039C\u0397"; + String lower = "\u03B4\u03BF\u03BA\u03B9\u03BC\u03B7"; + String accented = "\u03B4\u03CC\u03BA\u03AF\u03BC\u03AE"; + String special = "\u037E\u03C2\u03B0"; + + utfStmt.executeUpdate("INSERT INTO greekunicode VALUES ('1','" + upper + "','" + lower + "','" + accented + "','" + special + "')"); + + this.rs = utfStmt.executeQuery("SELECT UpperCase, LowerCase, Accented, Special from greekunicode"); + + this.rs.next(); + + assertTrue(upper.equals(this.rs.getString(1))); + assertTrue(lower.equals(this.rs.getString(2))); + assertTrue(accented.equals(this.rs.getString(3))); + assertTrue(special.equals(this.rs.getString(4))); + } + + /** + * Tests fix for BUG#24840 - character encoding of "US-ASCII" doesn't map correctly for 4.1 or newer + * + * @throws Exception + */ + @Test + public void testBug24840() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "US-ASCII"); + + getConnectionWithProps(props).close(); + } + /** * Tests fix for Bug#73663 (19479242), utf8mb4 does not work for connector/j >=5.1.13 * @@ -68,30 +655,34 @@ public void testBug73663() throws Exception { this.rs.next(); String collation = this.rs.getString(2); - if (collation != null && collation.startsWith("utf8mb4") - && "utf8mb4".equals(((MysqlConnection) this.conn).getSession().getServerSession().getServerVariable("character_set_server"))) { - Properties p = new Properties(); - p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug73663QueryInterceptor.class.getName()); + assumeTrue( + collation != null && collation.startsWith("utf8mb4") + && "utf8mb4".equals(((MysqlConnection) this.conn).getSession().getServerSession().getServerVariable("character_set_server")), + "This test requires server configured with character_set_server=utf8mb4 and collation-server set to one of utf8mb4 collations."); - getConnectionWithProps(p); - // exception will be thrown from the statement interceptor if any "SET NAMES utf8" statement is issued instead of "SET NAMES utf8mb4" - } else { - System.out.println( - "testBug73663 was skipped: This test is only run when character_set_server=utf8mb4 and collation-server set to one of utf8mb4 collations."); - } + Properties p = new Properties(); + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug73663QueryInterceptor.class.getName()); + + getConnectionWithProps(p); + // failure will be thrown from the statement interceptor if any "SET NAMES utf8" statement is issued instead of "SET NAMES utf8mb4" } /** * Statement interceptor used to implement preceding test. */ public static class Bug73663QueryInterceptor extends BaseQueryInterceptor { + @Override + public M preProcess(M queryPacket) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); + assertFalse(sql.contains("SET NAMES utf8") && !sql.contains("utf8mb4"), "Character set statement issued: " + sql); + return null; + } + @Override public T preProcess(Supplier str, Query interceptedQuery) { String sql = str.get(); - if (sql.contains("SET NAMES utf8") && !sql.contains("utf8mb4")) { - throw ExceptionFactory.createException("Character set statement issued: " + sql); - } + assertFalse(sql.contains("SET NAMES utf8") && !sql.contains("utf8mb4"), "Character set statement issued: " + sql); return null; } } @@ -150,12 +741,12 @@ public Void call() throws Exception { */ @Test public void testBug25504578() throws Exception { - - Properties p = new Properties(); - String cjCharset = CharsetMapping.getJavaEncodingForMysqlCharset("latin7"); - p.setProperty(PropertyKey.characterEncoding.getKeyName(), cjCharset); - - getConnectionWithProps(p); + String cjCharset = CharsetMappingWrapper.getStaticJavaEncodingForMysqlCharset("latin7"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), cjCharset); + getConnectionWithProps(props); } /** @@ -173,11 +764,13 @@ public void testBug81196() throws Exception { "(`id` int AUTO_INCREMENT NOT NULL, `name` varchar(50) NULL," + "CONSTRAINT `PK_LastViewedMatch_id` PRIMARY KEY (`id`))" + " ENGINE=InnoDB AUTO_INCREMENT=1 CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci"); - Properties p = new Properties(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); /* With a single-byte encoding */ - p.setProperty(PropertyKey.characterEncoding.getKeyName(), CharsetMapping.getJavaEncodingForMysqlCharset("latin1")); - Connection conn1 = getConnectionWithProps(p); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), CharsetMappingWrapper.getStaticJavaEncodingForMysqlCharset("latin1")); + Connection conn1 = getConnectionWithProps(props); Statement st1 = conn1.createStatement(); st1.executeUpdate("INSERT INTO testBug81196(name) VALUES ('" + fourBytesValue + "')"); ResultSet rs1 = st1.executeQuery("SELECT * from testBug81196"); @@ -186,8 +779,8 @@ public void testBug81196() throws Exception { /* With a UTF-8 encoding */ st1.executeUpdate("TRUNCATE TABLE testBug81196"); - p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - Connection conn2 = getConnectionWithProps(p); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + Connection conn2 = getConnectionWithProps(props); Statement st2 = conn2.createStatement(); st2.executeUpdate("INSERT INTO testBug81196(name) VALUES ('" + fourBytesValue + "')"); ResultSet rs2 = st2.executeQuery("SELECT * from testBug81196"); @@ -196,9 +789,9 @@ public void testBug81196() throws Exception { /* With a UTF-8 encoding and connectionCollation=utf8mb4_unicode_ci */ st1.executeUpdate("TRUNCATE TABLE testBug81196"); - p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - p.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8mb4_unicode_ci"); - Connection conn2_1 = getConnectionWithProps(p); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + props.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8mb4_unicode_ci"); + Connection conn2_1 = getConnectionWithProps(props); Statement st2_1 = conn2_1.createStatement(); st2_1.executeUpdate("INSERT INTO testBug81196(name) VALUES ('" + fourBytesValue + "')"); ResultSet rs2_1 = st2_1.executeQuery("SELECT * from testBug81196"); @@ -207,9 +800,9 @@ public void testBug81196() throws Exception { /* With connectionCollation=utf8_bin, SET NAMES utf8 is expected */ st1.executeUpdate("TRUNCATE TABLE testBug81196"); - p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - p.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8_bin"); - Connection conn3 = getConnectionWithProps(p); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + props.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8_bin"); + Connection conn3 = getConnectionWithProps(props); final Statement st3 = conn3.createStatement(); assertThrows(SQLException.class, "Incorrect string value: '\\\\xF0\\\\xA0\\\\x9C\\\\x8E' for column 'name' at row 1", new Callable() { @@ -220,4 +813,502 @@ public Void call() throws Exception { }); } } + + /** + * Tests fix for Bug#100606 (31818423), UNECESARY CALL TO "SET NAMES 'UTF8' COLLATE 'UTF8_GENERAL_CI'". + * + * @throws Exception + */ + @Test + public void testBug100606() throws Exception { + this.rs = this.stmt.executeQuery("show variables like 'collation_server'"); + this.rs.next(); + String collation = this.rs.getString(2); + + assumeTrue( + collation != null && collation.startsWith("utf8_general_ci") + && ((MysqlConnection) this.conn).getSession().getServerSession().getServerVariable("character_set_server").startsWith("utf8"), + "This test requires server configured with character_set_server=utf8 and collation-server=utf8_general_ci."); + + String fallbackCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; + + Properties p = new Properties(); + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestSetNamesQueryInterceptor.class.getName()); + checkCollationConnection(p, "SET NAMES", false, fallbackCollation); + + p.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8_general_ci"); + checkCollationConnection(p, "SET NAMES", false, "utf8_general_ci"); + } + + /** + * Tests fix for Bug#25554464, CONNECT FAILS WITH NPE WHEN THE SERVER STARTED WITH CUSTOM COLLATION. + * + * This test requires a special server configuration with: + *
    + *
  • character-set-server = custom + *
  • collation-server = custom_bin + *
+ * where 'custom_bin' is not a primary collation for 'custom' character set and has an index == 1024 on MySQL 8.0+ or index == 253 for older servers. + * + * @throws Exception + */ + @Test + public void testBug25554464_1() throws Exception { + this.rs = this.stmt.executeQuery("show variables like 'collation_server'"); + this.rs.next(); + String collation = this.rs.getString(2); + this.rs = this.stmt.executeQuery("select ID from INFORMATION_SCHEMA.COLLATIONS where COLLATION_NAME='custom_bin'"); + + assumeTrue( + collation != null && collation.startsWith("custom_bin") && this.rs.next() && this.rs.getInt(1) == (versionMeetsMinimum(8, 0, 1) ? 1024 : 253), + "This test requires server configured with custom character set and custom_bin collation."); + + String fallbackCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; + + Properties p = new Properties(); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestSetNamesQueryInterceptor.class.getName()); + + // With no specific properties c/J is using a predefined fallback collation 'utf8mb4_0900_ai_ci' or 'utf8mb4_general_ci' depending on server version. + // Post-handshake does not issue a SET NAMES because the predefined collation should still be used. + checkCollationConnection(p, "SET NAMES", false, fallbackCollation); + + // 'detectCustomCollations' itself doesn't change the expected collation, the predefined one should still be used. + p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + checkCollationConnection(p, "SET NAMES", false, fallbackCollation); + + // The predefined collation should still be used + p.setProperty(PropertyKey.customCharsetMapping.getKeyName(), "custom:Cp1252"); + checkCollationConnection(p, "SET NAMES", false, fallbackCollation); + + // Handshake collation is still 'utf8mb4_0900_ai_ci' because 'custom_bin' index > 255, then c/J sends the SET NAMES for the requested collation in a post-handshake. + p.setProperty(PropertyKey.connectionCollation.getKeyName(), "custom_bin"); + checkCollationConnection(p, "SET NAMES custom COLLATE custom_bin", true, "custom_bin"); + + p.setProperty(PropertyKey.connectionCollation.getKeyName(), "custom_general_ci"); + checkCollationConnection(p, "SET NAMES custom COLLATE custom_general_ci", true, "custom_general_ci"); + + // Handshake collation is still 'utf8mb4_0900_ai_ci' because 'Cp1252' is mapped to 'custom' charset via the 'customCharsetMapping' property. + // C/J sends the SET NAMES for the requested collation in a post-handshake. + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1252"); + p.remove(PropertyKey.connectionCollation.getKeyName()); + checkCollationConnection(p, "SET NAMES custom", true, "custom_general_ci"); + } + + /** + * Tests fix for Bug#25554464, CONNECT FAILS WITH NPE WHEN THE SERVER STARTED WITH CUSTOM COLLATION. + * + * This test requires a special server configuration with: + *
    + *
  • character-set-server = custom + *
  • collation-server = custom_general_ci + *
+ * where 'custom_general_ci' is a primary collation for 'custom' character set and has an index == 1025 on MySQL 8.0+ or index == 254 for older servers. + * + * @throws Exception + */ + @Test + public void testBug25554464_2() throws Exception { + this.rs = this.stmt.executeQuery("show variables like 'collation_server'"); + this.rs.next(); + String collation = this.rs.getString(2); + + this.rs = this.stmt.executeQuery("select ID from INFORMATION_SCHEMA.COLLATIONS where COLLATION_NAME='custom_general_ci'"); + + assumeTrue( + collation != null && collation.startsWith("custom_general_ci") && this.rs.next() + && this.rs.getInt(1) == (versionMeetsMinimum(8, 0, 1) ? 1025 : 254), + "This test requires server configured with custom character set and custom_general_ci collation."); + + String fallbackCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; + + Properties p = new Properties(); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestSetNamesQueryInterceptor.class.getName()); + + // With no specific properties c/J is using a predefined fallback collation 'utf8mb4_0900_ai_ci' or 'utf8mb4_general_ci' depending on server version. + // Post-handshake does not issue a SET NAMES because the predefined collation should still be used. + checkCollationConnection(p, "SET NAMES", false, fallbackCollation); + + // The predefined one should still be used. + p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + p.setProperty(PropertyKey.customCharsetMapping.getKeyName(), "custom:Cp1252"); + checkCollationConnection(p, "SET NAMES", false, fallbackCollation); + + // Sets the predefined collation via the handshake response, but, in a post-handshake, issues the SET NAMES setting the required 'connectionCollation'. + p.setProperty(PropertyKey.connectionCollation.getKeyName(), "custom_general_ci"); + checkCollationConnection(p, "SET NAMES custom COLLATE custom_general_ci", true, "custom_general_ci"); + + // Sets the predefined collation via the handshake response, but, in a post-handshake, issues the SET NAMES setting the required 'characterEncoding'. + // The chosen collation then is 'custom_general_ci' because it's a primary one for 'custom' character set. + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1252"); + p.remove(PropertyKey.connectionCollation.getKeyName()); + checkCollationConnection(p, "SET NAMES custom", true, "custom_general_ci"); + } + + public static class TestSetNamesQueryInterceptor extends BaseQueryInterceptor { + public static String query = ""; + public static boolean usedSetNames = false; + + @Override + public QueryInterceptor init(MysqlConnection conn, Properties props, Log log) { + usedSetNames = false; + return super.init(conn, props, log); + } + + @Override + public M preProcess(M queryPacket) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); + if (sql.contains(query)) { + usedSetNames = true; + } + return null; + } + + @Override + public T preProcess(Supplier str, Query interceptedQuery) { + String sql = str.get(); + if (sql.contains(query)) { + usedSetNames = true; + } + return null; + } + } + + @Test + public void testPasswordCharacterEncoding() throws Exception { + this.rs = this.stmt.executeQuery("show variables like 'collation_server'"); + this.rs.next(); + String collation = this.rs.getString(2); + assumeTrue(collation != null && collation.startsWith("latin1"), "This test requires a server configured with latin1 character set."); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestSetNamesQueryInterceptor.class.getName()); + + String requestedCollation = "latin1_swedish_ci"; + String fallbackCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; + + checkCollationConnection(props, "SET NAMES", false, fallbackCollation); + + props.setProperty(PropertyKey.passwordCharacterEncoding.getKeyName(), "UTF-8"); + checkCollationConnection(props, "SET NAMES", false, fallbackCollation); + + props.setProperty(PropertyKey.connectionCollation.getKeyName(), requestedCollation); + checkCollationConnection(props, "SET NAMES latin1 COLLATE " + requestedCollation, true, requestedCollation); + + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1252"); + props.remove(PropertyKey.connectionCollation.getKeyName()); + checkCollationConnection(props, "SET NAMES latin1", true, requestedCollation); + + requestedCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; + + props.setProperty(PropertyKey.connectionCollation.getKeyName(), requestedCollation); + checkCollationConnection(props, "SET NAMES", false, requestedCollation); + + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + props.remove(PropertyKey.connectionCollation.getKeyName()); + checkCollationConnection(props, "SET NAMES", false, requestedCollation); + } + + private void checkCollationConnection(Properties props, String query, boolean expectQueryIsIssued, String expectedCollation) throws Exception { + TestSetNamesQueryInterceptor.query = query; + Connection c = getConnectionWithProps(props); + if (expectQueryIsIssued) { + assertTrue(TestSetNamesQueryInterceptor.usedSetNames); + } else { + assertFalse(TestSetNamesQueryInterceptor.usedSetNames); + } + this.rs = c.createStatement().executeQuery("show variables like 'collation_connection'"); + this.rs.next(); + assertEquals(expectedCollation, this.rs.getString(2)); + c.close(); + } + + /** + * Tests fix for Bug#71038, Add an option for custom collations detection + * + * @throws Exception + */ + @Test + public void testBug71038() throws Exception { + Properties p = new Properties(); + p.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "false"); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug71038QueryInterceptor.class.getName()); + + JdbcConnection c = (JdbcConnection) getConnectionWithProps(p); + Bug71038QueryInterceptor si = (Bug71038QueryInterceptor) c.getQueryInterceptorsInstances().get(0); + assertTrue(si.cnt == 0, "SELECT from INFORMATION_SCHEMA.COLLATIONS was issued when detectCustomCollations=false"); + c.close(); + + p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug71038QueryInterceptor.class.getName()); + + c = (JdbcConnection) getConnectionWithProps(p); + si = (Bug71038QueryInterceptor) c.getQueryInterceptorsInstances().get(0); + assertTrue(si.cnt > 0, "SELECT from INFORMATION_SCHEMA.COLLATIONS wasn't issued when detectCustomCollations=true"); + c.close(); + } + + /** + * Counts the number of issued "SHOW COLLATION" statements. + */ + public static class Bug71038QueryInterceptor extends BaseQueryInterceptor { + int cnt = 0; + + @Override + public M preProcess(M queryPacket) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); + if (sql.contains("from INFORMATION_SCHEMA.COLLATIONS")) { + this.cnt++; + } + return null; + } + } + + /** + * Tests fix for Bug#91317 (28207422), Wrong defaults on collation mappings. + * + * @throws Exception + */ + @Test + public void testBug91317() throws Exception { + Map defaultCollations = new HashMap<>(); + + Properties p = new Properties(); + p.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + p.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + p.setProperty(PropertyKey.customCharsetMapping.getKeyName(), "custom:Cp1252"); + Connection c = getConnectionWithProps(p); + + // Compare server-side and client-side collation defaults. + this.rs = this.stmt.executeQuery("SELECT COLLATION_NAME, CHARACTER_SET_NAME, ID FROM INFORMATION_SCHEMA.COLLATIONS WHERE IS_DEFAULT = 'Yes'"); + while (this.rs.next()) { + String collationName = this.rs.getString(1); + String charsetName = this.rs.getString(2); + int collationId = this.rs.getInt(3); + + int mappedCollationId = ((MysqlConnection) c).getSession().getServerSession().getCharsetSettings() + .getCollationIndexForMysqlCharsetName(charsetName); + + defaultCollations.put(charsetName, collationName); + + // Default collation for 'utf8mb4' is 'utf8mb4_0900_ai_ci' in MySQL 8.0.1 and above, 'utf8mb4_general_ci' in the others. + if ("utf8mb4".equalsIgnoreCase(charsetName) && !versionMeetsMinimum(8, 0, 1)) { + mappedCollationId = 45; + } + + assertEquals(collationId, mappedCollationId); + assertEquals(collationName, + ((MysqlConnection) c).getSession().getServerSession().getCharsetSettings().getCollationNameForCollationIndex(mappedCollationId)); + } + + ServerVersion sv = ((JdbcConnection) this.conn).getServerVersion(); + + // Check `collation_connection` for each one of the known character sets. + this.rs = this.stmt.executeQuery("SELECT character_set_name FROM information_schema.character_sets"); + int csCount = 0; + while (this.rs.next()) { + csCount++; + String cs = this.rs.getString(1); + + // The following cannot be set as client_character_set + // (https://dev.mysql.com/doc/refman/8.0/en/charset-connection.html#charset-connection-impermissible-client-charset) + if (cs.equalsIgnoreCase("ucs2") || cs.equalsIgnoreCase("utf16") || cs.equalsIgnoreCase("utf16le") || cs.equalsIgnoreCase("utf32")) { + continue; + } + + String javaEnc = ((MysqlConnection) c).getSession().getServerSession().getCharsetSettings().getJavaEncodingForMysqlCharset(cs); + System.out.println(cs + "->" + javaEnc); + String charsetForJavaEnc = ((MysqlConnection) c).getSession().getServerSession().getCharsetSettings().getMysqlCharsetForJavaEncoding(javaEnc, sv); + String expectedCollation = defaultCollations.get(charsetForJavaEnc); + + if ("UTF-8".equalsIgnoreCase(javaEnc)) { + // UTF-8 is the exception. This encoding is converted to MySQL charset 'utf8mb4' instead of 'utf8', and its corresponding collation. + expectedCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; + } + + Properties p2 = new Properties(); + p2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + p2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + p2.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + p2.setProperty(PropertyKey.customCharsetMapping.getKeyName(), "custom:Cp1252"); + p2.setProperty(PropertyKey.characterEncoding.getKeyName(), javaEnc); + + Connection testConn = getConnectionWithProps(p2); + ResultSet testRs = testConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'collation_connection'"); + assertTrue(testRs.next()); + assertEquals(expectedCollation, testRs.getString(2)); + testConn.close(); + } + // Assert that some charsets were tested. + assertTrue(csCount > 35); // There are 39 charsets in MySQL 5.5.61, 40 in MySQL 5.6.41 and 41 in MySQL 5.7.23 and above, but these numbers can vary. + } + + /** + * Test for Bug#72712 - SET NAMES issued unnecessarily. + * + * Using a statement interceptor, ensure that SET NAMES is not called if the encoding requested by the client application matches that of + * character_set_server. + * + * Also test that character_set_results is not set unnecessarily. + * + * @throws Exception + */ + @Test + public void testBug72712() throws Exception { + assumeTrue(((MysqlConnection) this.conn).getSession().getServerSession().getServerVariable("character_set_server").equals("latin1"), + "This test only run when character_set_server=latin1"); + + Properties p = new Properties(); + p.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + p.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "cp1252"); + p.setProperty(PropertyKey.characterSetResults.getKeyName(), "cp1252"); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug72712QueryInterceptor.class.getName()); + + getConnectionWithProps(p); + // exception will be thrown from the statement interceptor if any SET statements are issued + } + + /** + * Statement interceptor used to implement preceding test. + */ + public static class Bug72712QueryInterceptor extends BaseQueryInterceptor { + @Override + public T preProcess(Supplier str, Query interceptedQuery) { + String sql = str.get(); + if (sql.contains("SET NAMES") + || sql.contains(CharsetSettings.CHARACTER_SET_RESULTS) && !(sql.contains("SHOW VARIABLES") || sql.contains("SELECT @@"))) { + throw ExceptionFactory.createException("Wrongt statement issued: " + sql); + } + return null; + } + } + + /** + * Tests fix for Bug#95139 (29807572), CACHESERVERCONFIGURATION APPEARS TO THWART CHARSET DETECTION. + * + * @throws Exception + */ + @Test + public void testBug95139() throws Exception { + + Properties p = new Properties(); + p.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + p.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug95139QueryInterceptor.class.getName()); + testBug95139CheckVariables(p, 1, null, "SET " + CharsetSettings.CHARACTER_SET_RESULTS + " = NULL"); + + p.setProperty(PropertyKey.cacheServerConfiguration.getKeyName(), "true"); + p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + + // Empty the cache possibly created by other tests to get a correct queryVarsCnt on the next step + Connection c = getConnectionWithProps(p); + Field f = NativeSession.class.getDeclaredField("serverConfigCache"); + f.setAccessible(true); + @SuppressWarnings("unchecked") + CacheAdapter> cache = (CacheAdapter>) f.get(((MysqlConnection) c).getSession()); + if (cache != null) { + cache.invalidateAll(); + } + + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "cp1252"); + p.setProperty(PropertyKey.characterSetResults.getKeyName(), "cp1252"); + testBug95139CheckVariables(p, 1, null, null); + + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + p.setProperty(PropertyKey.characterSetResults.getKeyName(), "cp1252"); + testBug95139CheckVariables(p, 0, null, "SET " + CharsetSettings.CHARACTER_SET_RESULTS + " = latin1"); + + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + p.remove(PropertyKey.characterSetResults.getKeyName()); + testBug95139CheckVariables(p, 0, null, "SET " + CharsetSettings.CHARACTER_SET_RESULTS + " = NULL"); + + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + p.setProperty(PropertyKey.passwordCharacterEncoding.getKeyName(), "latin1"); + testBug95139CheckVariables(p, 0, "SET NAMES utf8mb4", "SET " + CharsetSettings.CHARACTER_SET_RESULTS + " = NULL"); + + p.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + p.setProperty(PropertyKey.passwordCharacterEncoding.getKeyName(), "latin1"); + p.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8mb4_bin"); + testBug95139CheckVariables(p, 0, "SET NAMES utf8mb4 COLLATE utf8mb4_bin", "SET " + CharsetSettings.CHARACTER_SET_RESULTS + " = NULL"); + } + + private void testBug95139CheckVariables(Properties p, int queryVarsCnt, String expSetNamesQuery, String expSetCharacterSetResultsquery) throws Exception { + Connection con = getConnectionWithProps(p); + + Bug95139QueryInterceptor si = (Bug95139QueryInterceptor) ((JdbcConnection) con).getQueryInterceptorsInstances().get(0); + assertEquals(queryVarsCnt, si.queryVarsCnt); + assertEquals(expSetNamesQuery == null ? 0 : 1, si.setNamesCnt); + assertEquals(expSetCharacterSetResultsquery == null ? 0 : 1, si.setCharacterSetResultsCnt); + if (expSetNamesQuery != null) { + assertEquals(expSetNamesQuery, si.setNamesQuery); + } + if (expSetCharacterSetResultsquery != null) { + assertEquals(expSetCharacterSetResultsquery, si.setCharacterSetResultsQuery); + } + + Map svs = ((MysqlConnection) con).getSession().getServerSession().getServerVariables(); + System.out.println(svs); + Map exp = new HashMap<>(); + exp.put("character_set_client", svs.get("character_set_client")); + exp.put("character_set_connection", svs.get("character_set_connection")); + exp.put("character_set_results", svs.get("character_set_results") == null ? "" : svs.get("character_set_results")); + exp.put("character_set_server", svs.get("character_set_server")); + exp.put("collation_server", svs.get("collation_server")); + exp.put("collation_connection", svs.get("collation_connection")); + + ResultSet rset = con.createStatement() + .executeQuery("show variables where variable_name='character_set_client' or variable_name='character_set_connection'" + + " or variable_name='character_set_results' or variable_name='character_set_server' or variable_name='collation_server'" + + " or variable_name='collation_connection'"); + while (rset.next()) { + System.out.println(rset.getString(1) + "=" + rset.getString(2)); + assertEquals(exp.get(rset.getString(1)), rset.getString(2), rset.getString(1)); + } + + con.close(); + } + + public static class Bug95139QueryInterceptor extends BaseQueryInterceptor { + int queryVarsCnt = 0; + int setNamesCnt = 0; + String setNamesQuery = null; + int setCharacterSetResultsCnt = 0; + String setCharacterSetResultsQuery = null; + + @Override + public M preProcess(M queryPacket) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 0, queryPacket.getPosition()); + if (sql.contains("SET NAMES")) { + this.setNamesCnt++; + this.setNamesQuery = sql.substring(sql.indexOf("SET")); + } else if (sql.contains("SET " + CharsetSettings.CHARACTER_SET_RESULTS)) { + this.setCharacterSetResultsCnt++; + this.setCharacterSetResultsQuery = sql.substring(sql.indexOf("SET")); + } else if (sql.contains("SHOW VARIABLES") || sql.contains("SELECT @@")) { + System.out.println(sql.substring(sql.indexOf("S"))); + this.queryVarsCnt++; + } + return null; + } + + @Override + public T preProcess(Supplier str, Query interceptedQuery) { + String sql = str.get(); + if (sql.contains("SET NAMES")) { + this.setNamesCnt++; + } else if (sql.contains("SET " + CharsetSettings.CHARACTER_SET_RESULTS)) { + this.setCharacterSetResultsCnt++; + } else if (sql.contains("SHOW VARIABLES") || sql.contains("SELECT @@")) { + System.out.println(sql.substring(sql.indexOf("S"))); + this.queryVarsCnt++; + } + return null; + } + } + } diff --git a/src/test/java/testsuite/regression/ConnectionRegressionTest.java b/src/test/java/testsuite/regression/ConnectionRegressionTest.java index dac614df2..14ca22d8d 100644 --- a/src/test/java/testsuite/regression/ConnectionRegressionTest.java +++ b/src/test/java/testsuite/regression/ConnectionRegressionTest.java @@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -87,7 +88,6 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; @@ -118,9 +118,11 @@ import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CharsetMappingWrapper; +import com.mysql.cj.CharsetSettings; import com.mysql.cj.Constants; import com.mysql.cj.Messages; import com.mysql.cj.MysqlConnection; @@ -146,7 +148,9 @@ import com.mysql.cj.exceptions.PropertyNotModifiableException; import com.mysql.cj.interceptors.QueryInterceptor; import com.mysql.cj.jdbc.ClientInfoProvider; +import com.mysql.cj.jdbc.ClientInfoProviderSP; import com.mysql.cj.jdbc.ClientPreparedStatement; +import com.mysql.cj.jdbc.CommentClientInfoProvider; import com.mysql.cj.jdbc.ConnectionGroupManager; import com.mysql.cj.jdbc.ConnectionImpl; import com.mysql.cj.jdbc.JdbcConnection; @@ -184,6 +188,10 @@ import com.mysql.cj.protocol.PacketSentTimeHolder; import com.mysql.cj.protocol.Resultset; import com.mysql.cj.protocol.ServerSession; +import com.mysql.cj.protocol.ServerSessionStateController; +import com.mysql.cj.protocol.ServerSessionStateController.ServerSessionStateChanges; +import com.mysql.cj.protocol.ServerSessionStateController.SessionStateChange; +import com.mysql.cj.protocol.ServerSessionStateController.SessionStateChangesListener; import com.mysql.cj.protocol.StandardSocketFactory; import com.mysql.cj.protocol.a.DebugBufferingPacketReader; import com.mysql.cj.protocol.a.DebugBufferingPacketSender; @@ -260,6 +268,8 @@ public void testBug3790() throws Exception { ResultSet rs2 = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); try { createTable("testBug3790", "(field1 INT NOT NULL PRIMARY KEY, field2 VARCHAR(32)) ", "InnoDB"); @@ -305,71 +315,6 @@ public void testBug3790() throws Exception { } } - /** - * Tests if the driver configures character sets correctly for 4.1.x servers. Requires that the 'admin connection' is configured, as this test needs to - * create/drop databases. - * - * @throws Exception - */ - @Test - public void testCollation41() throws Exception { - if (isAdminConnectionConfigured()) { - Map charsetsAndCollations = getCharacterSetsAndCollations(); - charsetsAndCollations.remove("latin7"); // Maps to multiple Java - // charsets - charsetsAndCollations.remove("ucs2"); // can't be used as a - // connection charset - - for (String charsetName : charsetsAndCollations.keySet()) { - Connection charsetConn = null; - Statement charsetStmt = null; - - try { - //String collationName = charsetsAndCollations.get(charsetName); - - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), charsetName); - - System.out.println("Testing character set " + charsetName); - - charsetConn = getAdminConnectionWithProps(props); - - charsetStmt = charsetConn.createStatement(); - - charsetStmt.executeUpdate("DROP DATABASE IF EXISTS testCollation41"); - charsetStmt.executeUpdate("DROP TABLE IF EXISTS testCollation41"); - charsetStmt.executeUpdate("CREATE DATABASE testCollation41 DEFAULT CHARACTER SET " + charsetName); - charsetStmt.close(); - - charsetConn.setCatalog("testCollation41"); - - // We've switched catalogs, so we need to recreate the - // statement to pick this up... - charsetStmt = charsetConn.createStatement(); - - StringBuilder createTableCommand = new StringBuilder("CREATE TABLE testCollation41(field1 VARCHAR(255), field2 INT)"); - - charsetStmt.executeUpdate(createTableCommand.toString()); - - charsetStmt.executeUpdate("INSERT INTO testCollation41 VALUES ('abc', 0)"); - - int updateCount = charsetStmt.executeUpdate("UPDATE testCollation41 SET field2=1 WHERE field1='abc'"); - assertTrue(updateCount == 1); - } finally { - if (charsetStmt != null) { - charsetStmt.executeUpdate("DROP TABLE IF EXISTS testCollation41"); - charsetStmt.executeUpdate("DROP DATABASE IF EXISTS testCollation41"); - charsetStmt.close(); - } - - if (charsetConn != null) { - charsetConn.close(); - } - } - } - } - } - /** * Tests setReadOnly() being reset during failover * @@ -378,6 +323,8 @@ public void testCollation41() throws Exception { @Test public void testSetReadOnly() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); String sepChar = "?"; @@ -397,7 +344,10 @@ public void testSetReadOnly() throws Exception { boolean isReadOnly = reconnectableConn.isReadOnly(); - Connection killConn = getConnectionWithProps((Properties) null); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection killConn = getConnectionWithProps(props2); killConn.createStatement().executeUpdate("KILL " + connectionId); Thread.sleep(2000); @@ -433,35 +383,6 @@ public void testSetReadOnly() throws Exception { assertTrue(reconnectableConn.isReadOnly() == isReadOnly); } - private Map getCharacterSetsAndCollations() throws Exception { - Map charsetsToLoad = new HashMap<>(); - - try { - this.rs = this.stmt.executeQuery("SHOW character set"); - - while (this.rs.next()) { - charsetsToLoad.put(this.rs.getString("Charset"), this.rs.getString("Default collation")); - } - - // - // These don't have mappings in Java... - // - charsetsToLoad.remove("swe7"); - charsetsToLoad.remove("hp8"); - charsetsToLoad.remove("dec8"); - charsetsToLoad.remove("koi8u"); - charsetsToLoad.remove("keybcs2"); - charsetsToLoad.remove("geostd8"); - charsetsToLoad.remove("armscii8"); - } finally { - if (this.rs != null) { - this.rs.close(); - } - } - - return charsetsToLoad; - } - /** * Tests fix for BUG#4334, port #'s not being picked up for failover/autoreconnect. * @@ -469,144 +390,147 @@ private Map getCharacterSetsAndCollations() throws Exception { */ @Test public void testBug4334() throws Exception { - if (isAdminConnectionConfigured()) { - Connection adminConnection = null; - - try { - adminConnection = getAdminConnection(); + Connection adminConnection = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - int bogusPortNumber = 65534; + try { + adminConnection = getConnectionWithProps(props); - HostInfo defaultHost = mainConnectionUrl.getMainHost(); + int bogusPortNumber = 65534; - String host = defaultHost.getHost(); - int port = defaultHost.getPort(); - String database = defaultHost.getDatabase(); - String user = defaultHost.getUser(); - String password = defaultHost.getPassword(); + HostInfo defaultHost = mainConnectionUrl.getMainHost(); - StringBuilder newUrlToTestPortNum = new StringBuilder("jdbc:mysql://"); + String host = defaultHost.getHost(); + int port = defaultHost.getPort(); + String database = defaultHost.getDatabase(); + String user = defaultHost.getUser(); + String password = defaultHost.getPassword(); - if (host != null) { - newUrlToTestPortNum.append(host); - } + StringBuilder newUrlToTestPortNum = new StringBuilder("jdbc:mysql://"); - newUrlToTestPortNum.append(":").append(port); - newUrlToTestPortNum.append(","); + if (host != null) { + newUrlToTestPortNum.append(host); + } - if (host != null) { - newUrlToTestPortNum.append(host); - } + newUrlToTestPortNum.append(":").append(port); + newUrlToTestPortNum.append(","); - newUrlToTestPortNum.append(":").append(bogusPortNumber); - newUrlToTestPortNum.append("/"); + if (host != null) { + newUrlToTestPortNum.append(host); + } - if (database != null) { - newUrlToTestPortNum.append(database); - } + newUrlToTestPortNum.append(":").append(bogusPortNumber); + newUrlToTestPortNum.append("/"); - if ((user != null) || (password != null)) { - newUrlToTestPortNum.append("?"); + if (database != null) { + newUrlToTestPortNum.append(database); + } - if (user != null) { - newUrlToTestPortNum.append("user=").append(user); + if ((user != null) || (password != null)) { + newUrlToTestPortNum.append("?"); - if (password != null) { - newUrlToTestPortNum.append("&"); - } - } + if (user != null) { + newUrlToTestPortNum.append("user=").append(user); if (password != null) { - newUrlToTestPortNum.append("password=").append(password); + newUrlToTestPortNum.append("&"); } } - Properties autoReconnectProps = new Properties(); - autoReconnectProps.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); + if (password != null) { + newUrlToTestPortNum.append("password=").append(password); + } + } - System.out.println(newUrlToTestPortNum); + Properties autoReconnectProps = new Properties(); + autoReconnectProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + autoReconnectProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + autoReconnectProps.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); - // - // First test that port #'s are being correctly picked up - // - // We do this by looking at the error message that is returned - // - Connection portNumConn = DriverManager.getConnection(newUrlToTestPortNum.toString(), autoReconnectProps); - Statement portNumStmt = portNumConn.createStatement(); - this.rs = portNumStmt.executeQuery("SELECT connection_id()"); - this.rs.next(); + System.out.println(newUrlToTestPortNum); - killConnection(adminConnection, this.rs.getString(1)); + // + // First test that port #'s are being correctly picked up + // + // We do this by looking at the error message that is returned + // + Connection portNumConn = DriverManager.getConnection(newUrlToTestPortNum.toString(), autoReconnectProps); + Statement portNumStmt = portNumConn.createStatement(); + this.rs = portNumStmt.executeQuery("SELECT connection_id()"); + this.rs.next(); - try { - portNumStmt.executeQuery("SELECT connection_id()"); - } catch (SQLException sqlEx) { - // we expect this one - } + killConnection(adminConnection, this.rs.getString(1)); - try { - portNumStmt.executeQuery("SELECT connection_id()"); - } catch (SQLException sqlEx) { - assertTrue(sqlEx.getMessage().toLowerCase().indexOf("connection refused") != -1); - } + try { + portNumStmt.executeQuery("SELECT connection_id()"); + } catch (SQLException sqlEx) { + // we expect this one + } - // - // Now make sure failover works - // - StringBuilder newUrlToTestFailover = new StringBuilder("jdbc:mysql://"); + try { + portNumStmt.executeQuery("SELECT connection_id()"); + } catch (SQLException sqlEx) { + assertTrue(sqlEx.getMessage().toLowerCase().indexOf("connection refused") != -1); + } - if (host != null) { - newUrlToTestFailover.append(host); - } + // + // Now make sure failover works + // + StringBuilder newUrlToTestFailover = new StringBuilder("jdbc:mysql://"); - newUrlToTestFailover.append(":").append(port); - newUrlToTestFailover.append(","); + if (host != null) { + newUrlToTestFailover.append(host); + } - if (host != null) { - newUrlToTestFailover.append(host); - } + newUrlToTestFailover.append(":").append(port); + newUrlToTestFailover.append(","); - newUrlToTestFailover.append(":").append(bogusPortNumber); - newUrlToTestFailover.append("/"); + if (host != null) { + newUrlToTestFailover.append(host); + } - if (database != null) { - newUrlToTestFailover.append(database); - } + newUrlToTestFailover.append(":").append(bogusPortNumber); + newUrlToTestFailover.append("/"); - if ((user != null) || (password != null)) { - newUrlToTestFailover.append("?"); + if (database != null) { + newUrlToTestFailover.append(database); + } - if (user != null) { - newUrlToTestFailover.append("user=").append(user); + if ((user != null) || (password != null)) { + newUrlToTestFailover.append("?"); - if (password != null) { - newUrlToTestFailover.append("&"); - } - } + if (user != null) { + newUrlToTestFailover.append("user=").append(user); if (password != null) { - newUrlToTestFailover.append("password=").append(password); + newUrlToTestFailover.append("&"); } } - Connection failoverConn = DriverManager.getConnection(newUrlToTestFailover.toString(), autoReconnectProps); - Statement failoverStmt = failoverConn.createStatement(); - this.rs = failoverStmt.executeQuery("SELECT connection_id()"); - this.rs.next(); + if (password != null) { + newUrlToTestFailover.append("password=").append(password); + } + } - killConnection(adminConnection, this.rs.getString(1)); + Connection failoverConn = DriverManager.getConnection(newUrlToTestFailover.toString(), autoReconnectProps); + Statement failoverStmt = failoverConn.createStatement(); + this.rs = failoverStmt.executeQuery("SELECT connection_id()"); + this.rs.next(); - try { - failoverStmt.executeQuery("SELECT connection_id()"); - } catch (SQLException sqlEx) { - // we expect this one - } + killConnection(adminConnection, this.rs.getString(1)); - this.rs = failoverStmt.executeQuery("SELECT connection_id()"); - } finally { - if (adminConnection != null) { - adminConnection.close(); - } + try { + failoverStmt.executeQuery("SELECT connection_id()"); + } catch (SQLException sqlEx) { + // we expect this one + } + + this.rs = failoverStmt.executeQuery("SELECT connection_id()"); + } finally { + if (adminConnection != null) { + adminConnection.close(); } } } @@ -624,6 +548,8 @@ private static void killConnection(Connection adminConn, String threadId) throws @Test public void testBug6966() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory"); @@ -679,12 +605,16 @@ public void testBug6966() throws Exception { public void testBug7952() throws Exception { String host = getEncodedHostPortPairFromTestsuiteUrl() + "," + getEncodedHostPortPairFromTestsuiteUrl(); Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + Connection killerConnection = getConnectionWithProps(props); + props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.queriesBeforeRetrySource.getKeyName(), "10"); props.setProperty(PropertyKey.maxReconnects.getKeyName(), "1"); Connection failoverConnection = null; - Connection killerConnection = getConnectionWithProps((String) null); try { failoverConnection = getConnectionWithProps("jdbc:mysql://" + host + "/", props); @@ -743,150 +673,6 @@ public void testBug7952() throws Exception { } } - /** - * Tests fix for BUG#7607 - MS932, SHIFT_JIS and Windows_31J not recog. as aliases for sjis. - * - * @throws Exception - */ - @Test - public void testBug7607() throws Exception { - Connection ms932Conn = null, cp943Conn = null, shiftJisConn = null, windows31JConn = null; - - try { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "MS932"); - - ms932Conn = getConnectionWithProps(props); - - this.rs = ms932Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); - assertTrue(this.rs.next()); - String encoding = this.rs.getString(2); - assertEquals("cp932", encoding.toLowerCase(Locale.ENGLISH)); - - this.rs = ms932Conn.createStatement().executeQuery("SELECT 'abc'"); - assertTrue(this.rs.next()); - - String charsetToCheck = "ms932"; - - assertEquals(charsetToCheck, - ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toLowerCase(Locale.ENGLISH)); - - try { - ms932Conn.createStatement().executeUpdate("drop table if exists testBug7607"); - ms932Conn.createStatement().executeUpdate("create table testBug7607 (sortCol int, col1 varchar(100) ) character set sjis"); - ms932Conn.createStatement().executeUpdate("insert into testBug7607 values(1, 0x835C)"); // standard - // sjis - ms932Conn.createStatement().executeUpdate("insert into testBug7607 values(2, 0x878A)"); // NEC - // kanji - - this.rs = ms932Conn.createStatement().executeQuery("SELECT col1 FROM testBug7607 ORDER BY sortCol ASC"); - assertTrue(this.rs.next()); - String asString = this.rs.getString(1); - assertTrue("\u30bd".equals(asString)); - - assertTrue(this.rs.next()); - asString = this.rs.getString(1); - assertEquals("\u3231", asString); - } finally { - ms932Conn.createStatement().executeUpdate("drop table if exists testBug7607"); - } - - props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "SHIFT_JIS"); - - shiftJisConn = getConnectionWithProps(props); - - this.rs = shiftJisConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); - assertTrue(this.rs.next()); - encoding = this.rs.getString(2); - assertTrue("sjis".equalsIgnoreCase(encoding)); - - this.rs = shiftJisConn.createStatement().executeQuery("SELECT 'abc'"); - assertTrue(this.rs.next()); - - String charSetUC = ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toUpperCase(Locale.US); - - props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "WINDOWS-31J"); - - windows31JConn = getConnectionWithProps(props); - - this.rs = windows31JConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); - assertTrue(this.rs.next()); - encoding = this.rs.getString(2); - - assertEquals("cp932", encoding.toLowerCase(Locale.ENGLISH)); - - this.rs = windows31JConn.createStatement().executeQuery("SELECT 'abc'"); - assertTrue(this.rs.next()); - - assertEquals("windows-31j".toLowerCase(Locale.ENGLISH), - ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toLowerCase(Locale.ENGLISH)); - - props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "CP943"); - - cp943Conn = getConnectionWithProps(props); - - this.rs = cp943Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_set_client'"); - assertTrue(this.rs.next()); - encoding = this.rs.getString(2); - assertTrue("sjis".equalsIgnoreCase(encoding)); - - this.rs = cp943Conn.createStatement().executeQuery("SELECT 'abc'"); - assertTrue(this.rs.next()); - - charSetUC = ((com.mysql.cj.jdbc.result.ResultSetMetaData) this.rs.getMetaData()).getColumnCharacterEncoding(1).toUpperCase(Locale.US); - - assertEquals("CP943", charSetUC); - - } finally { - if (ms932Conn != null) { - ms932Conn.close(); - } - - if (shiftJisConn != null) { - shiftJisConn.close(); - } - - if (windows31JConn != null) { - windows31JConn.close(); - } - - if (cp943Conn != null) { - cp943Conn.close(); - } - } - } - - /** - * Tests fix for BUG#9206, can not use 'UTF-8' for characterSetResults configuration property. - * - * @throws Exception - */ - @Test - public void testBug9206() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "UTF-8"); - getConnectionWithProps(props).close(); - } - - /** - * These two charsets have different names depending on version of MySQL server. - * - * @throws Exception - */ - @Test - public void testNewCharsetsConfiguration() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_KR"); - getConnectionWithProps(props).close(); - - props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "KOI8_R"); - getConnectionWithProps(props).close(); - } - /** * Tests fix for BUG#10144 - Memory leak in ServerPreparedStatement if serverPrepare() fails. * @@ -895,6 +681,8 @@ public void testNewCharsetsConfiguration() throws Exception { @Test public void testBug10144() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.emulateUnsupportedPstmts.getKeyName(), "false"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); @@ -912,24 +700,6 @@ public void testBug10144() throws Exception { } } - /** - * Tests fix for BUG#10496 - SQLException is thrown when using property "characterSetResults" - * - * @throws Exception - */ - @Test - public void testBug10496() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "WINDOWS-31J"); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "WINDOWS-31J"); - getConnectionWithProps(props).close(); - - props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP"); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "EUC_JP"); - getConnectionWithProps(props).close(); - } - /** * Tests fix for BUG#11259, autoReconnect ping causes exception on connection startup. * @@ -940,6 +710,8 @@ public void testBug11259() throws Exception { Connection dsConn = null; try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); dsConn = getConnectionWithProps(props); } finally { @@ -956,17 +728,17 @@ public void testBug11259() throws Exception { */ @Test public void testBug11879() throws Exception { - if (runMultiHostTests()) { - Connection replConn = null; - - try { - replConn = getSourceReplicaReplicationConnection(); - replConn.setReadOnly(true); - replConn.setReadOnly(false); - } finally { - if (replConn != null) { - replConn.close(); - } + Connection replConn = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + replConn = getSourceReplicaReplicationConnection(props); + replConn.setReadOnly(true); + replConn.setReadOnly(false); + } finally { + if (replConn != null) { + replConn.close(); } } } @@ -979,6 +751,8 @@ public void testBug11879() throws Exception { @Test public void testBug11976() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useConfigs.getKeyName(), "maxPerformance"); Connection maxPerfConn = getConnectionWithProps(props); @@ -992,26 +766,25 @@ public void testBug11976() throws Exception { */ @Test public void testBug12218() throws Exception { - if (runMultiHostTests()) { - Connection replConn = null; + Connection replConn = null; - HostInfo hostInfo = mainConnectionUrl.getMainHost(); - Properties props = new Properties(); - props.setProperty(PropertyKey.USER.getKeyName(), hostInfo.getUser() == null ? "" : hostInfo.getUser()); - props.setProperty(PropertyKey.PASSWORD.getKeyName(), hostInfo.getPassword() == null ? "" : hostInfo.getPassword()); - props = appendRequiredProperties(props); + HostInfo hostInfo = mainConnectionUrl.getMainHost(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.USER.getKeyName(), hostInfo.getUser() == null ? "" : hostInfo.getUser()); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), hostInfo.getPassword() == null ? "" : hostInfo.getPassword()); + props = appendRequiredProperties(props); - String replUrl = String.format("%1$s//address=(host=%2$s)(port=%3$d),address=(host=%2$s)(port=%3$d)(isReplica=true)/%4$s", - ConnectionUrl.Type.REPLICATION_CONNECTION.getScheme(), getEncodedHostFromTestsuiteUrl(), getPortFromTestsuiteUrl(), hostInfo.getDatabase()); + String replUrl = String.format("%1$s//address=(host=%2$s)(port=%3$d),address=(host=%2$s)(port=%3$d)(isReplica=true)/%4$s", + ConnectionUrl.Type.REPLICATION_CONNECTION.getScheme(), getEncodedHostFromTestsuiteUrl(), getPortFromTestsuiteUrl(), hostInfo.getDatabase()); - try { - replConn = DriverManager.getConnection(replUrl, props); - assertTrue( - !((ReplicationConnection) replConn).getSourceConnection().hasSameProperties(((ReplicationConnection) replConn).getReplicaConnection())); - } finally { - if (replConn != null) { - replConn.close(); - } + try { + replConn = DriverManager.getConnection(replUrl, props); + assertTrue(!((ReplicationConnection) replConn).getSourceConnection().hasSameProperties(((ReplicationConnection) replConn).getReplicaConnection())); + } finally { + if (replConn != null) { + replConn.close(); } } } @@ -1027,6 +800,8 @@ public void testBug12229() throws Exception { this.stmt.executeUpdate("insert into testBug12229 values (123456),(1)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.slowQueryThresholdMillis.getKeyName(), "0"); props.setProperty(PropertyKey.logSlowQueries.getKeyName(), "true"); @@ -1048,18 +823,6 @@ public void testBug12229() throws Exception { assertTrue(this.rs.next()); } - /** - * Tests fix for BUG#12752 - Cp1251 incorrectly mapped to win1251 for servers newer than 4.0.x. - * - * @throws Exception - */ - @Test - public void testBug12752() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1251"); - getConnectionWithProps(props).close(); - } - /** * Tests fix for BUG#12753, sessionVariables=....=...., doesn't work as it's tokenized incorrectly. * @@ -1068,6 +831,8 @@ public void testBug12752() throws Exception { @Test public void testBug12753() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode=ansi"); Connection sessionConn = null; @@ -1100,6 +865,8 @@ public void testBug13048() throws Exception { System.setErr(new PrintStream(bOut)); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.maxQuerySizeToLog.getKeyName(), "2"); props.setProperty(PropertyKey.logger.getKeyName(), StandardLogger.class.getName()); @@ -1162,7 +929,10 @@ public void testBug13453() throws Exception { Connection encodedConn = null; try { - encodedConn = DriverManager.getConnection(urlBuf.toString(), null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + encodedConn = DriverManager.getConnection(urlBuf.toString(), props); this.rs = encodedConn.createStatement().executeQuery("SELECT @testBug13453"); assertTrue(this.rs.next()); @@ -1195,6 +965,8 @@ public void testBug15065() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), StandardLogger.class.getName()); @@ -1282,53 +1054,6 @@ public void testBug15065() throws Exception { } } - /** - * Tests fix for BUG#15544, no "dos" character set in MySQL > 4.1.0 - * - * @throws Exception - */ - @Test - public void testBug15544() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp437"); - Connection dosConn = null; - - try { - dosConn = getConnectionWithProps(props); - } finally { - if (dosConn != null) { - dosConn.close(); - } - } - } - - @Test - public void testCSC5765() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "utf8"); - props.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8_bin"); - - Connection utf8Conn = null; - - try { - utf8Conn = getConnectionWithProps(props); - this.rs = utf8Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_%'"); - while (this.rs.next()) { - System.out.println(this.rs.getString(1) + " = " + this.rs.getString(2)); - } - - this.rs = utf8Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'collation_%'"); - while (this.rs.next()) { - System.out.println(this.rs.getString(1) + " = " + this.rs.getString(2)); - } - } finally { - if (utf8Conn != null) { - utf8Conn.close(); - } - } - } - /** * Tests fix for BUG#15570 - ReplicationConnection incorrectly copies state, doesn't transfer connection context correctly when transitioning between the * same read-only states. @@ -1342,7 +1067,10 @@ public void testBug15570() throws Exception { Connection replConn = null; try { - replConn = getSourceReplicaReplicationConnection(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + replConn = getSourceReplicaReplicationConnection(props); boolean dbMapsToSchema = ((JdbcConnection) replConn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm) .getValue() == DatabaseTerm.SCHEMA; @@ -1403,6 +1131,8 @@ public void testBug15570() throws Exception { @Test public void testBug23281() throws Exception { Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "false"); props.setProperty(PropertyKey.failOverReadOnly.getKeyName(), "false"); props.setProperty(PropertyKey.connectTimeout.getKeyName(), "5000"); @@ -1448,14 +1178,8 @@ public void testBug23281() throws Exception { * @throws Exception */ @Test + @Disabled("'elideSetAutoCommits' feature was turned off due to Server Bug#66884. Turn this test back on as soon as the server bug is fixed. Consider making it version specific.") public void testBug24706() throws Exception { - // 'elideSetAutoCommits' feature was turned off due to Server Bug#66884. See also ConnectionPropertiesImpl#getElideSetAutoCommits(). - // TODO Turn this test back on as soon as the server bug is fixed. Consider making it version specific. - boolean ignoreTest = true; - if (ignoreTest) { - return; - } - Properties props = new Properties(); props.setProperty(PropertyKey.elideSetAutoCommits.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); @@ -1503,8 +1227,11 @@ public void testBug24706() throws Exception { */ @Test public void testBug25514() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (int i = 0; i < 10; i++) { - getConnectionWithProps((Properties) null).close(); + getConnectionWithProps(props).close(); } ThreadGroup root = Thread.currentThread().getThreadGroup().getParent(); @@ -1658,6 +1385,13 @@ private void testBug23626ForClass(Class clazz, List propertyNames) th */ @Test public void testBug25545() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + createProcedure("testBug25545", "() BEGIN SELECT 1; END"); String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; @@ -1671,8 +1405,7 @@ public void testBug25545() throws Exception { try { Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); sslConn = getConnectionWithProps(props); sslConn.prepareCall("{ call testBug25545()}").execute(); @@ -1692,12 +1425,20 @@ public void testBug25545() throws Exception { */ @Test public void testBug36948() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + Connection _conn = null; try { String hostSpec = getEncodedHostPortPairFromTestsuiteUrl(); Properties props = getHostFreePropertiesFromTestsuiteUrl(); String db = props.getProperty(PropertyKey.DBNAME.getKeyName(), "test"); + props.remove(PropertyKey.sslMode.getKeyName()); props.remove(PropertyKey.useSSL.getKeyName()); props.remove(PropertyKey.requireSSL.getKeyName()); props.remove(PropertyKey.verifyServerCertificate.getKeyName()); @@ -1705,7 +1446,7 @@ public void testBug36948() throws Exception { props.remove(PropertyKey.trustCertificateKeyStoreType.getKeyName()); props.remove(PropertyKey.trustCertificateKeyStorePassword.getKeyName()); - final String url = "jdbc:mysql://" + hostSpec + "/" + db + "?useSSL=true&requireSSL=true&verifyServerCertificate=true" + final String url = "jdbc:mysql://" + hostSpec + "/" + db + "?sslMode=REQUIRED&verifyServerCertificate=true" + "&trustCertificateKeyStoreUrl=file:src/test/config/ssl-test-certs/ca-truststore&trustCertificateKeyStoreType=JKS" + "&trustCertificateKeyStorePassword=password"; @@ -1725,6 +1466,8 @@ public void testBug36948() throws Exception { @Test public void testBug27655() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); BufferingLogger.startLoggingToBuffer(); @@ -1754,6 +1497,8 @@ public void testBug27655() throws Exception { @Test public void testFailoverReadOnly() throws Exception { Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.queriesBeforeRetrySource.getKeyName(), "0"); props.setProperty(PropertyKey.secondsBeforeRetrySource.getKeyName(), "0"); // +^ enable fall back to primary as soon as possible @@ -1825,19 +1570,18 @@ public void testPropertiesDescriptionsKeys() throws Exception { String description = dpi[i].description; String propertyName = dpi[i].name; - if (description.indexOf("Missing error message for key '") != -1 || description.startsWith("!")) { - fail("Missing message for configuration property " + propertyName); - } - - if (description.length() < 10) { - fail("Suspiciously short description for configuration property " + propertyName); - } + assertFalse(description.indexOf("Missing error message for key '") != -1 || description.startsWith("!"), + "Missing message for configuration property " + propertyName); + assertFalse(description.length() < 10, "Suspiciously short description for configuration property " + propertyName); } } @Test public void testBug29852() throws Exception { - Connection lbConn = getLoadBalancedConnection(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection lbConn = getLoadBalancedConnection(props); assertTrue(!lbConn.getClass().getName().startsWith("com.mysql.cj.jdbc")); lbConn.close(); } @@ -1853,7 +1597,10 @@ public void testBug29852() throws Exception { public void testBug22643() throws Exception { checkPingQuery(this.conn); - Connection replConnection = getSourceReplicaReplicationConnection(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection replConnection = getSourceReplicaReplicationConnection(props); try { checkPingQuery(replConnection); @@ -1863,7 +1610,7 @@ public void testBug22643() throws Exception { } } - Connection lbConn = getLoadBalancedConnection(); + Connection lbConn = getLoadBalancedConnection(props); try { checkPingQuery(lbConn); @@ -1906,6 +1653,8 @@ private void checkPingQuery(Connection c) throws SQLException { @Test public void testBug31053() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectTimeout.getKeyName(), "2000"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "random"); @@ -1921,6 +1670,8 @@ public void testBug31053() throws Exception { @Test public void testBug32877() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectTimeout.getKeyName(), "2000"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "bestResponseTime"); @@ -1945,7 +1696,12 @@ public void testBug32877() throws Exception { */ @Test public void testBug33734() throws Exception { - Connection testConn = getConnectionWithProps("cachePrepStmts=true,useServerPrepStmts=false"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); + Connection testConn = getConnectionWithProps(props); try { testConn.prepareStatement("SELECT 1"); } finally { @@ -1962,7 +1718,10 @@ public void testBug33734() throws Exception { public void testBug34703() throws Exception { Method isValid = java.sql.Connection.class.getMethod("isValid", new Class[] { Integer.TYPE }); - Connection newConn = getConnectionWithProps((Properties) null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection newConn = getConnectionWithProps(props); isValid.invoke(newConn, new Object[] { new Integer(1) }); Thread.sleep(2000); assertTrue(((Boolean) isValid.invoke(newConn, new Object[] { new Integer(0) })).booleanValue()); @@ -1990,6 +1749,8 @@ public void testBug34937() throws Exception { String url = urlBuf.toString(); url = "jdbc:mysql:replication:" + url.substring(url.indexOf("jdbc:mysql:") + "jdbc:mysql:".length()); ds.setURL(url); + ds.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); Connection replConn = ds.getPooledConnection().getConnection(); boolean readOnly = false; @@ -2006,8 +1767,11 @@ public void testBug34937() throws Exception { @Test public void testBug35660() throws Exception { - Connection lbConn = getLoadBalancedConnection(null); - Connection lbConn2 = getLoadBalancedConnection(null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection lbConn = getLoadBalancedConnection(props); + Connection lbConn2 = getLoadBalancedConnection(props); try { assertEquals(this.conn, this.conn); @@ -2023,34 +1787,35 @@ public void testBug35660() throws Exception { @Test public void testBug37570() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf-8"); props.setProperty(PropertyKey.passwordCharacterEncoding.getKeyName(), "utf-8"); - // TODO enable for usual connection? - Connection adminConn = getAdminConnectionWithProps(props); - - if (adminConn != null) { - - String unicodePassword = "\u0430\u0431\u0432"; // Cyrillic string - String user = "bug37570"; - Statement adminStmt = adminConn.createStatement(); + Connection con = getConnectionWithProps(props); + String unicodePassword = "\u0430\u0431\u0432"; // Cyrillic string + String user = "bug37570"; + Statement st = con.createStatement(); - adminStmt.executeUpdate("create user '" + user + "'@'127.0.0.1' identified by 'foo'"); - adminStmt.executeUpdate("grant usage on *.* to '" + user + "'@'127.0.0.1'"); - adminStmt.executeUpdate("update mysql.user set password=PASSWORD('" + unicodePassword + "') where user = '" + user + "'"); - adminStmt.executeUpdate("flush privileges"); + createUser(st, "'" + user + "'@'%'", "identified WITH mysql_native_password"); + st.executeUpdate("grant all on *.* to '" + user + "'@'%'"); + st.executeUpdate(versionMeetsMinimum(5, 7, 6) ? "ALTER USER '" + user + "'@'%' IDENTIFIED BY '" + unicodePassword + "'" + : "SET PASSWORD FOR '" + user + "'@'%' = PASSWORD('" + unicodePassword + "')"); + st.executeUpdate("flush privileges"); - try { - ((JdbcConnection) adminConn).changeUser(user, unicodePassword); - } catch (SQLException sqle) { - assertTrue(false, "Connection with non-latin1 password failed"); - } + try { + ((JdbcConnection) con).changeUser(user, unicodePassword); + } catch (SQLException sqle) { + sqle.printStackTrace(); + fail("Connection with non-latin1 password failed"); } } @Test public void testUnreliableSocketFactory() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "bestResponseTime"); Connection conn2 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); assertNotNull(this.conn, "Connection should not be null"); @@ -2073,6 +1838,8 @@ public void testReplicationConnectionGroupHostManagement() throws Exception { String replicationGroup1 = "rg1"; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.replicationConnectionGroup.getKeyName(), replicationGroup1); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "3"); props.setProperty(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "1"); @@ -2139,6 +1906,8 @@ public void testReplicationConnectionGroupHostManagement() throws Exception { @Test public void testReplicationConnectionHostManagement() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "3"); props.setProperty(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "1"); @@ -2230,6 +1999,8 @@ public void testReplicationConnectionHostManagement() throws Exception { @Test public void testReplicationConnectWithNoSource() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "3"); props.setProperty(PropertyKey.allowSourceDownConnections.getKeyName(), "true"); @@ -2262,6 +2033,8 @@ public void testReplicationConnectWithNoSource() throws Exception { @Test public void testReplicationConnectWithMultipleSources() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "3"); Set configs = new HashSet<>(); @@ -2284,6 +2057,8 @@ public void testReplicationConnectWithMultipleSources() throws Exception { @Test public void testReplicationConnectionMemory() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "3"); String replicationGroup = "memoryGroup"; props.setProperty(PropertyKey.replicationConnectionGroup.getKeyName(), replicationGroup); @@ -2328,6 +2103,8 @@ public void testReplicationConnectionMemory() throws Exception { @Test public void testReplicationJMXInterfaces() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "3"); String replicationGroup = "testReplicationJMXInterfaces"; props.setProperty(PropertyKey.replicationConnectionGroup.getKeyName(), replicationGroup); @@ -2402,36 +2179,42 @@ private ReplicationGroupManagerMBean getReplicationMBean() throws Exception { @Test public void testBug43421() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "bestResponseTime"); - Connection conn2 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); + final Connection conn2 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); conn2.createStatement().execute("SELECT 1"); conn2.createStatement().execute("SELECT 1"); // both connections are live now UnreliableSocketFactory.downHost("second"); UnreliableSocketFactory.downHost("first"); - try { + + assertThrows("Pings should not succeed when one host is down and using loadbalance w/o global blocklist.", SQLException.class, () -> { conn2.createStatement().execute("/* ping */"); - fail("Pings will not succeed when one host is down and using loadbalance w/o global blocklist."); - } catch (SQLException sqlEx) { - } + return null; + }); + + conn2.close(); UnreliableSocketFactory.flushAllStaticData(); props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.loadBalanceBlocklistTimeout.getKeyName(), "200"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "bestResponseTime"); - conn2 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); + Connection conn3 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); assertNotNull(this.conn, "Connection should not be null"); - conn2.createStatement().execute("SELECT 1"); - conn2.createStatement().execute("SELECT 1"); + conn3.createStatement().execute("SELECT 1"); + conn3.createStatement().execute("SELECT 1"); // both connections are live now UnreliableSocketFactory.downHost("second"); try { - conn2.createStatement().execute("/* ping */"); + conn3.createStatement().execute("/* ping */"); } catch (SQLException sqlEx) { fail("Pings should succeed even though host is down."); } @@ -2440,6 +2223,8 @@ public void testBug43421() throws Exception { @Test public void testBug48442() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "random"); Connection conn2 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); @@ -2559,7 +2344,11 @@ public void testBug46637() throws Exception { UnreliableSocketFactory.downHost(hostname); try { - Connection noConn = getConnectionWithProps("socketFactory=testsuite.UnreliableSocketFactory"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.socketFactory.getKeyName(), UnreliableSocketFactory.class.getName()); + Connection noConn = getConnectionWithProps(props); noConn.close(); } catch (SQLException sqlEx) { assertTrue(sqlEx.getMessage().indexOf("has not received") != -1); @@ -2616,9 +2405,13 @@ public void testBug46925() throws Exception { Xid txid = new MysqlXid(new byte[] { 0x1 }, new byte[] { 0xf }, 3306); + xads1.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + xads1.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); xads1.getProperty(PropertyKey.pinGlobalTxToPhysicalConnection).setValue(true); xads1.setUrl(dbUrl); + xads2.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + xads2.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); xads2.getProperty(PropertyKey.pinGlobalTxToPhysicalConnection).setValue(true); xads2.setUrl(dbUrl); @@ -2638,26 +2431,29 @@ public void testBug46925() throws Exception { @Test public void testBug47494() throws Exception { try { - getConnectionWithProps("jdbc:mysql://localhost:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory"); + getConnectionWithProps( + "jdbc:mysql://localhost:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory,sslMode=DISABLED,allowPublicKeyRetrieval=true"); } catch (SQLException sqlEx) { assertTrue(sqlEx.getCause() instanceof IOException); } try { - getConnectionWithProps("jdbc:mysql://:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory"); + getConnectionWithProps( + "jdbc:mysql://:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory,sslMode=DISABLED,allowPublicKeyRetrieval=true"); } catch (SQLException sqlEx) { assertTrue(sqlEx.getCause() instanceof IOException); } try { - getConnectionWithProps("jdbc:mysql://:9999,:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory"); + getConnectionWithProps( + "jdbc:mysql://:9999,:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory,sslMode=DISABLED,allowPublicKeyRetrieval=true"); } catch (SQLException sqlEx) { assertTrue(sqlEx.getCause() instanceof IOException); } try { getConnectionWithProps( - "jdbc:mysql://localhost:9999,localhost:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory"); + "jdbc:mysql://localhost:9999,localhost:9999/test?socketFactory=testsuite.regression.ConnectionRegressionTest$PortNumberSocketFactory,sslMode=DISABLED,allowPublicKeyRetrieval=true"); } catch (SQLException sqlEx) { assertTrue(sqlEx.getCause() instanceof IOException); } @@ -2692,6 +2488,8 @@ public void testBug48486() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setUrl(newUrl); + ds.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); Connection c = ds.getPooledConnection().getConnection(); this.rs = c.createStatement().executeQuery("SELECT 1"); @@ -2701,6 +2499,8 @@ public void testBug48486() throws Exception { @Test public void testBug48605() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "random"); props.setProperty(PropertyKey.selfDestructOnPingMaxOperations.getKeyName(), "5"); final Connection conn2 = this.getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); @@ -2739,7 +2539,11 @@ public Void call() throws Exception { @Test public void testBug49700() throws Exception { - Connection c = getConnectionWithProps("sessionVariables=@foo='bar'"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "@foo='bar'"); + Connection c = getConnectionWithProps(props); assertEquals("bar", getSingleIndexedValueWithQuery(c, 1, "SELECT @foo")); ((com.mysql.cj.jdbc.JdbcConnection) c).resetServerState(); assertEquals("bar", getSingleIndexedValueWithQuery(c, 1, "SELECT @foo")); @@ -2748,6 +2552,8 @@ public void testBug49700() throws Exception { @Test public void testBug51266() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Set downedHosts = new HashSet<>(); downedHosts.add("first"); @@ -2766,6 +2572,8 @@ public void testBug51266() throws Exception { @Test public void testBug51643() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), SequentialBalanceStrategy.class.getName()); Connection lbConn = getUnreliableLoadBalancedConnection(new String[] { "first", "second" }, props); @@ -2811,6 +2619,8 @@ public void testBug51643() throws Exception { @Test public void testBug51783() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), ForcedLoadBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalanceBlocklistTimeout.getKeyName(), "5000"); props.setProperty(PropertyKey.loadBalancePingTimeout.getKeyName(), "100"); @@ -2833,6 +2643,8 @@ public void testBug51783() throws Exception { conn2.close(); props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), ForcedLoadBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalanceBlocklistTimeout.getKeyName(), "5000"); props.setProperty(PropertyKey.loadBalancePingTimeout.getKeyName(), "100"); @@ -2887,6 +2699,8 @@ public com.mysql.cj.jdbc.ConnectionImpl pickConnection(InvocationHandler proxy, @Test public void testAutoCommitLB() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), CountingReBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalanceAutoCommitStatementThreshold.getKeyName(), "3"); @@ -2962,6 +2776,8 @@ public com.mysql.cj.jdbc.ConnectionImpl pickConnection(InvocationHandler proxy, @Test public void testBug56429() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory"); @@ -3014,10 +2830,9 @@ public void testBug56429() throws Exception { assert (endConnCount > 0); - if (endConnCount - startConnCount >= 20) { - // this may be bogus if run on a real system, we should probably look to see they're coming from this testsuite? - fail("We're leaking connections even when not failed over"); - } + assertFalse(endConnCount - startConnCount >= 20, + // this may be bogus if run on a real system, we should probably look to see they're coming from this testsuite? + "We're leaking connections even when not failed over"); } finally { if (failoverConnection != null) { failoverConnection.close(); @@ -3034,6 +2849,8 @@ public void testBug56955() throws Exception { @Test public void testBug58706() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory"); @@ -3092,7 +2909,12 @@ public void testBug58706() throws Exception { @Test public void testStatementComment() throws Exception { - Connection c = getConnectionWithProps("autoGenerateTestcaseScript=true,logger=StandardLogger"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.autoGenerateTestcaseScript.getKeyName(), "true"); + props.setProperty(PropertyKey.logger.getKeyName(), "StandardLogger"); + Connection c = getConnectionWithProps(props); PrintStream oldErr = System.err; try { @@ -3122,7 +2944,15 @@ public void testStatementComment() throws Exception { @Test public void testReconnectWithCachedConfig() throws Exception { - Connection rConn = getConnectionWithProps("autoReconnect=true,initialTimeout=2,maxReconnects=3,cacheServerConfiguration=true,elideSetAutoCommits=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); + props.setProperty(PropertyKey.initialTimeout.getKeyName(), "2"); + props.setProperty(PropertyKey.maxReconnects.getKeyName(), "3"); + props.setProperty(PropertyKey.cacheServerConfiguration.getKeyName(), "true"); + props.setProperty(PropertyKey.elideSetAutoCommits.getKeyName(), "true"); + Connection rConn = getConnectionWithProps(props); String threadId = getSingleIndexedValueWithQuery(rConn, 1, "select connection_id()").toString(); killConnection(this.conn, threadId); boolean detectedDeadConn = false; @@ -3139,14 +2969,15 @@ public void testReconnectWithCachedConfig() throws Exception { assertTrue(detectedDeadConn); rConn.prepareStatement("SELECT 1").execute(); - Connection rConn2 = getConnectionWithProps( - "autoReconnect=true,initialTimeout=2,maxReconnects=3,cacheServerConfiguration=true,elideSetAutoCommits=true"); + Connection rConn2 = getConnectionWithProps(props); rConn2.prepareStatement("SELECT 1").execute(); } @Test public void testBug61201() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "FOREIGN_KEY_CHECKS=0"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); @@ -3159,6 +2990,8 @@ public void testBug61201() throws Exception { @Test public void testChangeUser() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); Statement testStmt = testConn.createStatement(); @@ -3186,38 +3019,40 @@ public void testChangeUser() throws Exception { @Test public void testChangeUserNoDb() throws Exception { String databaseName = "testchangeusernodb"; - this.stmt.executeUpdate("DROP DATABASE IF EXISTS " + databaseName); - Properties props = getPropertiesFromTestsuiteUrl(); - props.setProperty(PropertyKey.createDatabaseIfNotExist.getKeyName(), "true"); - props.setProperty(PropertyKey.DBNAME.getKeyName(), databaseName); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + try { + Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.createDatabaseIfNotExist.getKeyName(), "true"); + props.setProperty(PropertyKey.DBNAME.getKeyName(), databaseName); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - Connection con = getConnectionWithProps(props); + Connection con = getConnectionWithProps(props); - this.rs = this.stmt.executeQuery("show databases like '" + databaseName + "'"); - if (this.rs.next()) { + this.rs = this.stmt.executeQuery("show databases like '" + databaseName + "'"); + assertTrue(this.rs.next(), "Database " + databaseName + " is not found."); assertEquals(databaseName, this.rs.getString(1)); - } else { - fail("Database " + databaseName + " is not found."); - } - ((com.mysql.cj.jdbc.JdbcConnection) con).changeUser(props.getProperty(PropertyKey.USER.getKeyName()), - props.getProperty(PropertyKey.PASSWORD.getKeyName())); + ((com.mysql.cj.jdbc.JdbcConnection) con).changeUser(props.getProperty(PropertyKey.USER.getKeyName()), + props.getProperty(PropertyKey.PASSWORD.getKeyName())); - this.rs = con.createStatement().executeQuery("select DATABASE()"); - assertTrue(this.rs.next()); - assertEquals(databaseName, this.rs.getString(1)); + this.rs = con.createStatement().executeQuery("select DATABASE()"); + assertTrue(this.rs.next()); + assertEquals(databaseName, this.rs.getString(1)); - con.close(); + con.close(); + } finally { + this.stmt.executeUpdate("DROP DATABASE IF EXISTS " + databaseName); + } } @Test public void testChangeUserClosedConn() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); - Connection newConn = getConnectionWithProps((Properties) null); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection newConn = getConnectionWithProps(props); try { newConn.close(); @@ -3237,6 +3072,8 @@ public void testChangeUserClosedConn() throws Exception { @Test public void testBug63284() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory"); @@ -3292,10 +3129,11 @@ public void testBug63284() throws Exception { @Test public void testDefaultPlugin() throws Exception { - if (!versionMeetsMinimum(5, 5, 7)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.5.7+ is required to run this test."); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), ""); assertThrows(SQLException.class, "Improper value \"\" for property 'defaultAuthenticationPlugin'\\.", () -> getConnectionWithProps(props)); @@ -3319,10 +3157,11 @@ public void testDefaultPlugin() throws Exception { @Test public void testDisabledPlugins() throws Exception { - if (!versionMeetsMinimum(5, 5, 7)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.5.7+ is required to run this test."); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); // Disable the built-in default authentication plugin, by name. props.setProperty(PropertyKey.disabledAuthenticationPlugins.getKeyName(), "mysql_native_password"); @@ -3369,9 +3208,8 @@ public void testDisabledPlugins() throws Exception { @Test public void testAuthTestPlugin() throws Exception { - if (!versionMeetsMinimum(5, 5, 7) || isSysPropDefined(PropertyDefinitions.SYSP_testsuite_no_server_testsuite)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.5.7+ is required to run this test."); + boolean installPluginInRuntime = false; try { // Install plugin if required. @@ -3384,8 +3222,17 @@ public void testAuthTestPlugin() throws Exception { } if (installPluginInRuntime) { - String ext = isServerRunningOnWindows() ? ".dll" : ".so"; - this.stmt.executeUpdate("INSTALL PLUGIN test_plugin_server SONAME 'auth_test_plugin" + ext + "'"); + try { + String ext = isServerRunningOnWindows() ? ".dll" : ".so"; + this.stmt.executeUpdate("INSTALL PLUGIN test_plugin_server SONAME 'auth_test_plugin" + ext + "'"); + } catch (SQLException e) { + if (e.getErrorCode() == MysqlErrorNumbers.ER_CANT_OPEN_LIBRARY) { + installPluginInRuntime = false; // to disable plugin deinstallation attempt in a finally block + assumeTrue(false, "This test requires the server installed with the test package."); + } else { + throw e; + } + } } String dbname = getPropertiesFromTestsuiteUrl().getProperty(PropertyKey.DBNAME.getKeyName()); @@ -3403,6 +3250,8 @@ public void testAuthTestPlugin() throws Exception { this.stmt.executeUpdate("FLUSH PRIVILEGES"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.USER.getKeyName(), "wl5851user1"); props.setProperty(PropertyKey.PASSWORD.getKeyName(), "wl5851user1prx"); props.setProperty(PropertyKey.authenticationPlugins.getKeyName(), AuthTestPlugin.class.getName()); @@ -3423,24 +3272,30 @@ public void testAuthTestPlugin() throws Exception { @Test public void testTwoQuestionsPlugin() throws Exception { - if (!versionMeetsMinimum(5, 5, 7) || isSysPropDefined(PropertyDefinitions.SYSP_testsuite_no_server_testsuite)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.5.7+ is required to run this test."); + boolean installPluginInRuntime = false; try { // Install plugin if required. this.rs = this.stmt.executeQuery("SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='two_questions'"); if (this.rs.next()) { - if (!this.rs.getString(1).equals("ACTIVE")) { - fail("The 'two_questions' plugin is preinstalled but not active."); - } + assumeTrue(this.rs.getString(1).equals("ACTIVE"), "The 'two_questions' plugin is preinstalled but not active."); } else { installPluginInRuntime = true; } if (installPluginInRuntime) { - String ext = isServerRunningOnWindows() ? ".dll" : ".so"; - this.stmt.executeUpdate("INSTALL PLUGIN two_questions SONAME 'auth" + ext + "'"); + try { + String ext = isServerRunningOnWindows() ? ".dll" : ".so"; + this.stmt.executeUpdate("INSTALL PLUGIN two_questions SONAME 'auth" + ext + "'"); + } catch (SQLException e) { + if (e.getErrorCode() == MysqlErrorNumbers.ER_CANT_OPEN_LIBRARY) { + installPluginInRuntime = false; // to disable plugin deinstallation attempt in a finally block + assumeTrue(false, "This test requires the server installed with the test package."); + } else { + throw e; + } + } } String dbname = getPropertiesFromTestsuiteUrl().getProperty(PropertyKey.DBNAME.getKeyName()); @@ -3455,6 +3310,8 @@ public void testTwoQuestionsPlugin() throws Exception { this.stmt.executeUpdate("FLUSH PRIVILEGES"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.USER.getKeyName(), "wl5851user2"); props.setProperty(PropertyKey.PASSWORD.getKeyName(), "two_questions_password"); props.setProperty(PropertyKey.authenticationPlugins.getKeyName(), TwoQuestionsPlugin.class.getName()); @@ -3474,24 +3331,30 @@ public void testTwoQuestionsPlugin() throws Exception { @Test public void testThreeAttemptsPlugin() throws Exception { - if (!versionMeetsMinimum(5, 5, 7) || isSysPropDefined(PropertyDefinitions.SYSP_testsuite_no_server_testsuite)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.5.7+ is required to run this test."); + boolean installPluginInRuntime = false; try { // Install plugin if required. this.rs = this.stmt.executeQuery("SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='three_attempts'"); if (this.rs.next()) { - if (!this.rs.getString(1).equals("ACTIVE")) { - fail("The 'three_attempts' plugin is preinstalled but not active."); - } + assumeTrue(this.rs.getString(1).equals("ACTIVE"), "The 'three_attempts' plugin is preinstalled but not active."); } else { installPluginInRuntime = true; } if (installPluginInRuntime) { - String ext = isServerRunningOnWindows() ? ".dll" : ".so"; - this.stmt.executeUpdate("INSTALL PLUGIN three_attempts SONAME 'auth" + ext + "'"); + try { + String ext = isServerRunningOnWindows() ? ".dll" : ".so"; + this.stmt.executeUpdate("INSTALL PLUGIN three_attempts SONAME 'auth" + ext + "'"); + } catch (SQLException e) { + if (e.getErrorCode() == MysqlErrorNumbers.ER_CANT_OPEN_LIBRARY) { + installPluginInRuntime = false; // to disable plugin deinstallation attempt in a finally block + assumeTrue(false, "This test requires the server installed with the test package."); + } else { + throw e; + } + } } String dbname = getPropertiesFromTestsuiteUrl().getProperty(PropertyKey.DBNAME.getKeyName()); @@ -3506,6 +3369,8 @@ public void testThreeAttemptsPlugin() throws Exception { this.stmt.executeUpdate("FLUSH PRIVILEGES"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.USER.getKeyName(), "wl5851user3"); props.setProperty(PropertyKey.PASSWORD.getKeyName(), "three_attempts_password"); props.setProperty(PropertyKey.authenticationPlugins.getKeyName(), ThreeAttemptsPlugin.class.getName()); @@ -3640,11 +3505,8 @@ public void reset() { @Test public void testOldPasswordPlugin() throws Exception { - if (!versionMeetsMinimum(5, 5, 7) || versionMeetsMinimum(5, 7, 5)) { - // As of 5.7.5, support for mysql_old_password is removed. - System.out.println("testOldPasswordPlugin was skipped: This test is only run for 5.5.7 - 5.7.4 server versions."); - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7) && !versionMeetsMinimum(5, 7, 5), + "testOldPasswordPlugin was skipped: This test only run for 5.5.7 - 5.7.4 server versions."); Connection testConn = null; try { @@ -3665,6 +3527,8 @@ public void testOldPasswordPlugin() throws Exception { this.stmt.executeUpdate("flush privileges"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); // connect with default plugin props.setProperty(PropertyKey.USER.getKeyName(), "bug64983user1"); @@ -3749,9 +3613,13 @@ public void testOldPasswordPlugin() throws Exception { @Test public void testAuthCleartextPlugin() throws Exception { - if (!versionMeetsMinimum(5, 5, 7) || isSysPropDefined(PropertyDefinitions.SYSP_testsuite_no_server_testsuite)) { - return; - } + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + boolean installPluginInRuntime = false; try { // Install plugin if required. @@ -3764,8 +3632,17 @@ public void testAuthCleartextPlugin() throws Exception { } if (installPluginInRuntime) { - String ext = isServerRunningOnWindows() ? ".dll" : ".so"; - this.stmt.executeUpdate("INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin" + ext + "'"); + try { + String ext = isServerRunningOnWindows() ? ".dll" : ".so"; + this.stmt.executeUpdate("INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin" + ext + "'"); + } catch (SQLException e) { + if (e.getErrorCode() == MysqlErrorNumbers.ER_CANT_OPEN_LIBRARY) { + installPluginInRuntime = false; // To disable plugin deinstallation attempt in the finally block. + assumeTrue(false, "This test requires a server installed with the test package."); + } else { + throw e; + } + } } String dbname = getPropertiesFromTestsuiteUrl().getProperty(PropertyKey.DBNAME.getKeyName()); @@ -3782,7 +3659,7 @@ public void testAuthCleartextPlugin() throws Exception { Properties props = new Properties(); props.setProperty(PropertyKey.USER.getKeyName(), "wl5735user"); props.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); assertThrows(SQLException.class, "SSL connection required for plugin \"mysql_clear_password\"\\. Check if 'sslMode' is enabled\\.", () -> getConnectionWithProps(props)); @@ -3790,7 +3667,7 @@ public void testAuthCleartextPlugin() throws Exception { String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; System.setProperty("javax.net.ssl.trustStore", trustStorePath); System.setProperty("javax.net.ssl.trustStorePassword", "password"); - props.setProperty(PropertyKey.sslMode.getKeyName(), "REQUIRED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); try (Connection testConn = getConnectionWithProps(props)) { assertTrue(((MysqlConnection) testConn).getSession().isSSLEstablished(), "SSL connection isn't actually established!"); @@ -3809,345 +3686,235 @@ public void testAuthCleartextPlugin() throws Exception { } /** - * This test requires two server instances: - * 1) main test server pointed by com.mysql.cj.testsuite.url variable configured without RSA encryption support (sha256_password_private_key_path, - * sha256_password_public_key_path, caching_sha2_password_private_key_path and caching_sha2_password_public_key_path config options are unset). - * 2) additional server instance pointed by com.mysql.cj.testsuite.url.openssl variable configured with default-authentication-plugin=sha256_password and - * RSA encryption enabled. - * - * To run this test please add this variable to ant call: - * -Dcom.mysql.cj.testsuite.url.openssl=jdbc:mysql://localhost:3307/test?user=root&password=pwd + * Test for sha256_password authentication. * * @throws Exception */ @Test public void testSha256PasswordPlugin() throws Exception { + assumeTrue(versionMeetsMinimum(5, 6, 5), "MySQL 5.6.5+ is required to run this test."); + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password plugin required to run this test"); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; System.setProperty("javax.net.ssl.trustStore", trustStorePath); System.setProperty("javax.net.ssl.trustStorePassword", "password"); - /* - * Test against server without RSA support. - */ - if (versionMeetsMinimum(5, 6, 5)) { - if (!pluginIsActive(this.stmt, "sha256_password")) { - fail("sha256_password required to run this test"); - } - + try { // newer GPL servers, like 8.0.4+, are using OpenSSL and can use RSA encryption, while old ones compiled with yaSSL cannot - boolean gplWithRSA = allowsRsa(this.stmt); + boolean withRSA = allowsRsa(this.stmt); + boolean withTestRsaKeys = supportsTestSha256PasswordKeys(this.stmt); + + // create user with long password and sha256_password auth + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); + } + createUser(this.stmt, "'wl5602user'@'%'", "IDENTIFIED WITH sha256_password"); + this.stmt.executeUpdate("GRANT ALL ON *.* TO 'wl5602user'@'%'"); + createUser(this.stmt, "'wl5602nopassword'@'%'", "identified WITH sha256_password"); + this.stmt.executeUpdate("GRANT ALL ON *.* TO 'wl5602nopassword'@'%'"); + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords= 2"); + this.stmt.executeUpdate("SET SESSION old_passwords= 2"); + } + this.stmt.executeUpdate(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl5602user'@'%' IDENTIFIED BY 'pwd'" + : "SET PASSWORD FOR 'wl5602user'@'%' = PASSWORD('pwd')"); + this.stmt.executeUpdate("FLUSH PRIVILEGES"); - try { - if (!versionMeetsMinimum(8, 0, 5)) { - this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); - } - createUser("'wl5602user'@'%'", "IDENTIFIED WITH sha256_password"); - this.stmt.executeUpdate("GRANT ALL ON *.* TO 'wl5602user'@'%'"); - createUser("'wl5602nopassword'@'%'", "IDENTIFIED WITH sha256_password"); - this.stmt.executeUpdate("GRANT ALL ON *.* TO 'wl5602nopassword'@'%'"); - if (!versionMeetsMinimum(8, 0, 5)) { - this.stmt.executeUpdate("SET GLOBAL old_passwords= 2"); - this.stmt.executeUpdate("SET SESSION old_passwords= 2"); - } - this.stmt.executeUpdate(versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl5602user'@'%' IDENTIFIED BY 'pwd'" - : "SET PASSWORD FOR 'wl5602user'@'%' = PASSWORD('pwd')"); - this.stmt.executeUpdate("FLUSH PRIVILEGES"); - - final Properties propsNoRetrieval = new Properties(); - propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl5602user"); - propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - final Properties propsNoRetrievalNoPassword = new Properties(); - propsNoRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl5602nopassword"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - final Properties propsAllowRetrieval = new Properties(); - propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl5602user"); - propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - final Properties propsAllowRetrievalNoPassword = new Properties(); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl5602nopassword"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - // 1. without SSL - // SQLException expected due to server doesn't recognize Public Key Retrieval packet - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", () -> getConnectionWithProps(propsNoRetrieval)); - - if (gplWithRSA) { - assertCurrentUser(null, propsAllowRetrieval, "wl5602user", false); - } else { - assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(propsAllowRetrieval)); - } - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 2. with serverRSAPublicKeyFile specified - // SQLException expected due to server doesn't recognize RSA encrypted payload - propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - - assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(propsNoRetrieval)); - assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(propsAllowRetrieval)); - - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 3. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - assertCurrentUser(null, propsNoRetrieval, "wl5602user", true); - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(null, propsAllowRetrieval, "wl5602user", true); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // over SSL with client-default Sha256PasswordPlugin - propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - propsNoRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - - assertCurrentUser(null, propsNoRetrieval, "wl5602user", true); - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(null, propsAllowRetrieval, "wl5602user", true); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + final Properties propsNoRetrieval = new Properties(); + propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl5602user"); + propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - } finally { - this.stmt.executeUpdate("FLUSH PRIVILEGES"); - if (!versionMeetsMinimum(8, 0, 5)) { - this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); - } - } - } + final Properties propsNoRetrievalNoPassword = new Properties(); + propsNoRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl5602nopassword"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - /* - * Test against server with RSA support. - */ - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 6, 5)) { + final Properties propsAllowRetrieval = new Properties(); + propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl5602user"); + propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); + propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - if (!pluginIsActive(this.sha256Stmt, "sha256_password")) { - fail("sha256_password required to run this test"); - } - if (!allowsRsa(this.sha256Stmt)) { - fail("RSA encryption must be enabled on " + sha256Url + " to run this test"); + final Properties propsAllowRetrievalNoPassword = new Properties(); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl5602nopassword"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + // 1. with client-default MysqlNativePasswordPlugin + propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); + propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); + + // 1.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + + assertThrows(SQLException.class, "Public Key Retrieval is not allowed", () -> getConnectionWithProps(dbUrl, propsNoRetrieval)); + + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl5602nopassword", false); + if (withRSA) { + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl5602user", false); + } else { + assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); + } + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + + // 1.2. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + assertCurrentUser(dbUrl, propsNoRetrieval, "wl5602user", true); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl5602nopassword", false); + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl5602user", true); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + + // 2. with client-default Sha256PasswordPlugin + propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); + propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); + + // 2.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + + assertThrows(SQLException.class, "Public Key Retrieval is not allowed", () -> getConnectionWithProps(dbUrl, propsNoRetrieval)); + + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl5602nopassword", false); + if (withRSA) { + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl5602user", false); + } else { + assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); + } + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + + // 2.2. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + assertCurrentUser(dbUrl, propsNoRetrieval, "wl5602user", true); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl5602nopassword", false); + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl5602user", false); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + + // 3. with serverRSAPublicKeyFile specified + propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + + // 3.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + + if (withRSA && withTestRsaKeys) { + assertCurrentUser(dbUrl, propsNoRetrieval, "wl5602user", false); + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl5602user", false); + } else { + assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(dbUrl, propsNoRetrieval)); + assertThrows(SQLException.class, "Access denied for user 'wl5602user'.*", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); } - try { - // create user with long password and sha256_password auth - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); - } - createUser(this.sha256Stmt, "'wl5602user'@'%'", "IDENTIFIED WITH sha256_password"); - this.sha256Stmt.executeUpdate("GRANT ALL ON *.* TO 'wl5602user'@'%'"); - createUser(this.sha256Stmt, "'wl5602nopassword'@'%'", "identified WITH sha256_password"); - this.sha256Stmt.executeUpdate("GRANT ALL ON *.* TO 'wl5602nopassword'@'%'"); - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords= 2"); - this.sha256Stmt.executeUpdate("SET SESSION old_passwords= 2"); - } - this.sha256Stmt.executeUpdate( - ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl5602user'@'%' IDENTIFIED BY 'pwd'" - : "SET PASSWORD FOR 'wl5602user'@'%' = PASSWORD('pwd')"); - this.sha256Stmt.executeUpdate("FLUSH PRIVILEGES"); - - final Properties propsNoRetrieval = new Properties(); - propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl5602user"); - propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - - final Properties propsNoRetrievalNoPassword = new Properties(); - propsNoRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl5602nopassword"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - - final Properties propsAllowRetrieval = new Properties(); - propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl5602user"); - propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - - final Properties propsAllowRetrievalNoPassword = new Properties(); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl5602nopassword"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - - // 1. with client-default MysqlNativePasswordPlugin - propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); - propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); - - // 1.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", () -> getConnectionWithProps(sha256Url, propsNoRetrieval)); - - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl5602user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 1.2. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - assertCurrentUser(sha256Url, propsNoRetrieval, "wl5602user", true); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl5602user", true); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 2. with client-default Sha256PasswordPlugin - propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - propsNoRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - - // 2.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", () -> getConnectionWithProps(sha256Url, propsNoRetrieval)); - - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl5602user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 2.2. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - assertCurrentUser(sha256Url, propsNoRetrieval, "wl5602user", true); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl5602user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 3. with serverRSAPublicKeyFile specified - propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - - // 3.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - assertCurrentUser(sha256Url, propsNoRetrieval, "wl5602user", false); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl5602user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 3.2. Runtime setServerRSAPublicKeyFile must be denied - final Connection c2 = getConnectionWithProps(sha256Url, propsNoRetrieval); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl5602nopassword", false); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + + // 3.2. Runtime setServerRSAPublicKeyFile must be denied + if (withRSA && withTestRsaKeys) { + final Connection c2 = getConnectionWithProps(dbUrl, propsNoRetrieval); assertThrows(PropertyNotModifiableException.class, "Dynamic change of ''serverRSAPublicKeyFile'' is not allowed.", () -> { ((JdbcConnection) c2).getPropertySet().getProperty(PropertyKey.serverRSAPublicKeyFile).setValue("src/test/config/ssl-test-certs/mykey.pub"); return null; }); c2.close(); + } - // 3.3. Runtime setAllowPublicKeyRetrieval must be denied - final Connection c3 = getConnectionWithProps(sha256Url, propsNoRetrieval); - assertThrows(PropertyNotModifiableException.class, "Dynamic change of ''allowPublicKeyRetrieval'' is not allowed.", () -> { - ((JdbcConnection) c3).getPropertySet().getProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); + // 3.4. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + assertCurrentUser(dbUrl, propsNoRetrieval, "wl5602user", true); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl5602nopassword", false); + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl5602user", true); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl5602nopassword", false); + + // 4. with wrong serverRSAPublicKeyFile specified + propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + + // 4.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", () -> getConnectionWithProps(dbUrl, propsNoRetrieval)); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", + () -> getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword)); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", + () -> getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword)); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsNoRetrieval)); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword)); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword)); + + // 4.2. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", () -> getConnectionWithProps(dbUrl, propsNoRetrieval)); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", + () -> getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword)); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", + () -> getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword)); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); return null; - }); - c3.close(); - - // 3.4. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - assertCurrentUser(sha256Url, propsNoRetrieval, "wl5602user", true); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl5602nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl5602user", true); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl5602nopassword", false); - - // 4. with wrong serverRSAPublicKeyFile specified - propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - - // 4.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsNoRetrieval)); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword)); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsAllowRetrieval)); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword)); - - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsNoRetrieval)); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword)); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsAllowRetrieval)); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword)); - - // 4.2. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsNoRetrieval)); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword)); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsAllowRetrieval)); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", - () -> getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword)); - - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword)); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsAllowRetrieval)); - assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword)); - - } finally { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); } + }); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword)); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsAllowRetrieval)); + assertThrows(SQLException.class, "Unable to read public key ", () -> getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword)); + + } finally { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); } } } @@ -4206,192 +3973,6 @@ public void testBug36662() throws Exception { } } - @Test - public void testBug37931() throws Exception { - Connection _conn = null; - Properties props = new Properties(); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "ISO88591"); - - try { - _conn = getConnectionWithProps(props); - assertTrue(false, "This point should not be reached."); - } catch (Exception e) { - assertEquals("Can't map ISO88591 given for characterSetResults to a supported MySQL encoding.", e.getMessage()); - } finally { - if (_conn != null) { - _conn.close(); - } - } - - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "null"); - - try { - _conn = getConnectionWithProps(props); - - Statement _stmt = _conn.createStatement(); - ResultSet _rs = _stmt.executeQuery("show variables where variable_name='character_set_results'"); - if (_rs.next()) { - String res = _rs.getString(2); - if (res == null || "NULL".equalsIgnoreCase(res) || res.length() == 0) { - assertTrue(true); - } else { - assertTrue(false); - } - } - } finally { - if (_conn != null) { - _conn.close(); - } - } - } - - @Test - public void testBug64205() throws Exception { - Properties props = getPropertiesFromTestsuiteUrl(); - String dbname = props.getProperty(PropertyKey.DBNAME.getKeyName()); - if (dbname == null) { - assertTrue(false, "No database selected"); - } - - props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP"); - - Connection testConn = null; - Statement testSt = null; - ResultSet testRs = null; - try { - testConn = getConnectionWithProps(props); - testSt = testConn.createStatement(); - testRs = testSt.executeQuery("SELECT * FROM `" + dbname + "`.`\u307b\u3052\u307b\u3052`"); - } catch (SQLException e1) { - if (e1.getClass().getName().endsWith("SQLSyntaxErrorException")) { - assertEquals("Table '" + dbname + ".\u307B\u3052\u307b\u3052' doesn't exist", e1.getMessage()); - } else if (e1.getErrorCode() == MysqlErrorNumbers.ER_FILE_NOT_FOUND) { - // this could happen on Windows with 5.5 and 5.6 servers where BUG#14642248 exists - assertTrue(e1.getMessage().contains("Can't find file")); - } else { - throw e1; - } - - testSt.close(); - testConn.close(); - - try { - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "SJIS"); - testConn = getConnectionWithProps(props); - testSt = testConn.createStatement(); - testSt.execute("SET lc_messages = 'ru_RU'"); - testRs = testSt.executeQuery("SELECT * FROM `" + dbname + "`.`\u307b\u3052\u307b\u3052`"); - } catch (SQLException e2) { - if (e2.getClass().getName().endsWith("SQLSyntaxErrorException")) { - assertEquals("\u0422\u0430\u0431\u043b\u0438\u0446\u0430 '" + dbname - + ".\u307b\u3052\u307b\u3052' \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442", e2.getMessage()); - } else if (e2.getErrorCode() == MysqlErrorNumbers.ER_FILE_NOT_FOUND) { - // this could happen on Windows with 5.5 and 5.6 servers where BUG#14642248 exists - assertTrue(e2.getMessage().indexOf("\u0444\u0430\u0439\u043b") > -1, - "File not found error message should be russian but is this one: " + e2.getMessage()); - } else { - throw e2; - } - } - - } finally { - if (testRs != null) { - testRs.close(); - } - if (testSt != null) { - testSt.close(); - } - if (testConn != null) { - testConn.close(); - } - } - - // also test with explicit characterSetResults and cacheServerConfiguration - try { - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "EUC_JP"); - props.setProperty(PropertyKey.cacheServerConfiguration.getKeyName(), "true"); - testConn = getConnectionWithProps(props); - testSt = testConn.createStatement(); - testRs = testSt.executeQuery("SELECT * FROM `" + dbname + "`.`\u307b\u3052\u307b\u3052`"); - fail("Exception should be thrown for attemping to query non-existing table"); - } catch (SQLException e1) { - if (e1.getClass().getName().endsWith("SQLSyntaxErrorException")) { - assertEquals("Table '" + dbname + ".\u307B\u3052\u307b\u3052' doesn't exist", e1.getMessage()); - } else if (e1.getErrorCode() == MysqlErrorNumbers.ER_FILE_NOT_FOUND) { - // this could happen on Windows with 5.5 and 5.6 servers where BUG#14642248 exists - assertTrue(e1.getMessage().contains("Can't find file")); - } else { - throw e1; - } - } finally { - testConn.close(); - } - props.remove(PropertyKey.cacheServerConfiguration.getKeyName()); - - // Error messages may also be received after the handshake but before connection initialization is complete. This tests the interpretation of - // errors thrown during this time window using a SatementInterceptor that throws an Exception while setting the session variables. - // Start by getting the Latin1 version of the error to compare later. - String latin1ErrorMsg = ""; - int latin1ErrorLen = 0; - try { - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Latin1"); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "Latin1"); - props.setProperty(PropertyKey.sessionVariables.getKeyName(), "lc_messages=ru_RU"); - props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug64205QueryInterceptor.class.getName()); - testConn = getConnectionWithProps(props); - fail("Exception should be trown for syntax error, caused by the exception interceptor"); - } catch (Exception e) { - latin1ErrorMsg = e.getMessage(); - latin1ErrorLen = latin1ErrorMsg.length(); - } - // Now compare with results when using a proper encoding. - try { - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP"); - props.setProperty(PropertyKey.characterSetResults.getKeyName(), "EUC_JP"); - props.setProperty(PropertyKey.sessionVariables.getKeyName(), "lc_messages=ru_RU"); - props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug64205QueryInterceptor.class.getName()); - testConn = getConnectionWithProps(props); - fail("Exception should be trown for syntax error, caused by the exception interceptor"); - } catch (SQLException e) { - // There should be the Russian version of this error message, correctly encoded. A mis-interpretation, e.g. decoding as latin1, would return a - // wrong message with the wrong size. - assertEquals(29 + dbname.length(), e.getMessage().length()); - assertFalse(latin1ErrorMsg.equals(e.getMessage())); - assertFalse(latin1ErrorLen == e.getMessage().length()); - } finally { - testConn.close(); - } - } - - public static class TestBug64205QueryInterceptor extends BaseQueryInterceptor { - private JdbcConnection connection; - - @Override - public QueryInterceptor init(MysqlConnection conn, Properties props, Log log) { - this.connection = (JdbcConnection) conn; - return super.init(conn, props, log); - } - - @Override - public M postProcess(M queryPacket, M originalResponsePacket) { - String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); - if (sql.contains("lc_messages=ru_RU")) { - try { - this.connection.createStatement() - .executeQuery("SELECT * FROM `" - + (this.connection.getPropertySet().getEnumProperty(PropertyKey.databaseTerm) - .getValue() == DatabaseTerm.SCHEMA ? this.connection.getSchema() : this.connection.getCatalog()) - + "`.`\u307b\u3052\u307b\u3052`"); - } catch (Exception e) { - throw ExceptionFactory.createException(e.getMessage(), e); - } - } - return originalResponsePacket; - } - } - @Test public void testIsLocal() throws Exception { boolean normalState = ((ConnectionImpl) this.conn).isServerLocal(); @@ -4399,7 +3980,7 @@ public void testIsLocal() throws Exception { if (normalState) { Properties props = new Properties(); props.setProperty(PropertyKey.socketFactory.getKeyName(), NonLocalSocketFactory.class.getName()); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); boolean isLocal = ((ConnectionImpl) getConnectionWithProps(props)).isServerLocal(); @@ -4418,6 +3999,8 @@ public void testBug57662() throws Exception { Connection conn_is = null; try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.useNanosForElapsedTime.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), TestBug57662Logger.class.getName()); @@ -4452,6 +4035,8 @@ protected String logInternal(int level, Object msg, Throwable exception) { @Test public void testBug14563127() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), ForcedLoadBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalanceBlocklistTimeout.getKeyName(), "5000"); props.setProperty(PropertyKey.loadBalancePingTimeout.getKeyName(), "100"); @@ -4498,11 +4083,12 @@ public void testBug14563127() throws Exception { */ @Test public void testBug11237() throws Exception { + assumeTrue(supportsLoadLocalInfile(this.stmt), "This test requires the server started with --local-infile=ON"); + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); this.rs.next(); - if (this.rs.getInt(2) < 4 + 1024 * 1024 * 16 - 1) { - fail("You need to increase max_allowed_packet to at least " + (4 + 1024 * 1024 * 16 - 1) + " before running this test!"); - } + long defaultMaxAllowedPacket = this.rs.getInt(2); + boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < 4 + 1024 * 1024 * 16 - 1; int requiredSize = 1024 * 1024 * 300; int fieldLength = 1023; @@ -4556,18 +4142,34 @@ public void testBug11237() throws Exception { fileNameBuf = new StringBuilder(testFile.getAbsolutePath()); } - Properties props = new Properties(); - props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); - props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); - Connection conn1 = getConnectionWithProps(props); - Statement stmt1 = conn1.createStatement(); + Connection conn1 = null; + try { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 17); + } + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); + props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); + conn1 = getConnectionWithProps(props); + Statement stmt1 = conn1.createStatement(); - int updateCount = stmt1.executeUpdate("LOAD DATA LOCAL INFILE '" + fileNameBuf.toString() + "' INTO TABLE testBug11237 CHARACTER SET " - + CharsetMapping.getMysqlCharsetForJavaEncoding( - ((MysqlConnection) this.conn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), - ((JdbcConnection) conn1).getServerVersion())); + int updateCount = stmt1.executeUpdate("LOAD DATA LOCAL INFILE '" + fileNameBuf.toString() + "' INTO TABLE testBug11237 CHARACTER SET " + + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding( + ((MysqlConnection) this.conn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), + ((JdbcConnection) conn1).getServerVersion())); - assertTrue(updateCount == loops); + assertTrue(updateCount == loops); + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket); + } + if (conn1 != null) { + conn1.close(); + } + } } @Test @@ -4583,9 +4185,8 @@ public void testStackOverflowOnMissingInterceptor() throws Exception { @Test public void testExpiredPassword() throws Exception { - if (!versionMeetsMinimum(5, 6, 10)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 10), "MySQL 5.6.10+ is required to run this test."); + Connection testConn = null; Statement testSt = null; ResultSet testRs = null; @@ -4600,7 +4201,7 @@ public void testExpiredPassword() throws Exception { createUser("'must_change2'@'%'", "IDENTIFIED BY 'aha'"); this.stmt.executeUpdate("grant all on `" + dbname + "`.* to 'must_change2'@'%'"); - // TODO workaround for Bug#77732, should be fixed in 5.7.9 + // workaround for Bug#77732 if (versionMeetsMinimum(5, 7, 6) && !versionMeetsMinimum(8, 0, 5)) { this.stmt.executeUpdate("GRANT SELECT ON `performance_schema`.`session_variables` TO 'must_change1'@'%'"); this.stmt.executeUpdate("GRANT SELECT ON `performance_schema`.`session_variables` TO 'must_change2'@'%'"); @@ -4610,6 +4211,8 @@ public void testExpiredPassword() throws Exception { : "ALTER USER 'must_change1'@'%' PASSWORD EXPIRE, 'must_change2'@'%' PASSWORD EXPIRE"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); // ALTER USER can be prepared as of 5.6.8 (BUG#14646014) if (versionMeetsMinimum(5, 6, 8)) { @@ -4737,28 +4340,31 @@ public void testExpiredPassword() throws Exception { } /** - * Tests connection attributes + * Tests fix for Bug#79612 (22362474), CONNECTION ATTRIBUTES LOST WHEN CONNECTING WITHOUT DEFAULT DATABASE. * * @throws Exception */ @Test - public void testConnectionAttributes() throws Exception { - if (versionMeetsMinimum(5, 6)) { - testConnectionAttributes(dbUrl); - } - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 6, 5)) { - testConnectionAttributes(sha256Url); - } + public void testBug79612() throws Exception { + assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 6, 5), "Requires MySQL 5.6.5+."); + testConnectionAttributes(getNoDbUrl(dbUrl), null); + + createDatabase("testBug79612"); + testConnectionAttributes(dbUrl, "testBug79612"); } @Test - private void testConnectionAttributes(String url) throws Exception { - if (!versionMeetsMinimum(5, 6)) { - return; - } + private void testConnectionAttributes(String url, String db) throws Exception { + assumeTrue(versionMeetsMinimum(5, 6), "MySQL 5.6+ is required to run this test."); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionAttributes.getKeyName(), "first:one,again:two"); props.setProperty(PropertyKey.USER.getKeyName(), getPropertiesFromTestsuiteUrl().getProperty(PropertyKey.USER.getKeyName())); + if (db != null) { + props.setProperty(PropertyKey.DBNAME.getKeyName(), db); + } Connection attConn = super.getConnectionWithProps(url, props); ResultSet rslt = attConn.createStatement() .executeQuery("SELECT * FROM performance_schema.session_connect_attrs WHERE processlist_id = CONNECTION_ID()"); @@ -4778,9 +4384,7 @@ private void testConnectionAttributes(String url) throws Exception { while (rslt.next()) { String key = rslt.getString(2); String val = rslt.getString(3); - if (!matchedCounts.containsKey(key)) { - fail("Unexpected connection attribute key: " + key); - } + assertTrue(matchedCounts.containsKey(key), "Unexpected connection attribute key: " + key); matchedCounts.put(key, matchedCounts.get(key) + 1); if (key.equals("_runtime_version")) { assertEquals(Constants.JVM_VERSION, val); @@ -4807,17 +4411,13 @@ private void testConnectionAttributes(String url) throws Exception { attConn.close(); for (String key : matchedCounts.keySet()) { - if (matchedCounts.get(key) != 1) { - fail("Incorrect number of entries for key \"" + key + "\": " + matchedCounts.get(key)); - } + assertTrue(matchedCounts.get(key) == 1, "Incorrect number of entries for key \"" + key + "\": " + matchedCounts.get(key)); } props.setProperty(PropertyKey.connectionAttributes.getKeyName(), "none"); attConn = super.getConnectionWithProps(url, props); rslt = attConn.createStatement().executeQuery("SELECT * FROM performance_schema.session_connect_attrs WHERE processlist_id = CONNECTION_ID()"); - if (rslt.next()) { - fail("Expected no connection attributes."); - } + assertFalse(rslt.next(), "Expected no connection attributes."); } /** @@ -4845,12 +4445,15 @@ public void testBug16224249() throws Exception { String loadbalanceUrl = String.format("jdbc:mysql:loadbalance://%s,%s/%s?%s", hostSpec, hostSpec, database, configs.toString()); String failoverUrl = String.format("jdbc:mysql://%s,%s/%s?%s", hostSpec, "127.0.0.1:" + port, database, configs.toString()); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - Connection[] loadbalancedconnection = new Connection[] { new NonRegisteringDriver().connect(loadbalanceUrl, null), - new NonRegisteringDriver().connect(loadbalanceUrl, null), new NonRegisteringDriver().connect(loadbalanceUrl, null) }; + Connection[] loadbalancedconnection = new Connection[] { new NonRegisteringDriver().connect(loadbalanceUrl, props2), + new NonRegisteringDriver().connect(loadbalanceUrl, props2), new NonRegisteringDriver().connect(loadbalanceUrl, props2) }; - Connection[] failoverconnection = new Connection[] { new NonRegisteringDriver().connect(failoverUrl, null), - new NonRegisteringDriver().connect(failoverUrl, null), new NonRegisteringDriver().connect(failoverUrl, null) }; + Connection[] failoverconnection = new Connection[] { new NonRegisteringDriver().connect(failoverUrl, props2), + new NonRegisteringDriver().connect(failoverUrl, props2), new NonRegisteringDriver().connect(failoverUrl, props2) }; // WebLogic-style test Class mysqlCls = null; @@ -4945,7 +4548,10 @@ public void testBug16224249() throws Exception { public void testBug68763() throws Exception { ReplicationConnection replConn = null; - replConn = (ReplicationConnection) getSourceReplicaReplicationConnection(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + replConn = (ReplicationConnection) getSourceReplicaReplicationConnection(props); replConn.setReadOnly(true); assertFalse(replConn.isSourceConnection(), "isSourceConnection() should be false for replica connection"); replConn.setReadOnly(false); @@ -4960,6 +4566,8 @@ public void testBug68763() throws Exception { @Test public void testBug68733() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), ForcedLoadBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalancePingTimeout.getKeyName(), "100"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); @@ -5029,83 +4637,79 @@ public void testBug68733() throws Exception { // leaving connection tied to replica2, bring replica2 down and replica1 up: UnreliableSocketFactory.downHost("replica2"); - try { + assertThrows("Expected failure because current replica connection is down.", SQLException.class, () -> { conn2.createStatement().execute("/* ping */ SELECT 1"); - fail("Expected failure because current replica connection is down."); - } catch (SQLException e) { - } + return null; + }); conn2.close(); ForcedLoadBalanceStrategy.forceFutureServer("replica1:" + portNumber, -1); UnreliableSocketFactory.flushAllStaticData(); - conn2 = this.getUnreliableReplicationConnection(new String[] { "source", "replica1", "replica2" }, props); - conn2.setAutoCommit(false); + ReplicationConnection conn3 = this.getUnreliableReplicationConnection(new String[] { "source", "replica1", "replica2" }, props); + conn3.setAutoCommit(false); // go to replicas: - conn2.setReadOnly(true); + conn3.setReadOnly(true); // on replica1 now: - conn2.commit(); + conn3.commit(); ForcedLoadBalanceStrategy.forceFutureServer("replica2:" + portNumber, -1); // on replica2 now: - conn2.commit(); + conn3.commit(); // disable source: UnreliableSocketFactory.downHost("source"); // ping should succeed, because we're still attached to replicas: - conn2.createStatement().execute("/* ping */ SELECT 1"); + conn3.createStatement().execute("/* ping */ SELECT 1"); // bring source back up: UnreliableSocketFactory.dontDownHost("source"); // get back to source, confirm it's recovered: - conn2.commit(); - conn2.createStatement().execute("/* ping */ SELECT 1"); + conn3.commit(); + conn3.createStatement().execute("/* ping */ SELECT 1"); try { - conn2.setReadOnly(false); + conn3.setReadOnly(false); } catch (SQLException e) { } - conn2.commit(); + conn3.commit(); // take down both replicas: UnreliableSocketFactory.downHost("replica1"); UnreliableSocketFactory.downHost("replica2"); - assertTrue(conn2.isSourceConnection()); + assertTrue(conn3.isSourceConnection()); // should succeed, as we're still on source: - conn2.createStatement().execute("/* ping */ SELECT 1"); + conn3.createStatement().execute("/* ping */ SELECT 1"); UnreliableSocketFactory.dontDownHost("replica1"); UnreliableSocketFactory.dontDownHost("replica2"); UnreliableSocketFactory.downHost("source"); - try { - conn2.createStatement().execute("/* ping */ SELECT 1"); - fail("should have failed because source is offline"); - } catch (SQLException e) { - - } + assertThrows("should have failed because source is offline", SQLException.class, () -> { + conn3.createStatement().execute("/* ping */ SELECT 1"); + return null; + }); UnreliableSocketFactory.dontDownHost("source"); - conn2.createStatement().execute("SELECT 1"); + conn3.createStatement().execute("SELECT 1"); // continue on replica2: - conn2.setReadOnly(true); + conn3.setReadOnly(true); // should succeed, as replica2 is up: - conn2.createStatement().execute("/* ping */ SELECT 1"); + conn3.createStatement().execute("/* ping */ SELECT 1"); UnreliableSocketFactory.downHost("replica2"); - try { - conn2.createStatement().execute("/* ping */ SELECT 1"); - fail("should have failed because replica2 is offline and the active chosen connection."); - } catch (SQLException e) { - } + assertThrows("should have failed because replica2 is offline and the active chosen connection.", SQLException.class, () -> { + conn3.createStatement().execute("/* ping */ SELECT 1"); + return null; + }); - conn2.close(); + conn3.close(); } protected int testServerPrepStmtDeadlockCounter = 0; @@ -5206,6 +4810,8 @@ public void testBug68400() throws Exception { this.stmt.executeUpdate("insert into testBug68400 values ('" + s1 + "')"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); props.setProperty(PropertyKey.connectionAttributes.getKeyName(), "testBug68400:true"); @@ -5360,6 +4966,8 @@ public void testBug17251955() throws Exception { String url = "jdbc:mysql://" + getEncodedHostPortPairFromTestsuiteUrl(); try { + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); c1 = getConnectionWithProps(props); st1 = c1.createStatement(); @@ -5368,6 +4976,8 @@ public void testBug17251955() throws Exception { st1.execute("grant all on `\u30C6\u30B9\u30C8\u30C6\u30B9\u30C8`.* to '\u30C6\u30B9\u30C8\u30C6\u30B9\u30C8'@'%'"); props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.USER.getKeyName(), "\u30C6\u30B9\u30C8\u30C6\u30B9\u30C8\u30C6\u30B9\u30C8"); props.setProperty(PropertyKey.PASSWORD.getKeyName(), "msandbox"); props.remove(PropertyKey.DBNAME.getKeyName()); @@ -5378,6 +4988,7 @@ public void testBug17251955() throws Exception { } catch (SQLException e) { assertFalse(e.getCause() instanceof java.lang.ArrayIndexOutOfBoundsException, "e.getCause() instanceof java.lang.ArrayIndexOutOfBoundsException"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); props.setProperty(PropertyKey.USER.getKeyName(), "\u30C6\u30B9\u30C8\u30C6\u30B9\u30C8"); c2 = DriverManager.getConnection(url + "/\u30C6\u30B9\u30C8\u30C6\u30B9\u30C8", props); this.rs = c2.createStatement().executeQuery("select 1"); @@ -5407,6 +5018,8 @@ public void testBug69506() throws Exception { MysqlXADataSource dataSource = new MysqlXADataSource(); dataSource.setUrl(dbUrl); + dataSource.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + dataSource.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); XAConnection testXAConn1 = dataSource.getXAConnection(); XAConnection testXAConn2 = dataSource.getXAConnection(); @@ -5438,7 +5051,11 @@ public void testBug69746() throws Exception { /* * Test explicit closes */ - testConnection = getConnectionWithProps("dontTrackOpenResources=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.dontTrackOpenResources.getKeyName(), "true"); + testConnection = getConnectionWithProps(props); testStatement = testConnection.createStatement(); testResultSet = testStatement.executeQuery("SELECT 1"); @@ -5471,7 +5088,7 @@ public void testBug69746() throws Exception { createProcedure("testBug69746_proc", "() BEGIN SELECT 1; SELECT 2; SELECT 3; END"); createTable("testBug69746_tbl", "(fld1 INT NOT NULL AUTO_INCREMENT, fld2 INT, PRIMARY KEY(fld1))"); - testConnection = getConnectionWithProps("dontTrackOpenResources=true"); + testConnection = getConnectionWithProps(props); testStatement = testConnection.createStatement(); testResultSet = testStatement.executeQuery("SELECT 1"); @@ -5543,89 +5160,88 @@ private boolean isResultSetClosedForTestBug69746(ResultSet resultSet) { } /** - * This test requires additional server instance configured withm default-authentication-plugin=sha256_password and RSA encryption enabled. - * - * To run this test please add this variable to ant call: - * -Dcom.mysql.cj.testsuite.url.openssl=jdbc:mysql://localhost:3307/test?user=root&password=pwd + * Test for sha256_password long data exchange. * * @throws Exception */ @Test public void testLongAuthResponsePayload() throws Exception { - NativeSession sha256Sess; - if (this.sha256Conn != null && (sha256Sess = (NativeSession) ((JdbcConnection) this.sha256Conn).getSession()).versionMeetsMinimum(5, 6, 6)) { - Properties props = new Properties(); - props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + NativeSession testSess; + assumeTrue((testSess = (NativeSession) ((MysqlConnection) this.conn).getSession()).versionMeetsMinimum(5, 6, 6), "Requires MySQL 5.6.6+."); + assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password required to run this test"); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + assumeTrue(supportsTestSha256PasswordKeys(this.stmt), + "This test requires the server configured with RSA keys from ConnectorJ/src/test/config/ssl-test-certs"); - // check that sha256_password plugin is available - if (!pluginIsActive(this.sha256Stmt, "sha256_password")) { - fail("sha256_password required to run this test"); - } + Properties props = new Properties(); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + try { + // create user with long password and sha256_password auth + String pwd = testSess.versionMeetsMinimum(8, 0, 4) || testSess.versionMeetsMinimum(5, 7, 21) && !testSess.versionMeetsMinimum(8, 0, 0) + || testSess.versionMeetsMinimum(5, 6, 39) && !testSess.versionMeetsMinimum(5, 7, 0) + ? "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" + : "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee" + + "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee" + + "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee"; + + if (!testSess.versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); + } + createUser(this.stmt, "'wl6134user'@'%'", "identified WITH sha256_password"); + this.stmt.executeUpdate("grant all on *.* to 'wl6134user'@'%'"); + if (!testSess.versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords= 2"); + this.stmt.executeUpdate("SET SESSION old_passwords= 2"); + } + this.stmt.executeUpdate( + ((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl6134user'@'%' IDENTIFIED BY '" + pwd + "'" + : "set password for 'wl6134user'@'%' = PASSWORD('" + pwd + "')"); + this.stmt.executeUpdate("flush privileges"); + + this.rs = this.stmt.executeQuery("SELECT plugin FROM mysql.user WHERE user='wl6134user'"); + assertTrue(this.rs.next()); + assumeTrue("sha256_password".equals(this.rs.getString(1)), "This test requires the server configured with default sha256_password plugin"); + + props.setProperty(PropertyKey.USER.getKeyName(), "wl6134user"); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), pwd); + props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + Connection testConn2 = null; try { - // create user with long password and sha256_password auth - String pwd = sha256Sess.versionMeetsMinimum(8, 0, 4) || sha256Sess.versionMeetsMinimum(5, 7, 21) && !sha256Sess.versionMeetsMinimum(8, 0, 0) - || sha256Sess.versionMeetsMinimum(5, 6, 39) && !sha256Sess.versionMeetsMinimum(5, 7, 0) - ? "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" - : "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee" - + "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee" - + "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeaaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee"; - - if (!sha256Sess.versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); - } - createUser(this.sha256Stmt, "'wl6134user'@'%'", "identified WITH sha256_password"); - this.sha256Stmt.executeUpdate("grant all on *.* to 'wl6134user'@'%'"); - if (!sha256Sess.versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords= 2"); - this.sha256Stmt.executeUpdate("SET SESSION old_passwords= 2"); + testConn2 = DriverManager.getConnection(dbUrl, props); + fail("SQLException expected due to password is too long for RSA encryption"); + } catch (Exception e) { + assertTrue(e.getMessage().startsWith("Data must not be longer than")); + } finally { + if (testConn2 != null) { + testConn2.close(); } - this.sha256Stmt.executeUpdate(((MysqlConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6) - ? "ALTER USER 'wl6134user'@'%' IDENTIFIED BY '" + pwd + "'" - : "set password for 'wl6134user'@'%' = PASSWORD('" + pwd + "')"); - this.sha256Stmt.executeUpdate("flush privileges"); + } - props.setProperty(PropertyKey.USER.getKeyName(), "wl6134user"); - props.setProperty(PropertyKey.PASSWORD.getKeyName(), pwd); - props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + try { + String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; + System.setProperty("javax.net.ssl.keyStore", trustStorePath); + System.setProperty("javax.net.ssl.keyStorePassword", "password"); + System.setProperty("javax.net.ssl.trustStore", trustStorePath); + System.setProperty("javax.net.ssl.trustStorePassword", "password"); - Connection testConn = null; - try { - testConn = DriverManager.getConnection(sha256Url, props); - fail("SQLException expected due to password is too long for RSA encryption"); - } catch (Exception e) { - assertTrue(e.getMessage().startsWith("Data must not be longer than")); - } finally { - if (testConn != null) { - testConn.close(); - } - } + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + assertCurrentUser(dbUrl, props, "wl6134user", true); - try { - String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; - System.setProperty("javax.net.ssl.keyStore", trustStorePath); - System.setProperty("javax.net.ssl.keyStorePassword", "password"); - System.setProperty("javax.net.ssl.trustStore", trustStorePath); - System.setProperty("javax.net.ssl.trustStorePassword", "password"); - - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); - assertCurrentUser(sha256Url, props, "wl6134user", true); - - } catch (Exception e) { - throw e; - } finally { - if (testConn != null) { - testConn.close(); - } - } + } catch (Exception e) { + throw e; } finally { - if (!sha256Sess.versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); + if (testConn2 != null) { + testConn2.close(); } } + } finally { + if (!testSess.versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); + } } } @@ -5641,11 +5257,17 @@ public void testBug69452() throws Exception { JdbcConnection connWithMemProps; long[] memMultiplier = new long[] { 1024, 1024 * 1024, 1024 * 1024 * 1024 }; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (int i = 0; i < testMemUnits.length; i++) { for (int j = 0; j < testMemUnits[i].length; j++) { // testing with memory values under 2GB because higher values aren't supported. - connWithMemProps = (com.mysql.cj.jdbc.JdbcConnection) getConnectionWithProps( - String.format("blobSendChunkSize=1.2%1$s,largeRowSizeThreshold=1.4%1$s,locatorFetchBufferSize=1.6%1$s", testMemUnits[i][j])); + props.setProperty(PropertyKey.blobSendChunkSize.getKeyName(), String.format("1.2%1$s", testMemUnits[i][j])); + props.setProperty(PropertyKey.largeRowSizeThreshold.getKeyName(), String.format("1.4%1$s", testMemUnits[i][j])); + props.setProperty(PropertyKey.locatorFetchBufferSize.getKeyName(), String.format("1.6%1$s", testMemUnits[i][j])); + connWithMemProps = (com.mysql.cj.jdbc.JdbcConnection) getConnectionWithProps(props); // test values of property 'blobSendChunkSize' assertEquals((int) (memMultiplier[i] * 1.2), @@ -5679,17 +5301,24 @@ public void testBug69452() throws Exception { public void testBug69777() throws Exception { final int maxPacketSizeThreshold = 8203; // ServerPreparedStatement.BLOB_STREAM_READ_BUF_SIZE + 11 + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + // test maxAllowedPacket below threshold and useServerPrepStmts=true + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "" + (maxPacketSizeThreshold - 1)); assertThrows(SQLException.class, "Connection setting too low for 'maxAllowedPacket'.*", new Callable() { public Void call() throws Exception { - getConnectionWithProps("useServerPrepStmts=true,maxAllowedPacket=" + (maxPacketSizeThreshold - 1)).close(); + getConnectionWithProps(props).close(); return null; } }); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "" + maxPacketSizeThreshold); assertThrows(SQLException.class, "Connection setting too low for 'maxAllowedPacket'.*", new Callable() { public Void call() throws Exception { - getConnectionWithProps("useServerPrepStmts=true,maxAllowedPacket=" + maxPacketSizeThreshold).close(); + getConnectionWithProps(props).close(); return null; } }); @@ -5697,16 +5326,21 @@ public Void call() throws Exception { // the following instructions should execute without any problem // test maxAllowedPacket above threshold and useServerPrepStmts=true - getConnectionWithProps("useServerPrepStmts=true,maxAllowedPacket=" + (maxPacketSizeThreshold + 1)).close(); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "" + (maxPacketSizeThreshold + 1)); + getConnectionWithProps(props).close(); // test maxAllowedPacket below threshold and useServerPrepStmts=false - getConnectionWithProps("useServerPrepStmts=false,maxAllowedPacket=" + (maxPacketSizeThreshold - 1)).close(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "" + (maxPacketSizeThreshold - 1)); + getConnectionWithProps(props).close(); // test maxAllowedPacket on threshold and useServerPrepStmts=false - getConnectionWithProps("useServerPrepStmts=false,maxAllowedPacket=" + maxPacketSizeThreshold).close(); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "" + maxPacketSizeThreshold); + getConnectionWithProps(props).close(); // test maxAllowedPacket above threshold and useServerPrepStmts=false - getConnectionWithProps("useServerPrepStmts=false,maxAllowedPacket=" + (maxPacketSizeThreshold + 1)).close(); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "" + (maxPacketSizeThreshold + 1)); + getConnectionWithProps(props).close(); } /** @@ -5816,58 +5450,16 @@ public Connection call() throws Exception { } } - /** - * Tests fix for Bug#71038, Add an option for custom collations detection - * - * @throws Exception - */ - @Test - public void testBug71038() throws Exception { - Properties p = new Properties(); - p.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "false"); - p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug71038QueryInterceptor.class.getName()); - - JdbcConnection c = (JdbcConnection) getConnectionWithProps(p); - Bug71038QueryInterceptor si = (Bug71038QueryInterceptor) c.getQueryInterceptorsInstances().get(0); - assertTrue(si.cnt == 0, "SHOW COLLATION was issued when detectCustomCollations=false"); - c.close(); - - p.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); - p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug71038QueryInterceptor.class.getName()); - - c = (JdbcConnection) getConnectionWithProps(p); - si = (Bug71038QueryInterceptor) c.getQueryInterceptorsInstances().get(0); - assertTrue(si.cnt > 0, "SHOW COLLATION wasn't issued when detectCustomCollations=true"); - c.close(); - } - - /** - * Counts the number of issued "SHOW COLLATION" statements. - */ - public static class Bug71038QueryInterceptor extends BaseQueryInterceptor { - int cnt = 0; - - @Override - public M preProcess(M queryPacket) { - String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); - if (sql.contains("SHOW COLLATION")) { - this.cnt++; - } - return null; - } - } - /** * Internal method for tests to get a replication connection with a * single source host to the test URL. * * @param sourceHost + * @param props * @return a replication connection * @throws Exception */ - private ReplicationConnection getTestReplicationConnectionNoReplicas(String sourceHost) throws Exception { - Properties props = getHostFreePropertiesFromTestsuiteUrl(); + private ReplicationConnection getTestReplicationConnectionNoReplicas(String sourceHost, Properties props) throws Exception { List sourceHosts = new ArrayList<>(); sourceHosts.add(mainConnectionUrl.getHostOrSpawnIsolated(sourceHost)); List replicaHosts = new ArrayList<>(); // empty @@ -5888,8 +5480,14 @@ private ReplicationConnection getTestReplicationConnectionNoReplicas(String sour @Test public void testReplicationConnectionNoReplicasRemainOnSource() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); String sourceHost = props.getProperty(PropertyKey.HOST.getKeyName()) + ":" + props.getProperty(PropertyKey.PORT.getKeyName()); - ReplicationConnection replConn = getTestReplicationConnectionNoReplicas(sourceHost); + + Properties props2 = getHostFreePropertiesFromTestsuiteUrl(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + ReplicationConnection replConn = getTestReplicationConnectionNoReplicas(sourceHost, props2); Statement s = replConn.createStatement(); ResultSet rs1 = s.executeQuery("select CONNECTION_ID()"); assertTrue(rs1.next()); @@ -5913,8 +5511,14 @@ public void testReplicationConnectionNoReplicasBasics() throws Exception { // create a replication connection with only a source, get the // connection id for later use Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); String sourceHost = props.getProperty(PropertyKey.HOST.getKeyName()) + ":" + props.getProperty(PropertyKey.PORT.getKeyName()); - ReplicationConnection replConn = getTestReplicationConnectionNoReplicas(sourceHost); + + Properties props2 = getHostFreePropertiesFromTestsuiteUrl(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + ReplicationConnection replConn = getTestReplicationConnectionNoReplicas(sourceHost, props2); replConn.setAutoCommit(false); Statement s = replConn.createStatement(); ResultSet rs1 = s.executeQuery("select CONNECTION_ID()"); @@ -5993,8 +5597,12 @@ public void testReplicationConnectionNoReplicasBasics() throws Exception { public void testBug71850() throws Exception { assertThrows(Exception.class, "ExceptionInterceptor.init\\(\\) called 1 time\\(s\\)", new Callable() { public Void call() throws Exception { - getConnectionWithProps( - "exceptionInterceptors=testsuite.regression.ConnectionRegressionTest$TestBug71850ExceptionInterceptor," + "user=unexistent_user"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.exceptionInterceptors.getKeyName(), TestBug71850ExceptionInterceptor.class.getName()); + props.setProperty(PropertyKey.USER.getKeyName(), "unexistent_user"); + getConnectionWithProps(props); return null; } }); @@ -6026,6 +5634,8 @@ public SQLException interceptException(Exception sqlEx) { public void testBug67803() throws Exception { MysqlXADataSource dataSource = new MysqlXADataSource(); dataSource.setUrl(dbUrl); + dataSource.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + dataSource.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); dataSource.getProperty(PropertyKey.useCursorFetch).setValue(true); dataSource.getProperty(PropertyKey.defaultFetchSize).setValue(50); dataSource.getProperty(PropertyKey.useServerPrepStmts).setValue(true); @@ -6057,46 +5667,6 @@ public SQLException interceptException(Exception sqlEx) { } } - /** - * Test for Bug#72712 - SET NAMES issued unnecessarily. - * - * Using a statement interceptor, ensure that SET NAMES is not called if the encoding requested by the client application matches that of - * character_set_server. - * - * Also test that character_set_results is not set unnecessarily. - * - * @throws Exception - */ - @Test - public void testBug72712() throws Exception { - // this test is only run when character_set_server=latin1 - if (!((MysqlConnection) this.conn).getSession().getServerSession().getServerVariable("character_set_server").equals("latin1")) { - return; - } - - Properties p = new Properties(); - p.setProperty(PropertyKey.characterEncoding.getKeyName(), "cp1252"); - p.setProperty(PropertyKey.characterSetResults.getKeyName(), "cp1252"); - p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug72712QueryInterceptor.class.getName()); - - getConnectionWithProps(p); - // exception will be thrown from the statement interceptor if any SET statements are issued - } - - /** - * Statement interceptor used to implement preceding test. - */ - public static class Bug72712QueryInterceptor extends BaseQueryInterceptor { - @Override - public T preProcess(Supplier str, Query interceptedQuery) { - String sql = str.get(); - if (sql.contains("SET NAMES") || sql.contains("character_set_results") && !(sql.contains("SHOW VARIABLES") || sql.contains("SELECT @@"))) { - throw ExceptionFactory.createException("Wrongt statement issued: " + sql); - } - return null; - } - } - /** * Test for Bug#62577 - XA connection fails with ClassCastException * @@ -6105,6 +5675,8 @@ public T preProcess(Supplier str, Query intercepte @Test public void testBug62577() throws Exception { Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); String hostSpec = getEncodedHostPortPairFromTestsuiteUrl(); String database = props.getProperty(PropertyKey.DBNAME.getKeyName()); props.remove(PropertyKey.DBNAME.getKeyName()); @@ -6145,94 +5717,87 @@ private void testBug62577TestUrl(String url) throws Exception { /** * Test fix for Bug#18869381 - CHANGEUSER() FOR SHA USER RESULTS IN NULLPOINTEREXCEPTION * - * This test requires additional server instance configured with default-authentication-plugin=sha256_password and RSA encryption enabled. - * - * To run this test please add this variable to ant call: - * -Dcom.mysql.cj.testsuite.url.openssl=jdbc:mysql://localhost:3307/test?user=root&password=pwd - * * @throws Exception */ @Test public void testBug18869381() throws Exception { - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 6, 6)) { - - if (!pluginIsActive(this.sha256Stmt, "sha256_password")) { - fail("sha256_password required to run this test"); - } + assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 6, 6), "Requires MySQL 5.6.6+."); + assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password plugin required to run this test"); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); - try { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); - } - createUser(this.sha256Stmt, "'bug18869381user1'@'%'", "identified WITH sha256_password"); - this.sha256Stmt.executeUpdate("grant all on *.* to 'bug18869381user1'@'%'"); - createUser(this.sha256Stmt, "'bug18869381user2'@'%'", "identified WITH sha256_password"); - this.sha256Stmt.executeUpdate("grant all on *.* to 'bug18869381user2'@'%'"); - createUser(this.sha256Stmt, "'bug18869381user3'@'%'", "identified WITH mysql_native_password"); - this.sha256Stmt.executeUpdate("grant all on *.* to 'bug18869381user3'@'%'"); - this.sha256Stmt.executeUpdate( - ((MysqlConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'bug18869381user3'@'%' IDENTIFIED BY 'pwd3'" - : "set password for 'bug18869381user3'@'%' = PASSWORD('pwd3')"); - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords= 2"); - this.sha256Stmt.executeUpdate("SET SESSION old_passwords= 2"); - } - this.sha256Stmt.executeUpdate(((MysqlConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6) - ? "ALTER USER 'bug18869381user1'@'%' IDENTIFIED BY 'LongLongLongLongLongLongLongLongLongLongLongLongPwd1'" - : "set password for 'bug18869381user1'@'%' = PASSWORD('LongLongLongLongLongLongLongLongLongLongLongLongPwd1')"); - this.sha256Stmt.executeUpdate( - ((MysqlConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'bug18869381user2'@'%' IDENTIFIED BY 'pwd2'" - : "set password for 'bug18869381user2'@'%' = PASSWORD('pwd2')"); - this.sha256Stmt.executeUpdate("flush privileges"); + try { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); + } + createUser(this.stmt, "'bug18869381user1'@'%'", "identified WITH sha256_password"); + this.stmt.executeUpdate("grant all on *.* to 'bug18869381user1'@'%'"); + createUser(this.stmt, "'bug18869381user2'@'%'", "identified WITH sha256_password"); + this.stmt.executeUpdate("grant all on *.* to 'bug18869381user2'@'%'"); + createUser(this.stmt, "'bug18869381user3'@'%'", "identified WITH mysql_native_password"); + this.stmt.executeUpdate("grant all on *.* to 'bug18869381user3'@'%'"); + this.stmt.executeUpdate( + ((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'bug18869381user3'@'%' IDENTIFIED BY 'pwd3'" + : "set password for 'bug18869381user3'@'%' = PASSWORD('pwd3')"); + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords= 2"); + this.stmt.executeUpdate("SET SESSION old_passwords= 2"); + } + this.stmt.executeUpdate(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6) + ? "ALTER USER 'bug18869381user1'@'%' IDENTIFIED BY 'LongLongLongLongLongLongLongLongLongLongLongLongPwd1'" + : "set password for 'bug18869381user1'@'%' = PASSWORD('LongLongLongLongLongLongLongLongLongLongLongLongPwd1')"); + this.stmt.executeUpdate( + ((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'bug18869381user2'@'%' IDENTIFIED BY 'pwd2'" + : "set password for 'bug18869381user2'@'%' = PASSWORD('pwd2')"); + this.stmt.executeUpdate("flush privileges"); - Properties props = new Properties(); - props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); - props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); - testBug18869381WithProperties(props); - props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); - testBug18869381WithProperties(props); + props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); + props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); + testBug18869381WithProperties(dbUrl, props); + props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); + testBug18869381WithProperties(dbUrl, props); - props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); - props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); - testBug18869381WithProperties(props); - props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); - testBug18869381WithProperties(props); + props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), Sha256PasswordPlugin.class.getName()); + props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); + testBug18869381WithProperties(dbUrl, props); + props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); + testBug18869381WithProperties(dbUrl, props); - props.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); - testBug18869381WithProperties(props); - props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); - testBug18869381WithProperties(props); + props.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); + testBug18869381WithProperties(dbUrl, props); + props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); + testBug18869381WithProperties(dbUrl, props); - String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; - System.setProperty("javax.net.ssl.keyStore", trustStorePath); - System.setProperty("javax.net.ssl.keyStorePassword", "password"); - System.setProperty("javax.net.ssl.trustStore", trustStorePath); - System.setProperty("javax.net.ssl.trustStorePassword", "password"); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); - testBug18869381WithProperties(props); - props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); - testBug18869381WithProperties(props); + String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; + System.setProperty("javax.net.ssl.keyStore", trustStorePath); + System.setProperty("javax.net.ssl.keyStorePassword", "password"); + System.setProperty("javax.net.ssl.trustStore", trustStorePath); + System.setProperty("javax.net.ssl.trustStorePassword", "password"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); + testBug18869381WithProperties(dbUrl, props); + props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); + testBug18869381WithProperties(dbUrl, props); - } finally { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); - } + } finally { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); } } } @Test - private void testBug18869381WithProperties(Properties props) throws Exception { + private void testBug18869381WithProperties(String url, Properties props) throws Exception { Connection testConn = null; Statement testSt = null; ResultSet testRs = null; try { - testConn = getConnectionWithProps(sha256Url, props); + testConn = getConnectionWithProps(url, props); ((JdbcConnection) testConn).changeUser("bug18869381user1", "LongLongLongLongLongLongLongLongLongLongLongLongPwd1"); testSt = testConn.createStatement(); @@ -6271,17 +5836,23 @@ private void testBug18869381WithProperties(Properties props) throws Exception { */ @Test public void testBug73053() throws Exception { + assumeFalse(isServerRunningOnWindows(), "This test requires the server running on Linux."); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); /* * Test reported issue using a Socket implementation that simulates the buggy behavior. */ try { - Connection testConn = getConnectionWithProps( - "sslMode=DISABLED,socketFactory=testsuite.regression.ConnectionRegressionTest$TestBug73053SocketFactory"); + props.setProperty(PropertyKey.socketFactory.getKeyName(), TestBug73053SocketFactory.class.getName()); + Connection testConn = getConnectionWithProps(props); Statement testStmt = testConn.createStatement(); this.rs = testStmt.executeQuery("SELECT 1"); testStmt.close(); testConn.close(); } catch (SQLException e) { + e.printStackTrace(); fail("No SQLException should be thrown."); } @@ -6291,7 +5862,8 @@ public void testBug73053() throws Exception { * the statement, thus calling MysqlIO.clearInputStream() and effectively discard unread data. */ try { - Connection testConn = getConnectionWithProps("allowMultiQueries=true"); + props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); Statement testStmt = testConn.createStatement(); testStmt.setFetchSize(Integer.MIN_VALUE); // set for streaming results @@ -6322,7 +5894,11 @@ public void testBug73053() throws Exception { public void run() { try { // set socketTimeout so this thread doesn't hang if no exception is thrown after killing the connection at server side - Connection testConn = getConnectionWithProps("socketTimeout=" + timeout); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props2.setProperty(PropertyKey.socketTimeout.getKeyName(), "" + timeout); + Connection testConn = getConnectionWithProps(props2); Statement testStmt = testConn.createStatement(); try { testStmt.execute(query); @@ -6358,9 +5934,7 @@ public void run() { elapsedTime = System.currentTimeMillis() - timestamp; // allow it 10% more time to reach the socketTimeout threshold - if (elapsedTime > timeout * 1.1) { - fail("Failed killing the connection at server side."); - } + assertFalse(elapsedTime > timeout * 1.1, "Failed killing the connection at server side."); } } catch (SQLException e) { fail("No SQLException should be thrown."); @@ -6694,9 +6268,8 @@ public String toString() { */ @Test public void testBug19354014() throws Exception { - if (!versionMeetsMinimum(5, 5, 7)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.7.7+ is required to run this test."); + Connection con = null; createUser("'bug19354014user'@'%'", "identified WITH mysql_native_password"); this.stmt.executeUpdate("grant all on *.* to 'bug19354014user'@'%'"); @@ -6706,6 +6279,8 @@ public void testBug19354014() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); con = getConnectionWithProps(props); @@ -6727,6 +6302,8 @@ public void testBug19354014() throws Exception { @Test public void testBug75168() throws Exception { final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.loadBalanceExceptionChecker.getKeyName(), Bug75168LoadBalanceExceptionChecker.class.getName()); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug75168QueryInterceptor.class.getName()); @@ -6797,9 +6374,7 @@ public QueryInterceptor init(MysqlConnection conn, Properties props, Log log) { @Override public void destroy() { this.connection = null; - if (previousConnection == null) { - fail("Test testBug75168 didn't run as expected."); - } + assertNotNull(previousConnection, "Test testBug75168 didn't run as expected."); } @Override @@ -6924,9 +6499,13 @@ private void testBug71084AssertCase(Properties connProps, String clientTZ, Strin */ @Test public void testBug20685022() throws Exception { - if (!isCommunityEdition()) { - return; - } + assumeTrue(isCommunityEdition(), "Commercial server version is required to run this test."); + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); final Properties props = new Properties(); @@ -6934,9 +6513,7 @@ public void testBug20685022() throws Exception { * case 1: non verifying server certificate */ props.clear(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); getConnectionWithProps(props); @@ -6944,9 +6521,7 @@ public void testBug20685022() throws Exception { * case 2: verifying server certificate using key store provided by connection properties */ props.clear(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.VERIFY_CA.name()); props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore"); props.setProperty(PropertyKey.trustCertificateKeyStoreType.getKeyName(), "JKS"); props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password"); @@ -6957,9 +6532,7 @@ public void testBug20685022() throws Exception { * case 3: verifying server certificate using key store provided by system properties */ props.clear(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.VERIFY_CA.name()); String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; System.setProperty("javax.net.ssl.keyStore", trustStorePath); @@ -6979,13 +6552,18 @@ public void testBug20685022() throws Exception { public void testBug75592() throws Exception { if (versionMeetsMinimum(5, 0, 3)) { - JdbcConnection con = (JdbcConnection) getConnectionWithProps("queryInterceptors=" + Bug75592QueryInterceptor.class.getName()); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug75592QueryInterceptor.class.getName()); + JdbcConnection con = (JdbcConnection) getConnectionWithProps(props); // reference values Map serverVariables = new HashMap<>(); this.rs = con.createStatement().executeQuery("SHOW VARIABLES"); while (this.rs.next()) { - serverVariables.put(this.rs.getString(1), this.rs.getString(2)); + String val = this.rs.getString(2); + serverVariables.put(this.rs.getString(1), "utf8mb3".equals(val) ? "utf8" : val); } // fix the renaming of "tx_isolation" to "transaction_isolation" that is made in NativeSession.loadServerVariables(). @@ -6996,15 +6574,17 @@ public void testBug75592() throws Exception { // check values from "select @@var..." assertEquals(serverVariables.get("auto_increment_increment"), session.getServerSession().getServerVariable("auto_increment_increment")); - assertEquals(serverVariables.get("character_set_client"), session.getServerSession().getServerVariable("character_set_client")); - assertEquals(serverVariables.get("character_set_connection"), session.getServerSession().getServerVariable("character_set_connection")); + assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_CLIENT), + session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_CLIENT)); + assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_CONNECTION), + session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_CONNECTION)); // we override character_set_results sometimes when configuring client charsets, thus need to check against actual value - if (session.getServerSession().getServerVariable(ServerSession.LOCAL_CHARACTER_SET_RESULTS) == null) { - assertEquals("", serverVariables.get("character_set_results")); + if (session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_RESULTS) == null) { + assertEquals("", serverVariables.get(CharsetSettings.CHARACTER_SET_RESULTS)); } else { - assertEquals(serverVariables.get("character_set_results"), - session.getServerSession().getServerVariable(ServerSession.LOCAL_CHARACTER_SET_RESULTS)); + assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_RESULTS), + session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_RESULTS)); } assertEquals(serverVariables.get("character_set_server"), session.getServerSession().getServerVariable("character_set_server")); @@ -7061,12 +6641,16 @@ public void testBug62452() throws Exception { MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource(); pds.setUrl(dbUrl); + pds.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + pds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); con = pds.getPooledConnection(); assertTrue(con instanceof MysqlPooledConnection); testBug62452WithConnection(con); MysqlXADataSource xads = new MysqlXADataSource(); xads.setUrl(dbUrl); + xads.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + xads.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); xads.getProperty(PropertyKey.pinGlobalTxToPhysicalConnection).setValue(false); con = xads.getXAConnection(); @@ -7092,138 +6676,130 @@ private void testBug62452WithConnection(PooledConnection con) throws Exception { /** * Tests fix for BUG#20825727 - CONNECT FAILURE WHEN TRY TO CONNECT SHA USER WITH DIFFERENT CHARSET. * - * This test runs through all authentication plugins when one of the following server requirements is met: - * 1. Default connection string points to a server configured with both SSL *and* RSA encryption. - * or - * 2. Default connection string points to a server configured with SSL enabled but no RSA encryption *and* the property - * com.mysql.cj.testsuite.url.openssl points to an additional server configured with - * default-authentication-plugin=sha256_password and RSA encryption. - * - * If none of the servers has SSL and RSA encryption enabled then only 'mysql_native_password' and 'mysql_old_password' plugins are tested. - * * @throws Exception */ @Test public void testBug20825727() throws Exception { - if (!versionMeetsMinimum(5, 5, 7) || isSysPropDefined(PropertyDefinitions.SYSP_testsuite_no_server_testsuite)) { - return; - } + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); - final String[] testDbUrls; Properties props = new Properties(); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - if (this.sha256Conn != null && ((MysqlConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 5, 7)) { - testDbUrls = new String[] { BaseTestCase.dbUrl, sha256Url }; - } else { - testDbUrls = new String[] { BaseTestCase.dbUrl }; - } + JdbcConnection testConn = (JdbcConnection) getConnectionWithProps(dbUrl, props); + Statement testStmt = testConn.createStatement(); - for (String testDbUrl : testDbUrls) { - JdbcConnection testConn = (JdbcConnection) getConnectionWithProps(testDbUrl, props); - Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT @@GLOBAL.HAVE_SSL = 'YES' AS have_ssl"); + final boolean sslEnabled = this.rs.next() && this.rs.getBoolean(1); - this.rs = testStmt.executeQuery("SELECT @@GLOBAL.HAVE_SSL = 'YES' AS have_ssl"); - final boolean sslEnabled = this.rs.next() && this.rs.getBoolean(1); + this.rs = testStmt.executeQuery("SHOW STATUS LIKE '%Rsa_public_key%'"); + final boolean rsaEnabled = this.rs.next() && this.rs.getString(1).length() > 0; - this.rs = testStmt.executeQuery("SHOW STATUS LIKE '%Rsa_public_key%'"); - final boolean rsaEnabled = this.rs.next() && this.rs.getString(1).length() > 0; + System.out.println(); + System.out.println("* Testing URL: " + dbUrl + " [SSL enabled: " + sslEnabled + "] [RSA enabled: " + rsaEnabled + "]"); + System.out.println("******************************************************************************************************************************" + + "*************"); + System.out.printf("%-25s : %-25s : %s : %-25s : %-18s : %-18s [%s]%n", "Connection Type", "Auth. Plugin", "pwd ", "Encoding Prop.", "Encoding Value", + "Server Encoding", "TstRes"); + System.out.println("------------------------------------------------------------------------------------------------------------------------------" + + "-------------"); - System.out.println(); - System.out.println("* Testing URL: " + testDbUrl + " [SSL enabled: " + sslEnabled + "] [RSA enabled: " + rsaEnabled + "]"); - System.out.println("******************************************************************************************************************************" - + "*************"); - System.out.printf("%-25s : %-25s : %s : %-25s : %-18s : %-18s [%s]%n", "Connection Type", "Auth. Plugin", "pwd ", "Encoding Prop.", - "Encoding Value", "Server Encoding", "TstRes"); - System.out.println("------------------------------------------------------------------------------------------------------------------------------" - + "-------------"); - - boolean clearTextPluginInstalled = false; - boolean secureAuthChanged = false; - try { - String[] plugins; + boolean clearTextPluginInstalled = false; + boolean secureAuthChanged = false; + try { + String[] plugins; + + // install cleartext plugin if required + this.rs = testStmt.executeQuery( + "SELECT (PLUGIN_LIBRARY LIKE 'auth_test_plugin%') FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='cleartext_plugin_server'"); + if (!this.rs.next() || !this.rs.getBoolean(1)) { + String ext = System.getProperty(PropertyDefinitions.SYSP_os_name).toUpperCase().indexOf("WINDOWS") > -1 ? ".dll" : ".so"; - // install cleartext plugin if required - this.rs = testStmt.executeQuery( - "SELECT (PLUGIN_LIBRARY LIKE 'auth_test_plugin%') FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='cleartext_plugin_server'"); - if (!this.rs.next() || !this.rs.getBoolean(1)) { - String ext = System.getProperty(PropertyDefinitions.SYSP_os_name).toUpperCase().indexOf("WINDOWS") > -1 ? ".dll" : ".so"; + try { testStmt.execute("INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin" + ext + "'"); - clearTextPluginInstalled = true; + } catch (SQLException e) { + if (e.getErrorCode() == MysqlErrorNumbers.ER_CANT_OPEN_LIBRARY) { + assumeTrue(false, "This test requires a server installed with the test package."); + } else { + throw e; + } } - if (testConn.getSession().versionMeetsMinimum(5, 7, 5)) { - // mysql_old_password plugin not supported - plugins = new String[] { "cleartext_plugin_server,-1", "mysql_native_password,0", "sha256_password,2" }; - } else if (testConn.getSession().versionMeetsMinimum(5, 6, 6)) { - plugins = new String[] { "cleartext_plugin_server,-1", "mysql_native_password,0", "mysql_old_password,1", "sha256_password,2" }; + clearTextPluginInstalled = true; + } - // temporarily disable --secure-auth mode to allow old format passwords - testStmt.executeUpdate("SET @current_secure_auth = @@global.secure_auth"); - testStmt.executeUpdate("SET @@global.secure_auth = off"); - secureAuthChanged = true; - } else { - // sha256_password plugin not supported - plugins = new String[] { "cleartext_plugin_server,-1", "mysql_native_password,0", "mysql_old_password,1" }; - } + if (testConn.getSession().versionMeetsMinimum(5, 7, 5)) { + // mysql_old_password plugin not supported + plugins = new String[] { "cleartext_plugin_server,-1", "mysql_native_password,0", "sha256_password,2" }; + } else if (testConn.getSession().versionMeetsMinimum(5, 6, 6)) { + plugins = new String[] { "cleartext_plugin_server,-1", "mysql_native_password,0", "mysql_old_password,1", "sha256_password,2" }; + + // temporarily disable --secure-auth mode to allow old format passwords + testStmt.executeUpdate("SET @current_secure_auth = @@global.secure_auth"); + testStmt.executeUpdate("SET @@global.secure_auth = off"); + secureAuthChanged = true; + } else { + // sha256_password plugin not supported + plugins = new String[] { "cleartext_plugin_server,-1", "mysql_native_password,0", "mysql_old_password,1" }; + } - final String simplePwd = "my\tpass word"; - final String complexPwd = "my\tp\u00e4ss w\u263ard"; + final String simplePwd = "my\tpass word"; + final String complexPwd = "my\tp\u00e4ss w\u263ard"; - for (String encoding : new String[] { "", "UTF-8", "ISO-8859-1", "US-ASCII" }) { - for (String plugin : plugins) { + for (String encoding : new String[] { "", "UTF-8", "ISO-8859-1", "US-ASCII" }) { + for (String plugin : plugins) { - String pluginName = plugin.split(",")[0]; - int pwdHashingMethod = Integer.parseInt(plugin.split(",")[1]); + String pluginName = plugin.split(",")[0]; + int pwdHashingMethod = Integer.parseInt(plugin.split(",")[1]); - String testStep = ""; + String testStep = ""; + try { + testStep = "create user"; + testBug20825727CreateUser(dbUrl, "testBug20825727", simplePwd, pluginName, pwdHashingMethod); + testStep = "login with simple password"; + testBug20825727TestLogin(dbUrl, testConn.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), sslEnabled, + rsaEnabled, "testBug20825727", simplePwd, encoding, pluginName); + + testStep = "change password"; + testBug20825727ChangePassword(dbUrl, "testBug20825727", complexPwd, pluginName, pwdHashingMethod); + testStep = "login with complex password"; + testBug20825727TestLogin(dbUrl, testConn.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), sslEnabled, + rsaEnabled, "testBug20825727", complexPwd, encoding, pluginName); + } catch (SQLException e) { + e.printStackTrace(); + fail("Failed at '" + testStep + "' using encoding '" + encoding + "' and plugin '" + pluginName + + "'. See also system output for more details."); + } finally { try { - testStep = "create user"; - testBug20825727CreateUser(testDbUrl, "testBug20825727", simplePwd, encoding, pluginName, pwdHashingMethod); - testStep = "login with simple password"; - testBug20825727TestLogin(testDbUrl, testConn.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), - sslEnabled, rsaEnabled, "testBug20825727", simplePwd, encoding, pluginName); - - testStep = "change password"; - testBug20825727ChangePassword(testDbUrl, "testBug20825727", complexPwd, encoding, pluginName, pwdHashingMethod); - testStep = "login with complex password"; - testBug20825727TestLogin(testDbUrl, testConn.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), - sslEnabled, rsaEnabled, "testBug20825727", complexPwd, encoding, pluginName); - } catch (SQLException e) { - e.printStackTrace(); - fail("Failed at '" + testStep + "' using encoding '" + encoding + "' and plugin '" + pluginName - + "'. See also system output for more details."); - } finally { - try { - dropUser(testStmt, "'testBug20825727'@'%'"); - } catch (Exception e) { - } + dropUser(testStmt, "'testBug20825727'@'%'"); + } catch (Exception e) { } } } - } finally { - if (clearTextPluginInstalled) { - testStmt.executeUpdate("UNINSTALL PLUGIN cleartext_plugin_server"); - } - if (secureAuthChanged) { - testStmt.executeUpdate("SET @@global.secure_auth = @current_secure_auth"); - } - - testStmt.close(); - testConn.close(); } + } finally { + if (clearTextPluginInstalled) { + testStmt.executeUpdate("UNINSTALL PLUGIN cleartext_plugin_server"); + } + if (secureAuthChanged) { + testStmt.executeUpdate("SET @@global.secure_auth = @current_secure_auth"); + } + + testStmt.close(); + testConn.close(); } } - private void testBug20825727CreateUser(String testDbUrl, String user, String password, String encoding, String pluginName, int pwdHashingMethod) - throws SQLException { + private void testBug20825727CreateUser(String testDbUrl, String user, String password, String pluginName, int pwdHashingMethod) throws SQLException { JdbcConnection testConn = null; try { Properties props = new Properties(); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - if (encoding.length() > 0) { - props.setProperty(PropertyKey.characterEncoding.getKeyName(), encoding); - } + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); testConn = (JdbcConnection) getConnectionWithProps(testDbUrl, props); Statement testStmt = testConn.createStatement(); @@ -7252,15 +6828,12 @@ private void testBug20825727CreateUser(String testDbUrl, String user, String pas } } - private void testBug20825727ChangePassword(String testDbUrl, String user, String password, String encoding, String pluginName, int pwdHashingMethod) - throws SQLException { + private void testBug20825727ChangePassword(String testDbUrl, String user, String password, String pluginName, int pwdHashingMethod) throws SQLException { JdbcConnection testConn = null; try { Properties props = new Properties(); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - if (encoding.length() > 0) { - props.setProperty(PropertyKey.characterEncoding.getKeyName(), encoding); - } + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); testConn = (JdbcConnection) getConnectionWithProps(testDbUrl, props); Statement testStmt = testConn.createStatement(); @@ -7317,8 +6890,7 @@ private void testBug20825727TestLogin(final String testDbUrl, String defaultServ continue; } props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); testCaseMsg = "Non-SSL/Non-RSA"; break; @@ -7330,16 +6902,13 @@ private void testBug20825727TestLogin(final String testDbUrl, String defaultServ continue; } props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "false"); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); testCaseMsg = "SSL"; break; case 3: /* * Test with an RSA encryption enabled connection, using public key retrieved from server. - * Requires additional server instance pointed by 'com.mysql.cj.testsuite.url.openssl'. * Can't be used with plugin 'cleartext_plugin_server'. */ if (pluginName.equals("cleartext_plugin_server") || !rsaEnabled) { @@ -7352,7 +6921,6 @@ private void testBug20825727TestLogin(final String testDbUrl, String defaultServ case 4: /* * Test with an RSA encryption enabled connection, using public key pointed by the property 'serverRSAPublicKeyFile'. - * Requires additional server instance pointed by 'com.mysql.cj.testsuite.url.openssl'. * Can't be used with plugin 'cleartext_plugin_server'. */ if (pluginName.equals("cleartext_plugin_server") || !rsaEnabled) { @@ -7366,13 +6934,8 @@ private void testBug20825727TestLogin(final String testDbUrl, String defaultServ boolean testShouldPass = true; if (pwdIsComplex) { - // if no encoding is specifically defined then our default password encoding ('UTF-8') and server's encoding must coincide - testShouldPass = encoding.length() > 0 || defaultServerEncoding.equalsIgnoreCase("UTF-8"); - - if (!testBaseConn.getSession().versionMeetsMinimum(5, 7, 6) && pluginName.equals("cleartext_plugin_server")) { - // 'cleartext_plugin_server' from servers below version 5.7.6 requires UTF-8 encoding - testShouldPass = encoding.equals("UTF-8") || (encoding.length() == 0 && defaultServerEncoding.equals("UTF-8")); - } + // if no encoding is specifically defined then our default password encoding is set to server's encoding + testShouldPass = encoding.equals("UTF-8") || (encoding.length() == 0 && defaultServerEncoding.equals("UTF-8")); } System.out.printf("%-25s : %-25s : %s : %-25s : %-18s : %-18s [%s]%n", testCaseMsg, pluginName, pwdIsComplex ? "cplx" : "smpl", encProp, @@ -7386,9 +6949,8 @@ private void testBug20825727TestLogin(final String testDbUrl, String defaultServ this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); assertTrue(this.rs.next()); - if (!this.rs.getString(1).startsWith(user) || !this.rs.getString(2).startsWith(user)) { - fail("Unexpected failure in test case '" + testCaseMsg + "' using encoding '" + encoding + "' in property '" + encProp + "'."); - } + assertFalse(!this.rs.getString(1).startsWith(user) || !this.rs.getString(2).startsWith(user), + "Unexpected failure in test case '" + testCaseMsg + "' using encoding '" + encoding + "' in property '" + encProp + "'."); this.rs.close(); testStmt.close(); } else { @@ -7417,151 +6979,150 @@ public Void call() throws Exception { /** * Tests fix for BUG#75670 - Connection fails with "Public Key Retrieval is not allowed" for native auth. * - * Requires additional server instance pointed by com.mysql.cj.testsuite.url.openssl variable configured with default-authentication-plugin=sha256_password - * and RSA encryption enabled. + * Requires the server to be configured with default-authentication-plugin=sha256_password and RSA encryption enabled. * * @throws Exception */ @Test public void testBug75670() throws Exception { - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 6, 6)) { + assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 6, 6), "Requires MySQL 5.6.6+."); + assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password plugin required to run this test"); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + assumeTrue(supportsTestSha256PasswordKeys(this.stmt), + "This test requires the server configured with RSA keys from ConnectorJ/src/test/config/ssl-test-certs"); - if (!pluginIsActive(this.sha256Stmt, "sha256_password")) { - fail("sha256_password required to run this test"); + try { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); } - try { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); + createUser(this.stmt, "'bug75670user'@'%'", ""); // let --default-authentication-plugin option force sha256_password + this.rs = this.stmt.executeQuery("SELECT plugin FROM mysql.user WHERE user='bug75670user'"); + assertTrue(this.rs.next()); + assumeTrue("sha256_password".equals(this.rs.getString(1)), "This test requires the server configured with default sha256_password plugin"); + + if (((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6)) { + createUser(this.stmt, "'bug75670user_mnp'@'%'", "IDENTIFIED WITH mysql_native_password BY 'bug75670user_mnp'"); + createUser(this.stmt, "'bug75670user_sha'@'%'", "IDENTIFIED WITH sha256_password BY 'bug75670user_sha'"); + } else { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.execute("SET @@session.old_passwords = 0"); } + createUser(this.stmt, "'bug75670user_mnp'@'%'", "IDENTIFIED WITH mysql_native_password"); + this.stmt.execute("SET PASSWORD FOR 'bug75670user_mnp'@'%' = PASSWORD('bug75670user_mnp')"); + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.execute("SET @@session.old_passwords = 2"); + } + createUser(this.stmt, "'bug75670user_sha'@'%'", "IDENTIFIED WITH sha256_password"); + this.stmt.execute("SET PASSWORD FOR 'bug75670user_sha'@'%' = PASSWORD('bug75670user_sha')"); + } + this.stmt.execute("GRANT ALL ON *.* TO 'bug75670user_mnp'@'%'"); + this.stmt.execute("GRANT ALL ON *.* TO 'bug75670user_sha'@'%'"); - createUser(this.sha256Stmt, "'bug75670user'@'%'", ""); // let --default-authentication-plugin option force sha256_password - this.rs = this.sha256Stmt.executeQuery("SELECT plugin FROM mysql.user WHERE user='bug75670user'"); - assertTrue(this.rs.next()); - assertEquals("sha256_password", this.rs.getString(1), "Wrong default authentication plugin (check test conditions):"); + System.out.println(); + System.out.printf("%-25s : %-18s : %-25s : %-25s : %s%n", "DefAuthPlugin", "AllowPubKeyRet", "User", "Passwd", "Test result"); + System.out.println( + "----------------------------------------------------------------------------------------------------" + "------------------------------"); + + for (Class defAuthPlugin : new Class[] { MysqlNativePasswordPlugin.class, Sha256PasswordPlugin.class }) { + for (String user : new String[] { "bug75670user_mnp", "bug75670user_sha" }) { + for (String pwd : new String[] { user, "wrong*pwd", "" }) { + for (boolean allowPubKeyRetrieval : new boolean[] { true, false }) { + final Connection testConn; + Statement testStmt; + + boolean expectedPubKeyRetrievalFail = (user.endsWith("_sha") + || user.endsWith("_mnp") && defAuthPlugin.equals(Sha256PasswordPlugin.class)) && !allowPubKeyRetrieval && pwd.length() > 0; + boolean expectedAccessDeniedFail = !user.equals(pwd); + System.out.printf("%-25s : %-18s : %-25s : %-25s : %s%n", defAuthPlugin.getSimpleName(), allowPubKeyRetrieval, user, pwd, + expectedPubKeyRetrievalFail ? "Fail [Pub. Key retrieval]" : expectedAccessDeniedFail ? "Fail [Access denied]" : "Ok"); + + final Properties props = new Properties(); + props.setProperty(PropertyKey.USER.getKeyName(), user); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), pwd); + props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), defAuthPlugin.getName()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), Boolean.toString(allowPubKeyRetrieval)); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + + if (expectedPubKeyRetrievalFail) { + // connection will fail due to public key retrieval failure + assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, props); + return null; + } + }); + + } else if (expectedAccessDeniedFail) { + // connection will fail due to wrong password + assertThrows(SQLException.class, "Access denied for user '" + user + "'@.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, props); + return null; + } + }); - if (((MysqlConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6)) { - createUser(this.sha256Stmt, "'bug75670user_mnp'@'%'", "IDENTIFIED WITH mysql_native_password BY 'bug75670user_mnp'"); - createUser(this.sha256Stmt, "'bug75670user_sha'@'%'", "IDENTIFIED WITH sha256_password BY 'bug75670user_sha'"); - } else { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.execute("SET @@session.old_passwords = 0"); - } - createUser(this.sha256Stmt, "'bug75670user_mnp'@'%'", "IDENTIFIED WITH mysql_native_password"); - this.sha256Stmt.execute("SET PASSWORD FOR 'bug75670user_mnp'@'%' = PASSWORD('bug75670user_mnp')"); - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.execute("SET @@session.old_passwords = 2"); - } - createUser(this.sha256Stmt, "'bug75670user_sha'@'%'", "IDENTIFIED WITH sha256_password"); - this.sha256Stmt.execute("SET PASSWORD FOR 'bug75670user_sha'@'%' = PASSWORD('bug75670user_sha')"); - } - this.sha256Stmt.execute("GRANT ALL ON *.* TO 'bug75670user_mnp'@'%'"); - this.sha256Stmt.execute("GRANT ALL ON *.* TO 'bug75670user_sha'@'%'"); + } else { + // connection will succeed + testConn = getConnectionWithProps(dbUrl, props); + testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(1).startsWith(user)); + assertTrue(this.rs.getString(2).startsWith(user)); + this.rs.close(); + testStmt.close(); + + // change user using same credentials will succeed + System.out.printf("%25s : %-18s : %-25s : %-25s : %s%n", "| ChangeUser (same)", allowPubKeyRetrieval, user, pwd, "Ok"); + ((JdbcConnection) testConn).changeUser(user, user); + testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(1).startsWith(user)); + assertTrue(this.rs.getString(2).startsWith(user)); + this.rs.close(); + testStmt.close(); - System.out.println(); - System.out.printf("%-25s : %-18s : %-25s : %-25s : %s%n", "DefAuthPlugin", "AllowPubKeyRet", "User", "Passwd", "Test result"); - System.out.println("----------------------------------------------------------------------------------------------------" - + "------------------------------"); - - for (Class defAuthPlugin : new Class[] { MysqlNativePasswordPlugin.class, Sha256PasswordPlugin.class }) { - for (String user : new String[] { "bug75670user_mnp", "bug75670user_sha" }) { - for (String pwd : new String[] { user, "wrong*pwd", "" }) { - for (boolean allowPubKeyRetrieval : new boolean[] { true, false }) { - final Connection testConn; - Statement testStmt; - - boolean expectedPubKeyRetrievalFail = (user.endsWith("_sha") - || user.endsWith("_mnp") && defAuthPlugin.equals(Sha256PasswordPlugin.class)) && !allowPubKeyRetrieval - && pwd.length() > 0; - boolean expectedAccessDeniedFail = !user.equals(pwd); - System.out.printf("%-25s : %-18s : %-25s : %-25s : %s%n", defAuthPlugin.getSimpleName(), allowPubKeyRetrieval, user, pwd, - expectedPubKeyRetrievalFail ? "Fail [Pub. Key retrieval]" : expectedAccessDeniedFail ? "Fail [Access denied]" : "Ok"); - - final Properties props = new Properties(); - props.setProperty(PropertyKey.USER.getKeyName(), user); - props.setProperty(PropertyKey.PASSWORD.getKeyName(), pwd); - props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), defAuthPlugin.getName()); - props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), Boolean.toString(allowPubKeyRetrieval)); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + // change user using different credentials + final String swapUser = user.indexOf("_sha") == -1 ? "bug75670user_sha" : "bug75670user_mnp"; + expectedPubKeyRetrievalFail = swapUser.endsWith("_sha") && !allowPubKeyRetrieval + || swapUser.endsWith("_mnp") && defAuthPlugin.equals(Sha256PasswordPlugin.class) && !allowPubKeyRetrieval; + + System.out.printf("%25s : %-18s : %-25s : %-25s : %s%n", "| ChangeUser (diff)", allowPubKeyRetrieval, swapUser, swapUser, + expectedPubKeyRetrievalFail ? "Fail [Pub. Key retrieval]" : "Ok"); if (expectedPubKeyRetrievalFail) { - // connection will fail due to public key retrieval failure + // change user will fail due to public key retrieval failure assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, props); - return null; - } - }); - - } else if (expectedAccessDeniedFail) { - // connection will fail due to wrong password - assertThrows(SQLException.class, "Access denied for user '" + user + "'@.*", new Callable() { - @SuppressWarnings("synthetic-access") public Void call() throws Exception { - getConnectionWithProps(sha256Url, props); + ((JdbcConnection) testConn).changeUser(swapUser, swapUser); return null; } }); - } else { - // connection will succeed - testConn = getConnectionWithProps(sha256Url, props); - testStmt = testConn.createStatement(); - this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); - assertTrue(this.rs.next()); - assertTrue(this.rs.getString(1).startsWith(user)); - assertTrue(this.rs.getString(2).startsWith(user)); - this.rs.close(); - testStmt.close(); - - // change user using same credentials will succeed - System.out.printf("%25s : %-18s : %-25s : %-25s : %s%n", "| ChangeUser (same)", allowPubKeyRetrieval, user, pwd, "Ok"); - ((JdbcConnection) testConn).changeUser(user, user); + // change user will succeed + ((JdbcConnection) testConn).changeUser(swapUser, swapUser); testStmt = testConn.createStatement(); this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); assertTrue(this.rs.next()); - assertTrue(this.rs.getString(1).startsWith(user)); - assertTrue(this.rs.getString(2).startsWith(user)); + assertTrue(this.rs.getString(1).startsWith(swapUser)); + assertTrue(this.rs.getString(2).startsWith(swapUser)); this.rs.close(); - testStmt.close(); - - // change user using different credentials - final String swapUser = user.indexOf("_sha") == -1 ? "bug75670user_sha" : "bug75670user_mnp"; - expectedPubKeyRetrievalFail = (swapUser.endsWith("_sha") - || swapUser.endsWith("_mnp") && defAuthPlugin.equals(Sha256PasswordPlugin.class)) && !allowPubKeyRetrieval; - System.out.printf("%25s : %-18s : %-25s : %-25s : %s%n", "| ChangeUser (diff)", allowPubKeyRetrieval, swapUser, swapUser, - expectedPubKeyRetrievalFail ? "Fail [Pub. Key retrieval]" : "Ok"); - - if (expectedPubKeyRetrievalFail) { - // change user will fail due to public key retrieval failure - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { - public Void call() throws Exception { - ((JdbcConnection) testConn).changeUser(swapUser, swapUser); - return null; - } - }); - } else { - // change user will succeed - ((JdbcConnection) testConn).changeUser(swapUser, swapUser); - testStmt = testConn.createStatement(); - this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); - assertTrue(this.rs.next()); - assertTrue(this.rs.getString(1).startsWith(swapUser)); - assertTrue(this.rs.getString(2).startsWith(swapUser)); - this.rs.close(); - } - - testConn.close(); } + + testConn.close(); } } } } - } finally { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); - } + } + } finally { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); } } } @@ -7579,13 +7140,17 @@ public void testBug16634180() throws Exception { Connection c1 = null; Connection c2 = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + try { - c1 = getConnectionWithProps(new Properties()); + c1 = getConnectionWithProps(props); c1.setAutoCommit(false); Statement s1 = c1.createStatement(); s1.executeUpdate("update testBug16634180 set val=val+1 where pk=0"); - c2 = getConnectionWithProps(new Properties()); + c2 = getConnectionWithProps(props); c2.setAutoCommit(false); Statement s2 = c2.createStatement(); try { @@ -7642,6 +7207,8 @@ public void testBug16634180() throws Exception { @Test public void testBug21934573() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.exceptionInterceptors.getKeyName(), TestBug21934573ExceptionInterceptor.class.getName()); props.setProperty(PropertyKey.replicationConnectionGroup.getKeyName(), "deadlock"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); @@ -7773,6 +7340,13 @@ public Exception interceptException(Exception sqlEx) { */ @Test public void testBug21947042() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + System.setProperty("javax.net.ssl.trustStore", ""); System.setProperty("javax.net.ssl.trustStorePassword", ""); @@ -7953,6 +7527,8 @@ public void testBug56100() throws Exception { final String hostReplica = "replica:" + port; final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug56100QueryInterceptor.class.getName()); final ReplicationConnection testConn = getUnreliableReplicationConnection(new String[] { "source", "replica" }, props); @@ -8050,157 +7626,135 @@ public void destroy() { /** * Tests fix for WL#8196, Support for TLSv1.2 Protocol. * - * This test requires community server (preferably compiled with yaSSL) in -Dcom.mysql.cj.testsuite.url and commercial server (with OpenSSL) in - * -Dcom.mysql.cj.testsuite.url.openssl - * - * Test certificates from test/config/ssl-test-certs must be installed on both servers. - * * @throws Exception */ @Test public void testTLSVersion() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + // Find out which TLS protocol versions are supported by this JVM. SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, null, null); List jvmSupportedProtocols = Arrays.asList(sslContext.createSSLEngine().getSupportedProtocols()); - final String[] testDbUrls; Properties props = new Properties(); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore"); props.setProperty(PropertyKey.trustCertificateKeyStoreType.getKeyName(), "JKS"); props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password"); - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 5, 7)) { - testDbUrls = new String[] { BaseTestCase.dbUrl, sha256Url }; - } else { - testDbUrls = new String[] { BaseTestCase.dbUrl }; - } + System.out.println(dbUrl); + System.out.println("JVM version: " + System.getProperty(PropertyDefinitions.SYSP_java_version)); + System.out.println("JVM supports TLS protocols: " + jvmSupportedProtocols); + Connection sslConn = getConnectionWithProps(dbUrl, props); + assertTrue(((MysqlConnection) sslConn).getSession().isSSLEstablished()); + System.out.println("MySQL version: " + ((MysqlConnection) sslConn).getSession().getServerSession().getServerVersion()); + this.rs = sslConn.createStatement().executeQuery("SHOW STATUS LIKE 'ssl_version'"); + assertTrue(this.rs.next()); + String tlsVersionUsed = this.rs.getString(2); + System.out.println("TLS version used: " + tlsVersionUsed); - for (String testDbUrl : testDbUrls) { - System.out.println(testDbUrl); - System.out.println("JVM version: " + System.getProperty(PropertyDefinitions.SYSP_java_version)); - System.out.println("JVM supports TLS protocols: " + jvmSupportedProtocols); - Connection sslConn = getConnectionWithProps(testDbUrl, props); - assertTrue(((MysqlConnection) sslConn).getSession().isSSLEstablished()); - System.out.println("MySQL version: " + ((MysqlConnection) sslConn).getSession().getServerSession().getServerVersion()); - this.rs = sslConn.createStatement().executeQuery("SHOW STATUS LIKE 'ssl_version'"); + if (((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 7, 10)) { + this.rs = sslConn.createStatement().executeQuery("SHOW GLOBAL VARIABLES LIKE 'tls_version'"); assertTrue(this.rs.next()); - String tlsVersionUsed = this.rs.getString(2); - System.out.println("TLS version used: " + tlsVersionUsed); - - if (((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 7, 10)) { - this.rs = sslConn.createStatement().executeQuery("SHOW GLOBAL VARIABLES LIKE 'tls_version'"); - assertTrue(this.rs.next()); - List serverSupportedProtocols = Arrays.asList(this.rs.getString(2).trim().split("\\s*,\\s*")); - String highestCommonTlsVersion = ""; - for (String p : new String[] { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" }) { - if (jvmSupportedProtocols.contains(p) && serverSupportedProtocols.contains(p)) { - highestCommonTlsVersion = p; - break; - } + List serverSupportedProtocols = Arrays.asList(this.rs.getString(2).trim().split("\\s*,\\s*")); + String highestCommonTlsVersion = ""; + for (String p : new String[] { "TLSv1.3", "TLSv1.2" }) { + if (jvmSupportedProtocols.contains(p) && serverSupportedProtocols.contains(p)) { + highestCommonTlsVersion = p; + break; } - System.out.println("Server supports TLS protocols: " + serverSupportedProtocols); - System.out.println("Highest common TLS protocol: " + highestCommonTlsVersion); - - assertEquals(highestCommonTlsVersion, tlsVersionUsed); - } else if (((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 6, 46) - && !((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 7, 0)) { - assertEquals("TLSv1.2", tlsVersionUsed); - } else { - assertEquals("TLSv1", tlsVersionUsed); } - System.out.println(); + System.out.println("Server supports TLS protocols: " + serverSupportedProtocols); + System.out.println("Highest common TLS protocol: " + highestCommonTlsVersion); - sslConn.close(); + assertEquals(highestCommonTlsVersion, tlsVersionUsed); + } else { + assertEquals("TLSv1.2", tlsVersionUsed); } + System.out.println(); + + sslConn.close(); } /** - * Tests fix for Bug#87379. This allows TLS version to be overridden through a new configuration option - enabledTLSProtocols. When set to some combination - * of TLSv1, TLSv1.1, or TLSv1.2 (comma-separated, no spaces), the default behavior restricting the TLS version based on JRE and MySQL Server version is + * Tests fix for Bug#87379. This allows TLS version to be overridden through a new configuration option - tlsVersions. When set to some combination + * of TLSv1.2 or TLSv1.3 (comma-separated, no spaces), the default behavior restricting the TLS version based on JRE and MySQL Server version is * bypassed to enable or restrict specific TLS versions. * - * This test requires community server (preferably compiled with yaSSL) in -Dcom.mysql.cj.testsuite.url and commercial server (with OpenSSL) in - * -Dcom.mysql.cj.testsuite.url.openssl - * - * Test certificates from testsuite/ssl-test-certs must be installed on both servers. - * * @throws Exception */ @Test public void testEnableTLSVersion() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + // Find out which TLS protocol versions are supported by this JVM. SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, null, null); List jvmSupportedProtocols = Arrays.asList(sslContext.createSSLEngine().getSupportedProtocols()); - final String[] testDbUrls; Properties props = new Properties(); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore"); props.setProperty(PropertyKey.trustCertificateKeyStoreType.getKeyName(), "JKS"); props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password"); - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 5, 7)) { - testDbUrls = new String[] { BaseTestCase.dbUrl, sha256Url }; + System.out.println(dbUrl); + System.out.println("JVM version: " + System.getProperty(PropertyDefinitions.SYSP_java_version)); + System.out.println("JVM supports TLS protocols: " + jvmSupportedProtocols); + Connection sslConn = getConnectionWithProps(dbUrl, props); + assertTrue(((MysqlConnection) sslConn).getSession().isSSLEstablished()); + System.out.println("MySQL version: " + ((MysqlConnection) sslConn).getSession().getServerSession().getServerVersion()); + List commonSupportedProtocols = new ArrayList<>(); + if (((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 7, 10)) { + this.rs = sslConn.createStatement().executeQuery("SHOW GLOBAL VARIABLES LIKE 'tls_version'"); + assertTrue(this.rs.next()); + List serverSupportedProtocols = Arrays.asList(this.rs.getString(2).trim().split("\\s*,\\s*")); + System.out.println("Server supports TLS protocols: " + serverSupportedProtocols); + commonSupportedProtocols.addAll(serverSupportedProtocols); + commonSupportedProtocols.retainAll(jvmSupportedProtocols); } else { - testDbUrls = new String[] { BaseTestCase.dbUrl }; - } - - for (String testDbUrl : testDbUrls) { - System.out.println(testDbUrl); - System.out.println("JVM version: " + System.getProperty(PropertyDefinitions.SYSP_java_version)); - System.out.println("JVM supports TLS protocols: " + jvmSupportedProtocols); - Connection sslConn = getConnectionWithProps(testDbUrl, props); - assertTrue(((MysqlConnection) sslConn).getSession().isSSLEstablished()); - System.out.println("MySQL version: " + ((MysqlConnection) sslConn).getSession().getServerSession().getServerVersion()); - List commonSupportedProtocols = new ArrayList<>(); - if (((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 7, 10)) { - this.rs = sslConn.createStatement().executeQuery("SHOW GLOBAL VARIABLES LIKE 'tls_version'"); - assertTrue(this.rs.next()); - List serverSupportedProtocols = Arrays.asList(this.rs.getString(2).trim().split("\\s*,\\s*")); - System.out.println("Server supports TLS protocols: " + serverSupportedProtocols); - commonSupportedProtocols.addAll(serverSupportedProtocols); - commonSupportedProtocols.retainAll(jvmSupportedProtocols); - } else if (((JdbcConnection) sslConn).getSession().versionMeetsMinimum(5, 6, 46)) { - commonSupportedProtocols.add("TLSv1"); - commonSupportedProtocols.add("TLSv1.1"); - commonSupportedProtocols.add("TLSv1.2"); - } else { - commonSupportedProtocols.add("TLSv1"); - } + commonSupportedProtocols.add("TLSv1.2"); + commonSupportedProtocols.add("TLSv1.3"); + commonSupportedProtocols.retainAll(jvmSupportedProtocols); + } - String[] testingProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1" }; - for (String protocol : testingProtocols) { - Properties testProps = new Properties(); - testProps.putAll(props); - testProps.setProperty(PropertyKey.enabledTLSProtocols.getKeyName(), protocol); - System.out.println("Testing " + protocol + " expecting connection: " + commonSupportedProtocols.contains(protocol)); - try { - Connection tlsConn = getConnectionWithProps(testDbUrl, testProps); - if (!commonSupportedProtocols.contains(protocol)) { - fail("Expected to fail connection with " + protocol + " due to lack of jvm/server support."); - } - ResultSet rset = tlsConn.createStatement().executeQuery("SHOW STATUS LIKE 'ssl_version'"); - assertTrue(rset.next()); - String tlsVersion = rset.getString(2); - assertEquals(protocol, tlsVersion); - tlsConn.close(); - } catch (Exception e) { - if (commonSupportedProtocols.contains(protocol)) { - e.printStackTrace(); - fail("Expected to be able to connect with " + protocol + " protocol, but failed."); - } + String[] testingProtocols = { "TLSv1.2", "TLSv1.3" }; + for (String protocol : testingProtocols) { + Properties testProps = new Properties(); + testProps.putAll(props); + testProps.setProperty(PropertyKey.tlsVersions.getKeyName(), protocol); + System.out.println("Testing " + protocol + " expecting connection: " + commonSupportedProtocols.contains(protocol)); + try { + Connection tlsConn = getConnectionWithProps(dbUrl, testProps); + assertTrue(commonSupportedProtocols.contains(protocol), "Expected to fail connection with " + protocol + " due to lack of jvm/server support."); + ResultSet rset = tlsConn.createStatement().executeQuery("SHOW STATUS LIKE 'ssl_version'"); + assertTrue(rset.next()); + String tlsVersion = rset.getString(2); + assertEquals(protocol, tlsVersion); + tlsConn.close(); + } catch (Exception e) { + if (commonSupportedProtocols.contains(protocol)) { + e.printStackTrace(); + fail("Expected to be able to connect with " + protocol + " protocol, but failed."); } } - System.out.println(); - sslConn.close(); } + System.out.println(); + sslConn.close(); + } /** @@ -8210,8 +7764,11 @@ public void testEnableTLSVersion() throws Exception { */ @Test public void testBug56122() throws Exception { - for (final Connection testConn : new Connection[] { this.conn, getFailoverConnection(), getLoadBalancedConnection(), - getSourceReplicaReplicationConnection() }) { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (final Connection testConn : new Connection[] { this.conn, getFailoverConnection(props), getLoadBalancedConnection(props), + getSourceReplicaReplicationConnection(props) }) { testConn.createClob(); testConn.createBlob(); testConn.createNClob(); @@ -8253,6 +7810,8 @@ public void testBug21286268() throws Exception { final String[] hosts = new String[] { SOURCE, REPLICA }; final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectTimeout.getKeyName(), "100"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); // Failed connection attempts will show up twice. final Set downedHosts = new HashSet<>(); @@ -8578,7 +8137,9 @@ public void testBug77171() throws Exception { } Properties props = new Properties(); - props.put("sessionVariables", "sql_mode='" + newSqlMode + "'"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + newSqlMode + "'"); Connection testConn = getConnectionWithProps(props); assertFalse(((JdbcConnection) testConn).getSession().getServerSession().useAnsiQuotedIdentifiers()); assertFalse(((JdbcConnection) testConn).getSession().getServerSession().isNoBackslashEscapesSet()); @@ -8586,7 +8147,9 @@ public void testBug77171() throws Exception { props.clear(); newSqlMode = sqlMode + "ANSI_QUOTES"; - props.put("sessionVariables", "sql_mode='" + newSqlMode + "'"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + newSqlMode + "'"); testConn = getConnectionWithProps(props); assertTrue(((JdbcConnection) testConn).getSession().getServerSession().useAnsiQuotedIdentifiers()); assertFalse(((JdbcConnection) testConn).getSession().getServerSession().isNoBackslashEscapesSet()); @@ -8594,7 +8157,9 @@ public void testBug77171() throws Exception { props.clear(); newSqlMode = sqlMode + "NO_BACKSLASH_ESCAPES"; - props.put("sessionVariables", "sql_mode='" + newSqlMode + "'"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + newSqlMode + "'"); testConn = getConnectionWithProps(props); assertFalse(((JdbcConnection) testConn).getSession().getServerSession().useAnsiQuotedIdentifiers()); assertTrue(((JdbcConnection) testConn).getSession().getServerSession().isNoBackslashEscapesSet()); @@ -8602,7 +8167,9 @@ public void testBug77171() throws Exception { props.clear(); newSqlMode = sqlMode + "ANSI_QUOTES,NO_BACKSLASH_ESCAPES"; - props.put("sessionVariables", "sql_mode='" + newSqlMode + "'"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + newSqlMode + "'"); testConn = getConnectionWithProps(props); assertTrue(((JdbcConnection) testConn).getSession().getServerSession().useAnsiQuotedIdentifiers()); assertTrue(((JdbcConnection) testConn).getSession().getServerSession().isNoBackslashEscapesSet()); @@ -8623,6 +8190,8 @@ public void testBug22730682() throws Exception { final String dummyHost = "bug22730682:12345"; final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection testConn; final String lbConnGroup1 = "Bug22730682LB1"; @@ -8700,6 +8269,8 @@ private void subTestBug22848249A() throws Exception { System.out.println("********************************************************************************"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "0"); props.setProperty(PropertyKey.loadBalanceConnectionGroup.getKeyName(), lbConnGroup); Connection testConn = getUnreliableLoadBalancedConnection(new String[] { host1, host2, host3 }, props); @@ -8764,9 +8335,8 @@ private void subTestBug22848249A() throws Exception { connectedHost = newConnectedHost; connectionSwaps++; } - if (--attemptsLeft == 0) { - fail("Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); - } + assertFalse(--attemptsLeft == 0, + "Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); } System.out.println("\t2. Swapped connections " + connectionSwaps + " times before hitting the new host."); assertTrue(connectionSwaps > 0); // Non-deterministic, but something must be wrong if there are no swaps after 100 transaction boundaries. @@ -8796,9 +8366,8 @@ private void subTestBug22848249A() throws Exception { connectedHost = newConnectedHost; connectionSwaps++; } - if (--attemptsLeft == 0) { - fail("Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); - } + assertFalse(--attemptsLeft == 0, + "Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); } System.out.println("\t3. Swapped connections " + connectionSwaps + " times before hitting the new host."); assertTrue(connectionSwaps > 0); // Non-deterministic, but something must be wrong if there are no swaps after 100 transaction boundaries. @@ -8865,6 +8434,8 @@ private void subTestBug22848249B() throws Exception { System.out.println("********************************************************************************"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "0"); props.setProperty(PropertyKey.loadBalanceConnectionGroup.getKeyName(), lbConnGroup); Connection testConn = getUnreliableLoadBalancedConnection(new String[] { host1, host2, host3 }, props); @@ -8931,9 +8502,8 @@ private void subTestBug22848249B() throws Exception { connectedHost = newConnectedHost; connectionSwaps++; } - if (--attemptsLeft == 0) { - fail("Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); - } + assertFalse(--attemptsLeft == 0, + "Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); } System.out.println("\t2. Swapped connections " + connectionSwaps + " times before hitting the new host."); assertTrue(connectionSwaps > 0); // Non-deterministic, but something must be wrong if there are no swaps after 100 transaction boundaries. @@ -8963,9 +8533,8 @@ private void subTestBug22848249B() throws Exception { connectedHost = newConnectedHost; connectionSwaps++; } - if (--attemptsLeft == 0) { - fail("Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); - } + assertFalse(--attemptsLeft == 0, + "Failed to swap to the newly added host after 100 transaction boundaries and " + connectionSwaps + " connection swaps."); } System.out.println("\t3. Swapped connections " + connectionSwaps + " times before hitting the new host."); assertTrue(connectionSwaps > 0); // Non-deterministic, but something must be wrong if there are no swaps after 100 transaction boundaries. @@ -9035,6 +8604,8 @@ private void subTestBug22848249C() throws Exception { * Initial connection will be able to use all hosts, even after removed from the connection group. */ Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "0"); props.setProperty(PropertyKey.loadBalanceConnectionGroup.getKeyName(), lbConnGroup); Connection testConn = getUnreliableLoadBalancedConnection(new String[] { host1, host2, host3, host4 }, props); @@ -9147,6 +8718,8 @@ private void subTestBug22848249D() throws Exception { * Initial connection will be able to use only the hosts available when it was initialized, even after adding new ones to the connection group. */ Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "0"); props.setProperty(PropertyKey.loadBalanceConnectionGroup.getKeyName(), lbConnGroup); Connection testConn = getUnreliableLoadBalancedConnection(new String[] { host1, host2 }, props); @@ -9259,7 +8832,7 @@ public void testBug22678872() throws Exception { props.put(PropertyKey.USER.getKeyName(), username); props.put(PropertyKey.PASSWORD.getKeyName(), password); props.put(PropertyKey.DBNAME.getKeyName(), database); - props.put(PropertyKey.useSSL.getKeyName(), "false"); + props.put(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.put(PropertyKey.loadBalanceHostRemovalGracePeriod.getKeyName(), "0"); // Speed up the test execution. // Replicate the properties used in FabricMySQLConnectionProxy.getActiveConnection(). props.put(PropertyKey.retriesAllDown.getKeyName(), "1"); @@ -9596,6 +9169,8 @@ public void testBug77649() throws Exception { } Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.socketFactory.getKeyName(), UnreliableSocketFactory.class.getName()); for (String h : hosts) { getConnectionWithProps(String.format("jdbc:mysql://%s:%s", h, port), props).close(); @@ -9612,17 +9187,13 @@ public void testBug77649() throws Exception { */ @Test public void testBug74711() throws Exception { - if (!((MysqlConnection) this.conn).getSession().getServerSession().isQueryCacheEnabled()) { - System.err.println("Warning! testBug77411() requires a server supporting a query cache."); - return; - } + assumeTrue(((MysqlConnection) this.conn).getSession().getServerSession().isQueryCacheEnabled(), + "testBug77411() requires a server supporting a query cache."); + this.rs = this.stmt.executeQuery("SELECT @@global.query_cache_type, @@global.query_cache_size"); this.rs.next(); - if (!"ON".equalsIgnoreCase(this.rs.getString(1)) || "0".equals(this.rs.getString(2))) { - System.err - .println("Warning! testBug77411() requires a server started with the options '--query_cache_type=1' and '--query_cache_size=N', (N > 0)."); - return; - } + assumeTrue("ON".equalsIgnoreCase(this.rs.getString(1)) && !"0".equals(this.rs.getString(2)), + "testBug77411() requires a server started with the options '--query_cache_type=1' and '--query_cache_size=N', (N > 0)."); boolean useLocTransSt = false; boolean useElideSetAC = false; @@ -9653,6 +9224,8 @@ public void testBug75209() throws Exception { boolean useLocTransSt = false; final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); do { this.stmt.executeUpdate("TRUNCATE TABLE testBug75209"); this.stmt.executeUpdate("INSERT INTO testBug75209 VALUES (1)"); @@ -9698,7 +9271,10 @@ public void testBug75209() throws Exception { public void testBug75615() throws Exception { // Main use case: although this could cause an exception due to a race condition in MysqlIO.mysqlConnection it is silently swallowed within the running // thread. - final Connection testConn1 = getConnectionWithProps(""); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + final Connection testConn1 = getConnectionWithProps(props); testConn1.setNetworkTimeout(Executors.newSingleThreadExecutor(), 1000); testConn1.close(); @@ -9706,7 +9282,7 @@ public void testBug75615() throws Exception { // This part is repeated several times to increase the chance of hitting the reported bug. for (int i = 0; i < 25; i++) { final ExecutorService execService = Executors.newSingleThreadExecutor(); - final Connection testConn2 = getConnectionWithProps(""); + final Connection testConn2 = getConnectionWithProps(props); testConn2.setNetworkTimeout(new Executor() { public void execute(Runnable command) { // Attach the future to the parent object so that it can track the exception in the main thread. @@ -9726,7 +9302,7 @@ public void execute(Runnable command) { // Test the expected exception on null executor. assertThrows(SQLException.class, "Executor can not be null", new Callable() { public Void call() throws Exception { - Connection testConn = getConnectionWithProps(""); + Connection testConn = getConnectionWithProps(props); testConn.setNetworkTimeout(null, 1000); testConn.close(); return null; @@ -9741,15 +9317,14 @@ public Void call() throws Exception { */ @Test public void testBug70785() throws Exception { + assumeTrue(versionMeetsMinimum(5, 5), "MySQL 5.5+ is required to run this test."); + // Make sure that both client and server have autocommit turned on. assertTrue(this.conn.getAutoCommit()); this.rs = this.stmt.executeQuery("SELECT @@session.autocommit"); this.rs.next(); assertTrue(this.rs.getBoolean(1)); - if (!versionMeetsMinimum(5, 5)) { - return; - } this.rs = this.stmt.executeQuery("SELECT @@global.init_connect"); this.rs.next(); String originalInitConnect = this.rs.getString(1); @@ -9772,6 +9347,8 @@ public void testBug70785() throws Exception { final String testCase = String.format("Case: [AutoCommit: %s, CacheSrvConf: %s, LocTransSt: %s, ElideSetAC: %s ]", autoCommit ? "Y" : "N", cacheServerConf ? "Y" : "N", useLocTransSt ? "Y" : "N", elideSetAutoCommit ? "Y" : "N"); final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheServerConfiguration.getKeyName(), Boolean.toString(cacheServerConf)); props.setProperty(PropertyKey.useLocalTransactionState.getKeyName(), Boolean.toString(useLocTransSt)); props.setProperty(PropertyKey.elideSetAutoCommits.getKeyName(), Boolean.toString(elideSetAutoCommit)); @@ -9804,319 +9381,185 @@ public void testBug70785() throws Exception { } /** - * This test requires two server instances: - * 1) main test server pointed to by the com.mysql.cj.testsuite.url variable configured without RSA encryption support (sha256_password_private_key_path, - * sha256_password_public_key_path, caching_sha2_password_private_key_path and caching_sha2_password_public_key_path config options are unset). - * 2) additional server instance pointed to by the com.mysql.cj.testsuite.url.openssl variable configured with - * default-authentication-plugin=sha256_password, RSA encryption enabled, and server configuration options "caching_sha2_password_private_key_path" and - * "caching_sha2_password_public_key_path" set to the same values as "sha256_password_private_key_path" and "sha256_password_public_key_path" respectively. - * - * To run this test, please add this variable to the ant call: - * -Dcom.mysql.cj.testsuite.url.openssl=jdbc:mysql://localhost:3307/test?user=root&password=pwd + * Test for caching_sha2_password authentication. * * @throws Exception */ @Test public void testCachingSha2PasswordPlugin() throws Exception { + + assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 3), "Requires MySQL 8.0.3+."); + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + assumeTrue(pluginIsActive(this.stmt, "caching_sha2_password"), "caching_sha2_password plugin required to run this test"); + String trustStorePath = "src/test/config/ssl-test-certs/ca-truststore"; System.setProperty("javax.net.ssl.keyStore", trustStorePath); System.setProperty("javax.net.ssl.keyStorePassword", "password"); System.setProperty("javax.net.ssl.trustStore", trustStorePath); System.setProperty("javax.net.ssl.trustStorePassword", "password"); - /* - * test against server without RSA support - */ - if (versionMeetsMinimum(8, 0, 3)) { - if (!pluginIsActive(this.stmt, "caching_sha2_password")) { - fail("caching_sha2_password required to run this test"); - } - - // newer GPL servers, like 8.0.4+, are using OpenSSL and can use RSA encryption, while old ones compiled with yaSSL cannot - boolean gplWithRSA = allowsRsa(this.stmt); - - try { - if (!versionMeetsMinimum(8, 0, 5)) { - this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); - } - createUser("'wl11060user'@'%'", "identified WITH caching_sha2_password"); - this.stmt.executeUpdate("grant all on *.* to 'wl11060user'@'%'"); - createUser("'wl11060nopassword'@'%'", "identified WITH caching_sha2_password"); - this.stmt.executeUpdate("grant all on *.* to 'wl11060nopassword'@'%'"); - if (!versionMeetsMinimum(8, 0, 5)) { - this.stmt.executeUpdate("SET GLOBAL old_passwords= 2"); - this.stmt.executeUpdate("SET SESSION old_passwords= 2"); - } - this.stmt.executeUpdate(versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl11060user'@'%' IDENTIFIED BY 'pwd'" - : "set password for 'wl11060user'@'%' = PASSWORD('pwd')"); - this.stmt.executeUpdate("flush privileges"); - - final Properties propsNoRetrieval = new Properties(); - propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); - propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - final Properties propsNoRetrievalNoPassword = new Properties(); - propsNoRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl11060nopassword"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - final Properties propsAllowRetrieval = new Properties(); - propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); - propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - final Properties propsAllowRetrievalNoPassword = new Properties(); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl11060nopassword"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - // 1. without SSL - // SQLException expected due to server doesn't recognize Public Key Retrieval packet - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { - public Void call() throws Exception { - getConnectionWithProps(propsNoRetrieval); - return null; - } - }); - if (gplWithRSA) { - assertCurrentUser(null, propsAllowRetrieval, "wl11060user", false); - } else { - assertThrows(SQLException.class, "Access denied for user 'wl11060user'.*", new Callable() { - public Void call() throws Exception { - getConnectionWithProps(propsAllowRetrieval); - return null; - } - }); - } - - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl11060nopassword", false); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 2. with serverRSAPublicKeyFile specified - // SQLException expected due to server not recognizing RSA encrypted payload - propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + boolean withCachingTestRsaKeys = supportsTestCachingSha2PasswordKeys(this.stmt); - this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + try { + // create user with long password and caching_sha2_password auth + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); + } + createUser(this.stmt, "'wl11060user'@'%'", "identified WITH caching_sha2_password"); + this.stmt.executeUpdate("grant all on *.* to 'wl11060user'@'%'"); + createUser(this.stmt, "'wl11060nopassword'@'%'", "identified WITH caching_sha2_password"); + this.stmt.executeUpdate("grant all on *.* to 'wl11060nopassword'@'%'"); + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords= 2"); + this.stmt.executeUpdate("SET SESSION old_passwords= 2"); + } + this.stmt.executeUpdate(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl11060user'@'%' IDENTIFIED BY 'pwd'" + : "set password for 'wl11060user'@'%' = PASSWORD('pwd')"); + this.stmt.executeUpdate("flush privileges"); - assertThrows(SQLException.class, "Access denied for user 'wl11060user'.*", new Callable() { - public Void call() throws Exception { - getConnectionWithProps(propsNoRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Access denied for user 'wl11060user'.*", new Callable() { - public Void call() throws Exception { - getConnectionWithProps(propsAllowRetrieval); - return null; - } - }); + final Properties propsNoRetrieval = new Properties(); + propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); + propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl11060nopassword", false); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl11060nopassword", false); + final Properties propsNoRetrievalNoPassword = new Properties(); + propsNoRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl11060nopassword"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - // 3. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); + final Properties propsAllowRetrieval = new Properties(); + propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); + propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); + propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(null, propsNoRetrieval, "wl11060user", true); - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl11060nopassword", false); + final Properties propsAllowRetrievalNoPassword = new Properties(); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl11060nopassword"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(null, propsAllowRetrieval, "wl11060user", true); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl11060nopassword", false); + // 1. with client-default MysqlNativePasswordPlugin + propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); + propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); - // over SSL with client-default CachingSha2PasswordPlugin - propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - propsNoRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); + // 1.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); - this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(null, propsNoRetrieval, "wl11060user", true); - assertCurrentUser(null, propsNoRetrievalNoPassword, "wl11060nopassword", false); + assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); + return null; + } + }); - this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(null, propsAllowRetrieval, "wl11060user", true); - assertCurrentUser(null, propsAllowRetrievalNoPassword, "wl11060nopassword", false); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl11060nopassword", false); + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl11060user", false); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - // 4. without SSL but now we hit the cached scramble - propsNoRetrieval.clear(); - propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); - propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + // 1.2. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); - propsAllowRetrieval.clear(); - propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); - propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsNoRetrieval, "wl11060user", true); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl11060nopassword", false); - assertCurrentUser(null, propsNoRetrieval, "wl11060user", false); // note that is was failing on step 1 - assertCurrentUser(null, propsAllowRetrieval, "wl11060user", false); // note that is was failing on step 1 + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl11060user", true); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - } finally { - this.stmt.executeUpdate("flush privileges"); - if (!versionMeetsMinimum(8, 0, 5)) { - this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); - } - } - } + // 2. with client-default CachingSha2PasswordPlugin + propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); + propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - /* - * test against server with RSA support - */ - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 3)) { + // 2.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); - if (!pluginIsActive(this.sha256Stmt, "caching_sha2_password")) { - fail("caching_sha2_password required to run this test"); - } - if (!allowsRsa(this.sha256Stmt)) { - fail("RSA encryption must be enabled on " + sha256Url + " to run this test"); - } + assertCurrentUser(dbUrl, propsNoRetrieval, "wl11060user", false); // wl11060user scramble is cached now, thus authenticated successfully - try { - // create user with long password and caching_sha2_password auth - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET @current_old_passwords = @@global.old_passwords"); - } - createUser(this.sha256Stmt, "'wl11060user'@'%'", "identified WITH caching_sha2_password"); - this.sha256Stmt.executeUpdate("grant all on *.* to 'wl11060user'@'%'"); - createUser(this.sha256Stmt, "'wl11060nopassword'@'%'", "identified WITH caching_sha2_password"); - this.sha256Stmt.executeUpdate("grant all on *.* to 'wl11060nopassword'@'%'"); - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords= 2"); - this.sha256Stmt.executeUpdate("SET SESSION old_passwords= 2"); + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); // now, with full authentication, it's failed + return null; } - this.sha256Stmt.executeUpdate( - ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'wl11060user'@'%' IDENTIFIED BY 'pwd'" - : "set password for 'wl11060user'@'%' = PASSWORD('pwd')"); - this.sha256Stmt.executeUpdate("flush privileges"); - - final Properties propsNoRetrieval = new Properties(); - propsNoRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); - propsNoRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - - final Properties propsNoRetrievalNoPassword = new Properties(); - propsNoRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl11060nopassword"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - - final Properties propsAllowRetrieval = new Properties(); - propsAllowRetrieval.setProperty(PropertyKey.USER.getKeyName(), "wl11060user"); - propsAllowRetrieval.setProperty(PropertyKey.PASSWORD.getKeyName(), "pwd"); - propsAllowRetrieval.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - - final Properties propsAllowRetrievalNoPassword = new Properties(); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.USER.getKeyName(), "wl11060nopassword"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.PASSWORD.getKeyName(), ""); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - - // 1. with client-default MysqlNativePasswordPlugin - propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); - propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), MysqlNativePasswordPlugin.class.getName()); - - // 1.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { + }); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl11060nopassword", false); + + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl11060user", false); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl11060nopassword", false); + + // 2.2. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsNoRetrieval, "wl11060user", true); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl11060nopassword", false); + + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl11060user", false); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl11060nopassword", false); + + // 3. with serverRSAPublicKeyFile specified + propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); + + // 3.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + + if (withCachingTestRsaKeys) { + assertCurrentUser(dbUrl, propsNoRetrieval, "wl11060user", false); + } else { + assertThrows(SQLException.class, "Access denied for user 'wl11060user'.*", new Callable() { @SuppressWarnings("synthetic-access") public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); + getConnectionWithProps(dbUrl, propsNoRetrieval); return null; } }); + } + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl11060nopassword", false); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl11060nopassword", false); - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl11060user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 1.2. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsNoRetrieval, "wl11060user", true); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl11060nopassword", false); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl11060user", true); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 2. with client-default CachingSha2PasswordPlugin - propsNoRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - propsNoRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - propsAllowRetrieval.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), CachingSha2PasswordPlugin.class.getName()); - - // 2.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - assertCurrentUser(sha256Url, propsNoRetrieval, "wl11060user", false); // wl11060user scramble is cached now, thus authenticated successfully - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertThrows(SQLException.class, "Public Key Retrieval is not allowed", new Callable() { + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + if (withCachingTestRsaKeys) { + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl11060user", false); + } else { + assertThrows(SQLException.class, "Access denied for user 'wl11060user'.*", new Callable() { @SuppressWarnings("synthetic-access") public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); // now, with full authentication, it's failed + getConnectionWithProps(dbUrl, propsAllowRetrieval); return null; } }); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl11060nopassword", false); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl11060user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 2.2. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsNoRetrieval, "wl11060user", true); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl11060nopassword", false); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl11060user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 3. with serverRSAPublicKeyFile specified - propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "src/test/config/ssl-test-certs/mykey.pub"); - - // 3.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsNoRetrieval, "wl11060user", false); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl11060nopassword", false); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl11060user", false); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 3.2. Runtime setServerRSAPublicKeyFile must be denied - final Connection c2 = getConnectionWithProps(sha256Url, propsNoRetrieval); + } + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl11060nopassword", false); + + // 3.2. Runtime setServerRSAPublicKeyFile must be denied + if (withCachingTestRsaKeys) { + final Connection c2 = getConnectionWithProps(dbUrl, propsNoRetrieval); assertThrows(PropertyNotModifiableException.class, "Dynamic change of ''serverRSAPublicKeyFile'' is not allowed.", new Callable() { public Void call() throws Exception { ((JdbcConnection) c2).getPropertySet().getProperty(PropertyKey.serverRSAPublicKeyFile) @@ -10125,189 +9568,187 @@ public Void call() throws Exception { } }); c2.close(); - - // 3.3. Runtime setAllowPublicKeyRetrieval must be denied - final Connection c3 = getConnectionWithProps(sha256Url, propsNoRetrieval); - assertThrows(PropertyNotModifiableException.class, "Dynamic change of ''allowPublicKeyRetrieval'' is not allowed.", new Callable() { - public Void call() throws Exception { - ((JdbcConnection) c3).getPropertySet().getProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); - return null; - } - }); - c3.close(); - - // 3.4. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsNoRetrieval, "wl11060user", true); - assertCurrentUser(sha256Url, propsNoRetrievalNoPassword, "wl11060nopassword", false); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - assertCurrentUser(sha256Url, propsAllowRetrieval, "wl11060user", true); - assertCurrentUser(sha256Url, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - - // 4. with wrong serverRSAPublicKeyFile specified - propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - - // 4.1. RSA - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "false"); - - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - - this.sha256Stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication - - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + } else { + assertThrows(SQLException.class, "Access denied for user 'wl11060user'.*", new Callable() { @SuppressWarnings("synthetic-access") public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword); + getConnectionWithProps(dbUrl, propsNoRetrieval); return null; } }); + } - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword); - return null; - } - }); + // 3.4. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); - // 4.2. over SSL - propsNoRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.useSSL.getKeyName(), "true"); + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsNoRetrieval, "wl11060user", true); + assertCurrentUser(dbUrl, propsNoRetrievalNoPassword, "wl11060nopassword", false); - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + assertCurrentUser(dbUrl, propsAllowRetrieval, "wl11060user", true); + assertCurrentUser(dbUrl, propsAllowRetrievalNoPassword, "wl11060nopassword", false); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword); - return null; - } - }); + // 4. with wrong serverRSAPublicKeyFile specified + propsNoRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + propsAllowRetrieval.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.serverRSAPublicKeyFile.getKeyName(), "unexistant/dummy.pub"); - propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsNoRetrievalNoPassword); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrieval); - return null; - } - }); - assertThrows(SQLException.class, "Unable to read public key ", new Callable() { - @SuppressWarnings("synthetic-access") - public Void call() throws Exception { - getConnectionWithProps(sha256Url, propsAllowRetrievalNoPassword); - return null; - } - }); + // 4.1. RSA + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); - } finally { - if (!((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(8, 0, 5)) { - this.sha256Stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + + this.stmt.executeUpdate("flush privileges"); // to ensure that we'll go through the full authentication + + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword); + return null; + } + }); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword); + return null; + } + }); + + // 4.2. over SSL + propsNoRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsNoRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrieval.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "false"); + + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key 'unexistant/dummy.pub'.*", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword); + return null; + } + }); + + propsNoRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsNoRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrieval.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + propsAllowRetrievalNoPassword.setProperty(PropertyKey.paranoid.getKeyName(), "true"); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsNoRetrievalNoPassword); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrieval); + return null; + } + }); + assertThrows(SQLException.class, "Unable to read public key ", new Callable() { + @SuppressWarnings("synthetic-access") + public Void call() throws Exception { + getConnectionWithProps(dbUrl, propsAllowRetrievalNoPassword); + return null; } + }); + + } finally { + if (!((MysqlConnection) this.conn).getSession().versionMeetsMinimum(8, 0, 5)) { + this.stmt.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords"); } } } @@ -10322,9 +9763,15 @@ public Void call() throws Exception { */ @Test public void testBug88242() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.socketTimeout.getKeyName(), "1500"); @@ -10372,7 +9819,7 @@ public void testBug88232() throws Exception { createTable("testBug88232", "(id INT)", "INNODB"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.socketTimeout.getKeyName(), "2000"); @@ -10424,33 +9871,18 @@ public Void call() throws Exception { @Test public void testBug27131768() throws Exception { Properties props = new Properties(); - props.setProperty("useServerPrepStmts", "true"); - props.setProperty("useInformationSchema", "true"); - props.setProperty("useCursorFetch", "true"); - props.setProperty("defaultFetchSize", "3"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "3"); Connection testConn = getConnectionWithProps(props); testConn.createStatement().executeQuery("SELECT 1"); testConn.close(); } - /** - * Tests fix for Bug#79612 (22362474), CONNECTION ATTRIBUTES LOST WHEN CONNECTING WITHOUT DEFAULT DATABASE. - * - * @throws Exception - */ - @Test - public void testBug79612() throws Exception { - // The case with database present in URL is covered by testConnectionAttributes() test case. - // Testing without database here. - if (versionMeetsMinimum(5, 6)) { - testConnectionAttributes(getNoDbUrl(dbUrl)); - } - if (this.sha256Conn != null && ((JdbcConnection) this.sha256Conn).getSession().versionMeetsMinimum(5, 6, 5)) { - testConnectionAttributes(getNoDbUrl(sha256Url)); - } - } - /** * Tests fix for Bug#88227 (27029657), Connector/J 5.1.44 cannot be used against MySQL 5.7.20 without warnings. * @@ -10458,25 +9890,41 @@ public void testBug79612() throws Exception { */ @Test public void testBug88227() throws Exception { - java.sql.Connection testConn = getConnectionWithProps("statementInterceptors=" + Bug88227QueryInterceptor.class.getName()); - Bug88227QueryInterceptor.mayHaveWarnings = false; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug88227QueryInterceptor.class.getName()); + Bug88227QueryInterceptor.enabled = false; // some warnings are expected here when running against old server versions + java.sql.Connection testConn = getConnectionWithProps(props); + Bug88227QueryInterceptor.enabled = true; testConn.getTransactionIsolation(); testConn.isReadOnly(); testConn.close(); } public static class Bug88227QueryInterceptor extends BaseQueryInterceptor { - public static boolean mayHaveWarnings = true; + public static boolean enabled = false; @Override public T preProcess(Supplier sql, Query interceptedQuery) { - assertFalse(sql.get().contains("SHOW WARNINGS"), "Unexpected [SHOW WARNINGS] was issued"); + if (enabled) { + assertFalse(sql.get().contains("SHOW WARNINGS"), "Unexpected [SHOW WARNINGS] was issued"); + } return super.preProcess(sql, interceptedQuery); } + @Override + public M preProcess(M queryPacket) { + if (enabled) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); + assertFalse(sql.contains("SHOW WARNINGS"), "Unexpected [SHOW WARNINGS] was issued"); + } + return super.preProcess(queryPacket); + } + @Override public T postProcess(Supplier sql, Query interceptedQuery, T originalResultSet, ServerSession serverSession) { - if (!mayHaveWarnings) { + if (enabled) { assertEquals(0, ((NativeSession) interceptedQuery.getSession()).getProtocol().getWarningCount(), "Warnings while executing [" + sql + "]"); } return super.postProcess(sql, interceptedQuery, originalResultSet, serverSession); @@ -10493,12 +9941,22 @@ public void testBug26819691() throws Exception { assertThrows(SQLException.class, "The connection property 'packetDebugBufferSize' only accepts integer values in the range of 1 - 2147483647, " + "the value '0' exceeds this range\\.", new Callable() { public Void call() throws Exception { - getConnectionWithProps("packetDebugBufferSize=0,enablePacketDebug=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.packetDebugBufferSize.getKeyName(), "0"); + props.setProperty(PropertyKey.enablePacketDebug.getKeyName(), "true"); + getConnectionWithProps(props); return null; } }); - getConnectionWithProps("packetDebugBufferSize=1,enablePacketDebug=true").close(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.packetDebugBufferSize.getKeyName(), "1"); + props.setProperty(PropertyKey.enablePacketDebug.getKeyName(), "true"); + getConnectionWithProps(props).close(); } /** @@ -10514,8 +9972,11 @@ public void testBug86741() throws Exception { this.stmt.execute("SET GLOBAL autocommit=0"); try { Connection testConn; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - testConn = getConnectionWithProps(""); + testConn = getConnectionWithProps(props); assertTrue(testConn.getAutoCommit(), "Wrong connection autocommit state"); this.rs = testConn.createStatement().executeQuery("SELECT @@global.autocommit, @@session.autocommit"); this.rs.next(); @@ -10523,7 +9984,7 @@ public void testBug86741() throws Exception { assertEquals(1, this.rs.getInt(2), "Wrong @@session.autocommit"); testConn.close(); - testConn = getFailoverConnection(); + testConn = getFailoverConnection(props); assertTrue(testConn.getAutoCommit(), "Wrong connection autocommit state"); this.rs = testConn.createStatement().executeQuery("SELECT @@global.autocommit, @@session.autocommit"); this.rs.next(); @@ -10531,7 +9992,7 @@ public void testBug86741() throws Exception { assertEquals(1, this.rs.getInt(2), "Wrong @@session.autocommit"); testConn.close(); - testConn = getLoadBalancedConnection(); + testConn = getLoadBalancedConnection(props); assertTrue(testConn.getAutoCommit(), "Wrong connection autocommit state"); this.rs = testConn.createStatement().executeQuery("SELECT @@global.autocommit, @@session.autocommit"); this.rs.next(); @@ -10539,7 +10000,7 @@ public void testBug86741() throws Exception { assertEquals(1, this.rs.getInt(2), "Wrong @@session.autocommit"); testConn.close(); - testConn = getSourceReplicaReplicationConnection(); + testConn = getSourceReplicaReplicationConnection(props); assertTrue(testConn.getAutoCommit(), "Wrong connection autocommit state"); this.rs = testConn.createStatement().executeQuery("SELECT @@global.autocommit, @@session.autocommit"); this.rs.next(); @@ -10563,7 +10024,7 @@ public void testBug90753() throws Exception { int seconds = 2; Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); try { getConnectionWithProps(props).createStatement().executeUpdate("SET @@global.wait_timeout=" + seconds + ", @@global.interactive_timeout=" + seconds); @@ -10630,6 +10091,8 @@ public Void call() throws Exception { @Test public void testBug91421() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "exception"); // legacy EXCEPTION alias JdbcConnection con = (JdbcConnection) getConnectionWithProps(props); @@ -10646,8 +10109,11 @@ public void testBug91421() throws Exception { String user = mainConnectionUrl.getDefaultUser() == null ? "" : mainConnectionUrl.getMainHost().getUser(); String password = mainConnectionUrl.getDefaultPassword() == null ? "" : mainConnectionUrl.getMainHost().getPassword(); - con = (JdbcConnection) getConnectionWithProps("jdbc:mysql://(port=" + getPortFromTestsuiteUrl() + ",user=" + user + ",password=" + password - + ",zeroDateTimeBehavior=convertToNull)/" + this.dbName, appendRequiredProperties(null)); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + con = (JdbcConnection) getConnectionWithProps("jdbc:mysql://(host=" + getHostFromTestsuiteUrl() + ",port=" + getPortFromTestsuiteUrl() + ",user=" + user + + ",password=" + password + ",zeroDateTimeBehavior=convertToNull)/" + this.dbName, appendRequiredProperties(props)); assertEquals(ZeroDatetimeBehavior.CONVERT_TO_NULL, con.getPropertySet().getEnumProperty(PropertyKey.zeroDateTimeBehavior).getValue()); } @@ -10664,13 +10130,15 @@ public void testBug28150662() throws Exception { String password = hostInfo.getPassword() == null ? "" : hostInfo.getPassword(); List connStr = new ArrayList<>(); - connStr.add(dbUrl + "&sessionVariables=sql_mode='IGNORE_SPACE,ANSI',FOREIGN_KEY_CHECKS=0&connectionCollation=utf8mb4_unicode_ci"); - connStr.add(dbUrl + "&connectionCollation=utf8mb4_unicode_ci&sessionVariables=sql_mode='IGNORE_SPACE,ANSI',FOREIGN_KEY_CHECKS=0"); + connStr.add(dbUrl + + "&sessionVariables=sql_mode='IGNORE_SPACE,ANSI',FOREIGN_KEY_CHECKS=0&connectionCollation=utf8mb4_unicode_ci&sslMode=DISABLED&allowPublicKeyRetrieval=true"); + connStr.add(dbUrl + + "&connectionCollation=utf8mb4_unicode_ci&sslMode=DISABLED&allowPublicKeyRetrieval=true&sessionVariables=sql_mode='IGNORE_SPACE,ANSI',FOREIGN_KEY_CHECKS=0"); connStr.add(String.format( - "jdbc:mysql://address=(host=%1$s)(port=%2$d)(connectionCollation=utf8mb4_unicode_ci)(sessionVariables=sql_mode='IGNORE_SPACE,ANSI',FOREIGN_KEY_CHECKS=0)(user=%3$s)(password=%4$s)/%5$s", + "jdbc:mysql://address=(host=%1$s)(port=%2$d)(sslMode=DISABLED)(allowPublicKeyRetrieval=true)(connectionCollation=utf8mb4_unicode_ci)(sessionVariables=sql_mode='IGNORE_SPACE,ANSI',FOREIGN_KEY_CHECKS=0)(user=%3$s)(password=%4$s)/%5$s", getEncodedHostFromTestsuiteUrl(), getPortFromTestsuiteUrl(), user, password, hostInfo.getDatabase())); connStr.add(String.format( - "jdbc:mysql://(host=%1$s,port=%2$d,connectionCollation=utf8mb4_unicode_ci,sessionVariables=sql_mode='IGNORE_SPACE%3$sANSI'%3$sFOREIGN_KEY_CHECKS=0,user=%4$s,password=%5$s)/%6$s", + "jdbc:mysql://(host=%1$s,port=%2$d,connectionCollation=utf8mb4_unicode_ci,sslMode=DISABLED,allowPublicKeyRetrieval=true,sessionVariables=sql_mode='IGNORE_SPACE%3$sANSI'%3$sFOREIGN_KEY_CHECKS=0,user=%4$s,password=%5$s)/%6$s", getEncodedHostFromTestsuiteUrl(), getPortFromTestsuiteUrl(), "%2C", user, password, hostInfo.getDatabase())); for (String cs : connStr) { @@ -10694,6 +10162,13 @@ public void testBug28150662() throws Exception { */ @Test public void testBug27102307() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + // Basic SSL properties translation is tested in testBug21947042(). Testing only missing variants here. System.setProperty("javax.net.ssl.trustStore", ""); System.setProperty("javax.net.ssl.trustStorePassword", ""); @@ -10854,6 +10329,8 @@ public void testBug89948() throws Exception { allowMQ ? "Y" : "N", rwBatchStmts ? "Y" : "N", useLTS ? "Y" : "N", useLSS ? "Y" : "N"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), Boolean.toString(allowMQ)); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), Boolean.toString(rwBatchStmts)); props.setProperty(PropertyKey.useLocalTransactionState.getKeyName(), Boolean.toString(useLTS)); @@ -10924,68 +10401,6 @@ private void testBug89948Check(String testCase, int expectedCount, int idOffset) assertEquals(expectedCount, c, testCase); } - /** - * Tests fix for Bug#91317 (28207422), Wrong defaults on collation mappings. - * - * @throws Exception - */ - @Test - public void testBug91317() throws Exception { - Map defaultCollations = new HashMap<>(); - - // Compare server-side and client-side collation defaults. - this.rs = this.stmt.executeQuery("SELECT COLLATION_NAME, CHARACTER_SET_NAME, ID FROM INFORMATION_SCHEMA.COLLATIONS WHERE IS_DEFAULT = 'Yes'"); - while (this.rs.next()) { - String collationName = this.rs.getString(1); - String charsetName = this.rs.getString(2); - int collationId = this.rs.getInt(3); - int mappedCollationId = CharsetMapping.CHARSET_NAME_TO_COLLATION_INDEX.get(charsetName); - - defaultCollations.put(charsetName, collationName); - - // Default collation for 'utf8mb4' is 'utf8mb4_0900_ai_ci' in MySQL 8.0.1 and above, 'utf8mb4_general_ci' in the others. - if ("utf8mb4".equalsIgnoreCase(charsetName) && !versionMeetsMinimum(8, 0, 1)) { - mappedCollationId = 45; - } - - assertEquals(collationId, mappedCollationId); - assertEquals(collationName, CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[mappedCollationId]); - } - - ServerVersion sv = ((JdbcConnection) this.conn).getServerVersion(); - - // Check `collation_connection` for each one of the known character sets. - this.rs = this.stmt.executeQuery("SELECT character_set_name FROM information_schema.character_sets"); - int csCount = 0; - while (this.rs.next()) { - csCount++; - String cs = this.rs.getString(1); - - // The following cannot be set as client_character_set - // (https://dev.mysql.com/doc/refman/8.0/en/charset-connection.html#charset-connection-impermissible-client-charset) - if (cs.equalsIgnoreCase("ucs2") || cs.equalsIgnoreCase("utf16") || cs.equalsIgnoreCase("utf16le") || cs.equalsIgnoreCase("utf32")) { - continue; - } - - String javaEnc = CharsetMapping.getJavaEncodingForMysqlCharset(cs); - String charsetForJavaEnc = CharsetMapping.getMysqlCharsetForJavaEncoding(javaEnc, sv); - String expectedCollation = defaultCollations.get(charsetForJavaEnc); - - if ("UTF-8".equalsIgnoreCase(javaEnc)) { - // UTF-8 is the exception. This encoding is converted to MySQL charset 'utf8mb4' instead of 'utf8', and its corresponding collation. - expectedCollation = versionMeetsMinimum(8, 0, 1) ? "utf8mb4_0900_ai_ci" : "utf8mb4_general_ci"; - } - - Connection testConn = getConnectionWithProps("characterEncoding=" + javaEnc); - ResultSet testRs = testConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'collation_connection'"); - assertTrue(testRs.next()); - assertEquals(expectedCollation, testRs.getString(2)); - testConn.close(); - } - // Assert that some charsets were tested. - assertTrue(csCount > 35); // There are 39 charsets in MySQL 5.5.61, 40 in MySQL 5.6.41 and 41 in MySQL 5.7.23 and above, but these numbers can vary. - } - /** * Tests fix for Bug#25642226, CHANGEUSER() NOT SETTING THE DATABASE PROPERLY WITH SHA USER. * @@ -10993,37 +10408,30 @@ public void testBug91317() throws Exception { */ @Test public void testBug25642226() throws Exception { - testBug25642226Task(dbUrl, "\u4F5C\u4F5C\u4F5C"); - testBug25642226Task(sha256Url, "\u4F5C\u4F5C\u4F5C"); - } + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 6, 5), "Requires MySQL 5.6.5+ server."); + assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password required to run this test"); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + + String pwd = "\u4F5C\u4F5C\u4F5C"; - private void testBug25642226Task(String url, String pwd) throws Exception { final Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "REQUIRED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore"); props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - Connection c1 = getConnectionWithProps(url, props); + Connection c1 = getConnectionWithProps(dbUrl, props); Connection c2 = null; Session sess = ((JdbcConnection) c1).getSession(); - - if (!sess.versionMeetsMinimum(5, 6, 5)) { - System.out.println("Skipped. Requires MySQL 5.6.5+ server."); - c1.close(); - return; - } - Statement s1 = c1.createStatement(); - if (!pluginIsActive(s1, "sha256_password")) { - c1.close(); - fail("sha256_password required to run this test"); - } this.rs = s1.executeQuery("select database()"); this.rs.next(); String origDb = this.rs.getString(1); - System.out.println("URL [" + url + "]"); + System.out.println("URL [" + dbUrl + "]"); System.out.println("1. Original database [" + origDb + "]"); try { @@ -11039,7 +10447,7 @@ private void testBug25642226Task(String url, String pwd) throws Exception { : "set password for 'Bug25642226u1'@'%' = PASSWORD('" + pwd + "')"); s1.executeUpdate("flush privileges"); - c2 = getConnectionWithProps(url, props); + c2 = getConnectionWithProps(dbUrl, props); Statement s2 = c2.createStatement(); ((JdbcConnection) c2).changeUser("Bug25642226u1", pwd); @@ -11051,9 +10459,7 @@ private void testBug25642226Task(String url, String pwd) throws Exception { // create user with required password and caching_sha2_password auth if (sess.versionMeetsMinimum(8, 0, 3)) { - if (!pluginIsActive(s1, "caching_sha2_password")) { - fail("caching_sha2_password required to run this test"); - } + assertTrue(pluginIsActive(s1, "caching_sha2_password"), "caching_sha2_password required to run this test"); // create user with required password and sha256_password auth createUser(s1, "'Bug25642226u2'@'%'", "identified WITH caching_sha2_password"); s1.executeUpdate("grant all on *.* to 'Bug25642226u2'@'%'"); @@ -11092,6 +10498,8 @@ private void testBug25642226Task(String url, String pwd) throws Exception { @Test public void testBug92625() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); Connection con = getConnectionWithProps(props); @@ -11112,7 +10520,7 @@ public Void call() throws Exception { @Test public void testBug25642021() throws Exception { Properties props = getPropertiesFromTestsuiteUrl(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.enablePacketDebug.getKeyName(), "true"); props.setProperty(PropertyKey.maintainTimeStats.getKeyName(), "true"); @@ -11145,6 +10553,8 @@ public void testBug25642021() throws Exception { @Test public void testBug93007() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), ForcedLoadBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalanceBlocklistTimeout.getKeyName(), "5000"); props.setProperty(PropertyKey.loadBalancePingTimeout.getKeyName(), "100"); @@ -11202,6 +10612,8 @@ public void testBug93007() throws Exception { @Test public void testBug29329326() throws Exception { Properties p = new Properties(); + p.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + p.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); p.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug29329326QueryInterceptor.class.getName()); JdbcConnection c = (JdbcConnection) getConnectionWithProps(p); @@ -11316,6 +10728,8 @@ public void testBug74690() throws Exception { @Test public void testBug70677() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); BufferingLogger.startLoggingToBuffer(); @@ -11347,8 +10761,14 @@ public void testBug70677() throws Exception { public void testBug98445() throws Exception { createProcedure("setCiTestBug98445", "(IN k VARCHAR(100), IN v VARCHAR(100)) BEGIN SET @testBug98445=v; END"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + // clientInfoProvider=ClientInfoProviderSP - Connection testConn1 = getConnectionWithProps("clientInfoProvider=ClientInfoProviderSP,clientInfoSetSPName=setCiTestBug98445"); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), "ClientInfoProviderSP"); + props.setProperty(ClientInfoProviderSP.PNAME_clientInfoSetSPName, "setCiTestBug98445"); + Connection testConn1 = getConnectionWithProps(props); testConn1.setClientInfo("testBug98445", "testBug98445Data1"); Statement testStmt = testConn1.createStatement(); this.rs = testStmt.executeQuery("SELECT @testBug98445"); @@ -11357,7 +10777,9 @@ public void testBug98445() throws Exception { testConn1.close(); // clientInfoProvider=com.mysql.cj.jdbc.ClientInfoProviderSP - testConn1 = getConnectionWithProps("clientInfoProvider=com.mysql.cj.jdbc.ClientInfoProviderSP,clientInfoSetSPName=setCiTestBug98445"); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), ClientInfoProviderSP.class.getName()); + props.setProperty(ClientInfoProviderSP.PNAME_clientInfoSetSPName, "setCiTestBug98445"); + testConn1 = getConnectionWithProps(props); testConn1.setClientInfo("testBug98445", "testBug98445Data2"); testStmt = testConn1.createStatement(); this.rs = testStmt.executeQuery("SELECT @testBug98445"); @@ -11370,7 +10792,10 @@ public void testBug98445() throws Exception { System.setErr(new PrintStream(newErr)); // clientInfoProvider=CommentClientInfoProvider - testConn1 = getConnectionWithProps("clientInfoProvider=CommentClientInfoProvider,profileSQL=true"); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), "CommentClientInfoProvider"); + props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); + props.remove("clientInfoSetSPName"); + testConn1 = getConnectionWithProps(props); testConn1.setClientInfo("testBug98445", "testBug98445Data3"); testStmt = testConn1.createStatement(); this.rs = testStmt.executeQuery("SELECT 'testBug98445Data3'"); @@ -11383,7 +10808,9 @@ public void testBug98445() throws Exception { newErr.reset(); // clientInfoProvider=com.mysql.cj.jdbc.CommentClientInfoProvider - testConn1 = getConnectionWithProps("clientInfoProvider=com.mysql.cj.jdbc.CommentClientInfoProvider,profileSQL=true"); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), CommentClientInfoProvider.class.getName()); + props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); + testConn1 = getConnectionWithProps(props); testConn1.setClientInfo("testBug98445", "testBug98445Data4"); testStmt = testConn1.createStatement(); this.rs = testStmt.executeQuery("SELECT 'testBug98445Data4'"); @@ -11397,7 +10824,9 @@ public void testBug98445() throws Exception { System.setErr(oldErr); // clientInfoProvider=TestBug98445ClientInfoProvider - testConn1 = getConnectionWithProps("clientInfoProvider=" + TestBug98445ClientInfoProvider.class.getName()); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), TestBug98445ClientInfoProvider.class.getName()); + props.remove(PropertyKey.profileSQL.getKeyName()); + testConn1 = getConnectionWithProps(props); testConn1.setClientInfo("testBug98445", "testBug98445Data7"); testStmt = testConn1.createStatement(); this.rs = testStmt.executeQuery("SELECT @testBug98445"); @@ -11406,7 +10835,8 @@ public void testBug98445() throws Exception { testConn1.close(); // clientInfoProvider=DummyClass - Connection testConn2 = getConnectionWithProps("clientInfoProvider=DummyClass"); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), "DummyClass"); + Connection testConn2 = getConnectionWithProps(props); Throwable t = assertThrows(SQLClientInfoException.class, () -> { testConn2.setClientInfo("testBug98445", "testBug98445Data5"); return null; @@ -11416,7 +10846,8 @@ public void testBug98445() throws Exception { testConn2.close(); // clientInfoProvider=java.lang.Object - Connection testConn3 = getConnectionWithProps("clientInfoProvider=java.lang.Object"); + props.setProperty(PropertyKey.clientInfoProvider.getKeyName(), Object.class.getName()); + Connection testConn3 = getConnectionWithProps(props); t = assertThrows(SQLClientInfoException.class, () -> { testConn3.setClientInfo("testBug98445", "testBug98445Data6"); return null; @@ -11467,13 +10898,16 @@ public void setClientInfo(Connection conn, String name, String value) throws SQL */ @Test public void testBug97714() throws Exception { + assumeFalse(isServerRunningOnWindows(), "SLEEP() is not precise enough on Windows."); boolean useSPS = false; do { final String testCase = String.format("Case: [useServerPrepStmts: %s]", useSPS ? "Y" : "N"); Properties props = new Properties(); - props.setProperty("useServerPrepStmts", Boolean.toString(useSPS)); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); Connection testConn = getConnectionWithProps(props); // Statement @@ -11514,6 +10948,13 @@ public void testBug97714() throws Exception { */ @Test public void testBug99767() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + try { final Properties props = getPropertiesFromTestsuiteUrl(); props.setProperty(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory"); @@ -11580,14 +11021,10 @@ public void testBug99767() throws Exception { */ @Test public void testBug99076() throws Exception { - if (!versionMeetsMinimum(8, 0, 16)) { - return; - } + assumeTrue(versionMeetsMinimum(8, 0, 16), "MySQL 8.0.16+ is required to run this test."); String xUrl = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_mysqlx); - if (xUrl == null || xUrl.length() == 0) { - return; - } + assumeTrue(xUrl != null && xUrl.length() != 0, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); final ConnectionUrl conUrl = ConnectionUrl.getConnectionUrlInstance(xUrl, null); final HostInfo hostInfo = conUrl.getMainHost(); @@ -11607,15 +11044,16 @@ public void testBug99076() throws Exception { */ @Test public void testBug98667() throws Exception { + assumeTrue(isServerRunningOnWindows() && isMysqlRunningLocally(), + "This test can run only when client and server are running on the same Windows host."); + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'named_pipe'"); - if (!this.rs.next() || !this.rs.getString(2).equalsIgnoreCase("on")) { - return; // Only runs on Windows with named pipes enabled. - } + assumeTrue(this.rs.next() && this.rs.getString(2).equalsIgnoreCase("on"), "Only runs on Windows with named_pipe=ON."); + String namedPipeName = null; this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'socket'"); - assumeTrue(this.rs.next()); - String namedPipeName = this.rs.getString(2); - assumeFalse(StringUtils.isNullOrEmpty(namedPipeName)); + assumeTrue(this.rs.next() && !StringUtils.isNullOrEmpty(namedPipeName = this.rs.getString(2)), + "Only runs on Windows with enabled named pipes and not empty socket name."); final String namedPipePath = "\\\\.\\pipe\\" + namedPipeName; final Properties props = getHostFreePropertiesFromTestsuiteUrl(); @@ -11667,6 +11105,8 @@ public void testBug21789378() throws Exception { f.setAccessible(true); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -11689,31 +11129,27 @@ public void testBug21789378() throws Exception { */ @Test public void testDefaultUserWithoutPasswordAuthentication() throws Exception { - if (!versionMeetsMinimum(5, 5, 7)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 7), "MySQL 5.5.7+ is required to run this test."); String systemUsername = System.getProperty("user.name"); - if (StringUtils.isNullOrEmpty(systemUsername)) { - return; - } + assumeFalse(StringUtils.isNullOrEmpty(systemUsername), "This test can't proceed with empty system user name."); this.rs = this.stmt.executeQuery("SELECT user FROM mysql.user WHERE user = '" + systemUsername + "'"); - assumeFalse(this.rs.next()); // Probably user 'root' and there is one already. This test can't mess with it. + assumeFalse(this.rs.next(), "Probably user 'root' and there is one already. This test can't proceed with it."); String[] authenticationPlugins = new String[] { "caching_sha2_password", "sha256_password", "mysql_native_password" }; List authenticationPluginsTested = new ArrayList<>(); for (String authPlugin : authenticationPlugins) { if (pluginIsActive(this.stmt, authPlugin)) { - assertThrows(SQLException.class, String.format("Access denied for user '%s'@'%s'.*", systemUsername, getHostFromTestsuiteUrl()), + assertThrows(SQLException.class, String.format("Access denied for user '%s'@.*", systemUsername), () -> getConnectionWithProps(String.format("jdbc:mysql://%s:%s", getHostFromTestsuiteUrl(), getPortFromTestsuiteUrl()), - "defaultAuthenticationPlugin=" + authPlugin)); + "sslMode=DISABLED,allowPublicKeyRetrieval=true,defaultAuthenticationPlugin=" + authPlugin)); createUser(systemUsername, "IDENTIFIED WITH " + authPlugin); this.stmt.execute("GRANT ALL ON *.* TO " + systemUsername); Connection testConn = getConnectionWithProps(String.format("jdbc:mysql://%s:%s", getHostFromTestsuiteUrl(), getPortFromTestsuiteUrl()), - "defaultAuthenticationPlugin=" + authPlugin); + "sslMode=DISABLED,allowPublicKeyRetrieval=true,defaultAuthenticationPlugin=" + authPlugin); Statement testStmt = testConn.createStatement(); ResultSet testRs = testStmt.executeQuery("SELECT CURRENT_USER()"); assertTrue(testRs.next()); @@ -11734,23 +11170,19 @@ public void testDefaultUserWithoutPasswordAuthentication() throws Exception { */ @Test public void testDefaultUserWithPasswordAuthentication() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { // New CREATE USER options. - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); String systemUsername = System.getProperty("user.name"); - if (StringUtils.isNullOrEmpty(systemUsername)) { - return; - } + assumeFalse(StringUtils.isNullOrEmpty(systemUsername), "This test can't proceed with empty system user name."); this.rs = this.stmt.executeQuery("SELECT user FROM mysql.user WHERE user = '" + systemUsername + "'"); - assumeFalse(this.rs.next()); // Probably user 'root' and there is one already. This test can't mess with it. + assumeFalse(this.rs.next(), "Probably user 'root' and there is one already. This test can't proceed with it."); String[] authenticationPlugins = new String[] { "caching_sha2_password", "sha256_password", "mysql_native_password" }; List authenticationPluginsTested = new ArrayList<>(); for (String authPlugin : authenticationPlugins) { if (pluginIsActive(this.stmt, authPlugin)) { - assertThrows(SQLException.class, String.format("Access denied for user '%s'@'%s'.*", systemUsername, getHostFromTestsuiteUrl()), + assertThrows(SQLException.class, String.format("Access denied for user '%s'@.*", systemUsername), () -> getConnectionWithProps(String.format("jdbc:mysql://%s:%s", getHostFromTestsuiteUrl(), getPortFromTestsuiteUrl()), "defaultAuthenticationPlugin=" + authPlugin)); @@ -11783,6 +11215,8 @@ public void testBug101596() throws Exception { try { String transformClassName = TestBug101596Transformer.class.getName(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.propertiesTransform.getKeyName(), transformClassName); testConn = getConnectionWithProps(props); // it was failing before the fix } finally { @@ -11806,6 +11240,8 @@ public Properties transformProperties(Properties props) { @Test public void testBug22508715() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection con = getConnectionWithProps(props); con.close(); @@ -11836,20 +11272,359 @@ public void testBug102188() throws Exception { * After this fix the provider 'MySQLScramShaSasl' should not be loaded while connecting using an authentication plugin different than * 'authentication_ldap_sasl_client'. */ - getConnectionWithProps("").close(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + getConnectionWithProps(props).close(); assertNull(Security.getProvider("MySQLScramShaSasl")); /* * Disabling the authentication plugin 'authentication_ldap_sasl_client' is another way to avoid loading the provider 'MySQLScramShaSasl'. */ - getConnectionWithProps("disabledAuthenticationPlugins=authentication_ldap_sasl_client").close(); + props.setProperty(PropertyKey.disabledAuthenticationPlugins.getKeyName(), "authentication_ldap_sasl_client"); + getConnectionWithProps(props).close(); assertNull(Security.getProvider("MySQLScramShaSasl")); /* * Setting 'authentication_ldap_sasl_client' as the default authentication plugin initializes it and, thus, the provider 'MySQLScramShaSasl' gets * loaded. */ - getConnectionWithProps("defaultAuthenticationPlugin=authentication_ldap_sasl_client").close(); + props.remove(PropertyKey.disabledAuthenticationPlugins.getKeyName()); + props.setProperty(PropertyKey.defaultAuthenticationPlugin.getKeyName(), "authentication_ldap_sasl_client"); + getConnectionWithProps(props).close(); assertNotNull(Security.getProvider("MySQLScramShaSasl")); } + + /** + * Tests fix for Bug#102404 (32435618), CONTRIBUTION: ADD TRACK SESSION STATE CHANGE. + * + * @throws Exception + */ + @Test + public void testBug102404() throws Exception { + assumeTrue(versionMeetsMinimum(5, 7, 6), "Session state tracking requires at least MySQL 5.7.6"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.trackSessionState.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); + Connection c = getConnectionWithProps(props); + + TestBug102404Listener listener = new TestBug102404Listener(); + + ((MysqlConnection) c).getServerSessionStateController().addSessionStateChangesListener(listener); + + Statement testStmt = c.createStatement(); + + /* + * SET NAMES should generate three SESSION_TRACK_SYSTEM_VARIABLES changes, + * for character_set_client, character_set_connection and character_set_results system variables + */ + System.out.println("\n=== Test SESSION_TRACK_SYSTEM_VARIABLES ==="); + testStmt.executeUpdate("SET NAMES latin5"); + int cnt1 = 0; + + assertEquals(listener.changes, ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges()); + + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_SYSTEM_VARIABLES) { + cnt1++; + assertTrue( + "character_set_client".contentEquals(change.getValues().get(0)) || "character_set_connection".contentEquals(change.getValues().get(0)) + || "character_set_results".contentEquals(change.getValues().get(0))); + assertEquals("latin5", change.getValues().get(1)); + } + } + assertEquals(3, cnt1); + + /* + * Check SESSION_TRACK_SCHEMA and SESSION_TRACK_STATE_CHANGE + */ + System.out.println("\n=== Test SESSION_TRACK_SCHEMA and SESSION_TRACK_STATE_CHANGE ==="); + testStmt.executeUpdate("SET SESSION session_track_state_change=1"); // this statement itself does not produce SESSION_TRACK_STATE_CHANGE + testStmt.executeUpdate("USE " + this.dbName); // should produce both SESSION_TRACK_SCHEMA and SESSION_TRACK_STATE_CHANGE + cnt1 = 0; + int cnt2 = 0; + + assertEquals(listener.changes, ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges()); + + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_SCHEMA) { + cnt1++; + assertEquals(this.dbName, change.getValues().get(0)); + } else if (change.getType() == ServerSessionStateController.SESSION_TRACK_STATE_CHANGE) { + cnt2++; + assertEquals("1", change.getValues().get(0)); + } + } + assertEquals(1, cnt1); + assertEquals(1, cnt2); + + /* + * Check SESSION_TRACK_TRANSACTION_STATE, SESSION_TRACK_TRANSACTION_CHARACTERISTICS and SESSION_TRACK_GTIDS. + * SESSION_TRACK_GTIDS requires the server configured for replication with GTIDs. + */ + this.rs = testStmt.executeQuery("SELECT @@gtid_mode, @@log_bin, @@enforce_gtid_consistency"); + this.rs.next(); + boolean checkGTIDs = "ON".equalsIgnoreCase(this.rs.getString(1)) && "1".equalsIgnoreCase(this.rs.getString(2)) + && "ON".equalsIgnoreCase(this.rs.getString(3)); + System.out.println("\n=== Test SESSION_TRACK_TRANSACTION_STATE, SESSION_TRACK_TRANSACTION_CHARACTERISTICS and SESSION_TRACK_GTIDS ==="); + + createTable(testStmt, "testBug102404", "(val varchar(10))"); + c.createStatement().executeUpdate("SET @@session.session_track_gtids='OWN_GTID'"); + + c.createStatement().executeUpdate("SET @@SESSION.session_track_transaction_info='CHARACTERISTICS'"); + c.createStatement().executeUpdate("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + cnt1 = 0; + + assertEquals(listener.changes, ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges()); + + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_CHARACTERISTICS) { + cnt1++; + assertEquals("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;", change.getValues().get(0)); + } + } + assertEquals(1, cnt1); + + cnt1 = 0; + c.createStatement().executeUpdate("SET TRANSACTION READ WRITE"); + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_CHARACTERISTICS) { + cnt1++; + assertEquals("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET TRANSACTION READ WRITE;", change.getValues().get(0)); + } + } + assertEquals(1, cnt1); + + System.out.println("START TRANSACTION"); + cnt1 = 0; + cnt2 = 0; + testStmt.execute("START TRANSACTION"); + + assertEquals(listener.changes, ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges()); + + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_STATE) { + cnt1++; + assertTrue(change.getValues().get(0).startsWith("T")); + } else if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_CHARACTERISTICS) { + cnt2++; + assertEquals("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION READ WRITE;", change.getValues().get(0)); + } + } + assertEquals(1, cnt1); + assertEquals(1, cnt2); + + System.out.println("insert into testBug102404 values('abc')"); + ((MysqlConnection) c).getServerSessionStateController().removeSessionStateChangesListener(listener); + + cnt1 = 0; + testStmt.executeUpdate("insert into testBug102404 values('abc')"); + + assertNotEquals(listener.changes, ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges()); + + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_STATE) { + cnt1++; + assertTrue(change.getValues().get(0).startsWith("T") && change.getValues().get(0).contains("W")); + } + } + assertEquals(1, cnt1); + + System.out.println("COMMIT"); + ((MysqlConnection) c).getServerSessionStateController().addSessionStateChangesListener(listener); + cnt1 = 0; + cnt2 = 0; + int cnt3 = 0; + testStmt.execute("COMMIT"); + + assertEquals(listener.changes, ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges()); + + for (SessionStateChange change : ((MysqlConnection) c).getServerSessionStateController().getSessionStateChanges().getSessionStateChangesList()) { + if (change.getType() == ServerSessionStateController.SESSION_TRACK_GTIDS) { + String gtid = change.getValues().get(0); + int colonPos = gtid.indexOf(":"); + assertTrue(colonPos > 0); + gtid = gtid.substring(0, colonPos); + + this.rs = this.stmt.executeQuery("show global variables like 'gtid_executed'"); + while (this.rs.next()) { + if (this.rs.getString(2).startsWith(gtid)) { + cnt1++; + break; + } + } + + } else if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_STATE) { + cnt2++; + assertTrue(change.getValues().get(0).startsWith("_")); + + } else if (change.getType() == ServerSessionStateController.SESSION_TRACK_TRANSACTION_CHARACTERISTICS) { + cnt3++; + assertEquals("", change.getValues().get(0)); + } + + } + assertEquals(checkGTIDs ? 1 : 0, cnt1); + assertEquals(1, cnt2); + assertEquals(1, cnt3); + + } + + class TestBug102404Listener implements SessionStateChangesListener { + + ServerSessionStateChanges changes = null; + + @Override + public void handleSessionStateChanges(ServerSessionStateChanges ch) { + this.changes = ch; + for (SessionStateChange change : ch.getSessionStateChangesList()) { + printChange(change); + } + } + + private void printChange(SessionStateChange change) { + System.out.print(change.getType() + " == > "); + int pos = 0; + if (change.getType() == ServerSessionStateController.SESSION_TRACK_SYSTEM_VARIABLES) { + System.out.print(change.getValues().get(pos++) + "="); + } + System.out.println(change.getValues().get(pos)); + } + + } + + /** + * Tests fix for Bug#95564 (29894324), createDatabaseIfNotExist is not working for databases with hyphen in name. + * + * @throws Exception + */ + @Test + public void testBug95564() throws Exception { + String databaseName = "test-bug95564"; + + try { + this.stmt.executeUpdate("DROP DATABASE IF EXISTS " + StringUtils.quoteIdentifier(databaseName, true)); + + Properties props = getPropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.createDatabaseIfNotExist.getKeyName(), "true"); + props.setProperty(PropertyKey.DBNAME.getKeyName(), databaseName); + + Connection con = getConnectionWithProps(props); + + this.rs = this.stmt.executeQuery("SHOW DATABASES LIKE '" + databaseName + "'"); + assertTrue(this.rs.next(), "Database " + databaseName + " was not found."); + assertEquals(databaseName, this.rs.getString(1)); + + con.close(); + } finally { + this.stmt.executeUpdate("DROP DATABASE IF EXISTS " + StringUtils.quoteIdentifier(databaseName, true)); + } + } + + /** + * Tests fix for Bug#28725534, MULTI HOST CONNECTION WOULD BLOCK IN CONNECTION POOLING. + * + * @throws Exception + */ + @Test + public void testBug28725534() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + final Connection testConn = getFailoverConnection(); + + ExecutorService slowQueryExecutor = Executors.newSingleThreadExecutor(); + slowQueryExecutor.execute(() -> { + try { + testConn.createStatement().executeQuery("SELECT SLEEP(3)"); + } catch (SQLException e) { + fail("failed executing SLEEP()"); + } + }); + + TimeUnit.SECONDS.sleep(1); // Give it some time to start the slow query. + + long start = System.currentTimeMillis(); + testConn.equals(testConn); + testConn.toString(); + testConn.hashCode(); + long end = System.currentTimeMillis(); + + slowQueryExecutor.shutdown(); + slowQueryExecutor.awaitTermination(3, TimeUnit.SECONDS); + testConn.close(); + + assertTrue(end - start < 250, ".equals() took too long to exectute, the method is being locked by a synchronized block."); + } + + /** + * Tests fix for Bug#104067 (33054827), No reset autoCommit after unknown issue occurs. + * + * @throws Exception + */ + @Test + public void testBug104067() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug104067QueryInterceptor.class.getName()); + Connection testConn = getConnectionWithProps(props); + Statement testStmt = testConn.createStatement(); + + // Connection vs session autocommit value: + // 1. Default value. + assertTrue(testConn.getAutoCommit()); + this.rs = testStmt.executeQuery("SHOW SESSION VARIABLES LIKE 'autocommit'"); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(2).equalsIgnoreCase("ON")); + + // 2. After Connection.setAutcommit(true). + try { + testConn.setAutoCommit(true); + } catch (SQLException e) { + fail("Exception not expected.", e); + } + assertTrue(testConn.getAutoCommit()); + this.rs = testStmt.executeQuery("SHOW SESSION VARIABLES LIKE 'autocommit'"); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(2).equalsIgnoreCase("ON")); + + // 3. After Connection.setAutcommit(false). + assertThrows(SQLException.class, () -> { + testConn.setAutoCommit(false); + return null; + }); + assertTrue(testConn.getAutoCommit()); + this.rs = testStmt.executeQuery("SHOW SESSION VARIABLES LIKE 'autocommit'"); + this.rs.next(); + assertTrue(this.rs.getString(2).equalsIgnoreCase("ON")); + + // 4. After Connection.setAutcommit(true). + try { + testConn.setAutoCommit(true); + } catch (SQLException e) { + fail("Exception not expected.", e); + } + assertTrue(testConn.getAutoCommit()); + this.rs = testStmt.executeQuery("SHOW SESSION VARIABLES LIKE 'autocommit'"); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(2).equalsIgnoreCase("ON")); + } + + public static class Bug104067QueryInterceptor extends BaseQueryInterceptor { + @Override + public T preProcess(Supplier str, Query interceptedQuery) { + String sql = str.get(); + if (sql.equalsIgnoreCase("SET autocommit=0")) { + throw ExceptionFactory.createException("Artificial non-connection related exception while executing \"" + sql + "\""); + } + return super.preProcess(str, interceptedQuery); + } + } } diff --git a/src/test/java/testsuite/regression/DataSourceRegressionTest.java b/src/test/java/testsuite/regression/DataSourceRegressionTest.java index 554017c96..4f6de68b6 100644 --- a/src/test/java/testsuite/regression/DataSourceRegressionTest.java +++ b/src/test/java/testsuite/regression/DataSourceRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -34,6 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -70,6 +71,7 @@ import com.mysql.cj.MysqlConnection; import com.mysql.cj.conf.AbstractRuntimeProperty; import com.mysql.cj.conf.PropertyDefinitions; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.RuntimeProperty; import com.mysql.cj.jdbc.JdbcConnection; @@ -132,6 +134,8 @@ public void tearDown() throws Exception { public void testBug4808() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); + ds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); PooledConnection closeMeTwice = ds.getPooledConnection(); closeMeTwice.close(); closeMeTwice.close(); @@ -212,63 +216,65 @@ public void testBug3920() throws Exception { String port = System.getProperty(PropertyDefinitions.SYSP_testsuite_ds_port); String serverName = System.getProperty(PropertyDefinitions.SYSP_testsuite_ds_host); - // Only run this test if at least one of the above are set - if ((databaseName != null) || (serverName != null) || (userName != null) || (password != null) || (port != null)) { - MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); + assumeTrue((databaseName != null) || (serverName != null) || (userName != null) || (password != null) || (port != null), + "This test requires that at least one of the following properties is set:\n" + PropertyDefinitions.SYSP_testsuite_ds_db + ",\n" + + PropertyDefinitions.SYSP_testsuite_ds_user + ",\n" + PropertyDefinitions.SYSP_testsuite_ds_password + ",\n" + + PropertyDefinitions.SYSP_testsuite_ds_port + ",\n" + PropertyDefinitions.SYSP_testsuite_ds_host); - if (databaseName != null) { - ds.setDatabaseName(databaseName); - } + MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); - if (userName != null) { - ds.setUser(userName); - } + if (databaseName != null) { + ds.setDatabaseName(databaseName); + } - if (password != null) { - ds.setPassword(password); - } + if (userName != null) { + ds.setUser(userName); + } - if (port != null) { - ds.setPortNumber(Integer.parseInt(port)); - } + if (password != null) { + ds.setPassword(password); + } - if (serverName != null) { - ds.setServerName(serverName); - } + if (port != null) { + ds.setPortNumber(Integer.parseInt(port)); + } - bindDataSource(jndiName, ds); + if (serverName != null) { + ds.setServerName(serverName); + } - ConnectionPoolDataSource boundDs = null; + bindDataSource(jndiName, ds); - try { - boundDs = (ConnectionPoolDataSource) lookupDatasourceInJNDI(jndiName); + ConnectionPoolDataSource boundDs = null; - assertNotNull(boundDs, "Datasource not bound"); + try { + boundDs = (ConnectionPoolDataSource) lookupDatasourceInJNDI(jndiName); - Connection dsCon = null; - Statement dsStmt = null; + assertNotNull(boundDs, "Datasource not bound"); - try { - dsCon = boundDs.getPooledConnection().getConnection(); - dsStmt = dsCon.createStatement(); - dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920"); - dsStmt.executeUpdate("CREATE TABLE testBug3920 (field1 varchar(32))"); + Connection dsCon = null; + Statement dsStmt = null; - assertNotNull(dsCon, "Connection can not be obtained from data source"); - } finally { - if (dsStmt != null) { - dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920"); - dsStmt.close(); - } - if (dsCon != null) { - dsCon.close(); - } - } + try { + dsCon = boundDs.getPooledConnection().getConnection(); + dsStmt = dsCon.createStatement(); + dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920"); + dsStmt.executeUpdate("CREATE TABLE testBug3920 (field1 varchar(32))"); + + assertNotNull(dsCon, "Connection can not be obtained from data source"); } finally { - if (boundDs != null) { - this.ctx.unbind(jndiName); + if (dsStmt != null) { + dsStmt.executeUpdate("DROP TABLE IF EXISTS testBug3920"); + dsStmt.close(); + } + if (dsCon != null) { + dsCon.close(); } } + } finally { + if (boundDs != null) { + this.ctx.unbind(jndiName); + } } } @@ -336,6 +342,8 @@ private DataSource lookupDatasourceInJNDI(String jndiName) throws Exception { public void testCSC4616() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); + ds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); PooledConnection pooledConn = ds.getPooledConnection(); Connection physConn = pooledConn.getConnection(); Statement physStatement = physConn.createStatement(); @@ -419,6 +427,8 @@ private void removeFromRef(Reference ref, String key) { public void testBug32101() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); + ds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); PooledConnection pc = ds.getPooledConnection(); assertNotNull(pc.getConnection().prepareStatement("SELECT 1")); assertNotNull(pc.getConnection().prepareStatement("SELECT 1", Statement.RETURN_GENERATED_KEYS)); @@ -443,6 +453,8 @@ public void testBug35810() throws Exception { dsUrl += "connectTimeout=" + nonDefaultConnectTimeout; cpds.setUrl(dsUrl); + cpds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + cpds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); Connection dsConn = cpds.getPooledConnection().getConnection(); int configuredConnectTimeout = ((JdbcConnection) dsConn).getPropertySet().getIntegerProperty(PropertyKey.connectTimeout).getValue(); @@ -455,6 +467,8 @@ public void testBug35810() throws Exception { public void testBug42267() throws Exception { MysqlDataSource ds = new MysqlDataSource(); ds.setUrl(dbUrl); + ds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); Connection c = ds.getConnection(); String query = "select 1,2,345"; PreparedStatement ps = c.prepareStatement(query); @@ -474,6 +488,8 @@ public void testBug42267() throws Exception { public void testBug72890() throws Exception { MysqlXADataSource myDs = new MysqlXADataSource(); myDs.setUrl(BaseTestCase.dbUrl); + myDs.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + myDs.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); try { final Xid xid = new MysqlXid("72890".getBytes(), "72890".getBytes(), 1); @@ -508,9 +524,7 @@ public void testBug72890() throws Exception { connAliveChecks = -1; } } - if (connAliveChecks == 0) { - fail("Failed to kill the Connection id " + connId + " in a timely manner."); - } + assertFalse(connAliveChecks == 0, "Failed to kill the Connection id " + connId + " in a timely manner."); XAException xaEx = assertThrows(XAException.class, "Undetermined error occurred in the underlying Connection - check your data for consistency", new Callable() { diff --git a/src/test/java/testsuite/regression/DateTimeRegressionTest.java b/src/test/java/testsuite/regression/DateTimeRegressionTest.java index 0543d7f13..cb9d7b538 100644 --- a/src/test/java/testsuite/regression/DateTimeRegressionTest.java +++ b/src/test/java/testsuite/regression/DateTimeRegressionTest.java @@ -30,20 +30,253 @@ package testsuite.regression; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.Calendar; import java.util.Properties; +import java.util.TimeZone; import org.junit.jupiter.api.Test; import com.mysql.cj.MysqlType; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; +import com.mysql.cj.util.TimeUtil; import testsuite.BaseTestCase; public class DateTimeRegressionTest extends BaseTestCase { + /** + * Tests fix for BUG#3620 -- Timezone not respected correctly. + * + * @throws SQLException + */ + @Test + public void testBug3620() throws SQLException { + // FIXME: This test is sensitive to being in CST/CDT it seems + assumeTrue(TimeZone.getDefault().equals(TimeZone.getTimeZone("America/Chicago")), "This test is running only in America/Chicago time zone."); + + long epsillon = 3000; // 3 seconds time difference + + try { + this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3620"); + this.stmt.executeUpdate("CREATE TABLE testBug3620 (field1 TIMESTAMP)"); + + PreparedStatement tsPstmt = this.conn.prepareStatement("INSERT INTO testBug3620 VALUES (?)"); + + Calendar pointInTime = Calendar.getInstance(); + pointInTime.set(2004, 02, 29, 10, 0, 0); + + long pointInTimeOffset = pointInTime.getTimeZone().getRawOffset(); + + java.sql.Timestamp ts = new java.sql.Timestamp(pointInTime.getTime().getTime()); + + tsPstmt.setTimestamp(1, ts); + tsPstmt.executeUpdate(); + + String tsValueAsString = getSingleValue("testBug3620", "field1", null).toString(); + + System.out.println("Timestamp as string with no calendar: " + tsValueAsString.toString()); + + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + + this.stmt.executeUpdate("DELETE FROM testBug3620"); + + Statement tsStmt = this.conn.createStatement(); + + tsPstmt = this.conn.prepareStatement("INSERT INTO testBug3620 VALUES (?)"); + + tsPstmt.setTimestamp(1, ts, cal); + tsPstmt.executeUpdate(); + + tsValueAsString = getSingleValue("testBug3620", "field1", null).toString(); + + Timestamp tsValueAsTimestamp = (Timestamp) getSingleValue("testBug3620", "field1", null); + + System.out.println("Timestamp as string with UTC calendar: " + tsValueAsString.toString()); + System.out.println("Timestamp as Timestamp with UTC calendar: " + tsValueAsTimestamp); + + this.rs = tsStmt.executeQuery("SELECT field1 FROM testBug3620"); + this.rs.next(); + + Timestamp tsValueUTC = this.rs.getTimestamp(1, cal); + + // + // We use this testcase with other vendors, JDBC spec requires result set fields can only be read once, although MySQL doesn't require this ;) + // + this.rs = tsStmt.executeQuery("SELECT field1 FROM testBug3620"); + this.rs.next(); + + Timestamp tsValueStmtNoCal = this.rs.getTimestamp(1); + + System.out.println("Timestamp specifying UTC calendar from normal statement: " + tsValueUTC.toString()); + + PreparedStatement tsPstmtRetr = this.conn.prepareStatement("SELECT field1 FROM testBug3620"); + + this.rs = tsPstmtRetr.executeQuery(); + this.rs.next(); + + Timestamp tsValuePstmtUTC = this.rs.getTimestamp(1, cal); + + System.out.println("Timestamp specifying UTC calendar from prepared statement: " + tsValuePstmtUTC.toString()); + + // + // We use this testcase with other vendors, JDBC spec requires result set fields can only be read once, although MySQL doesn't require this ;) + // + this.rs = tsPstmtRetr.executeQuery(); + this.rs.next(); + + Timestamp tsValuePstmtNoCal = this.rs.getTimestamp(1); + + System.out.println("Timestamp specifying no calendar from prepared statement: " + tsValuePstmtNoCal.toString()); + + long stmtDeltaTWithCal = (ts.getTime() - tsValueStmtNoCal.getTime()); + + long deltaOrig = Math.abs(stmtDeltaTWithCal - pointInTimeOffset); + + assertTrue((deltaOrig < epsillon), "Difference between original timestamp and timestamp retrieved using java.sql.Statement " + + "set in database using UTC calendar is not ~= " + epsillon + ", it is actually " + deltaOrig); + + long pStmtDeltaTWithCal = (ts.getTime() - tsValuePstmtNoCal.getTime()); + + System.out.println( + Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) + " < " + epsillon + (Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) < epsillon)); + assertTrue((Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) < epsillon), + "Difference between original timestamp and timestamp retrieved using java.sql.PreparedStatement " + + "set in database using UTC calendar is not ~= " + epsillon + ", it is actually " + pStmtDeltaTWithCal); + + System.out.println("Difference between original ts and ts with no calendar: " + (ts.getTime() - tsValuePstmtNoCal.getTime()) + ", offset should be " + + pointInTimeOffset); + } finally { + this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3620"); + } + } + + /** + * Tests fix for Bug#5874, Timezone conversion goes in the wrong direction + * (when useTimezone=true and server timezone differs from client timezone). + * + * @throws Exception + */ + @Test + public void testBug5874() throws Exception { + assumeTrue(supportsTimeZoneNames(this.stmt), "This test requies the server with populated time zone tables."); + + TimeZone defaultTimeZone = TimeZone.getDefault(); + + try { + String clientTimeZoneName = "America/Los_Angeles"; + String connectionTimeZoneName = "America/Chicago"; + + TimeZone.setDefault(TimeZone.getTimeZone(clientTimeZoneName)); + + long offsetDifference = TimeZone.getDefault().getRawOffset() - TimeZone.getTimeZone(connectionTimeZoneName).getRawOffset(); + + SimpleDateFormat timestampFormat = TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd HH:mm:ss", null); + SimpleDateFormat timeFormat = TimeUtil.getSimpleDateFormat(null, "HH:mm:ss", null); + + long pointInTime = timestampFormat.parse("2004-10-04 09:19:00").getTime(); + + Properties props = new Properties(); + props.put("useTimezone", "true"); + props.put(PropertyKey.connectionTimeZone.getKeyName(), connectionTimeZoneName); + props.put(PropertyKey.forceConnectionTimeZoneToSession.getKeyName(), "true"); + props.put(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); + props.setProperty(PropertyKey.preserveInstants.getKeyName(), "true"); + + Connection tzConn = getConnectionWithProps(props); + Statement tzStmt = tzConn.createStatement(); + createTable("testBug5874", "(tstamp DATETIME, t TIME)"); + + PreparedStatement tsPstmt = tzConn.prepareStatement("INSERT INTO testBug5874 VALUES (?, ?)"); + + tsPstmt.setTimestamp(1, new Timestamp(pointInTime)); + tsPstmt.setTime(2, new Time(pointInTime)); + tsPstmt.executeUpdate(); + + this.rs = tzStmt.executeQuery("SELECT * from testBug5874"); + + while (this.rs.next()) { // Driver now converts/checks DATE/TIME/TIMESTAMP/DATETIME types when calling getString()... + String retrTimestampString = new String(this.rs.getBytes(1)); + Timestamp retrTimestamp = this.rs.getTimestamp(1); + + java.util.Date timestampOnServer = timestampFormat.parse(retrTimestampString); + + long retrievedOffsetForTimestamp = retrTimestamp.getTime() - timestampOnServer.getTime(); + + assertEquals(offsetDifference, retrievedOffsetForTimestamp, + "Original timestamp and timestamp retrieved using client timezone are not the same"); + + String retrTimeString = new String(this.rs.getBytes(2)); + Time retrTime = this.rs.getTime(2); + + java.util.Date timeOnServerAsDate = timeFormat.parse(retrTimeString); + Time timeOnServer = new Time(timeOnServerAsDate.getTime()); + + long retrievedOffsetForTime = retrTime.getTime() - timeOnServer.getTime(); + + assertEquals(0, retrievedOffsetForTime, "Original time and time retrieved using client timezone are not the same"); + } + + tzConn.close(); + } finally { + TimeZone.setDefault(defaultTimeZone); + } + } + + /** + * Tests fix for Bug#15604 (11745460), TIMEZONE DISCARDED STORING JAVA.UTIL.CALENDAR INTO DATETIME. + * + * @throws Exception + */ + @Test + public void testBug15604() throws Exception { + assumeTrue(supportsTimeZoneNames(this.stmt), "This test requies the server with populated time zone tables."); + + createTable("testBug15604_date_cal", "(field1 DATE)"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "time_zone='America/Chicago'"); + + Connection nonLegacyConn = getConnectionWithProps(props); + + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + + cal.set(Calendar.YEAR, 2005); + cal.set(Calendar.MONTH, 4); + cal.set(Calendar.DAY_OF_MONTH, 15); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + java.sql.Date sqlDate = new java.sql.Date(cal.getTime().getTime()); + + Calendar cal2 = Calendar.getInstance(); + cal2.setTime(sqlDate); + System.out.println(new java.sql.Date(cal2.getTime().getTime())); + this.pstmt = nonLegacyConn.prepareStatement("INSERT INTO testBug15604_date_cal VALUES (?)"); + + this.pstmt.setDate(1, sqlDate, cal); + this.pstmt.executeUpdate(); + this.rs = nonLegacyConn.createStatement().executeQuery("SELECT field1 FROM testBug15604_date_cal"); + this.rs.next(); + + assertEquals(sqlDate.getTime(), this.rs.getDate(1, cal).getTime()); + } + /** * Tests fix for Bug#20391832, SETOBJECT() FOR TYPES.TIME RESULTS IN EXCEPTION WHEN VALUE HAS FRACTIONAL PART. * @@ -56,6 +289,8 @@ public void testBug20391832() throws Exception { boolean withFract = versionMeetsMinimum(5, 6, 4); // fractional seconds are not supported in previous versions Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); for (boolean useSSPS : new boolean[] { false, true }) { for (boolean sendFr : new boolean[] { false, true }) { @@ -740,4 +975,191 @@ private void subTestBug20391832(Properties props, MysqlType targetType, String v + "insert into testBug20391832 values(" + (targetType == MysqlType.YEAR ? exp : "'" + exp + "'") + ")", query); } + /** + * Tests fix for Bug#20316640, GETTIMESTAMP() CALL WITH CALANDER VALUE NULL RESULTS IN NULLPOINTEREXCEPTION. + * + * @throws Exception + */ + @Test + public void testBug20316640() throws Exception { + createTable("testBug20316640", "(c1 timestamp, c2 time, c3 date)"); + this.stmt.execute("insert into testBug20316640 values('2038-01-19 03:14:07','18:59:59','9999-12-31')"); + this.rs = this.stmt.executeQuery("select * from testBug20316640"); + this.rs.next(); + System.out.println("Col 1 [" + this.rs.getTimestamp("c1", null) + "]"); + System.out.println("Col 2 [" + this.rs.getTime("c2", null) + "]"); + System.out.println("Col 3 [" + this.rs.getDate("c3", null) + "]"); + } + + /** + * Tests fix for Bug#20818678, GETFLOAT() AND GETDOUBLE() CALL ON YEAR COLUMN RESULTS IN EXCEPTION + * + * @throws Exception + */ + @Test + public void testBug20818678() throws Exception { + createTable("testBug20818678", "(c1 YEAR)"); + this.stmt.execute("insert into testBug20818678 values(2155)"); + + Connection con = null; + PreparedStatement ps = null; + + try { + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (boolean yearIsDateType : new boolean[] { true, false }) { + for (boolean useSSPS : new boolean[] { false, true }) { + props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "" + yearIsDateType); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); + + con = getConnectionWithProps(props); + ps = con.prepareStatement("select * from testBug20818678 "); + this.rs = ps.executeQuery(); + this.rs.next(); + + if (yearIsDateType) { + assertEquals("2155-01-01", this.rs.getString("c1")); + } else { + assertEquals("2155", this.rs.getString("c1")); + } + assertEquals(Float.valueOf(2155), this.rs.getFloat("c1")); + assertEquals(Double.valueOf(2155), this.rs.getDouble("c1")); + + ps.close(); + con.close(); + } + } + + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Tests fix for Bug#21308907, NO WAY TO GET THE FRACTIONAL PART OF A TIME FIELD WHEN USESERVERPREPSTMTS=TRUE. + * + * @throws Exception + */ + @Test + public void testBug21308907() throws Exception { + assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported in this server version."); + + createTable("testBug21308907", "(c1 time(6))"); + this.stmt.execute("insert into testBug21308907 values('12:59:59.123456')"); + + Connection con = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + con = getConnectionWithProps(props); + + this.stmt = con.createStatement(); + this.rs = this.stmt.executeQuery("select * from testBug21308907"); + this.rs.next(); + + assertEquals(123456000, this.rs.getTimestamp("c1").getNanos()); + String s = this.rs.getString("c1"); + assertEquals(".123456", s.substring(s.indexOf('.'))); + + this.rs.close(); + + this.pstmt = con.prepareStatement("select * from testBug21308907 "); + this.rs = this.pstmt.executeQuery(); + this.rs.next(); + + assertEquals(123456000, this.rs.getTimestamp("c1").getNanos()); + s = this.rs.getString("c1"); + assertEquals(".123456", s.substring(s.indexOf('.'))); + + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Tests fix for Bug#22305930, GETTIMESTAMP() CALL ON CLOSED RESULT SET PRODUCES NPE. + * + * @throws Exception + */ + @Test + public void testBug22305930() throws Exception { + ResultSet rs1 = this.stmt.executeQuery("select '2015-12-09 16:28:01' as tm"); + rs1.next(); + rs1.close(); + + assertThrows(SQLException.class, "Operation not allowed after ResultSet closed", () -> { + rs1.getTimestamp("tm"); + return null; + }); + } + + /** + * Tests fix for Bug#101413 (32099505), JAVA.TIME.LOCALDATETIME CANNOT BE CAST TO JAVA.SQL.TIMESTAMP. + * + * @throws Exception + */ + @Test + public void testBug101413() throws Exception { + assumeTrue(supportsTimeZoneNames(this.stmt), "This test requies the server with populated time zone tables."); + + createTable("testBug101413", "(createtime1 TIMESTAMP, createtime2 DATETIME)"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (boolean forceConnectionTimeZoneToSession : new boolean[] { false, true }) { + for (boolean preserveInstants : new boolean[] { false, true }) { + for (boolean useSSPS : new boolean[] { false, true }) { + props.setProperty(PropertyKey.forceConnectionTimeZoneToSession.getKeyName(), "" + forceConnectionTimeZoneToSession); + props.setProperty(PropertyKey.preserveInstants.getKeyName(), "" + preserveInstants); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + + Connection con = getConnectionWithProps(props); + PreparedStatement ps = con.prepareStatement("insert into testBug101413(createtime1, createtime2) values(?, ?)"); + + con.setAutoCommit(false); + for (int i = 1; i <= 20000; i++) { + ps.setObject(1, LocalDateTime.now()); + ps.setObject(2, LocalDateTime.now()); + ps.addBatch(); + if (i % 500 == 0) { + ps.executeBatch(); + ps.clearBatch(); + } + } + con.commit(); + } + } + } + } + + /** + * Tests fix for Bug#104559 (33232419), ResultSet.getObject(i, java.util.Date.class) throws NPE when the value is null. + * + * @throws Exception + */ + @Test + public void testBug104559() throws Exception { + createTable("testBug104559", "(`dt` DATE DEFAULT NULL)"); + this.stmt.executeUpdate("INSERT INTO testBug104559 VALUES (null)"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection con = getConnectionWithProps(props); + Statement st = con.createStatement(); + this.rs = st.executeQuery("SELECT * FROM testBug104559"); + assertTrue(this.rs.next()); + assertNull(this.rs.getObject("dt", java.util.Date.class)); + assertFalse(this.rs.next()); + } } diff --git a/src/test/java/testsuite/regression/MetaDataRegressionTest.java b/src/test/java/testsuite/regression/MetaDataRegressionTest.java index 7c141eb76..bc5294c52 100644 --- a/src/test/java/testsuite/regression/MetaDataRegressionTest.java +++ b/src/test/java/testsuite/regression/MetaDataRegressionTest.java @@ -64,11 +64,13 @@ import org.junit.jupiter.api.Test; import org.opentest4j.AssertionFailedError; -import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CharsetMappingWrapper; import com.mysql.cj.Constants; import com.mysql.cj.MysqlConnection; +import com.mysql.cj.NativeCharsetSettings; import com.mysql.cj.Query; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.jdbc.ClientPreparedStatement; @@ -234,6 +236,8 @@ public void testFixForBug1673() throws Exception { createTable("testBug1673", "(field_1 INT, field_2 INT)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection con = getConnectionWithProps(props); try { @@ -407,55 +411,45 @@ public void testGetPropertyInfo() throws Exception { */ @Test public void testIsCaseSensitive() throws Exception { - try { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testIsCaseSensitive"); - this.stmt.executeUpdate( - "CREATE TABLE testIsCaseSensitive (bin_char CHAR(1) BINARY, bin_varchar VARCHAR(64) BINARY, ci_char CHAR(1), ci_varchar VARCHAR(64))"); - this.rs = this.stmt.executeQuery("SELECT bin_char, bin_varchar, ci_char, ci_varchar FROM testIsCaseSensitive"); - - ResultSetMetaData rsmd = this.rs.getMetaData(); - assertTrue(rsmd.isCaseSensitive(1)); - assertTrue(rsmd.isCaseSensitive(2)); - assertTrue(!rsmd.isCaseSensitive(3)); - assertTrue(!rsmd.isCaseSensitive(4)); - } finally { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testIsCaseSensitive"); - } + createSchemaObject(this.stmt, "DATABASE", "testIsCaseSensitive", "DEFAULT CHARACTER SET utf8mb4"); + createTable("testIsCaseSensitive.testIsCaseSensitive", + "(bin_char CHAR(1) BINARY, bin_varchar VARCHAR(64) BINARY, ci_char CHAR(1), ci_varchar VARCHAR(64))"); + createTable("testIsCaseSensitive.testIsCaseSensitiveCs", + "(bin_char CHAR(1) CHARACTER SET latin1 COLLATE latin1_general_cs, bin_varchar VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_general_cs," + + "ci_char CHAR(1) CHARACTER SET latin1 COLLATE latin1_general_ci," + + "ci_varchar VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_general_ci, " + + "bin_tinytext TINYTEXT CHARACTER SET latin1 COLLATE latin1_general_cs, bin_text TEXT CHARACTER SET latin1 COLLATE latin1_general_cs," + + "bin_med_text MEDIUMTEXT CHARACTER SET latin1 COLLATE latin1_general_cs," + + "bin_long_text LONGTEXT CHARACTER SET latin1 COLLATE latin1_general_cs," + + "ci_tinytext TINYTEXT CHARACTER SET latin1 COLLATE latin1_general_ci, ci_text TEXT CHARACTER SET latin1 COLLATE latin1_general_ci," + + "ci_med_text MEDIUMTEXT CHARACTER SET latin1 COLLATE latin1_general_ci," + + "ci_long_text LONGTEXT CHARACTER SET latin1 COLLATE latin1_general_ci)"); + + this.rs = this.stmt.executeQuery("SELECT bin_char, bin_varchar, ci_char, ci_varchar FROM testIsCaseSensitive.testIsCaseSensitive"); + ResultSetMetaData rsmd = this.rs.getMetaData(); + assertTrue(rsmd.isCaseSensitive(1)); + assertTrue(rsmd.isCaseSensitive(2)); + assertTrue(!rsmd.isCaseSensitive(3)); + assertTrue(!rsmd.isCaseSensitive(4)); - try { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testIsCaseSensitiveCs"); - this.stmt.executeUpdate("CREATE TABLE testIsCaseSensitiveCs (bin_char CHAR(1) CHARACTER SET latin1 COLLATE latin1_general_cs," - + "bin_varchar VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_general_cs," - + "ci_char CHAR(1) CHARACTER SET latin1 COLLATE latin1_general_ci," - + "ci_varchar VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_general_ci, " - + "bin_tinytext TINYTEXT CHARACTER SET latin1 COLLATE latin1_general_cs, bin_text TEXT CHARACTER SET latin1 COLLATE latin1_general_cs," - + "bin_med_text MEDIUMTEXT CHARACTER SET latin1 COLLATE latin1_general_cs," - + "bin_long_text LONGTEXT CHARACTER SET latin1 COLLATE latin1_general_cs," - + "ci_tinytext TINYTEXT CHARACTER SET latin1 COLLATE latin1_general_ci, ci_text TEXT CHARACTER SET latin1 COLLATE latin1_general_ci," - + "ci_med_text MEDIUMTEXT CHARACTER SET latin1 COLLATE latin1_general_ci," - + "ci_long_text LONGTEXT CHARACTER SET latin1 COLLATE latin1_general_ci)"); - - this.rs = this.stmt.executeQuery("SELECT bin_char, bin_varchar, ci_char, ci_varchar, bin_tinytext, bin_text, bin_med_text, bin_long_text, " - + "ci_tinytext, ci_text, ci_med_text, ci_long_text FROM testIsCaseSensitiveCs"); + this.rs = this.stmt.executeQuery("SELECT bin_char, bin_varchar, ci_char, ci_varchar, bin_tinytext, bin_text, bin_med_text, bin_long_text, " + + "ci_tinytext, ci_text, ci_med_text, ci_long_text FROM testIsCaseSensitive.testIsCaseSensitiveCs"); - ResultSetMetaData rsmd = this.rs.getMetaData(); - assertTrue(rsmd.isCaseSensitive(1)); - assertTrue(rsmd.isCaseSensitive(2)); - assertTrue(!rsmd.isCaseSensitive(3)); - assertTrue(!rsmd.isCaseSensitive(4)); - - assertTrue(rsmd.isCaseSensitive(5)); - assertTrue(rsmd.isCaseSensitive(6)); - assertTrue(rsmd.isCaseSensitive(7)); - assertTrue(rsmd.isCaseSensitive(8)); - - assertTrue(!rsmd.isCaseSensitive(9)); - assertTrue(!rsmd.isCaseSensitive(10)); - assertTrue(!rsmd.isCaseSensitive(11)); - assertTrue(!rsmd.isCaseSensitive(12)); - } finally { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testIsCaseSensitiveCs"); - } + rsmd = this.rs.getMetaData(); + assertTrue(rsmd.isCaseSensitive(1)); + assertTrue(rsmd.isCaseSensitive(2)); + assertTrue(!rsmd.isCaseSensitive(3)); + assertTrue(!rsmd.isCaseSensitive(4)); + + assertTrue(rsmd.isCaseSensitive(5)); + assertTrue(rsmd.isCaseSensitive(6)); + assertTrue(rsmd.isCaseSensitive(7)); + assertTrue(rsmd.isCaseSensitive(8)); + + assertTrue(!rsmd.isCaseSensitive(9)); + assertTrue(!rsmd.isCaseSensitive(10)); + assertTrue(!rsmd.isCaseSensitive(11)); + assertTrue(!rsmd.isCaseSensitive(12)); } /** @@ -633,40 +627,36 @@ public void testBug4860() throws Exception { */ @Test public void testBug4880() throws Exception { - try { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4880"); - this.stmt.executeUpdate("CREATE TABLE testBug4880 (field1 VARCHAR(80), field2 TINYBLOB, field3 BLOB, field4 MEDIUMBLOB, field5 LONGBLOB)"); - this.rs = this.stmt.executeQuery("SELECT field1, field2, field3, field4, field5 FROM testBug4880"); - ResultSetMetaData rsmd = this.rs.getMetaData(); + createSchemaObject(this.stmt, "DATABASE", "testBug4880Db", "DEFAULT CHARACTER SET latin1"); + createTable("testBug4880Db.testBug4880", "(field1 VARCHAR(80), field2 TINYBLOB, field3 BLOB, field4 MEDIUMBLOB, field5 LONGBLOB)"); - assertEquals(80, rsmd.getPrecision(1)); - assertEquals(Types.VARCHAR, rsmd.getColumnType(1)); - assertEquals(80, rsmd.getColumnDisplaySize(1)); + this.rs = this.stmt.executeQuery("SELECT field1, field2, field3, field4, field5 FROM testBug4880Db.testBug4880"); + ResultSetMetaData rsmd = this.rs.getMetaData(); - assertEquals(255, rsmd.getPrecision(2)); - assertEquals(Types.VARBINARY, rsmd.getColumnType(2)); - assertTrue("TINYBLOB".equalsIgnoreCase(rsmd.getColumnTypeName(2))); - assertEquals(255, rsmd.getColumnDisplaySize(2)); - - assertEquals(65535, rsmd.getPrecision(3)); - assertEquals(Types.LONGVARBINARY, rsmd.getColumnType(3)); - assertTrue("BLOB".equalsIgnoreCase(rsmd.getColumnTypeName(3))); - assertEquals(65535, rsmd.getColumnDisplaySize(3)); - - assertEquals(16777215, rsmd.getPrecision(4)); - assertEquals(Types.LONGVARBINARY, rsmd.getColumnType(4)); - assertTrue("MEDIUMBLOB".equalsIgnoreCase(rsmd.getColumnTypeName(4))); - assertEquals(16777215, rsmd.getColumnDisplaySize(4)); - - // Server doesn't send us enough information to detect LONGBLOB - // type - assertEquals(Integer.MAX_VALUE, rsmd.getPrecision(5)); - assertEquals(Types.LONGVARBINARY, rsmd.getColumnType(5)); - assertTrue("LONGBLOB".equalsIgnoreCase(rsmd.getColumnTypeName(5))); - assertEquals(Integer.MAX_VALUE, rsmd.getColumnDisplaySize(5)); - } finally { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug4880"); - } + assertEquals(80, rsmd.getPrecision(1)); + assertEquals(Types.VARCHAR, rsmd.getColumnType(1)); + assertEquals(80, rsmd.getColumnDisplaySize(1)); + + assertEquals(255, rsmd.getPrecision(2)); + assertEquals(Types.VARBINARY, rsmd.getColumnType(2)); + assertTrue("TINYBLOB".equalsIgnoreCase(rsmd.getColumnTypeName(2))); + assertEquals(255, rsmd.getColumnDisplaySize(2)); + + assertEquals(65535, rsmd.getPrecision(3)); + assertEquals(Types.LONGVARBINARY, rsmd.getColumnType(3)); + assertTrue("BLOB".equalsIgnoreCase(rsmd.getColumnTypeName(3))); + assertEquals(65535, rsmd.getColumnDisplaySize(3)); + + assertEquals(16777215, rsmd.getPrecision(4)); + assertEquals(Types.LONGVARBINARY, rsmd.getColumnType(4)); + assertTrue("MEDIUMBLOB".equalsIgnoreCase(rsmd.getColumnTypeName(4))); + assertEquals(16777215, rsmd.getColumnDisplaySize(4)); + + // Server doesn't send us enough information to detect LONGBLOB type + assertEquals(Integer.MAX_VALUE, rsmd.getPrecision(5)); + assertEquals(Types.LONGVARBINARY, rsmd.getColumnType(5)); + assertTrue("LONGBLOB".equalsIgnoreCase(rsmd.getColumnTypeName(5))); + assertEquals(Integer.MAX_VALUE, rsmd.getColumnDisplaySize(5)); } /** @@ -725,49 +715,49 @@ public void testBug7081() throws Exception { */ @Test public void testBug7033() throws Exception { - if (!this.DISABLED_testBug7033) { // disabled for now - Connection big5Conn = null; - Statement big5Stmt = null; - PreparedStatement big5PrepStmt = null; + Connection big5Conn = null; + Statement big5Stmt = null; + PreparedStatement big5PrepStmt = null; - String testString = "\u5957 \u9910"; + String testString = "\u5957 \u9910"; - try { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Big5"); + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Big5"); - big5Conn = getConnectionWithProps(props); - big5Stmt = big5Conn.createStatement(); + big5Conn = getConnectionWithProps(props); + big5Stmt = big5Conn.createStatement(); - byte[] foobar = testString.getBytes("Big5"); - System.out.println(Arrays.toString(foobar)); + byte[] foobar = testString.getBytes("Big5"); + System.out.println(Arrays.toString(foobar)); - this.rs = big5Stmt.executeQuery("select 1 as '\u5957 \u9910'"); - String retrString = this.rs.getMetaData().getColumnName(1); - assertTrue(testString.equals(retrString)); + this.rs = big5Stmt.executeQuery("select 1 as '\u5957 \u9910'"); + String retrString = this.rs.getMetaData().getColumnName(1); + assertTrue(testString.equals(retrString)); - big5PrepStmt = big5Conn.prepareStatement("select 1 as '\u5957 \u9910'"); - this.rs = big5PrepStmt.executeQuery(); - retrString = this.rs.getMetaData().getColumnName(1); - assertTrue(testString.equals(retrString)); - } finally { - if (this.rs != null) { - this.rs.close(); - this.rs = null; - } + big5PrepStmt = big5Conn.prepareStatement("select 1 as '\u5957 \u9910'"); + this.rs = big5PrepStmt.executeQuery(); + retrString = this.rs.getMetaData().getColumnName(1); + assertTrue(testString.equals(retrString)); + } finally { + if (this.rs != null) { + this.rs.close(); + this.rs = null; + } - if (big5Stmt != null) { - big5Stmt.close(); + if (big5Stmt != null) { + big5Stmt.close(); - } + } - if (big5PrepStmt != null) { - big5PrepStmt.close(); - } + if (big5PrepStmt != null) { + big5PrepStmt.close(); + } - if (big5Conn != null) { - big5Conn.close(); - } + if (big5Conn != null) { + big5Conn.close(); } } } @@ -930,6 +920,8 @@ public void testBug9778() throws Exception { @Test public void testBug9769() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection con = getConnectionWithProps(props); try { @@ -992,7 +984,11 @@ public void testBug9917() throws Exception { this.rs.close(); // 'true' means only current database to be checked - Connection con = getConnectionWithProps("nullCatalogMeansCurrent=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); + Connection con = getConnectionWithProps(props); try { this.rs = con.getMetaData().getTables(null, null, tableName, null); while (this.rs.next()) { @@ -1065,27 +1061,40 @@ public void testBug11781() throws Exception { * this.conn.getCatalog(), null, "app tab", this.conn.getCatalog(), * null, "app tab"); */ - String db = ((JdbcConnection) this.conn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA - ? this.conn.getSchema() - : this.conn.getCatalog(); - this.rs = ((com.mysql.cj.jdbc.DatabaseMetaData) this.conn.getMetaData()).extractForeignKeyFromCreateTable(db, "app tab"); - assertTrue(this.rs.next(), "must return a row"); - assertEquals(("comment; APPFK(`C1`) REFER `" + db + "`/ `app tab` (`C1`)").toUpperCase(), this.rs.getString(3).toUpperCase()); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (boolean useIS : new boolean[] { false, true }) { + for (String databaseTerm : new String[] { "CATALOG", "SCHEMA" }) { + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); + props.setProperty(PropertyKey.databaseTerm.getKeyName(), databaseTerm); - this.rs.close(); + System.out.println("useInformationSchema=" + useIS + ", databaseTerm=" + databaseTerm); - this.rs = ((JdbcConnection) this.conn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA - ? this.conn.getMetaData().getImportedKeys(null, this.conn.getCatalog(), "app tab") - : this.conn.getMetaData().getImportedKeys(this.conn.getCatalog(), null, "app tab"); + Connection con = getConnectionWithProps(props); - assertTrue(this.rs.next()); + String db = databaseTerm.contentEquals("SCHEMA") ? con.getSchema() : con.getCatalog(); + this.rs = ((com.mysql.cj.jdbc.DatabaseMetaData) con.getMetaData()).extractForeignKeyFromCreateTable(db, "app tab"); + assertTrue(this.rs.next(), "must return a row"); - this.rs = ((JdbcConnection) this.conn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA - ? this.conn.getMetaData().getExportedKeys(null, this.conn.getCatalog(), "app tab") - : this.conn.getMetaData().getExportedKeys(this.conn.getCatalog(), null, "app tab"); + assertEquals(("comment; APPFK(`C1`) REFER `" + db + "`/ `app tab` (`C1`)").toUpperCase(), this.rs.getString(3).toUpperCase()); + + this.rs.close(); + + this.rs = databaseTerm.contentEquals("SCHEMA") ? con.getMetaData().getImportedKeys(null, con.getSchema(), "app tab") + : con.getMetaData().getImportedKeys(con.getCatalog(), null, "app tab"); + + assertTrue(this.rs.next()); + + this.rs = databaseTerm.contentEquals("SCHEMA") ? con.getMetaData().getExportedKeys(null, con.getSchema(), "app tab") + : con.getMetaData().getExportedKeys(con.getCatalog(), null, "app tab"); + + assertTrue(this.rs.next()); + + } + } - assertTrue(this.rs.next()); } /** @@ -1137,7 +1146,8 @@ public void testBug12975() throws Exception { try { Properties props = new Properties(); - + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.overrideSupportsIntegrityEnhancementFacility.getKeyName(), "true"); overrideConn = getConnectionWithProps(props); @@ -1332,8 +1342,10 @@ private void testBug18554(int columnNameLength) throws Exception { } private void checkRsmdForBug13277(ResultSetMetaData rsmd) throws SQLException { - int i = ((com.mysql.cj.jdbc.ConnectionImpl) this.conn).getSession().getServerSession().getMaxBytesPerChar(CharsetMapping - .getJavaEncodingForMysqlCharset(((com.mysql.cj.jdbc.JdbcConnection) this.conn).getSession().getServerSession().getServerDefaultCharset())); + int i = ((com.mysql.cj.jdbc.ConnectionImpl) this.conn).getSession().getServerSession().getCharsetSettings() + .getMaxBytesPerChar(CharsetMappingWrapper.getStaticJavaEncodingForMysqlCharset( + ((NativeCharsetSettings) ((com.mysql.cj.jdbc.JdbcConnection) this.conn).getSession().getServerSession().getCharsetSettings()) + .getServerDefaultCharset())); if (i == 1) { // This is INT field but still processed in // ResultsetMetaData.getColumnDisplaySize @@ -1382,6 +1394,8 @@ public void testBug21267() throws Exception { this.pstmt.close(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.generateSimpleParameterMetadata.getKeyName(), "true"); this.pstmt = getConnectionWithProps(props).prepareStatement("SELECT Col1, Col2,Col4 FROM bug21267 WHERE Col1=?"); @@ -1392,8 +1406,9 @@ public void testBug21267() throws Exception { } /** - * Tests fix for BUG#21544 - When using information_schema for metadata, COLUMN_SIZE for getColumns() is not clamped to range of java.lang.Integer as is the - * case when not using information_schema, thus leading to a truncation exception that isn't present when not using information_schema. + * Tests fix for BUG#21544 - When using information_schema for metadata, COLUMN_SIZE for getColumns() is not clamped to range + * of java.lang.Integer as is the case when not using information_schema, thus leading to a truncation exception that + * isn't present when not using information_schema. * * @throws Exception */ @@ -1404,6 +1419,8 @@ public void testBug21544() throws Exception { Connection infoSchemConn = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); @@ -1439,6 +1456,8 @@ public void testBug22613() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); infoSchemConn = getConnectionWithProps(props); @@ -1582,9 +1601,13 @@ public void testBug23304() throws Exception { try { Properties noInfoSchemaProps = new Properties(); + noInfoSchemaProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + noInfoSchemaProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); noInfoSchemaProps.setProperty(PropertyKey.useInformationSchema.getKeyName(), "false"); Properties infoSchemaProps = new Properties(); + infoSchemaProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + infoSchemaProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); infoSchemaProps.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); infoSchemaProps.setProperty(PropertyKey.dumpQueriesOnException.getKeyName(), "true"); @@ -1692,14 +1715,10 @@ public void testBug23304() throws Exception { private void compareResultSets(ResultSet expected, ResultSet actual) throws Exception { if (expected == null) { - if (actual != null) { - fail("Expected null result set, actual was not null."); - } else { - return; - } - } else if (actual == null) { - fail("Expected non-null actual result set."); + assertTrue(actual == null, "Expected null result set, actual was not null."); + return; } + assertFalse(actual == null, "Expected non-null actual result set."); expected.last(); @@ -1750,9 +1769,10 @@ private void compareResultSets(ResultSet expected, ResultSet actual) throws Exce } if ("CHAR_OCTET_LENGTH".equals(metadataExpected.getColumnName(i + 1))) { - if (((com.mysql.cj.jdbc.ConnectionImpl) this.conn).getSession().getServerSession() - .getMaxBytesPerChar(CharsetMapping.getJavaEncodingForMysqlCharset( - ((com.mysql.cj.jdbc.JdbcConnection) this.conn).getSession().getServerSession().getServerDefaultCharset())) > 1) { + if (((com.mysql.cj.jdbc.ConnectionImpl) this.conn).getSession().getServerSession().getCharsetSettings() + .getMaxBytesPerChar(CharsetMappingWrapper + .getStaticJavaEncodingForMysqlCharset(((NativeCharsetSettings) ((com.mysql.cj.jdbc.JdbcConnection) this.conn) + .getSession().getServerSession().getCharsetSettings()).getServerDefaultCharset())) > 1) { continue; // SHOW CREATE and CHAR_OCT *will* differ } } @@ -1825,7 +1845,11 @@ public void testBug27915() throws Exception { checkBug27915(); - this.rs = getConnectionWithProps("useInformationSchema=true").getMetaData().getColumns(this.conn.getCatalog(), null, "testBug27915", "%"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + this.rs = getConnectionWithProps(props).getMetaData().getColumns(this.conn.getCatalog(), null, "testBug27915", "%"); this.rs.next(); checkBug27915(); @@ -1883,6 +1907,8 @@ public void testBug27916() throws Exception { } Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "false"); ArrayList types = new ArrayList<>(); Connection PropConn = getConnectionWithProps(props); @@ -1909,6 +1935,8 @@ public void testBug27916() throws Exception { PropConn.close(); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); PropConn = getConnectionWithProps(props); dbmd = PropConn.getMetaData(); @@ -2033,6 +2061,8 @@ public void testBug33594() throws Exception { } Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "false"); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "false"); props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "100"); @@ -2053,6 +2083,8 @@ public void testBug33594() throws Exception { } Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useInformationSchema.getKeyName(), "false"); props2.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); props2.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "100"); @@ -2107,6 +2139,8 @@ public void testBug34194() throws Exception { @Test public void testNoSystemTablesReturned() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -2154,7 +2188,11 @@ public void testNoSystemTablesReturned() throws Exception { @Test public void testABunchOfReturnTypes() throws Exception { checkABunchOfReturnTypesForConnection(this.conn); - checkABunchOfReturnTypesForConnection(getConnectionWithProps("useInformationSchema=true")); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + checkABunchOfReturnTypesForConnection(getConnectionWithProps(props)); } private void checkABunchOfReturnTypesForConnection(Connection mdConn) throws Exception { @@ -2375,7 +2413,11 @@ private void checkTypes(ResultSet rsToCheck, int[] types) throws Exception { public void testBug43714() throws Exception { Connection c_IS = null; try { - c_IS = getConnectionWithProps("useInformationSchema=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + c_IS = getConnectionWithProps(props); DatabaseMetaData dbmd = c_IS.getMetaData(); this.rs = dbmd.getExportedKeys("x", "y", "z"); } finally { @@ -2397,7 +2439,11 @@ public void testBug43714() throws Exception { public void testBug41269() throws Exception { createProcedure("bug41269", "(in param1 int, out result varchar(197)) BEGIN select 1, ''; END"); - Connection con = getConnectionWithProps("nullCatalogMeansCurrent=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); + Connection con = getConnectionWithProps(props); try { ResultSet procMD = con.getMetaData().getProcedureColumns(null, null, "bug41269", "%"); assertTrue(procMD.next()); @@ -2416,7 +2462,11 @@ public void testBug41269() throws Exception { public void testBug31187() throws Exception { createTable("testBug31187", "(field1 int)"); - Connection nullCatConn = getConnectionWithProps("nullCatalogMeansCurrent=false"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "false"); + Connection nullCatConn = getConnectionWithProps(props); DatabaseMetaData dbmd = nullCatConn.getMetaData(); ResultSet dbTblCols = dbmd.getColumns(null, null, "testBug31187", "%"); @@ -2477,6 +2527,8 @@ public void testBug51912() throws Exception { Connection overrideConn = null; try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "false"); overrideConn = getConnectionWithProps(props); @@ -2508,6 +2560,8 @@ public void testBug38367() throws Exception { "(OUT nfact VARCHAR(100), IN ccuenta VARCHAR(100),\nOUT ffact VARCHAR(100),\nOUT fdoc VARCHAR(100))" + "\nBEGIN\nEND"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection con = getConnectionWithProps(props); try { DatabaseMetaData dbMeta = con.getMetaData(); @@ -2535,6 +2589,8 @@ public void testBug57808() throws Exception { try { createTable("bug57808", "(ID INT(3) NOT NULL PRIMARY KEY, ADate DATE NOT NULL)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); if (versionMeetsMinimum(5, 7, 4)) { props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); } @@ -2555,11 +2611,8 @@ public void testBug57808() throws Exception { this.rs = this.stmt.executeQuery("SELECT ID, ADate FROM bug57808 WHERE ID = 1"); if (this.rs.first()) { Date theDate = this.rs.getDate("ADate"); - if (theDate == null) { - assertTrue(this.rs.wasNull(), "wasNull is FALSE"); - } else { - fail("Original date was not NULL!"); - } + assertNull(theDate, "Original date was not NULL!"); + assertTrue(this.rs.wasNull(), "wasNull is FALSE"); } } finally { } @@ -2580,6 +2633,8 @@ public void testBug61150() throws Exception { Statement savedSt = this.stmt; Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.remove(PropertyKey.DBNAME.getKeyName()); Connection conn1 = DriverManager.getConnection(newUrlToTestNoDB.toString(), props); @@ -2630,6 +2685,8 @@ public void testBug61150() throws Exception { @Test public void testBug61332() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), QueryInterceptorBug61332.class.getName()); @@ -2742,9 +2799,17 @@ public void testBug61203() throws Exception { private void testBug61203checks(Connection rootConn, Connection userConn) throws SQLException { CallableStatement cStmt = null; + // 1.1. with information schema - rootConn = getConnectionWithProps("noAccessToProcedureBodies=true,useInformationSchema=true"); - userConn = getConnectionWithProps("noAccessToProcedureBodies=true,useInformationSchema=true,user=bug61203user,password=foo"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.noAccessToProcedureBodies.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + rootConn = getConnectionWithProps(props); + props.setProperty(PropertyKey.USER.getKeyName(), "bug61203user"); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), "foo"); + userConn = getConnectionWithProps(props); // 1.1.1. root call; callFunction(cStmt, rootConn); callProcedure(cStmt, rootConn); @@ -2753,8 +2818,15 @@ private void testBug61203checks(Connection rootConn, Connection userConn) throws callProcedure(cStmt, userConn); // 1.2. no information schema - rootConn = getConnectionWithProps("noAccessToProcedureBodies=true,useInformationSchema=false"); - userConn = getConnectionWithProps("noAccessToProcedureBodies=true,useInformationSchema=false,user=bug61203user,password=foo"); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.noAccessToProcedureBodies.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "false"); + rootConn = getConnectionWithProps(props); + props.setProperty(PropertyKey.USER.getKeyName(), "bug61203user"); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), "foo"); + userConn = getConnectionWithProps(props); // 1.2.1. root call; callFunction(cStmt, rootConn); callProcedure(cStmt, rootConn); @@ -2825,6 +2897,8 @@ public void testBug63800() throws Exception { for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); if (versionMeetsMinimum(5, 7, 4)) { props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); } @@ -3066,7 +3140,11 @@ public void testBug16436511() throws Exception { @Test public void testBug68098() throws Exception { String[] testStepDescription = new String[] { "MySQL MetaData", "I__S MetaData" }; - Connection connUseIS = getConnectionWithProps("useInformationSchema=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + Connection connUseIS = getConnectionWithProps(props); Connection[] testConnections = new Connection[] { this.conn, connUseIS }; String[] expectedIndexesOrder = new String[] { "index_1", "index_1", "index_3", "PRIMARY", "index_2", "index_2", "index_4" }; @@ -3114,6 +3192,9 @@ public void testBug65871() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode=ansi"); nonPedanticConn = getConnectionWithProps(props); @@ -3168,11 +3249,8 @@ private void testBug65871_testCatalog(String unquotedDbName, String quotedDbName st1.executeUpdate("DROP DATABASE IF EXISTS " + quotedDbName); st1.executeUpdate("CREATE DATABASE " + quotedDbName); this.rs = st1.executeQuery("show databases like '" + unquotedDbName + "'"); - if (this.rs.next()) { - assertEquals(unquotedDbName, this.rs.getString(1)); - } else { - fail("Database " + unquotedDbName + " (quoted " + quotedDbName + ") not found."); - } + assertTrue(this.rs.next(), "Database " + unquotedDbName + " (quoted " + quotedDbName + ") not found."); + assertEquals(unquotedDbName, this.rs.getString(1)); boolean pedantic = ((MysqlConnection) conn1).getPropertySet().getBooleanProperty(PropertyKey.pedantic).getValue(); @@ -3372,6 +3450,8 @@ private void testBug65871_testTable(String unquotedDbName, String quotedDbName, @Test public void testBug69298() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); Connection testConn; @@ -3639,6 +3719,8 @@ private void checkGetProcedureColumnsForBug69298(String stepDescription, Connect @Test public void testBug17248345() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); Connection testConn; @@ -3760,9 +3842,19 @@ private void checkMetaDataInfoForBug17248345(Connection testConn) throws Excepti @Test public void testBug69290() throws Exception { String[] testStepDescription = new String[] { "MySQL MetaData", "I__S MetaData" }; - Connection connUseIS = getConnectionWithProps("useInformationSchema=true"); - Connection connNullAll = getConnectionWithProps("nullCatalogMeansCurrent=false"); - Connection connUseISAndNullAll = getConnectionWithProps("useInformationSchema=true,nullCatalogMeansCurrent=false"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + Connection connUseIS = getConnectionWithProps(props); + + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "false"); + Connection connUseISAndNullAll = getConnectionWithProps(props); + + props.remove(PropertyKey.useInformationSchema.getKeyName()); + Connection connNullAll = getConnectionWithProps(props); + boolean dbMapsToSchema = ((JdbcConnection) this.conn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm) .getValue() == DatabaseTerm.SCHEMA; @@ -3780,9 +3872,7 @@ public void testBug69290() throws Exception { int idx = 0; while (this.rs.next()) { String message = testStepDescription[i] + ", table type '" + this.rs.getString("TABLE_TYPE") + "'"; - if (idx >= tableTypes.size()) { - fail(message + " not expected."); - } + assertFalse(idx >= tableTypes.size(), message + " not expected."); assertEquals(tableTypes.get(idx++), this.rs.getString("TABLE_TYPE"), message); } } @@ -3887,7 +3977,11 @@ public void testBug35115() throws Exception { /* * test connection with property 'yearIsDateType=false' */ - testConnection = getConnectionWithProps("yearIsDateType=false"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "false"); + testConnection = getConnectionWithProps(props); Statement st = testConnection.createStatement(); this.rs = st.executeQuery("SELECT * FROM testBug35115"); rsMetaData = this.rs.getMetaData(); @@ -3903,7 +3997,8 @@ public void testBug35115() throws Exception { /* * test connection with property 'yearIsDateType=true' */ - testConnection = getConnectionWithProps("yearIsDateType=true"); + props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "true"); + testConnection = getConnectionWithProps(props); st = testConnection.createStatement(); this.rs = st.executeQuery("SELECT * FROM testBug35115"); rsMetaData = this.rs.getMetaData(); @@ -3934,7 +4029,11 @@ public void testBug68307() throws Exception { checkProcedureColumnTypeForBug68307("MySQL", testDbMetaData); // test metadata from I__S - Connection connUseIS = getConnectionWithProps("useInformationSchema=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + Connection connUseIS = getConnectionWithProps(props); testDbMetaData = connUseIS.getMetaData(); checkFunctionColumnTypeForBug68307("I__S", testDbMetaData); checkProcedureColumnTypeForBug68307("I__S", testDbMetaData); @@ -3989,7 +4088,11 @@ public void testBug44451() throws Exception { String methodName; List expectedFields; String[] testStepDescription = new String[] { "MySQL MetaData", "I__S MetaData" }; - Connection connUseIS = getConnectionWithProps("useInformationSchema=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + Connection connUseIS = getConnectionWithProps(props); Connection[] testConnections = new Connection[] { this.conn, connUseIS }; methodName = "getClientInfoProperties()"; @@ -4048,7 +4151,14 @@ public void testBug20504139() throws Exception { useFuncsInProcs); System.out.printf("testBug20504139_%d: %s%n", testCase, connProps); - Connection testConn = getConnectionWithProps(connProps); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.pedantic.getKeyName(), "" + usePedantic); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useInformationSchema); + props.setProperty(PropertyKey.getProceduresReturnsFunctions.getKeyName(), "" + useFuncsInProcs); + + Connection testConn = getConnectionWithProps(props); DatabaseMetaData dbmd = testConn.getMetaData(); boolean dbMapsToSchema = ((JdbcConnection) testConn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm) .getValue() == DatabaseTerm.SCHEMA; @@ -4081,9 +4191,8 @@ public void testBug20504139() throws Exception { i++; } } catch (SQLException e) { - if (e.getMessage().matches("FUNCTION `testBug20504139(:?`{2})?[fp]` does not exist")) { - fail(testCase + "." + i + ". failed to retrieve function columns, with getProcedureColumns(), from database meta data."); - } + assertFalse(e.getMessage().matches("FUNCTION `testBug20504139(:?`{2})?[fp]` does not exist"), + testCase + "." + i + ". failed to retrieve function columns, with getProcedureColumns(), from database meta data."); throw e; } @@ -4104,9 +4213,8 @@ public void testBug20504139() throws Exception { i++; } } catch (SQLException e) { - if (e.getMessage().matches("PROCEDURE `testBug20504139(:?`{2})?[fp]` does not exist")) { - fail(testCase + "." + i + ". failed to retrieve prodedure columns, with getProcedureColumns(), from database meta data."); - } + assertFalse(e.getMessage().matches("PROCEDURE `testBug20504139(:?`{2})?[fp]` does not exist"), + testCase + "." + i + ". failed to retrieve prodedure columns, with getProcedureColumns(), from database meta data."); throw e; } @@ -4130,9 +4238,8 @@ public void testBug20504139() throws Exception { i++; } } catch (SQLException e) { - if (e.getMessage().matches("FUNCTION `testBug20504139(:?`{2})?[fp]` does not exist")) { - fail(testCase + "." + i + ". failed to retrieve function columns, with getFunctionColumns(), from database meta data."); - } + assertFalse(e.getMessage().matches("FUNCTION `testBug20504139(:?`{2})?[fp]` does not exist"), + testCase + "." + i + ". failed to retrieve function columns, with getFunctionColumns(), from database meta data."); throw e; } @@ -4150,9 +4257,8 @@ public void testBug20504139() throws Exception { i++; } } catch (SQLException e) { - if (e.getMessage().matches("PROCEDURE `testBug20504139(:?`{2})?[fp]` does not exist")) { - fail(testCase + "." + i + ". failed to retrieve procedure columns, with getFunctionColumns(), from database meta data."); - } + assertFalse(e.getMessage().matches("PROCEDURE `testBug20504139(:?`{2})?[fp]` does not exist"), + testCase + "." + i + ". failed to retrieve procedure columns, with getFunctionColumns(), from database meta data."); throw e; } } finally { @@ -4209,8 +4315,10 @@ public void testBug21215151() throws Exception { @Test public void testBug19803348() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.getProceduresReturnsFunctions.getKeyName(), "false"); - props.setProperty("nullCatalogMeansCurrent", "false"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "false"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { @@ -4349,12 +4457,17 @@ public void testBug20727196() throws Exception { "(p ENUM ('Yes', 'No')) RETURNS ENUM ('Yes', 'No') DETERMINISTIC BEGIN RETURN IF(p='Yes', 'Yes', if(p='No', 'No', '?')); END"); createProcedure("testBug20727196_p1", "(p ENUM ('Yes', 'No')) BEGIN SELECT IF(p='Yes', 'Yay!', if(p='No', 'Ney!', 'What?')); END"); - for (String connProps : new String[] { "nullCatalogMeansCurrent=true,getProceduresReturnsFunctions=false,useInformationSchema=false", - "nullCatalogMeansCurrent=true,getProceduresReturnsFunctions=false,useInformationSchema=true" }) { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); + props.setProperty(PropertyKey.getProceduresReturnsFunctions.getKeyName(), "false"); + for (boolean useInformationSchema : new boolean[] { false, true }) { + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useInformationSchema); Connection testConn = null; try { - testConn = getConnectionWithProps(connProps); + testConn = getConnectionWithProps(props); DatabaseMetaData dbmd = testConn.getMetaData(); this.rs = dbmd.getFunctionColumns(null, null, "testBug20727196_%", "%"); @@ -4462,7 +4575,7 @@ public void testBug23212347() throws Exception { Properties props = new Properties(); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); @@ -4506,6 +4619,8 @@ public void testBug73775() throws Exception { dbMapsToSchema ? "Y" : "N"); final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), Boolean.toString(useIS)); props.setProperty(PropertyKey.getProceduresReturnsFunctions.getKeyName(), Boolean.toString(inclFuncs)); props.setProperty(PropertyKey.databaseTerm.getKeyName(), dbMapsToSchema ? DatabaseTerm.SCHEMA.name() : DatabaseTerm.CATALOG.name()); @@ -4641,6 +4756,8 @@ public void testBug87826() throws Exception { createProcedure("bug87826", "(in param1 int, out result varchar(197)) BEGIN select 1, ''; END"); final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); for (String useIS : new String[] { "false", "true" }) { @@ -4683,6 +4800,8 @@ public void testBug90887() throws Exception { List resNames = new ArrayList<>(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "false"); Connection con = getConnectionWithProps(props); DatabaseMetaData metaData = con.getMetaData(); @@ -4765,6 +4884,8 @@ public void testBug29186870() throws Exception { String fname = "testBug29186870func"; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); try { for (String useIS : new String[] { "false", "true" }) { @@ -4929,7 +5050,7 @@ public void testBug97413() throws Exception { String errMsg = "useIS=" + useIS + ", useSSPS=" + useSSPS + ", tinyInt1isBit=" + tinyInt1isBit + ", transformedBitIsBoolean=" + transformedBitIsBoolean + "\n"; props.clear(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); @@ -5148,4 +5269,127 @@ public void testBug102076() throws Exception { } while (this.rs.next()); } + + /** + * Tests fix for Bug#95280 (29757140), DATABASEMETADATA.GETIMPORTEDKEYS RETURNS DOUBLE THE ROWS. + * + * @throws Exception + */ + @Test + public void testBug95280() throws Exception { + String databaseName1 = "dbBug95280_1"; + createDatabase(databaseName1); + createTable(databaseName1 + ".table1", + "(cat_id int not null auto_increment primary key, cat_name varchar(255) not null, cat_description text) ENGINE=InnoDB;"); + createTable(databaseName1 + ".table2", + "(prd_id int not null auto_increment primary key, prd_name varchar(355) not null, prd_price decimal, cat_id int not null," + + " FOREIGN KEY fk_cat(cat_id) REFERENCES table1(cat_id) ON UPDATE CASCADE ON DELETE RESTRICT) ENGINE=InnoDB;"); + + String databaseName2 = "dbBug95280_2"; + createDatabase(databaseName2); + createTable(databaseName2 + ".table1", + "(cat_id int not null auto_increment primary key, cat_name varchar(255) not null, cat_description text) ENGINE=InnoDB;"); + createTable(databaseName2 + ".table2", + "(prd_id int not null auto_increment primary key, prd_name varchar(355) not null, prd_price decimal, cat_id int not null," + + " FOREIGN KEY fk_cat(cat_id) REFERENCES table1(cat_id) ON UPDATE CASCADE ON DELETE RESTRICT) ENGINE=InnoDB;"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (boolean useIS : new boolean[] { false, true }) { + for (String databaseTerm : new String[] { "CATALOG", "SCHEMA" }) { + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); + props.setProperty(PropertyKey.databaseTerm.getKeyName(), databaseTerm); + + Connection con = getConnectionWithProps(props); + DatabaseMetaData meta = con.getMetaData(); + + this.rs = databaseTerm.contentEquals("SCHEMA") ? meta.getImportedKeys(null, databaseName1, "table2") + : meta.getImportedKeys(databaseName1, null, "table2"); + assertTrue(this.rs.next()); + assertEquals("table2", this.rs.getString("FKTABLE_NAME")); + assertEquals("cat_id", this.rs.getString("FKCOLUMN_NAME")); + assertEquals(1, this.rs.getInt("KEY_SEQ")); + assertFalse(this.rs.next()); + con.close(); + } + } + } + + /** + * Tests fix for Bug#104641 (33237255), DatabaseMetaData.getImportedKeys can return duplicated foreign keys. + * + * @throws Exception + */ + @Test + public void testBug104641() throws Exception { + String databaseName1 = "dbBug104641"; + createDatabase(databaseName1); + createTable(databaseName1 + ".table1", + "(`CREATED` datetime DEFAULT NULL,`ID` bigint NOT NULL AUTO_INCREMENT,`LRN_ID` bigint DEFAULT '0',`USERNAME` varchar(50) NOT NULL," + + "PRIMARY KEY (`ID`),UNIQUE KEY `U_table1_LRN_ID` (`LRN_ID`),UNIQUE KEY `U_table1_USERNAME` (`USERNAME`) )"); + createTable(databaseName1 + ".table2", + "(`AL_ID` varchar(50) DEFAULT NULL,`CREATED` datetime DEFAULT NULL,`ID` bigint NOT NULL AUTO_INCREMENT,`USER_ID` bigint DEFAULT NULL," + + "PRIMARY KEY (`ID`),KEY `fk_table2_user_id` (`USER_ID`),KEY `index_al_id1` (`AL_ID`)," + + "CONSTRAINT `fk_table2_user_id` FOREIGN KEY (`USER_ID`) REFERENCES `table1` (`ID`) )"); + createTable(databaseName1 + ".table3", + "(`AL_ID` varchar(50) DEFAULT NULL,`ID` bigint NOT NULL AUTO_INCREMENT,`USER_ID` bigint DEFAULT NULL,`LRN_ID` bigint DEFAULT '0'," + + "PRIMARY KEY (`ID`),KEY `fk_table3_LRN_ID` (`LRN_ID`),KEY `index_al_id2` (`AL_ID`)," + + "CONSTRAINT `fk_table3_LRN_ID` FOREIGN KEY `U_table1_LRN_ID` (`LRN_ID`) REFERENCES `table1` (`LRN_ID`) )"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + for (boolean useIS : new boolean[] { false, true }) { + for (String databaseTerm : new String[] { "CATALOG", "SCHEMA" }) { + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); + props.setProperty(PropertyKey.databaseTerm.getKeyName(), databaseTerm); + + boolean dbTermIsSchema = databaseTerm.contentEquals("SCHEMA"); + + String err = "useInformationSchema=" + useIS + ", databaseTerm=" + databaseTerm; + Connection con = getConnectionWithProps(props); + DatabaseMetaData meta = con.getMetaData(); + + this.rs = dbTermIsSchema ? meta.getImportedKeys(null, databaseName1, "table2") : meta.getImportedKeys(databaseName1, null, "table2"); + assertTrue(this.rs.next(), err); + assertEquals(dbTermIsSchema ? "def" : databaseName1, this.rs.getString("PKTABLE_CAT"), err); + assertEquals(dbTermIsSchema ? databaseName1 : null, this.rs.getString("PKTABLE_SCHEM"), err); + assertEquals(dbTermIsSchema ? "def" : databaseName1, this.rs.getString("FKTABLE_CAT"), err); + assertEquals(dbTermIsSchema ? databaseName1 : null, this.rs.getString("FKTABLE_SCHEM"), err); + assertEquals("table1", this.rs.getString("PKTABLE_NAME"), err); + assertEquals("ID", this.rs.getString("PKCOLUMN_NAME"), err); + assertEquals("table2", this.rs.getString("FKTABLE_NAME"), err); + assertEquals("USER_ID", this.rs.getString("FKCOLUMN_NAME"), err); + assertEquals(1, this.rs.getInt("KEY_SEQ"), err); + assertEquals(1, this.rs.getInt("UPDATE_RULE"), err); + assertEquals(1, this.rs.getInt("DELETE_RULE"), err); + assertEquals("fk_table2_user_id", this.rs.getString("FK_NAME"), err); + assertEquals(useIS ? "PRIMARY" : null, this.rs.getString("PK_NAME"), err); + assertEquals(7, this.rs.getInt("DEFERRABILITY"), err); + assertFalse(this.rs.next(), err); + + this.rs = dbTermIsSchema ? meta.getImportedKeys(null, databaseName1, "table3") : meta.getImportedKeys(databaseName1, null, "table3"); + assertTrue(this.rs.next(), err); + assertEquals(dbTermIsSchema ? "def" : databaseName1, this.rs.getString("PKTABLE_CAT"), err); + assertEquals(dbTermIsSchema ? databaseName1 : null, this.rs.getString("PKTABLE_SCHEM"), err); + assertEquals(dbTermIsSchema ? "def" : databaseName1, this.rs.getString("FKTABLE_CAT"), err); + assertEquals(dbTermIsSchema ? databaseName1 : null, this.rs.getString("FKTABLE_SCHEM"), err); + assertEquals("table1", this.rs.getString("PKTABLE_NAME"), err); + assertEquals("LRN_ID", this.rs.getString("PKCOLUMN_NAME"), err); + assertEquals("table3", this.rs.getString("FKTABLE_NAME"), err); + assertEquals("LRN_ID", this.rs.getString("FKCOLUMN_NAME"), err); + assertEquals(1, this.rs.getInt("KEY_SEQ"), err); + assertEquals(1, this.rs.getInt("UPDATE_RULE"), err); + assertEquals(1, this.rs.getInt("DELETE_RULE"), err); + assertEquals("fk_table3_LRN_ID", this.rs.getString("FK_NAME"), err); + assertEquals(useIS ? "U_table1_LRN_ID" : null, this.rs.getString("PK_NAME"), err); + assertEquals(7, this.rs.getInt("DEFERRABILITY"), err); + assertFalse(this.rs.next(), err); + + con.close(); + } + } + + } } diff --git a/src/test/java/testsuite/regression/PooledConnectionRegressionTest.java b/src/test/java/testsuite/regression/PooledConnectionRegressionTest.java index 9b988c860..2581cba74 100644 --- a/src/test/java/testsuite/regression/PooledConnectionRegressionTest.java +++ b/src/test/java/testsuite/regression/PooledConnectionRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -34,7 +34,6 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.io.BufferedInputStream; import java.io.FileInputStream; @@ -59,6 +58,7 @@ import com.mysql.cj.NativeSession; import com.mysql.cj.ServerVersion; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.jdbc.Blob; import com.mysql.cj.jdbc.CallableStatementWrapper; @@ -151,6 +151,8 @@ public void setUp() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); + ds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); this.cpds = ds; } @@ -167,9 +169,11 @@ public void tearDown() throws Exception { /** * Tests fix for BUG#7136 ... Statement.getConnection() returning physical connection instead of logical connection. + * + * @throws Exception */ @Test - public void testBug7136() { + public void testBug7136() throws Exception { final ConnectionEventListener conListener = new ConnectionListener(); PooledConnection pc = null; this.closeEventCount = 0; @@ -201,24 +205,20 @@ public void testBug7136() { assertEquals(1, this.closeEventCount, "One close event should've been registered"); - } catch (SQLException ex) { - fail(ex.toString()); } finally { if (pc != null) { - try { - pc.close(); - } catch (SQLException ex) { - ex.printStackTrace(); - } + pc.close(); } } } /** * Test the nb of closeEvents generated when a Connection is reclaimed. No event should be generated in that case. + * + * @throws Exception */ @Test - public void testConnectionReclaim() { + public void testConnectionReclaim() throws Exception { final ConnectionEventListener conListener = new ConnectionListener(); PooledConnection pc = null; final int NB_TESTS = 5; @@ -249,22 +249,12 @@ public void testConnectionReclaim() { } } } - } catch (SQLException ex) { - ex.printStackTrace(); - fail(ex.toString()); } finally { if (pc != null) { - try { - System.out.println("Before pooledConnection.close()."); - - // This should not generate a close event. - pc.close(); - - System.out.println("After pooledConnection.close()."); - } catch (SQLException ex) { - ex.printStackTrace(); - fail(ex.toString()); - } + System.out.println("Before pooledConnection.close()."); + // This should not generate a close event. + pc.close(); + System.out.println("After pooledConnection.close()."); } } @@ -299,12 +289,10 @@ public void testPacketTooLargeException() throws Exception { pstmtFromPool.setBinaryStream(1, new BufferedInputStream(new FileInputStream(newTempBinaryFile("testPacketTooLargeException", numChars))), numChars); - try { + assertThrows(PacketTooBigException.class, () -> { pstmtFromPool.executeUpdate(); - fail("Expecting PacketTooLargeException"); - } catch (PacketTooBigException ptbe) { - // We're expecting this one... - } + return null; + }); // This should still work okay, even though the last query on the same connection didn't... this.rs = connFromPool.createStatement().executeQuery("SELECT 1"); @@ -316,9 +304,11 @@ public void testPacketTooLargeException() throws Exception { /** * Test the nb of closeEvents generated by a PooledConnection. A JDBC-compliant driver should only generate 1 closeEvent each time connection.close() is * called. + * + * @throws Exception */ @Test - public void testCloseEvent() { + public void testCloseEvent() throws Exception { final ConnectionEventListener conListener = new ConnectionListener(); PooledConnection pc = null; final int NB_TESTS = 5; @@ -338,8 +328,6 @@ public void testCloseEvent() { System.out.println("After connection.close()."); } - } catch (SQLException ex) { - fail(ex.toString()); } finally { if (pc != null) { try { @@ -381,18 +369,24 @@ public void connectionErrorOccurred(ConnectionEvent event) { public void testBug35489() throws Exception { MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource(); pds.setUrl(dbUrl); + pds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + pds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); this.pstmt = pds.getPooledConnection().getConnection().prepareStatement("SELECT 1"); this.pstmt.execute(); this.pstmt.close(); MysqlXADataSource xads = new MysqlXADataSource(); xads.setUrl(dbUrl); + xads.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + xads.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); this.pstmt = xads.getXAConnection().getConnection().prepareStatement("SELECT 1"); this.pstmt.execute(); this.pstmt.close(); xads = new MysqlXADataSource(); xads.setUrl(dbUrl); + xads.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + xads.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); xads.getProperty(PropertyKey.pinGlobalTxToPhysicalConnection).setValue(true); this.pstmt = xads.getXAConnection().getConnection().prepareStatement("SELECT 1"); this.pstmt.execute(); diff --git a/src/test/java/testsuite/regression/ResultSetRegressionTest.java b/src/test/java/testsuite/regression/ResultSetRegressionTest.java index 630b7c09f..0f3bf45b9 100644 --- a/src/test/java/testsuite/regression/ResultSetRegressionTest.java +++ b/src/test/java/testsuite/regression/ResultSetRegressionTest.java @@ -35,6 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -92,6 +93,7 @@ import javax.sql.rowset.CachedRowSet; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.mysql.cj.Messages; @@ -99,6 +101,7 @@ import com.mysql.cj.MysqlType; import com.mysql.cj.conf.DefaultPropertySet; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJCommunicationsException; import com.mysql.cj.exceptions.ExceptionInterceptor; @@ -119,6 +122,7 @@ import com.mysql.cj.protocol.a.result.ResultsetRowsCursor; import com.mysql.cj.protocol.a.result.ResultsetRowsStreaming; import com.mysql.cj.result.SqlDateValueFactory; +import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.TimeUtil; import com.mysql.cj.util.Util; @@ -234,30 +238,29 @@ public void testBug2623() throws Exception { */ @Test public void testBug2654() throws Exception { - if (!this.DISABLED_testBug2654) { // this is currently a server-level bug + createTable("foo", "(id tinyint(3) default NULL, data varchar(255) default NULL) DEFAULT CHARSET=latin1", "MyISAM "); + this.stmt.executeUpdate("INSERT INTO foo VALUES (2,'male'), (1,'female') "); - createTable("foo", "(id tinyint(3) default NULL, data varchar(255) default NULL) DEFAULT CHARSET=latin1", "MyISAM "); - this.stmt.executeUpdate("INSERT INTO foo VALUES (1,'male'),(2,'female')"); + createTable("bar", "(id tinyint(3) unsigned default NULL, data char(3) default '0') DEFAULT CHARSET=latin1", "MyISAM "); - createTable("bar", "(id tinyint(3) unsigned default NULL, data char(3) default '0') DEFAULT CHARSET=latin1", "MyISAM "); + this.stmt.executeUpdate("INSERT INTO bar VALUES (1,'no'), (2,'yes')"); - this.stmt.executeUpdate("INSERT INTO bar VALUES (1,'yes'),(2,'no')"); + String statement = "select foo.id, foo.data, bar.data from foo, bar where foo.id = bar.id order by foo.id"; - String statement = "select foo.id, foo.data, bar.data from foo, bar where foo.id = bar.id order by foo.id"; + this.rs = this.stmt.executeQuery(statement); - String column = "foo.data"; - - this.rs = this.stmt.executeQuery(statement); + ResultSetMetaData rsmd = this.rs.getMetaData(); + assertEquals("foo", rsmd.getTableName(1)); + assertEquals("id", rsmd.getColumnName(1)); - ResultSetMetaData rsmd = this.rs.getMetaData(); - System.out.println(rsmd.getTableName(1)); - System.out.println(rsmd.getColumnName(1)); + this.rs.next(); + assertEquals("female", this.rs.getString("foo.data")); + assertEquals("no", this.rs.getString("bar.data")); - this.rs.next(); + this.rs.next(); + assertEquals("male", this.rs.getString("foo.data")); + assertEquals("yes", this.rs.getString("bar.data")); - String fooData = this.rs.getString(column); - assertNotNull(fooData); - } } /** @@ -294,6 +297,8 @@ public void testClobTruncate() throws Exception { public void testClobberStreamingRS() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.clobberStreamingResults.getKeyName(), "true"); Connection clobberConn = getConnectionWithProps(props); @@ -474,23 +479,19 @@ public void testNextAndPrevious() throws Exception { this.rs.previous(); - try { + assertThrows("Should not be able to retrieve values with invalid cursor.", SQLException.class, "Before start.*", () -> { System.out.println("Value at row " + this.rs.getRow() + " is " + this.rs.getString(1)); - fail("Should not be able to retrieve values with invalid cursor"); - } catch (SQLException sqlEx) { - assertTrue(sqlEx.getMessage().startsWith("Before start")); - } + return null; + }); this.rs.next(); this.rs.next(); - try { + assertThrows("Should not be able to retrieve values with invalid cursor.", SQLException.class, "After end.*", () -> { System.out.println("Value at row " + this.rs.getRow() + " is " + this.rs.getString(1)); - fail("Should not be able to retrieve values with invalid cursor"); - } catch (SQLException sqlEx) { - assertTrue(sqlEx.getMessage().startsWith("After end")); - } + return null; + }); } /** @@ -611,6 +612,8 @@ public void testUpdatability() throws Exception { @Test public void testUpdatabilityAndEscaping() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "big5"); Connection updConn = getConnectionWithProps(props); @@ -920,37 +923,38 @@ public void testBug5069() throws Exception { */ @Test public void testBug5136() throws Exception { - if (!this.DISABLED_testBug5136) { - PreparedStatement toGeom = this.conn.prepareStatement("select GeomFromText(?)"); - PreparedStatement toText = this.conn.prepareStatement("select AsText(?)"); + boolean useSTfunctions = versionMeetsMinimum(5, 6); - String inText = "POINT(146.67596278 -36.54368233)"; + PreparedStatement toGeom = this.conn.prepareStatement(useSTfunctions ? "select ST_GeomFromText(?)" : "select GeomFromText(?)"); + PreparedStatement toText = this.conn.prepareStatement(useSTfunctions ? "select ST_AsText(?)" : "select AsText(?)"); - // First assert that the problem is not at the server end - this.rs = this.stmt.executeQuery("select AsText(GeomFromText('" + inText + "'))"); - this.rs.next(); + String inText = "POINT(146.67596278 -36.54368233)"; - String outText = this.rs.getString(1); - this.rs.close(); - assertTrue(inText.equals(outText), "Server side only\n In: " + inText + "\nOut: " + outText); + // First assert that the problem is not at the server end + this.rs = this.stmt + .executeQuery(useSTfunctions ? "select ST_AsText(ST_GeomFromText('" + inText + "'))" : "select AsText(GeomFromText('" + inText + "'))"); + this.rs.next(); - // Now bring a binary geometry object to the client and send it back - toGeom.setString(1, inText); - this.rs = toGeom.executeQuery(); - this.rs.next(); + String outText = this.rs.getString(1); + this.rs.close(); + assertTrue(inText.equals(outText), "Server side only\n In: " + inText + "\nOut: " + outText); - // Return a binary geometry object from the WKT - Object geom = this.rs.getObject(1); - this.rs.close(); - toText.setObject(1, geom); - this.rs = toText.executeQuery(); - this.rs.next(); + // Now bring a binary geometry object to the client and send it back + toGeom.setString(1, inText); + this.rs = toGeom.executeQuery(); + this.rs.next(); - // Return WKT from the binary geometry - outText = this.rs.getString(1); - this.rs.close(); - assertTrue(inText.equals(outText), "Server to client and back\n In: " + inText + "\nOut: " + outText); - } + // Return a binary geometry object from the WKT + Object geom = this.rs.getObject(1); + this.rs.close(); + toText.setObject(1, geom); + this.rs = toText.executeQuery(); + this.rs.next(); + + // Return WKT from the binary geometry + outText = this.rs.getString(1); + this.rs.close(); + assertTrue(inText.equals(outText), "Server to client and back\n In: " + inText + "\nOut: " + outText); } /** @@ -997,26 +1001,18 @@ public void testBug5717() throws Exception { createTable("testBug5717", "(field1 DOUBLE)"); this.pstmt = this.conn.prepareStatement("INSERT INTO testBug5717 VALUES (?)"); - try { + assertThrows(Exception.class, () -> { this.pstmt.setDouble(1, Double.NEGATIVE_INFINITY); - fail("Exception should've been thrown"); - } catch (Exception ex) { - // expected - } - - try { + return null; + }); + assertThrows(Exception.class, () -> { this.pstmt.setDouble(1, Double.POSITIVE_INFINITY); - fail("Exception should've been thrown"); - } catch (Exception ex) { - // expected - } - - try { + return null; + }); + assertThrows(Exception.class, () -> { this.pstmt.setDouble(1, Double.NaN); - fail("Exception should've been thrown"); - } catch (Exception ex) { - // expected - } + return null; + }); } /** @@ -1090,7 +1086,8 @@ public void testBug6743() throws Exception { String katakanaStr = "\u30BD"; Properties props = new Properties(); - + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS"); Connection sjisConn = null; @@ -1143,19 +1140,25 @@ public void testBug6743() throws Exception { @Test public void testBug6561() throws Exception { Connection testConn = this.conn; - Connection zeroConn = getConnectionWithProps("zeroDateTimeBehavior=CONVERT_TO_NULL"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL"); + Connection zeroConn = getConnectionWithProps(props); try { if (versionMeetsMinimum(5, 7, 4)) { - Properties props = new Properties(); - props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props2.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); if (versionMeetsMinimum(5, 7, 5)) { String sqlMode = getMysqlVariable("sql_mode"); if (sqlMode.contains("STRICT_TRANS_TABLES")) { sqlMode = removeSqlMode("STRICT_TRANS_TABLES", sqlMode); - props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'"); + props2.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'"); } } - testConn = getConnectionWithProps(props); + testConn = getConnectionWithProps(props2); this.stmt = testConn.createStatement(); } @@ -1250,6 +1253,8 @@ public void testBug8428() throws Exception { this.stmt.executeUpdate("INSERT INTO testBug8428 VALUES ('1999', '2005-02-11 12:54:41')"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.noDatetimeStringSync.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "false"); @@ -1601,6 +1606,8 @@ public void testBug14562() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); infoSchemConn = getConnectionWithProps(props); @@ -1624,39 +1631,6 @@ public void testBug14562() throws Exception { } } - @Test - public void testBug15604() throws Exception { - createTable("testBug15604_date_cal", "(field1 DATE)"); - Properties props = new Properties(); - props.setProperty(PropertyKey.sessionVariables.getKeyName(), "time_zone='America/Chicago'"); - - Connection nonLegacyConn = getConnectionWithProps(props); - - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - - cal.set(Calendar.YEAR, 2005); - cal.set(Calendar.MONTH, 4); - cal.set(Calendar.DAY_OF_MONTH, 15); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); - - java.sql.Date sqlDate = new java.sql.Date(cal.getTime().getTime()); - - Calendar cal2 = Calendar.getInstance(); - cal2.setTime(sqlDate); - System.out.println(new java.sql.Date(cal2.getTime().getTime())); - this.pstmt = nonLegacyConn.prepareStatement("INSERT INTO testBug15604_date_cal VALUES (?)"); - - this.pstmt.setDate(1, sqlDate, cal); - this.pstmt.executeUpdate(); - this.rs = nonLegacyConn.createStatement().executeQuery("SELECT field1 FROM testBug15604_date_cal"); - this.rs.next(); - - assertEquals(sqlDate.getTime(), this.rs.getDate(1, cal).getTime()); - } - @Test public void testBug14897() throws Exception { createTable("table1", "(id int, name_id int)"); @@ -1855,6 +1829,8 @@ public void testBug19724() throws Exception { Statement updStmt = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode=ansi"); try { @@ -1978,6 +1954,8 @@ public void testNPEWithUsageAdvisor() throws Exception { Connection advisorConn = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); advisorConn = getConnectionWithProps(props); @@ -1992,7 +1970,7 @@ public void testAllTypesForNull() throws Exception { Properties props = new Properties(); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "ROUND"); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection conn2 = getConnectionWithProps(props); Statement stmt2 = conn2.createStatement(); @@ -2280,6 +2258,8 @@ public void testEmptyStringsWithNumericGetters() throws Exception { checkEmptyConvertToZero(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection noFastIntParseConn = getConnectionWithProps(props); Statement noFastIntStmt = noFastIntParseConn.createStatement(); @@ -2297,6 +2277,8 @@ public void testEmptyStringsWithNumericGetters() throws Exception { // props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.emptyStringsConvertToZero.getKeyName(), "false"); Connection pedanticConn = getConnectionWithProps(props); @@ -2312,6 +2294,8 @@ public void testEmptyStringsWithNumericGetters() throws Exception { checkEmptyConvertToZeroException(); props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.emptyStringsConvertToZero.getKeyName(), "false"); pedanticConn = getConnectionWithProps(props); @@ -2392,10 +2376,7 @@ private void checkEmptyConvertToZeroException() { */ @Test public void testBug10485() throws Exception { - if (versionMeetsMinimum(5, 7, 5)) { - // Nothing to test, YEAR(2) is removed starting from 5.7.5 - return; - } + assumeTrue(!versionMeetsMinimum(5, 7, 5), "Nothing to test, YEAR(2) is removed starting from 5.7.5"); String tableName = "testBug10485"; @@ -2418,6 +2399,8 @@ public void testBug10485() throws Exception { assertEquals(newYears2005.toString(), this.rs.getString(1)); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.yearIsDateType.getKeyName(), "false"); Connection yearShortConn = getConnectionWithProps(props); @@ -2503,19 +2486,14 @@ public void testTruncationOfNonSigDigits() throws Exception { this.stmt.executeUpdate("INSERT INTO testTruncationOfNonSigDigits VALUES (123456.2345, 'ab')"); - try { + assertThrows("Should have thrown a truncation error", MysqlDataTruncation.class, () -> { this.stmt.executeUpdate("INSERT INTO testTruncationOfNonSigDigits VALUES (1234561234561.2345, 'ab')"); - fail("Should have thrown a truncation error"); - } catch (MysqlDataTruncation truncEx) { - // We expect this - } - - try { + return null; + }); + assertThrows("Should have thrown a truncation error", MysqlDataTruncation.class, () -> { this.stmt.executeUpdate("INSERT INTO testTruncationOfNonSigDigits VALUES (1234.2345, 'abcd')"); - fail("Should have thrown a truncation error"); - } catch (MysqlDataTruncation truncEx) { - // We expect this - } + return null; + }); } /** @@ -2862,6 +2840,8 @@ public void testBug21379() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useOldAliasMetadataBehavior.getKeyName(), "true"); legacyConn = getConnectionWithProps(props); legacyStmt = legacyConn.createStatement(); @@ -2967,6 +2947,8 @@ public void testBug25517() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); @@ -3058,6 +3040,8 @@ public void testBug25787() throws Exception { Connection deserializeConn = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoDeserialize.getKeyName(), "true"); props.setProperty(PropertyKey.treatUtilDateAsTimestamp.getKeyName(), "false"); @@ -3078,6 +3062,8 @@ public void testBug25787() throws Exception { @Test public void testTruncationDisable() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); Connection truncConn = null; @@ -3095,6 +3081,8 @@ public void testUsageAdvisorOnZeroRowResultSet() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); @@ -3195,7 +3183,7 @@ public void testBug26173() throws Exception { Statement stmtRead = null; Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); @@ -3406,6 +3394,8 @@ private void checkUpdatabilityMessage(SQLException sqlEx, String messageToCheck) @Test public void testBug24886() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.blobsAreStrings.getKeyName(), "true"); Connection noBlobConn = getConnectionWithProps(props); @@ -3419,6 +3409,8 @@ public void testBug24886() throws Exception { assertEquals("java.lang.String", this.rs.getObject(1).getClass().getName()); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.functionsNeverReturnBlobs.getKeyName(), "true"); noBlobConn = getConnectionWithProps(props); this.rs = noBlobConn.createStatement() @@ -3464,7 +3456,11 @@ public void testBug30664() throws Exception { */ @Test public void testBug30851() throws Exception { - Connection padConn = getConnectionWithProps("padCharsWithSpace=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.padCharsWithSpace.getKeyName(), "true"); + Connection padConn = getConnectionWithProps(props); try { createTable("bug30851", "(CharCol CHAR(10) DEFAULT NULL)"); @@ -3656,7 +3652,11 @@ public void testBug35610() throws Exception { createTable("testBug35610", "(field1 int, field2 int, field3 int)"); this.stmt.executeUpdate("INSERT INTO testBug35610 VALUES (1, 2, 3)"); exercise35610(this.stmt, false); - exercise35610(getConnectionWithProps("useColumnNamesInFindColumn=true").createStatement(), true); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useColumnNamesInFindColumn.getKeyName(), "true"); + exercise35610(getConnectionWithProps(props).createStatement(), true); } private void exercise35610(Statement configuredStmt, boolean force30Behavior) throws Exception { @@ -3700,19 +3700,14 @@ private void exercise35610(Statement configuredStmt, boolean force30Behavior) th } if (!force30Behavior) { - try { + assertThrows("findColumn(\"field1\" should have failed with an exception", SQLException.class, () -> { this.rs.findColumn("field1"); - fail("findColumn(\"field1\" should have failed with an exception"); - } catch (SQLException sqlEx) { - // expected - } - - try { + return null; + }); + assertThrows("findColumn(\"field2\" should have failed with an exception", SQLException.class, () -> { this.rs.findColumn("field2"); - fail("findColumn(\"field2\" should have failed with an exception"); - } catch (SQLException sqlEx) { - // expected - } + return null; + }); } } @@ -3748,6 +3743,8 @@ private void checkTimestampNanos() throws SQLException { public void testBug38387() throws Exception { Connection noBlobConn = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.functionsNeverReturnBlobs.getKeyName(), "true");// toggle, no change noBlobConn = getConnectionWithProps(props); try { @@ -3852,7 +3849,11 @@ public void testBug41484() throws Exception { @Test public void testBug41484_2() throws Exception { - Connection cachedRsmdConn = getConnectionWithProps("cacheResultSetMetadata=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), "true"); + Connection cachedRsmdConn = getConnectionWithProps(props); try { createTable("bug41484", "(id int not null primary key, day date not null) DEFAULT CHARSET=utf8"); @@ -3941,19 +3942,26 @@ public void testBug43759() throws Exception { public void testBug32525() throws Exception { Connection testConn = this.conn; Statement st = this.stmt; - Connection noStringSyncConn = getConnectionWithProps("noDatetimeStringSync=true"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.noDatetimeStringSync.getKeyName(), "true"); + Connection noStringSyncConn = getConnectionWithProps(props); try { if (versionMeetsMinimum(5, 7, 4)) { - Properties props = new Properties(); - props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props2.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); if (versionMeetsMinimum(5, 7, 5)) { String sqlMode = getMysqlVariable("sql_mode"); if (sqlMode.contains("STRICT_TRANS_TABLES")) { sqlMode = removeSqlMode("STRICT_TRANS_TABLES", sqlMode); - props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'"); + props2.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + "'"); } } - testConn = getConnectionWithProps(props); + testConn = getConnectionWithProps(props2); st = testConn.createStatement(); } @@ -4014,22 +4022,20 @@ public void testBug49516() throws Exception { @Test public void testBug48820() throws Exception { - if (versionMeetsMinimum(8, 0, 5)) { - // old_passwords and PASSWORD() were removed since MySQL 8.0.5 - return; - } + assumeTrue(!versionMeetsMinimum(8, 0, 5), "Old_passwords and PASSWORD() were removed since MySQL 8.0.5"); CachedRowSet crs; - Connection noBlobsConn = getConnectionWithProps("functionsNeverReturnBlobs=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.functionsNeverReturnBlobs.getKeyName(), "true"); + Connection noBlobsConn = getConnectionWithProps(props); if (versionMeetsMinimum(5, 6, 6)) { this.rs = noBlobsConn.createStatement().executeQuery("SHOW VARIABLES LIKE 'old_passwords'"); if (this.rs.next()) { - if (this.rs.getInt(2) == 2) { - System.out.println("Skip testBug48820 due to SHA-256 password hashing."); - return; - } + assumeTrue(this.rs.getInt(2) != 2, "Skip testBug48820 due to SHA-256 password hashing."); } } @@ -4067,6 +4073,8 @@ public void testBug60313() throws Exception { this.rs.close(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); Connection sspsCon = getConnectionWithProps(props); PreparedStatement ssPStmt = sspsCon.prepareStatement("select repeat('Z', 3000), now() + interval 1 microsecond"); @@ -4082,34 +4090,32 @@ public void testBug60313() throws Exception { * Tests fix for BUG#65503 - ResultSets created by PreparedStatement.getGeneratedKeys() are not close()d. * * To get results quicker add option -Xmx10M, with this option I got an out of memory failure after about 6500 passes. - * Since it's a very long test it is disabled by default. * * @throws Exception */ @Test + @Disabled("It's a very long test") public void testBug65503() throws Exception { - if (!this.DISABLED_testBug65503) { - createTable("testBug65503", "(id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, value INTEGER)"); - - PreparedStatement pStmt = this.conn.prepareStatement("INSERT INTO testBug65503 (value) VALUES (?)", Statement.RETURN_GENERATED_KEYS), - stmt2 = this.conn.prepareStatement("SELECT * FROM testBug65503 LIMIT 6"); - for (int i = 0; i < 100000000; ++i) { - pStmt.setString(1, "48"); - pStmt.executeUpdate(); - - ResultSet result = pStmt.getGeneratedKeys(); - result.next(); - result.getInt(1); - result.next(); - - result = stmt2.executeQuery(); - while (result.next()) { - } + createTable("testBug65503", "(id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, value INTEGER)"); - if (i % 500 == 0) { - System.out.printf("free-mem: %d, id: %d\n", Runtime.getRuntime().freeMemory() / 1024 / 1024, i); - this.conn.createStatement().execute("TRUNCATE TABLE testBug65503"); - } + PreparedStatement pStmt = this.conn.prepareStatement("INSERT INTO testBug65503 (value) VALUES (?)", Statement.RETURN_GENERATED_KEYS), + stmt2 = this.conn.prepareStatement("SELECT * FROM testBug65503 LIMIT 6"); + for (int i = 0; i < 100000000; ++i) { + pStmt.setString(1, "48"); + pStmt.executeUpdate(); + + ResultSet result = pStmt.getGeneratedKeys(); + result.next(); + result.getInt(1); + result.next(); + + result = stmt2.executeQuery(); + while (result.next()) { + } + + if (i % 500 == 0) { + System.out.printf("free-mem: %d, id: %d\n", Runtime.getRuntime().freeMemory() / 1024 / 1024, i); + this.conn.createStatement().execute("TRUNCATE TABLE testBug65503"); } } } @@ -4121,86 +4127,97 @@ public void testBug65503() throws Exception { */ @Test public void testBug64204() throws Exception { - final Properties props = new Properties(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.socketTimeout.getKeyName(), "30000"); - this.conn = getConnectionWithProps(props); - if (((JdbcConnection) this.conn).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) { - this.conn.setSchema("information_schema"); - } else { - this.conn.setCatalog("information_schema"); - } - this.conn.setAutoCommit(true); - - this.stmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - this.stmt.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode + Connection con = null; + try { + con = getConnectionWithProps(props); - this.rs = this.stmt.executeQuery("SELECT CONNECTION_ID()"); - this.rs.next(); - final String connectionId = this.rs.getString(1); - this.rs.close(); + if (((JdbcConnection) con).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) { + con.setSchema("information_schema"); + } else { + con.setCatalog("information_schema"); + } + con.setAutoCommit(true); - System.out.println("testBug64204.main: PID is " + connectionId); + Statement st = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + st.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode - ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(); - es.schedule(new Callable() { + this.rs = st.executeQuery("SELECT CONNECTION_ID()"); + this.rs.next(); + final String connectionId = this.rs.getString(1); + this.rs.close(); - public Boolean call() throws Exception { - boolean res = false; - Connection con2 = getConnectionWithProps(props); - if (((JdbcConnection) con2).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) { - con2.setSchema("information_schema"); - } else { - con2.setCatalog("information_schema"); - } - con2.setAutoCommit(true); + System.out.println("testBug64204.main: PID is " + connectionId); - Statement st2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - st2.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode - try { - System.out.println("testBug64204.replica: Running KILL QUERY " + connectionId); - st2.execute("KILL QUERY " + connectionId + ";"); + ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(); + es.schedule(new Callable() { - Thread.sleep(5000); - System.out.println("testBug64204.replica: parent thread should be hung now!!!"); - res = true; - } finally { - st2.close(); - con2.close(); - } + public Boolean call() throws Exception { + boolean res = false; + Connection con2 = getConnectionWithProps(props); + if (((JdbcConnection) con2).getPropertySet().getEnumProperty(PropertyKey.databaseTerm).getValue() == DatabaseTerm.SCHEMA) { + con2.setSchema("information_schema"); + } else { + con2.setCatalog("information_schema"); + } + con2.setAutoCommit(true); - System.out.println("testBug64204.replica: Done."); - return res; - } - }, 10, TimeUnit.SECONDS); + Statement st2 = con2.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + st2.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode + try { + System.out.println("testBug64204.replica: Running KILL QUERY " + connectionId); + st2.execute("KILL QUERY " + connectionId + ";"); - try { - this.rs = this.stmt.executeQuery("SELECT sleep(5) FROM character_sets LIMIT 10"); + Thread.sleep(5000); + System.out.println("testBug64204.replica: parent thread should be hung now!!!"); + res = true; + } finally { + st2.close(); + con2.close(); + } - int rows = 0; - int columnCount = this.rs.getMetaData().getColumnCount(); - System.out.println("testBug64204.main: fetched result set, " + columnCount + " columns"); + System.out.println("testBug64204.replica: Done."); + return res; + } + }, 10, TimeUnit.SECONDS); - long totalDataCount = 0; - while (this.rs.next()) { - rows++; - //get row size - long rowSize = 0; - for (int i = 0; i < columnCount; i++) { - String s = this.rs.getString(i + 1); - if (s != null) { - rowSize += s.length(); + try { + this.rs = st.executeQuery("SELECT sleep(5) FROM character_sets LIMIT 10"); + + int rows = 0; + int columnCount = this.rs.getMetaData().getColumnCount(); + System.out.println("testBug64204.main: fetched result set, " + columnCount + " columns"); + + long totalDataCount = 0; + while (this.rs.next()) { + rows++; + //get row size + long rowSize = 0; + for (int i = 0; i < columnCount; i++) { + String s = this.rs.getString(i + 1); + if (s != null) { + rowSize += s.length(); + } } + totalDataCount += rowSize; } - totalDataCount += rowSize; - } - System.out.println("testBug64204.main: character_sets total rows " + rows + ", data " + totalDataCount); + System.out.println("testBug64204.main: character_sets total rows " + rows + ", data " + totalDataCount); - } catch (SQLException se) { - assertEquals("70100", se.getSQLState(), "ER_QUERY_INTERRUPTED expected."); - if (!"70100".equals(se.getSQLState())) { - throw se; + } catch (SQLException se) { + assertEquals("70100", se.getSQLState(), "ER_QUERY_INTERRUPTED expected."); + if (!"70100".equals(se.getSQLState())) { + throw se; + } + } + } finally { + if (con != null) { + con.close(); + con = null; } } } @@ -4216,12 +4233,11 @@ public void testBug45757() throws SQLException { this.stmt = this.conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); this.rs = this.stmt.executeQuery("select id from bug45757"); this.rs.moveToInsertRow(); - try { - this.rs.updateRow(); - fail("updateRow() should throw an exception, not allowed to be called on insert row"); - } catch (SQLException sqlEx) { - assertTrue(sqlEx.getMessage().startsWith("Can not call updateRow() when on insert row.")); - } + assertThrows("updateRow() should throw an exception, not allowed to be called on insert row", SQLException.class, + "Can not call updateRow\\(\\) when on insert row.*", () -> { + this.rs.updateRow(); + return null; + }); } /** @@ -4271,6 +4287,8 @@ public void testBug38252() throws Exception { @Test public void testBug67318() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.exceptionInterceptors.getKeyName(), "testsuite.regression.ResultSetRegressionTest$TestBug67318ExceptionInterceptor"); @@ -4287,9 +4305,7 @@ public void testBug67318() throws Exception { } } - if (ei == null) { - fail("TestBug67318ExceptionInterceptor is not found on connection"); - } + assertNotNull(ei, "TestBug67318ExceptionInterceptor is not found on connection"); Statement st1 = c.createStatement(); ResultSet rs1 = st1.executeQuery("select 1"); @@ -4430,7 +4446,11 @@ public void testBug72023() throws Exception { "1,1,1,1,1,1,1,1,1,1,1,1,1,1", "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL", "NULL,1,NULL,1,NULL,NULL,1,NULL,1,NULL,NULL,NULL,NULL,1,1", "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" }; - Connection testConn = getConnectionWithProps("useServerPrepStmts=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); PreparedStatement testPstmt; ResultSet testRS; @@ -4462,7 +4482,11 @@ public void testBug72023() throws Exception { */ @Test public void testBug75309() throws Exception { - Connection testConn = getConnectionWithProps("socketTimeout=1000"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.socketTimeout.getKeyName(), "1000"); + Connection testConn = getConnectionWithProps(props); Statement testStmt = testConn.createStatement(); // turn on streaming results. @@ -4613,16 +4637,17 @@ private void testBug19536760CheckStates(ResultSet rset, boolean expectedIsBefore */ @Test public void testBug20804635() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; // fractional seconds are not supported in previous versions - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server"); - createTable("testBug20804635", "(c1 timestamp(2), c2 time(3), c3 datetime(4))"); - this.stmt.executeUpdate("INSERT INTO testBug20804635 VALUES ('2031-01-15 03:14:07.339999','12:59:00.9889','2031-01-15 03:14:07.333399')"); + createTable("testBug20804635", "(c1 timestamp(2), c2 time(3), c3 datetime(4), c4 time(6))"); + this.stmt.executeUpdate( + "INSERT INTO testBug20804635 VALUES ('2031-01-15 03:14:07.339999','12:59:00.9889','2031-01-15 03:14:07.333399', '838:59:58.123456')"); Calendar cal = Calendar.getInstance(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); @@ -4655,6 +4680,9 @@ public void testBug20804635() throws Exception { assertEquals("03:14:07", this.rs.getTime(3, cal).toString()); assertEquals("2031-01-15 03:14:07.3334", this.rs.getTimestamp(3).toString()); assertEquals("2031-01-15 03:14:07.3334", this.rs.getTimestamp(3, cal).toString()); + + assertEquals("838:59:58.123456", StringUtils.toString(this.rs.getBytes("c4"))); + assertEquals("838:59:58.123456", this.rs.getString("c4")); } /** @@ -4667,6 +4695,8 @@ public void testBug80522() throws Exception { createTable("testBug80522", "(t TIME, d DATE, s TEXT)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); String sqlMode = getMysqlVariable("sql_mode"); if (sqlMode.contains("NO_ZERO_DATE")) { sqlMode = removeSqlMode("NO_ZERO_DATE", sqlMode); @@ -4705,9 +4735,7 @@ public Void call() throws Exception { */ @Test public void testBug56479() throws Exception { - if (!versionMeetsMinimum(5, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6), "MySQL 5.6+ is required to run this test."); String tsStr1 = "2010-09-02 03:55:10"; String tsStr2 = "2010-09-02 03:55:10.123456"; @@ -4823,179 +4851,189 @@ public void testBug22931433() throws Exception { + " b'1100110011001100110011001100110011001100110011001100110011001100', 0x00, -2)"); Properties props = new Properties(); - props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); - Connection testConn = getConnectionWithProps(props); - Statement testStmt = testConn.createStatement(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); // TODO fails with jdbcCompliantTruncation=true - ResultSet rs1 = testStmt.executeQuery("SELECT * FROM testBug22931433"); - rs1.next(); + for (String useSSPS : new String[] { "false", "true" }) { + for (String cacheResultSetMetadata : new String[] { "false", "true" }) { + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS); + props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), cacheResultSetMetadata); + + Connection testConn = getConnectionWithProps(props); + Statement testStmt = testConn.createStatement(); + + ResultSet rs1 = testStmt.executeQuery("SELECT * FROM testBug22931433"); + rs1.next(); + + assertEquals('a', rs1.getByte("c1")); + assertEquals('a', rs1.getByte("c2")); + assertEquals('a', rs1.getByte("c3")); + assertEquals('a', rs1.getByte("c4")); + assertEquals('a', rs1.getByte("c5")); + assertEquals('a', rs1.getByte("c6")); + assertEquals('a', rs1.getByte("c7")); + assertEquals('a', rs1.getByte("c8")); + + assertEquals(97, rs1.getShort("c1")); + assertEquals(25185, rs1.getShort("c2")); + assertEquals(25185, rs1.getShort("c3")); // truncated to 2 bytes + assertEquals(25185, rs1.getShort("c4")); // truncated to 2 bytes + assertEquals(25185, rs1.getShort("c5")); // truncated to 2 bytes + assertEquals(25185, rs1.getShort("c6")); // truncated to 2 bytes + assertEquals(25185, rs1.getShort("c7")); // truncated to 2 bytes + assertEquals(25185, rs1.getShort("c8")); // truncated to 2 bytes + + assertEquals(97, rs1.getInt("c1")); + assertEquals(25185, rs1.getInt("c2")); + assertEquals(6513249, rs1.getInt("c3")); + assertEquals(1684234849, rs1.getInt("c4")); + assertEquals(1684234849, rs1.getInt("c5")); // truncated to 4 bytes + assertEquals(1684234849, rs1.getInt("c6")); // truncated to 4 bytes + assertEquals(1684234849, rs1.getInt("c7")); // truncated to 4 bytes + assertEquals(1684234849, rs1.getInt("c8")); // truncated to 4 bytes + + assertEquals(97, rs1.getLong("c1")); + assertEquals(25185, rs1.getLong("c2")); + assertEquals(6513249, rs1.getLong("c3")); + assertEquals(1684234849, rs1.getLong("c4")); + assertEquals(435475931745L, rs1.getLong("c5")); + assertEquals(112585661964897L, rs1.getLong("c6")); + assertEquals(29104508263162465L, rs1.getLong("c7")); + assertEquals(7523094288207667809L, rs1.getLong("c8")); + + assertEquals(BigDecimal.valueOf(97), rs1.getBigDecimal("c1")); + assertEquals(BigDecimal.valueOf(25185), rs1.getBigDecimal("c2")); + assertEquals(BigDecimal.valueOf(6513249), rs1.getBigDecimal("c3")); + assertEquals(BigDecimal.valueOf(1684234849), rs1.getBigDecimal("c4")); + assertEquals(BigDecimal.valueOf(435475931745L), rs1.getBigDecimal("c5")); + assertEquals(BigDecimal.valueOf(112585661964897L), rs1.getBigDecimal("c6")); + assertEquals(BigDecimal.valueOf(29104508263162465L), rs1.getBigDecimal("c7")); + assertEquals(BigDecimal.valueOf(7523094288207667809L), rs1.getBigDecimal("c8")); + + assertEquals(97f, rs1.getFloat("c1")); + assertEquals(25185f, rs1.getFloat("c2")); + assertEquals(6513249f, rs1.getFloat("c3")); + assertEquals(1684234849f, rs1.getFloat("c4")); + assertEquals(435475931745f, rs1.getFloat("c5")); + assertEquals(112585661964897f, rs1.getFloat("c6")); + assertEquals(29104508263162465f, rs1.getFloat("c7")); + assertEquals(7523094288207667809f, rs1.getFloat("c8")); + + assertEquals(Double.valueOf(97), Double.valueOf(rs1.getDouble("c1"))); + assertEquals(Double.valueOf(25185), Double.valueOf(rs1.getDouble("c2"))); + assertEquals(Double.valueOf(6513249), Double.valueOf(rs1.getDouble("c3"))); + assertEquals(Double.valueOf(1684234849), Double.valueOf(rs1.getDouble("c4"))); + assertEquals(Double.valueOf(435475931745L), Double.valueOf(rs1.getDouble("c5"))); + assertEquals(Double.valueOf(112585661964897L), Double.valueOf(rs1.getDouble("c6"))); + assertEquals(Double.valueOf(29104508263162465L), Double.valueOf(rs1.getDouble("c7"))); + assertEquals(Double.valueOf(7523094288207667809L), Double.valueOf(rs1.getDouble("c8"))); + + assertEquals(true, rs1.getBoolean("c1")); + assertEquals(true, rs1.getBoolean("cb1")); + assertEquals(true, rs1.getBoolean("cb2")); + + assertEquals(BigDecimal.valueOf(97).toString(), rs1.getString("c1")); + assertEquals(BigDecimal.valueOf(25185).toString(), rs1.getString("c2")); + assertEquals(BigDecimal.valueOf(6513249).toString(), rs1.getString("c3")); + assertEquals(BigDecimal.valueOf(1684234849).toString(), rs1.getString("c4")); + assertEquals(BigDecimal.valueOf(435475931745L).toString(), rs1.getString("c5")); + assertEquals(BigDecimal.valueOf(112585661964897L).toString(), rs1.getString("c6")); + assertEquals(BigDecimal.valueOf(29104508263162465L).toString(), rs1.getString("c7")); + assertEquals(BigDecimal.valueOf(7523094288207667809L).toString(), rs1.getString("c8")); + + assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Date", new Callable() { + public Void call() throws Exception { + rs1.getDate("c1"); + return null; + } + }); - assertEquals('a', rs1.getByte("c1")); - assertEquals('a', rs1.getByte("c2")); - assertEquals('a', rs1.getByte("c3")); - assertEquals('a', rs1.getByte("c4")); - assertEquals('a', rs1.getByte("c5")); - assertEquals('a', rs1.getByte("c6")); - assertEquals('a', rs1.getByte("c7")); - assertEquals('a', rs1.getByte("c8")); - - assertEquals(97, rs1.getShort("c1")); - assertEquals(25185, rs1.getShort("c2")); - assertEquals(25185, rs1.getShort("c3")); // truncated to 2 bytes - assertEquals(25185, rs1.getShort("c4")); // truncated to 2 bytes - assertEquals(25185, rs1.getShort("c5")); // truncated to 2 bytes - assertEquals(25185, rs1.getShort("c6")); // truncated to 2 bytes - assertEquals(25185, rs1.getShort("c7")); // truncated to 2 bytes - assertEquals(25185, rs1.getShort("c8")); // truncated to 2 bytes - - assertEquals(97, rs1.getInt("c1")); - assertEquals(25185, rs1.getInt("c2")); - assertEquals(6513249, rs1.getInt("c3")); - assertEquals(1684234849, rs1.getInt("c4")); - assertEquals(1684234849, rs1.getInt("c5")); // truncated to 4 bytes - assertEquals(1684234849, rs1.getInt("c6")); // truncated to 4 bytes - assertEquals(1684234849, rs1.getInt("c7")); // truncated to 4 bytes - assertEquals(1684234849, rs1.getInt("c8")); // truncated to 4 bytes - - assertEquals(97, rs1.getLong("c1")); - assertEquals(25185, rs1.getLong("c2")); - assertEquals(6513249, rs1.getLong("c3")); - assertEquals(1684234849, rs1.getLong("c4")); - assertEquals(435475931745L, rs1.getLong("c5")); - assertEquals(112585661964897L, rs1.getLong("c6")); - assertEquals(29104508263162465L, rs1.getLong("c7")); - assertEquals(7523094288207667809L, rs1.getLong("c8")); - - assertEquals(BigDecimal.valueOf(97), rs1.getBigDecimal("c1")); - assertEquals(BigDecimal.valueOf(25185), rs1.getBigDecimal("c2")); - assertEquals(BigDecimal.valueOf(6513249), rs1.getBigDecimal("c3")); - assertEquals(BigDecimal.valueOf(1684234849), rs1.getBigDecimal("c4")); - assertEquals(BigDecimal.valueOf(435475931745L), rs1.getBigDecimal("c5")); - assertEquals(BigDecimal.valueOf(112585661964897L), rs1.getBigDecimal("c6")); - assertEquals(BigDecimal.valueOf(29104508263162465L), rs1.getBigDecimal("c7")); - assertEquals(BigDecimal.valueOf(7523094288207667809L), rs1.getBigDecimal("c8")); - - assertEquals(97f, rs1.getFloat("c1")); - assertEquals(25185f, rs1.getFloat("c2")); - assertEquals(6513249f, rs1.getFloat("c3")); - assertEquals(1684234849f, rs1.getFloat("c4")); - assertEquals(435475931745f, rs1.getFloat("c5")); - assertEquals(112585661964897f, rs1.getFloat("c6")); - assertEquals(29104508263162465f, rs1.getFloat("c7")); - assertEquals(7523094288207667809f, rs1.getFloat("c8")); - - assertEquals(Double.valueOf(97), Double.valueOf(rs1.getDouble("c1"))); - assertEquals(Double.valueOf(25185), Double.valueOf(rs1.getDouble("c2"))); - assertEquals(Double.valueOf(6513249), Double.valueOf(rs1.getDouble("c3"))); - assertEquals(Double.valueOf(1684234849), Double.valueOf(rs1.getDouble("c4"))); - assertEquals(Double.valueOf(435475931745L), Double.valueOf(rs1.getDouble("c5"))); - assertEquals(Double.valueOf(112585661964897L), Double.valueOf(rs1.getDouble("c6"))); - assertEquals(Double.valueOf(29104508263162465L), Double.valueOf(rs1.getDouble("c7"))); - assertEquals(Double.valueOf(7523094288207667809L), Double.valueOf(rs1.getDouble("c8"))); - - assertEquals(true, rs1.getBoolean("c1")); - assertEquals(true, rs1.getBoolean("cb1")); - assertEquals(true, rs1.getBoolean("cb2")); - - assertEquals(BigDecimal.valueOf(97).toString(), rs1.getString("c1")); - assertEquals(BigDecimal.valueOf(25185).toString(), rs1.getString("c2")); - assertEquals(BigDecimal.valueOf(6513249).toString(), rs1.getString("c3")); - assertEquals(BigDecimal.valueOf(1684234849).toString(), rs1.getString("c4")); - assertEquals(BigDecimal.valueOf(435475931745L).toString(), rs1.getString("c5")); - assertEquals(BigDecimal.valueOf(112585661964897L).toString(), rs1.getString("c6")); - assertEquals(BigDecimal.valueOf(29104508263162465L).toString(), rs1.getString("c7")); - assertEquals(BigDecimal.valueOf(7523094288207667809L).toString(), rs1.getString("c8")); - - assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Date", new Callable() { - public Void call() throws Exception { - rs1.getDate("c1"); - return null; - } - }); + assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Time", new Callable() { + public Void call() throws Exception { + rs1.getTime("c1"); + return null; + } + }); - assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Time", new Callable() { - public Void call() throws Exception { - rs1.getTime("c1"); - return null; - } - }); + assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Timestamp", new Callable() { + public Void call() throws Exception { + rs1.getTimestamp("c1"); + return null; + } + }); - assertThrows(SQLException.class, "Unsupported conversion from BIT to java.sql.Timestamp", new Callable() { - public Void call() throws Exception { - rs1.getTimestamp("c1"); - return null; + // test negative values + rs1.next(); + + assertEquals(-52, rs1.getByte("c1")); + assertEquals(-52, rs1.getByte("c2")); + assertEquals(-52, rs1.getByte("c3")); + assertEquals(-52, rs1.getByte("c4")); + assertEquals(-52, rs1.getByte("c5")); + assertEquals(-52, rs1.getByte("c6")); + assertEquals(-52, rs1.getByte("c7")); + assertEquals(-52, rs1.getByte("c8")); + + assertEquals(204, rs1.getShort("c1")); + assertEquals(-13108, rs1.getShort("c2")); + assertEquals(-13108, rs1.getShort("c3")); // truncated to 2 bytes + assertEquals(-13108, rs1.getShort("c4")); // truncated to 2 bytes + assertEquals(-13108, rs1.getShort("c5")); // truncated to 2 bytes + assertEquals(-13108, rs1.getShort("c6")); // truncated to 2 bytes + assertEquals(-13108, rs1.getShort("c7")); // truncated to 2 bytes + assertEquals(-13108, rs1.getShort("c8")); // truncated to 2 bytes + + assertEquals(204, rs1.getInt("c1")); + assertEquals(52428, rs1.getInt("c2")); + assertEquals(13421772, rs1.getInt("c3")); + assertEquals(-858993460, rs1.getInt("c4")); + assertEquals(-858993460, rs1.getInt("c5")); // truncated to 4 bytes + assertEquals(-858993460, rs1.getInt("c6")); // truncated to 4 bytes + assertEquals(-858993460, rs1.getInt("c7")); // truncated to 4 bytes + assertEquals(-858993460, rs1.getInt("c8")); // truncated to 4 bytes + + assertEquals(204, rs1.getLong("c1")); + assertEquals(52428, rs1.getLong("c2")); + assertEquals(13421772, rs1.getLong("c3")); + assertEquals(3435973836L, rs1.getLong("c4")); + assertEquals(879609302220L, rs1.getLong("c5")); + assertEquals(225179981368524L, rs1.getLong("c6")); + assertEquals(57646075230342348L, rs1.getLong("c7")); + assertEquals(-3689348814741910324L, rs1.getLong("c8")); + + assertEquals(BigDecimal.valueOf(204), rs1.getBigDecimal("c1")); + assertEquals(BigDecimal.valueOf(52428), rs1.getBigDecimal("c2")); + assertEquals(BigDecimal.valueOf(13421772), rs1.getBigDecimal("c3")); + assertEquals(BigDecimal.valueOf(3435973836L), rs1.getBigDecimal("c4")); + assertEquals(BigDecimal.valueOf(879609302220L), rs1.getBigDecimal("c5")); + assertEquals(BigDecimal.valueOf(225179981368524L), rs1.getBigDecimal("c6")); + assertEquals(BigDecimal.valueOf(57646075230342348L), rs1.getBigDecimal("c7")); + assertEquals(new BigDecimal(new BigInteger("14757395258967641292")), rs1.getBigDecimal("c8")); + + assertEquals(204f, rs1.getFloat("c1")); + assertEquals(52428f, rs1.getFloat("c2")); + assertEquals(13421772f, rs1.getFloat("c3")); + assertEquals(3435973836f, rs1.getFloat("c4")); + assertEquals(879609302220f, rs1.getFloat("c5")); + assertEquals(225179981368524f, rs1.getFloat("c6")); + assertEquals(57646075230342348f, rs1.getFloat("c7")); + assertEquals(14757395258967641292f, rs1.getFloat("c8")); + + assertEquals(Double.valueOf(204), Double.valueOf(rs1.getDouble("c1"))); + assertEquals(Double.valueOf(52428), Double.valueOf(rs1.getDouble("c2"))); + assertEquals(Double.valueOf(13421772), Double.valueOf(rs1.getDouble("c3"))); + assertEquals(Double.valueOf(3435973836L), Double.valueOf(rs1.getDouble("c4"))); + assertEquals(Double.valueOf(879609302220L), Double.valueOf(rs1.getDouble("c5"))); + assertEquals(Double.valueOf(225179981368524L), Double.valueOf(rs1.getDouble("c6"))); + assertEquals(Double.valueOf(57646075230342348L), Double.valueOf(rs1.getDouble("c7"))); + assertEquals(Double.valueOf(new BigInteger("14757395258967641292").doubleValue()), Double.valueOf(rs1.getDouble("c8"))); + + assertEquals(false, rs1.getBoolean("c8")); + assertEquals(false, rs1.getBoolean("cb1")); + assertEquals(false, rs1.getBoolean("cb2")); } - }); - - // test negative values - rs1.next(); - - assertEquals(-52, rs1.getByte("c1")); - assertEquals(-52, rs1.getByte("c2")); - assertEquals(-52, rs1.getByte("c3")); - assertEquals(-52, rs1.getByte("c4")); - assertEquals(-52, rs1.getByte("c5")); - assertEquals(-52, rs1.getByte("c6")); - assertEquals(-52, rs1.getByte("c7")); - assertEquals(-52, rs1.getByte("c8")); - - assertEquals(204, rs1.getShort("c1")); - assertEquals(-13108, rs1.getShort("c2")); - assertEquals(-13108, rs1.getShort("c3")); // truncated to 2 bytes - assertEquals(-13108, rs1.getShort("c4")); // truncated to 2 bytes - assertEquals(-13108, rs1.getShort("c5")); // truncated to 2 bytes - assertEquals(-13108, rs1.getShort("c6")); // truncated to 2 bytes - assertEquals(-13108, rs1.getShort("c7")); // truncated to 2 bytes - assertEquals(-13108, rs1.getShort("c8")); // truncated to 2 bytes - - assertEquals(204, rs1.getInt("c1")); - assertEquals(52428, rs1.getInt("c2")); - assertEquals(13421772, rs1.getInt("c3")); - assertEquals(-858993460, rs1.getInt("c4")); - assertEquals(-858993460, rs1.getInt("c5")); // truncated to 4 bytes - assertEquals(-858993460, rs1.getInt("c6")); // truncated to 4 bytes - assertEquals(-858993460, rs1.getInt("c7")); // truncated to 4 bytes - assertEquals(-858993460, rs1.getInt("c8")); // truncated to 4 bytes - - assertEquals(204, rs1.getLong("c1")); - assertEquals(52428, rs1.getLong("c2")); - assertEquals(13421772, rs1.getLong("c3")); - assertEquals(3435973836L, rs1.getLong("c4")); - assertEquals(879609302220L, rs1.getLong("c5")); - assertEquals(225179981368524L, rs1.getLong("c6")); - assertEquals(57646075230342348L, rs1.getLong("c7")); - assertEquals(-3689348814741910324L, rs1.getLong("c8")); - - assertEquals(BigDecimal.valueOf(204), rs1.getBigDecimal("c1")); - assertEquals(BigDecimal.valueOf(52428), rs1.getBigDecimal("c2")); - assertEquals(BigDecimal.valueOf(13421772), rs1.getBigDecimal("c3")); - assertEquals(BigDecimal.valueOf(3435973836L), rs1.getBigDecimal("c4")); - assertEquals(BigDecimal.valueOf(879609302220L), rs1.getBigDecimal("c5")); - assertEquals(BigDecimal.valueOf(225179981368524L), rs1.getBigDecimal("c6")); - assertEquals(BigDecimal.valueOf(57646075230342348L), rs1.getBigDecimal("c7")); - assertEquals(new BigDecimal(new BigInteger("14757395258967641292")), rs1.getBigDecimal("c8")); - - assertEquals(204f, rs1.getFloat("c1")); - assertEquals(52428f, rs1.getFloat("c2")); - assertEquals(13421772f, rs1.getFloat("c3")); - assertEquals(3435973836f, rs1.getFloat("c4")); - assertEquals(879609302220f, rs1.getFloat("c5")); - assertEquals(225179981368524f, rs1.getFloat("c6")); - assertEquals(57646075230342348f, rs1.getFloat("c7")); - assertEquals(14757395258967641292f, rs1.getFloat("c8")); - - assertEquals(Double.valueOf(204), Double.valueOf(rs1.getDouble("c1"))); - assertEquals(Double.valueOf(52428), Double.valueOf(rs1.getDouble("c2"))); - assertEquals(Double.valueOf(13421772), Double.valueOf(rs1.getDouble("c3"))); - assertEquals(Double.valueOf(3435973836L), Double.valueOf(rs1.getDouble("c4"))); - assertEquals(Double.valueOf(879609302220L), Double.valueOf(rs1.getDouble("c5"))); - assertEquals(Double.valueOf(225179981368524L), Double.valueOf(rs1.getDouble("c6"))); - assertEquals(Double.valueOf(57646075230342348L), Double.valueOf(rs1.getDouble("c7"))); - assertEquals(Double.valueOf(new BigInteger("14757395258967641292").doubleValue()), Double.valueOf(rs1.getDouble("c8"))); - - assertEquals(false, rs1.getBoolean("c8")); - assertEquals(false, rs1.getBoolean("cb1")); - assertEquals(false, rs1.getBoolean("cb2")); + } } /** @@ -5032,6 +5070,8 @@ public void testBug78685() throws Exception { String testCase = String.format("Case [useSPS: %s, StmtType: %s]", useServerPrepStmts ? "Y" : "N", "Plain"); final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); this.rs = testConn.createStatement().executeQuery("SELECT b1, b1 + 0, BIN(b1), b2, b2 + 0, BIN(b2), b3, b3 + 0, BIN(b3) FROM testBug78685"); testBug78685CheckData(testCase); @@ -5169,9 +5209,7 @@ public Void call() throws Exception { */ @Test public void testBug80631() throws Exception { - if (!versionMeetsMinimum(5, 7, 9)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 9), "MySQL 5.7.9+ is required to run this test."); /* * \u4E2D\u56FD (Simplified Chinese): "China" @@ -5253,15 +5291,13 @@ public void testBug80631() throws Exception { */ @Test public void testBug23197238() throws Exception { - if (!versionMeetsMinimum(5, 7, 9)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 9), "MySQL 5.7.9+ is required to run this test."); createTable("testBug23197238", "(id INT AUTO_INCREMENT PRIMARY KEY, doc JSON DEFAULT NULL, field3 int DEFAULT 10)"); String[] docs = new String[] { "{\"key10\": \"value10\"}", "{\"key2\": \"value2\"}", "{\"key3\": \"value3\"}" }; Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); @@ -5337,6 +5373,8 @@ public void testBug81202() throws Exception { OffsetTime testOffsetTime = OffsetTime.of(12, 34, 56, 7890, ZoneOffset.UTC); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection testConn = getConnectionWithProps(timeZoneFreeDbUrl, props); this.pstmt = testConn.prepareStatement("INSERT INTO testBug81202 VALUES (?, TIMESTAMP '2016-04-27 12:15:55', ?, ?, ?, ?, ?, ?)"); @@ -5396,6 +5434,8 @@ public void testBug82964() throws Exception { TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin")); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "Europe/Berlin"); ResultSet rs1 = getConnectionWithProps(props).createStatement().executeQuery("SELECT '2016-03-27 02:15:00'"); @@ -5461,16 +5501,24 @@ public void testBug24525461() throws Exception { createTable("testBug24525461", sb.toString()); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); Statement st = testConn.createStatement(); - tstBug24525461testBytes("connectionTimeZone=LOCAL,useSSL=false,allowPublicKeyRetrieval=true", testJSON, st); // CSPS - tstBug24525461testBytes("connectionTimeZone=LOCAL,useSSL=false,allowPublicKeyRetrieval=true,useServerPrepStmts=true", testJSON, st); // SSPS without cursor - tstBug24525461testBytes("connectionTimeZone=LOCAL,useSSL=false,allowPublicKeyRetrieval=true,useCursorFetch=true,defaultFetchSize=1", testJSON, st); // SSPS with cursor + props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); + tstBug24525461testBytes(props, testJSON, st); // CSPS + + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + tstBug24525461testBytes(props, testJSON, st); // SSPS without cursor + + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "1"); + tstBug24525461testBytes(props, testJSON, st); // SSPS with cursor } - private void tstBug24525461testBytes(String params, boolean testJSON, Statement st) throws Exception { + private void tstBug24525461testBytes(Properties props, boolean testJSON, Statement st) throws Exception { st.executeUpdate("truncate table testBug24525461"); String fGeomFromText = versionMeetsMinimum(5, 6, 1) ? "ST_GeomFromText" : "GeomFromText"; @@ -5486,8 +5534,8 @@ private void tstBug24525461testBytes(String params, boolean testJSON, Statement st.executeUpdate(sb.toString()); - System.out.println(" with params = " + params); - Connection con = getConnectionWithProps(params); + System.out.println(" with params = " + props); + Connection con = getConnectionWithProps(props); PreparedStatement testPstmt = con.prepareStatement("SELECT * FROM testBug24525461", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); ResultSet rs1 = testPstmt.executeQuery(); @@ -5568,6 +5616,8 @@ private void tstBug24525461assertResults1(boolean testJSON, Statement st) throws String fAsText = versionMeetsMinimum(5, 6, 1) ? "ST_AsText" : "AsText"; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); @@ -5647,6 +5697,8 @@ private void tstBug24525461assertResults2(boolean testJSON, Statement st) throws String fAsText = versionMeetsMinimum(5, 6, 1) ? "ST_AsText" : "AsText"; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); @@ -5733,7 +5785,7 @@ public void testBug24527173() throws Exception { this.stmt.execute("insert into testBug24527173 (a) values (101),(102),(103),(104)"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "2"); @@ -5837,9 +5889,7 @@ public void testBug23702040() throws Exception { */ @Test public void testBug82707() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; // fractional seconds are not supported in previous versions - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server"); List ts = new ArrayList<>(); ts.add("2016-08-24 07:47:46.057000"); @@ -5881,7 +5931,7 @@ public void testBug25215008() throws Exception { // test 1 - OK Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection conn1 = getConnectionWithProps(props); PreparedStatement pstm1 = conn1.prepareStatement("select id, val_one, val_blob, val_three from testBug25215008 where val_one = ?"); @@ -6108,9 +6158,7 @@ public Void call() throws Exception { */ @Test public void testBug25650305() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; // fractional seconds are not supported in previous versions - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server"); createTable("testBug25650305", "(c1 timestamp(5))"); this.stmt.executeUpdate("INSERT INTO testBug25650305 VALUES ('2031-01-15 03:14:07.339999')"); @@ -6119,7 +6167,7 @@ public void testBug25650305() throws Exception { Connection testConn; Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); testConn = getConnectionWithProps(props); this.rs = testConn.createStatement().executeQuery("SELECT * FROM testBug25650305"); @@ -6155,15 +6203,13 @@ public void testBug25650305() throws Exception { */ @Test public void testBug26750705() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; // fractional seconds are not supported in previous versions - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server"); createTable("testBug26750705", "(c1 time(3), c2 time(3))"); this.stmt.execute("insert into testBug26750705 values('80:59:59','8:59:59.01')"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); @@ -6209,6 +6255,8 @@ public void testBug26266731() throws Exception { assertEquals(3, getRowCount("testBug26266731")); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); Connection c = getConnectionWithProps(props); @@ -6250,14 +6298,12 @@ public void testBug85941() throws Exception { */ @Test public void testBug22305979() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; // fractional seconds are not supported in previous versions - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "Fractional seconds are not supported by server"); /* Test from bug report */ Connection testConn2; Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sendFractionalSeconds.getKeyName(), "false"); @@ -6567,9 +6613,6 @@ public void testBug22305979() throws Exception { assertEquals(c_exp.get(Calendar.SECOND), c_res.get(Calendar.SECOND), testCase); assertEquals(c_exp.get(Calendar.MILLISECOND), c_res.get(Calendar.MILLISECOND), testCase); - // TODO java.sql.Time does not provide any way for setting/getting milliseconds and removes them from toString() method. - // So the rs.updateTime(String columnName, java.sql.Time x) will always truncate milliseconds. Probably it is a bug because - // java.sql.Time contains milliseconds internally. We have a Bug#76775 feature request about that. c_exp.setTime(sendFractionalSeconds && sendFractionalSecondsForTime ? (sqlModeTimeTruncateFractional ? t_ins_expected_truncate[len] : t_ins_expected_round[len]) : t_ins_expected_not_sendFractionalSeconds[len]); @@ -6615,7 +6658,7 @@ public void testBug22305979() throws Exception { @Test public void testBug80532() throws Exception { Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (String enc : new String[] { "ISO8859_1", "UTF-8" }) { @@ -6696,7 +6739,7 @@ public void testBug72609() throws Exception { createTable("testBug72609", "(d date, pd date, dt datetime, pdt datetime)"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); boolean sendFractionalSeconds = false; @@ -6780,29 +6823,6 @@ public Void call() throws Exception { } while ((sendFractionalSeconds = !sendFractionalSeconds) || (useServerPrepStmts = !useServerPrepStmts)); } - /** - * Tests for fix to BUG#92574 (28706219), WHEN CONVERTING FROM VARCHAR TO JAVA BOOLEAN, 'N' IS NOT SUPPORTED. - * - * @throws Exception - */ - @Test - public void testBug92574() throws Exception { - String[] strValues = new String[] { null, "N", "n", "Y", "y", "0", "1" }; - boolean[] boolValues = new boolean[] { false, false, false, true, true, false, true }; - - createTable("testBug92574", "(id int not null, f varchar(1), key(id))"); - for (int i = 0; i < strValues.length; i++) { - String val = strValues[i] == null ? null : "'" + strValues[i] + "'"; - this.stmt.executeUpdate("insert into testBug92574 values(" + i + "," + val + ")"); - } - this.rs = this.stmt.executeQuery("SELECT * from testBug92574"); - while (this.rs.next()) { - int i = this.rs.getInt(1); - assertEquals(strValues[i], this.rs.getString(2)); - assertEquals(boolValues[i], this.rs.getBoolean(2)); - } - } - /** * Tests fix for Bug#91065 (28101003), ZERODATETIMEBEHAVIOR=CONVERT_TO_NULL SHOULD NOT APPLY TO 00:00:00 TIME COLUMNS. * @@ -6814,7 +6834,7 @@ public void testBug91065() throws Exception { this.stmt.executeUpdate("insert into testBug91065 values('00:00:00')"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL"); Connection con = getConnectionWithProps(props); @@ -6838,7 +6858,7 @@ public void testBug92536() throws Exception { this.stmt.executeUpdate("INSERT INTO `testBug92536` VALUES ('key', 0)"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (String useSSPS : new String[] { "false", "true" }) { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), useSSPS); @@ -6876,7 +6896,7 @@ public void testBug25650482() throws Exception { this.stmt.executeUpdate("INSERT INTO `testBug25650482` VALUES (1, 'a')"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (String useSSPS : new String[] { "false", "true" }) { @@ -6915,7 +6935,7 @@ public void testBug25650514() throws Exception { this.stmt.executeUpdate("INSERT INTO `testBug25650514` VALUES (1, 'a')"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (String useSSPS : new String[] { "false", "true" }) { @@ -6959,7 +6979,7 @@ public void testBug25650385() throws Exception { this.stmt.execute("INSERT INTO testBug25650385 values (10, 'a', 48, 10, 23)"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useSSPS : new boolean[] { false, true }) { for (boolean jdbcCompliantTruncation : new boolean[] { false, true }) { @@ -6994,6 +7014,7 @@ public Void call() throws Exception { assertEquals('a', rs1.getBytes(2)[0]); assertEquals('a', rs1.getByte(2)); assertThrows(SQLDataException.class, "Cannot determine value type from string 'a'", new Callable() { + public Void call() throws Exception { rs1.getInt(2); return null; @@ -7089,6 +7110,7 @@ public Void call() throws Exception { }); assertTrue(rs1.getString(5).startsWith("23")); } + } } @@ -7102,7 +7124,7 @@ public void testBug27784363() throws Exception { createTable("testBug27784363", "(col0 TEXT)"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); Connection c1 = getConnectionWithProps(props); @@ -7178,6 +7200,8 @@ public void testBug94533() throws Exception { public void testBug94585() throws Exception { createTable("testBug94585", "(column_1 INT NOT NULL)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useSSPS : new boolean[] { false, true }) { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); Connection con = getConnectionWithProps(props); @@ -7200,7 +7224,7 @@ public void testBug94585() throws Exception { public void testBug80441() throws Exception { createTable("testBug80441", "( id varchar(50) NOT NULL, data longtext, start DATETIME, PRIMARY KEY (id) )"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection con = null; for (String sessVars : new String[] { null, "sql_mode='NO_BACKSLASH_ESCAPES'" }) { @@ -7251,7 +7275,7 @@ public void testBug80441() throws Exception { public void testBug20913289() throws Exception { createTable("testBug20913289", "(c1 int,c2 blob)"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection con = null; for (String sessVars : new String[] { null, "sql_mode='NO_BACKSLASH_ESCAPES'" }) { @@ -7314,6 +7338,8 @@ public void testBug96059() throws Exception { Connection con = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); for (boolean useSSPS : new boolean[] { false, true }) { @@ -7373,6 +7399,8 @@ public void testBug96383() throws Exception { this.stmt.execute("INSERT INTO testBug96383 values ('time', '00:00:05.123')"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useSSPS : new boolean[] { false, true }) { for (boolean useCursorFetch : new boolean[] { false, true }) { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); @@ -7404,6 +7432,8 @@ public void testBug96383() throws Exception { @Test public void testBug97757() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean cacheResultSetMetadata : new boolean[] { false, true }) { props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), "" + cacheResultSetMetadata); Connection con = getConnectionWithProps(props); @@ -7412,7 +7442,7 @@ public void testBug97757() throws Exception { System.out.println("MySQL Server: " + meta.getDatabaseProductVersion() + "; Driver: " + meta.getDriverName() + meta.getDriverVersion()); Statement s = con.createStatement(); - s.executeQuery("set autocommit = 0;"); + s.execute("set autocommit = 0;"); con.close(); } @@ -7527,7 +7557,7 @@ public void testBug94457() throws Exception { : "(dt DATETIME NOT NULL, ts TIMESTAMP NOT NULL, t TIME NOT NULL, odt VARCHAR(30), ot VARCHAR(20))"); Properties props = new Properties(); - props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); @@ -7650,6 +7680,8 @@ public void testBug99013() throws Exception { this.stmt.executeUpdate("INSERT INTO partorder VALUES('P1','S1','1990-04-30','1990-06-21')"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); @@ -7679,8 +7711,9 @@ public void testBug31747910() throws Exception { * 3. cursor-based; * 4. cursor-based & scroll-tolerant. */ - String[] connOpts = new String[] { "", "", "scrollTolerantForwardOnly=true", "useCursorFetch=true", - "useCursorFetch=true,scrollTolerantForwardOnly=true" }; + String[] connOpts = new String[] { "useSSL=false,allowPublicKeyRetrieval=true", "useSSL=false,allowPublicKeyRetrieval=true", + "useSSL=false,allowPublicKeyRetrieval=true,scrollTolerantForwardOnly=true", "useSSL=false,allowPublicKeyRetrieval=true,useCursorFetch=true", + "useSSL=false,allowPublicKeyRetrieval=true,useCursorFetch=true,scrollTolerantForwardOnly=true" }; int[] fetchSize = new int[] { 0, Integer.MIN_VALUE, Integer.MIN_VALUE, 2, 2 }; for (int i = 0; i < connOpts.length; i++) { for (int j = 0; j < 3; j++) { // Statement; PreparedStatement and ServerPreparedStatement. @@ -7689,7 +7722,7 @@ public void testBug31747910() throws Exception { switch (j) { case 0: // Default behavior using Statement - testConn = getConnectionWithProps("connOpts[i]"); + testConn = getConnectionWithProps(connOpts[i]); testStmt = testConn.createStatement(); if (fetchSize[i] != 0) { testStmt.setFetchSize(fetchSize[i]); @@ -7743,19 +7776,19 @@ public void testBug31747910() throws Exception { switch (i) { case 0: // Scroll-tolerant using Statement - testConn = getConnectionWithProps("scrollTolerantForwardOnly=true"); + testConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,scrollTolerantForwardOnly=true"); testStmt = testConn.createStatement(); this.rs = testStmt.executeQuery("SELECT * FROM testBug31747910"); break; case 1: // Scroll-tolerant using PreparedStatement - testConn = getConnectionWithProps("scrollTolerantForwardOnly=true"); + testConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,scrollTolerantForwardOnly=true"); testStmt = testConn.prepareStatement("SELECT * FROM testBug31747910"); this.rs = ((PreparedStatement) testStmt).executeQuery(); break; case 2: // Scroll-tolerant using ServerPreparedStatement - testConn = getConnectionWithProps("useServerPrepStmts=true,scrollTolerantForwardOnly=true"); + testConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,useServerPrepStmts=true,scrollTolerantForwardOnly=true"); testStmt = testConn.prepareStatement("SELECT * FROM testBug31747910"); this.rs = ((PreparedStatement) testStmt).executeQuery(); break; @@ -7825,4 +7858,267 @@ public void testBug102131() throws Exception { assertEquals("a", this.rs.getString("name")); assertEquals(20, this.rs.getInt("age")); } + + /** + * Test fix for Bug#20391659, GETBYTE() CALL RESULTS IN EXCEPTION WHEN USEUSAGEADVISOR = TRUE. + * + * @throws Exception + * if the test fails + */ + @Test + public void testBug20391659() throws Exception { + createTable("testBug20391659", "(c1 char(1),c2 char(1))"); + this.stmt.executeUpdate("INSERT INTO testBug20391659 VALUES('1','0')"); + + Connection con = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); + props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + con = getConnectionWithProps(props); + + PreparedStatement ps = con.prepareStatement("select * from testBug20391659 ", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); + this.rs = ps.executeQuery(); + while (this.rs.next()) { + assertEquals('1', this.rs.getByte(1)); // was issuing java.lang.ArrayIndexOutOfBoundsException + assertEquals('0', this.rs.getByte(2)); + } + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Test fix for Bug#20391631, GETBOOLEAN() CALL RESULTS IN EXCEPTION WHEN USEUSAGEADVISOR = TRUE. + * + * @throws Exception + * if the test fails + */ + @Test + public void testBug20391631() throws Exception { + createTable("testBug20391631", "(c1 char(1),c2 char(1))"); + this.stmt.executeUpdate("INSERT INTO testBug20391631 VALUES('1','0')"); + + Connection con = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); + props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + con = getConnectionWithProps(props); + + this.rs = con.createStatement().executeQuery("select * from testBug20391631"); + while (this.rs.next()) { + assertTrue(this.rs.getBoolean(1)); // was issuing java.lang.ArrayIndexOutOfBoundsException + assertFalse(this.rs.getBoolean(2)); + } + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Test fix for Bug#19805370, GETBINARYSTREAM() WITH INVALID COLUMN INDEX RETURNS EXCEPTION. + * + * @throws Exception + * if the test fails + */ + @Test + public void testBug19805370() throws Exception { + Connection con = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + con = getConnectionWithProps(props); + + PreparedStatement ps = con.prepareStatement("select 'abcd'"); + this.rs = ps.executeQuery(); + this.rs.next(); + + assertThrows(SQLException.class, "Column Index out of range, 0 < 1.*", () -> { + this.rs.getBinaryStream(0); + return null; + }); + assertThrows(SQLException.class, "Column Index out of range, 0 < 1.*", () -> { + this.rs.getCharacterStream(0); + return null; + }); + assertThrows(SQLException.class, "Column Index out of range, 0 < 1.*", () -> { + this.rs.getAsciiStream(0); + return null; + }); + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Test fix for Bug#20802947, RESULTSET UPDATE METHODS FAILS WHEN TABLENAME CONTAINS SPECIAL CHARACTERS. + * + * @throws Exception + * if the test fails + */ + @Test + public void testBug20802947() throws Exception { + createTable("`test``Bug20802947`", "(id int,c char(10),primary key(id))"); + this.stmt.executeUpdate("INSERT INTO `test``Bug20802947` VALUES(10,'a'),(20,'b'),(30,'c'),(40,'d'),(50,'e')"); + + Statement st = this.conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + ResultSet rs1 = st.executeQuery("select * from `test``Bug20802947`"); + rs1.absolute(1); + rs1.updateString(2, rs1.getString(2) + rs1.getString(2)); + rs1.updateRow(); + rs1.close(); + this.rs = this.stmt.executeQuery("select * from `test``Bug20802947` where id=10"); + while (this.rs.next()) { + assertEquals(10, this.rs.getInt(1)); + assertEquals("aa", this.rs.getString(2)); + } + this.rs.close(); + + rs1 = st.executeQuery("select * from `test``Bug20802947`"); + rs1.absolute(1); + rs1.updateNull(2); + rs1.updateRow(); + rs1.close(); + this.rs = this.stmt.executeQuery("select * from `test``Bug20802947` where id=10"); + while (this.rs.next()) { + assertEquals(10, this.rs.getInt(1)); + assertNull(this.rs.getString(2)); + } + rs1.close(); + } + + /** + * Test fix for Bug#32954396, EXECUTEQUERY HANGS WITH USECURSORFETCH=TRUE & SETFETCHSIZE. + * + * @throws Exception + */ + @Test + public void testBug32954396() throws Exception { + createTable("testBug32954396", "(id INT, name VARCHAR(10))"); + + this.stmt.executeUpdate("INSERT INTO testBug32954396 VALUES (1, 'value1'), (2, 'value2')"); + + boolean useCursorFetch = false; + boolean setFetchSize = false; + do { + String testCase = String.format("Case: [useCursorFetch=%s, setFetchSize=%s]", useCursorFetch ? "Y" : "N", setFetchSize ? "Y" : "N"); + Properties props = new Properties(); + props.setProperty(PropertyKey.socketTimeout.getKeyName(), "1000"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), Boolean.toString(useCursorFetch)); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + this.pstmt = testConn.prepareStatement("SELECT id, name, (SELECT id FROM testBug32954396) FROM testBug32954396"); + if (setFetchSize) { + this.pstmt.setFetchSize(1); + } + assertThrows(testCase, SQLException.class, "Subquery returns more than 1 row", this.pstmt::executeQuery); + testConn.close(); + } while ((useCursorFetch = !useCursorFetch) || (setFetchSize = !setFetchSize)); + } + + /** + * Test fix for Bug#33185116, Have method ResultSet.getBoolean() supporting conversion of 'T' and 'F' in a VARCHAR to True/False (boolean). + * Extended the test for BUG#92574 (28706219), WHEN CONVERTING FROM VARCHAR TO JAVA BOOLEAN, 'N' IS NOT SUPPORTED. + * + * @throws Exception + */ + @Test + public void testBug33185116() throws Exception { + String[] strValues = new String[] { null, "N", "n", "Y", "y", "0", "1", "T", "t", "F", "f", "yes", "Yes", "no", "No", "true", "TrUe", "false", + "FalsE" }; + boolean[] boolValues = new boolean[] { false, false, false, true, true, false, true, true, true, false, false, true, true, false, false, true, true, + false, false }; + + createTable("testBug33185116", "(id int not null, f varchar(5), key(id))"); + for (int i = 0; i < strValues.length; i++) { + String val = strValues[i] == null ? null : "'" + strValues[i] + "'"; + this.stmt.executeUpdate("insert into testBug33185116 values(" + i + "," + val + ")"); + } + this.rs = this.stmt.executeQuery("SELECT * from testBug33185116"); + while (this.rs.next()) { + int i = this.rs.getInt(1); + assertEquals(strValues[i], this.rs.getString(2)); + assertEquals(boolValues[i], this.rs.getBoolean(2)); + } + } + + /** + * Tests for Bug#105197 (33461744), Statement.executeQuery() may return non-navigable ResultSet. + * + * @throws Exception + */ + @Test + public void testBug105197() throws Exception { + createProcedure("testBug105197Proc", "() BEGIN END"); + this.rs = this.stmt.executeQuery("CALL testBug105197Proc()"); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.absolute(1); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.relative(1); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.getRow(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.beforeFirst(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.isBeforeFirst(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.first(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.isFirst(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.previous(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.next(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.last(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.isLast(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.afterLast(); + return null; + }); + assertThrows(SQLException.class, "Not a navigable ResultSet\\.", () -> { + this.rs.isAfterLast(); + return null; + }); + } } diff --git a/src/test/java/testsuite/regression/StatementRegressionTest.java b/src/test/java/testsuite/regression/StatementRegressionTest.java index 12352408f..b5c53e9a1 100644 --- a/src/test/java/testsuite/regression/StatementRegressionTest.java +++ b/src/test/java/testsuite/regression/StatementRegressionTest.java @@ -37,6 +37,8 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -49,7 +51,6 @@ import java.io.PrintStream; import java.io.Reader; import java.io.StringReader; -import java.io.UnsupportedEncodingException; import java.io.Writer; import java.lang.reflect.Field; import java.math.BigDecimal; @@ -104,16 +105,19 @@ import java.util.function.ToIntFunction; import javax.sql.XAConnection; +import javax.sql.rowset.serial.SerialBlob; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CharsetMappingWrapper; import com.mysql.cj.ClientPreparedQuery; import com.mysql.cj.MysqlConnection; import com.mysql.cj.Query; import com.mysql.cj.ServerPreparedQuery; import com.mysql.cj.Session; import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJCommunicationsException; import com.mysql.cj.exceptions.ExceptionFactory; @@ -138,15 +142,19 @@ import com.mysql.cj.jdbc.result.ResultSetInternalMethods; import com.mysql.cj.log.Log; import com.mysql.cj.protocol.ColumnDefinition; +import com.mysql.cj.protocol.Message; import com.mysql.cj.protocol.Resultset; import com.mysql.cj.protocol.ResultsetRows; import com.mysql.cj.protocol.ServerSession; +import com.mysql.cj.protocol.a.NativeServerSession; import com.mysql.cj.util.LRUCache; +import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.TimeUtil; import testsuite.BaseQueryInterceptor; import testsuite.BaseTestCase; import testsuite.UnreliableSocketFactory; +import testsuite.simple.StatementsTest; /** * Regression tests for the Statement class @@ -371,24 +379,14 @@ private void execQueryBug5191(PreparedStatement pStmt, int catId) throws SQLExce assertFalse(this.rs.next()); } - private String getByteArrayString(byte[] ba) { - StringBuilder buffer = new StringBuilder(); - if (ba != null) { - for (int i = 0; i < ba.length; i++) { - buffer.append("0x" + Integer.toHexString(ba[i] & 0xff) + " "); - } - } else { - buffer.append("null"); - } - return buffer.toString(); - } - /** * @param continueBatchOnError * @throws SQLException */ private void innerBug6823(boolean continueBatchOnError) throws SQLException { Properties continueBatchOnErrorProps = new Properties(); + continueBatchOnErrorProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + continueBatchOnErrorProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); continueBatchOnErrorProps.setProperty(PropertyKey.continueBatchOnError.getKeyName(), String.valueOf(continueBatchOnError)); this.conn = getConnectionWithProps(continueBatchOnErrorProps); Statement statement = this.conn.createStatement(); @@ -479,7 +477,10 @@ public void testBug10630() throws Exception { Statement stmt2 = null; try { - conn2 = getConnectionWithProps((Properties) null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + conn2 = getConnectionWithProps(props); stmt2 = conn2.createStatement(); conn2.close(); @@ -550,6 +551,8 @@ public void testBug11540() throws Exception { this.stmt.executeUpdate("INSERT INTO testBug11540 VALUES (NOW(), NOW())"); Locale.setDefault(new Locale("th", "TH")); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); @@ -578,6 +581,8 @@ public void testBug11540() throws Exception { this.rs.close(); props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); TimeZone serverTz = ((MysqlConnection) testConn).getSession().getServerSession().getSessionTimeZone(); @@ -672,6 +677,8 @@ public void testBug13255() throws Exception { createTable("testBug13255", "(field_1 int)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); Connection reconnectConn = null; @@ -754,6 +761,8 @@ public void testBug15024() throws Exception { testStreamsForBug15024(false, false); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useConfigs.getKeyName(), "3-0-Compat"); Connection compatConn = null; @@ -832,6 +841,8 @@ public void testBug18041() throws Exception { createTable("testBug18041", "(`a` tinyint(4) NOT NULL, `b` char(4) default NULL)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); @@ -982,7 +993,8 @@ public void testBug1933() throws Exception { try { Properties props = new Properties(); - + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.maxRows.getKeyName(), "1"); maxRowsConn = getConnectionWithProps(props); @@ -1230,114 +1242,6 @@ public void testBug3557() throws Exception { } } - /** - * Tests fix for BUG#3620 -- Timezone not respected correctly. - * - * @throws SQLException - */ - @Test - public void testBug3620() throws SQLException { - // FIXME: This test is sensitive to being in CST/CDT it seems - if (!TimeZone.getDefault().equals(TimeZone.getTimeZone("America/Chicago"))) { - return; - } - - long epsillon = 3000; // 3 seconds time difference - - try { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3620"); - this.stmt.executeUpdate("CREATE TABLE testBug3620 (field1 TIMESTAMP)"); - - PreparedStatement tsPstmt = this.conn.prepareStatement("INSERT INTO testBug3620 VALUES (?)"); - - Calendar pointInTime = Calendar.getInstance(); - pointInTime.set(2004, 02, 29, 10, 0, 0); - - long pointInTimeOffset = pointInTime.getTimeZone().getRawOffset(); - - java.sql.Timestamp ts = new java.sql.Timestamp(pointInTime.getTime().getTime()); - - tsPstmt.setTimestamp(1, ts); - tsPstmt.executeUpdate(); - - String tsValueAsString = getSingleValue("testBug3620", "field1", null).toString(); - - System.out.println("Timestamp as string with no calendar: " + tsValueAsString.toString()); - - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - - this.stmt.executeUpdate("DELETE FROM testBug3620"); - - Statement tsStmt = this.conn.createStatement(); - - tsPstmt = this.conn.prepareStatement("INSERT INTO testBug3620 VALUES (?)"); - - tsPstmt.setTimestamp(1, ts, cal); - tsPstmt.executeUpdate(); - - tsValueAsString = getSingleValue("testBug3620", "field1", null).toString(); - - Timestamp tsValueAsTimestamp = (Timestamp) getSingleValue("testBug3620", "field1", null); - - System.out.println("Timestamp as string with UTC calendar: " + tsValueAsString.toString()); - System.out.println("Timestamp as Timestamp with UTC calendar: " + tsValueAsTimestamp); - - this.rs = tsStmt.executeQuery("SELECT field1 FROM testBug3620"); - this.rs.next(); - - Timestamp tsValueUTC = this.rs.getTimestamp(1, cal); - - // - // We use this testcase with other vendors, JDBC spec requires result set fields can only be read once, although MySQL doesn't require this ;) - // - this.rs = tsStmt.executeQuery("SELECT field1 FROM testBug3620"); - this.rs.next(); - - Timestamp tsValueStmtNoCal = this.rs.getTimestamp(1); - - System.out.println("Timestamp specifying UTC calendar from normal statement: " + tsValueUTC.toString()); - - PreparedStatement tsPstmtRetr = this.conn.prepareStatement("SELECT field1 FROM testBug3620"); - - this.rs = tsPstmtRetr.executeQuery(); - this.rs.next(); - - Timestamp tsValuePstmtUTC = this.rs.getTimestamp(1, cal); - - System.out.println("Timestamp specifying UTC calendar from prepared statement: " + tsValuePstmtUTC.toString()); - - // - // We use this testcase with other vendors, JDBC spec requires result set fields can only be read once, although MySQL doesn't require this ;) - // - this.rs = tsPstmtRetr.executeQuery(); - this.rs.next(); - - Timestamp tsValuePstmtNoCal = this.rs.getTimestamp(1); - - System.out.println("Timestamp specifying no calendar from prepared statement: " + tsValuePstmtNoCal.toString()); - - long stmtDeltaTWithCal = (ts.getTime() - tsValueStmtNoCal.getTime()); - - long deltaOrig = Math.abs(stmtDeltaTWithCal - pointInTimeOffset); - - assertTrue((deltaOrig < epsillon), "Difference between original timestamp and timestamp retrieved using java.sql.Statement " - + "set in database using UTC calendar is not ~= " + epsillon + ", it is actually " + deltaOrig); - - long pStmtDeltaTWithCal = (ts.getTime() - tsValuePstmtNoCal.getTime()); - - System.out.println( - Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) + " < " + epsillon + (Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) < epsillon)); - assertTrue((Math.abs(pStmtDeltaTWithCal - pointInTimeOffset) < epsillon), - "Difference between original timestamp and timestamp retrieved using java.sql.PreparedStatement " - + "set in database using UTC calendar is not ~= " + epsillon + ", it is actually " + pStmtDeltaTWithCal); - - System.out.println("Difference between original ts and ts with no calendar: " + (ts.getTime() - tsValuePstmtNoCal.getTime()) + ", offset should be " - + pointInTimeOffset); - } finally { - this.stmt.executeUpdate("DROP TABLE IF EXISTS testBug3620"); - } - } - /** * Tests fix for BUG#3620 -- Timezone not respected correctly. * @@ -1345,13 +1249,8 @@ public void testBug3620() throws SQLException { * */ @Test - public void testBug3620new() throws SQLException { - // TODO: should replace testBug3620() - if (this.DISABLED_testBug3620new) { - // TODO: this test is working in c/J 5.1 but fails here; disable for later analysis - return; - } - + @Disabled("this test is working in c/J 5.1 but fails here; disabled for later analysis") + public void testBug3620new() throws SQLException { // TODO: should replace testBug3620() final long epsillon = 3000; // allow 3 seconds time difference TimeZone defaultTimeZone = TimeZone.getDefault(); @@ -1786,6 +1685,8 @@ public void testBug5191() throws Exception { @Test public void testBug5235() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL"); if (versionMeetsMinimum(5, 7, 4)) { props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); @@ -1824,6 +1725,8 @@ public void testBug5450() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf-8"); Connection utf8Conn = getConnectionWithProps(props); @@ -1892,75 +1795,6 @@ public void testBug5510() throws Exception { pStmt.executeUpdate(); } - /** - * Tests fix for BUG#5874, timezone correction goes in wrong 'direction' (when useTimezone=true and server timezone differs from client timezone). - * - * @throws Exception - */ - @Test - public void testBug5874() throws Exception { - TimeZone defaultTimeZone = TimeZone.getDefault(); - - try { - String clientTimeZoneName = "America/Los_Angeles"; - String connectionTimeZoneName = "America/Chicago"; - - TimeZone.setDefault(TimeZone.getTimeZone(clientTimeZoneName)); - - long offsetDifference = TimeZone.getDefault().getRawOffset() - TimeZone.getTimeZone(connectionTimeZoneName).getRawOffset(); - - SimpleDateFormat timestampFormat = TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd HH:mm:ss", null); - SimpleDateFormat timeFormat = TimeUtil.getSimpleDateFormat(null, "HH:mm:ss", null); - - long pointInTime = timestampFormat.parse("2004-10-04 09:19:00").getTime(); - - Properties props = new Properties(); - props.put("useTimezone", "true"); - props.put(PropertyKey.connectionTimeZone.getKeyName(), connectionTimeZoneName); - props.put(PropertyKey.forceConnectionTimeZoneToSession.getKeyName(), "true"); - props.put(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); - props.setProperty(PropertyKey.preserveInstants.getKeyName(), "true"); - - Connection tzConn = getConnectionWithProps(props); - Statement tzStmt = tzConn.createStatement(); - createTable("testBug5874", "(tstamp DATETIME, t TIME)"); - - PreparedStatement tsPstmt = tzConn.prepareStatement("INSERT INTO testBug5874 VALUES (?, ?)"); - - tsPstmt.setTimestamp(1, new Timestamp(pointInTime)); - tsPstmt.setTime(2, new Time(pointInTime)); - tsPstmt.executeUpdate(); - - this.rs = tzStmt.executeQuery("SELECT * from testBug5874"); - - while (this.rs.next()) { // Driver now converts/checks DATE/TIME/TIMESTAMP/DATETIME types when calling getString()... - String retrTimestampString = new String(this.rs.getBytes(1)); - Timestamp retrTimestamp = this.rs.getTimestamp(1); - - java.util.Date timestampOnServer = timestampFormat.parse(retrTimestampString); - - long retrievedOffsetForTimestamp = retrTimestamp.getTime() - timestampOnServer.getTime(); - - assertEquals(offsetDifference, retrievedOffsetForTimestamp, - "Original timestamp and timestamp retrieved using client timezone are not the same"); - - String retrTimeString = new String(this.rs.getBytes(2)); - Time retrTime = this.rs.getTime(2); - - java.util.Date timeOnServerAsDate = timeFormat.parse(retrTimeString); - Time timeOnServer = new Time(timeOnServerAsDate.getTime()); - - long retrievedOffsetForTime = retrTime.getTime() - timeOnServer.getTime(); - - assertEquals(0, retrievedOffsetForTime, "Original time and time retrieved using client timezone are not the same"); - } - - tzConn.close(); - } finally { - TimeZone.setDefault(defaultTimeZone); - } - } - @Test public void testBug6823() throws SQLException { innerBug6823(true); @@ -2063,6 +1897,8 @@ public void testBug9704() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); multiStmtConn = getConnectionWithProps(props); @@ -2124,83 +1960,6 @@ public void testCloseTwice() throws Exception { closeMe.close(); } - @Test - public void testCsc4194() throws Exception { - try { - "".getBytes("Windows-31J"); - } catch (UnsupportedEncodingException ex) { - return; // test doesn't work on this platform - } - - Connection sjisConn = null; - Connection windows31JConn = null; - - try { - String tableNameText = "testCsc4194Text"; - String tableNameBlob = "testCsc4194Blob"; - - createTable(tableNameBlob, "(field1 BLOB)"); - String charset = ""; - - charset = " CHARACTER SET cp932"; - - createTable(tableNameText, "(field1 TEXT)" + charset); - - Properties windows31JProps = new Properties(); - windows31JProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "Windows-31J"); - - windows31JConn = getConnectionWithProps(windows31JProps); - testCsc4194InsertCheckBlob(windows31JConn, tableNameBlob); - - testCsc4194InsertCheckText(windows31JConn, tableNameText, "Windows-31J"); - - Properties sjisProps = new Properties(); - sjisProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "sjis"); - - sjisConn = getConnectionWithProps(sjisProps); - testCsc4194InsertCheckBlob(sjisConn, tableNameBlob); - testCsc4194InsertCheckText(sjisConn, tableNameText, "Windows-31J"); - - } finally { - - if (windows31JConn != null) { - windows31JConn.close(); - } - - if (sjisConn != null) { - sjisConn.close(); - } - } - } - - private void testCsc4194InsertCheckBlob(Connection c, String tableName) throws Exception { - byte[] bArray = new byte[] { (byte) 0xac, (byte) 0xed, (byte) 0x00, (byte) 0x05 }; - - PreparedStatement testStmt = c.prepareStatement("INSERT INTO " + tableName + " VALUES (?)"); - testStmt.setBytes(1, bArray); - testStmt.executeUpdate(); - - this.rs = c.createStatement().executeQuery("SELECT field1 FROM " + tableName); - assertTrue(this.rs.next()); - assertEquals(getByteArrayString(bArray), getByteArrayString(this.rs.getBytes(1))); - this.rs.close(); - } - - private void testCsc4194InsertCheckText(Connection c, String tableName, String encoding) throws Exception { - byte[] kabuInShiftJIS = { (byte) 0x87, // a double-byte charater("kabu") in Shift JIS - (byte) 0x8a, }; - - String expected = new String(kabuInShiftJIS, encoding); - PreparedStatement testStmt = c.prepareStatement("INSERT INTO " + tableName + " VALUES (?)"); - testStmt.setString(1, expected); - testStmt.executeUpdate(); - - this.rs = c.createStatement().executeQuery("SELECT field1 FROM " + tableName); - assertTrue(this.rs.next()); - assertEquals(expected, this.rs.getString(1)); - this.rs.close(); - } - /** * Tests all forms of statements influencing getGeneratedKeys(). * @@ -2344,71 +2103,70 @@ public void testLimitAndMaxRows() throws Exception { */ @Test public void testLoadData() throws Exception { - try { - //int maxAllowedPacket = 1048576; + assumeTrue(supportsLoadLocalInfile(this.stmt), "This test requires the server started with --local-infile=ON"); - this.stmt.executeUpdate("DROP TABLE IF EXISTS loadDataRegress"); - this.stmt.executeUpdate("CREATE TABLE loadDataRegress (field1 int, field2 int)"); + createTable("loadDataRegress", "(field1 int, field2 int)"); - File tempFile = File.createTempFile("mysql", ".txt"); + //int maxAllowedPacket = 1048576; - // tempFile.deleteOnExit(); - System.out.println(tempFile); + File tempFile = File.createTempFile("mysql", ".txt"); - Writer out = new FileWriter(tempFile); + // tempFile.deleteOnExit(); + System.out.println(tempFile); - int localCount = 0; - int rowCount = 128; // maxAllowedPacket * 4; + Writer out = new FileWriter(tempFile); - for (int i = 0; i < rowCount; i++) { - out.write((localCount++) + "\t" + (localCount++) + "\n"); - } + int localCount = 0; + int rowCount = 128; // maxAllowedPacket * 4; - out.close(); + for (int i = 0; i < rowCount; i++) { + out.write((localCount++) + "\t" + (localCount++) + "\n"); + } - StringBuilder fileNameBuf = null; + out.close(); - if (File.separatorChar == '\\') { - fileNameBuf = new StringBuilder(); + StringBuilder fileNameBuf = null; - String fileName = tempFile.getAbsolutePath(); - int fileNameLength = fileName.length(); + if (File.separatorChar == '\\') { + fileNameBuf = new StringBuilder(); - for (int i = 0; i < fileNameLength; i++) { - char c = fileName.charAt(i); + String fileName = tempFile.getAbsolutePath(); + int fileNameLength = fileName.length(); - if (c == '\\') { - fileNameBuf.append("/"); - } else { - fileNameBuf.append(c); - } + for (int i = 0; i < fileNameLength; i++) { + char c = fileName.charAt(i); + + if (c == '\\') { + fileNameBuf.append("/"); + } else { + fileNameBuf.append(c); } - } else { - fileNameBuf = new StringBuilder(tempFile.getAbsolutePath()); } - final String fileName = fileNameBuf.toString(); - - assertThrows(SQLSyntaxErrorException.class, - versionMeetsMinimum(8, 0, 19) ? "Loading local data is disabled;.*" : "The used command is not allowed with this MySQL version", () -> { - this.stmt.executeUpdate("LOAD DATA LOCAL INFILE '" + fileName + "' INTO TABLE loadDataRegress CHARACTER SET " - + CharsetMapping.getMysqlCharsetForJavaEncoding( - ((MysqlConnection) this.conn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), - this.serverVersion)); - return null; - }); + } else { + fileNameBuf = new StringBuilder(tempFile.getAbsolutePath()); + } + final String fileName = fileNameBuf.toString(); - Properties props = new Properties(); - props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); - Connection testConn = getConnectionWithProps(props); - int updateCount = testConn.createStatement() - .executeUpdate("LOAD DATA LOCAL INFILE '" + fileNameBuf.toString() + "' INTO TABLE loadDataRegress CHARACTER SET " - + CharsetMapping.getMysqlCharsetForJavaEncoding( + assertThrows(SQLSyntaxErrorException.class, + versionMeetsMinimum(8, 0, 19) ? "Loading local data is disabled;.*" : "The used command is not allowed with this MySQL version", () -> { + this.stmt.executeUpdate("LOAD DATA LOCAL INFILE '" + fileName + "' INTO TABLE loadDataRegress CHARACTER SET " + + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding( ((MysqlConnection) this.conn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), this.serverVersion)); - assertTrue(updateCount == rowCount); - } finally { - this.stmt.executeUpdate("DROP TABLE IF EXISTS loadDataRegress"); - } + return null; + }); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + int updateCount = testConn.createStatement() + .executeUpdate("LOAD DATA LOCAL INFILE '" + fileNameBuf.toString() + "' INTO TABLE loadDataRegress CHARACTER SET " + + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding( + ((MysqlConnection) this.conn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), + this.serverVersion)); + assertTrue(updateCount == rowCount); } @Test @@ -2581,7 +2339,10 @@ public void testServerPrepStmtAndDate() throws Exception { @Test public void testServerPrepStmtDeadlock() throws Exception { - Connection c = getConnectionWithProps((Properties) null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection c = getConnectionWithProps(props); Thread testThread1 = new PrepareThread(c); Thread testThread2 = new PrepareThread(c); @@ -2947,7 +2708,10 @@ public void testBug20029() throws Exception { long initialTimeout = 20; // may need to raise this depending on environment we try and do this automatically in this testcase for (int i = 0; i < 10; i++) { - final Connection toBeKilledConn = getConnectionWithProps(new Properties()); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + final Connection toBeKilledConn = getConnectionWithProps(props); final long timeout = initialTimeout; PreparedStatement toBeKilledPstmt = null; @@ -3013,6 +2777,8 @@ public void testBug20687() throws Exception { Connection poolingConn = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); PreparedStatement pstmt1 = null; @@ -3049,6 +2815,8 @@ public void testLikeWithBackslashes() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode=NO_BACKSLASH_ESCAPES"); noBackslashEscapesConn = getConnectionWithProps(props); @@ -3093,7 +2861,10 @@ public void testBug20650() throws Exception { Statement cancelStmt = null; try { - closedConn = getConnectionWithProps((String) null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + closedConn = getConnectionWithProps(props); cancelStmt = closedConn.createStatement(); closedConn.close(); @@ -3209,6 +2980,8 @@ public void testBug22290() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='STRICT_TRANS_TABLES'"); configuredConn = getConnectionWithProps(props); @@ -3247,6 +3020,8 @@ public void testBug24360() throws Exception { Connection c = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); try { @@ -3280,6 +3055,8 @@ public void testBug24344() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); conn2 = getConnectionWithProps(props); @@ -3326,6 +3103,8 @@ public void testBug24344() throws Exception { @Test public void testBug25073() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); Connection multiConn = getConnectionWithProps(props); createTable("testBug25073", "(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)"); @@ -3347,6 +3126,8 @@ public void testBug25073() throws Exception { createTable("testBug25073", "(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)"); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "1024"); props.setProperty(PropertyKey.dumpQueriesOnException.getKeyName(), "true"); @@ -3369,6 +3150,8 @@ public void testBug25073() throws Exception { createTable("testBug25073", "(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)"); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); props.setProperty(PropertyKey.dumpQueriesOnException.getKeyName(), "true"); @@ -3420,6 +3203,8 @@ public void testBug25073() throws Exception { @Test public void testBug25009() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); Connection multiConn = getConnectionWithProps(props); @@ -3467,6 +3252,8 @@ public void testBug25025() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); @@ -3557,15 +3344,12 @@ public void testBug28469() throws Exception { for (int i = 0; i < statementsToTest.length; i++) { commentStmt = this.conn.prepareStatement(statementsToTest[i]); - assertNotNull(commentStmt.getMetaData()); - - try { - commentStmt.executeUpdate(); - fail("Should not be able to call executeUpdate() on a SELECT statement!"); - } catch (SQLException sqlEx) { - // expected - } + PreparedStatement localCommentStmt = commentStmt; + assertThrows("Should not be able to call executeUpdate() on a SELECT statement!", SQLException.class, () -> { + localCommentStmt.executeUpdate(); + return null; + }); this.rs = commentStmt.executeQuery(); this.rs.next(); @@ -3579,22 +3363,18 @@ public void testBug28469() throws Exception { for (int i = 0; i < updatesToTest.length; i++) { commentStmt = this.conn.prepareStatement(updatesToTest[i]); - + PreparedStatement localCommentStmt = commentStmt; assertNull(commentStmt.getMetaData()); - try { - this.rs = commentStmt.executeQuery(); - fail("Should not be able to call executeQuery() on a SELECT statement!"); - } catch (SQLException sqlEx) { - // expected - } - - try { - this.rs = this.stmt.executeQuery(updatesToTest[i]); - fail("Should not be able to call executeQuery() on a SELECT statement!"); - } catch (SQLException sqlEx) { - // expected - } + assertThrows("Should not be able to call executeQuery() on a SELECT statement!", SQLException.class, () -> { + localCommentStmt.executeQuery(); + return null; + }); + int localI = i; + assertThrows("Should not be able to call executeQuery() on a SELECT statement!", SQLException.class, () -> { + this.stmt.executeQuery(updatesToTest[localI]); + return null; + }); } } finally { if (commentStmt != null) { @@ -3667,7 +3447,11 @@ public void testBug28596() throws Exception { public void testBug30550() throws Exception { createTable("testBug30550", "(field1 int)"); - Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection rewriteConn = getConnectionWithProps(props); PreparedStatement batchPStmt = null; Statement batchStmt = null; @@ -3711,6 +3495,8 @@ public void testBug30550() throws Exception { @Test public void testBug27412() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), "true"); @@ -3825,6 +3611,8 @@ public void testLancesBitMappingBug() throws Exception { public void testBug32577() throws Exception { createTable("testBug32577", "(id INT, field_datetime DATETIME, field_timestamp TIMESTAMP)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "time_zone='+0:00'"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "UTC"); @@ -4792,11 +4580,16 @@ public Object getSyncMutex() { public void testBug34093() throws Exception { Connection rewriteConn = null; - rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + rewriteConn = getConnectionWithProps(props); checkBug34093(rewriteConn); - rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true,useServerPrepStmts=true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + rewriteConn = getConnectionWithProps(props); checkBug34093(rewriteConn); } @@ -4915,7 +4708,11 @@ public void testBug34093_nonbatch() throws Exception { @Test public void testBug34518() throws Exception { - Connection fetchConn = getConnectionWithProps("useCursorFetch=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + Connection fetchConn = getConnectionWithProps(props); Statement fetchStmt = fetchConn.createStatement(); int stmtCount = ((com.mysql.cj.jdbc.JdbcConnection) fetchConn).getActiveStatementCount(); @@ -4960,7 +4757,11 @@ public void testBug35170() throws Exception { @Test public void testBug35666() throws Exception { - Connection loggingConn = getConnectionWithProps("logSlowQueries=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.logSlowQueries.getKeyName(), "true"); + Connection loggingConn = getConnectionWithProps(props); this.pstmt = ((com.mysql.cj.jdbc.JdbcConnection) loggingConn).serverPrepareStatement("SELECT SLEEP(4)"); this.pstmt.execute(); } @@ -4975,7 +4776,11 @@ public void testDeadlockBatchBehavior() throws Exception { this.conn.setAutoCommit(false); this.rs = this.conn.createStatement().executeQuery("SELECT * FROM t1 WHERE id=0 FOR UPDATE"); - final Connection deadlockConn = getConnectionWithProps("includeInnodbStatusInDeadlockExceptions=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.includeInnodbStatusInDeadlockExceptions.getKeyName(), "true"); + final Connection deadlockConn = getConnectionWithProps(props); deadlockConn.setAutoCommit(false); final Statement deadlockStmt = deadlockConn.createStatement(); @@ -5016,7 +4821,11 @@ public void run() { @Test public void testBug39352() throws Exception { - Connection affectedRowsConn = getConnectionWithProps("useAffectedRows=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useAffectedRows.getKeyName(), "true"); + Connection affectedRowsConn = getConnectionWithProps(props); try { @@ -5075,7 +4884,11 @@ public void testBug39956() throws Exception { String tableName = "testBug39956_" + engineName; - Connection twoConn = getConnectionWithProps("sessionVariables=auto_increment_increment=2"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "auto_increment_increment=2"); + Connection twoConn = getConnectionWithProps(props); try { for (int i = 0; i < 2; i++) { @@ -5149,7 +4962,11 @@ public void testBug34185() throws Exception { public void testBug41161() throws Exception { createTable("testBug41161", "(a int, b int)"); - Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection rewriteConn = getConnectionWithProps(props); try { this.pstmt = rewriteConn.prepareStatement("INSERT INTO testBug41161 (a, b) VALUES (?, ?, ?)"); @@ -5189,12 +5006,10 @@ public void testBug41448() throws Exception { this.stmt.executeUpdate("INSERT INTO testBug41448 (field1) VALUES ('ghi')"); - try { + assertThrows(SQLException.class, () -> { this.stmt.getGeneratedKeys(); - fail("Expected a SQLException here"); - } catch (SQLException sqlEx) { - // expected - } + return null; + }); this.stmt.execute("INSERT INTO testBug41448 (field1) VALUES ('jkl')", Statement.RETURN_GENERATED_KEYS); this.stmt.getGeneratedKeys(); @@ -5207,12 +5022,10 @@ public void testBug41448() throws Exception { this.stmt.execute("INSERT INTO testBug41448 (field1) VALUES ('stu')"); - try { + assertThrows(SQLException.class, () -> { this.stmt.getGeneratedKeys(); - fail("Expected a SQLException here"); - } catch (SQLException sqlEx) { - // expected - } + return null; + }); this.pstmt = this.conn.prepareStatement("INSERT INTO testBug41448 (field1) VALUES (?)", Statement.RETURN_GENERATED_KEYS); this.pstmt.setString(1, "abc"); @@ -5238,27 +5051,28 @@ public void testBug41448() throws Exception { this.pstmt = this.conn.prepareStatement("INSERT INTO testBug41448 (field1) VALUES (?)"); this.pstmt.setString(1, "abc"); this.pstmt.executeUpdate(); - try { + assertThrows(SQLException.class, () -> { this.pstmt.getGeneratedKeys(); - fail("Expected a SQLException here"); - } catch (SQLException sqlEx) { - // expected - } + return null; + }); this.pstmt.execute(); - try { + assertThrows(SQLException.class, () -> { this.pstmt.getGeneratedKeys(); - fail("Expected a SQLException here"); - } catch (SQLException sqlEx) { - // expected - } + return null; + }); } @Test public void testBug48172() throws Exception { createTable("testBatchInsert", "(a INT PRIMARY KEY AUTO_INCREMENT)"); - Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true,dumpQueriesOnException=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + props.setProperty(PropertyKey.dumpQueriesOnException.getKeyName(), "true"); + Connection rewriteConn = getConnectionWithProps(props); assertEquals("0", getSingleIndexedValueWithQuery(rewriteConn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); this.pstmt = rewriteConn.prepareStatement("INSERT INTO testBatchInsert VALUES (?)"); @@ -5312,26 +5126,43 @@ public void testBug48172() throws Exception { */ @Test public void testBug41532() throws Exception { + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); + this.rs.next(); + long len = 1024 * 1024 * 2; + long defaultMaxAllowedPacket = this.rs.getInt(2); + boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < len; + createTable("testBug41532", "(ID INTEGER, S1 VARCHAR(100), S2 VARCHAR(100), S3 VARCHAR(100), D1 DATETIME, D2 DATETIME, D3 DATETIME, " + "N1 DECIMAL(28,6), N2 DECIMAL(28,6), N3 DECIMAL(28,6), UNIQUE KEY UNIQUE_KEY_TEST_DUPLICATE (ID) )"); - int numTests = 5000; - Connection rewriteConn = getConnectionWithProps("useSSL=false,allowPublicKeyRetrieval=true,rewriteBatchedStatements=true,dumpQueriesOnException=true"); + try { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + len); + } - assertEquals("0", getSingleIndexedValueWithQuery(rewriteConn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); - long batchedTime = timeBatch(rewriteConn, numTests); - assertEquals("1", getSingleIndexedValueWithQuery(rewriteConn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); + int numTests = 5000; + Connection rewriteConn = getConnectionWithProps( + "useSSL=false,allowPublicKeyRetrieval=true,rewriteBatchedStatements=true,dumpQueriesOnException=true"); + + assertEquals("0", getSingleIndexedValueWithQuery(rewriteConn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); + long batchedTime = timeBatch(rewriteConn, numTests); + assertEquals("1", getSingleIndexedValueWithQuery(rewriteConn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); - this.stmt.executeUpdate("TRUNCATE TABLE testBug41532"); + this.stmt.executeUpdate("TRUNCATE TABLE testBug41532"); - assertEquals("0", getSingleIndexedValueWithQuery(this.conn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); - long unbatchedTime = timeBatch(this.conn, numTests); - assertEquals(String.valueOf(numTests), getSingleIndexedValueWithQuery(this.conn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); - assertTrue(batchedTime < unbatchedTime); + assertEquals("0", getSingleIndexedValueWithQuery(this.conn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); + long unbatchedTime = timeBatch(this.conn, numTests); + assertEquals(String.valueOf(numTests), getSingleIndexedValueWithQuery(this.conn, 2, "SHOW SESSION STATUS LIKE 'Com_insert'").toString()); + assertTrue(batchedTime < unbatchedTime); - rewriteConn = getConnectionWithProps( - "useSSL=false,allowPublicKeyRetrieval=true,rewriteBatchedStatements=true,useCursorFetch=true,defaultFetchSize=10000"); - timeBatch(rewriteConn, numTests); + rewriteConn = getConnectionWithProps( + "useSSL=false,allowPublicKeyRetrieval=true,rewriteBatchedStatements=true,useCursorFetch=true,defaultFetchSize=10000"); + timeBatch(rewriteConn, numTests); + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket); + } + } } private long timeBatch(Connection c, int numberOfRows) throws SQLException { @@ -5401,43 +5232,6 @@ private void checkOpenResultsFor44056(Statement newStmt) throws SQLException { assertEquals(0, ((com.mysql.cj.jdbc.JdbcStatement) newStmt).getOpenResultSetCount()); } - /** - * Bug #41730 - SQL Injection when using U+00A5 and SJIS/Windows-31J - * - * @throws Exception - */ - @Test - public void testBug41730() throws Exception { - try { - "".getBytes("sjis"); - } catch (UnsupportedEncodingException ex) { - return; // test doesn't work on this platform - } - - Connection conn2 = null; - PreparedStatement pstmt2 = null; - try { - conn2 = getConnectionWithProps("characterEncoding=sjis"); - pstmt2 = conn2.prepareStatement("select ?"); - pstmt2.setString(1, "\u00A5'"); - // this will throw an exception with a syntax error if it fails - this.rs = pstmt2.executeQuery(); - } finally { - try { - if (pstmt2 != null) { - pstmt2.close(); - } - } catch (SQLException ex) { - } - try { - if (conn2 != null) { - conn2.close(); - } - } catch (SQLException ex) { - } - } - } - @Test public void testBug43196() throws Exception { createTable("`bug43196`", @@ -5520,7 +5314,11 @@ public void testBug40439() throws Exception { Connection conn2 = null; try { createTable("testBug40439VALUES", "(x int)"); - conn2 = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + conn2 = getConnectionWithProps(props); PreparedStatement ps = conn2.prepareStatement("insert into testBug40439VALUES (x) values (?)"); ps.setInt(1, 1); ps.addBatch(); @@ -5574,6 +5372,8 @@ public T preProcess(Supplier sql, Query intercepte public void testBug39426() throws Exception { for (boolean useSPS : new boolean[] { false, true }) { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), "testsuite.regression.StatementRegressionTest$Bug39426Interceptor"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); @@ -5606,7 +5406,11 @@ public void testBugDupeKeySingle() throws Exception { createTable("testBugDupeKeySingle", "(field1 int not null primary key)"); Connection conn2 = null; try { - conn2 = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + conn2 = getConnectionWithProps(props); this.pstmt = conn2.prepareStatement("INSERT INTO testBugDupeKeySingle VALUES (?) ON DUPLICATE KEY UPDATE field1=VALUES(field1)"); this.pstmt.setInt(1, 1); @@ -5667,7 +5471,10 @@ public void testBug34555() throws Exception { createTable("testBug34555", "(field1 int)", "INNODB"); this.stmt.executeUpdate("INSERT INTO testBug34555 VALUES (0)"); - final Connection lockerConn = getConnectionWithProps(""); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + final Connection lockerConn = getConnectionWithProps(props); lockerConn.setAutoCommit(false); lockerConn.createStatement().execute("SELECT * FROM testBug34555 WHERE field1=0 FOR UPDATE"); @@ -5694,7 +5501,11 @@ public void testBug34555() throws Exception { public void testBug46788() throws Exception { createTable("testBug46788", "(modified varchar(32), id varchar(32))"); - Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection rewriteConn = getConnectionWithProps(props); this.pstmt = rewriteConn.prepareStatement("insert into testBug46788 (modified,id) values (?,?) ON DUPLICATE KEY UPDATE modified=?"); @@ -5712,7 +5523,11 @@ public void testBug46788() throws Exception { @Test public void testBug31193() throws Exception { createTable("bug31193", "(sometime datetime, junk text)"); - Connection fetchConn = getConnectionWithProps("useCursorFetch=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + Connection fetchConn = getConnectionWithProps(props); Statement fetchStmt = fetchConn.createStatement(); fetchStmt.setFetchSize(10000); @@ -5731,6 +5546,8 @@ public void testBug31193() throws Exception { @Test public void testBug51776() throws Exception { Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.socketFactory.getKeyName(), "testsuite.UnreliableSocketFactory"); Properties parsed = getPropertiesFromTestsuiteUrl(); @@ -5745,17 +5562,19 @@ public void testBug51776() throws Exception { testConn.setAutoCommit(false); testConn.createStatement().execute("SELECT 1"); UnreliableSocketFactory.downHost("first"); - try { + assertThrows("Should receive SQLException on rollback().", SQLException.class, () -> { testConn.rollback(); - fail("Should receive SQLException on rollback()."); - } catch (SQLException e) { - - } + return null; + }); } @Test public void testBug51666() throws Exception { - Connection testConn = getConnectionWithProps("queryInterceptors=" + TestBug51666QueryInterceptor.class.getName()); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug51666QueryInterceptor.class.getName()); + Connection testConn = getConnectionWithProps(props); createTable("testQueryInterceptorCount", "(field1 int)"); this.stmt.executeUpdate("INSERT INTO testQueryInterceptorCount VALUES (0)"); ResultSet testRs = testConn.createStatement().executeQuery("SHOW SESSION STATUS LIKE 'Com_select'"); @@ -5802,7 +5621,11 @@ public void testReversalOfScanFlags() throws Exception { createTable("testReversalOfScanFlags", "(field1 int)"); this.stmt.executeUpdate("INSERT INTO testReversalOfScanFlags VALUES (1),(2),(3)"); - Connection scanningConn = getConnectionWithProps("queryInterceptors=" + ScanDetectingInterceptor.class.getName()); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), ScanDetectingInterceptor.class.getName()); + Connection scanningConn = getConnectionWithProps(props); try { this.rs = scanningConn.createStatement().executeQuery("SELECT field1 FROM testReversalOfScanFlags"); @@ -5840,7 +5663,11 @@ public T postProcess(Supplier sql, Query intercept @Test public void testBug51704() throws Exception { createTable("testBug51704", "(field1 TIMESTAMP)"); - Connection rewriteConn = getConnectionWithProps("rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection rewriteConn = getConnectionWithProps(props); Statement rewriteStmt = rewriteConn.createStatement(); try { @@ -5864,7 +5691,11 @@ public void testBug51704() throws Exception { @Test public void testBug54175() throws Exception { - Connection utf8conn = getConnectionWithProps("characterEncoding=utf8"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + Connection utf8conn = getConnectionWithProps(props); createTable("testBug54175", "(a VARCHAR(10)) CHARACTER SET utf8mb4"); this.stmt.execute("INSERT INTO testBug54175 VALUES(0xF0AFA6B2)"); @@ -5887,6 +5718,8 @@ public void testBug58728() throws Exception { MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource(); pds.setUrl(dbUrl); + pds.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + pds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).setValue(true); Statement stmt1 = pds.getPooledConnection().getConnection().createStatement(); stmt1.executeUpdate("UPDATE testbug58728 SET txt = 'New text' WHERE Id > 0"); ResultSet rs1 = stmt1.getResultSet(); @@ -5994,6 +5827,8 @@ public void testbug12565726() throws Exception { // and b) to generate the wrong query with multiple ON DUPLICATE KEY Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); props.setProperty(PropertyKey.enablePacketDebug.getKeyName(), "true"); @@ -6047,6 +5882,8 @@ public void testBug36478() throws Exception { PreparedStatement s = null; try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); _conn = getConnectionWithProps(props); @@ -6085,9 +5922,7 @@ public void testBug36478() throws Exception { */ @Test public void testBug40279() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "MySQL 5.6.4+ is required to run this test."); createTable("testBug40279", "(f1 int, f2 timestamp(6))"); @@ -6098,6 +5933,8 @@ public void testBug40279() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "UTC"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); ps_conn = getConnectionWithProps(props); @@ -6228,7 +6065,12 @@ void undoSystemErrDiversion() throws IOException { Connection getNewConnectionForSlowQueries() throws SQLException { releaseConnectionResources(); - this.testConn = getConnectionWithProps("logSlowQueries=true,explainSlowQueries=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.logSlowQueries.getKeyName(), "true"); + props.setProperty(PropertyKey.explainSlowQueries.getKeyName(), "true"); + this.testConn = getConnectionWithProps(props); Statement st = this.testConn.createStatement(); // execute several fast queries to unlock slow query analysis and lower query execution time mean for (int i = 0; i < 25; i++) { @@ -6342,6 +6184,8 @@ private int[] testBug68562ExecuteBatch(int batchSize, boolean useAffectedRows, b String tableName = "testBug68562"; Properties properties = new Properties(); + properties.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + properties.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); if (useAffectedRows) { properties.setProperty(PropertyKey.useAffectedRows.getKeyName(), "true"); tableName += "_affected"; @@ -6378,7 +6222,11 @@ private int[] testBug68562ExecuteBatch(int batchSize, boolean useAffectedRows, b */ @Test public void testBug55340() throws Exception { - Connection testConnCacheRSMD = getConnectionWithProps("cacheResultSetMetadata=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), "true"); + Connection testConnCacheRSMD = getConnectionWithProps(props); ResultSetMetaData rsmd; createTable("testBug55340", "(col1 INT, col2 CHAR(10))"); @@ -6441,7 +6289,10 @@ public void testBug71396() throws Exception { /* * Case 1: Statement.executeQuery() and Statement.execute() with plain Connection. */ - testConn = getConnectionWithProps(""); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + testConn = getConnectionWithProps(props); // safety check testBug71396StatementMultiCheck(testConn, queries, new int[] { 2, 4, 3, 3, 3 }); @@ -6464,7 +6315,7 @@ public void testBug71396() throws Exception { /* * Case 2: PreparedStatement.executeQuery() and PreparedStatement.execute() with plain Connection. */ - testConn = getConnectionWithProps(""); + testConn = getConnectionWithProps(props); // safety check testBug71396PrepStatementMultiCheck(testConn, queries, new int[] { 2, 4, 3, 3, 3 }); @@ -6492,7 +6343,8 @@ public void testBug71396() throws Exception { * Case 3: PreparedStatement.executeQuery() and PreparedStatement.execute() with * Connection[useServerPrepStmts=true]. */ - testConn = getConnectionWithProps("useServerPrepStmts=true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + testConn = getConnectionWithProps(props); // safety check testBug71396PrepStatementMultiCheck(testConn, queries, new int[] { 2, 4, 3, 3, 3 }); @@ -6519,7 +6371,9 @@ public void testBug71396() throws Exception { /* * Case 4: Statement.executeQuery() and Statement.execute() with Connection[maxRows=2]. */ - testConn = getConnectionWithProps("maxRows=2"); + props.remove(PropertyKey.useServerPrepStmts.getKeyName()); + props.setProperty(PropertyKey.maxRows.getKeyName(), "2"); + testConn = getConnectionWithProps(props); // safety check testBug71396StatementMultiCheck(testConn, queries, new int[] { 2, 2, 2, 2, 2 }); @@ -6542,7 +6396,7 @@ public void testBug71396() throws Exception { /* * Case 5: PreparedStatement.executeQuery() and PreparedStatement.execute() with Connection[maxRows=2]. */ - testConn = getConnectionWithProps("maxRows=2"); + testConn = getConnectionWithProps(props); // safety check testBug71396PrepStatementMultiCheck(testConn, queries, new int[] { 2, 2, 2, 2, 2 }); @@ -6570,7 +6424,8 @@ public void testBug71396() throws Exception { * Case 6: PreparedStatement.executeQuery() and PreparedStatement.execute() with * Connection[useServerPrepStmts=true;maxRows=2]. */ - testConn = getConnectionWithProps("maxRows=2,useServerPrepStmts=true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + testConn = getConnectionWithProps(props); // safety check testBug71396PrepStatementMultiCheck(testConn, queries, new int[] { 2, 2, 2, 2, 2 }); @@ -6598,32 +6453,41 @@ public void testBug71396() throws Exception { * Case 7: Multiple combinations between maxRows connection prop, Statement.setMaxRows() and LIMIT clause. * Covers some cases not tested previously. */ - testBug71396MultiSettingsCheck("", -1, 1, 1); - testBug71396MultiSettingsCheck("", -1, 2, 2); - testBug71396MultiSettingsCheck("", 1, 1, 1); - testBug71396MultiSettingsCheck("", 1, 2, 1); - testBug71396MultiSettingsCheck("", 2, 1, 1); - testBug71396MultiSettingsCheck("", 2, 2, 2); - - testBug71396MultiSettingsCheck("maxRows=1", -1, 1, 1); - testBug71396MultiSettingsCheck("maxRows=1", -1, 2, 1); - testBug71396MultiSettingsCheck("maxRows=1", 1, 1, 1); - testBug71396MultiSettingsCheck("maxRows=1", 1, 2, 1); - testBug71396MultiSettingsCheck("maxRows=1", 2, 1, 1); - testBug71396MultiSettingsCheck("maxRows=1", 2, 2, 2); - - testBug71396MultiSettingsCheck("maxRows=2", -1, 1, 1); - testBug71396MultiSettingsCheck("maxRows=2", -1, 2, 2); - testBug71396MultiSettingsCheck("maxRows=2", 1, 1, 1); - testBug71396MultiSettingsCheck("maxRows=2", 1, 2, 1); - testBug71396MultiSettingsCheck("maxRows=2", 2, 1, 1); - testBug71396MultiSettingsCheck("maxRows=2", 2, 2, 2); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + testBug71396MultiSettingsCheck(props, -1, 1, 1); + testBug71396MultiSettingsCheck(props, -1, 2, 2); + testBug71396MultiSettingsCheck(props, 1, 1, 1); + testBug71396MultiSettingsCheck(props, 1, 2, 1); + testBug71396MultiSettingsCheck(props, 2, 1, 1); + testBug71396MultiSettingsCheck(props, 2, 2, 2); + + props.setProperty(PropertyKey.maxRows.getKeyName(), "1"); + testBug71396MultiSettingsCheck(props, -1, 1, 1); + testBug71396MultiSettingsCheck(props, -1, 2, 1); + testBug71396MultiSettingsCheck(props, 1, 1, 1); + testBug71396MultiSettingsCheck(props, 1, 2, 1); + testBug71396MultiSettingsCheck(props, 2, 1, 1); + testBug71396MultiSettingsCheck(props, 2, 2, 2); + + props.setProperty(PropertyKey.maxRows.getKeyName(), "2"); + testBug71396MultiSettingsCheck(props, -1, 1, 1); + testBug71396MultiSettingsCheck(props, -1, 2, 2); + testBug71396MultiSettingsCheck(props, 1, 1, 1); + testBug71396MultiSettingsCheck(props, 1, 2, 1); + testBug71396MultiSettingsCheck(props, 2, 1, 1); + testBug71396MultiSettingsCheck(props, 2, 2, 2); // Case 8: New session due to user change createUser("'testBug71396User'@'%'", "IDENTIFIED BY 'testBug71396User'"); this.stmt.execute("GRANT SELECT ON *.* TO 'testBug71396User'@'%'"); - testConn = getConnectionWithProps(""); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + testConn = getConnectionWithProps(props); testStmt = testBug71396StatementInit(testConn, 5); ((JdbcConnection) testConn).changeUser("testBug71396User", "testBug71396User"); @@ -6647,7 +6511,7 @@ public void testBug71396() throws Exception { testConn.close(); // Case 9: New session due to reconnection - testConn = getConnectionWithProps(""); + testConn = getConnectionWithProps(props); testStmt = testBug71396StatementInit(testConn, 5); ((JdbcConnection) testConn).createNewIO(true); // true or false argument is irrelevant for this test case @@ -6700,9 +6564,7 @@ private Statement testBug71396StatementInit(Connection testConn, int maxRows) th * @throws SQLException */ private void testBug71396StatementMultiCheck(Connection testConn, String[] queries, int[] expRowCount) throws SQLException { - if (queries.length != expRowCount.length) { - fail("Bad arguments!"); - } + assertTrue(queries.length == expRowCount.length, "Bad arguments!"); Statement testStmt = testConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); testBug71396StatementMultiCheck(testStmt, queries, expRowCount); testStmt.close(); @@ -6717,9 +6579,7 @@ private void testBug71396StatementMultiCheck(Connection testConn, String[] queri * @throws SQLException */ private void testBug71396StatementMultiCheck(Statement testStmt, String[] queries, int[] expRowCount) throws SQLException { - if (queries.length != expRowCount.length) { - fail("Bad arguments!"); - } + assertTrue(queries.length == expRowCount.length, "Bad arguments!"); for (int i = 0; i < queries.length; i++) { testBug71396StatementCheck(testStmt, queries[i], expRowCount[i]); } @@ -6790,9 +6650,7 @@ private void testBug71396PrepStatementClose(PreparedStatement[] testPStmt) throw * @throws SQLException */ private void testBug71396PrepStatementMultiCheck(Connection testConn, String[] queries, int[] expRowCount) throws SQLException { - if (queries.length != expRowCount.length) { - fail("Bad arguments!"); - } + assertTrue(queries.length == expRowCount.length, "Bad arguments!"); for (int i = 0; i < queries.length; i++) { testBug71396PrepStatementCheck(testConn, queries[i], expRowCount[i], -1); } @@ -6807,9 +6665,7 @@ private void testBug71396PrepStatementMultiCheck(Connection testConn, String[] q * @throws SQLException */ private void testBug71396PrepStatementMultiCheck(PreparedStatement[] testPStmt, String[] queries, int[] expRowCount) throws SQLException { - if (testPStmt.length != queries.length || testPStmt.length != expRowCount.length) { - fail("Bad arguments!"); - } + assertFalse(testPStmt.length != queries.length || testPStmt.length != expRowCount.length, "Bad arguments!"); for (int i = 0; i < queries.length; i++) { testBug71396PrepStatementCheck(testPStmt[i], queries[i], expRowCount[i]); } @@ -6868,7 +6724,7 @@ private void testBug71396PrepStatementCheck(PreparedStatement testPStmt, String * @param expRowCount * @throws SQLException */ - private void testBug71396MultiSettingsCheck(String connProps, int maxRows, int limitClause, int expRowCount) throws SQLException { + private void testBug71396MultiSettingsCheck(Properties connProps, int maxRows, int limitClause, int expRowCount) throws SQLException { Connection testConn = getConnectionWithProps(connProps); Statement testStmt = testConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); @@ -6941,6 +6797,8 @@ public void testBug66947() throws Exception { Connection con = null; try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.prepStmtCacheSize.getKeyName(), "2"); @@ -7011,6 +6869,8 @@ public void testBug66947() throws Exception { */ @Test public void testBug68916() throws Exception { + assumeTrue(supportsTimeZoneNames(this.stmt), "This test requies the server with populated time zone tables."); + // Prepare common test objects createProcedure("testBug68916_proc", "() BEGIN SELECT 1; SELECT 2; SELECT 3; END"); createTable("testBug68916_tbl", "(fld1 INT NOT NULL AUTO_INCREMENT, fld2 INT, PRIMARY KEY(fld1))"); @@ -8444,10 +8304,14 @@ public void testBug71672() throws SQLException { final int[] expectedGenKeysForBatchPStmtRW = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; // Test multiple connection props + Properties props = new Properties(); do { + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); switch (++testStep) { case 1: - testConn = getConnectionWithProps(""); + testConn = getConnectionWithProps(props); expectedUpdCount = expectedUpdCountDef; expectedGenKeys = expectedGenKeysForChkODKU; expectedGenKeysBatchStmt = expectedGenKeys; @@ -8455,7 +8319,8 @@ public void testBug71672() throws SQLException { expectedGenKeysBatchPStmt = expectedGenKeysForBatchPStmtChkODKU; break; case 2: - testConn = getConnectionWithProps("dontCheckOnDuplicateKeyUpdateInSQL=true"); + props.setProperty(PropertyKey.dontCheckOnDuplicateKeyUpdateInSQL.getKeyName(), "true"); + testConn = getConnectionWithProps(props); expectedUpdCount = expectedUpdCountDef; expectedGenKeys = expectedGenKeysForNoChkODKU; expectedGenKeysBatchStmt = expectedGenKeys; @@ -8463,7 +8328,8 @@ public void testBug71672() throws SQLException { expectedGenKeysBatchPStmt = expectedGenKeysForBatchPStmtNoChkODKU; break; case 3: - testConn = getConnectionWithProps("rewriteBatchedStatements=true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + testConn = getConnectionWithProps(props); expectedUpdCount = expectedUpdCountDef; expectedGenKeys = expectedGenKeysForChkODKU; expectedGenKeysBatchStmt = expectedGenKeysForBatchStmtRW; @@ -8472,7 +8338,9 @@ public void testBug71672() throws SQLException { break; case 4: // dontCheckOnDuplicateKeyUpdateInSQL=true is canceled by rewriteBatchedStatements=true - testConn = getConnectionWithProps("rewriteBatchedStatements=true,dontCheckOnDuplicateKeyUpdateInSQL=true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + props.setProperty(PropertyKey.dontCheckOnDuplicateKeyUpdateInSQL.getKeyName(), "true"); + testConn = getConnectionWithProps(props); expectedUpdCount = expectedUpdCountDef; expectedGenKeys = expectedGenKeysForChkODKU; expectedGenKeysBatchStmt = expectedGenKeysForBatchStmtRW; @@ -8567,13 +8435,19 @@ public void testBug71672() throws SQLException { allQueries += q + ";"; } do { + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); switch (++testStep) { case 5: - testConn = getConnectionWithProps("allowMultiQueries=true"); + props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); + testConn = getConnectionWithProps(props); expectedGenKeysMultiQueries = new int[] { 1 }; break; case 6: - testConn = getConnectionWithProps("allowMultiQueries=true,dontCheckOnDuplicateKeyUpdateInSQL=true"); + props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); + props.setProperty(PropertyKey.dontCheckOnDuplicateKeyUpdateInSQL.getKeyName(), "true"); + testConn = getConnectionWithProps(props); expectedGenKeysMultiQueries = new int[] { 1, 2, 3 }; lastTest = true; break; @@ -8824,11 +8698,7 @@ public void testBug73163() throws Exception { try { this.stmt = this.conn.prepareStatement("LOAD DATA INFILE ? INTO TABLE testBug73163"); } catch (SQLException e) { - if (e.getCause() instanceof IndexOutOfBoundsException) { - fail("IOOBE thrown in Java6+ while preparing a LOAD DATA statement with placeholders."); - } else { - throw e; - } + assertFalse(e.getCause() instanceof IndexOutOfBoundsException, "IOOBE thrown in Java6+ while preparing a LOAD DATA statement with placeholders."); } } @@ -8851,47 +8721,71 @@ public void testBug73163() throws Exception { public void testBug74998() throws Exception { int maxAllowedPacketAtServer = Integer.parseInt(((JdbcConnection) this.conn).getSession().getServerSession().getServerVariable("max_allowed_packet")); int maxAllowedPacketMinimumForTest = 32 * 1024 * 1024; - if (maxAllowedPacketAtServer < maxAllowedPacketMinimumForTest) { - fail("You need to increase max_allowed_packet to at least " + maxAllowedPacketMinimumForTest + " before running this test!"); + boolean changeMaxAllowedPacket = maxAllowedPacketAtServer < maxAllowedPacketMinimumForTest; + + if (!versionMeetsMinimum(5, 7)) { + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'innodb_log_file_size'"); + this.rs.next(); + long defaultInnodbLogFileSize = this.rs.getInt(2); + assumeFalse(defaultInnodbLogFileSize < maxAllowedPacketMinimumForTest * 10, + "This test requires innodb_log_file_size > " + (maxAllowedPacketMinimumForTest * 10)); } createTable("testBug74998", "(id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, data LONGBLOB)"); // (*2) - StringBuilder query = new StringBuilder("INSERT INTO testBug74998 (data) VALUES ('X')"); - for (int i = 0; i < 121; i++) { - query.append(",('X')"); - } - assertEquals(122, this.stmt.executeUpdate(query.toString())); // (*3) + Connection con1 = null; + try { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + maxAllowedPacketMinimumForTest); + } - int lengthOfRowForMultiPacket = maxAllowedPacketMinimumForTest - 15; // 32MB - 15Bytes causes an empty packet at the end of the multipacket sequence + StringBuilder query = new StringBuilder("INSERT INTO testBug74998 (data) VALUES ('X')"); + for (int i = 0; i < 121; i++) { + query.append(",('X')"); + } - this.stmt.executeUpdate("INSERT INTO testBug74998 (data) VALUES (REPEAT('Y', " + lengthOfRowForMultiPacket + "))"); // (*4) - this.stmt.executeUpdate("INSERT INTO testBug74998 (data) VALUES ('Z')"); // (*5) + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + con1 = getConnectionWithProps(props); + Statement st = con1.createStatement(); - try { - this.rs = this.stmt.executeQuery("SELECT id, data FROM testBug74998 ORDER BY id"); // (*1) - } catch (CJCommunicationsException | CommunicationsException e) { - if (e.getCause() instanceof IOException && "Packets received out of order".compareTo(e.getCause().getMessage()) == 0) { - fail("Failed to correctly fetch all data from communications layer due to wrong processing of muli-packet number."); - } else { - throw e; + assertEquals(122, st.executeUpdate(query.toString())); // (*3) + + int lengthOfRowForMultiPacket = maxAllowedPacketMinimumForTest - 15; // 32MB - 15Bytes causes an empty packet at the end of the multipacket sequence + + st.executeUpdate("INSERT INTO testBug74998 (data) VALUES (REPEAT('Y', " + lengthOfRowForMultiPacket + "))"); // (*4) + st.executeUpdate("INSERT INTO testBug74998 (data) VALUES ('Z')"); // (*5) + + try { + this.rs = st.executeQuery("SELECT id, data FROM testBug74998 ORDER BY id"); // (*1) + } catch (CJCommunicationsException | CommunicationsException e) { + assertFalse(e.getCause() instanceof IOException && "Packets received out of order".compareTo(e.getCause().getMessage()) == 0, + "Failed to correctly fetch all data from communications layer due to wrong processing of muli-packet number."); } - } - // safety check - for (int i = 1; i <= 122; i++) { + // safety check + for (int i = 1; i <= 122; i++) { + assertTrue(this.rs.next()); + assertEquals(i, this.rs.getInt(1)); + assertEquals("X", this.rs.getString(2)); + } + assertTrue(this.rs.next()); + assertEquals(123, this.rs.getInt(1)); + assertEquals("YYYYY", this.rs.getString(2).substring(0, 5)); + assertEquals("YYYYY", this.rs.getString(2).substring(lengthOfRowForMultiPacket - 5)); assertTrue(this.rs.next()); - assertEquals(i, this.rs.getInt(1)); - assertEquals("X", this.rs.getString(2)); + assertEquals(124, this.rs.getInt(1)); + assertEquals("Z", this.rs.getString(2)); + assertFalse(this.rs.next()); + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + maxAllowedPacketAtServer); + } + if (con1 != null) { + con1.close(); + } } - assertTrue(this.rs.next()); - assertEquals(123, this.rs.getInt(1)); - assertEquals("YYYYY", this.rs.getString(2).substring(0, 5)); - assertEquals("YYYYY", this.rs.getString(2).substring(lengthOfRowForMultiPacket - 5)); - assertTrue(this.rs.next()); - assertEquals(124, this.rs.getInt(1)); - assertEquals("Z", this.rs.getString(2)); - assertFalse(this.rs.next()); } /** @@ -8910,6 +8804,9 @@ public void testBug50348() throws Exception { final TimeZone defaultTZ = TimeZone.getDefault(); final Properties testConnProps = new Properties(); + testConnProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + testConnProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + testConnProps.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); testConnProps.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); testConnProps.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); testConnProps.setProperty(PropertyKey.preserveInstants.getKeyName(), "true"); @@ -9021,9 +8918,7 @@ private void checkPreparedStatementForTestBug50348(Connection testConn, Timestam */ @Test public void testBug77449() throws Exception { - if (!versionMeetsMinimum(5, 6, 4)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 4), "MySQL 5.6.4+ is required to run this test."); Timestamp originalTs = new Timestamp(TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd HH:mm:ss.SSS", null).parse("2014-12-31 23:59:59.999").getTime()); Timestamp roundedTs = new Timestamp(originalTs.getTime() + 1); @@ -9052,6 +8947,8 @@ public void testBug77449() throws Exception { String testCase = String.format("Case: %d [ useSSPS=%s | sendFracSecs=%s ]", tst, useServerPrepStmts, sendFractionalSeconds); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug77449QueryInterceptor.class.getName()); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useServerPrepStmts)); props.setProperty(PropertyKey.sendFractionalSeconds.getKeyName(), Boolean.toString(sendFractionalSeconds)); @@ -9253,9 +9150,7 @@ public T preProcess(Supplier sql, Query intercepte if (query != null && ((query.startsWith("INSERT") || query.startsWith("UPDATE") || query.startsWith("CALL")) && !query.contains("no_ts_trunk"))) { - if (this.sendFracSecs ^ query.contains(".999")) { - fail("Wrong TIMESTAMP trunctation in query [" + query + "]"); - } + assertFalse(this.sendFracSecs ^ query.contains(".999"), "Wrong TIMESTAMP trunctation in query [" + query + "]"); } } return super.preProcess(sql, interceptedQuery); @@ -9278,6 +9173,8 @@ public void testBug77681() throws Exception { createTable("testBug77681", "(id INT, txt VARCHAR(50), PRIMARY KEY (id))"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug77681QueryInterceptor.class.getName()); for (int tst = 0; tst < 4; tst++) { @@ -9347,7 +9244,6 @@ public static class TestBug77681QueryInterceptor extends BaseQueryInterceptor { @Override public QueryInterceptor init(MysqlConnection conn, Properties props, Log log) { - // TODO Auto-generated method stub super.init(conn, props, log); System.out.println("\nuseServerPrepStmts: " + props.getProperty(PropertyKey.useServerPrepStmts.getKeyName()) + " | rewriteBatchedStatements: " + props.getProperty(PropertyKey.rewriteBatchedStatements.getKeyName())); @@ -9365,9 +9261,7 @@ public T preProcess(Supplier sql, Query intercepte } if (query != null && query.indexOf("testBug77681") != -1) { System.out.println(this.execCounter + " --> " + query); - if (this.execCounter > this.expected.length) { - fail("Failed to rewrite statements"); - } + assertFalse(this.execCounter > this.expected.length, "Failed to rewrite statements"); assertEquals(this.expected[this.execCounter++], query.charAt(0), "Wrong statement at execution number " + this.execCounter); } return super.preProcess(sql, interceptedQuery); @@ -9390,6 +9284,8 @@ public void testBug21876798() throws Exception { boolean rewriteBatchedStatements = (tst & 0x2) != 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useServerPrepStmts)); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), Boolean.toString(rewriteBatchedStatements)); @@ -9454,10 +9350,13 @@ public void testBug21876798() throws Exception { public void testBug78961() throws Exception { createProcedure("testBug78961", "(IN c1 FLOAT, IN c2 FLOAT, OUT h FLOAT, INOUT t FLOAT) BEGIN SET h = SQRT(c1 * c1 + c2 * c2); SET t = t + h; END;"); - Connection highLevelConn = getLoadBalancedConnection(null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection highLevelConn = getLoadBalancedConnection(props); assertTrue(highLevelConn.getClass().getName().startsWith("com.sun.proxy") || highLevelConn.getClass().getName().startsWith("$Proxy")); - Connection lowLevelConn = getSourceReplicaReplicationConnection(null); + Connection lowLevelConn = getSourceReplicaReplicationConnection(props); // This simulates the behavior from Fabric connections that are causing the problem. ((ReplicationConnection) lowLevelConn).setProxy((JdbcConnection) highLevelConn); @@ -9481,7 +9380,12 @@ public void testBug78961() throws Exception { @Test public void testBug75956() throws Exception { createTable("bug75956", "(id int not null primary key auto_increment, dt1 datetime, dt2 datetime)"); - Connection sspsConn = getConnectionWithProps("useCursorFetch=true,useLegacyDatetimeCode=false"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + // props.setProperty("useLegacyDatetimeCode", "false"); + Connection sspsConn = getConnectionWithProps(props); this.pstmt = sspsConn.prepareStatement("insert into bug75956 (dt1, dt2) values (?, ?)"); this.pstmt.setTimestamp(1, new Timestamp(System.currentTimeMillis())); this.pstmt.setTimestamp(2, new Timestamp(System.currentTimeMillis())); @@ -9528,7 +9432,13 @@ public Void call() throws Exception { public void testBug23188498() throws Exception { createTable("testBug23188498", "(id INT)"); - JdbcConnection testConn = (JdbcConnection) getConnectionWithProps("useServerPrepStmts=true,useInformationSchema=true,profileSQL=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); + props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); + JdbcConnection testConn = (JdbcConnection) getConnectionWithProps(props); ExecutorService executor = Executors.newSingleThreadExecutor(); // Insert data: @@ -9582,6 +9492,13 @@ public ResultSet call() throws Exception { */ @Test public void testBug23201930() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + boolean useSSL = false; boolean useSPS = false; boolean useCursor = false; @@ -9599,12 +9516,8 @@ public void testBug23201930() throws Exception { + "f4 INT DEFAULT 1, f5 INT DEFAULT 1, fl LONGBLOB)"); final Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), Boolean.toString(useSSL)); + props.setProperty(PropertyKey.sslMode.getKeyName(), useSSL ? SslMode.REQUIRED.name() : SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); - if (useSSL) { - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); - } props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), Boolean.toString(useCursor)); if (useCursor) { @@ -9697,7 +9610,11 @@ public void testBug80615() throws Exception { try { // Check if it is possible to create a server prepared statement with the current max_prepared_stmt_count. - Connection checkConn = getConnectionWithProps("useServerPrepStmts=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection checkConn = getConnectionWithProps(props); PreparedStatement checkPstmt = checkConn.prepareStatement("SELECT 1"); assertTrue(checkPstmt instanceof ServerPreparedStatement, "Failed to create a server prepared statement possibly because there are too many active prepared statements on server already."); @@ -9735,9 +9652,7 @@ public void testBug80615() throws Exception { createTable("testBug80615", "(id INT)"); - final Properties props = new Properties(); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); - props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), Boolean.toString(useCache)); if (useCache) { props.setProperty(PropertyKey.prepStmtCacheSize.getKeyName(), String.valueOf(prepStmtCacheSize)); @@ -9874,6 +9789,8 @@ public void testBug81706() throws Exception { readOnly ? "Y" : "N"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); props.setProperty(PropertyKey.cacheResultSetMetadata.getKeyName(), Boolean.toString(cacheRsMd)); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestBug81706QueryInterceptor.class.getName()); @@ -9957,6 +9874,8 @@ public void testBug66430() throws Exception { final String testCase = String.format("Case: [useSPS: %s, cachePS: %s ]", useSPS ? "Y" : "N", cachePS ? "Y" : "N"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), Boolean.toString(cachePS)); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); @@ -10040,6 +9959,8 @@ public Void call() throws Exception { boolean useSPS = false; do { final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); final String testCase = String.format("Case [SPS: %s]", useSPS ? "Y" : "N"); @@ -10047,7 +9968,7 @@ public Void call() throws Exception { Connection testConn; // Test using a failover connection. - testConn = getUnreliableFailoverConnection(new String[] { "host1", "host2" }, null); + testConn = getUnreliableFailoverConnection(new String[] { "host1", "host2" }, props); final Statement testStmtFO = testConn.createStatement(); testStmtFO.setQueryTimeout(1); assertThrows(testCase, SQLException.class, "Statement cancelled due to timeout or client request", new Callable() { @@ -10067,7 +9988,7 @@ public Void call() throws Exception { testConn.close(); // Test using a load-balanced connection. - testConn = getUnreliableLoadBalancedConnection(new String[] { "host1", "host2" }, null); + testConn = getUnreliableLoadBalancedConnection(new String[] { "host1", "host2" }, props); final Statement testStmtLB = testConn.createStatement(); testStmtLB.setQueryTimeout(1); assertThrows(testCase, SQLException.class, "Statement cancelled due to timeout or client request", new Callable() { @@ -10087,7 +10008,7 @@ public Void call() throws Exception { testConn.close(); // Test using a replication connection. - testConn = getUnreliableReplicationConnection(new String[] { "host1", "host2" }, null); + testConn = getUnreliableReplicationConnection(new String[] { "host1", "host2" }, props); final Statement testStmtR = testConn.createStatement(); testStmtR.setQueryTimeout(1); assertThrows(testCase, SQLException.class, "Statement cancelled due to timeout or client request", new Callable() { @@ -10122,6 +10043,8 @@ public void testBug74932() throws Exception { String sql2 = "SELECT * FROM testBug74932 WHERE c2 = ?"; final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.prepStmtCacheSize.getKeyName(), "10"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); @@ -10195,7 +10118,10 @@ public void testBug78313() throws Exception { Connection testConn; // Plain connection. - testConn = getConnectionWithProps(""); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + testConn = getConnectionWithProps(props); assertFalse(testConn.getClass().getName().matches("^(?:com\\.sun\\.proxy\\.)?\\$Proxy\\d*")); assertTrue(testConn.equals(testConn)); this.stmt = testConn.createStatement(); @@ -10212,7 +10138,6 @@ public void testBug78313() throws Exception { testConn.close(); // Plain connection with proxied result sets. - final Properties props = new Properties(); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), ResultSetScannerInterceptor.class.getName()); props.setProperty(ResultSetScannerInterceptor.PNAME_resultSetScannerRegex, ".*"); testConn = getConnectionWithProps(props); @@ -10233,7 +10158,10 @@ public void testBug78313() throws Exception { testConn.close(); // Fail-over connection; all JDBC objects are proxied. - testConn = getFailoverConnection(); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + testConn = getFailoverConnection(props); assertTrue(testConn.getClass().getName().matches("^(?:com\\.sun\\.proxy\\.)?\\$Proxy\\d*")); assertTrue(testConn.equals(testConn)); this.stmt = testConn.createStatement(); @@ -10251,7 +10179,7 @@ public void testBug78313() throws Exception { testConn.close(); // Load-balanced connection; all JDBC objects are proxied. - testConn = getLoadBalancedConnection(); + testConn = getLoadBalancedConnection(props); assertTrue(testConn.getClass().getName().matches("^(?:com\\.sun\\.proxy\\.)?\\$Proxy\\d*")); assertTrue(testConn.equals(testConn)); this.stmt = testConn.createStatement(); @@ -10269,7 +10197,7 @@ public void testBug78313() throws Exception { testConn.close(); // Replication connection; all JDBC objects are proxied. - testConn = getSourceReplicaReplicationConnection(); + testConn = getSourceReplicaReplicationConnection(props); assertTrue(testConn.getClass().getName().matches("^(?:com\\.sun\\.proxy\\.)?\\$Proxy\\d*")); assertTrue(testConn.equals(testConn)); this.stmt = testConn.createStatement(); @@ -10289,6 +10217,8 @@ public void testBug78313() throws Exception { // XA Connection; unwrapped connections and statements are proxied. MysqlXADataSource xaDs = new MysqlXADataSource(); xaDs.setUrl(BaseTestCase.dbUrl); + xaDs.getEnumProperty(PropertyKey.sslMode).setValue(SslMode.DISABLED); + xaDs.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); XAConnection xaTestConn = xaDs.getXAConnection(); testConn = xaTestConn.getConnection(); Connection unwrappedTestConn = testConn.unwrap(JdbcConnection.class); @@ -10335,6 +10265,8 @@ public void testBug87429() throws Exception { boolean cachePS = false; do { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), Boolean.toString(cachePS)); props.setProperty(PropertyKey.prepStmtCacheSize.getKeyName(), "5"); @@ -10361,9 +10293,7 @@ public void testBug87429() throws Exception { try { this.pstmt.setPoolable(false); // De-caches the statement or no-op if not cached. } catch (SQLException e) { - if (cachedSPS) { - fail("Exception [" + e.getMessage() + "] not expected."); - } + assertFalse(cachedSPS, "Exception [" + e.getMessage() + "] not expected."); } this.pstmt.close(); // No-op. assertEquals(0, testConn.getActiveStatementCount()); @@ -10412,36 +10342,28 @@ public void testBug87429() throws Exception { try { pstmt4.setPoolable(false); // De-caches the statement or no-op if not cached. } catch (SQLException e) { - if (cachedSPS && j == 4) { - fail("Exception [" + e.getMessage() + "] not expected."); - } + assertFalse(cachedSPS && j == 4, "Exception [" + e.getMessage() + "] not expected."); } pstmt4.close(); // No-op. assertEquals(cachedSPS ? (j < 4 ? 2 : 1) : 0, testConn.getActiveStatementCount()); try { pstmt3.setPoolable(false); // De-caches the statement or no-op if not cached. } catch (SQLException e) { - if (cachedSPS && j == 3) { - fail("Exception [" + e.getMessage() + "] not expected."); - } + assertFalse(cachedSPS && j == 3, "Exception [" + e.getMessage() + "] not expected."); } pstmt3.close(); // No-op. assertEquals(cachedSPS ? (j < 3 ? 2 : 1) : 0, testConn.getActiveStatementCount()); try { pstmt2.setPoolable(false); // De-caches the statement or no-op if not cached. } catch (SQLException e) { - if (cachedSPS && j == 2) { - fail("Exception [" + e.getMessage() + "] not expected."); - } + assertFalse(cachedSPS && j == 2, "Exception [" + e.getMessage() + "] not expected."); } pstmt2.close(); // No-op. assertEquals(cachedSPS ? 1 : 0, testConn.getActiveStatementCount()); try { pstmt1.setPoolable(false); // De-caches the statement or no-op if not cached. } catch (SQLException e) { - if (cachedSPS) { - fail("Exception [" + e.getMessage() + "] not expected."); - } + assertFalse(cachedSPS, "Exception [" + e.getMessage() + "] not expected."); } pstmt1.close(); // No-op. assertEquals(0, testConn.getActiveStatementCount()); @@ -10464,6 +10386,8 @@ public void testBug26995710() throws Exception { boolean useSPS = false; do { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); props.setProperty(PropertyKey.autoGenerateTestcaseScript.getKeyName(), "true"); @@ -10620,7 +10544,7 @@ public void testBug26748909() throws Exception { PreparedStatement ps3 = null; Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); boolean useSPS = false; @@ -10681,15 +10605,18 @@ public void testBug26748909() throws Exception { */ @Test public void testBug87534() throws Exception { - if (versionMeetsMinimum(5, 7) && !versionMeetsMinimum(5, 7, 22)) { - return; - } + assumeFalse(versionMeetsMinimum(5, 7) && !versionMeetsMinimum(5, 7, 22), + "This test doesn't work with MySQL 5.7.0-5.7.21 because of server Bug#27422376."); System.out.println("running"); boolean useSPS = false; do { - Connection testConn = getConnectionWithProps(PropertyKey.useServerPrepStmts + "=" + Boolean.toString(useSPS)); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSPS); + Connection testConn = getConnectionWithProps(props); this.pstmt = testConn.prepareStatement("SELECT CAST(NOW() AS DATE) UNION SELECT CAST(NOW() AS DATE)"); this.rs = this.pstmt.executeQuery(); @@ -10740,6 +10667,8 @@ public void testBug84813() throws Exception { final String testCase = String.format("Case [rwBS: %s, useSPS: %s]", rwBS ? "Y" : "N", useSPS ? "Y" : "N"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), Boolean.toString(rwBS)); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); props.setProperty(PropertyKey.emulateUnsupportedPstmts.getKeyName(), "false"); @@ -10802,6 +10731,8 @@ public void testBug81063() throws Exception { final String testCase = String.format("Case [rwBS: %s, useSPS: %s]", rwBS ? "Y" : "N", useSPS ? "Y" : "N"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.emulateUnsupportedPstmts.getKeyName(), "true"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), Boolean.toString(rwBS)); @@ -10877,6 +10808,8 @@ public void testBug22931700() throws Exception { this.stmt.executeUpdate("INSERT INTO testBug22931700 values(100,false)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); Connection testConn = getConnectionWithProps(props); @@ -10902,7 +10835,7 @@ public void testBug22931700() throws Exception { @Test public void testBug27453692() throws Exception { Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); @@ -10937,6 +10870,8 @@ public void testBug96442() throws Exception { createTable("testBug96442", "(id INT UNSIGNED NOT NULL, rdate DATE NOT NULL, ts TIMESTAMP NOT NULL)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); boolean useSPS = false; do { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); @@ -10968,6 +10903,8 @@ public void testBug91112() throws Exception { TimeZone defaultTimeZone = TimeZone.getDefault(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.put(PropertyKey.connectionTimeZone, "UTC"); try { // setting a custom calendar time zone later than the client one to detect day switch @@ -11113,7 +11050,7 @@ public void testBug98536() throws Exception { createTable("testBug98536", "(d1 date, pd date, d2 date)"); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); boolean useServerPrepStmts = false; @@ -11167,6 +11104,8 @@ public void testBug98237() throws Exception { String[] trues = new String[] { "true", "y", "1", "-1", "1.0", "-1.0", "0.01", "-0.01" }; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); boolean useSPS = false; do { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); @@ -11223,6 +11162,8 @@ public void testBug99713() throws Exception { createTable("testBug99713", "(d1 DATE)"); Connection con = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); try { for (boolean useSSPS : new boolean[] { false, true }) { for (boolean cacheDefaultTimeZone : new boolean[] { true, false }) { @@ -11255,6 +11196,8 @@ public void testBug101242() throws Exception { Connection con = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); try { for (boolean useSSPS : new boolean[] { false, true }) { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); @@ -11286,6 +11229,8 @@ public void testBug101242() throws Exception { */ @Test public void testBug98695() throws Exception { + assumeTrue(supportsLoadLocalInfile(this.stmt), "This test requires the server started with --local-infile=ON"); + createTable("testBug98695", "(id bigint(20) NOT NULL AUTO_INCREMENT, ts timestamp NULL DEFAULT NULL, dt datetime DEFAULT NULL,date_input_str varchar(45) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB"); @@ -11306,6 +11251,8 @@ public void testBug98695() throws Exception { out.close(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); @@ -11349,43 +11296,6 @@ public void testBug98695() throws Exception { } - /** - * Tests fix for Bug#101413 (32099505), JAVA.TIME.LOCALDATETIME CANNOT BE CAST TO JAVA.SQL.TIMESTAMP. - * - * @throws Exception - */ - @Test - public void testBug101413() throws Exception { - createTable("testBug101413", "(createtime1 TIMESTAMP, createtime2 DATETIME)"); - - Properties props = new Properties(); - for (boolean forceConnectionTimeZoneToSession : new boolean[] { false, true }) { - for (boolean preserveInstants : new boolean[] { false, true }) { - for (boolean useSSPS : new boolean[] { false, true }) { - props.setProperty(PropertyKey.forceConnectionTimeZoneToSession.getKeyName(), "" + forceConnectionTimeZoneToSession); - props.setProperty(PropertyKey.preserveInstants.getKeyName(), "" + preserveInstants); - props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); - props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); - - Connection con = getConnectionWithProps(props); - PreparedStatement ps = con.prepareStatement("insert into testBug101413(createtime1, createtime2) values(?, ?)"); - - con.setAutoCommit(false); - for (int i = 1; i <= 20000; i++) { - ps.setObject(1, LocalDateTime.now()); - ps.setObject(2, LocalDateTime.now()); - ps.addBatch(); - if (i % 500 == 0) { - ps.executeBatch(); - ps.clearBatch(); - } - } - con.commit(); - } - } - } - } - /** * Tests fix for Bug#101558 (32141210), NULLPOINTEREXCEPTION WHEN EXECUTING INVALID QUERY WITH USEUSAGEADVISOR ENABLED. * @@ -11393,8 +11303,9 @@ public void testBug101413() throws Exception { */ @Test public void testBug101558() throws Exception { - Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); @@ -11411,4 +11322,671 @@ public void testBug101558() throws Exception { testConn.close(); } + + /** + * Test fix for Bug#20453773, EXECUTEUPDATE() FAILS FOR SERVER SIDE STMT WHEN LOGSLOWQUERIES=TRUE. + * + * @throws Exception + */ + @Test + public void testBug20453773() throws Exception { + createTable("testBug20453773", "(c1 longblob)"); + this.stmt.execute("insert into testBug20453773 values('xyz')"); + + Connection con = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.useNanosForElapsedTime.getKeyName(), "true"); + props.setProperty(PropertyKey.logSlowQueries.getKeyName(), "true"); + con = getConnectionWithProps(props); + + this.rs = this.stmt.executeQuery("select * from testBug20453773"); + this.rs.next(); + Clob clob1 = this.rs.getClob(1); + this.rs.close(); + + PreparedStatement ps = con.prepareStatement("insert into testBug20453773 (c1) values(?) ", ResultSet.TYPE_SCROLL_SENSITIVE, + ResultSet.CONCUR_READ_ONLY); + ps.setClob(1, clob1); + ps.executeUpdate(); + ps.close(); + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Test fix for Bug#21132376, PSTMT->CLOSE() AFTER PSTMT->EXECUTEBATCH() CALL RETURNS ERROR. + * + * @throws Exception + */ + @Test + public void testBug21132376() throws Exception { + createTable("testBug21132376", "(c1 longblob,c2 longblob)"); + + this.rs = this.stmt.executeQuery("show variables like 'max_allowed_packet'"); + this.rs.next(); + int initialPLen = this.rs.getInt(2); + + try { + this.stmt.executeUpdate("set global max_allowed_packet=1024*1024"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection con = getConnectionWithProps(props); + Statement st = con.createStatement(); + con = getConnectionWithProps(props); + st = con.createStatement(); + this.rs = st.executeQuery("show variables like 'max_allowed_packet'"); + this.rs.next(); + int pLen = this.rs.getInt(2); + this.rs.close(); + assertEquals(1024 * 1024, pLen); + byte[] blobData = new byte[pLen + 1]; + Arrays.fill(blobData, (byte) 'S'); + + PreparedStatement ps = con.prepareStatement("insert into testBug21132376 values(?,?) ", ResultSet.TYPE_SCROLL_SENSITIVE, + ResultSet.CONCUR_UPDATABLE); + ps.setInt(1, 1); + ps.setBytes(2, blobData); + ps.addBatch(); + ps.setInt(1, 2); + ps.setString(2, "2"); + ps.addBatch(); + ps.setInt(1, 3); + ps.setBytes(2, blobData); + ps.addBatch(); + assertThrows(SQLException.class, "Packet for query is too large \\(1.048.59\\d > 1.048.576\\).*", () -> ps.executeBatch()); + ps.close(); // was failing + + this.rs = st.executeQuery("select c1 from testBug21132376 "); + assertTrue(this.rs.next()); + assertEquals("2", this.rs.getString(1)); + assertFalse(this.rs.next()); + + } finally { + this.stmt.executeUpdate("set global max_allowed_packet=" + initialPLen); + } + } + + /** + * Test fix for Bug#20391550, SETSTRING() CALL AFTER CONNECTION CLOSE RESULTS IN NULLPOINTEREXCEPTION. + * + * @throws Exception + */ + @Test + public void testBug20391550() throws Exception { + createTable("testBug20391550", "(c1 int,c2 varchar(100))"); + this.stmt.execute("insert into testBug20391550 values(100,'strval')"); + + Connection con = null; + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); + props.setProperty(PropertyKey.enableQueryTimeouts.getKeyName(), "true"); + props.setProperty(PropertyKey.queryTimeoutKillsConnection.getKeyName(), "true"); + con = getConnectionWithProps(props); + + Statement st = con.createStatement(); + st.setQueryTimeout(2); + + PreparedStatement ps = con.prepareStatement("update testBug20391550 set c2=? where c1=?"); + assertThrows(SQLException.class, "Statement cancelled due to timeout or client request", () -> st.executeQuery("select sleep(3)")); + + assertThrows(SQLException.class, "No operations allowed after statement closed.", () -> { + ps.setInt(2, 100); + return null; + }); + assertThrows(SQLException.class, "No operations allowed after statement closed.", () -> { + ps.setString(1, "NewData"); + return null; + }); + assertThrows(SQLException.class, "No operations allowed after statement closed.", () -> { + ps.executeUpdate(); + return null; + }); + + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Test fix for Bug#103303 (32766143), JAVA.LANG.CLASSCASTEXCEPTION WHEN INSERTING BLOB WITH SERVER PREPARED STATEMENT. + * + * @throws Exception + */ + @Test + public void testBug103303() throws Exception { + createTable("testBug103303", "(id INT AUTO_INCREMENT PRIMARY KEY, blob1 MEDIUMBLOB)"); + + Connection con = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + for (boolean rewriteBS : new boolean[] { true, false }) { + for (boolean useSSPS : new boolean[] { true, false }) { + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "" + rewriteBS); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); + + try { + this.stmt.executeUpdate("truncate table testBug103303"); + + con = getConnectionWithProps(props); + + PreparedStatement statement = con.prepareStatement("INSERT INTO testBug103303(blob1) VALUES(?)", PreparedStatement.RETURN_GENERATED_KEYS); + statement.setBlob(1, new SerialBlob("test1".getBytes())); + statement.addBatch(); + statement.setBlob(1, new SerialBlob("test2".getBytes())); + statement.addBatch(); + statement.executeBatch(); + + this.rs = this.stmt.executeQuery("select blob1 from testBug103303 order by id"); + assertTrue(this.rs.next()); + assertEquals("test1", this.rs.getString(1)); + assertTrue(this.rs.next()); + assertEquals("test2", this.rs.getString(1)); + assertFalse(this.rs.next()); + + } finally { + if (con != null) { + con.close(); + } + } + } + } + } + + /** + * Tests fix for Bug#103796 (32922715), CONNECTOR/J 8 STMT SETQUERYTIMEOUT CAN NOT WORK. + * + * @throws Exception + */ + @Test + public void testBug103796() throws Exception { + Connection con = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + + try { + con = getConnectionWithProps(props); + Statement timeoutStmt = con.createStatement(); + timeoutStmt.setFetchSize(10); + timeoutStmt.setQueryTimeout(2); + + long begin = System.currentTimeMillis(); + + try { + timeoutStmt.execute("SELECT SLEEP(5)"); + fail("Query didn't time out"); + } catch (MySQLTimeoutException timeoutEx) { + long duration = System.currentTimeMillis() - begin; + assertTrue(duration > 1000 && duration < 3000); + } + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Tests fix for Bug#103878 (32954449), CONNECTOR/J 8 : QUERY WITH 'SHOW XXX' WILL GET EXCEPTION WHEN USE CURSOR. + * + * @throws Exception + */ + @Test + public void testBug103878() throws Exception { + createTable("testBug103878", "(`fid` bigint(20) DEFAULT NULL, `ftext` longtext COLLATE utf8mb4_unicode_ci)" + + " ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"); + createView("testBug103878_view", "AS SELECT * FROM testBug103878 WHERE fid > 1"); + + Connection con = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); + props.setProperty(PropertyKey.defaultFetchSize.getKeyName(), "100"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + + try { + con = getConnectionWithProps(props); + PreparedStatement ps = con.prepareStatement("SHOW CREATE TABLE testBug103878"); + this.rs = ps.executeQuery(); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(2).startsWith("CREATE TABLE")); + System.out.println(this.rs.getString(2)); + ps.close(); + + ps = con.prepareStatement("SHOW CREATE VIEW testBug103878_view"); + this.rs = ps.executeQuery(); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(2).startsWith("CREATE")); + assertTrue(this.rs.getString(2).contains("testBug103878_view")); + System.out.println(this.rs.getString(2)); + ps.close(); + + ps = con.prepareStatement("SHOW PROCESSLIST"); + this.rs = ps.executeQuery(); + assertTrue(this.rs.next()); + ps.close(); + + if (versionMeetsMinimum(5, 7)) { + createUser("'testBug103878User'@'%'", "IDENTIFIED BY 'abc'"); + + ps = con.prepareStatement("SHOW CREATE USER testBug103878User"); + this.rs = ps.executeQuery(); + assertTrue(this.rs.next()); + assertTrue(this.rs.getString(1).startsWith("CREATE USER")); + System.out.println(this.rs.getString(1)); + ps.close(); + } + } finally { + if (con != null) { + con.close(); + } + } + } + + /** + * Tests fix for Bug#23204652, CURSOR POSITIONING API'S DOESNOT CHECK THE VALIDITY OF RESULTSET. + * + * @throws Exception + * + * @see StatementsTest#testResultSetProducingQueries() + */ + @Test + public void testBug23204652() throws Exception { + assertThrows(SQLException.class, "Statement\\.executeQuery\\(\\) cannot issue statements that do not produce result sets\\.", () -> { + this.stmt.executeQuery("DO 1 + 2"); + return null; + }); + } + + /** + * Tests fix for Bug#71929 (18346501), Prefixing query with double comments cancels query DML validation. + * + * @throws Exception + */ + @Test + public void testBug71929() throws Exception { + String[] queries = new String[] { "CREATE TABLE testBug71929 (id INT)", "/* comments */CREATE TABLE testBug71929 (id INT)", + "/* comments *//* more comments */CREATE TABLE testBug71929 (id INT)" }; + for (String query : queries) { + assertThrows(SQLException.class, "Statement\\.executeQuery\\(\\) cannot issue statements that do not produce result sets\\.", () -> { + this.stmt.executeQuery(query); + return null; + }); + } + + queries = new String[] { "SELECT 1", "/* comments */SELECT 1", "/* comments *//* more comments */SELECT 1" }; + for (String query : queries) { + assertThrows(SQLException.class, + "Statement\\.executeUpdate\\(\\) or Statement\\.executeLargeUpdate\\(\\) cannot issue statements that produce result sets\\.", () -> { + this.stmt.executeUpdate(query); + return null; + }); + } + } + + /** + * Test fix for Bug#103612 (32902019), Incorrectly identified WITH...SELECT as unsafe for read-only connections. + * + * @throws Exception + * + * @see StatementsTest#testReadOnlySafeStatements() + */ + @Test + public void testBug103612() throws Exception { + assumeTrue(versionMeetsMinimum(8, 0, 1), "MySQL 8.0.1+ is required to run this test."); + + createTable("testBug103612", "(id INT)"); + String query = "WITH cte AS (SELECT * FROM testBug103612) SELECT * FROM cte"; + + boolean useSPS = false; + boolean readOnly = false; + do { + final String testCase = String.format("Case [SPS: %s, ReadOnly: %s]", useSPS ? "Y" : "N", readOnly ? "Y" : "N"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); + Connection testConn = getConnectionWithProps(props); + testConn.setReadOnly(readOnly); + + try (Statement testStmt = testConn.createStatement()) { + assertTrue(testStmt.execute(query), testCase); + } + try (PreparedStatement testPstmt = testConn.prepareStatement(query)) { + assertTrue(testPstmt.execute(), testCase); + } + + testConn.close(); + } while ((useSPS = !useSPS) || (readOnly = !readOnly)); + } + + /** + * Tests fix for Bug#101389 (32089018), GETWARNINGS SHOULD CHECK WARNING COUNT BEFORE SENDING SHOW. + * + * @throws Exception + */ + @Test + public void testBug101389() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug101389QueryInterceptor.class.getName()); + + boolean useSPS = false; + do { + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); + + Bug101389QueryInterceptor.enabled = false; // some warnings are expected here when running against old server versions + java.sql.Connection testConn = getConnectionWithProps(props); + + Bug101389QueryInterceptor.enabled = true; + Statement st = testConn.createStatement(); + st.executeQuery("SELECT 1 FROM dual;"); + st.getWarnings(); + + PreparedStatement ps = testConn.prepareStatement("SELECT 1 FROM dual;"); + ps.execute(); + ps.getWarnings(); + + testConn.close(); + } while ((useSPS = !useSPS)); + + } + + public static class Bug101389QueryInterceptor extends BaseQueryInterceptor { + public static boolean enabled = false; + + @Override + public T preProcess(Supplier sql, Query interceptedQuery) { + if (enabled) { + assertFalse(sql.get().contains("SHOW WARNINGS"), "Unexpected [SHOW WARNINGS] was issued"); + } + return super.preProcess(sql, interceptedQuery); + } + + @Override + public M preProcess(M queryPacket) { + if (enabled) { + String sql = StringUtils.toString(queryPacket.getByteBuffer(), 1, (queryPacket.getPosition() - 1)); + assertFalse(sql.contains("SHOW WARNINGS"), "Unexpected [SHOW WARNINGS] was issued"); + } + return super.preProcess(queryPacket); + } + } + + @Test + public void testBlobWithSJIS() throws Exception { + createTable("testBlobWithSJIS", "(a SERIAL, b BLOB)"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + String sqlMode = getMysqlVariable("sql_mode"); + sqlMode = removeSqlMode("ANSI_QUOTES", sqlMode); + sqlMode = removeSqlMode("NO_BACKSLASH_ESCAPES", sqlMode); + if (sqlMode.length() > 0) { + sqlMode += ","; + } + + byte[] data1 = new byte[20]; + data1[0] = -21; + data1[1] = '\''; + data1[2] = '"'; + data1[3] = '\b'; + data1[4] = '\n'; + data1[5] = '\r'; + data1[6] = '\t'; + data1[7] = 26; // \Z ASCII 26 (Control+Z) + data1[8] = '\\'; + data1[9] = '%'; // \% A % character; see note following the table + data1[10] = '_'; // \_ A _ character; see note following the table + data1[11] = 39; // \' + + for (boolean useSSPS : new boolean[] { false, true }) { + for (String mode : new String[] { "", "ANSI_QUOTES", "NO_BACKSLASH_ESCAPES", "ANSI_QUOTES,NO_BACKSLASH_ESCAPES" }) { + for (String enc : new String[] { "SJIS", "UTF-8" }) { + props.setProperty(PropertyKey.characterEncoding.getKeyName(), enc); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); + props.setProperty(PropertyKey.sessionVariables.getKeyName(), "sql_mode='" + sqlMode + mode + "'"); + + Connection con = getConnectionWithProps(props); + Statement st = con.createStatement(); + + st.executeUpdate("truncate table testBlobWithSJIS"); + + PreparedStatement ps1 = con.prepareStatement("INSERT INTO testBlobWithSJIS (b) VALUES(?)"); + Blob blob = con.createBlob(); + blob.setBytes(1, data1); + ps1.setBlob(1, blob); + ps1.executeUpdate(); + + this.rs = st.executeQuery("SELECT b FROM testBlobWithSJIS"); + assertTrue(this.rs.next()); + byte[] data2 = this.rs.getBytes(1); + assertTrue(Arrays.equals(data1, data2)); + assertFalse(this.rs.next()); + + con.close(); + } + } + } + } + + /** + * Test fix for Bug#105211 (33468860), class java.time.LocalDate cannot be cast to class java.sql.Date. + * + * @throws Exception + */ + @Test + public void testBug105211() throws Exception { + createTable("testBug105211", "(dt date)"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + + boolean useSPS = false; + do { + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); + + Connection con = getConnectionWithProps(props); + this.pstmt = con.prepareStatement("insert into testBug105211(dt) values(?)"); + con.setAutoCommit(false); + + for (int i = 0; i <= 1000; i++) { + this.pstmt.setObject(1, LocalDate.now()); + this.pstmt.addBatch(); + if (i % 100 == 0) { + this.pstmt.executeBatch(); + this.pstmt.clearBatch(); + } + } + con.commit(); + + con.close(); + } while (useSPS = !useSPS); + } + + /** + * Test fix for Bug#84365 (33425867), INSERT..VALUE with VALUES function lead to a StringIndexOutOfBoundsException. + * + * @throws Exception + */ + @Test + public void testBug84365() throws Exception { + createTable("testBug84365", "(id int(11) NOT NULL, name varchar(25) DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=latin1"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + + for (boolean useSSPS : new boolean[] { false, true }) { + + this.stmt.executeUpdate("truncate table testBug84365"); + + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "" + useSSPS); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement st = testConn.prepareStatement("insert into testBug84365(id, name) VALUES(?,?) on duplicate key update id = values(id) + 1"); + st.setInt(1, 1); + st.setString(2, "Name1"); + st.execute(); + st.close(); + + st = testConn.prepareStatement("insert into testBug84365(id, name) VALUE(?,?) on duplicate key update id = values(id) + 1"); + st.setInt(1, 2); + st.setString(2, "Name2"); + st.execute(); + st.close(); + + st = testConn.prepareStatement("insert into testBug84365 set id = 2 on duplicate key update id = values(id) + 1"); + st.execute(); + + this.rs = testConn.createStatement().executeQuery("select * from testBug84365 order by id"); + assertTrue(this.rs.next()); + assertEquals(1, this.rs.getInt("id")); + assertEquals("Name1", this.rs.getString("name")); + assertTrue(this.rs.next()); + assertEquals(3, this.rs.getInt("id")); + assertEquals("Name2", this.rs.getString("name")); + assertFalse(this.rs.next()); + } + } + + /** + * Test fix for Bug#85223 (25656020), MYSQLSQLXML SETSTRING CRASH. + * + * @throws Exception + */ + @Test + public void testBug85223() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + String elementString = "MFNameLName"; + Connection con = getConnectionWithProps(props); + PreparedStatement pst = con.prepareStatement("SELECT ExtractValue(?,?) as val1"); + SQLXML xml = con.createSQLXML(); + xml.setString(elementString); + pst.setSQLXML(1, xml); + pst.setString(2, "/Person/LastName"); + pst.execute(); + this.rs = pst.getResultSet(); + this.rs.next(); + String value = this.rs.getString(1); + assertEquals("LName", value); + } + + /** + * Test fix for Bug#96900 (30355150), STATEMENT.CANCEL()CREATE A DATABASE CONNECTION BUT DOES NOT CLOSE THE CONNECTION. + * + * @throws Exception + */ + @Test + public void testBug96900() throws Exception { + assumeTrue(versionMeetsMinimum(5, 7), "MySQL 5.7+ is required to run this test."); + + Supplier sessionCount = () -> { + try { + this.stmt.execute("FLUSH STATUS"); + this.rs = this.stmt.executeQuery("SHOW STATUS LIKE 'threads_connected'"); + this.rs.next(); + return this.rs.getInt(2); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + }; + + int initialSessionCount = sessionCount.get(); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + Connection testConn = getConnectionWithProps(props); + Statement testStmt = testConn.createStatement(); + + new Thread(() -> { + try { + testStmt.executeQuery("SELECT SLEEP(600)"); + } catch (Throwable e) { + e.printStackTrace(); + } + }).start(); + + // The difference between the `initialSessionCount` and the current session count would be greater than one if connections external to this test are + // created in between. Chances for this to happen in a controlled or development environment are very low and can be neglected. + assertEquals(1, sessionCount.get() - initialSessionCount); + + testStmt.cancel(); + assertEquals(1, sessionCount.get() - initialSessionCount); + + testConn.close(); + assertEquals(0, sessionCount.get() - initialSessionCount); + } + + /** + * Tests fix for Bug#99260 (31189960), statement.setQueryTimeout,creates a database connection and does not close. + * + * @throws Exception + */ + @Test + public void testBug99260() throws Exception { + assumeTrue(versionMeetsMinimum(5, 7), "MySQL 5.7+ is required to run this test."); + + Supplier sessionCount = () -> { + try { + this.stmt.execute("FLUSH STATUS"); + this.rs = this.stmt.executeQuery("SHOW STATUS LIKE 'threads_connected'"); + this.rs.next(); + return this.rs.getInt(2); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + }; + + int initialSessionCount = sessionCount.get(); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + Connection testConn = getConnectionWithProps(props); + Statement testStmt = testConn.createStatement(); + + testStmt.setQueryTimeout(1); + for (int i = 0; i < 5; i++) { + assertThrows(MySQLTimeoutException.class, "Statement cancelled due to timeout or client request", () -> { + testStmt.executeQuery("SELECT SLEEP(30)"); + return null; + }); + // The difference between the `initialSessionCount` and the current session count would be greater than one if connections external to this test are + // created in between. Chances for this to happen in a controlled or development environment are very low and can be neglected. + assertEquals(1, sessionCount.get() - initialSessionCount); + } + + testConn.close(); + } } diff --git a/src/test/java/testsuite/regression/StressRegressionTest.java b/src/test/java/testsuite/regression/StressRegressionTest.java index d9e0998f3..520c5aa37 100644 --- a/src/test/java/testsuite/regression/StressRegressionTest.java +++ b/src/test/java/testsuite/regression/StressRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -29,6 +29,7 @@ package testsuite.regression; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -48,8 +49,12 @@ import java.util.List; import java.util.Properties; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; +import com.mysql.cj.conf.PropertyKey; + import testsuite.BaseTestCase; /** @@ -59,63 +64,62 @@ public class StressRegressionTest extends BaseTestCase { private int numThreadsStarted; @Test + @Disabled("It's a very long test") public synchronized void testContention() throws Exception { - if (!this.DISABLED_testContention) { - System.out.println("Calculating baseline elapsed time..."); + System.out.println("Calculating baseline elapsed time..."); - long start = System.currentTimeMillis(); + long start = System.currentTimeMillis(); - contentiousWork(this.conn, this.stmt, 0); + contentiousWork(this.conn, this.stmt, 0); - long singleThreadElapsedTimeMillis = System.currentTimeMillis() - start; + long singleThreadElapsedTimeMillis = System.currentTimeMillis() - start; - System.out.println("Single threaded execution took " + singleThreadElapsedTimeMillis + " ms."); + System.out.println("Single threaded execution took " + singleThreadElapsedTimeMillis + " ms."); - int numThreadsToStart = 95; + int numThreadsToStart = 95; - System.out.println("\nStarting " + numThreadsToStart + " threads."); + System.out.println("\nStarting " + numThreadsToStart + " threads."); - this.numThreadsStarted = numThreadsToStart; + this.numThreadsStarted = numThreadsToStart; - ContentionThread[] threads = new ContentionThread[this.numThreadsStarted]; + ContentionThread[] threads = new ContentionThread[this.numThreadsStarted]; - for (int i = 0; i < numThreadsToStart; i++) { - threads[i] = new ContentionThread(i); - threads[i].start(); - } + for (int i = 0; i < numThreadsToStart; i++) { + threads[i] = new ContentionThread(i); + threads[i].start(); + } - for (;;) { - try { - wait(); + for (;;) { + try { + wait(); - if (this.numThreadsStarted == 0) { - break; - } - } catch (InterruptedException ie) { - // ignore + if (this.numThreadsStarted == 0) { + break; } + } catch (InterruptedException ie) { + // ignore } + } - // Collect statistics... - System.out.println("Done!"); + // Collect statistics... + System.out.println("Done!"); - double avgElapsedTimeMillis = 0; + double avgElapsedTimeMillis = 0; - List elapsedTimes = new ArrayList<>(); + List elapsedTimes = new ArrayList<>(); - for (int i = 0; i < numThreadsToStart; i++) { - elapsedTimes.add(new Long(threads[i].elapsedTimeMillis)); + for (int i = 0; i < numThreadsToStart; i++) { + elapsedTimes.add(new Long(threads[i].elapsedTimeMillis)); - avgElapsedTimeMillis += ((double) threads[i].elapsedTimeMillis / numThreadsToStart); - } + avgElapsedTimeMillis += ((double) threads[i].elapsedTimeMillis / numThreadsToStart); + } - Collections.sort(elapsedTimes); + Collections.sort(elapsedTimes); - System.out.println("Average elapsed time per-thread was " + avgElapsedTimeMillis + " ms."); - System.out.println("Median elapsed time per-thread was " + elapsedTimes.get(elapsedTimes.size() / 2) + " ms."); - System.out.println("Minimum elapsed time per-thread was " + elapsedTimes.get(0) + " ms."); - System.out.println("Maximum elapsed time per-thread was " + elapsedTimes.get(elapsedTimes.size() - 1) + " ms."); - } + System.out.println("Average elapsed time per-thread was " + avgElapsedTimeMillis + " ms."); + System.out.println("Median elapsed time per-thread was " + elapsedTimes.get(elapsedTimes.size() / 2) + " ms."); + System.out.println("Minimum elapsed time per-thread was " + elapsedTimes.get(0) + " ms."); + System.out.println("Maximum elapsed time per-thread was " + elapsedTimes.get(elapsedTimes.size() - 1) + " ms."); } @Test @@ -312,7 +316,10 @@ public void testBug67760() throws Exception { /* * Use a brand new Connection not shared by anyone else, otherwise it may block later on test teardown. */ - final Connection testConn = getConnectionWithProps(""); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + final Connection testConn = getConnectionWithProps(props); /* * Thread to execute set[Timestamp|Date|Time]() methods in an instance of a PreparedStatement constructed from a shared Connection. @@ -397,9 +404,8 @@ public void run() { Thread.sleep(recheckWaitTimeUnit); delta0IterationsCountdown = SharedInfoForTestBug67760.iterationsChanged() ? delta0IterationsCountdownSize : delta0IterationsCountdown - 1; - if (SharedInfoForTestBug67760.running && (!job1.isAlive() || !job2.isAlive())) { - fail("Something as failed. At least one of the threads has died."); - } + assertFalse(SharedInfoForTestBug67760.running && (!job1.isAlive() || !job2.isAlive()), + "Something has failed. At least one of the threads has died."); if (delta0IterationsCountdown == 0 || !SharedInfoForTestBug67760.running) { if (!SharedInfoForTestBug67760.running && (job1.isAlive() || job2.isAlive())) { diff --git a/src/test/java/testsuite/regression/StringRegressionTest.java b/src/test/java/testsuite/regression/StringRegressionTest.java index bc4704255..87226c039 100644 --- a/src/test/java/testsuite/regression/StringRegressionTest.java +++ b/src/test/java/testsuite/regression/StringRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -31,22 +31,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.sql.Clob; import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.Statement; import java.util.Properties; import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.util.Base64Decoder; +import com.mysql.cj.util.SearchMode; import com.mysql.cj.util.StringUtils; import testsuite.BaseTestCase; @@ -55,189 +53,6 @@ * Tests for regressions of bugs in String handling in the driver. */ public class StringRegressionTest extends BaseTestCase { - /** - * Tests character conversion bug. - * - * @throws Exception - */ - @Test - public void testAsciiCharConversion() throws Exception { - byte[] buf = new byte[10]; - buf[0] = (byte) '?'; - buf[1] = (byte) 'S'; - buf[2] = (byte) 't'; - buf[3] = (byte) 'a'; - buf[4] = (byte) 't'; - buf[5] = (byte) 'e'; - buf[6] = (byte) '-'; - buf[7] = (byte) 'b'; - buf[8] = (byte) 'o'; - buf[9] = (byte) 't'; - - String testString = "?State-bot"; - String convertedString = StringUtils.toAsciiString(buf); - - for (int i = 0; i < convertedString.length(); i++) { - System.out.println((byte) convertedString.charAt(i)); - } - - assertTrue(testString.equals(convertedString), "Converted string != test string"); - } - - /** - * Tests for regression of encoding forced by user, reported by Jive Software - * - * @throws Exception - */ - @Test - public void testEncodingRegression() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - DriverManager.getConnection(dbUrl, props).close(); - } - - /** - * Tests fix for BUG#879 - * - * @throws Exception - */ - @Test - public void testEscapeSJISDoubleEscapeBug() throws Exception { - String testString = "'It\\'s a boy!'"; - - //byte[] testStringAsBytes = testString.getBytes("SJIS"); - - byte[] origByteStream = new byte[] { (byte) 0x95, (byte) 0x5c, (byte) 0x8e, (byte) 0x96, (byte) 0x5c, (byte) 0x62, (byte) 0x5c }; - - //String origString = "\u955c\u8e96\u5c62\\"; - - origByteStream = new byte[] { (byte) 0x8d, (byte) 0xb2, (byte) 0x93, (byte) 0x91, (byte) 0x81, (byte) 0x40, (byte) 0x8c, (byte) 0x5c }; - - testString = new String(origByteStream, "SJIS"); - - Properties connProps = new Properties(); - connProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "sjis"); - - Connection sjisConn = getConnectionWithProps(connProps); - Statement sjisStmt = sjisConn.createStatement(); - - try { - sjisStmt.executeUpdate("DROP TABLE IF EXISTS doubleEscapeSJISTest"); - sjisStmt.executeUpdate("CREATE TABLE doubleEscapeSJISTest (field1 BLOB)"); - - PreparedStatement sjisPStmt = sjisConn.prepareStatement("INSERT INTO doubleEscapeSJISTest VALUES (?)"); - sjisPStmt.setString(1, testString); - sjisPStmt.executeUpdate(); - - this.rs = sjisStmt.executeQuery("SELECT * FROM doubleEscapeSJISTest"); - - this.rs.next(); - - String retrString = this.rs.getString(1); - - System.out.println(retrString.equals(testString)); - } finally { - sjisStmt.executeUpdate("DROP TABLE IF EXISTS doubleEscapeSJISTest"); - } - } - - @Test - public void testGreekUtf8411() throws Exception { - Properties newProps = new Properties(); - newProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - - Connection utf8Conn = this.getConnectionWithProps(newProps); - - Statement utfStmt = utf8Conn.createStatement(); - - createTable("greekunicode", "(ID INTEGER NOT NULL AUTO_INCREMENT,UpperCase VARCHAR (30),LowerCase VARCHAR (30),Accented " - + " VARCHAR (30),Special VARCHAR (30),PRIMARY KEY(ID)) DEFAULT CHARACTER SET utf8", "InnoDB"); - - String upper = "\u0394\u930F\u039A\u0399\u039C\u0397"; - String lower = "\u03B4\u03BF\u03BA\u03B9\u03BC\u03B7"; - String accented = "\u03B4\u03CC\u03BA\u03AF\u03BC\u03AE"; - String special = "\u037E\u03C2\u03B0"; - - utfStmt.executeUpdate("INSERT INTO greekunicode VALUES ('1','" + upper + "','" + lower + "','" + accented + "','" + special + "')"); - - this.rs = utfStmt.executeQuery("SELECT UpperCase, LowerCase, Accented, Special from greekunicode"); - - this.rs.next(); - - assertTrue(upper.equals(this.rs.getString(1))); - assertTrue(lower.equals(this.rs.getString(2))); - assertTrue(accented.equals(this.rs.getString(3))); - assertTrue(special.equals(this.rs.getString(4))); - } - - /** - * Tests that 'latin1' character conversion works correctly. - * - * @throws Exception - */ - @Test - public void testLatin1Encoding() throws Exception { - char[] latin1Charset = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, - 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, - 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, - 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, - 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, - 0x007C, 0x007D, 0x007E, 0x007F, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, - 0x008E, 0x008F, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, - 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1, - 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, - 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, - 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, - 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, - 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF }; - - String latin1String = new String(latin1Charset); - Connection latin1Conn = null; - PreparedStatement pStmt = null; - - try { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "cp1252"); - latin1Conn = getConnectionWithProps(props); - - createTable("latin1RegressTest", "(stringField TEXT)"); - - pStmt = latin1Conn.prepareStatement("INSERT INTO latin1RegressTest VALUES (?)"); - pStmt.setString(1, latin1String); - pStmt.executeUpdate(); - - ((com.mysql.cj.jdbc.JdbcConnection) latin1Conn).getPropertySet().getProperty(PropertyKey.traceProtocol).setValue(true); - - this.rs = latin1Conn.createStatement().executeQuery("SELECT * FROM latin1RegressTest"); - ((com.mysql.cj.jdbc.JdbcConnection) latin1Conn).getPropertySet().getProperty(PropertyKey.traceProtocol).setValue(false); - - this.rs.next(); - - String retrievedString = this.rs.getString(1); - - System.out.println(latin1String); - System.out.println(retrievedString); - - if (!retrievedString.equals(latin1String)) { - int stringLength = Math.min(retrievedString.length(), latin1String.length()); - - for (int i = 0; i < stringLength; i++) { - char rChar = retrievedString.charAt(i); - char origChar = latin1String.charAt(i); - - if ((rChar != '?') && (rChar != origChar)) { - fail("characters differ at position " + i + "'" + rChar + "' retrieved from database, original char was '" + origChar + "'"); - } - } - } - } finally { - if (latin1Conn != null) { - latin1Conn.close(); - } - } - } /** * Tests newline being treated correctly. @@ -275,191 +90,6 @@ public void testNewlines() throws Exception { * testConversionForString("latin1", "������������������"); } */ - /** - * Tests that the 0x5c escaping works (we didn't use to have this). - * - * @throws Exception - */ - @Test - public void testSjis5c() throws Exception { - byte[] origByteStream = new byte[] { (byte) 0x95, (byte) 0x5c, (byte) 0x8e, (byte) 0x96 }; - - // - // Print the hex values of the string - // - StringBuilder bytesOut = new StringBuilder(); - - for (int i = 0; i < origByteStream.length; i++) { - bytesOut.append(Integer.toHexString(origByteStream[i] & 255)); - bytesOut.append(" "); - } - - System.out.println(bytesOut.toString()); - - String origString = new String(origByteStream, "SJIS"); - byte[] newByteStream = StringUtils.getBytes(origString, "SJIS"); - - // - // Print the hex values of the string (should have an extra 0x5c) - // - bytesOut = new StringBuilder(); - - for (int i = 0; i < newByteStream.length; i++) { - bytesOut.append(Integer.toHexString(newByteStream[i] & 255)); - bytesOut.append(" "); - } - - System.out.println(bytesOut.toString()); - - // - // Now, insert and retrieve the value from the database - // - Connection sjisConn = null; - Statement sjisStmt = null; - - try { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS"); - sjisConn = getConnectionWithProps(props); - - sjisStmt = sjisConn.createStatement(); - - this.rs = sjisStmt.executeQuery("SHOW VARIABLES LIKE 'character_set%'"); - - while (this.rs.next()) { - System.out.println(this.rs.getString(1) + " = " + this.rs.getString(2)); - } - - sjisStmt.executeUpdate("DROP TABLE IF EXISTS sjisTest"); - - sjisStmt.executeUpdate("CREATE TABLE sjisTest (field1 char(50)) DEFAULT CHARACTER SET SJIS"); - - this.pstmt = sjisConn.prepareStatement("INSERT INTO sjisTest VALUES (?)"); - this.pstmt.setString(1, origString); - this.pstmt.executeUpdate(); - - this.rs = sjisStmt.executeQuery("SELECT * FROM sjisTest"); - - while (this.rs.next()) { - byte[] testValueAsBytes = this.rs.getBytes(1); - - bytesOut = new StringBuilder(); - - for (int i = 0; i < testValueAsBytes.length; i++) { - bytesOut.append(Integer.toHexString(testValueAsBytes[i] & 255)); - bytesOut.append(" "); - } - - System.out.println("Value retrieved from database: " + bytesOut.toString()); - - String testValue = this.rs.getString(1); - - assertTrue(testValue.equals(origString)); - } - } finally { - this.stmt.executeUpdate("DROP TABLE IF EXISTS sjisTest"); - } - } - - /** - * Tests that UTF-8 character conversion works correctly. - * - * @throws Exception - */ - @Test - public void testUtf8Encoding() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF8"); - props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); - - Connection utfConn = DriverManager.getConnection(dbUrl, props); - testConversionForString("UTF8", utfConn, "\u043c\u0438\u0445\u0438"); - } - - @Test - public void testUtf8Encoding2() throws Exception { - String field1 = "K��sel"; - String field2 = "B�b"; - byte[] field1AsBytes = field1.getBytes("utf-8"); - byte[] field2AsBytes = field2.getBytes("utf-8"); - - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF8"); - - Connection utfConn = DriverManager.getConnection(dbUrl, props); - Statement utfStmt = utfConn.createStatement(); - - try { - utfStmt.executeUpdate("DROP TABLE IF EXISTS testUtf8"); - utfStmt.executeUpdate("CREATE TABLE testUtf8 (field1 varchar(32), field2 varchar(32)) CHARACTER SET UTF8"); - utfStmt.executeUpdate("INSERT INTO testUtf8 VALUES ('" + field1 + "','" + field2 + "')"); - - PreparedStatement pStmt = utfConn.prepareStatement("INSERT INTO testUtf8 VALUES (?, ?)"); - pStmt.setString(1, field1); - pStmt.setString(2, field2); - pStmt.executeUpdate(); - - this.rs = utfStmt.executeQuery("SELECT * FROM testUtf8"); - assertTrue(this.rs.next()); - - // Compare results stored using direct statement - // Compare to original string - assertTrue(field1.equals(this.rs.getString(1))); - assertTrue(field2.equals(this.rs.getString(2))); - - // Compare byte-for-byte, ignoring encoding - assertTrue(bytesAreSame(field1AsBytes, this.rs.getBytes(1))); - assertTrue(bytesAreSame(field2AsBytes, this.rs.getBytes(2))); - - assertTrue(this.rs.next()); - - // Compare to original string - assertTrue(field1.equals(this.rs.getString(1))); - assertTrue(field2.equals(this.rs.getString(2))); - - // Compare byte-for-byte, ignoring encoding - assertTrue(bytesAreSame(field1AsBytes, this.rs.getBytes(1))); - assertTrue(bytesAreSame(field2AsBytes, this.rs.getBytes(2))); - } finally { - utfStmt.executeUpdate("DROP TABLE IF EXISTS testUtf8"); - } - } - - private boolean bytesAreSame(byte[] byte1, byte[] byte2) { - if (byte1.length != byte2.length) { - return false; - } - - for (int i = 0; i < byte1.length; i++) { - if (byte1[i] != byte2[i]) { - return false; - } - } - - return true; - } - - private void testConversionForString(String charsetName, Connection convConn, String charsToTest) throws Exception { - PreparedStatement pStmt = null; - - this.stmt = convConn.createStatement(); - - createTable("charConvTest_" + charsetName, "(field1 CHAR(50) CHARACTER SET " + charsetName + ")"); - - this.stmt.executeUpdate("INSERT INTO charConvTest_" + charsetName + " VALUES ('" + charsToTest + "')"); - pStmt = convConn.prepareStatement("INSERT INTO charConvTest_" + charsetName + " VALUES (?)"); - pStmt.setString(1, charsToTest); - pStmt.executeUpdate(); - this.rs = this.stmt.executeQuery("SELECT * FROM charConvTest_" + charsetName); - - assertTrue(this.rs.next()); - - String testValue = this.rs.getString(1); - System.out.println(testValue); - assertTrue(testValue.equals(charsToTest)); - - } - /** * Tests fix for BUG#7601, '+' duplicated in fixDecimalExponent(). * @@ -533,7 +163,7 @@ public void printCallStackTrace() { System.setErr(newErr); Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); getConnectionWithProps(props).close(); System.setOut(oldOut); @@ -568,6 +198,8 @@ public void testBug11614() throws Exception { "(`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, `text` TEXT NOT NULL," + "PRIMARY KEY(`id`)) CHARACTER SET utf8 COLLATE utf8_general_ci"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); Connection utf8Conn = null; @@ -607,51 +239,6 @@ public void testBug11614() throws Exception { } } - @Test - public void testCodePage1252() throws Exception { - /* - * from - * ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/ - * CP1252.TXT - * - * 0x80 0x20AC #EURO SIGN 0x81 #UNDEFINED 0x82 0x201A #SINGLE LOW-9 - * QUOTATION MARK 0x83 0x0192 #LATIN SMALL LETTER F WITH HOOK 0x84 - * 0x201E #DOUBLE LOW-9 QUOTATION MARK 0x85 0x2026 #HORIZONTAL - * ELLIPSIS 0x86 0x2020 #DAGGER 0x87 0x2021 #DOUBLE DAGGER 0x88 - * 0x02C6 #MODIFIER LETTER CIRCUMFLEX ACCENT 0x89 0x2030 #PER MILLE - * SIGN 0x8A 0x0160 #LATIN CAPITAL LETTER S WITH CARON 0x8B 0x2039 - * #SINGLE LEFT-POINTING ANGLE QUOTATION MARK 0x8C 0x0152 #LATIN - * CAPITAL LIGATURE OE 0x8D #UNDEFINED 0x8E 0x017D #LATIN CAPITAL - * LETTER Z WITH CARON 0x8F #UNDEFINED 0x90 #UNDEFINED - */ - String codePage1252 = new String(new byte[] { (byte) 0x80, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, - (byte) 0x89, (byte) 0x8a, (byte) 0x8b, (byte) 0x8c, (byte) 0x8e }, "Cp1252"); - - System.out.println(codePage1252); - - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1252"); - Connection cp1252Conn = getConnectionWithProps(props); - createTable("testCp1252", "(field1 varchar(32) CHARACTER SET latin1)"); - cp1252Conn.createStatement().executeUpdate("INSERT INTO testCp1252 VALUES ('" + codePage1252 + "')"); - this.rs = cp1252Conn.createStatement().executeQuery("SELECT field1 FROM testCp1252"); - this.rs.next(); - assertEquals(this.rs.getString(1), codePage1252); - } - - /** - * Tests fix for BUG#24840 - character encoding of "US-ASCII" doesn't map correctly for 4.1 or newer - * - * @throws Exception - */ - @Test - public void testBug24840() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "US-ASCII"); - - getConnectionWithProps(props).close(); - } - /** * Tests fix for BUG#25047 - StringUtils.indexOfIgnoreCaseRespectQuotes() isn't case-insensitive on the first character of the target. * @@ -661,13 +248,13 @@ public void testBug24840() throws Exception { */ @Test public void testBug25047() throws Exception { - assertEquals(26, StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) values (?)", "VALUES", "`", "`", StringUtils.SEARCH_MODE__MRK_COM_WS)); - assertEquals(26, StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) VALUES (?)", "values", "`", "`", StringUtils.SEARCH_MODE__MRK_COM_WS)); + assertEquals(26, StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) values (?)", "VALUES", "`", "`", SearchMode.__MRK_COM_MYM_HNT_WS)); + assertEquals(26, StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) VALUES (?)", "values", "`", "`", SearchMode.__MRK_COM_MYM_HNT_WS)); - assertEquals(StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) values (?)", "VALUES", "`", "`", StringUtils.SEARCH_MODE__MRK_COM_WS), - StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) VALUES (?)", "VALUES", "`", "`", StringUtils.SEARCH_MODE__MRK_COM_WS)); - assertEquals(StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) values (?)", "values", "`", "`", StringUtils.SEARCH_MODE__MRK_COM_WS), - StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) VALUES (?)", "values", "`", "`", StringUtils.SEARCH_MODE__MRK_COM_WS)); + assertEquals(StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) values (?)", "VALUES", "`", "`", SearchMode.__MRK_COM_MYM_HNT_WS), + StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) VALUES (?)", "VALUES", "`", "`", SearchMode.__MRK_COM_MYM_HNT_WS)); + assertEquals(StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) values (?)", "values", "`", "`", SearchMode.__MRK_COM_MYM_HNT_WS), + StringUtils.indexOfIgnoreCase(0, "insert into Test (TestID) VALUES (?)", "values", "`", "`", SearchMode.__MRK_COM_MYM_HNT_WS)); } /** diff --git a/src/test/java/testsuite/regression/SyntaxRegressionTest.java b/src/test/java/testsuite/regression/SyntaxRegressionTest.java index 68ebe41a1..ac60a705f 100644 --- a/src/test/java/testsuite/regression/SyntaxRegressionTest.java +++ b/src/test/java/testsuite/regression/SyntaxRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -34,7 +34,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; import java.io.FileInputStream; @@ -91,11 +92,12 @@ public class SyntaxRegressionTest extends BaseTestCase { */ @Test public void testAlterTableAlgorithmLock() throws SQLException { - if (!versionMeetsMinimum(5, 6, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 6) && !isServerRunningOnWindows(), "The non-Windows MySQL 5.6.6+ is required to run this test."); + Connection c = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); try { @@ -152,9 +154,7 @@ public void testAlterTableAlgorithmLock() throws SQLException { */ @Test public void testCreateTableDataDirectory() throws SQLException { - if (!versionMeetsMinimum(5, 6, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 6) && isMysqlRunningLocally(), "Locally running MySQL 5.6.6+ is required to perform this test."); try { String tmpdir = null; @@ -169,16 +169,11 @@ public void testCreateTableDataDirectory() throws SQLException { if (File.separatorChar == '\\') { tmpdir = StringUtils.escapeQuote(tmpdir, File.separator); } - if (versionMeetsMinimum(8, 0, 21) && !getMysqlVariable("innodb_directories").contains(tmpdir)) { - // The server is not configured properly, skip this test. - System.err.println("testTransportableTablespaces: server must be initialized with '--innodb-directories=\"\"' " - + "where is the same value as the system variable 'tmpdir'."); - return; - } + assumeFalse(versionMeetsMinimum(8, 0, 21) && !getMysqlVariable("innodb_directories").contains(tmpdir), + "testTransportableTablespaces: server must be initialized with '--innodb-directories=\"\"' " + + "where is the same value as the system variable 'tmpdir'."); } else if ("innodb_file_per_table".equals(this.rs.getString(1))) { - if (!this.rs.getString(2).equals("ON")) { - fail("You need to set innodb_file_per_table to ON before running this test!"); - } + assertTrue(this.rs.getString(2).equals("ON"), "You need to set innodb_file_per_table to ON before running this test!"); } } @@ -236,13 +231,8 @@ public void testCreateTableDataDirectory() throws SQLException { */ @Test public void testTransportableTablespaces() throws Exception { - if (!versionMeetsMinimum(5, 6, 8)) { - return; - } - if (!isMysqlRunningLocally()) { - System.err.println("Skip test as client and server are running on different machines."); - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 8), "MySQL 5.6.8+ is required to run this test."); + assumeTrue(isMysqlRunningLocally(), "Skip test as client and server are running on different machines."); String tmpdir = null; String uuid = null; @@ -253,16 +243,11 @@ public void testTransportableTablespaces() throws Exception { if (tmpdir.endsWith(File.separator)) { tmpdir = tmpdir.substring(0, tmpdir.length() - File.separator.length()); } - if (versionMeetsMinimum(8, 0, 21) && !getMysqlVariable("innodb_directories").contains(tmpdir)) { - // The server is not configured properly, skip this test. - System.err.println("testTransportableTablespaces: server must be initialized with '--innodb-directories=\"\"' " - + "where is the same value as the system variable 'tmpdir'."); - return; - } + assumeFalse(versionMeetsMinimum(8, 0, 21) && !getMysqlVariable("innodb_directories").contains(tmpdir), + "testTransportableTablespaces: server must be initialized with '--innodb-directories=\"\"' " + + "where is the same value as the system variable 'tmpdir'."); } else if ("innodb_file_per_table".equals(this.rs.getString(1))) { - if (!this.rs.getString(2).equals("ON")) { - fail("You need to set innodb_file_per_table to ON before running this test!"); - } + assumeTrue(this.rs.getString(2).equals("ON"), "You need to set innodb_file_per_table to ON before running this test!"); } else if ("server_uuid".equals(this.rs.getString(1))) { uuid = this.rs.getString(2); } @@ -370,9 +355,8 @@ private void copyFile(File source, File dest) throws IOException { */ @Test public void testExchangePartition() throws Exception { - if (!versionMeetsMinimum(5, 6, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 6), "MySQL 5.6.6+ is required to run this test."); + createTable("testExchangePartition1", "(id int(11) NOT NULL AUTO_INCREMENT, year year(4) DEFAULT NULL," + " modified timestamp NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB ROW_FORMAT=COMPACT PARTITION BY HASH (id) PARTITIONS 2"); createTable("testExchangePartition2", "LIKE testExchangePartition1"); @@ -414,7 +398,12 @@ public void testExchangePartition() throws Exception { Connection testConn = null; try { - testConn = getConnectionWithProps("useServerPrepStmts=true,emulateUnsupportedPstmts=false"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.emulateUnsupportedPstmts.getKeyName(), "false"); + testConn = getConnectionWithProps(props); // Using Server PreparedStatement, with validation. if (versionMeetsMinimum(5, 7, 5)) { @@ -454,9 +443,7 @@ public void testExchangePartition() throws Exception { */ @Test public void testExplicitPartitions() throws Exception { - if (!versionMeetsMinimum(5, 6, 5)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 5) && isMysqlRunningLocally(), "Locally running MySQL 5.6.5+ is required to perform this test."); String datadir = null; this.rs = this.stmt.executeQuery("SHOW VARIABLES WHERE Variable_name='datadir'"); @@ -469,13 +456,11 @@ public void testExplicitPartitions() throws Exception { this.rs = this.stmt.executeQuery("SHOW VARIABLES WHERE Variable_name='secure_file_priv'"); this.rs.next(); String fileprivdir = this.rs.getString(2); - if ("NULL".equalsIgnoreCase(this.rs.getString(2))) { - fail("To run this test the server needs to be started with the option\"--secure-file-priv=\""); - } else if (fileprivdir.length() > 0) { + assumeFalse("NULL".equalsIgnoreCase(this.rs.getString(2)), "To run this test the server needs to be started with the option\"--secure-file-priv=\""); + if (fileprivdir.length() > 0) { fileprivdir = new File(fileprivdir).getCanonicalPath(); - if (!datadir.equals(fileprivdir)) { - fail("To run this test the server option\"--secure-file-priv=\" needs to be empty or to match the server's data directory."); - } + assumeTrue(datadir.equals(fileprivdir), + "To run this test the server option\"--secure-file-priv=\" needs to be empty or to match the server's data directory."); } Properties props = getPropertiesFromTestsuiteUrl(); @@ -549,13 +534,10 @@ public void testExplicitPartitions() throws Exception { this.stmt.executeUpdate("UNLOCK TABLES"); // Test LOAD - if (dbname == null) { - fail("No database selected"); - } else { - File f = new File(datadir + File.separator + dbname + File.separator + "loadtestExplicitPartitions.txt"); - if (f.exists()) { - f.delete(); - } + assertNotNull(dbname, "No database selected"); + File f = new File(datadir + File.separator + dbname + File.separator + "loadtestExplicitPartitions.txt"); + if (f.exists()) { + f.delete(); } this.pstmt = this.conn @@ -694,9 +676,6 @@ public void testExplicitPartitions() throws Exception { this.stmt.executeUpdate("SET @@default_storage_engine = @old_default_storage_engine"); - } catch (SQLException e) { - fail(e.getMessage()); - } finally { this.stmt.executeUpdate("DROP TABLE IF EXISTS testExplicitPartitions, testExplicitPartitions2, testExplicitPartitions3"); @@ -721,10 +700,7 @@ public void testExplicitPartitions() throws Exception { */ @Test public void testIPv6Functions() throws Exception { - if (!versionMeetsMinimum(5, 6, 11)) { - // MySQL 5.6.11 includes a bug fix (Bug#68454) that is required to run this test successfully. - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 11), "MySQL 5.6.11 includes a bug fix (Bug#68454) that is required to run this test successfully."); String[][] dataSamples = new String[][] { { "127.0.0.1", "172.0.0.1" }, { "192.168.1.1", "::ffff:192.168.1.1" }, { "10.1", "::ffff:10.1" }, { "172.16.260.4", "172.16.260.4" }, { "::1", "::1" }, { "10AA:10bb:10CC:10dd:10EE:10FF:10aa:10BB", "10aa:10bb:10cc:10dd:10ee:10ff:10aa:10bb" }, @@ -787,9 +763,7 @@ public void testIPv6Functions() throws Exception { */ @Test public void testFULLTEXTSearchInnoDB() throws Exception { - if (!versionMeetsMinimum(5, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6), "MySQL 5.6+ is required to run this test."); createTable("testFULLTEXTSearchInnoDB", "(id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, " + "title VARCHAR(200), body TEXT, FULLTEXT (title , body)) ENGINE=InnoDB"); @@ -826,9 +800,7 @@ public void testFULLTEXTSearchInnoDB() throws Exception { */ @Test public void testRenameIndex() throws Exception { - if (!versionMeetsMinimum(5, 7, 1)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 1), "MySQL 5.7.1+ is required to run this test."); createTable("testRenameIndex", "(col1 INT, col2 INT, INDEX (col1)) ENGINE=InnoDB"); this.stmt.execute("CREATE INDEX testIdx ON testRenameIndex (col2)"); @@ -863,9 +835,7 @@ public void testRenameIndex() throws Exception { */ @Test public void testGetStackedDiagnostics() throws Exception { - if (!versionMeetsMinimum(5, 7, 2)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 2), "MySQL 5.7.2+ is required to run this test."); // test calling GET STACKED DIAGNOSTICS outside an handler final Statement locallyScopedStmt = this.stmt; @@ -957,9 +927,7 @@ public Void call() throws Exception { */ @Test public void testDiscardImportPartitions() throws Exception { - if (!versionMeetsMinimum(5, 7, 4)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 4), "MySQL 5.7.4+ is required to run this test."); createTable("testDiscardImportPartitions", "(id INT) ENGINE = InnoDB PARTITION BY RANGE (id) (PARTITION p1 VALUES LESS THAN (0), PARTITION p2 VALUES LESS THAN MAXVALUE)"); @@ -1022,9 +990,7 @@ public Void call() throws Exception { */ @Test public void testJsonType() throws Exception { - if (!versionMeetsMinimum(5, 7, 8)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 8), "MySQL 5.7.8+ is required to run this test."); createTable("testJsonType", "(id INT PRIMARY KEY, jsonDoc JSON)"); assertEquals(1, this.stmt.executeUpdate("INSERT INTO testJsonType VALUES (1, '{\"key1\": \"value1\"}')")); @@ -1135,9 +1101,7 @@ private void testJsonTypeCheckFunction(String sql, String expectedResult) throws */ @Test public void testHints() throws Exception { - if (!versionMeetsMinimum(5, 7, 7)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 7), "MySQL 5.7.7+ is required to run this test."); /* * Test hints syntax variations. @@ -1273,7 +1237,6 @@ public void testHints() throws Exception { } } - @Test private void testHintsSyntax(String query, boolean processesHint, boolean warningExpected) throws Exception { this.stmt.clearWarnings(); this.rs = this.stmt.executeQuery(query); @@ -1299,9 +1262,7 @@ private void testHintsSyntax(String query, boolean processesHint, boolean warnin */ @Test public void testCreateTablespace() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); try { this.stmt.execute("CREATE TABLESPACE testTs1 ADD DATAFILE 'testTs1.ibd'"); @@ -1377,9 +1338,7 @@ private void testCreateTablespaceCheckTables(String tablespace, int expectedTblC */ @Test public void testSetMergeThreshold() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); Map keyMergeThresholds = new HashMap<>(); keyMergeThresholds.put("k2", 45); @@ -1446,9 +1405,9 @@ private void testSetMergeThresholdIndices(int defaultMergeThreshold, Map() { @@ -1909,9 +1867,7 @@ public Void call() throws Exception { */ @Test public void testUserAccountLocking() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); final String user = "testAccLck"; final String pwd = "testAccLck"; @@ -1977,9 +1933,7 @@ public Void call() throws Exception { */ @Test public void testUserAccountPwdExpiration() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); final String user = "testAccPwdExp"; final String pwd = "testAccPwdExp"; @@ -2051,9 +2005,7 @@ public Void call() throws Exception { */ @Test public void testInnodbTablespaceEncryption() throws Exception { - if (!versionMeetsMinimum(5, 7, 11)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 11), "MySQL 5.7.11+ is required to run this test."); boolean keyringPluginIsActive = false; this.rs = this.stmt.executeQuery("SELECT (PLUGIN_STATUS='ACTIVE') AS `TRUE` FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE 'keyring_file'"); @@ -2088,9 +2040,7 @@ public void testInnodbTablespaceEncryption() throws Exception { } else { // Syntax can still be tested by with different outcome. System.out.println("Although not required it is recommended that the 'keyring_file' plugin is properly installed and configured to run this test."); - String err = versionMeetsMinimum(8, 0, 4) || versionMeetsMinimum(5, 7, 22) && !versionMeetsMinimum(8, 0, 0) - ? "Can't find master key from keyring, please check in the server log if a keyring plugin is loaded and initialized successfully." - : "Can't find master key from keyring, please check keyring plugin is loaded."; + String err = "Can't find master key from keyring.*"; final Statement testStmt = this.conn.createStatement(); assertThrows(SQLException.class, err, new Callable() { diff --git a/src/test/java/testsuite/regression/UtilsRegressionTest.java b/src/test/java/testsuite/regression/UtilsRegressionTest.java index f5c510792..7ab89281a 100644 --- a/src/test/java/testsuite/regression/UtilsRegressionTest.java +++ b/src/test/java/testsuite/regression/UtilsRegressionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020, Oracle and/or its affiliates. + * Copyright (c) 2014, 2021, 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 @@ -33,8 +33,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -46,6 +50,7 @@ import com.mysql.cj.exceptions.ExceptionInterceptor; import com.mysql.cj.jdbc.exceptions.SQLError; import com.mysql.cj.log.Log; +import com.mysql.cj.util.StringUtils; import com.mysql.cj.util.TimeUtil; import testsuite.BaseTestCase; @@ -642,4 +647,43 @@ public Exception interceptException(Exception sqlEx) { assertEquals("INTERCEPT_EXCEPTION", ex.getMessage()); assertEquals("INTERCEPT_CAUSE", ex.getCause().getMessage()); } + + /** + * Test fix for Bug#20913114, STRINGUTILS.WILDCOMPARE() FAILS WITH STACKOVERFLOWERROR ERROR. + * + * @throws Exception + */ + @Test + public void testBug20913114() throws Exception { + String s1 = "String Consist of some small Sample Data"; + + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "St%no")); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "Srin")); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "Dae")); + + assertTrue(StringUtils.wildCompareIgnoreCase(s1, "St%so%Sa%Da%")); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "St%so%Sp%Da%")); + assertTrue(StringUtils.wildCompareIgnoreCase(s1, "S_%s_%S_%Da%")); + assertTrue(StringUtils.wildCompareIgnoreCase(s1, "S_r_n_ C_n_i_t%")); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "St%so%Sp%Da%")); + + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "Dae")); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "Srin")); + assertFalse(StringUtils.wildCompareIgnoreCase(s1, "St%no")); + } + + /** + * Tests fix for Bug#104170 (33064455), CONTRIBUTION: CLIENTPREPAREDSTMT: LEAVE CALENDAR UNTOUCHED. + * + * @throws Exception + */ + @Test + public void testBug104170() throws Exception { + Calendar cal = Calendar.getInstance(); + long orig = cal.getTimeInMillis(); + + TimeUtil.getSimpleDateFormat("''yyyy-MM-dd''", cal).format(java.util.Date.from(LocalDateTime.of(1980, 1, 1, 0, 0).toInstant(ZoneOffset.UTC))); + + assertEquals(orig, cal.getTimeInMillis()); + } } diff --git a/src/test/java/testsuite/simple/AuthenticationTest.java b/src/test/java/testsuite/simple/AuthenticationTest.java index 521cf0469..3d4a0e02e 100644 --- a/src/test/java/testsuite/simple/AuthenticationTest.java +++ b/src/test/java/testsuite/simple/AuthenticationTest.java @@ -33,16 +33,24 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import javax.security.sasl.SaslException; import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.CJException; +import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.protocol.AuthenticationPlugin; import com.mysql.cj.protocol.a.NativeConstants.StringSelfDataType; import com.mysql.cj.protocol.a.NativePacketPayload; @@ -536,4 +544,200 @@ public void authLdapSaslCliPluginChallengeUnsupportedMech() throws Exception { return null; }); } + + /** + * Test for WL#14650 - Support for MFA (multi factor authentication) authentication + * + * @throws Exception + */ + @Test + public void testWl14650() throws Exception { + assumeTrue(versionMeetsMinimum(8, 0, 27), "MySQL 8.0.27+ is required to run this test."); + + boolean installPluginInRuntime = false; + try { + // Install plugin if required. + this.rs = this.stmt.executeQuery("SELECT (PLUGIN_LIBRARY LIKE 'auth_test_plugin%') AS installed FROM INFORMATION_SCHEMA.PLUGINS " + + "WHERE PLUGIN_NAME='cleartext_plugin_server'"); + installPluginInRuntime = !this.rs.next() || !this.rs.getBoolean(1); + + if (installPluginInRuntime) { + try { + String ext = isServerRunningOnWindows() ? ".dll" : ".so"; + this.stmt.executeUpdate("INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin" + ext + "'"); + } catch (SQLException e) { + if (e.getErrorCode() == MysqlErrorNumbers.ER_CANT_OPEN_LIBRARY) { + installPluginInRuntime = false; // To disable plugin deinstallation attempt in the finally block. + assumeTrue(false, "This test requires a server installed with the test package."); + } else { + throw e; + } + } + } + + // Create test users. + createUser("'wl14650_1fa'@'%'", "IDENTIFIED BY 'testpwd1'"); + this.stmt.executeUpdate("GRANT ALL ON * TO wl14650_1fa"); + createUser("'wl14650_2fa'@'%'", "IDENTIFIED BY 'testpwd1' AND IDENTIFIED WITH cleartext_plugin_server AS 'testpwd2'"); + this.stmt.executeUpdate("GRANT ALL ON * TO wl14650_2fa"); + createUser("'wl14650_3fa'@'%'", "IDENTIFIED BY 'testpwd1' AND IDENTIFIED WITH cleartext_plugin_server AS 'testpwd2' " + + "AND IDENTIFIED WITH cleartext_plugin_server AS 'testpwd3'"); + this.stmt.executeUpdate("GRANT ALL ON * TO wl14650_3fa"); + this.stmt.executeUpdate("FLUSH PRIVILEGES"); + + final StringBuilder urlBuilder1 = new StringBuilder("jdbc:mysql://").append(getHostFromTestsuiteUrl()).append(":").append(getPortFromTestsuiteUrl()) + .append("/"); + final String url1 = urlBuilder1.toString(); + + // TS.1.1: 2FA successful. + Properties props = new Properties(); + props.setProperty(PropertyKey.USER.getKeyName(), "wl14650_2fa"); + props.setProperty(PropertyKey.password1.getKeyName(), "testpwd1"); + props.setProperty(PropertyKey.password2.getKeyName(), "testpwd2"); + props.setProperty(PropertyKey.sslMode.getKeyName(), "REQUIRED"); + try (Connection testConn = getConnectionWithProps(url1, props)) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_2fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_2fa", this.rs.getString(2).split("@")[0]); + } + + // TS.1.2: 2FA fail - 1st password wrong. + props.setProperty(PropertyKey.password1.getKeyName(), "wrongpwd"); + assertThrows(SQLException.class, "Access denied for user 'wl14650_2fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.1.3: 2FA fail - 2nd password wrong. + props.setProperty(PropertyKey.password1.getKeyName(), "testpwd1"); + props.setProperty(PropertyKey.password2.getKeyName(), "wrongpwd"); + assertThrows(SQLException.class, "Access denied for user 'wl14650_2fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.1.4: 2FA fail - missing required password. + props.remove(PropertyKey.password2.getKeyName()); + assertThrows(SQLException.class, "Access denied for user 'wl14650_2fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.2.1: 3FA successful. + props.setProperty(PropertyKey.USER.getKeyName(), "wl14650_3fa"); + props.setProperty(PropertyKey.password1.getKeyName(), "testpwd1"); + props.setProperty(PropertyKey.password2.getKeyName(), "testpwd2"); + props.setProperty(PropertyKey.password3.getKeyName(), "testpwd3"); + props.setProperty(PropertyKey.sslMode.getKeyName(), "REQUIRED"); + try (Connection testConn = getConnectionWithProps(url1, props)) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_3fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_3fa", this.rs.getString(2).split("@")[0]); + } + + // TS.2.2: 2FA fail - 1st password wrong. + props.setProperty(PropertyKey.password1.getKeyName(), "wrongpwd"); + assertThrows(SQLException.class, "Access denied for user 'wl14650_3fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.2.3: 2FA fail - 2nd password wrong. + props.setProperty(PropertyKey.password1.getKeyName(), "testpwd1"); + props.setProperty(PropertyKey.password2.getKeyName(), "wrongpwd"); + assertThrows(SQLException.class, "Access denied for user 'wl14650_3fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.2.4: 2FA fail - 3rd password wrong. + props.setProperty(PropertyKey.password2.getKeyName(), "testpwd2"); + props.setProperty(PropertyKey.password3.getKeyName(), "wrongpwd"); + assertThrows(SQLException.class, "Access denied for user 'wl14650_3fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.2.5: 2FA fail - missing required password. + props.remove(PropertyKey.password3.getKeyName()); + assertThrows(SQLException.class, "Access denied for user 'wl14650_3fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url1, props)); + + // TS.3/TS.4/TS.5: new password options don't pollute original ones. + props.setProperty(PropertyKey.USER.getKeyName(), "wl14650_1fa"); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), "testpwd1"); + props.setProperty(PropertyKey.password1.getKeyName(), "wrongpwd"); + props.setProperty(PropertyKey.password2.getKeyName(), "wrongpwd"); + props.setProperty(PropertyKey.password3.getKeyName(), "wrongpwd"); + final StringBuilder urlBuilder2 = new StringBuilder("jdbc:mysql://").append(getHostFromTestsuiteUrl()).append(":").append(getPortFromTestsuiteUrl()) + .append("/?").append("password1=wrongpwd&password2=wrongpwd&password3=wrongpwd"); + final String url2 = urlBuilder2.toString(); + try (Connection testConn = getConnectionWithProps(url2, props)) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_1fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_1fa", this.rs.getString(2).split("@")[0]); + } + + props.remove(PropertyKey.USER.getKeyName()); + props.remove(PropertyKey.PASSWORD.getKeyName()); + final StringBuilder urlBuilder3 = new StringBuilder("jdbc:mysql://").append("wl14650_1fa:testpwd1@").append(getHostFromTestsuiteUrl()).append(":") + .append(getPortFromTestsuiteUrl()).append("/?").append("password1=wrongpwd&password2=wrongpwd&password3=wrongpwd"); + final String url3 = urlBuilder3.toString(); + try (Connection testConn = getConnectionWithProps(url3, props)) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_1fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_1fa", this.rs.getString(2).split("@")[0]); + } + + props.remove(PropertyKey.USER.getKeyName()); + props.remove(PropertyKey.PASSWORD.getKeyName()); + final StringBuilder urlBuilder4 = new StringBuilder("jdbc:mysql://").append(getHostFromTestsuiteUrl()).append(":").append(getPortFromTestsuiteUrl()) + .append("/?").append("user=wl14650_1fa&password=testpwd1&password1=wrongpwd&password2=wrongpwd&password3=wrongpwd"); + final String url4 = urlBuilder4.toString(); + try (Connection testConn = getConnectionWithProps(url4, props)) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_1fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_1fa", this.rs.getString(2).split("@")[0]); + } + + final StringBuilder urlBuilder5 = new StringBuilder("jdbc:mysql://").append(getHostFromTestsuiteUrl()).append(":").append(getPortFromTestsuiteUrl()) + .append("/?").append("password1=wrongpwd&password2=wrongpwd&password3=wrongpwd&sslMode=REQUIRED"); + final String url5 = urlBuilder5.toString(); + try (Connection testConn = DriverManager.getConnection(url5, "wl14650_1fa", "testpwd1")) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_1fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_1fa", this.rs.getString(2).split("@")[0]); + } + + // TS.6: 1FA successful with password given in 'password1'. + props.setProperty(PropertyKey.USER.getKeyName(), "wl14650_1fa"); + props.remove(PropertyKey.password1.getKeyName()); + props.remove(PropertyKey.password2.getKeyName()); + props.remove(PropertyKey.password3.getKeyName()); + final StringBuilder urlBuilder6 = new StringBuilder("jdbc:mysql://").append(getHostFromTestsuiteUrl()).append(":").append(getPortFromTestsuiteUrl()) + .append("/?").append("password1=testpwd1&password2=wrongpwd&password3=wrongpwd"); + final String url6 = urlBuilder6.toString(); + try (Connection testConn = getConnectionWithProps(url6, props)) { + Statement testStmt = testConn.createStatement(); + this.rs = testStmt.executeQuery("SELECT USER(), CURRENT_USER()"); + assertTrue(this.rs.next()); + assertEquals("wl14650_1fa", this.rs.getString(1).split("@")[0]); + assertEquals("wl14650_1fa", this.rs.getString(2).split("@")[0]); + } + + // TS.7: 1FA fail with 'password' wrong and 'password1' correct. + props.setProperty(PropertyKey.USER.getKeyName(), "wl14650_1fa"); + props.setProperty(PropertyKey.PASSWORD.getKeyName(), "wrongpwd"); + props.setProperty(PropertyKey.password1.getKeyName(), "testpwd1"); + final StringBuilder urlBuilder7 = new StringBuilder("jdbc:mysql://").append(getHostFromTestsuiteUrl()).append(":").append(getPortFromTestsuiteUrl()) + .append("/"); + final String url7 = urlBuilder7.toString(); + assertThrows(SQLException.class, "Access denied for user 'wl14650_1fa'@'localhost' \\(using password: YES\\)", + () -> getConnectionWithProps(url7, props)); + } finally { + if (installPluginInRuntime) { + this.stmt.executeUpdate("UNINSTALL PLUGIN cleartext_plugin_server"); + } + } + } } diff --git a/src/test/java/testsuite/simple/BlobTest.java b/src/test/java/testsuite/simple/BlobTest.java index 69a9cdb1d..8f590e510 100644 --- a/src/test/java/testsuite/simple/BlobTest.java +++ b/src/test/java/testsuite/simple/BlobTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -30,7 +30,8 @@ package testsuite.simple; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -86,6 +87,11 @@ public void setUp() throws Exception { @Test public void testByteStreamInsert() throws Exception { + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); + this.rs.next(); + long len = 4 + testBlobFile.length() * 2; + assumeTrue(this.rs.getInt(2) >= len, "You need to increase max_allowed_packet to at least " + (len) + " before running this test!"); + if (versionMeetsMinimum(5, 6, 20) && !versionMeetsMinimum(5, 7)) { /* * The 5.6.20 patch for Bug #16963396, Bug #19030353, Bug #69477 limits the size of redo log BLOB writes @@ -96,9 +102,8 @@ public void testByteStreamInsert() throws Exception { */ this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'innodb_log_file_size'"); this.rs.next(); - if (this.rs.getInt(2) < 10 * testBlobFile.length()) { - fail("You need to increase innodb_log_file_size to at least " + (10 * testBlobFile.length()) + " before running this test!"); - } + assumeFalse(this.rs.getInt(2) < 10 * testBlobFile.length(), + "You need to increase innodb_log_file_size to at least " + (10 * testBlobFile.length()) + " before running this test!"); } testByteStreamInsert(this.conn); } diff --git a/src/test/java/testsuite/simple/CallableStatementTest.java b/src/test/java/testsuite/simple/CallableStatementTest.java index 22af8528d..1489f16f0 100644 --- a/src/test/java/testsuite/simple/CallableStatementTest.java +++ b/src/test/java/testsuite/simple/CallableStatementTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -108,7 +108,13 @@ public void testBatch() throws Exception { executeBatchedStoredProc(this.conn); - batchedConn = getConnectionWithProps("logger=" + BufferingLogger.class.getName() + ",rewriteBatchedStatements=true,profileSQL=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); + batchedConn = getConnectionWithProps(props); BufferingLogger.startLoggingToBuffer(); executeBatchedStoredProc(batchedConn); @@ -259,9 +265,6 @@ public void testResultSet() throws Exception { assertTrue("abc".equals(this.rs.getString(1))); - // TODO: This does not yet work in MySQL 5.0 - // assertTrue(!storedProc.getMoreResults()); - // assertTrue(storedProc.getUpdateCount() == 2); assertTrue(storedProc.getMoreResults()); ResultSet nextResultSet = storedProc.getResultSet(); @@ -346,6 +349,8 @@ public void testSPCache() throws Exception { assertTrue(this.rs.getInt(1) == 1); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheCallableStmts.getKeyName(), "true"); Connection cachedSpConn = getConnectionWithProps(props); @@ -374,6 +379,8 @@ public void testOutParamsNoBodies() throws Exception { CallableStatement storedProc = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.noAccessToProcedureBodies.getKeyName(), "true"); Connection spConn = getConnectionWithProps(props); diff --git a/src/test/java/testsuite/simple/CharsetTest.java b/src/test/java/testsuite/simple/CharsetTest.java index f5bcf59f0..e359e7335 100644 --- a/src/test/java/testsuite/simple/CharsetTest.java +++ b/src/test/java/testsuite/simple/CharsetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. + * Copyright (c) 2005, 2021, 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 @@ -30,15 +30,19 @@ package testsuite.simple; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; @@ -53,7 +57,11 @@ import org.junit.jupiter.api.Test; import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CharsetMappingWrapper; +import com.mysql.cj.MysqlConnection; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; +import com.mysql.cj.util.StringUtils; import testsuite.BaseTestCase; @@ -63,10 +71,12 @@ public void testCP932Backport() throws Exception { try { "".getBytes("WINDOWS-31J"); } catch (UnsupportedEncodingException uee) { - return; + assumeFalse(true, "Test requires JVM with WINDOWS-31J support."); } Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "WINDOWS-31J"); getConnectionWithProps(props).close(); } @@ -76,7 +86,7 @@ public void testNECExtendedCharsByEUCJPSolaris() throws Exception { try { "".getBytes("EUC_JP_Solaris"); } catch (UnsupportedEncodingException uee) { - return; + assumeFalse(true, "Test requires JVM with EUC_JP_Solaris support."); } char necExtendedChar = 0x3231; // 0x878A of WINDOWS-31J, NEC @@ -84,6 +94,8 @@ public void testNECExtendedCharsByEUCJPSolaris() throws Exception { String necExtendedCharString = String.valueOf(necExtendedChar); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_JP_Solaris"); @@ -159,7 +171,7 @@ public void testInsertCharStatement() throws Exception { try { "".getBytes("SJIS"); } catch (UnsupportedEncodingException uee) { - return; + assumeFalse(true, "Test requires JVM with SJIS support."); } Map testDataMap = new HashMap<>(); @@ -208,6 +220,8 @@ public void testInsertCharStatement() throws Exception { for (String charset : charsetList) { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), charset); Connection conn2 = getConnectionWithProps(props); @@ -243,15 +257,6 @@ public void testInsertCharStatement() throws Exception { } } - @Test - public void testStaticCharsetMappingConsistency() { - for (int i = 1; i < CharsetMapping.MAP_SIZE; i++) { - assertNotNull(CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[i], - "Assertion failure: No mapping from charset index " + i + " to a mysql collation"); - assertNotNull(CharsetMapping.COLLATION_INDEX_TO_CHARSET[i], "Assertion failure: No mapping from charset index " + i + " to a Java character set"); - } - } - /** * Prints static mappings for analysis. * @@ -269,43 +274,48 @@ public void testCharsetMapping() throws Exception { java.nio.charset.Charset cs = availableCharsets.get(canonicalName); canonicalName = cs.name(); - int index = CharsetMapping.getCollationIndexForJavaEncoding(canonicalName, this.serverVersion); - String csname = CharsetMapping.getMysqlCharsetNameForCollationIndex(index); + int index = CharsetMappingWrapper.getStaticCollationIndexForJavaEncoding(canonicalName, this.serverVersion); + String csname = CharsetMappingWrapper.getStaticMysqlCharsetNameForCollationIndex(index); System.out.println((canonicalName + " ").substring(0, 26) + " (" + cs.canEncode() + ") --> " - + CharsetMapping.getJavaEncodingForCollationIndex(index) + " : " + index + " : " - + CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[index] + " : " + CharsetMapping.getMysqlCharsetNameForCollationIndex(index) + " : " - + CharsetMapping.CHARSET_NAME_TO_CHARSET.get(csname) + " : " + CharsetMapping.getJavaEncodingForMysqlCharset(csname) + " : " - + CharsetMapping.getMysqlCharsetForJavaEncoding(canonicalName, this.serverVersion) + " : " - + CharsetMapping.getCollationIndexForJavaEncoding(canonicalName, this.serverVersion) + " : " - + CharsetMapping.isMultibyteCharset(canonicalName)); + + CharsetMappingWrapper.getStaticJavaEncodingForCollationIndex(index) + " : " + index + " : " + + CharsetMappingWrapper.getStaticCollationNameForCollationIndex(index) + " : " + + CharsetMappingWrapper.getStaticMysqlCharsetNameForCollationIndex(index) + " : " + + CharsetMappingWrapper.getStaticMysqlCharsetByName(csname) + " : " + CharsetMappingWrapper.getStaticJavaEncodingForMysqlCharset(csname) + + " : " + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding(canonicalName, this.serverVersion) + " : " + + CharsetMappingWrapper.getStaticCollationIndexForJavaEncoding(canonicalName, this.serverVersion) + " : " + + CharsetMappingWrapper.isStaticMultibyteCharset(canonicalName)); Set s = cs.aliases(); Iterator j = s.iterator(); while (j.hasNext()) { String alias = j.next(); - index = CharsetMapping.getCollationIndexForJavaEncoding(alias, this.serverVersion); - csname = CharsetMapping.getMysqlCharsetNameForCollationIndex(index); + index = CharsetMappingWrapper.getStaticCollationIndexForJavaEncoding(alias, this.serverVersion); + csname = CharsetMappingWrapper.getStaticMysqlCharsetNameForCollationIndex(index); System.out.println(" " + (alias + " ").substring(0, 30) + " --> " - + CharsetMapping.getJavaEncodingForCollationIndex(index) + " : " + index + " : " - + CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[index] + " : " + CharsetMapping.getMysqlCharsetNameForCollationIndex(index) - + " : " + CharsetMapping.CHARSET_NAME_TO_CHARSET.get(csname) + " : " + CharsetMapping.getJavaEncodingForMysqlCharset(csname) - + " : " + CharsetMapping.getMysqlCharsetForJavaEncoding(alias, this.serverVersion) + " : " - + CharsetMapping.getCollationIndexForJavaEncoding(alias, this.serverVersion) + " : " + CharsetMapping.isMultibyteCharset(alias)); + + CharsetMappingWrapper.getStaticJavaEncodingForCollationIndex(index) + " : " + index + " : " + + CharsetMappingWrapper.getStaticCollationNameForCollationIndex(index) + " : " + + CharsetMappingWrapper.getStaticMysqlCharsetNameForCollationIndex(index) + " : " + + CharsetMappingWrapper.getStaticMysqlCharsetByName(csname) + " : " + + CharsetMappingWrapper.getStaticJavaEncodingForMysqlCharset(csname) + " : " + + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding(alias, this.serverVersion) + " : " + + CharsetMappingWrapper.getStaticCollationIndexForJavaEncoding(alias, this.serverVersion) + " : " + + CharsetMappingWrapper.isStaticMultibyteCharset(alias)); } System.out.println("==================================="); } for (int i = 1; i < CharsetMapping.MAP_SIZE; i++) { - String csname = CharsetMapping.getMysqlCharsetNameForCollationIndex(i); - String enc = CharsetMapping.getJavaEncodingForCollationIndex(i); - System.out.println((i + " ").substring(0, 4) + " by index--> " - + (CharsetMapping.COLLATION_INDEX_TO_COLLATION_NAME[i] + " ").substring(0, 20) + " : " - + (csname + " ").substring(0, 10) + " : " + (enc + " ").substring(0, 20) + String csname = CharsetMappingWrapper.getStaticMysqlCharsetNameForCollationIndex(i); + if (csname != null) { + String enc = CharsetMappingWrapper.getStaticJavaEncodingForCollationIndex(i); + System.out.println((i + " ").substring(0, 4) + " by index--> " + + (CharsetMappingWrapper.getStaticCollationNameForCollationIndex(i) + " ").substring(0, 20) + " : " + + (csname + " ").substring(0, 10) + " : " + (enc + " ").substring(0, 20) - + " by charset--> " + (CharsetMapping.getJavaEncodingForMysqlCharset(csname) + " ").substring(0, 20) - - + " by encoding--> " + (CharsetMapping.getCollationIndexForJavaEncoding(enc, this.serverVersion) + " ").substring(0, 4) + " : " - + (CharsetMapping.getMysqlCharsetForJavaEncoding(enc, this.serverVersion) + " ").substring(0, 15)); + + " by charset--> " + (CharsetMappingWrapper.getStaticJavaEncodingForMysqlCharset(csname) + " ").substring(0, 20) + + " by encoding--> " + (CharsetMappingWrapper.getStaticCollationIndexForJavaEncoding(enc, this.serverVersion) + " ").substring(0, 4) + + " : " + (CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding(enc, this.serverVersion) + " ").substring(0, 15)); + } } } @@ -318,9 +328,7 @@ public void testCharsetMapping() throws Exception { public void testGB18030() throws Exception { // check that server supports this character set this.rs = this.stmt.executeQuery("show collation like 'gb18030_chinese_ci'"); - if (!this.rs.next()) { - return; - } + assumeTrue(this.rs.next(), "This test requires the server suporting gb18030 character set."); // phrases to check String[][] str = new String[][] { @@ -405,4 +413,858 @@ public void testGB18030() throws Exception { st.executeUpdate("DROP TABLE IF EXISTS testGB18030"); con.close(); } + + /** + * Tests the ability to set the connection collation via properties. + * + * @throws Exception + */ + @Test + public void testNonStandardConnectionCollation() throws Exception { + String collationToSet = "utf8_bin"; + String characterSet = "utf-8"; + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.connectionCollation.getKeyName(), collationToSet); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), characterSet); + + Connection collConn = null; + Statement collStmt = null; + ResultSet collRs = null; + + try { + collConn = getConnectionWithProps(props); + + collStmt = collConn.createStatement(); + + collRs = collStmt.executeQuery("SHOW VARIABLES LIKE 'collation_connection'"); + + assertTrue(collRs.next()); + assertTrue(collationToSet.equalsIgnoreCase(collRs.getString(2))); + } finally { + if (collConn != null) { + collConn.close(); + } + } + } + + @Test + public void testCharsets() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); + + Connection utfConn = getConnectionWithProps(props); + + this.stmt = utfConn.createStatement(); + + createTable("t1", "(comment CHAR(32) ASCII NOT NULL,koi8_ru_f CHAR(32) CHARACTER SET koi8r NOT NULL) CHARSET=latin5"); + + this.stmt.executeUpdate("ALTER TABLE t1 CHANGE comment comment CHAR(32) CHARACTER SET latin2 NOT NULL"); + this.stmt.executeUpdate("ALTER TABLE t1 ADD latin5_f CHAR(32) NOT NULL"); + this.stmt.executeUpdate("ALTER TABLE t1 CHARSET=latin2"); + this.stmt.executeUpdate("ALTER TABLE t1 ADD latin2_f CHAR(32) NOT NULL"); + this.stmt.executeUpdate("ALTER TABLE t1 DROP latin2_f, DROP latin5_f"); + + this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) VALUES ('a','LAT SMALL A')"); + /* + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('b','LAT SMALL B')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('c','LAT SMALL C')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('d','LAT SMALL D')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('e','LAT SMALL E')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('f','LAT SMALL F')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('g','LAT SMALL G')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('h','LAT SMALL H')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('i','LAT SMALL I')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('j','LAT SMALL J')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('k','LAT SMALL K')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('l','LAT SMALL L')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('m','LAT SMALL M')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('n','LAT SMALL N')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('o','LAT SMALL O')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('p','LAT SMALL P')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('q','LAT SMALL Q')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('r','LAT SMALL R')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('s','LAT SMALL S')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('t','LAT SMALL T')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('u','LAT SMALL U')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('v','LAT SMALL V')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('w','LAT SMALL W')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('x','LAT SMALL X')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('y','LAT SMALL Y')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('z','LAT SMALL Z')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('A','LAT CAPIT A')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('B','LAT CAPIT B')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('C','LAT CAPIT C')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('D','LAT CAPIT D')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('E','LAT CAPIT E')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('F','LAT CAPIT F')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('G','LAT CAPIT G')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('H','LAT CAPIT H')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('I','LAT CAPIT I')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('J','LAT CAPIT J')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('K','LAT CAPIT K')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('L','LAT CAPIT L')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('M','LAT CAPIT M')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('N','LAT CAPIT N')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('O','LAT CAPIT O')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('P','LAT CAPIT P')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('Q','LAT CAPIT Q')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('R','LAT CAPIT R')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('S','LAT CAPIT S')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('T','LAT CAPIT T')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('U','LAT CAPIT U')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('V','LAT CAPIT V')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('W','LAT CAPIT W')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('X','LAT CAPIT X')"); this.stmt.executeUpdate("INSERT + * INTO t1 (koi8_ru_f,comment) VALUES ('Y','LAT CAPIT Y')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES ('Z','LAT CAPIT Z')"); + */ + + String cyrillicSmallA = "\u0430"; + this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) VALUES ('" + cyrillicSmallA + "','CYR SMALL A')"); + + /* + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL BE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL VE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL GE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL DE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL IE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL IO')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL ZHE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL ZE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL I')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL KA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL EL')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL EM')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL EN')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL O')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL PE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL ER')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL ES')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL TE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL U')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL EF')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL HA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL TSE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL CHE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL SHA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL SCHA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL HARD SIGN')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL YERU')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL SOFT SIGN')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL E')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL YU')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR SMALL YA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT A')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT BE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT VE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT GE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT DE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT IE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT IO')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT ZHE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT ZE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT I')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT KA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT EL')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT EM')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT EN')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT O')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT PE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT ER')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT ES')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT TE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT U')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT EF')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT HA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT TSE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT CHE')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT SHA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT SCHA')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT HARD SIGN')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT YERU')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT SOFT SIGN')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT E')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT YU')"); + * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) + * VALUES (_koi8r'?��','CYR CAPIT YA')"); + */ + + this.stmt.executeUpdate("ALTER TABLE t1 ADD utf8_f CHAR(32) CHARACTER SET utf8 NOT NULL"); + this.stmt.executeUpdate("UPDATE t1 SET utf8_f=CONVERT(koi8_ru_f USING utf8)"); + this.stmt.executeUpdate("SET CHARACTER SET koi8r"); + // this.stmt.executeUpdate("SET CHARACTER SET UTF8"); + this.rs = this.stmt.executeQuery("SELECT * FROM t1"); + + ResultSetMetaData rsmd = this.rs.getMetaData(); + + int numColumns = rsmd.getColumnCount(); + + for (int i = 0; i < numColumns; i++) { + System.out.print(rsmd.getColumnName(i + 1)); + System.out.print("\t\t"); + } + + System.out.println(); + + while (this.rs.next()) { + System.out.println(this.rs.getString(1) + "\t\t" + this.rs.getString(2) + "\t\t" + this.rs.getString(3)); + + if (this.rs.getString(1).equals("CYR SMALL A")) { + this.rs.getString(2); + } + } + + System.out.println(); + + this.stmt.executeUpdate("SET NAMES utf8"); + this.rs = this.stmt.executeQuery("SELECT _koi8r 0xC1;"); + + rsmd = this.rs.getMetaData(); + + numColumns = rsmd.getColumnCount(); + + for (int i = 0; i < numColumns; i++) { + System.out.print(rsmd.getColumnName(i + 1)); + System.out.print("\t\t"); + } + + System.out.println(); + + while (this.rs.next()) { + System.out.println(this.rs.getString(1).equals("\u0430") + "\t\t"); + System.out.println(new String(this.rs.getBytes(1), "KOI8_R")); + + } + + char[] c = new char[] { 0xd0b0 }; + + System.out.println(new String(c)); + System.out.println("\u0430"); + } + + /** + * Tests if the driver configures character sets correctly for 4.1.x servers. Requires that the 'admin connection' is configured, as this test needs to + * create/drop databases. + * + * @throws Exception + */ + @Test + public void testCollation41() throws Exception { + Map charsetsAndCollations = getCharacterSetsAndCollations(); + charsetsAndCollations.remove("latin7"); // Maps to multiple Java + // charsets + charsetsAndCollations.remove("ucs2"); // can't be used as a + // connection charset + + for (String charsetName : charsetsAndCollations.keySet()) { + String enc = ((MysqlConnection) this.conn).getSession().getServerSession().getCharsetSettings().getJavaEncodingForMysqlCharset(charsetName); + if (enc == null) { + continue; + } + Connection charsetConn = null; + Statement charsetStmt = null; + + try { + System.out.print("Testing character set " + charsetName); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), enc); + + System.out.println(", Java encoding " + enc); + + charsetConn = getConnectionWithProps(props); + + charsetStmt = charsetConn.createStatement(); + + charsetStmt.executeUpdate("DROP DATABASE IF EXISTS testCollation41"); + charsetStmt.executeUpdate("DROP TABLE IF EXISTS testCollation41"); + charsetStmt.executeUpdate("CREATE DATABASE testCollation41 DEFAULT CHARACTER SET " + charsetName); + charsetStmt.close(); + + charsetConn.setCatalog("testCollation41"); + + // We've switched catalogs, so we need to recreate the + // statement to pick this up... + charsetStmt = charsetConn.createStatement(); + + StringBuilder createTableCommand = new StringBuilder("CREATE TABLE testCollation41(field1 VARCHAR(255), field2 INT)"); + + charsetStmt.executeUpdate(createTableCommand.toString()); + + charsetStmt.executeUpdate("INSERT INTO testCollation41 VALUES ('abc', 0)"); + + int updateCount = charsetStmt.executeUpdate("UPDATE testCollation41 SET field2=1 WHERE field1='abc'"); + assertTrue(updateCount == 1); + } finally { + if (charsetStmt != null) { + charsetStmt.executeUpdate("DROP TABLE IF EXISTS testCollation41"); + charsetStmt.executeUpdate("DROP DATABASE IF EXISTS testCollation41"); + charsetStmt.close(); + } + + if (charsetConn != null) { + charsetConn.close(); + } + } + } + } + + private Map getCharacterSetsAndCollations() throws Exception { + Map charsetsToLoad = new HashMap<>(); + + try { + this.rs = this.stmt.executeQuery("SHOW character set"); + + while (this.rs.next()) { + charsetsToLoad.put(this.rs.getString("Charset"), this.rs.getString("Default collation")); + } + + // + // These don't have mappings in Java... + // + charsetsToLoad.remove("swe7"); + charsetsToLoad.remove("hp8"); + charsetsToLoad.remove("dec8"); + charsetsToLoad.remove("koi8u"); + charsetsToLoad.remove("keybcs2"); + charsetsToLoad.remove("geostd8"); + charsetsToLoad.remove("armscii8"); + } finally { + if (this.rs != null) { + this.rs.close(); + } + } + + return charsetsToLoad; + } + + @Test + public void testCSC5765() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); + props.setProperty(PropertyKey.characterSetResults.getKeyName(), "utf8"); + props.setProperty(PropertyKey.connectionCollation.getKeyName(), "utf8_bin"); + + Connection utf8Conn = null; + + try { + utf8Conn = getConnectionWithProps(props); + this.rs = utf8Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'character_%'"); + while (this.rs.next()) { + System.out.println(this.rs.getString(1) + " = " + this.rs.getString(2)); + } + + this.rs = utf8Conn.createStatement().executeQuery("SHOW VARIABLES LIKE 'collation_%'"); + while (this.rs.next()) { + System.out.println(this.rs.getString(1) + " = " + this.rs.getString(2)); + } + } finally { + if (utf8Conn != null) { + utf8Conn.close(); + } + } + } + + /** + * These two charsets have different names depending on version of MySQL server. + * + * @throws Exception + */ + @Test + public void testNewCharsetsConfiguration() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "EUC_KR"); + getConnectionWithProps(props).close(); + + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "KOI8_R"); + getConnectionWithProps(props).close(); + } + + /** + * Tests that 'latin1' character conversion works correctly. + * + * @throws Exception + */ + @Test + public void testLatin1Encoding() throws Exception { + char[] latin1Charset = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, + 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, + 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, + 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, + 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, + 0x007C, 0x007D, 0x007E, 0x007F, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, + 0x008E, 0x008F, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1, + 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, + 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, + 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, + 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF }; + + String latin1String = new String(latin1Charset); + Connection latin1Conn = null; + PreparedStatement pStmt = null; + + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "cp1252"); + latin1Conn = getConnectionWithProps(props); + + createTable("latin1RegressTest", "(stringField TEXT)"); + + pStmt = latin1Conn.prepareStatement("INSERT INTO latin1RegressTest VALUES (?)"); + pStmt.setString(1, latin1String); + pStmt.executeUpdate(); + + ((com.mysql.cj.jdbc.JdbcConnection) latin1Conn).getPropertySet().getProperty(PropertyKey.traceProtocol).setValue(true); + + this.rs = latin1Conn.createStatement().executeQuery("SELECT * FROM latin1RegressTest"); + ((com.mysql.cj.jdbc.JdbcConnection) latin1Conn).getPropertySet().getProperty(PropertyKey.traceProtocol).setValue(false); + + this.rs.next(); + + String retrievedString = this.rs.getString(1); + + System.out.println(latin1String); + System.out.println(retrievedString); + + if (!retrievedString.equals(latin1String)) { + int stringLength = Math.min(retrievedString.length(), latin1String.length()); + + for (int i = 0; i < stringLength; i++) { + char rChar = retrievedString.charAt(i); + char origChar = latin1String.charAt(i); + + assertFalse((rChar != '?') && (rChar != origChar), + "characters differ at position " + i + "'" + rChar + "' retrieved from database, original char was '" + origChar + "'"); + } + } + } finally { + if (latin1Conn != null) { + latin1Conn.close(); + } + } + } + + /** + * Tests that the 0x5c escaping works (we didn't use to have this). + * + * @throws Exception + */ + @Test + public void testSjis5c() throws Exception { + byte[] origByteStream = new byte[] { (byte) 0x95, (byte) 0x5c, (byte) 0x8e, (byte) 0x96 }; + + // + // Print the hex values of the string + // + StringBuilder bytesOut = new StringBuilder(); + + for (int i = 0; i < origByteStream.length; i++) { + bytesOut.append(Integer.toHexString(origByteStream[i] & 255)); + bytesOut.append(" "); + } + + System.out.println(bytesOut.toString()); + + String origString = new String(origByteStream, "SJIS"); + byte[] newByteStream = StringUtils.getBytes(origString, "SJIS"); + + // + // Print the hex values of the string (should have an extra 0x5c) + // + bytesOut = new StringBuilder(); + + for (int i = 0; i < newByteStream.length; i++) { + bytesOut.append(Integer.toHexString(newByteStream[i] & 255)); + bytesOut.append(" "); + } + + System.out.println(bytesOut.toString()); + + // + // Now, insert and retrieve the value from the database + // + Connection sjisConn = null; + Statement sjisStmt = null; + + try { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS"); + sjisConn = getConnectionWithProps(props); + + sjisStmt = sjisConn.createStatement(); + + this.rs = sjisStmt.executeQuery("SHOW VARIABLES LIKE 'character_set%'"); + + while (this.rs.next()) { + System.out.println(this.rs.getString(1) + " = " + this.rs.getString(2)); + } + + sjisStmt.executeUpdate("DROP TABLE IF EXISTS sjisTest"); + + sjisStmt.executeUpdate("CREATE TABLE sjisTest (field1 char(50)) DEFAULT CHARACTER SET SJIS"); + + this.pstmt = sjisConn.prepareStatement("INSERT INTO sjisTest VALUES (?)"); + this.pstmt.setString(1, origString); + this.pstmt.executeUpdate(); + + this.rs = sjisStmt.executeQuery("SELECT * FROM sjisTest"); + + while (this.rs.next()) { + byte[] testValueAsBytes = this.rs.getBytes(1); + + bytesOut = new StringBuilder(); + + for (int i = 0; i < testValueAsBytes.length; i++) { + bytesOut.append(Integer.toHexString(testValueAsBytes[i] & 255)); + bytesOut.append(" "); + } + + System.out.println("Value retrieved from database: " + bytesOut.toString()); + + String testValue = this.rs.getString(1); + + assertTrue(testValue.equals(origString)); + } + } finally { + this.stmt.executeUpdate("DROP TABLE IF EXISTS sjisTest"); + } + } + + /** + * Tests that UTF-8 character conversion works correctly. + * + * @throws Exception + */ + @Test + public void testUtf8Encoding() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF8"); + props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); + + Connection utfConn = DriverManager.getConnection(dbUrl, props); + testConversionForString("UTF8", utfConn, "\u043c\u0438\u0445\u0438"); + } + + @Test + public void testUtf8Encoding2() throws Exception { + String field1 = "K��sel"; + String field2 = "B�b"; + byte[] field1AsBytes = field1.getBytes("utf-8"); + byte[] field2AsBytes = field2.getBytes("utf-8"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF8"); + + Connection utfConn = DriverManager.getConnection(dbUrl, props); + Statement utfStmt = utfConn.createStatement(); + + try { + utfStmt.executeUpdate("DROP TABLE IF EXISTS testUtf8"); + utfStmt.executeUpdate("CREATE TABLE testUtf8 (field1 varchar(32), field2 varchar(32)) CHARACTER SET UTF8"); + utfStmt.executeUpdate("INSERT INTO testUtf8 VALUES ('" + field1 + "','" + field2 + "')"); + + PreparedStatement pStmt = utfConn.prepareStatement("INSERT INTO testUtf8 VALUES (?, ?)"); + pStmt.setString(1, field1); + pStmt.setString(2, field2); + pStmt.executeUpdate(); + + this.rs = utfStmt.executeQuery("SELECT * FROM testUtf8"); + assertTrue(this.rs.next()); + + // Compare results stored using direct statement + // Compare to original string + assertTrue(field1.equals(this.rs.getString(1))); + assertTrue(field2.equals(this.rs.getString(2))); + + // Compare byte-for-byte, ignoring encoding + assertTrue(bytesAreSame(field1AsBytes, this.rs.getBytes(1))); + assertTrue(bytesAreSame(field2AsBytes, this.rs.getBytes(2))); + + assertTrue(this.rs.next()); + + // Compare to original string + assertTrue(field1.equals(this.rs.getString(1))); + assertTrue(field2.equals(this.rs.getString(2))); + + // Compare byte-for-byte, ignoring encoding + assertTrue(bytesAreSame(field1AsBytes, this.rs.getBytes(1))); + assertTrue(bytesAreSame(field2AsBytes, this.rs.getBytes(2))); + } finally { + utfStmt.executeUpdate("DROP TABLE IF EXISTS testUtf8"); + } + } + + private boolean bytesAreSame(byte[] byte1, byte[] byte2) { + if (byte1.length != byte2.length) { + return false; + } + + for (int i = 0; i < byte1.length; i++) { + if (byte1[i] != byte2[i]) { + return false; + } + } + + return true; + } + + private void testConversionForString(String charsetName, Connection convConn, String charsToTest) throws Exception { + PreparedStatement pStmt = null; + + this.stmt = convConn.createStatement(); + + createTable("charConvTest_" + charsetName, "(field1 CHAR(50) CHARACTER SET " + charsetName + ")"); + + this.stmt.executeUpdate("INSERT INTO charConvTest_" + charsetName + " VALUES ('" + charsToTest + "')"); + pStmt = convConn.prepareStatement("INSERT INTO charConvTest_" + charsetName + " VALUES (?)"); + pStmt.setString(1, charsToTest); + pStmt.executeUpdate(); + this.rs = this.stmt.executeQuery("SELECT * FROM charConvTest_" + charsetName); + + assertTrue(this.rs.next()); + + String testValue = this.rs.getString(1); + System.out.println(testValue); + assertTrue(testValue.equals(charsToTest)); + + } + + @Test + public void testCsc4194() throws Exception { + try { + "".getBytes("Windows-31J"); + } catch (UnsupportedEncodingException ex) { + assumeFalse(true, "Test requires JVM with Windows-31J support."); + } + + Connection sjisConn = null; + Connection windows31JConn = null; + + try { + String tableNameText = "testCsc4194Text"; + String tableNameBlob = "testCsc4194Blob"; + + createTable(tableNameBlob, "(field1 BLOB)"); + String charset = ""; + + charset = " CHARACTER SET cp932"; + + createTable(tableNameText, "(field1 TEXT)" + charset); + + Properties windows31JProps = new Properties(); + windows31JProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + windows31JProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + windows31JProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "Windows-31J"); + + windows31JConn = getConnectionWithProps(windows31JProps); + testCsc4194InsertCheckBlob(windows31JConn, tableNameBlob); + + testCsc4194InsertCheckText(windows31JConn, tableNameText, "Windows-31J"); + + Properties sjisProps = new Properties(); + sjisProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + sjisProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + sjisProps.setProperty(PropertyKey.characterEncoding.getKeyName(), "sjis"); + + sjisConn = getConnectionWithProps(sjisProps); + testCsc4194InsertCheckBlob(sjisConn, tableNameBlob); + testCsc4194InsertCheckText(sjisConn, tableNameText, "Windows-31J"); + + } finally { + + if (windows31JConn != null) { + windows31JConn.close(); + } + + if (sjisConn != null) { + sjisConn.close(); + } + } + } + + private void testCsc4194InsertCheckBlob(Connection c, String tableName) throws Exception { + byte[] bArray = new byte[] { (byte) 0xac, (byte) 0xed, (byte) 0x00, (byte) 0x05 }; + + PreparedStatement testStmt = c.prepareStatement("INSERT INTO " + tableName + " VALUES (?)"); + testStmt.setBytes(1, bArray); + testStmt.executeUpdate(); + + this.rs = c.createStatement().executeQuery("SELECT field1 FROM " + tableName); + assertTrue(this.rs.next()); + assertEquals(getByteArrayString(bArray), getByteArrayString(this.rs.getBytes(1))); + this.rs.close(); + } + + private void testCsc4194InsertCheckText(Connection c, String tableName, String encoding) throws Exception { + byte[] kabuInShiftJIS = { (byte) 0x87, // a double-byte charater("kabu") in Shift JIS + (byte) 0x8a, }; + + String expected = new String(kabuInShiftJIS, encoding); + PreparedStatement testStmt = c.prepareStatement("INSERT INTO " + tableName + " VALUES (?)"); + testStmt.setString(1, expected); + testStmt.executeUpdate(); + + this.rs = c.createStatement().executeQuery("SELECT field1 FROM " + tableName); + assertTrue(this.rs.next()); + assertEquals(expected, this.rs.getString(1)); + this.rs.close(); + } + + private String getByteArrayString(byte[] ba) { + StringBuilder buffer = new StringBuilder(); + if (ba != null) { + for (int i = 0; i < ba.length; i++) { + buffer.append("0x" + Integer.toHexString(ba[i] & 0xff) + " "); + } + } else { + buffer.append("null"); + } + return buffer.toString(); + } + + @Test + public void testCodePage1252() throws Exception { + /* + * from + * ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/ + * CP1252.TXT + * + * 0x80 0x20AC #EURO SIGN 0x81 #UNDEFINED 0x82 0x201A #SINGLE LOW-9 + * QUOTATION MARK 0x83 0x0192 #LATIN SMALL LETTER F WITH HOOK 0x84 + * 0x201E #DOUBLE LOW-9 QUOTATION MARK 0x85 0x2026 #HORIZONTAL + * ELLIPSIS 0x86 0x2020 #DAGGER 0x87 0x2021 #DOUBLE DAGGER 0x88 + * 0x02C6 #MODIFIER LETTER CIRCUMFLEX ACCENT 0x89 0x2030 #PER MILLE + * SIGN 0x8A 0x0160 #LATIN CAPITAL LETTER S WITH CARON 0x8B 0x2039 + * #SINGLE LEFT-POINTING ANGLE QUOTATION MARK 0x8C 0x0152 #LATIN + * CAPITAL LIGATURE OE 0x8D #UNDEFINED 0x8E 0x017D #LATIN CAPITAL + * LETTER Z WITH CARON 0x8F #UNDEFINED 0x90 #UNDEFINED + */ + String codePage1252 = new String(new byte[] { (byte) 0x80, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, + (byte) 0x89, (byte) 0x8a, (byte) 0x8b, (byte) 0x8c, (byte) 0x8e }, "Cp1252"); + + System.out.println(codePage1252); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), "Cp1252"); + Connection cp1252Conn = getConnectionWithProps(props); + createTable("testCp1252", "(field1 varchar(32) CHARACTER SET latin1)"); + cp1252Conn.createStatement().executeUpdate("INSERT INTO testCp1252 VALUES ('" + codePage1252 + "')"); + this.rs = cp1252Conn.createStatement().executeQuery("SELECT field1 FROM testCp1252"); + this.rs.next(); + assertEquals(this.rs.getString(1), codePage1252); + } } diff --git a/src/test/java/testsuite/simple/ConnectionTest.java b/src/test/java/testsuite/simple/ConnectionTest.java index 30683d634..8a2b05b61 100644 --- a/src/test/java/testsuite/simple/ConnectionTest.java +++ b/src/test/java/testsuite/simple/ConnectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -36,6 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -61,7 +63,6 @@ import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; import java.sql.Savepoint; @@ -83,7 +84,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CharsetMappingWrapper; import com.mysql.cj.MysqlConnection; import com.mysql.cj.NativeSession; import com.mysql.cj.PreparedQuery; @@ -111,6 +112,7 @@ import com.mysql.cj.protocol.a.NativePacketHeader; import com.mysql.cj.protocol.a.NativePacketPayload; import com.mysql.cj.protocol.a.NativeProtocol; +import com.mysql.cj.protocol.a.NativeServerSession; import com.mysql.cj.protocol.a.SimplePacketReader; import com.mysql.cj.protocol.a.SimplePacketSender; import com.mysql.cj.protocol.a.TimeTrackingPacketReader; @@ -202,62 +204,63 @@ public void testCatalog() throws Exception { public void testClusterConnection() throws Exception { String url = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_cluster); - if ((url != null) && (url.length() > 0)) { - Object versionNumObj = getSingleValueWithQuery("SHOW VARIABLES LIKE 'version'"); + assumeTrue(url != null && url.length() > 0, + "This test requires a two-node cluster URL specified in " + PropertyDefinitions.SYSP_testsuite_url_cluster + " system property"); - if ((versionNumObj != null) && (versionNumObj.toString().indexOf("cluster") != -1)) { - Connection clusterConn = null; - Statement clusterStmt = null; + Object versionNumObj = getSingleValueWithQuery("SHOW VARIABLES LIKE 'version'"); - try { - clusterConn = new NonRegisteringDriver().connect(url, null); + if ((versionNumObj != null) && (versionNumObj.toString().indexOf("cluster") != -1)) { + Connection clusterConn = null; + Statement clusterStmt = null; - clusterStmt = clusterConn.createStatement(); - clusterStmt.executeUpdate("DROP TABLE IF EXISTS testClusterConn"); - clusterStmt.executeUpdate("CREATE TABLE testClusterConn (field1 INT) ENGINE=ndbcluster"); - clusterStmt.executeUpdate("INSERT INTO testClusterConn VALUES (1)"); + try { + clusterConn = new NonRegisteringDriver().connect(url, null); - clusterConn.setAutoCommit(false); + clusterStmt = clusterConn.createStatement(); + clusterStmt.executeUpdate("DROP TABLE IF EXISTS testClusterConn"); + clusterStmt.executeUpdate("CREATE TABLE testClusterConn (field1 INT) ENGINE=ndbcluster"); + clusterStmt.executeUpdate("INSERT INTO testClusterConn VALUES (1)"); + + clusterConn.setAutoCommit(false); - clusterStmt.execute("SELECT * FROM testClusterConn"); - clusterStmt.executeUpdate("UPDATE testClusterConn SET field1=4"); + clusterStmt.execute("SELECT * FROM testClusterConn"); + clusterStmt.executeUpdate("UPDATE testClusterConn SET field1=4"); - // Kill the connection - @SuppressWarnings("unused") - String connectionId = getSingleValueWithQuery("SELECT CONNECTION_ID()").toString(); + // Kill the connection + @SuppressWarnings("unused") + String connectionId = getSingleValueWithQuery("SELECT CONNECTION_ID()").toString(); - System.out.println("Please kill the MySQL server now and press return..."); - System.in.read(); + System.out.println("Please kill the MySQL server now and press return..."); + System.in.read(); - System.out.println("Waiting for TCP/IP timeout..."); - Thread.sleep(10); + System.out.println("Waiting for TCP/IP timeout..."); + Thread.sleep(10); - System.out.println("Attempting auto reconnect"); + System.out.println("Attempting auto reconnect"); - try { - clusterConn.setAutoCommit(true); - clusterConn.setAutoCommit(false); - } catch (SQLException sqlEx) { - System.out.println(sqlEx); - } + try { + clusterConn.setAutoCommit(true); + clusterConn.setAutoCommit(false); + } catch (SQLException sqlEx) { + System.out.println(sqlEx); + } - // - // Test that this 'new' connection is not read-only - // - clusterStmt.executeUpdate("UPDATE testClusterConn SET field1=5"); + // + // Test that this 'new' connection is not read-only + // + clusterStmt.executeUpdate("UPDATE testClusterConn SET field1=5"); - ResultSet rset = clusterStmt.executeQuery("SELECT * FROM testClusterConn WHERE field1=5"); + ResultSet rset = clusterStmt.executeQuery("SELECT * FROM testClusterConn WHERE field1=5"); - assertTrue(rset.next(), "One row should be returned"); - } finally { - if (clusterStmt != null) { - clusterStmt.executeUpdate("DROP TABLE IF EXISTS testClusterConn"); - clusterStmt.close(); - } + assertTrue(rset.next(), "One row should be returned"); + } finally { + if (clusterStmt != null) { + clusterStmt.executeUpdate("DROP TABLE IF EXISTS testClusterConn"); + clusterStmt.close(); + } - if (clusterConn != null) { - clusterConn.close(); - } + if (clusterConn != null) { + clusterConn.close(); } } } @@ -281,6 +284,8 @@ public void testDeadlockDetection() throws Exception { this.conn.setAutoCommit(false); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.includeInnodbStatusInDeadlockExceptions.getKeyName(), "true"); props.setProperty(PropertyKey.includeThreadDumpInDeadlockExceptions.getKeyName(), "true"); @@ -313,9 +318,7 @@ public void testDeadlockDetection() throws Exception { assertTrue(sqlEx.getErrorCode() == 1205); // Make sure INNODB Status is getting dumped into error message - if (sqlEx.getMessage().indexOf("PROCESS privilege") != -1) { - fail("This test requires user with process privilege"); - } + assertFalse(sqlEx.getMessage().indexOf("PROCESS privilege") != -1, "This test requires user with process privilege"); assertTrue(sqlEx.getMessage().indexOf("INNODB MONITOR") != -1, "Can't find INNODB MONITOR in:\n\n" + sqlEx.getMessage()); @@ -327,289 +330,6 @@ public void testDeadlockDetection() throws Exception { } } - @Test - public void testCharsets() throws Exception { - Properties props = new Properties(); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); - - Connection utfConn = getConnectionWithProps(props); - - this.stmt = utfConn.createStatement(); - - createTable("t1", "(comment CHAR(32) ASCII NOT NULL,koi8_ru_f CHAR(32) CHARACTER SET koi8r NOT NULL) CHARSET=latin5"); - - this.stmt.executeUpdate("ALTER TABLE t1 CHANGE comment comment CHAR(32) CHARACTER SET latin2 NOT NULL"); - this.stmt.executeUpdate("ALTER TABLE t1 ADD latin5_f CHAR(32) NOT NULL"); - this.stmt.executeUpdate("ALTER TABLE t1 CHARSET=latin2"); - this.stmt.executeUpdate("ALTER TABLE t1 ADD latin2_f CHAR(32) NOT NULL"); - this.stmt.executeUpdate("ALTER TABLE t1 DROP latin2_f, DROP latin5_f"); - - this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) VALUES ('a','LAT SMALL A')"); - /* - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('b','LAT SMALL B')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('c','LAT SMALL C')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('d','LAT SMALL D')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('e','LAT SMALL E')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('f','LAT SMALL F')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('g','LAT SMALL G')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('h','LAT SMALL H')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('i','LAT SMALL I')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('j','LAT SMALL J')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('k','LAT SMALL K')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('l','LAT SMALL L')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('m','LAT SMALL M')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('n','LAT SMALL N')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('o','LAT SMALL O')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('p','LAT SMALL P')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('q','LAT SMALL Q')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('r','LAT SMALL R')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('s','LAT SMALL S')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('t','LAT SMALL T')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('u','LAT SMALL U')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('v','LAT SMALL V')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('w','LAT SMALL W')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('x','LAT SMALL X')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('y','LAT SMALL Y')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('z','LAT SMALL Z')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('A','LAT CAPIT A')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('B','LAT CAPIT B')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('C','LAT CAPIT C')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('D','LAT CAPIT D')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('E','LAT CAPIT E')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('F','LAT CAPIT F')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('G','LAT CAPIT G')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('H','LAT CAPIT H')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('I','LAT CAPIT I')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('J','LAT CAPIT J')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('K','LAT CAPIT K')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('L','LAT CAPIT L')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('M','LAT CAPIT M')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('N','LAT CAPIT N')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('O','LAT CAPIT O')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('P','LAT CAPIT P')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('Q','LAT CAPIT Q')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('R','LAT CAPIT R')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('S','LAT CAPIT S')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('T','LAT CAPIT T')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('U','LAT CAPIT U')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('V','LAT CAPIT V')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('W','LAT CAPIT W')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('X','LAT CAPIT X')"); this.stmt.executeUpdate("INSERT - * INTO t1 (koi8_ru_f,comment) VALUES ('Y','LAT CAPIT Y')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES ('Z','LAT CAPIT Z')"); - */ - - String cyrillicSmallA = "\u0430"; - this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) VALUES ('" + cyrillicSmallA + "','CYR SMALL A')"); - - /* - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL BE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL VE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL GE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL DE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL IE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL IO')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL ZHE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL ZE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL I')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL KA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL EL')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL EM')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL EN')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL O')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL PE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL ER')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL ES')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL TE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL U')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL EF')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL HA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL TSE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL CHE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL SHA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL SCHA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL HARD SIGN')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL YERU')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL SOFT SIGN')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL E')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL YU')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR SMALL YA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT A')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT BE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT VE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT GE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT DE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT IE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT IO')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT ZHE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT ZE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT I')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT KA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT EL')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT EM')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT EN')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT O')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT PE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT ER')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT ES')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT TE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT U')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT EF')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT HA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT TSE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT CHE')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT SHA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT SCHA')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT HARD SIGN')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT YERU')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT SOFT SIGN')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT E')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT YU')"); - * this.stmt.executeUpdate("INSERT INTO t1 (koi8_ru_f,comment) - * VALUES (_koi8r'?��','CYR CAPIT YA')"); - */ - - this.stmt.executeUpdate("ALTER TABLE t1 ADD utf8_f CHAR(32) CHARACTER SET utf8 NOT NULL"); - this.stmt.executeUpdate("UPDATE t1 SET utf8_f=CONVERT(koi8_ru_f USING utf8)"); - this.stmt.executeUpdate("SET CHARACTER SET koi8r"); - // this.stmt.executeUpdate("SET CHARACTER SET UTF8"); - this.rs = this.stmt.executeQuery("SELECT * FROM t1"); - - ResultSetMetaData rsmd = this.rs.getMetaData(); - - int numColumns = rsmd.getColumnCount(); - - for (int i = 0; i < numColumns; i++) { - System.out.print(rsmd.getColumnName(i + 1)); - System.out.print("\t\t"); - } - - System.out.println(); - - while (this.rs.next()) { - System.out.println(this.rs.getString(1) + "\t\t" + this.rs.getString(2) + "\t\t" + this.rs.getString(3)); - - if (this.rs.getString(1).equals("CYR SMALL A")) { - this.rs.getString(2); - } - } - - System.out.println(); - - this.stmt.executeUpdate("SET NAMES utf8"); - this.rs = this.stmt.executeQuery("SELECT _koi8r 0xC1;"); - - rsmd = this.rs.getMetaData(); - - numColumns = rsmd.getColumnCount(); - - for (int i = 0; i < numColumns; i++) { - System.out.print(rsmd.getColumnName(i + 1)); - System.out.print("\t\t"); - } - - System.out.println(); - - while (this.rs.next()) { - System.out.println(this.rs.getString(1).equals("\u0430") + "\t\t"); - System.out.println(new String(this.rs.getBytes(1), "KOI8_R")); - - } - - char[] c = new char[] { 0xd0b0 }; - - System.out.println(new String(c)); - System.out.println("\u0430"); - } - /** * Tests isolation level functionality * @@ -707,43 +427,11 @@ public void testSavepoint() throws Exception { } } - /** - * Tests the ability to set the connection collation via properties. - * - * @throws Exception - */ - @Test - public void testNonStandardConnectionCollation() throws Exception { - String collationToSet = "utf8_bin"; - String characterSet = "utf-8"; - - Properties props = new Properties(); - props.setProperty(PropertyKey.connectionCollation.getKeyName(), collationToSet); - props.setProperty(PropertyKey.characterEncoding.getKeyName(), characterSet); - - Connection collConn = null; - Statement collStmt = null; - ResultSet collRs = null; - - try { - collConn = getConnectionWithProps(props); - - collStmt = collConn.createStatement(); - - collRs = collStmt.executeQuery("SHOW VARIABLES LIKE 'collation_connection'"); - - assertTrue(collRs.next()); - assertTrue(collationToSet.equalsIgnoreCase(collRs.getString(2))); - } finally { - if (collConn != null) { - collConn.close(); - } - } - } - @Test public void testDumpQueriesOnException() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.dumpQueriesOnException.getKeyName(), "true"); String bogusSQL = "SELECT 1 TO BAZ"; Connection dumpConn = getConnectionWithProps(props); @@ -803,6 +491,8 @@ public void testConnectionPropertiesTransform() throws Exception { */ @Test public void testLocalInfileWithUrl() throws Exception { + assumeTrue(supportsLoadLocalInfile(this.stmt), "This test requires the server started with --local-infile=ON"); + File infile = File.createTempFile("foo", "txt"); infile.deleteOnExit(); String url = infile.toURI().toURL().toExternalForm(); @@ -814,18 +504,20 @@ public void testLocalInfileWithUrl() throws Exception { createTable("testLocalInfileWithUrl", "(field1 LONGTEXT)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); props.setProperty(PropertyKey.allowUrlInLocalInfile.getKeyName(), "true"); Connection loadConn = getConnectionWithProps(props); Statement loadStmt = loadConn.createStatement(); - String charset = " CHARACTER SET " + CharsetMapping.getMysqlCharsetForJavaEncoding( + String charset = " CHARACTER SET " + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding( ((MysqlConnection) loadConn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), ((JdbcConnection) loadConn).getServerVersion()); try { - loadStmt.executeQuery("LOAD DATA LOCAL INFILE '" + url + "' INTO TABLE testLocalInfileWithUrl" + charset); + loadStmt.execute("LOAD DATA LOCAL INFILE '" + url + "' INTO TABLE testLocalInfileWithUrl" + charset); } catch (SQLException sqlEx) { sqlEx.printStackTrace(); @@ -857,7 +549,7 @@ public void testLocalInfileWithUrl() throws Exception { assertTrue("Test".equals(this.rs.getString(1))); try { - loadStmt.executeQuery("LOAD DATA LOCAL INFILE 'foo:///' INTO TABLE testLocalInfileWithUrl" + charset); + loadStmt.execute("LOAD DATA LOCAL INFILE 'foo:///' INTO TABLE testLocalInfileWithUrl" + charset); } catch (SQLException sqlEx) { assertTrue(sqlEx.getMessage() != null); assertTrue(sqlEx.getMessage().indexOf("FileNotFoundException") != -1); @@ -866,6 +558,8 @@ public void testLocalInfileWithUrl() throws Exception { @Test public void testLocalInfileDisabled() throws Exception { + assumeTrue(supportsLoadLocalInfile(this.stmt), "This test requires the server started with --local-infile=ON"); + createTable("testLocalInfileDisabled", "(field1 varchar(255))"); File infile = File.createTempFile("foo", "txt"); @@ -885,6 +579,8 @@ public void testLocalInfileDisabled() throws Exception { // Test load local infile support enabled via client capabilities but disabled on the connector. Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); Connection loadConn = getConnectionWithProps(props); @@ -910,6 +606,8 @@ public void testLocalInfileDisabled() throws Exception { public void testServerConfigurationCache() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheServerConfiguration.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); @@ -951,6 +649,8 @@ public void testServerConfigurationCache() throws Exception { public void testUseLocalSessionState() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useLocalSessionState.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); @@ -983,6 +683,8 @@ public void testFailoverConnection() throws Exception { if (!isServerRunningOnWindows()) { // windows sockets don't work for this test Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.failOverReadOnly.getKeyName(), "false"); @@ -1003,12 +705,12 @@ public void testFailoverConnection() throws Exception { Thread.sleep(3000); - try { - failoverConnection.createStatement().execute("SELECT 1"); - fail("We expect an exception here, because the connection should be gone until the reconnect code picks it up again"); - } catch (SQLException sqlEx) { - // do-nothing - } + Connection localFailoverConnection = failoverConnection; + assertThrows("We expect an exception here, because the connection should be gone until the reconnect code picks it up again", + SQLException.class, () -> { + localFailoverConnection.createStatement().execute("SELECT 1"); + return null; + }); // Tickle re-connect @@ -1056,6 +758,8 @@ public Void call() throws Exception { @Test public void testDontTrackOpenResources() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.dontTrackOpenResources.getKeyName(), "true"); Connection noTrackConn = null; @@ -1097,22 +801,22 @@ public void testDontTrackOpenResources() throws Exception { @Test public void testPing() throws SQLException { - Connection conn2 = getConnectionWithProps((String) null); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection conn2 = getConnectionWithProps(props); ((com.mysql.cj.jdbc.JdbcConnection) conn2).ping(); conn2.close(); - try { + assertThrows(SQLException.class, () -> { ((com.mysql.cj.jdbc.JdbcConnection) conn2).ping(); - fail("Should have failed with an exception"); - } catch (SQLException sqlEx) { - // ignore for now - } + return null; + }); // // This feature caused BUG#8975, so check for that too! - Properties props = new Properties(); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); getConnectionWithProps(props); @@ -1125,6 +829,8 @@ public void testSessionVariables() throws Exception { int newWaitTimeout = Integer.parseInt(getInitialWaitTimeout) + 10000; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.sessionVariables.getKeyName(), "wait_timeout=" + newWaitTimeout); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); @@ -1153,17 +859,16 @@ public void testCreateDatabaseIfNotExist() throws Exception { this.stmt.executeUpdate("DROP DATABASE IF EXISTS " + databaseName); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.createDatabaseIfNotExist.getKeyName(), "true"); props.setProperty(PropertyKey.DBNAME.getKeyName(), databaseName); Connection con = getConnectionWithProps(props); this.rs = this.stmt.executeQuery("show databases like '" + databaseName + "'"); - if (this.rs.next()) { - assertEquals(databaseName, this.rs.getString(1)); - } else { - fail("Database " + databaseName + " is not found."); - } + assertTrue(this.rs.next(), "Database " + databaseName + " is not found."); + assertEquals(databaseName, this.rs.getString(1)); con.createStatement().executeUpdate("DROP DATABASE IF EXISTS " + databaseName); } @@ -1177,6 +882,8 @@ public void testCreateDatabaseIfNotExist() throws Exception { public void testGatherPerfMetrics() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.autoReconnect.getKeyName(), "true"); props.setProperty(PropertyKey.logSlowQueries.getKeyName(), "true"); props.setProperty(PropertyKey.slowQueryThresholdMillis.getKeyName(), "2000"); @@ -1204,9 +911,8 @@ public void testGatherPerfMetrics() throws Exception { public void testUseCompress() throws Exception { this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); this.rs.next(); - if (this.rs.getInt(2) < 4 + 1024 * 1024 * 16 - 1) { - fail("You need to increase max_allowed_packet to at least " + (4 + 1024 * 1024 * 16 - 1) + " before running this test!"); - } + long defaultMaxAllowedPacket = this.rs.getInt(2); + boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < 4 + 1024 * 1024 * 32 - 1; if (versionMeetsMinimum(5, 6, 20) && !versionMeetsMinimum(5, 7)) { /* @@ -1218,19 +924,28 @@ public void testUseCompress() throws Exception { */ this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'innodb_log_file_size'"); this.rs.next(); - if (this.rs.getInt(2) < 1024 * 1024 * 32 * 10) { - fail("You need to increase innodb_log_file_size to at least " + (1024 * 1024 * 32 * 10) + " before running this test!"); - } + assumeFalse(this.rs.getInt(2) < 1024 * 1024 * 32 * 10, + "You need to increase innodb_log_file_size to at least " + (1024 * 1024 * 32 * 10) + " before running this test!"); } - testCompressionWith("false", 1024 * 1024 * 16 - 2); // no split - testCompressionWith("false", 1024 * 1024 * 16 - 1); // split with additional empty packet - testCompressionWith("false", 1024 * 1024 * 32); // big payload + try { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 33); + } + + testCompressionWith("false", 1024 * 1024 * 16 - 2); // no split + testCompressionWith("false", 1024 * 1024 * 16 - 1); // split with additional empty packet + testCompressionWith("false", 1024 * 1024 * 32); // big payload - testCompressionWith("true", 1024 * 1024 * 16 - 2 - 3); // no split, one compressed packet - testCompressionWith("true", 1024 * 1024 * 16 - 2 - 2); // no split, two compressed packets - testCompressionWith("true", 1024 * 1024 * 16 - 1); // split with additional empty packet, two compressed packets - testCompressionWith("true", 1024 * 1024 * 32); // big payload + testCompressionWith("true", 1024 * 1024 * 16 - 2 - 3); // no split, one compressed packet + testCompressionWith("true", 1024 * 1024 * 16 - 2 - 2); // no split, two compressed packets + testCompressionWith("true", 1024 * 1024 * 16 - 1); // split with additional empty packet, two compressed packets + testCompressionWith("true", 1024 * 1024 * 32); // big payload + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket); + } + } } /** @@ -1261,6 +976,8 @@ private void testCompressionWith(String useCompression, int maxPayloadSize) thro bOut.close(); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCompression.getKeyName(), useCompression); Connection conn1 = getConnectionWithProps(props); Statement stmt1 = conn1.createStatement(); @@ -1284,9 +1001,7 @@ private void testCompressionWith(String useCompression, int maxPayloadSize) thro int count = 0; while ((blobbyte = is.read()) > -1) { int filebyte = bIn.read(); - if (filebyte < 0 || filebyte != blobbyte) { - fail("Blob is not identical to initial data."); - } + assertFalse(filebyte < 0 || filebyte != blobbyte, "Blob is not identical to initial data."); count++; } assertEquals(requiredSize, count); @@ -1384,6 +1099,8 @@ public void run() { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.localSocketAddress.getKeyName(), addr.getHostAddress()); props.setProperty(PropertyKey.connectTimeout.getKeyName(), "2000"); getConnectionWithProps(props).close(); @@ -1410,6 +1127,8 @@ public void testUsageAdvisorTooLargeResultSet() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); props.setProperty(PropertyKey.resultSetSizeThreshold.getKeyName(), "4"); props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); @@ -1432,11 +1151,11 @@ public void testUsageAdvisorTooLargeResultSet() throws Exception { @Test public void testUseLocalSessionStateRollback() throws Exception { - if (!versionMeetsMinimum(5, 5, 0)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 5, 0), "MySQL 5.5+ is required to run this test."); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useLocalSessionState.getKeyName(), "true"); props.setProperty(PropertyKey.useLocalTransactionState.getKeyName(), "true"); props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); @@ -1517,6 +1236,8 @@ public void testCouplingOfCursorFetch() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // force the issue props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); fetchConn = getConnectionWithProps(props); @@ -1533,9 +1254,14 @@ public void testCouplingOfCursorFetch() throws Exception { @Test public void testInterfaceImplementation() throws Exception { - testInterfaceImplementation(getConnectionWithProps((Properties) null)); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + testInterfaceImplementation(getConnectionWithProps(props)); MysqlConnectionPoolDataSource cpds = new MysqlConnectionPoolDataSource(); cpds.setUrl(dbUrl); + cpds.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + cpds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); testInterfaceImplementation(cpds.getPooledConnection().getConnection()); } @@ -1601,16 +1327,25 @@ private void checkInterfaceImplemented(Method[] interfaceMethods, Class imple @Test public void testNonVerifyServerCert() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); - props.setProperty(PropertyKey.requireSSL.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); getConnectionWithProps(props); } @Test public void testSelfDestruct() throws Exception { - Connection selfDestructingConn = getConnectionWithProps("selfDestructOnPingMaxOperations=2"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.selfDestructOnPingMaxOperations.getKeyName(), "2"); + Connection selfDestructingConn = getConnectionWithProps(props); boolean failed = false; @@ -1630,13 +1365,13 @@ public void testSelfDestruct() throws Exception { } } - if (!failed) { - fail("Connection should've self-destructed"); - } + assertTrue(failed, "Connection should've self-destructed"); failed = false; - selfDestructingConn = getConnectionWithProps("selfDestructOnPingSecondsLifetime=1"); + props.remove(PropertyKey.selfDestructOnPingMaxOperations.getKeyName()); + props.setProperty(PropertyKey.selfDestructOnPingSecondsLifetime.getKeyName(), "1"); + selfDestructingConn = getConnectionWithProps(props); for (int i = 0; i < 20; i++) { selfDestructingConn.createStatement().execute("SELECT SLEEP(1)"); @@ -1654,9 +1389,7 @@ public void testSelfDestruct() throws Exception { } } - if (!failed) { - fail("Connection should've self-destructed"); - } + assertTrue(failed, "Connection should've self-destructed"); } @Test @@ -1665,7 +1398,11 @@ public void testLifecyleInterceptor() throws Exception { Connection liConn = null; try { - liConn = getConnectionWithProps("connectionLifecycleInterceptors=testsuite.simple.TestLifecycleInterceptor"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.connectionLifecycleInterceptors.getKeyName(), TestLifecycleInterceptor.class.getName()); + liConn = getConnectionWithProps(props); liConn.setAutoCommit(false); liConn.createStatement().executeUpdate("INSERT INTO testLifecycleInterceptor VALUES (1)"); @@ -1698,6 +1435,8 @@ public void testNewHostParsing() throws Exception { user != null ? user : "", password != null ? password : "", database); Properties props = getHostFreePropertiesFromTestsuiteUrl(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.remove(PropertyKey.USER.getKeyName()); props.remove(PropertyKey.PASSWORD.getKeyName()); props.remove(PropertyKey.DBNAME.getKeyName()); @@ -1711,18 +1450,38 @@ public void testNewHostParsing() throws Exception { @Test public void testCompression() throws Exception { - Connection compressedConn = getConnectionWithProps("useCompression=true,maxAllowedPacket=33554432"); - Statement compressedStmt = compressedConn.createStatement(); - compressedStmt.setFetchSize(Integer.MIN_VALUE); - this.rs = compressedStmt.executeQuery("select repeat('a', 256 * 256 * 256 - 5)"); + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); this.rs.next(); - String str = this.rs.getString(1); + long len = 33554432; + long defaultMaxAllowedPacket = this.rs.getInt(2); + boolean changeMaxAllowedPacket = defaultMaxAllowedPacket < len; + + try { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + 1024 * 1024 * 32); + } + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useCompression.getKeyName(), "true"); + props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "33554432"); + Connection compressedConn = getConnectionWithProps(props); + Statement compressedStmt = compressedConn.createStatement(); + compressedStmt.setFetchSize(Integer.MIN_VALUE); + this.rs = compressedStmt.executeQuery("select repeat('a', 256 * 256 * 256 - 5)"); + this.rs.next(); + String str = this.rs.getString(1); - assertEquals((256 * 256 * 256 - 5), str.length()); + assertEquals((256 * 256 * 256 - 5), str.length()); - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) != 'a') { - fail(); + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) != 'a') { + fail(); + } + } + } finally { + if (changeMaxAllowedPacket) { + this.stmt.executeUpdate("SET GLOBAL max_allowed_packet=" + defaultMaxAllowedPacket); } } } @@ -1740,11 +1499,16 @@ public void testIsLocal() throws Exception { @Test public void testReadOnly56() throws Exception { - if (!versionMeetsMinimum(5, 6, 5)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 6, 5), "MySQL 5.6.5+ is required to run this test."); + + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.profileSQL.getKeyName(), "true"); + props.setProperty(PropertyKey.logger.getKeyName(), BufferingLogger.class.getName()); + try { - Connection notLocalState = getConnectionWithProps("profileSQL=true,logger=" + BufferingLogger.class.getName()); + Connection notLocalState = getConnectionWithProps(props); for (int i = 0; i < 2; i++) { BufferingLogger.startLoggingToBuffer(); @@ -1762,7 +1526,8 @@ public void testReadOnly56() throws Exception { assertTrue(notLocalState.isReadOnly()); } - Connection localState = getConnectionWithProps("profileSQL=true,useLocalSessionState=true,logger=" + BufferingLogger.class.getName()); + props.setProperty(PropertyKey.useLocalSessionState.getKeyName(), "true"); + Connection localState = getConnectionWithProps(props); String s = versionMeetsMinimum(8, 0, 3) ? "@@session.transaction_read_only" : "@@session.tx_read_only"; @@ -1779,7 +1544,9 @@ public void testReadOnly56() throws Exception { assertTrue(BufferingLogger.getBuffer().toString().indexOf("select @@session." + s) == -1); } - Connection noOptimization = getConnectionWithProps("profileSQL=true,readOnlyPropagatesToServer=false,logger=" + BufferingLogger.class.getName()); + props.remove(PropertyKey.useLocalSessionState.getKeyName()); + props.setProperty(PropertyKey.readOnlyPropagatesToServer.getKeyName(), "false"); + Connection noOptimization = getConnectionWithProps(props); for (int i = 0; i < 2; i++) { BufferingLogger.startLoggingToBuffer(); @@ -1801,16 +1568,15 @@ public void testReadOnly56() throws Exception { */ @Test public void testIPv6() throws Exception { - if (!versionMeetsMinimum(5, 6)) { - return; - // this test could work with MySQL 5.5 but requires specific server configuration, e.g. "--bind-address=::" - } + assumeTrue(versionMeetsMinimum(5, 6), "MySQL 5.6+ is required to run this test."); // this test could work with MySQL 5.5 but requires specific server configuration, e.g. "--bind-address=::" String testUser = "testIPv6User"; createUser("'" + testUser + "'@'%'", "IDENTIFIED BY '" + testUser + "'"); this.stmt.execute("GRANT ALL ON *.* TO '" + testUser + "'@'%'"); Properties connProps = getHostFreePropertiesFromTestsuiteUrl(); + connProps.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + connProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); connProps.setProperty(PropertyKey.USER.getKeyName(), testUser); connProps.setProperty(PropertyKey.PASSWORD.getKeyName(), testUser); @@ -1845,11 +1611,8 @@ public void testIPv6() throws Exception { } if (!atLeastOne) { - if (isMysqlRunningLocally()) { - fail("None of the tested hosts have server sockets listening on the port " + port + "."); - } else { - System.err.println("None of the tested hosts have server sockets listening on the port " + port + "."); - } + assertFalse(isMysqlRunningLocally(), "None of the tested hosts have server sockets listening on the port " + port + "."); + System.err.println("None of the tested hosts have server sockets listening on the port " + port + "."); } } @@ -1904,7 +1667,9 @@ public Void call() throws Exception { public void testDriverConnectPropertiesPrecedence() throws Exception { assertThrows(SQLException.class, "Access denied for user 'dummy'@'[^']+' \\(using password: YES\\)", new Callable() { public Void call() throws Exception { - DriverManager.getConnection(BaseTestCase.dbUrl, "dummy", "dummy"); + DriverManager.getConnection( + (BaseTestCase.dbUrl.endsWith("?") ? BaseTestCase.dbUrl : BaseTestCase.dbUrl + "&") + "sslMode=DISABLED&allowPublicKeyRetrieval=true", + "dummy", "dummy"); return null; } }); @@ -1923,6 +1688,9 @@ public Void call() throws Exception { testUrl = testUrl.substring(0, b) + testUrl.substring(e, testUrl.length()); } + // disable SSL + testUrl = (testUrl.endsWith("?") ? testUrl : testUrl + "&") + "sslMode=DISABLED&allowPublicKeyRetrieval=true"; + Properties props = new Properties(); props.setProperty(PropertyKey.maxRows.getKeyName(), "123"); @@ -1993,6 +1761,8 @@ public void testEnableEscapeProcessing() throws Exception { boolean useServerPrepStmts = (tst & 0x4) != 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), TestEnableEscapeProcessingQueryInterceptor.class.getName()); props.setProperty(PropertyKey.enableEscapeProcessing.getKeyName(), Boolean.toString(enableEscapeProcessing)); props.setProperty(PropertyKey.processEscapeCodesForPrepStmts.getKeyName(), Boolean.toString(processEscapeCodesForPrepStmts)); @@ -2086,6 +1856,8 @@ public void testDecoratorsChain() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCompression.getKeyName(), "false"); props.setProperty(PropertyKey.maintainTimeStats.getKeyName(), "true"); props.setProperty(PropertyKey.traceProtocol.getKeyName(), "true"); @@ -2207,9 +1979,9 @@ public void testDecoratorsChain() throws Exception { */ @Test public void testUserRequireSSL() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Connection testConn; Statement testStmt; @@ -2227,7 +1999,7 @@ public void testUserRequireSSL() throws Exception { /* * No SSL. */ - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); assertThrows(SQLException.class, "Access denied for user '" + user + "'@.*", new Callable() { public Void call() throws Exception { @@ -2239,8 +2011,7 @@ public Void call() throws Exception { /* * SSL: no server certificate validation & no client certificate. */ - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); testConn = getConnectionWithProps(props); testStmt = testConn.createStatement(); this.rs = testStmt.executeQuery("SELECT CURRENT_USER()"); @@ -2251,7 +2022,7 @@ public Void call() throws Exception { /* * SSL: server certificate validation & no client certificate. */ - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.VERIFY_CA.name()); props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore"); props.setProperty(PropertyKey.trustCertificateKeyStoreType.getKeyName(), "JKS"); props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password"); @@ -2278,7 +2049,7 @@ public Void call() throws Exception { /* * SSL: no server certificate validation & client certificate. */ - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); props.remove(PropertyKey.trustCertificateKeyStoreUrl.getKeyName()); props.remove(PropertyKey.trustCertificateKeyStoreType.getKeyName()); props.remove(PropertyKey.trustCertificateKeyStorePassword.getKeyName()); @@ -2300,9 +2071,9 @@ public Void call() throws Exception { */ @Test public void testUserRequireX509() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Connection testConn; Statement testStmt; @@ -2320,7 +2091,7 @@ public void testUserRequireX509() throws Exception { /* * No SSL. */ - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); assertThrows(SQLException.class, "Access denied for user '" + user + "'@.*", new Callable() { public Void call() throws Exception { @@ -2332,8 +2103,7 @@ public Void call() throws Exception { /* * SSL: no server certificate validation & no client certificate. */ - props.setProperty(PropertyKey.useSSL.getKeyName(), "true"); - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); assertThrows(SQLException.class, "Access denied for user '" + user + "'@.*", new Callable() { public Void call() throws Exception { getConnectionWithProps(props); @@ -2344,7 +2114,7 @@ public Void call() throws Exception { /* * SSL: server certificate validation & no client certificate. */ - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "true"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.VERIFY_CA.name()); props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore"); props.setProperty(PropertyKey.trustCertificateKeyStoreType.getKeyName(), "JKS"); props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password"); @@ -2371,7 +2141,7 @@ public Void call() throws Exception { /* * SSL: no server certificate validation & client certificate. */ - props.setProperty(PropertyKey.verifyServerCertificate.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); props.remove(PropertyKey.trustCertificateKeyStoreUrl.getKeyName()); props.remove(PropertyKey.trustCertificateKeyStoreType.getKeyName()); props.remove(PropertyKey.trustCertificateKeyStorePassword.getKeyName()); @@ -2390,6 +2160,13 @@ public Void call() throws Exception { */ @Test public void testSslConnectionOptions() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + Connection testConn; JdbcPropertySet propSet; @@ -2454,6 +2231,13 @@ public void testSslConnectionOptions() throws Exception { */ @Test public void testFallbackToSystemTrustStore() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + Connection testConn; /* @@ -2538,9 +2322,9 @@ public void testFallbackToSystemTrustStore() throws Exception { */ @Test public void testFallbackToSystemKeyStore() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); final String user = "testFbToSysKS"; createUser(user, "IDENTIFIED BY 'password' REQUIRE X509"); @@ -2629,6 +2413,8 @@ public void testFallbackToSystemKeyStore() throws Exception { */ @Test public void testAllowLoadLocalInfileInPath() throws Exception { + assumeTrue(supportsLoadLocalInfile(this.stmt), "This test requires the server started with --local-infile=ON"); + Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir")); /* @@ -2680,6 +2466,8 @@ public void testAllowLoadLocalInfileInPath() throws Exception { createTable("testAllowLoadLocalInfileInPath", "(data VARCHAR(100))"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); // Default behavior: 'allowLoadLocalInfile' not set (false) & 'allowLoadLocalInfile' not set (NULL) & 'allowUrlInLocalInfile' not set (false). try (Connection testConn = getConnectionWithProps(props)) { @@ -2687,7 +2475,7 @@ public void testAllowLoadLocalInfileInPath() throws Exception { assertThrows(SQLSyntaxErrorException.class, versionMeetsMinimum(8, 0, 19) ? "Loading local data is disabled; this must be enabled on both the client and server sides" : "The used command is not allowed with this MySQL version", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } // 'allowLoadLocalInfile=false' & 'allowLoadLocalInfile' not set (NULL) & 'allowUrlInLocalInfile' not set (false). @@ -2697,7 +2485,7 @@ public void testAllowLoadLocalInfileInPath() throws Exception { assertThrows(SQLSyntaxErrorException.class, versionMeetsMinimum(8, 0, 19) ? "Loading local data is disabled; this must be enabled on both the client and server sides" : "The used command is not allowed with this MySQL version", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } // 'allowLoadLocalInfile=true' & 'allowLoadLocalInfile' not set or set with any value & 'allowUrlInLocalInfile' not set (false). @@ -2705,31 +2493,31 @@ public void testAllowLoadLocalInfileInPath() throws Exception { props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), ""); // Empty dir name. try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), " "); // Dir name with spaces. try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir1.toString() + File.separator + "sub_12"); // Non-existing dir. try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir2.toString()); // File not in the dir. try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } @@ -2759,44 +2547,44 @@ public void testAllowLoadLocalInfileInPath() throws Exception { props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir1.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpSDir1.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpSSDir1.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir2.toString() + File.separator + ".." + File.separator + tmpDir1.getFileName()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } if (!skipLinkCheck) { props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpLink2.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } } @@ -2807,33 +2595,33 @@ public void testAllowLoadLocalInfileInPath() throws Exception { try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "(?i)The file '" + dataPath2 + "' is not under the safe path '.*'\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpSDir1.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "(?i)The file '" + dataPath2 + "' is not under the safe path '.*'\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpSSDir1.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "(?i)The file '" + dataPath2 + "' is not under the safe path '.*'\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir2.toString() + File.separator + ".." + File.separator + tmpDir1.getFileName()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "(?i)The file '" + dataPath2 + "' is not under the safe path '.*'\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } if (!skipLinkCheck) { props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpLink2.toString()); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "(?i)The file '" + dataPath2 + "' is not under the safe path '.*'\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } } @@ -2843,23 +2631,23 @@ public void testAllowLoadLocalInfileInPath() throws Exception { try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "The path '' specified in 'allowLoadLocalInfileInPath' does not exist\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "The path '' specified in 'allowLoadLocalInfileInPath' does not exist\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), " "); // Dir name with spaces. try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "The path ' ' specified in 'allowLoadLocalInfileInPath' does not exist\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "The path ' ' specified in 'allowLoadLocalInfileInPath' does not exist\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } props.setProperty(PropertyKey.allowLoadLocalInfileInPath.getKeyName(), tmpDir1.toString() + File.separator + "sub_12"); // Non-existing dir. try (Connection testConn = getConnectionWithProps(props)) { @@ -2867,14 +2655,14 @@ public void testAllowLoadLocalInfileInPath() throws Exception { assertThrows(SQLException.class, "(?i)The path '" + (tmpDir1.toString() + File.separator + "sub_12").replace("\\", "\\\\") + "' specified in 'allowLoadLocalInfileInPath' does not exist\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "(?i)The path '" + (tmpDir1.toString() + File.separator + "sub_12").replace("\\", "\\\\") + "' specified in 'allowLoadLocalInfileInPath' does not exist\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + fileRef2 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } } while ((inclALLI = !inclALLI) || (inclAUILI = !inclAUILI)); @@ -2885,13 +2673,13 @@ public void testAllowLoadLocalInfileInPath() throws Exception { props.setProperty(PropertyKey.allowUrlInLocalInfile.getKeyName(), "true"); try (Connection testConn = getConnectionWithProps(props)) { Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } try (Connection testConn = getConnectionWithProps(props)) { String filePrefix = Util.isRunningOnWindows() ? "file://localhost/" : "file://localhost"; Statement testStmt = testConn.createStatement(); - testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + filePrefix + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); + testStmt.execute("LOAD DATA LOCAL INFILE '" + filePrefix + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath"); testAllowLoadLocalInfileInPathCheckAndDelete(); } try (Connection testConn = getConnectionWithProps(props)) { @@ -2900,13 +2688,13 @@ public void testAllowLoadLocalInfileInPath() throws Exception { assertThrows(SQLException.class, "Cannot read from '.*'\\. Only local host names are supported when 'allowLoadLocalInfileInPath' is set\\. " + "Consider using the loopback network interface \\('localhost'\\)\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + filePrefix + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + filePrefix + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } try (Connection testConn = getConnectionWithProps(props)) { String ftpPrefix = Util.isRunningOnWindows() ? "ftp://localhost/" : "ftp://localhost"; Statement testStmt = testConn.createStatement(); assertThrows(SQLException.class, "Unsupported protocol 'ftp'\\. Only protocol 'file' is supported when 'allowLoadLocalInfileInPath' is set\\.", - () -> testStmt.executeQuery("LOAD DATA LOCAL INFILE '" + ftpPrefix + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); + () -> testStmt.execute("LOAD DATA LOCAL INFILE '" + ftpPrefix + dataPath1 + "' INTO TABLE testAllowLoadLocalInfileInPath")); } } @@ -2924,8 +2712,16 @@ private void testAllowLoadLocalInfileInPathCheckAndDelete() throws Exception { */ @Test public void testTimeoutErrors() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + int seconds = 2; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); // server reports timeout message only with SSL on props.setProperty(PropertyKey.sessionVariables.getKeyName(), "wait_timeout=" + seconds); Connection timeoutConn = getConnectionWithProps(props); @@ -2959,4 +2755,134 @@ public void testTimeoutErrors() throws Exception { () -> toBeKilledConn.createStatement().executeQuery("SELECT 1")); } + /** + * Tests fix for WL#14805, Remove support for TLS 1.0 and 1.1. + * + * @throws Exception + */ + @Test + public void testTLSVersionRemoval() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + + Connection con = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + // TS.FR.1_1. Create a Connection with the connection property tlsVersions=TLSv1.2. Assess that the connection is created successfully and it is using TLSv1.2. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1.2"); + con = getConnectionWithProps(props); + assertTrue(((MysqlConnection) con).getSession().isSSLEstablished()); + assertSessionStatusEquals(con.createStatement(), "ssl_version", "TLSv1.2"); + con.close(); + + // TS.FR.1_2. Create a Connection with the connection property enabledTLSProtocols=TLSv1.2. Assess that the connection is created successfully and it is using TLSv1.2. + props.remove(PropertyKey.tlsVersions.getKeyName()); + props.setProperty("enabledTLSProtocols", "TLSv1.2"); + con = getConnectionWithProps(props); + assertTrue(((MysqlConnection) con).getSession().isSSLEstablished()); + assertSessionStatusEquals(con.createStatement(), "ssl_version", "TLSv1.2"); + con.close(); + props.remove("enabledTLSProtocols"); + + // TS.FR.2_1. Create a Connection with the connection property tlsCiphersuites=[valid-cipher-suite]. Assess that the connection is created successfully and it is using the cipher suite specified. + props.setProperty(PropertyKey.tlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + con = getConnectionWithProps(props); + assertTrue(((MysqlConnection) con).getSession().isSSLEstablished()); + assertSessionStatusEquals(con.createStatement(), "ssl_cipher", "DHE-RSA-AES128-SHA"); + con.close(); + + // TS.FR.2_2. Create a Connection with the connection property enabledSSLCipherSuites=[valid-cipher-suite] . Assess that the connection is created successfully and it is using the cipher suite specified. + props.remove(PropertyKey.tlsCiphersuites.getKeyName()); + props.setProperty("enabledSSLCipherSuites", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + con = getConnectionWithProps(props); + assertTrue(((MysqlConnection) con).getSession().isSSLEstablished()); + assertSessionStatusEquals(con.createStatement(), "ssl_cipher", "DHE-RSA-AES128-SHA"); + con.close(); + props.remove("enabledSSLCipherSuites"); + + // TS.FR.3_1. Create a Connection with the connection property tlsVersions=TLSv1. Assess that the connection fails. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1"); + assertThrows(SQLException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.3_2. Create a Connection with the connection property tlsVersions=TLSv1.1. Assess that the connection fails. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1.1"); + assertThrows(SQLException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.3_3. Create a Connection with the connection property enabledTLSProtocols=TLSv1. Assess that the connection fails. + props.setProperty("enabledTLSProtocols", "TLSv1"); + assertThrows(SQLException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.3_4. Create a Connection with the connection property enabledTLSProtocols=TLSv1.1. Assess that the connection fails. + props.setProperty("enabledTLSProtocols", "TLSv1.1"); + assertThrows(SQLException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + props.remove("enabledTLSProtocols"); + + // TS.FR.4. Create a Connection with the connection property tlsVersions=TLSv1 and sslMode=DISABLED. Assess that the connection is created successfully and it is not using encryption. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + con = getConnectionWithProps(props); + assertFalse(((MysqlConnection) con).getSession().isSSLEstablished()); + con.close(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + + // TS.FR.5_1. Create a Connection with the connection property tlsVersions=FOO,BAR. + // Assess that the connection fails with the error message "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3." + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,BAR"); + assertThrows(SQLException.class, "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,,,BAR"); + assertThrows(SQLException.class, "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.5_2. Create a Connection with the connection property tlsVersions=FOO,TLSv1.1. + // Assess that the connection fails with the error message "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3." + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,TLSv1.1"); + assertThrows(SQLException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.5_3. Create a Connection with the connection property tlsVersions=TLSv1,TLSv1.1. + // Assess that the connection fails with the error message "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3." + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1,TLSv1.1"); + assertThrows(SQLException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.6. Create a Connection with the connection property tlsVersions= (empty value). + // Assess that the connection fails with the error message "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv13." + props.setProperty(PropertyKey.tlsVersions.getKeyName(), ""); + assertThrows(SQLException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), " "); + assertThrows(SQLException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), ",,,"); + assertThrows(SQLException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), ", ,,"); + assertThrows(SQLException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> getConnectionWithProps(props)); + + // TS.FR.7. Create a Connection with the connection property tlsVersions=FOO,TLSv1,TLSv1.1,TLSv1.2. + // Assess that the connection is created successfully and it is using TLSv1.2. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,TLSv1,TLSv1.1,TLSv1.2"); + con = getConnectionWithProps(props); + assertTrue(((MysqlConnection) con).getSession().isSSLEstablished()); + assertSessionStatusEquals(con.createStatement(), "ssl_version", "TLSv1.2"); + con.close(); + } + } diff --git a/src/test/java/testsuite/simple/DataSourceTest.java b/src/test/java/testsuite/simple/DataSourceTest.java index 43b1c2943..4f0ae3409 100644 --- a/src/test/java/testsuite/simple/DataSourceTest.java +++ b/src/test/java/testsuite/simple/DataSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -130,6 +130,11 @@ public void testDataSource() throws Exception { assertNotNull(boundDs, "Datasource not bound"); + if (boundDs instanceof MysqlDataSource) { + ((MysqlDataSource) boundDs).getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + ((MysqlDataSource) boundDs).getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); + } + Connection con = boundDs.getConnection(); assertNotNull(con, "Connection can not be obtained from data source"); con.close(); @@ -144,6 +149,8 @@ public void testDataSource() throws Exception { public void testChangeUserAndCharsets() throws Exception { MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource(); ds.setURL(BaseTestCase.dbUrl); + ds.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + ds.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); ds.getProperty(PropertyKey.characterEncoding).setValue("utf-8"); PooledConnection pooledConnection = ds.getPooledConnection(); diff --git a/src/test/java/testsuite/simple/DateTest.java b/src/test/java/testsuite/simple/DateTest.java index 71c239cd8..bd0a4c9ab 100644 --- a/src/test/java/testsuite/simple/DateTest.java +++ b/src/test/java/testsuite/simple/DateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -176,6 +176,8 @@ public void testZeroDateBehavior() throws Exception { Connection exceptionConn = null; try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); if (versionMeetsMinimum(5, 7, 4)) { props.setProperty(PropertyKey.jdbcCompliantTruncation.getKeyName(), "false"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); @@ -195,6 +197,8 @@ public void testZeroDateBehavior() throws Exception { this.stmt.executeUpdate("INSERT INTO testZeroDateBehavior VALUES ('0000-00-00 00:00:00', '0000-00-00 00:00:00')"); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "ROUND"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); roundConn = getConnectionWithProps(props); @@ -216,7 +220,11 @@ public void testZeroDateBehavior() throws Exception { assertEquals("0001-01-01", this.rs.getDate(2).toString()); assertEquals("0001-01-01 00:00:00.0", TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd HH:mm:ss.0", null).format(this.rs.getTimestamp(2))); - nullConn = getConnectionWithProps("zeroDateTimeBehavior=CONVERT_TO_NULL"); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "CONVERT_TO_NULL"); + nullConn = getConnectionWithProps(props); Statement nullStmt = nullConn.createStatement(); this.rs = nullStmt.executeQuery("SELECT fieldAsString, fieldAsDateTime FROM testZeroDateBehavior"); @@ -237,7 +245,11 @@ public void testZeroDateBehavior() throws Exception { assertNull(this.rs.getDate(2)); assertNull(this.rs.getTimestamp(2)); - exceptionConn = getConnectionWithProps("zeroDateTimeBehavior=EXCEPTION"); + props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.zeroDateTimeBehavior.getKeyName(), "EXCEPTION"); + exceptionConn = getConnectionWithProps(props); Statement exceptionStmt = exceptionConn.createStatement(); this.rs = exceptionStmt.executeQuery("SELECT fieldAsString, fieldAsDateTime FROM testZeroDateBehavior"); diff --git a/src/test/java/testsuite/simple/DateTimeTest.java b/src/test/java/testsuite/simple/DateTimeTest.java index 75d069505..5e4cb2f84 100644 --- a/src/test/java/testsuite/simple/DateTimeTest.java +++ b/src/test/java/testsuite/simple/DateTimeTest.java @@ -150,6 +150,8 @@ private String getKey(Properties props) { private void initConnections(TimeZone senderTz, String connectionTZ) throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); // applying 8.0 defaults to old servers @@ -227,6 +229,8 @@ public void testSqlDateSetters() throws Exception { Calendar cal_02 = GregorianCalendar.getInstance(tz_plus_02_00); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -461,6 +465,8 @@ public void testSqlTimeSetters() throws Exception { Calendar cal_02 = GregorianCalendar.getInstance(tz_plus_02_00); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -525,6 +531,8 @@ public void testSqlTimeSetters() throws Exception { .format(withFract ? TIME_FORMATTER_WITH_MILLIS_NO_OFFCET : TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET) : expTimeNoMs; + String expDate8_0_28 = zdt_19700101_120000_123_at_senderTz.format(DateTimeFormatter.ofPattern("20HH-mm-ss")); + String expTimeNoMsCal = zdt_19700101_120000_123_at_calendarTz.toLocalTime() .format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET); String expTimeCal = sendFractionalSeconds && sendTimeFract @@ -562,7 +570,9 @@ public void testSqlTimeSetters() throws Exception { /* Into DATE field */ - String expDateErr = incorrectDateErr.replace("X", expTimeSendTimeFract); + String expDateErr = incorrectDateErr.replace("X", + useSSPS && !(sendTimeFract && sendFractionalSeconds) && versionMeetsMinimum(8, 0, 28) ? expDate8_0_28 + : expTimeSendTimeFract); String expDateErrWithCal = incorrectDateErr.replace("X", expTimeCal); if (useSSPS) { @@ -624,7 +634,9 @@ public void testSqlTimeSetters() throws Exception { String expDatetime = expDate + " " + expTimeSendTimeFract; String expDatetimeWithCal = expDate + " " + expTimeCal; - String expDatetimeErr = incorrectDatetimeErr.replace("X", expTimeSendTimeFract); + String expDatetimeErr = incorrectDatetimeErr.replace("X", + useSSPS && !(sendTimeFract && sendFractionalSeconds) && versionMeetsMinimum(8, 0, 28) ? expDate8_0_28 + : expTimeSendTimeFract); String expDatetimeErrWithCal = incorrectDatetimeErr.replace("X", expTimeCal); if (useSSPS) { @@ -775,6 +787,8 @@ public void testSqlTimestampSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -1065,6 +1079,8 @@ public void testUtilCalendarSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -1334,6 +1350,8 @@ public void testUtilDateSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -1591,6 +1609,8 @@ public void testLocalDateSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -1777,6 +1797,8 @@ public void testLocalTimeSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -1832,6 +1854,8 @@ public void testLocalTimeSetters() throws Exception { String expTimeNoMs = zdt_no_date_120000_123456_on_wire.format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET); String expTime6 = zdt_no_date_120000_123456_on_wire.format(timeFmt); String expTime9 = zdt_no_date_120000_123456_on_wire.format(timeFmtForChars); + String expTime8_0_28 = zdt_no_date_120000_123456_on_wire.format(DateTimeFormatter.ofPattern("20HH-mm-ss")); + String expDatetimeDef = zdt_no_date_120000_123456_on_wire .format(useSSPS ? dateTimeFmt : DateTimeFormatter.ofPattern("20HH-mm-ss 00:00:00")); String expDefTimestamp = zdt_no_date_120000_123456_on_wire.withZoneSameInstant(tz_UTC.toZoneId()).format(dateTimeFmt); @@ -1844,9 +1868,13 @@ public void testLocalTimeSetters() throws Exception { : ""); String expDateErr6 = incorrectDateErr.replace("X", expTime6); - String expDateErr9 = incorrectDateErr.replace("X", expTime9); + + String expDateErr9 = incorrectDateErr.replace("X", + useSSPS && !sendFractionalSeconds && versionMeetsMinimum(8, 0, 28) ? expTime8_0_28 : expTime9); + String expDatetimeErr6 = incorrectDatetimeErr.replace("X", expTime6); - String expDatetimeErr9 = incorrectDatetimeErr.replace("X", expTime9); + String expDatetimeErr9 = incorrectDatetimeErr.replace("X", + useSSPS && !sendFractionalSeconds && versionMeetsMinimum(8, 0, 28) ? expTime8_0_28 : expTime9); /* Unsupported conversions */ @@ -1987,6 +2015,8 @@ public void testLocalDateTimeSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -2237,6 +2267,8 @@ public void testOffsetTimeSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -2297,6 +2329,10 @@ public void testOffsetTimeSetters() throws Exception { String expTimeNoMs = zdt_no_date_120000_123456_on_wire.format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET); String expTime = zdt_no_date_120000_123456_on_wire.format(timeFmt); String expTimeTz = ot_120000_123456_05_00.format(timeFmtTz).replace("+", "\\+"); + String expTimeTz8_0_28 = ot_120000_123456_05_00.format(DateTimeFormatter.ofPattern("20HH-mm-ss X:00:00")).replace("+", + ""); + String expDatetimeTz8_0_28 = ot_120000_123456_05_00.format(DateTimeFormatter.ofPattern("20HH-mm-ss X:00:00.000000")) + .replace("+", ""); String expDatetimeDef = zdt_no_date_120000_123456_on_wire .format(useSSPS ? dateTimeFmt : DateTimeFormatter.ofPattern("20HH-mm-ss 00:00:00")); @@ -2310,10 +2346,12 @@ public void testOffsetTimeSetters() throws Exception { : ""); String expDateErr = incorrectDateErr.replace("X", expTime); - String expDateErrTz = incorrectDateErr.replace("X", expTimeTz); + String expDateErrTz = incorrectDateErr.replace("X", + useSSPS && !sendFractionalSeconds && versionMeetsMinimum(8, 0, 28) ? expTimeTz8_0_28 : expTimeTz); String expTimeErrTz = incorrectTimeErr.replace("X", expTimeTz); String expDatetimeErr = incorrectDatetimeErr.replace("X", expTime); - String expDatetimeErrTz = incorrectDatetimeErr.replace("X", expTimeTz); + String expDatetimeErrTz = incorrectDatetimeErr.replace("X", + useSSPS && !sendFractionalSeconds && versionMeetsMinimum(8, 0, 28) ? expDatetimeTz8_0_28 : expTimeTz); /* Unsupported conversions */ @@ -2444,6 +2482,9 @@ public void testOffsetTimeSetters() throws Exception { public void testOffsetDatetimeSetters() throws Exception { boolean withFract = versionMeetsMinimum(5, 6, 4); // fractional seconds are not supported in previous versions boolean allowsOffset = versionMeetsMinimum(8, 0, 19); + // Starting from MySQL 8.0.22 server also converts string values in TIMESTAMP_WITH_TIMEZONE format to the session time zone + // for column types other than TIMESTAMP and DATETIME. In MySQL 8.0.26 it was reverted, restored in MySQL 8.0.28. + boolean serverConvertsTzForAllTypes = versionMeetsMinimum(8, 0, 22) && !versionMeetsMinimum(8, 0, 26) || versionMeetsMinimum(8, 0, 28); createTable(tYear, "(id INT, d YEAR)"); createTable(tDate, "(id INT, d DATE)"); @@ -2463,6 +2504,8 @@ public void testOffsetDatetimeSetters() throws Exception { TimeZone serverTz; try (Connection testConn = getConnectionWithProps(props)) { serverTz = ((MysqlConnection) testConn).getSession().getServerSession().getSessionTimeZone(); + System.out.println("Local tz: " + TimeZone.getDefault()); + System.out.println("Server tz: " + serverTz); } OffsetDateTime odt_20200101_120000_123456_05_00 = OffsetDateTime.of(2020, 1, 1, 12, 00, 00, withFract ? 123456000 : 0, ZoneOffset.ofHours(5)); @@ -2529,9 +2572,8 @@ public void testOffsetDatetimeSetters() throws Exception { String expDate = zdt_20200101_120000_123456_on_wire.format(TimeUtil.DATE_FORMATTER); String expDateDef = zdt_no_date_120000_123456_on_wire .format(useSSPS ? TimeUtil.DATE_FORMATTER : DateTimeFormatter.ofPattern("20HH-mm-ss")); - // Starting from MySQL 8.0.22 TIMESTAMP_WITH_TIMEZONE value is also converted to the server time zone by server - // for column types other than TIMESTAMP or DATETIME - String expDateChar = versionMeetsMinimum(8, 0, 22) + + String expDateChar = serverConvertsTzForAllTypes ? odt_20200101_120000_123456_05_00.atZoneSameInstant(sessionTz.toZoneId()).format(TimeUtil.DATE_FORMATTER) : odt_20200101_120000_123456_05_00.format(TimeUtil.DATE_FORMATTER); String expDateTS = zdt_TS_on_wire.format(TimeUtil.DATE_FORMATTER); @@ -2539,7 +2581,7 @@ public void testOffsetDatetimeSetters() throws Exception { String expTimeNoMs = zdt_20200101_120000_123456_on_wire.format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET); String expTime = zdt_20200101_120000_123456_on_wire.format(timeFmt); - String expTime2 = versionMeetsMinimum(8, 0, 22) + String expTime2 = serverConvertsTzForAllTypes ? odt_20200101_120000_123456_05_00.atZoneSameInstant(sessionTz.toZoneId()).format(timeFmt) : odt_20200101_120000_123456_05_00.format(timeFmt); String expTimeTS = zdt_TS_on_wire.format(timeFmt); @@ -2815,6 +2857,9 @@ public void testOffsetDatetimeSetters() throws Exception { public void testZonedDatetimeSetters() throws Exception { boolean withFract = versionMeetsMinimum(5, 6, 4); // fractional seconds are not supported in previous versions boolean allowsOffset = versionMeetsMinimum(8, 0, 19); + // Starting from MySQL 8.0.22 server also converts string values in TIMESTAMP_WITH_TIMEZONE format to the session time zone + // for column types other than TIMESTAMP and DATETIME. In MySQL 8.0.26 it was reverted, restored in MySQL 8.0.28. + boolean serverConvertsTzForAllTypes = versionMeetsMinimum(8, 0, 22) && !versionMeetsMinimum(8, 0, 26) || versionMeetsMinimum(8, 0, 28); createTable(tYear, "(id INT, d YEAR)"); createTable(tDate, "(id INT, d DATE)"); @@ -2826,12 +2871,16 @@ public void testZonedDatetimeSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); TimeZone serverTz; try (Connection testConn = getConnectionWithProps(props)) { serverTz = ((MysqlConnection) testConn).getSession().getServerSession().getSessionTimeZone(); + System.out.println("Local tz: " + TimeZone.getDefault()); + System.out.println("Server tz: " + serverTz); } ZonedDateTime zdt_20200101_120000_123456_05_00 = ZonedDateTime.of(withFract ? ldt_20200101_120000_123456 : ldt_20200101_120000_123456.withNano(0), @@ -2898,16 +2947,14 @@ public void testZonedDatetimeSetters() throws Exception { String expDate = zdt_20200101_120000_123456_on_wire.format(TimeUtil.DATE_FORMATTER); String expDateDef = zdt_no_date_120000_123456_on_wire .format(useSSPS ? TimeUtil.DATE_FORMATTER : DateTimeFormatter.ofPattern("20HH-mm-ss")); - // Starting from MySQL 8.0.22 TIMESTAMP_WITH_TIMEZONE value is also converted to the server time zone by server - // for column types other than TIMESTAMP or DATETIME - String expDateChar = versionMeetsMinimum(8, 0, 22) + String expDateChar = serverConvertsTzForAllTypes ? zdt_20200101_120000_123456_05_00.withZoneSameInstant(sessionTz.toZoneId()).format(TimeUtil.DATE_FORMATTER) : zdt_20200101_120000_123456_05_00.format(TimeUtil.DATE_FORMATTER); String expDateTS = zdt_TS_on_wire.format(TimeUtil.DATE_FORMATTER); String expTimeNoMs = zdt_20200101_120000_123456_on_wire.format(TimeUtil.TIME_FORMATTER_NO_FRACT_NO_OFFSET); String expTime = zdt_20200101_120000_123456_on_wire.format(timeFmt); - String expTime2 = versionMeetsMinimum(8, 0, 22) + String expTime2 = serverConvertsTzForAllTypes ? zdt_20200101_120000_123456_05_00.withZoneSameInstant(sessionTz.toZoneId()).format(timeFmt) : zdt_20200101_120000_123456_05_00.format(timeFmt); String expTimeTS = zdt_TS_on_wire.format(timeFmt); @@ -3185,6 +3232,8 @@ public void testDurationSetters() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); for (TimeZone senderTz : this.senderTimeZones) { @@ -3389,9 +3438,10 @@ void setObjectFromTz(Properties props, String tableName, Object parameter, SQLTy testConn = this.utcConnections.get(getKey(props)); Statement localStmt = testConn.createStatement(); localStmt.execute("set @@time_zone='+00:00'"); - ResultSet localRs = localStmt.executeQuery("SELECT * FROM " + tableName + " WHERE id = " + id + " AND d = '" + expectedUTCValue + "'" - + (expectedUnixTimestamp != null ? " AND unix_timestamp(d) = " + expectedUnixTimestamp : "")); - assertTrue(localRs.next()); + String sql = "SELECT * FROM " + tableName + " WHERE id = " + id + " AND d = '" + expectedUTCValue + "'" + + (expectedUnixTimestamp != null ? " AND unix_timestamp(d) = " + expectedUnixTimestamp : ""); + ResultSet localRs = localStmt.executeQuery(sql); + assertTrue(localRs.next(), parameter + "\n" + targetSqlType + "\n" + senderTz + "\n" + useMethod + "\n" + sql); localStmt.close(); } } finally { @@ -3407,6 +3457,8 @@ public void testDateGetters() throws Exception { Calendar cal_05 = GregorianCalendar.getInstance(tz_plus_05_00); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); final TimeZone origTz = TimeZone.getDefault(); @@ -3536,6 +3588,8 @@ public void testTimeGetters() throws Exception { Calendar cal_05 = GregorianCalendar.getInstance(tz_plus_05_00); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); final TimeZone origTz = TimeZone.getDefault(); @@ -3756,6 +3810,8 @@ public void testTimestampGetters() throws Exception { Calendar cal_05 = GregorianCalendar.getInstance(tz_plus_05_00); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -3882,6 +3938,8 @@ public void testDatetimeGetters() throws Exception { this.stmt.executeUpdate("INSERT INTO " + tDatetime + " VALUES ('" + ldt_20200101_020000_123456.toString() + "')"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); @@ -3997,6 +4055,8 @@ public void testYearGetters() throws Exception { Calendar cal_05 = GregorianCalendar.getInstance(tz_plus_05_00); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); final TimeZone origTz = TimeZone.getDefault(); @@ -4352,6 +4412,8 @@ public void testSymmetricInstantRetrieval() throws Exception { id = 0; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.cacheDefaultTimeZone.getKeyName(), "false"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "SERVER"); diff --git a/src/test/java/testsuite/simple/EscapeProcessingTest.java b/src/test/java/testsuite/simple/EscapeProcessingTest.java index 53ad3253b..a94a6b9c0 100644 --- a/src/test/java/testsuite/simple/EscapeProcessingTest.java +++ b/src/test/java/testsuite/simple/EscapeProcessingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -116,6 +116,8 @@ public void testTimestampConversion() throws Exception { } Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), newTimeZone); Connection tzConn = null; diff --git a/src/test/java/testsuite/simple/MetadataTest.java b/src/test/java/testsuite/simple/MetadataTest.java index 7c32c0b1b..fff1b80a1 100644 --- a/src/test/java/testsuite/simple/MetadataTest.java +++ b/src/test/java/testsuite/simple/MetadataTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -35,6 +35,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.lang.reflect.Field; import java.sql.Connection; @@ -76,6 +78,8 @@ public class MetadataTest extends BaseTestCase { @Test public void testSupports() throws SQLException { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -115,6 +119,8 @@ public void testSupports() throws SQLException { @Test public void testGetCatalogVsGetSchemas() throws SQLException { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -223,6 +229,8 @@ public void testForeignKeys() throws SQLException { + "index(TYPE_ID), foreign key(TYPE_ID) references fktable1(TYPE_ID)) ", "InnoDB"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection conn1 = null; for (boolean useIS : new boolean[] { false, true }) { @@ -416,6 +424,8 @@ public void testGetPrimaryKeys() throws SQLException { createTable("multikey", "(d INT NOT NULL, b INT NOT NULL, a INT NOT NULL, c INT NOT NULL, PRIMARY KEY (d, b, a, c))"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -458,9 +468,7 @@ public void testGetPrimaryKeys() throws SQLException { this.rs.getString("PK_NAME"); } - if ((keySeqs[0] != 3) && (keySeqs[1] != 2) && (keySeqs[2] != 4) && (keySeqs[3] != 1)) { - fail("Keys returned in wrong order"); - } + assertFalse((keySeqs[0] != 3) && (keySeqs[1] != 2) && (keySeqs[2] != 4) && (keySeqs[3] != 1), "Keys returned in wrong order"); } finally { if (conn1 != null) { @@ -615,6 +623,8 @@ public void testTinyint1IsBit() throws Exception { for (boolean tinyInt1isBit : new boolean[] { true, true }) { for (boolean transformedBitIsBoolean : new boolean[] { false, true }) { props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); props.setProperty(PropertyKey.tinyInt1isBit.getKeyName(), "" + tinyInt1isBit); props.setProperty(PropertyKey.transformedBitIsBoolean.getKeyName(), "" + transformedBitIsBoolean); @@ -688,6 +698,8 @@ public Void call() throws Exception { assertEquals(0, this.rs.getMetaData().getScale(1)); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useOldAliasMetadataBehavior.getKeyName(), "true"); Connection con = getConnectionWithProps(props); @@ -705,6 +717,8 @@ public Void call() throws Exception { public void testGetPrimaryKeysUsingInfoShcema() throws Exception { createTable("t1", "(c1 int(1) primary key)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); Connection conn1 = null; try { @@ -727,6 +741,8 @@ public void testGetIndexInfo() throws Exception { this.stmt.executeUpdate("CREATE INDEX index1 ON t1 (c1)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -789,6 +805,8 @@ public void testGetIndexInfo() throws Exception { public void testGetColumns() throws Exception { createTable("t1", "(c1 char(1))"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { @@ -848,6 +866,8 @@ public void testGetTablesUsingInfoSchema() throws Exception { tableNames.add("t1-1"); tableNames.add("t1-2"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); Connection conn1 = null; try { @@ -873,6 +893,8 @@ public void testGetTables() throws Exception { createTable("`t2`", "(c1 char(1))"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -961,86 +983,82 @@ private void testGetTables_checkResult(boolean useIS, boolean dbMapsToSchema) th @Test public void testGetColumnPrivileges() throws Exception { - if (!isSysPropDefined(PropertyDefinitions.SYSP_testsuite_cantGrant)) { - Properties props = new Properties(); + assumeFalse(isSysPropDefined(PropertyDefinitions.SYSP_testsuite_cantGrant), + "This testcase needs to be run with a URL that allows the user to issue GRANTs " + + " in the current database. Aborted because the system property \"" + PropertyDefinitions.SYSP_testsuite_cantGrant + "\" is set."); - props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); - Connection conn1 = null; - Statement stmt1 = null; - String userHostQuoted = null; + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.nullDatabaseMeansCurrent.getKeyName(), "true"); + Connection conn1 = null; + Statement stmt1 = null; + String userHostQuoted = null; - for (boolean useIS : new boolean[] { false, true }) { - for (boolean dbMapsToSchema : new boolean[] { false, true }) { - props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); - props.setProperty(PropertyKey.databaseTerm.getKeyName(), dbMapsToSchema ? DatabaseTerm.SCHEMA.name() : DatabaseTerm.CATALOG.name()); + for (boolean useIS : new boolean[] { false, true }) { + for (boolean dbMapsToSchema : new boolean[] { false, true }) { + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); + props.setProperty(PropertyKey.databaseTerm.getKeyName(), dbMapsToSchema ? DatabaseTerm.SCHEMA.name() : DatabaseTerm.CATALOG.name()); + + boolean grantFailed = true; - boolean grantFailed = true; + try { + conn1 = getConnectionWithProps(props); + stmt1 = conn1.createStatement(); + createTable("t1", "(c1 int)"); + this.rs = stmt1.executeQuery("SELECT CURRENT_USER()"); + this.rs.next(); + String user = this.rs.getString(1); + List userHost = StringUtils.split(user, "@", false); + assertFalse(userHost.size() < 2, "This test requires a JDBC URL with a user, and won't work with the anonymous user. " + + "You can skip this test by setting the system property " + PropertyDefinitions.SYSP_testsuite_cantGrant); + userHostQuoted = "'" + userHost.get(0) + "'@'" + userHost.get(1) + "'"; try { - conn1 = getConnectionWithProps(props); - stmt1 = conn1.createStatement(); - createTable("t1", "(c1 int)"); - this.rs = stmt1.executeQuery("SELECT CURRENT_USER()"); + stmt1.executeUpdate("GRANT update (c1) on t1 to " + userHostQuoted); + grantFailed = false; + } catch (SQLException sqlEx) { + fail("This testcase needs to be run with a URL that allows the user to issue GRANTs " + + " in the current database. You can skip this test by setting the system property \"" + + PropertyDefinitions.SYSP_testsuite_cantGrant + "\"."); + } + + if (!grantFailed) { + DatabaseMetaData metaData = conn1.getMetaData(); + this.rs = metaData.getColumnPrivileges(null, null, "t1", null); this.rs.next(); - String user = this.rs.getString(1); - List userHost = StringUtils.split(user, "@", false); - if (userHost.size() < 2) { - fail("This test requires a JDBC URL with a user, and won't work with the anonymous user. " - + "You can skip this test by setting the system property " + PropertyDefinitions.SYSP_testsuite_cantGrant); + if (dbMapsToSchema) { + assertEquals("def", this.rs.getString("TABLE_CAT")); + assertEquals(this.dbName, this.rs.getString("TABLE_SCHEM")); + } else { + assertEquals(this.dbName, this.rs.getString("TABLE_CAT")); + assertNull(this.rs.getString("TABLE_SCHEM")); } - userHostQuoted = "'" + userHost.get(0) + "'@'" + userHost.get(1) + "'"; + assertEquals("t1", this.rs.getString("TABLE_NAME")); + assertEquals("c1", this.rs.getString("COLUMN_NAME")); + assertEquals(useIS ? userHostQuoted : userHost.get(0) + "@" + userHost.get(1), this.rs.getString("GRANTEE")); + assertEquals("UPDATE", this.rs.getString("PRIVILEGE")); - try { - stmt1.executeUpdate("GRANT update (c1) on t1 to " + userHostQuoted); - - grantFailed = false; - - } catch (SQLException sqlEx) { - fail("This testcase needs to be run with a URL that allows the user to issue GRANTs " - + " in the current database. You can skip this test by setting the system property \"" - + PropertyDefinitions.SYSP_testsuite_cantGrant + "\"."); + if (dbMapsToSchema) { + String dbPattern = conn1.getSchema().substring(0, conn1.getSchema().length() - 1) + "%"; + this.rs = metaData.getColumnPrivileges(null, dbPattern, "t1", null); + assertFalse(this.rs.next(), "Schema pattern " + dbPattern + " should not be recognized."); + } else { + String dbPattern = conn1.getCatalog().substring(0, conn1.getCatalog().length() - 1) + "%"; + this.rs = metaData.getColumnPrivileges(dbPattern, null, "t1", null); + assertFalse(this.rs.next(), "Catalog pattern " + dbPattern + " should not be recognized."); } - + } + } finally { + if (stmt1 != null) { if (!grantFailed) { - DatabaseMetaData metaData = conn1.getMetaData(); - this.rs = metaData.getColumnPrivileges(null, null, "t1", null); - this.rs.next(); - if (dbMapsToSchema) { - assertEquals("def", this.rs.getString("TABLE_CAT")); - assertEquals(this.dbName, this.rs.getString("TABLE_SCHEM")); - } else { - assertEquals(this.dbName, this.rs.getString("TABLE_CAT")); - assertNull(this.rs.getString("TABLE_SCHEM")); - } - assertEquals("t1", this.rs.getString("TABLE_NAME")); - assertEquals("c1", this.rs.getString("COLUMN_NAME")); - assertEquals(useIS ? userHostQuoted : userHost.get(0) + "@" + userHost.get(1), this.rs.getString("GRANTEE")); - assertEquals("UPDATE", this.rs.getString("PRIVILEGE")); - - if (dbMapsToSchema) { - String dbPattern = conn1.getSchema().substring(0, conn1.getSchema().length() - 1) + "%"; - this.rs = metaData.getColumnPrivileges(null, dbPattern, "t1", null); - assertFalse(this.rs.next(), "Schema pattern " + dbPattern + " should not be recognized."); - } else { - String dbPattern = conn1.getCatalog().substring(0, conn1.getCatalog().length() - 1) + "%"; - this.rs = metaData.getColumnPrivileges(dbPattern, null, "t1", null); - assertFalse(this.rs.next(), "Catalog pattern " + dbPattern + " should not be recognized."); - } - - } - } finally { - if (stmt1 != null) { - - if (!grantFailed) { - stmt1.executeUpdate("REVOKE UPDATE (c1) ON t1 FROM " + userHostQuoted); - } - - stmt1.close(); + stmt1.executeUpdate("REVOKE UPDATE (c1) ON t1 FROM " + userHostQuoted); } + stmt1.close(); + } - if (conn1 != null) { - conn1.close(); - } + if (conn1 != null) { + conn1.close(); } } } @@ -1052,6 +1070,8 @@ public void testGetProcedures() throws Exception { createProcedure("sp1", "() COMMENT 'testGetProcedures comment1' \n BEGIN\nSELECT 1;end\n"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1116,6 +1136,8 @@ public void testGetFunctions() throws Exception { createFunction("testGetFunctionsF", "(d INT) RETURNS INT DETERMINISTIC COMMENT 'testGetFunctions comment1' BEGIN RETURN d; END"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1177,6 +1199,8 @@ public void testGetProcedureColumns() throws Exception { createProcedure("testGetProcedureColumnsP", "(d INT) COMMENT 'testGetProcedureColumns comment1' \n BEGIN\nSELECT 1;end\n"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1253,6 +1277,8 @@ public void testGetFunctionColumns() throws Exception { createFunction("testGetFunctionColumnsF", "(d INT) RETURNS INT DETERMINISTIC COMMENT 'testGetFunctionColumnsF comment1' BEGIN RETURN d; END"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1359,17 +1385,20 @@ public void testGetCrossReferenceUsingInfoSchema() throws Exception { this.stmt.executeUpdate( "CREATE TABLE child(id INT, parent_id INT, " + "FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET NULL) ENGINE=INNODB"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "true"); Connection conn1 = null; try { conn1 = getConnectionWithProps(props); DatabaseMetaData metaData = conn1.getMetaData(); this.rs = metaData.getCrossReference(null, null, "parent", null, null, "child"); - this.rs.next(); + assertTrue(this.rs.next()); assertEquals("parent", this.rs.getString("PKTABLE_NAME")); assertEquals("id", this.rs.getString("PKCOLUMN_NAME")); assertEquals("child", this.rs.getString("FKTABLE_NAME")); assertEquals("parent_id", this.rs.getString("FKCOLUMN_NAME")); + assertFalse(this.rs.next()); } finally { this.stmt.executeUpdate("DROP TABLE IF EXISTS child"); this.stmt.executeUpdate("DROP TABLE If EXISTS parent"); @@ -1387,6 +1416,8 @@ public void testGetExportedKeys() throws Exception { createTable("child", "(id INT, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET NULL) ENGINE=INNODB"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1451,6 +1482,8 @@ public void testGetImportedKeys() throws Exception { createTable("child", "(id INT, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET NULL) ENGINE=INNODB"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1519,9 +1552,7 @@ private void testGetImportedKeys_checkResult(boolean useIS, boolean dbMapsToSche */ @Test public void testGeneratedColumns() throws Exception { - if (!versionMeetsMinimum(5, 7, 6)) { - return; - } + assumeTrue(versionMeetsMinimum(5, 7, 6), "MySQL 5.7.6+ is required to run this test."); // Test GENERATED columns syntax. createTable("pythagorean_triple", "(side_a DOUBLE NULL, side_b DOUBLE NULL, " @@ -1623,13 +1654,14 @@ public void testGetSqlKeywordsStatic() throws Exception { + "SQL_CALC_FOUND_ROWS,SQL_SMALL_RESULT,SSL,STARTING,STORED,STRAIGHT_JOIN,TERMINATED,TINYBLOB,TINYINT,TINYTEXT,UNDO,UNLOCK,UNSIGNED,USAGE,USE," + "UTC_DATE,UTC_TIME,UTC_TIMESTAMP,VARBINARY,VARCHARACTER,VIRTUAL,WHILE,WRITE,XOR,YEAR_MONTH,ZEROFILL"; - if (!versionMeetsMinimum(8, 0, 11)) { - Connection testConn = getConnectionWithProps("useInformationSchema=true"); - assertEquals(mysqlKeywords, testConn.getMetaData().getSQLKeywords(), "MySQL keywords don't match expected."); - testConn.close(); - } + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useInformationSchema.getKeyName(), // + versionMeetsMinimum(8, 0, 11) ? "false" // Required for MySQL 8.0.11 and above, otherwise returns dynamic keywords + : "true"); - Connection testConn = getConnectionWithProps("useInformationSchema=false"); // Required for MySQL 8.0.11 and above, otherwise returns dynamic keywords. + Connection testConn = getConnectionWithProps(props); assertEquals(mysqlKeywords, testConn.getMetaData().getSQLKeywords(), "MySQL keywords don't match expected."); testConn.close(); } @@ -1644,10 +1676,7 @@ public void testGetSqlKeywordsStatic() throws Exception { */ @Test public void testGetSqlKeywordsDynamic() throws Exception { - if (!versionMeetsMinimum(8, 0, 11)) { - // Tested in testGetSqlKeywordsStatic(); - return; - } + assumeTrue(versionMeetsMinimum(8, 0, 11), "MySQL 8.0.11+ is required to run this test."); /* * Setup test case. @@ -1733,6 +1762,8 @@ public void testGetTablePrivileges() throws Exception { this.stmt.executeUpdate("grant SELECT on `" + this.dbName + "`.`testGetTablePrivileges` to 'testGTPUser'@'%'"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); @@ -1803,6 +1834,8 @@ public void testGetBestRowIdentifier() throws Exception { createTable(tableName, "(field1 INT NOT NULL PRIMARY KEY)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); for (boolean useIS : new boolean[] { false, true }) { for (boolean dbMapsToSchema : new boolean[] { false, true }) { props.setProperty(PropertyKey.useInformationSchema.getKeyName(), "" + useIS); diff --git a/src/test/java/testsuite/simple/MiniAdminTest.java b/src/test/java/testsuite/simple/MiniAdminTest.java index 0f9e3894b..9883abc6c 100644 --- a/src/test/java/testsuite/simple/MiniAdminTest.java +++ b/src/test/java/testsuite/simple/MiniAdminTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -29,9 +29,12 @@ package testsuite.simple; +import java.util.Properties; + import org.junit.jupiter.api.Test; import com.mysql.cj.conf.PropertyDefinitions; +import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.jdbc.admin.MiniAdmin; import testsuite.BaseTestCase; @@ -61,6 +64,9 @@ public void testShutdown() throws Exception { */ @Test public void testUrlConstructor() throws Exception { - new MiniAdmin(dbUrl); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + new MiniAdmin(dbUrl, props); } } diff --git a/src/test/java/testsuite/simple/MultiHostConnectionTest.java b/src/test/java/testsuite/simple/MultiHostConnectionTest.java index 4b8271254..66e5e3120 100644 --- a/src/test/java/testsuite/simple/MultiHostConnectionTest.java +++ b/src/test/java/testsuite/simple/MultiHostConnectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -151,6 +151,8 @@ public void testFailoverConnection() throws Exception { final String allDownURL = testURL.toString(); final Properties testConnProps = getHostFreePropertiesFromTestsuiteUrl(); + testConnProps.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + testConnProps.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); testConnProps.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); // all hosts down @@ -244,6 +246,8 @@ public void testFailoverTransitions() throws Exception { private void testFailoverTransition(String fromHost, String toHost, Set downedHosts, String recoverHost, String... expectedConnectionsHistory) throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); String fromHostOk = UnreliableSocketFactory.STATUS_CONNECTED + fromHost; @@ -313,6 +317,8 @@ private void testFailoverTransition(String fromHost, String toHost, Set @Test public void testFailoverDefaultSettings() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); Connection testConn = getUnreliableFailoverConnection(new String[] { HOST_1, HOST_2, HOST_3 }, props); @@ -443,6 +449,8 @@ public void testFailoverDefaultSettings() throws Exception { @Test public void testFailoverCombinations() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); for (int run = 1; run <= 3; run++) { @@ -601,6 +609,8 @@ public void testFailoverReadOnly() throws Exception { downedHosts.add(HOST_1); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); for (boolean foReadOnly : new boolean[] { true, false }) { @@ -703,6 +713,8 @@ public void testFailoverReadOnly() throws Exception { @Test public void testFailoverQueriesBeforeRetrySource() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); for (boolean setQueriesBeforeRetrySource : new boolean[] { true, false }) { @@ -790,6 +802,8 @@ public void testFailoverQueriesBeforeRetrySource() throws Exception { @Test public void testFailoverSecondsBeforeRetrySource() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); for (boolean setSecondsBeforeRetrySource : new boolean[] { true, false }) { @@ -894,6 +908,8 @@ public void testFailoverAutoFallBack() throws Exception { downedHosts.add(HOST_3); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); // test fall back on ('queriesBeforeRetrySource' > 0 || 'secondsBeforeRetrySource' > 0) @@ -1056,6 +1072,8 @@ public void testFailoverAutoReconnect() throws Exception { downedHosts.add(HOST_2); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); props.setProperty(PropertyKey.maxReconnects.getKeyName(), "2"); props.setProperty(PropertyKey.initialTimeout.getKeyName(), "1"); @@ -1189,6 +1207,8 @@ public void testFailoverConnectionSynchronization() throws Exception { downedHosts.add(HOST_3); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); props.setProperty(PropertyKey.failOverReadOnly.getKeyName(), "false"); @@ -1318,6 +1338,8 @@ public void testLoadBalanceServerAffinityStrategy() throws Exception { final String[] hosts = new String[] { HOST_1, HOST_2, HOST_3, HOST_4, HOST_5 }; final Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), "serverAffinity"); props.setProperty(PropertyKey.retriesAllDown.getKeyName(), "2"); props.setProperty(PropertyKey.maxReconnects.getKeyName(), "2"); diff --git a/src/test/java/testsuite/simple/QueryAttributesTest.java b/src/test/java/testsuite/simple/QueryAttributesTest.java new file mode 100644 index 000000000..8d662069e --- /dev/null +++ b/src/test/java/testsuite/simple/QueryAttributesTest.java @@ -0,0 +1,1004 @@ +/* + * Copyright (c) 2021, 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 also distributed with certain software (including but not + * limited to OpenSSL) 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 included with MySQL. + * + * 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 testsuite.simple; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +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.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import java.util.TimeZone; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mysql.cj.conf.PropertyKey; +import com.mysql.cj.jdbc.JdbcStatement; + +import testsuite.BaseTestCase; + +public class QueryAttributesTest extends BaseTestCase { + @BeforeEach + public void setUp() throws Exception { + assumeTrue(versionMeetsMinimum(8, 0, 26), "MySQL 8.0.26+ is required to run this test."); + + this.rs = this.stmt.executeQuery("SELECT * FROM mysql.component WHERE component_urn = 'file://component_query_attributes'"); + if (!this.rs.next()) { + this.stmt.execute("INSTALL COMPONENT 'file://component_query_attributes'"); + } + } + + @AfterEach + public void teardown() throws Exception { + if (versionMeetsMinimum(8, 0, 26)) { + this.stmt.execute("UNINSTALL COMPONENT 'file://component_query_attributes'"); + } + } + + /** + * Tests all supported query attributes types when used in plain statements. + * + * @throws Exception + */ + @Test + public void queryAttributesTypesInPlainStatement() throws Exception { + long testInstInMilli = 801216026987l; // Tuesday, May 23, 1995 08:00:26.987654 GMT + long testInstInSecs = 801216026; + int testInstHour = 8; + int testInstMin = 0; + int testInstSec = 26; + int testInstNano = 987654321; + int testOffset = 2; + String testZoneId = "UTC+2"; + String testTimezone = "Europe/Stockholm"; + Calendar testCal = Calendar.getInstance(TimeZone.getTimeZone(testTimezone)); + testCal.setTimeInMillis(testInstInMilli); + List testList = Arrays.asList("MySQL", "Connector/J"); + + String expectedLocalTime = new SimpleDateFormat("HH:mm:ss.SSS000").format(new Date(testInstInMilli)); + String expectedLocalDatetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000").format(new Date(testInstInMilli)) + + new SimpleDateFormat("XXX").format(new Date(testInstInMilli)).replaceAll("([+-])0", "$1").replace("Z", "+0:00"); + + Statement testStmt = this.conn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa01", null); + testJdbcStmt.setAttribute("qa02", "Query Attributes"); + testJdbcStmt.setAttribute("qa03", false); + testJdbcStmt.setAttribute("qa04", (byte) 42); + testJdbcStmt.setAttribute("qa05", (short) -42); + testJdbcStmt.setAttribute("qa06", Integer.MAX_VALUE); + testJdbcStmt.setAttribute("qa07", Long.MAX_VALUE); + testJdbcStmt.setAttribute("qa08", new BigInteger("351910092110")); + testJdbcStmt.setAttribute("qa09", 2.71828182f); + testJdbcStmt.setAttribute("qa10", 3.141592653589793d); + testJdbcStmt.setAttribute("qa11", new BigDecimal("1.61803398874989484820")); + testJdbcStmt.setAttribute("qa12", new java.sql.Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa13", LocalDate.of(1995, 5, 23)); + testJdbcStmt.setAttribute("qa14", new Time(testInstInMilli)); + testJdbcStmt.setAttribute("qa15", LocalTime.of(testInstHour, testInstMin, testInstSec, testInstNano)); + testJdbcStmt.setAttribute("qa16", OffsetTime.of(testInstHour, testInstMin, testInstSec, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa17", Duration.ofDays(-2).plusHours(2).plusMinutes(20)); + testJdbcStmt.setAttribute("qa18", LocalDateTime.ofEpochSecond(testInstInSecs, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa19", new Timestamp(testInstInMilli)); + testJdbcStmt.setAttribute("qa20", Instant.ofEpochMilli(testInstInMilli)); + testJdbcStmt.setAttribute("qa21", OffsetDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testZoneId))); + testJdbcStmt.setAttribute("qa22", ZonedDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testTimezone))); + testJdbcStmt.setAttribute("qa23", new Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa24", testCal); + testJdbcStmt.setAttribute("qa25", testList); + + this.rs = testStmt.executeQuery("SELECT 'MySQL Connector/J', " + IntStream.range(1, 26) + .mapToObj(i -> String.format("mysql_query_attribute_string('qa%1$02d') AS qa%1$02d", i)).collect(Collectors.joining(", ")) + ", '8.0.26'"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa01")); + assertTrue(this.rs.wasNull()); + assertEquals("Query Attributes", this.rs.getString("qa02")); + assertEquals("0", this.rs.getString("qa03")); + assertEquals("42", this.rs.getString("qa04")); + assertEquals("-42", this.rs.getString("qa05")); + assertEquals("2147483647", this.rs.getString("qa06")); + assertEquals("9223372036854775807", this.rs.getString("qa07")); + assertEquals("351910092110", this.rs.getString("qa08")); + assertTrue(this.rs.getString("qa09").startsWith("2.71828")); + assertTrue(this.rs.getString("qa10").startsWith("3.14159")); + assertTrue(this.rs.getString("qa11").startsWith("1.61803")); + assertEquals("1995-05-23", this.rs.getString("qa12")); + assertEquals("1995-05-23", this.rs.getString("qa13")); + assertEquals(expectedLocalTime, this.rs.getString("qa14")); + assertEquals("08:00:26.987654", this.rs.getString("qa15")); + assertEquals("08:00:26.987654", this.rs.getString("qa16")); + assertEquals("-45:40:00.000000", this.rs.getString("qa17")); + assertEquals("1995-05-23 10:00:26.987654", this.rs.getString("qa18")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa19")); + assertEquals("1995-05-23 08:00:26.987000+0:00", this.rs.getString("qa20")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa21")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa22")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa23")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa24")); + assertEquals("[MySQL, Connector/J]", this.rs.getString("qa25")); + assertEquals("8.0.26", this.rs.getString(27)); + assertFalse(this.rs.next()); + } + + /** + * Tests all supported query attributes types when used in client prepared statements. + * + * @throws Exception + */ + @Test + public void queryAttributesTypesInClientPreparedStatement() throws Exception { + long testInstInMilli = 801216026987l; // Tuesday, May 23, 1995 08:00:26.987654 GMT + long testInstInSecs = 801216026; + int testInstHour = 8; + int testInstMin = 0; + int testInstSec = 26; + int testInstNano = 987654321; + int testOffset = 2; + String testZoneId = "UTC+2"; + String testTimezone = "Europe/Stockholm"; + Calendar testCal = Calendar.getInstance(TimeZone.getTimeZone(testTimezone)); + testCal.setTimeInMillis(testInstInMilli); + List testList = Arrays.asList("MySQL", "Connector/J"); + + String expectedLocalTime = new SimpleDateFormat("HH:mm:ss.SSS000").format(new Date(testInstInMilli)); + String expectedLocalDatetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000").format(new Date(testInstInMilli)) + + new SimpleDateFormat("XXX").format(new Date(testInstInMilli)).replaceAll("([+-])0", "$1").replace("Z", "+0:00"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn.prepareStatement("SELECT ?, " + IntStream.range(1, 26) + .mapToObj(i -> String.format("mysql_query_attribute_string('qa%1$02d') AS qa%1$02d", i)).collect(Collectors.joining(", ")) + ", ?"); + testPstmt.setString(1, "MySQL Connector/J"); + testPstmt.setString(2, "8.0.26"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa01", null); + testJdbcStmt.setAttribute("qa02", "Query Attributes"); + testJdbcStmt.setAttribute("qa03", false); + testJdbcStmt.setAttribute("qa04", (byte) 42); + testJdbcStmt.setAttribute("qa05", (short) -42); + testJdbcStmt.setAttribute("qa06", Integer.MAX_VALUE); + testJdbcStmt.setAttribute("qa07", Long.MAX_VALUE); + testJdbcStmt.setAttribute("qa08", new BigInteger("351910092110")); + testJdbcStmt.setAttribute("qa09", 2.71828182f); + testJdbcStmt.setAttribute("qa10", 3.141592653589793d); + testJdbcStmt.setAttribute("qa11", new BigDecimal("1.61803398874989484820")); + testJdbcStmt.setAttribute("qa12", new java.sql.Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa13", LocalDate.of(1995, 5, 23)); + testJdbcStmt.setAttribute("qa14", new Time(testInstInMilli)); + testJdbcStmt.setAttribute("qa15", LocalTime.of(testInstHour, testInstMin, testInstSec, testInstNano)); + testJdbcStmt.setAttribute("qa16", OffsetTime.of(testInstHour, testInstMin, testInstSec, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa17", Duration.ofDays(-2).plusHours(2).plusMinutes(20)); + testJdbcStmt.setAttribute("qa18", LocalDateTime.ofEpochSecond(testInstInSecs, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa19", new Timestamp(testInstInMilli)); + testJdbcStmt.setAttribute("qa20", Instant.ofEpochMilli(testInstInMilli)); + testJdbcStmt.setAttribute("qa21", OffsetDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testZoneId))); + testJdbcStmt.setAttribute("qa22", ZonedDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testTimezone))); + testJdbcStmt.setAttribute("qa23", new Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa24", testCal); + testJdbcStmt.setAttribute("qa25", testList); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa01")); + assertTrue(this.rs.wasNull()); + assertEquals("Query Attributes", this.rs.getString("qa02")); + assertEquals("0", this.rs.getString("qa03")); + assertEquals("42", this.rs.getString("qa04")); + assertEquals("-42", this.rs.getString("qa05")); + assertEquals("2147483647", this.rs.getString("qa06")); + assertEquals("9223372036854775807", this.rs.getString("qa07")); + assertEquals("351910092110", this.rs.getString("qa08")); + assertTrue(this.rs.getString("qa09").startsWith("2.71828")); + assertTrue(this.rs.getString("qa10").startsWith("3.14159")); + assertTrue(this.rs.getString("qa11").startsWith("1.61803")); + assertEquals("1995-05-23", this.rs.getString("qa12")); + assertEquals("1995-05-23", this.rs.getString("qa13")); + assertEquals(expectedLocalTime, this.rs.getString("qa14")); + assertEquals("08:00:26.987654", this.rs.getString("qa15")); + assertEquals("08:00:26.987654", this.rs.getString("qa16")); + assertEquals("-45:40:00.000000", this.rs.getString("qa17")); + assertEquals("1995-05-23 10:00:26.987654", this.rs.getString("qa18")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa19")); + assertEquals("1995-05-23 08:00:26.987000+0:00", this.rs.getString("qa20")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa21")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa22")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa23")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa24")); + assertEquals("[MySQL, Connector/J]", this.rs.getString("qa25")); + assertEquals("8.0.26", this.rs.getString(27)); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests all supported query attributes types when used in server prepared statements. + * + * @throws Exception + */ + @Test + public void queryAttributesTypesInServerPreparedStatement() throws Exception { + long testInstInMilli = 801216026987l; // Tuesday, May 23, 1995 08:00:26.987654 GMT + long testInstInSecs = 801216026; + int testInstHour = 8; + int testInstMin = 0; + int testInstSec = 26; + int testInstNano = 987654321; + int testOffset = 2; + String testZoneId = "UTC+2"; + String testTimezone = "Europe/Stockholm"; + Calendar testCal = Calendar.getInstance(TimeZone.getTimeZone(testTimezone)); + testCal.setTimeInMillis(testInstInMilli); + List testList = Arrays.asList("MySQL", "Connector/J"); + + String expectedLocalTime = new SimpleDateFormat("HH:mm:ss.SSS000").format(new Date(testInstInMilli)); + String expectedLocalDatetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000").format(new Date(testInstInMilli)) + + new SimpleDateFormat("XXX").format(new Date(testInstInMilli)).replaceAll("([+-])0", "$1").replace("Z", "+0:00"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn.prepareStatement("SELECT ?, " + IntStream.range(1, 26) + .mapToObj(i -> String.format("mysql_query_attribute_string('qa%1$02d') AS qa%1$02d", i)).collect(Collectors.joining(", ")) + ", ?"); + testPstmt.setString(1, "MySQL Connector/J"); + testPstmt.setString(2, "8.0.26"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa01", null); + testJdbcStmt.setAttribute("qa02", "Query Attributes"); + testJdbcStmt.setAttribute("qa03", false); + testJdbcStmt.setAttribute("qa04", (byte) 42); + testJdbcStmt.setAttribute("qa05", (short) -42); + testJdbcStmt.setAttribute("qa06", Integer.MAX_VALUE); + testJdbcStmt.setAttribute("qa07", Long.MAX_VALUE); + testJdbcStmt.setAttribute("qa08", new BigInteger("351910092110")); + testJdbcStmt.setAttribute("qa09", 2.71828182f); + testJdbcStmt.setAttribute("qa10", 3.141592653589793d); + testJdbcStmt.setAttribute("qa11", new BigDecimal("1.61803398874989484820")); + testJdbcStmt.setAttribute("qa12", new java.sql.Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa13", LocalDate.of(1995, 5, 23)); + testJdbcStmt.setAttribute("qa14", new Time(testInstInMilli)); + testJdbcStmt.setAttribute("qa15", LocalTime.of(testInstHour, testInstMin, testInstSec, testInstNano)); + testJdbcStmt.setAttribute("qa16", OffsetTime.of(testInstHour, testInstMin, testInstSec, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa17", Duration.ofDays(-2).plusHours(2).plusMinutes(20)); + testJdbcStmt.setAttribute("qa18", LocalDateTime.ofEpochSecond(testInstInSecs, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa19", new Timestamp(testInstInMilli)); + testJdbcStmt.setAttribute("qa20", Instant.ofEpochMilli(testInstInMilli)); + testJdbcStmt.setAttribute("qa21", OffsetDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testZoneId))); + testJdbcStmt.setAttribute("qa22", ZonedDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testTimezone))); + testJdbcStmt.setAttribute("qa23", new Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa24", testCal); + testJdbcStmt.setAttribute("qa25", testList); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa01")); + assertTrue(this.rs.wasNull()); + assertEquals("Query Attributes", this.rs.getString("qa02")); + assertEquals("0", this.rs.getString("qa03")); + assertEquals("42", this.rs.getString("qa04")); + assertEquals("-42", this.rs.getString("qa05")); + assertEquals("2147483647", this.rs.getString("qa06")); + assertEquals("9223372036854775807", this.rs.getString("qa07")); + assertEquals("351910092110", this.rs.getString("qa08")); + assertTrue(this.rs.getString("qa09").startsWith("2.71828")); + assertTrue(this.rs.getString("qa10").startsWith("3.14159")); + assertTrue(this.rs.getString("qa11").startsWith("1.61803")); + assertEquals("1995-05-23", this.rs.getString("qa12")); + assertEquals("1995-05-23", this.rs.getString("qa13")); + assertEquals(expectedLocalTime, this.rs.getString("qa14")); + assertEquals("08:00:26.987654", this.rs.getString("qa15")); + assertEquals("08:00:26.987654", this.rs.getString("qa16")); + assertEquals("-45:40:00.000000", this.rs.getString("qa17")); + assertEquals("1995-05-23 10:00:26.987654", this.rs.getString("qa18")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa19")); + assertEquals("1995-05-23 08:00:26.987000+0:00", this.rs.getString("qa20")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa21")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa22")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa23")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa24")); + assertEquals("[MySQL, Connector/J]", this.rs.getString("qa25")); + assertEquals("8.0.26", this.rs.getString(27)); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests all supported query attributes types when used in callable statements. + * + * @throws Exception + */ + @Test + public void queryAttributesTypesInCallableStatement() throws Exception { + long testInstInMilli = 801216026987l; // Tuesday, May 23, 1995 08:00:26.987654 GMT + long testInstInSecs = 801216026; + int testInstHour = 8; + int testInstMin = 0; + int testInstSec = 26; + int testInstNano = 987654321; + int testOffset = 2; + String testZoneId = "UTC+2"; + String testTimezone = "Europe/Stockholm"; + Calendar testCal = Calendar.getInstance(TimeZone.getTimeZone(testTimezone)); + testCal.setTimeInMillis(testInstInMilli); + List testList = Arrays.asList("MySQL", "Connector/J"); + + String expectedLocalTime = new SimpleDateFormat("HH:mm:ss.SSS000").format(new Date(testInstInMilli)); + String expectedLocalDatetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000").format(new Date(testInstInMilli)) + + new SimpleDateFormat("XXX").format(new Date(testInstInMilli)).replaceAll("([+-])0", "$1").replace("Z", "+0:00"); + + createProcedure("testQueryAttrTypes", + "(IN p1 VARCHAR(100), IN p2 VARCHAR(100)) BEGIN SELECT p1, " + IntStream.range(1, 26) + .mapToObj(i -> String.format("mysql_query_attribute_string('qa%1$02d') AS qa%1$02d", i)).collect(Collectors.joining(", ")) + + ", p2; END"); + + PreparedStatement testCstmt = this.conn.prepareCall("{ CALL testQueryAttrTypes(?, ?) }"); + testCstmt.setString(1, "MySQL Connector/J"); + testCstmt.setString(2, "8.0.26"); + + assertTrue(JdbcStatement.class.isInstance(testCstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testCstmt; + testJdbcStmt.setAttribute("qa01", null); + testJdbcStmt.setAttribute("qa02", "Query Attributes"); + testJdbcStmt.setAttribute("qa03", false); + testJdbcStmt.setAttribute("qa04", (byte) 42); + testJdbcStmt.setAttribute("qa05", (short) -42); + testJdbcStmt.setAttribute("qa06", Integer.MAX_VALUE); + testJdbcStmt.setAttribute("qa07", Long.MAX_VALUE); + testJdbcStmt.setAttribute("qa08", new BigInteger("351910092110")); + testJdbcStmt.setAttribute("qa09", 2.71828182f); + testJdbcStmt.setAttribute("qa10", 3.141592653589793d); + testJdbcStmt.setAttribute("qa11", new BigDecimal("1.61803398874989484820")); + testJdbcStmt.setAttribute("qa12", new java.sql.Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa13", LocalDate.of(1995, 5, 23)); + testJdbcStmt.setAttribute("qa14", new Time(testInstInMilli)); + testJdbcStmt.setAttribute("qa15", LocalTime.of(testInstHour, testInstMin, testInstSec, testInstNano)); + testJdbcStmt.setAttribute("qa16", OffsetTime.of(testInstHour, testInstMin, testInstSec, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa17", Duration.ofDays(-2).plusHours(2).plusMinutes(20)); + testJdbcStmt.setAttribute("qa18", LocalDateTime.ofEpochSecond(testInstInSecs, testInstNano, ZoneOffset.ofHours(testOffset))); + testJdbcStmt.setAttribute("qa19", new Timestamp(testInstInMilli)); + testJdbcStmt.setAttribute("qa20", Instant.ofEpochMilli(testInstInMilli)); + testJdbcStmt.setAttribute("qa21", OffsetDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testZoneId))); + testJdbcStmt.setAttribute("qa22", ZonedDateTime.ofInstant(Instant.ofEpochMilli(testInstInMilli), ZoneId.of(testTimezone))); + testJdbcStmt.setAttribute("qa23", new Date(testInstInMilli)); + testJdbcStmt.setAttribute("qa24", testCal); + testJdbcStmt.setAttribute("qa25", testList); + + this.rs = testCstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa01")); + assertTrue(this.rs.wasNull()); + assertEquals("Query Attributes", this.rs.getString("qa02")); + assertEquals("0", this.rs.getString("qa03")); + assertEquals("42", this.rs.getString("qa04")); + assertEquals("-42", this.rs.getString("qa05")); + assertEquals("2147483647", this.rs.getString("qa06")); + assertEquals("9223372036854775807", this.rs.getString("qa07")); + assertEquals("351910092110", this.rs.getString("qa08")); + assertTrue(this.rs.getString("qa09").startsWith("2.71828")); + assertTrue(this.rs.getString("qa10").startsWith("3.14159")); + assertTrue(this.rs.getString("qa11").startsWith("1.61803")); + assertEquals("1995-05-23", this.rs.getString("qa12")); + assertEquals("1995-05-23", this.rs.getString("qa13")); + assertEquals(expectedLocalTime, this.rs.getString("qa14")); + assertEquals("08:00:26.987654", this.rs.getString("qa15")); + assertEquals("08:00:26.987654", this.rs.getString("qa16")); + assertEquals("-45:40:00.000000", this.rs.getString("qa17")); + assertEquals("1995-05-23 10:00:26.987654", this.rs.getString("qa18")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa19")); + assertEquals("1995-05-23 08:00:26.987000+0:00", this.rs.getString("qa20")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa21")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa22")); + assertEquals(expectedLocalDatetime, this.rs.getString("qa23")); + assertEquals("1995-05-23 10:00:26.987000+2:00", this.rs.getString("qa24")); + assertEquals("[MySQL, Connector/J]", this.rs.getString("qa25")); + assertEquals("8.0.26", this.rs.getString(27)); + assertFalse(this.rs.next()); + } + + /** + * Tests if query attributes are preserved between plain statement executions and cleared after calling the 'clearAttributes' method. + * + * @throws Exception + */ + @Test + public void preserveAndClearAttributesInPlainStatement() throws Exception { + Statement testStmt = this.conn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa", "8.0.26"); + + for (int c = 0; c < 2; c++) { + this.rs = testStmt.executeQuery("SELECT 'MySQL Connector/J', mysql_query_attribute_string('qa') AS qa"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertEquals("8.0.26", this.rs.getString("qa")); + assertFalse(this.rs.next()); + } // Execute twice. Query Attributes must be preserved. + + testJdbcStmt.clearAttributes(); + + this.rs = testStmt.executeQuery("SELECT 'MySQL Connector/J', mysql_query_attribute_string('qa') AS qa"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa")); + assertTrue(this.rs.wasNull()); + assertFalse(this.rs.next()); + } + + /** + * Tests if query attributes are preserved between client prepared statement executions and cleared after calling the 'clearAttributes' method. + * + * @throws Exception + */ + @Test + public void preserveAndClearAttributesInClientPreparedStatement() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn.prepareStatement("SELECT ?, mysql_query_attribute_string('qa') AS qa"); + testPstmt.setString(1, "MySQL Connector/J"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "8.0.26"); + + for (int c = 0; c < 2; c++) { + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertEquals("8.0.26", this.rs.getString("qa")); + assertFalse(this.rs.next()); + } // Execute twice. Query Attributes must be preserved. + + testJdbcStmt.clearAttributes(); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa")); + assertTrue(this.rs.wasNull()); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests if query attributes are preserved between server prepared statement executions and cleared after calling the 'clearAttributes' method. + * + * @throws Exception + */ + @Test + public void preserveAndClearAttributesInServerPreparedStatement() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn.prepareStatement("SELECT ?, mysql_query_attribute_string('qa') AS qa"); + testPstmt.setString(1, "MySQL Connector/J"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "8.0.26"); + + for (int c = 0; c < 2; c++) { + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertEquals("8.0.26", this.rs.getString("qa")); + assertFalse(this.rs.next()); + } // Execute twice. Query Attributes must be preserved. + + testJdbcStmt.clearAttributes(); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa")); + assertTrue(this.rs.wasNull()); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests if query attributes are preserved between callable statement executions and cleared after calling the 'clearAttributes' method. + * + * @throws Exception + */ + @Test + public void preserveAndClearAttributesInCallableStatement() throws Exception { + createProcedure("testQueryAttrPreserveAndClear", "(IN p1 VARCHAR(100)) BEGIN SELECT p1, mysql_query_attribute_string('qa') AS qa; END"); + + PreparedStatement testCstmt = this.conn.prepareCall("{ CALL testQueryAttrPreserveAndClear(?) }"); + testCstmt.setString(1, "MySQL Connector/J"); + + assertTrue(JdbcStatement.class.isInstance(testCstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testCstmt; + testJdbcStmt.setAttribute("qa", "8.0.26"); + + for (int c = 0; c < 2; c++) { + this.rs = testCstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertEquals("8.0.26", this.rs.getString("qa")); + assertFalse(this.rs.next()); + } // Execute twice. Query Attributes must be preserved. + + testJdbcStmt.clearAttributes(); + + this.rs = testCstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertNull(this.rs.getString("qa")); + assertTrue(this.rs.wasNull()); + assertFalse(this.rs.next()); + } + + /** + * Tests if query attributes hold in plain statements with multi-queries. + * + * @throws Exception + */ + @Test + public void multiQueriesWithAttributesInPlainStatement() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + Statement testStmt = testConn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa01", "MySQL Connector/J"); + testJdbcStmt.setAttribute("qa02", "8.0.26"); + + this.rs = testStmt.executeQuery("SELECT mysql_query_attribute_string('qa01') AS qa01; SELECT mysql_query_attribute_string('qa02') AS qa02;"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString("qa01")); + assertFalse(this.rs.next()); + + assertTrue(testStmt.getMoreResults()); + this.rs = testStmt.getResultSet(); + assertTrue(this.rs.next()); + assertEquals("8.0.26", this.rs.getString("qa02")); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests if query attributes hold in prepared statements with multi-queries. + * + * @throws Exception + */ + @Test + public void multiQueriesWithAttributesInPreparedStatement() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // Will fall-back to client prepared statement, anyway. + props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn + .prepareStatement("SELECT mysql_query_attribute_string('qa01') AS qa01; SELECT mysql_query_attribute_string('qa02') AS qa02;"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa01", "MySQL Connector/J"); + testJdbcStmt.setAttribute("qa02", "8.0.26"); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString("qa01")); + assertFalse(this.rs.next()); + + assertTrue(testPstmt.getMoreResults()); + this.rs = testPstmt.getResultSet(); + assertTrue(this.rs.next()); + assertEquals("8.0.26", this.rs.getString("qa02")); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests whether the query attributes are propagated to the internally created statement on query rewrites in plain statements. + * + * @throws Exception + */ + @Test + void rewriteQueriesWithAttributesInPlainStatement() throws Exception { + createTable("testRewritePlainStmt", "(c1 VARCHAR(100), c2 VARCHAR(100))"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + Statement testStmt = testConn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + testStmt.addBatch("INSERT INTO testRewritePlainStmt VALUES ('Row 1', mysql_query_attribute_string('qa'))"); + testStmt.addBatch("INSERT INTO testRewritePlainStmt VALUES ('Row 2', mysql_query_attribute_string('qa'))"); + testStmt.addBatch("INSERT INTO testRewritePlainStmt VALUES ('Row 3', mysql_query_attribute_string('qa'))"); + testStmt.addBatch("INSERT INTO testRewritePlainStmt VALUES ('Row 4', mysql_query_attribute_string('qa'))"); + testStmt.addBatch("INSERT INTO testRewritePlainStmt VALUES ('Row 5', mysql_query_attribute_string('qa'))"); + testStmt.executeBatch(); // Need 5 to rewrite as multi-queries. + + this.rs = this.stmt.executeQuery("SELECT * FROM testRewritePlainStmt"); + assertTrue(this.rs.next()); + assertEquals("Row 1", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 2", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 3", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 4", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 5", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests whether the query attributes are propagated to the internally created statement on query rewrites in client prepared statements. + * + * @throws Exception + */ + @Test + void rewriteQueriesWithAttributesInClientPreparedStatement() throws Exception { + createTable("testRewriteClientPstmt", "(c1 VARCHAR(100), c2 VARCHAR(100))"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn.prepareStatement("INSERT INTO testRewriteClientPstmt VALUES (?, mysql_query_attribute_string('qa'))"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + testPstmt.setString(1, "Row 1"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 2"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 3"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 4"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 5"); + testPstmt.addBatch(); + testPstmt.executeBatch(); + + this.rs = this.stmt.executeQuery("SELECT * FROM testRewriteClientPstmt"); + assertTrue(this.rs.next()); + assertEquals("Row 1", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 2", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 3", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 4", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 5", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests whether the query attributes are propagated to the internally created statement on query rewrites in server prepared statements. + * + * @throws Exception + */ + @Test + void rewriteQueriesWithAttributesInServerPreparedStatement() throws Exception { + createTable("testRewriteServerPstmt", "(c1 VARCHAR(100), c2 VARCHAR(100))"); + + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt = testConn.prepareStatement("INSERT INTO testRewriteServerPstmt VALUES (?, mysql_query_attribute_string('qa'))"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + testPstmt.setString(1, "Row 1"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 2"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 3"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 4"); + testPstmt.addBatch(); + testPstmt.setString(1, "Row 5"); + testPstmt.addBatch(); + testPstmt.executeBatch(); + + this.rs = this.stmt.executeQuery("SELECT * FROM testRewriteServerPstmt"); + assertTrue(this.rs.next()); + assertEquals("Row 1", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 2", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 3", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 4", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertTrue(this.rs.next()); + assertEquals("Row 5", this.rs.getString(1)); + assertEquals("MySQL Connector/J", this.rs.getString(2)); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests if server prepared statements get their query attributes cleared automatically when cached. + * + * @throws Exception + */ + @Test + void cachedServerPreparedStatementsWithQueryAttributes() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); + + PreparedStatement testPstmt1 = testConn.prepareStatement("SELECT ?, mysql_query_attribute_string('qa')"); + testPstmt1.setString(1, "Param 1"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt1)); + JdbcStatement testJdbcStmt1 = (JdbcStatement) testPstmt1; + testJdbcStmt1.setAttribute("qa", "QA 1"); + + this.rs = testPstmt1.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("Param 1", this.rs.getString(1)); + assertEquals("QA 1", this.rs.getString(2)); + assertFalse(this.rs.next()); + + testPstmt1.close(); + + PreparedStatement testPstmt2 = testConn.prepareStatement("SELECT ?, mysql_query_attribute_string('qa')"); + assertSame(testPstmt1, testPstmt2); + testPstmt2.setString(1, "Param 2"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt2)); + JdbcStatement testJdbcStmt2 = (JdbcStatement) testPstmt2; + testJdbcStmt2.setAttribute("qa", "QA 2"); + + this.rs = testPstmt2.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("Param 2", this.rs.getString(1)); + assertEquals("QA 2", this.rs.getString(2)); + assertFalse(this.rs.next()); + + testPstmt2.close(); + + PreparedStatement testPstmt3 = testConn.prepareStatement("SELECT ?, mysql_query_attribute_string('qa')"); + assertSame(testPstmt1, testPstmt3); + testPstmt3.setString(1, "Param 3"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt3)); + JdbcStatement testJdbcStmt3 = (JdbcStatement) testPstmt3; + testJdbcStmt3.setAttribute("qa_new", "QA 3"); // Don't set or set different query attribute. + + this.rs = testPstmt3.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("Param 3", this.rs.getString(1)); + assertNull(this.rs.getString(2)); + assertTrue(this.rs.wasNull()); + assertFalse(this.rs.next()); + + testPstmt3.close(); + + testConn.close(); + } + + /** + * Tests whether proxied plain statement objects created in multi-host connections handle query attributes correctly. + * + * @throws Exception + */ + @Test + void plainStatementWithQueryAttributesInMultiHost() throws Exception { + // Failover connection. + Connection testConn = getFailoverConnection(); + + Statement testStmt = testConn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + this.rs = testStmt.executeQuery("SELECT mysql_query_attribute_string('qa')"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertFalse(this.rs.next()); + + testConn.close(); + + // Loadbalanced connection. + testConn = getLoadBalancedConnection(); + + testStmt = testConn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + this.rs = testStmt.executeQuery("SELECT mysql_query_attribute_string('qa')"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertFalse(this.rs.next()); + + testConn.close(); + + // Replication connection. + testConn = getSourceReplicaReplicationConnection(); + + testStmt = testConn.createStatement(); + + assertTrue(JdbcStatement.class.isInstance(testStmt)); + testJdbcStmt = (JdbcStatement) testStmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + this.rs = testStmt.executeQuery("SELECT mysql_query_attribute_string('qa')"); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertFalse(this.rs.next()); + + testConn.close(); + } + + /** + * Tests whether proxied server prepared statement objects created in multi-host connections handle query attributes correctly. + * + * @throws Exception + */ + @Test + void serverPreparedStatementWithQueryAttributesInMultiHost() throws Exception { + Properties props = new Properties(); + props.setProperty("useServerPrepStmts", "true"); + + // Failover connection. + Connection testConn = getFailoverConnection(props); + + PreparedStatement testPstmt = testConn.prepareStatement("SELECT mysql_query_attribute_string('qa')"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + JdbcStatement testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertFalse(this.rs.next()); + + testConn.close(); + + // Loadbalanced connection. + testConn = getLoadBalancedConnection(props); + + testPstmt = testConn.prepareStatement("SELECT mysql_query_attribute_string('qa')"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertFalse(this.rs.next()); + + testConn.close(); + + // Replication connection. + testConn = getSourceReplicaReplicationConnection(props); + + testPstmt = testConn.prepareStatement("SELECT mysql_query_attribute_string('qa')"); + + assertTrue(JdbcStatement.class.isInstance(testPstmt)); + testJdbcStmt = (JdbcStatement) testPstmt; + testJdbcStmt.setAttribute("qa", "MySQL Connector/J"); + + this.rs = testPstmt.executeQuery(); + assertTrue(this.rs.next()); + assertEquals("MySQL Connector/J", this.rs.getString(1)); + assertFalse(this.rs.next()); + + testConn.close(); + } +} diff --git a/src/test/java/testsuite/simple/ReadOnlyCallableStatementTest.java b/src/test/java/testsuite/simple/ReadOnlyCallableStatementTest.java index 5eea3d7d2..421c01f8a 100644 --- a/src/test/java/testsuite/simple/ReadOnlyCallableStatementTest.java +++ b/src/test/java/testsuite/simple/ReadOnlyCallableStatementTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -57,7 +57,10 @@ public void testReadOnlyWithProcBodyAccess() throws Exception { createProcedure("`testProc.1`", "()\nREADS SQL DATA\nbegin\nSELECT NOW();\nend\n"); - replConn = getSourceReplicaReplicationConnection(); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + replConn = getSourceReplicaReplicationConnection(props2); replConn.setReadOnly(true); CallableStatement cstmt = replConn.prepareCall("CALL testProc1()"); @@ -93,7 +96,10 @@ public void testNotReadOnlyWithProcBodyAccess() throws Exception { createProcedure("`testProc.2`", "()\nMODIFIES SQL DATA\nbegin\nSELECT NOW();\nend\n"); - replConn = getSourceReplicaReplicationConnection(); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + replConn = getSourceReplicaReplicationConnection(props2); replConn.setReadOnly(true); CallableStatement cstmt = replConn.prepareCall("CALL testProc2()"); diff --git a/src/test/java/testsuite/simple/ResultSetTest.java b/src/test/java/testsuite/simple/ResultSetTest.java index beab3464f..f065ff62e 100644 --- a/src/test/java/testsuite/simple/ResultSetTest.java +++ b/src/test/java/testsuite/simple/ResultSetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. + * Copyright (c) 2005, 2021, 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 @@ -65,7 +65,6 @@ import org.junit.jupiter.api.Test; -import com.mysql.cj.CharsetMapping; import com.mysql.cj.MysqlConnection; import com.mysql.cj.MysqlType; import com.mysql.cj.conf.PropertyKey; @@ -83,18 +82,16 @@ public void testPadding() throws Exception { int numChars = 32; // build map of charsets supported by server - Connection c = getConnectionWithProps("detectCustomCollations=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.detectCustomCollations.getKeyName(), "true"); + Connection c = getConnectionWithProps(props); Map charsetsMap = new HashMap<>(); this.rs = this.stmt.executeQuery("SHOW COLLATION"); while (this.rs.next()) { int index = ((Number) this.rs.getObject(3)).intValue(); - String charsetName = null; - if (((ConnectionImpl) c).getSession().getProtocol().getServerSession().indexToCustomMysqlCharset != null) { - charsetName = ((ConnectionImpl) c).getSession().getProtocol().getServerSession().indexToCustomMysqlCharset.get(index); - } - if (charsetName == null) { - charsetName = CharsetMapping.getMysqlCharsetNameForCollationIndex(index); - } + String charsetName = ((ConnectionImpl) c).getSession().getServerSession().getCharsetSettings().getMysqlCharsetNameForCollationIndex(index); if (charsetName != null) { charsetsMap.put(charsetName, index); } @@ -156,10 +153,12 @@ public void testPadding() throws Exception { "INSERT INTO testPadding VALUES (" + emptyBuf.toString() + ", 1), (" + abcBuf.toString() + ", 2), (" + repeatBuf.toString() + ", 3)"); try { - Properties props = new Properties(); - props.setProperty(PropertyKey.padCharsWithSpace.getKeyName(), "true"); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props2.setProperty(PropertyKey.padCharsWithSpace.getKeyName(), "true"); - paddedConn = getConnectionWithProps(props); + paddedConn = getConnectionWithProps(props2); testPaddingForConnection(paddedConn, numChars, selectBuf); } finally { @@ -280,10 +279,14 @@ public void testWarningOnTimestampTruncation() throws SQLException { @Test public void testDateTimeRetrieval() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.connectionTimeZone.getKeyName(), "LOCAL"); Connection testConn = getConnectionWithProps(props); testDateTimeRetrieval_internal(testConn); - Connection sspsConn = getConnectionWithProps("connectionTimeZone=LOCAL,useServerPrepStmts=true"); + + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection sspsConn = getConnectionWithProps(props); testDateTimeRetrieval_internal(sspsConn); sspsConn.close(); } @@ -408,6 +411,8 @@ public void testUpdResultSetUpdateObjectAndNewSupportedTypes() throws Exception createTable("testUpdateObject1", "(id INT PRIMARY KEY, d DATE, t TIME, dt DATETIME, ts TIMESTAMP)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); Connection testConn = getConnectionWithProps(timeZoneFreeDbUrl, props); @@ -425,7 +430,10 @@ public void testUpdResultSetUpdateObjectAndNewSupportedTypes() throws Exception LocalTime testLocalTime = LocalTime.parse(testTimeString); LocalDateTime testLocalDateTime = LocalDateTime.parse(testISODateTimeString); - Connection testConn2 = getConnectionWithProps(timeZoneFreeDbUrl, new Properties()); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED"); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + Connection testConn2 = getConnectionWithProps(timeZoneFreeDbUrl, props2); TimeZone sessionTz = ((MysqlConnection) testConn2).getSession().getServerSession().getSessionTimeZone(); Date testSqlDate = Date.valueOf(testDateString); @@ -676,7 +684,7 @@ public void testUpdResultSetUpdateObjectAndNewSupportedTypes() throws Exception this.rs.updateObject("odt2", testOffsetDateTime); this.rs.insertRow(); - testConn = getConnectionWithProps(timeZoneFreeDbUrl, new Properties()); + testConn = getConnectionWithProps(timeZoneFreeDbUrl, props2); testStmt = testConn.createStatement(); long expTimeSeconds = testOffsetTime.atDate(LocalDate.now()).toEpochSecond(); diff --git a/src/test/java/testsuite/simple/SSLTest.java b/src/test/java/testsuite/simple/SSLTest.java index c4fa05855..6ffe06c77 100644 --- a/src/test/java/testsuite/simple/SSLTest.java +++ b/src/test/java/testsuite/simple/SSLTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -29,9 +29,14 @@ package testsuite.simple; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.mysql.cj.MysqlConnection; +import com.mysql.cj.protocol.a.NativeServerSession; + import testsuite.BaseTestCase; /** @@ -49,6 +54,13 @@ public void setUp() { */ @Test public void testConnect() throws Exception { + assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, + "This test requires server with SSL support."); + assumeTrue(supportsTLSv1_2(((MysqlConnection) this.conn).getSession().getServerSession().getServerVersion()), + "This test requires server with TLSv1.2+ support."); + assumeTrue(supportsTestCertificates(this.stmt), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + System.setProperty("javax.net.debug", "all"); String dbUrlLocal = dbUrl; diff --git a/src/test/java/testsuite/simple/SplitDBdotNameTest.java b/src/test/java/testsuite/simple/SplitDBdotNameTest.java index b8ef44d94..c96f9a3ab 100644 --- a/src/test/java/testsuite/simple/SplitDBdotNameTest.java +++ b/src/test/java/testsuite/simple/SplitDBdotNameTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -30,7 +30,7 @@ package testsuite.simple; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.ArrayList; import java.util.List; @@ -62,45 +62,33 @@ public void testSplit() throws Exception { //Test 1.1, weird DB.SP name src = "`MyDatabase 1.0.1.0`.`Proc 1.v1`"; resString = StringUtils.sanitizeProcOrFuncName(src); - if ((resString != null)) { - results = StringUtils.splitDBdotName(resString, null, "`", true); - assertEquals(results.get(0), "MyDatabase 1.0.1.0"); - assertEquals(results.get(1), "Proc 1.v1"); - } else { - fail("Test 1.1 returned null resString"); - } + assertNotNull(resString, "Test 1.1 returned null resString"); + results = StringUtils.splitDBdotName(resString, null, "`", true); + assertEquals(results.get(0), "MyDatabase 1.0.1.0"); + assertEquals(results.get(1), "Proc 1.v1"); //Test 1.2, toggle isNoBslashEscSet src = "`MyDatabase 1.0.1.0`.`Proc 1.v1`"; resString = StringUtils.sanitizeProcOrFuncName(src); - if ((resString != null)) { - results = StringUtils.splitDBdotName(resString, null, "`", false); - assertEquals(results.get(0), "MyDatabase 1.0.1.0"); - assertEquals(results.get(1), "Proc 1.v1"); - } else { - fail("Test 1.2 returned null resString"); - } + assertNotNull(resString, "Test 1.2 returned null resString"); + results = StringUtils.splitDBdotName(resString, null, "`", false); + assertEquals(results.get(0), "MyDatabase 1.0.1.0"); + assertEquals(results.get(1), "Proc 1.v1"); //Test 2.1, weird SP name, no DB parameter src = "`Proc 1.v1`"; resString = StringUtils.sanitizeProcOrFuncName(src); - if ((resString != null)) { - results = StringUtils.splitDBdotName(resString, null, "`", true); - assertEquals(results.get(0), null); - assertEquals(results.get(1), "Proc 1.v1"); - } else { - fail("Test 2.1 returned null resString"); - } + assertNotNull(resString, "Test 2.1 returned null resString"); + results = StringUtils.splitDBdotName(resString, null, "`", true); + assertEquals(results.get(0), null); + assertEquals(results.get(1), "Proc 1.v1"); //Test 2.2, toggle isNoBslashEscSet src = "`Proc 1.v1`"; resString = StringUtils.sanitizeProcOrFuncName(src); - if ((resString != null)) { - results = StringUtils.splitDBdotName(resString, null, "`", false); - assertEquals(results.get(0), null); - assertEquals(results.get(1), "Proc 1.v1"); - } else { - fail("Test 2.2 returned null resString"); - } + assertNotNull(resString, "Test 2.2 returned null resString"); + results = StringUtils.splitDBdotName(resString, null, "`", false); + assertEquals(results.get(0), null); + assertEquals(results.get(1), "Proc 1.v1"); } } diff --git a/src/test/java/testsuite/simple/StatementsTest.java b/src/test/java/testsuite/simple/StatementsTest.java index 2f6c3e60f..04aa54846 100644 --- a/src/test/java/testsuite/simple/StatementsTest.java +++ b/src/test/java/testsuite/simple/StatementsTest.java @@ -31,10 +31,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -77,9 +79,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.mysql.cj.CharsetMapping; +import com.mysql.cj.CharsetMappingWrapper; import com.mysql.cj.MysqlConnection; import com.mysql.cj.MysqlType; +import com.mysql.cj.conf.PropertyDefinitions.SslMode; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.jdbc.ClientPreparedStatement; @@ -187,79 +190,53 @@ public void testAccessorsAndMutators() throws SQLException { assertTrue(this.stmt.getConnection() == this.conn, "Connection can not be null, and must be same connection"); // Set max rows, to exercise code in execute(), executeQuery() and executeUpdate() - Statement accessorStmt = null; - - try { - accessorStmt = this.conn.createStatement(); - accessorStmt.setMaxRows(1); - accessorStmt.setMaxRows(0); // FIXME, test that this actually affects rows returned - accessorStmt.setMaxFieldSize(255); - assertTrue(accessorStmt.getMaxFieldSize() == 255, "Max field size should match what was set"); - - try { - accessorStmt.setMaxFieldSize(Integer.MAX_VALUE); - fail("Should not be able to set max field size > max_packet_size"); - } catch (SQLException sqlEx) { - // ignore - } - - accessorStmt.setCursorName("undef"); - accessorStmt.setEscapeProcessing(true); - accessorStmt.setFetchDirection(java.sql.ResultSet.FETCH_FORWARD); + Statement accessorStmt = this.conn.createStatement(); + accessorStmt.setMaxRows(1); + accessorStmt.setMaxRows(0); // FIXME, test that this actually affects rows returned + accessorStmt.setMaxFieldSize(255); + assertTrue(accessorStmt.getMaxFieldSize() == 255, "Max field size should match what was set"); + + assertThrows("Should not be able to set max field size > max_packet_size", SQLException.class, () -> { + accessorStmt.setMaxFieldSize(Integer.MAX_VALUE); + return null; + }); - int fetchDirection = accessorStmt.getFetchDirection(); - assertTrue(fetchDirection == java.sql.ResultSet.FETCH_FORWARD, "Set fetch direction != get fetch direction"); + accessorStmt.setCursorName("undef"); + accessorStmt.setEscapeProcessing(true); + accessorStmt.setFetchDirection(java.sql.ResultSet.FETCH_FORWARD); - try { - accessorStmt.setFetchDirection(Integer.MAX_VALUE); - fail("Should not be able to set fetch direction to invalid value"); - } catch (SQLException sqlEx) { - // ignore - } + int fetchDirection = accessorStmt.getFetchDirection(); + assertTrue(fetchDirection == java.sql.ResultSet.FETCH_FORWARD, "Set fetch direction != get fetch direction"); - try { - accessorStmt.setMaxRows(50000000 + 10); - fail("Should not be able to set max rows > 50000000"); - } catch (SQLException sqlEx) { - // ignore - } + assertThrows("Should not be able to set fetch direction to invalid value", SQLException.class, () -> { + accessorStmt.setFetchDirection(Integer.MAX_VALUE); + return null; + }); - try { - accessorStmt.setMaxRows(Integer.MIN_VALUE); - fail("Should not be able to set max rows < 0"); - } catch (SQLException sqlEx) { - // ignore - } + assertThrows("Should not be able to set max rows > 50000000", SQLException.class, () -> { + accessorStmt.setMaxRows(50000000 + 10); + return null; + }); - int fetchSize = this.stmt.getFetchSize(); + assertThrows("Should not be able to set max rows < 0", SQLException.class, () -> { + accessorStmt.setMaxRows(Integer.MIN_VALUE); + return null; + }); - try { - accessorStmt.setMaxRows(4); - accessorStmt.setFetchSize(Integer.MAX_VALUE); - fail("Should not be able to set FetchSize > max rows"); - } catch (SQLException sqlEx) { - // ignore - } + int fetchSize = this.stmt.getFetchSize(); - try { - accessorStmt.setFetchSize(-2); - fail("Should not be able to set FetchSize < 0"); - } catch (SQLException sqlEx) { - // ignore - } + accessorStmt.setMaxRows(4); + assertThrows("Should not be able to set FetchSize > max rows", SQLException.class, () -> { + accessorStmt.setFetchSize(Integer.MAX_VALUE); + return null; + }); - assertTrue(fetchSize == this.stmt.getFetchSize(), "Fetch size before invalid setFetchSize() calls should match fetch size now"); - } finally { - if (accessorStmt != null) { - try { - accessorStmt.close(); - } catch (SQLException sqlEx) { - // ignore - } + assertThrows("Should not be able to set FetchSize < 0", SQLException.class, () -> { + accessorStmt.setFetchSize(-2); + return null; + }); - accessorStmt = null; - } - } + assertTrue(fetchSize == this.stmt.getFetchSize(), "Fetch size before invalid setFetchSize() calls should match fetch size now"); } @Test @@ -272,29 +249,20 @@ public void testAutoIncrement() throws SQLException { int autoIncKeyFromApi = -1; this.rs = this.stmt.getGeneratedKeys(); - if (this.rs.next()) { - autoIncKeyFromApi = this.rs.getInt(1); - } else { - fail("Failed to retrieve AUTO_INCREMENT using Statement.getGeneratedKeys()"); - } + assertTrue(this.rs.next(), "Failed to retrieve AUTO_INCREMENT using Statement.getGeneratedKeys()"); + autoIncKeyFromApi = this.rs.getInt(1); this.rs.close(); int autoIncKeyFromFunc = -1; this.rs = this.stmt.executeQuery("SELECT LAST_INSERT_ID()"); - if (this.rs.next()) { - autoIncKeyFromFunc = this.rs.getInt(1); - } else { - fail("Failed to retrieve AUTO_INCREMENT using LAST_INSERT_ID()"); - } + assertTrue(this.rs.next(), "Failed to retrieve AUTO_INCREMENT using LAST_INSERT_ID()"); + autoIncKeyFromFunc = this.rs.getInt(1); - if ((autoIncKeyFromApi != -1) && (autoIncKeyFromFunc != -1)) { - assertTrue(autoIncKeyFromApi == autoIncKeyFromFunc, "Key retrieved from API (" + autoIncKeyFromApi - + ") does not match key retrieved from LAST_INSERT_ID() " + autoIncKeyFromFunc + ") function"); - } else { - fail("AutoIncrement keys were '0'"); - } + assertTrue((autoIncKeyFromApi != -1) && (autoIncKeyFromFunc != -1), "AutoIncrement keys were '0'"); + assertTrue(autoIncKeyFromApi == autoIncKeyFromFunc, "Key retrieved from API (" + autoIncKeyFromApi + + ") does not match key retrieved from LAST_INSERT_ID() " + autoIncKeyFromFunc + ") function"); } finally { if (this.rs != null) { try { @@ -316,7 +284,11 @@ public void testAutoIncrement() throws SQLException { @Test public void testBinaryResultSetNumericTypes() throws Exception { testBinaryResultSetNumericTypesInternal(this.conn); - Connection sspsConn = getConnectionWithProps("useServerPrepStmts=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection sspsConn = getConnectionWithProps(props); testBinaryResultSetNumericTypesInternal(sspsConn); sspsConn.close(); } @@ -490,10 +462,13 @@ public void testCallableStatement() throws Exception { @Test public void testCancelStatement() throws Exception { + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); Connection cancelConn = null; try { - cancelConn = getConnectionWithProps((String) null); + cancelConn = getConnectionWithProps(props); final Statement cancelStmt = cancelConn.createStatement(); cancelStmt.setQueryTimeout(1); @@ -708,7 +683,11 @@ public void run() { assertTrue(this.rs.next()); assertEquals(1, this.rs.getInt(1)); - final Connection forceCancel = getConnectionWithProps("queryTimeoutKillsConnection=true"); + Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props2.setProperty(PropertyKey.queryTimeoutKillsConnection.getKeyName(), "true"); + final Connection forceCancel = getConnectionWithProps(props2); final Statement forceStmt = forceCancel.createStatement(); forceStmt.setQueryTimeout(1); @@ -729,9 +708,7 @@ public Void call() throws Exception { Thread.sleep(100); } - if (count == 0) { - fail("Connection was never killed"); - } + assertFalse(count == 0, "Connection was never killed"); assertThrows(MySQLStatementCancelledException.class, new Callable() { public Void call() throws Exception { @@ -793,6 +770,8 @@ public void testEnableStreamingResults() throws Exception { @Test public void testHoldingResultSetsOverClose() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.holdResultsOpenOverStatementClose.getKeyName(), "true"); Connection conn2 = getConnectionWithProps(props); @@ -949,6 +928,8 @@ public void testMultiStatements() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowMultiQueries.getKeyName(), "true"); multiStmtConn = getConnectionWithProps(props); @@ -1038,6 +1019,8 @@ public void testNulls() throws SQLException { public void testParsedConversionWarning() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useUsageAdvisor.getKeyName(), "true"); Connection warnConn = getConnectionWithProps(props); @@ -1103,6 +1086,8 @@ public void testRowFetch() throws Exception { Connection fetchConn = null; Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useCursorFetch.getKeyName(), "true"); try { @@ -1157,6 +1142,8 @@ public void testSelectColumns() throws SQLException { @Test public void testSetObject() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.noDatetimeStringSync.getKeyName(), "true"); // value=true for #5 props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); Connection conn1 = getConnectionWithProps(props); @@ -1204,7 +1191,7 @@ public void testSetObject() throws Exception { @Test public void testSetObjectWithMysqlType() throws Exception { Properties props = new Properties(); - props.setProperty(PropertyKey.useSSL.getKeyName(), "false"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); props.setProperty(PropertyKey.noDatetimeStringSync.getKeyName(), "true"); // value=true for #5 props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); Connection conn1 = getConnectionWithProps(props); @@ -1266,6 +1253,8 @@ public void testSetObjectWithMysqlType() throws Exception { public void testStatementRewriteBatch() throws Exception { for (int j = 0; j < 2; j++) { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); if (j == 0) { props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); @@ -1311,6 +1300,8 @@ public void testStatementRewriteBatch() throws Exception { createTable("testStatementRewriteBatch", "(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)"); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "1024"); multiConn = getConnectionWithProps(props); @@ -1332,6 +1323,8 @@ public void testStatementRewriteBatch() throws Exception { createTable("testStatementRewriteBatch", "(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT)"); props.clear(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), j == 0 ? "true" : "false"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); multiConn = getConnectionWithProps(props); @@ -1526,6 +1519,8 @@ public void testBatchRewriteErrors() throws Exception { createTable("rewriteErrors", "(field1 int not null primary key) ENGINE=MyISAM"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); props.setProperty(PropertyKey.maxAllowedPacket.getKeyName(), "5660"); props.setProperty(PropertyKey.rewriteBatchedStatements.getKeyName(), "true"); @@ -1792,6 +1787,8 @@ public void testQueryInterceptors() throws Exception { try { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.queryInterceptors.getKeyName(), ServerStatusDiffInterceptor.class.getName()); interceptedConn = getConnectionWithProps(props); @@ -1806,6 +1803,8 @@ public void testQueryInterceptors() throws Exception { @Test public void testParameterBindings() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); props.setProperty(PropertyKey.treatUtilDateAsTimestamp.getKeyName(), "false"); props.setProperty(PropertyKey.autoDeserialize.getKeyName(), "true"); @@ -1861,19 +1860,25 @@ public void testParameterBindings() throws Exception { @Test public void testLocalInfileHooked() throws Exception { + this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'local_infile'"); + assumeTrue(this.rs.next() && "ON".equalsIgnoreCase(this.rs.getString(2)), "This test requires the server started with --local-infile=ON"); + this.rs.close(); + createTable("localInfileHooked", "(field1 int, field2 varchar(255))"); String streamData = "1\tabcd\n2\tefgh\n3\tijkl"; InputStream stream = new ByteArrayInputStream(streamData.getBytes()); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.allowLoadLocalInfile.getKeyName(), "true"); Connection testConn = getConnectionWithProps(props); Statement testStmt = testConn.createStatement(); try { ((com.mysql.cj.jdbc.JdbcStatement) testStmt).setLocalInfileInputStream(stream); - testStmt.execute( - "LOAD DATA LOCAL INFILE 'bogusFileName' INTO TABLE localInfileHooked CHARACTER SET " + CharsetMapping.getMysqlCharsetForJavaEncoding( + testStmt.execute("LOAD DATA LOCAL INFILE 'bogusFileName' INTO TABLE localInfileHooked CHARACTER SET " + + CharsetMappingWrapper.getStaticMysqlCharsetForJavaEncoding( ((MysqlConnection) this.conn).getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue(), this.serverVersion)); assertEquals(-1, stream.read()); this.rs = testStmt.executeQuery("SELECT field2 FROM localInfileHooked ORDER BY field1 ASC"); @@ -1970,6 +1975,8 @@ public void testSetNCharacterStream() throws Exception { createTable("testSetNCharacterStream", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10), " + "c3 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -1989,6 +1996,8 @@ public void testSetNCharacterStream() throws Exception { createTable("testSetNCharacterStream", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10), " + "c3 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2016,6 +2025,8 @@ public void testSetNCharacterStream() throws Exception { public void testSetNCharacterStreamServer() throws Exception { createTable("testSetNCharacterStreamServer", "(c1 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2032,6 +2043,8 @@ public void testSetNCharacterStreamServer() throws Exception { createTable("testSetNCharacterStreamServer", "(c1 LONGTEXT charset utf8) ENGINE=InnoDB"); Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2056,48 +2069,34 @@ public void testSetNClob() throws Exception { // suppose sql_mode don't include "NO_BACKSLASH_ESCAPES" createTable("testSetNClob", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10), " + "c3 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); - Properties props1 = new Properties(); - props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement - props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); // ensure charset isn't utf8 here - Connection conn1 = getConnectionWithProps(props1); - PreparedStatement pstmt1 = conn1.prepareStatement("INSERT INTO testSetNClob (c1, c2, c3) VALUES (?, ?, ?)"); - pstmt1.setNClob(1, (NClob) null); - NClob nclob2 = conn1.createNClob(); - nclob2.setString(1, "aaa"); - pstmt1.setNClob(2, nclob2); // for setNClob(int, NClob) - Reader reader3 = new StringReader("\'aaa\'"); - pstmt1.setNClob(3, reader3, 5); // for setNClob(int, Reader, long) - pstmt1.execute(); - ResultSet rs1 = this.stmt.executeQuery("SELECT c1, c2, c3 FROM testSetNClob"); - rs1.next(); - assertEquals(null, rs1.getString(1)); - assertEquals("aaa", rs1.getString(2)); - assertEquals("\'aaa\'", rs1.getString(3)); - rs1.close(); - pstmt1.close(); - conn1.close(); - createTable("testSetNClob", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10), " + "c3 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); - Properties props2 = new Properties(); - props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement - props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here - Connection conn2 = getConnectionWithProps(props2); - PreparedStatement pstmt2 = conn2.prepareStatement("INSERT INTO testSetNClob (c1, c2, c3) VALUES (?, ?, ?)"); - pstmt2.setNClob(1, (NClob) null); - nclob2 = conn2.createNClob(); - nclob2.setString(1, "aaa"); - pstmt2.setNClob(2, nclob2); // for setNClob(int, NClob) - reader3 = new StringReader("\'aaa\'"); - pstmt2.setNClob(3, reader3, 5); // for setNClob(int, Reader, long) - pstmt2.execute(); - ResultSet rs2 = this.stmt.executeQuery("SELECT c1, c2, c3 FROM testSetNClob"); - rs2.next(); - assertEquals(null, rs2.getString(1)); - assertEquals("aaa", rs2.getString(2)); - assertEquals("\'aaa\'", rs2.getString(3)); - rs2.close(); - pstmt2.close(); - conn2.close(); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement + + for (String enc : new String[] { "latin1", "UTF-8" }) { + this.stmt.execute("TRUNCATE TABLE testSetNClob"); + props.setProperty(PropertyKey.characterEncoding.getKeyName(), enc); // ensure charset isn't utf8 here + Connection conn1 = getConnectionWithProps(props); + PreparedStatement pstmt1 = conn1.prepareStatement("INSERT INTO testSetNClob (c1, c2, c3) VALUES (?, ?, ?)"); + pstmt1.setNClob(1, (NClob) null); + NClob nclob2 = conn1.createNClob(); + nclob2.setString(1, "aaa"); + pstmt1.setNClob(2, nclob2); // for setNClob(int, NClob) + Reader reader3 = new StringReader("\'aaa\'"); + pstmt1.setNClob(3, reader3, 5); // for setNClob(int, Reader, long) + pstmt1.execute(); + + ResultSet rs1 = this.stmt.executeQuery("SELECT c1, c2, c3 FROM testSetNClob"); + rs1.next(); + assertEquals(null, rs1.getString(1)); + assertEquals("aaa", rs1.getString(2)); + assertEquals("\'aaa\'", rs1.getString(3)); + rs1.close(); + pstmt1.close(); + conn1.close(); + } } /** @@ -2109,6 +2108,8 @@ public void testSetNClob() throws Exception { public void testSetNClobServer() throws Exception { createTable("testSetNClobServer", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2135,6 +2136,8 @@ public void testSetNClobServer() throws Exception { createTable("testSetNClobServer", "(c1 NATIONAL CHARACTER(10), c2 LONGTEXT charset utf8) ENGINE=InnoDB"); Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2165,6 +2168,8 @@ public void testSetNString() throws Exception { createTable("testSetNString", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10), " + "c3 NATIONAL CHARACTER(10)) DEFAULT CHARACTER SET cp932 ENGINE=InnoDB"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "MS932"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2185,6 +2190,8 @@ public void testSetNString() throws Exception { createTable("testSetNString", "(c1 NATIONAL CHARACTER(10), c2 NATIONAL CHARACTER(10), " + "c3 NATIONAL CHARACTER(10)) DEFAULT CHARACTER SET cp932 ENGINE=InnoDB"); Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "false"); // use client-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2212,6 +2219,8 @@ public void testSetNString() throws Exception { public void testSetNStringServer() throws Exception { createTable("testSetNStringServer", "(c1 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "latin1"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2228,6 +2237,8 @@ public void testSetNStringServer() throws Exception { createTable("testSetNStringServer", "(c1 NATIONAL CHARACTER(10)) ENGINE=InnoDB"); Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2251,6 +2262,8 @@ public void testSetNStringServer() throws Exception { public void testUpdateNCharacterStream() throws Exception { createTable("testUpdateNCharacterStream", "(c1 CHAR(10) PRIMARY KEY, c2 NATIONAL CHARACTER(10)) default character set sjis"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2280,6 +2293,8 @@ public void testUpdateNCharacterStream() throws Exception { createTable("testUpdateNCharacterStream", "(c1 CHAR(10) PRIMARY KEY, c2 CHAR(10)) default character set sjis"); // sjis field Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS"); // ensure charset isn't utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2311,6 +2326,8 @@ public void testUpdateNCharacterStream() throws Exception { public void testUpdateNClob() throws Exception { createTable("testUpdateNChlob", "(c1 CHAR(10) PRIMARY KEY, c2 NATIONAL CHARACTER(10)) default character set sjis"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset isn't utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2346,6 +2363,8 @@ public void testUpdateNClob() throws Exception { createTable("testUpdateNChlob", "(c1 CHAR(10) PRIMARY KEY, c2 CHAR(10)) default character set sjis"); // sjis field Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS"); // ensure charset isn't utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2379,6 +2398,8 @@ public void testUpdateNClob() throws Exception { public void testUpdateNString() throws Exception { createTable("testUpdateNString", "(c1 CHAR(10) PRIMARY KEY, c2 NATIONAL CHARACTER(10)) default character set sjis"); Properties props1 = new Properties(); + props1.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props1.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props1.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props1.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8"); // ensure charset is utf8 here Connection conn1 = getConnectionWithProps(props1); @@ -2408,6 +2429,8 @@ public void testUpdateNString() throws Exception { createTable("testUpdateNString", "(c1 CHAR(10) PRIMARY KEY, c2 CHAR(10)) default character set sjis"); // sjis field Properties props2 = new Properties(); + props2.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props2.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props2.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); // use server-side prepared statement props2.setProperty(PropertyKey.characterEncoding.getKeyName(), "SJIS"); // ensure charset isn't utf8 here Connection conn2 = getConnectionWithProps(props2); @@ -2433,6 +2456,8 @@ public void testUpdateNString() throws Exception { @Test public void testJdbc4LoadBalancing() throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.ha_loadBalanceStrategy.getKeyName(), CountingReBalanceStrategy.class.getName()); props.setProperty(PropertyKey.loadBalanceAutoCommitStatementThreshold.getKeyName(), "3"); @@ -2970,7 +2995,11 @@ public void testServerPrepStmtExecuteLargeBatch() throws Exception { */ createTable("testExecuteLargeBatch", "(id BIGINT AUTO_INCREMENT PRIMARY KEY, n INT)"); - Connection testConn = getConnectionWithProps("useServerPrepStmts=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); this.pstmt = testConn.prepareStatement("INSERT INTO testExecuteLargeBatch (n) VALUES (?)", Statement.RETURN_GENERATED_KEYS); this.pstmt.setInt(1, 1); @@ -3111,6 +3140,8 @@ public void testPrepStmtSetObjectAndNewSupportedTypes() throws Exception { createTable("testSetObjectPS1", "(id INT, d DATE, t TIME, dt DATETIME, ts TIMESTAMP)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); Connection testConn = getConnectionWithProps(timeZoneFreeDbUrl, props); @@ -3150,6 +3181,8 @@ public void testCallStmtSetObjectAndNewSupportedTypes() throws Exception { "(IN id INT, IN d DATE, IN t TIME, IN dt DATETIME, IN ts TIMESTAMP) BEGIN " + "INSERT INTO testSetObjectCS1 VALUES (id, d, t, dt, ts); END"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); Connection testConn = getConnectionWithProps(timeZoneFreeDbUrl, props); @@ -3191,6 +3224,8 @@ public void testServPrepStmtSetObjectAndNewSupportedTypes() throws Exception { createTable("testSetObjectSPS1", "(id INT, d DATE, t TIME, dt DATETIME, ts TIMESTAMP)"); Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); @@ -3213,7 +3248,11 @@ public void testServPrepStmtSetObjectAndNewSupportedTypes() throws Exception { */ @Test public void testServPrepStmtSetObjectAndNewUnsupportedTypes() throws Exception { - Connection testConn = getConnectionWithProps("useServerPrepStmts=true"); + Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), "true"); + Connection testConn = getConnectionWithProps(props); checkUnsupportedTypesBehavior(testConn.prepareStatement("SELECT ?")); testConn.close(); } @@ -3441,6 +3480,8 @@ private int insertTestDataOffsetDTTypes(PreparedStatement prepStmt) throws Excep */ private void validateTestDataOffsetDTTypes(String tableName, int expectedRowCount) throws Exception { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.preserveInstants.getKeyName(), "false"); Connection testConn = getConnectionWithProps(timeZoneFreeDbUrl, props); Statement testStmt = testConn.createStatement(); @@ -3856,6 +3897,8 @@ public void testServerPreparedStatementsCaching() throws Exception { boolean cachePS = false; do { Properties props = new Properties(); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); props.setProperty(PropertyKey.useServerPrepStmts.getKeyName(), Boolean.toString(useSPS)); props.setProperty(PropertyKey.cachePrepStmts.getKeyName(), Boolean.toString(cachePS)); props.setProperty(PropertyKey.prepStmtCacheSize.getKeyName(), "5"); @@ -4105,4 +4148,146 @@ public void testServerPreparedStatementsCaching() throws Exception { } } while ((useSPS = !useSPS) || (cachePS = !cachePS)); } + + @Test + public void testResultSetProducingQueries() throws Exception { + assumeTrue(versionMeetsMinimum(8, 0, 19), "MySQL 8.0.19+ is required to run this test."); + + // Prepare testing entities and data. + createTable("rsProdQuery", "(col1 INT, col2 VARCHAR(100))"); + createProcedure("rsProdQueryProc", "() BEGIN SELECT * FROM rsProdQuery; END"); + assertEquals(2, this.stmt.executeUpdate("INSERT INTO rsProdQuery VALUES (1, 'test1'), (2, 'test2')")); + assertFalse(this.stmt.execute("PREPARE rsProdQueryPS FROM \"SELECT * FROM rsProdQuery\"")); + + String[] okQueries = new String[] { + // Data Manipulation Statements: + "SELECT * FROM rsProdQuery", "TABLE rsProdQuery", "VALUES ROW (1, 'test1'), ROW (2, 'test2')", "CALL rsProdQueryProc()", + "WITH cte1 AS (TABLE rsProdQuery), cte2 AS (TABLE rsProdQuery) SELECT * FROM cte1", "WITH cte1 AS (TABLE rsProdQuery) TABLE cte1", + "WITH cte1 AS (TABLE rsProdQuery) VALUES ROW (1, 'test1'), ROW (2, 'test2')", + // Transactional and Locking Statements: + "XA RECOVER", + // Prepared Statements: + "EXECUTE rsProdQueryPS", + // Database Administration Statements/Table Maintenance Statements: + "ANALYZE TABLE rsProdQuery", "CHECK TABLE rsProdQuery", "CHECKSUM TABLE rsProdQuery", "OPTIMIZE TABLE rsProdQuery", "REPAIR TABLE rsProdQuery", + // Database Administration Statements/SHOW Statements: + "SHOW CREATE TABLE rsProdQuery", + // Utility Statements: + "DESC rsProdQuery", "DESCRIBE rsProdQuery", "EXPLAIN rsProdQuery", "HELP 'SELECT'" }; + for (String query : okQueries) { + try { + this.rs = this.stmt.executeQuery(query); + this.rs.absolute(2); + this.rs.beforeFirst(); + this.rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + fail("Should not have thrown an Exception while executing \"" + query + "\""); + } + } + + String[] notOkQueries = new String[] { + // Data Manipulation Statements: + "INSERT INTO rsProdQuery VALUES (99, 'test99')", "REPLACE INTO rsProdQuery VALUES (99, 'test99')", "UPDATE rsProdQuery SET col1 = col1 + 1", + "DELETE FROM rsProdQuery", "TRUNCATE TABLE rsProdQuery", "DO 1 + 1", "HANDLER rsProdQuery OPEN AS hrsProdQuery", + "IMPORT TABLE FROM 'rsProdQuery'", "LOAD DATA INFILE 'rsProdQuery' INTO TABLE rsProdQuery", + "WITH cte1 AS (TABLE rsProdQuery) UPDATE rsProdQuery SET c = c + 1", "WITH cte1 AS (TABLE rsProdQuery) DELETE FROM rsProdQuery", + // Transactional and Locking Statements: + "BEGIN", "START TRANSACTION", "SAVEPOINT rsProdQuery", "RELEASE SAVEPOINT rsProdQuery", "ROLLBACK", "COMMIT", "LOCK INSTANCE FOR BACKUP", + "UNLOCK INSTANCE", "XA START 'rsProdQuery'", + // Replication Statements: + "PURGE BINARY LOGS TO 'rsProdQuery'", "CHANGE REPLICATION SOURCE TO SOURCE_DELAY=0", "RESET REPLICA", "STOP REPLICA", + // Prepared Statements: + "PREPARE rsProdQueryPS FROM 'TABLE rsProdQuery'", "DEALLOCATE PREPARE rsProdQueryPS", + // Compound Statement Syntax/Condition Handling: + "SIGNAL SQLSTATE '01000'", "RESIGNAL", "GET DIAGNOSTICS @n = NUMBER", + // Database Administration Statements/Account Management Statements: + "CREATE USER rsProdQueryUser", "ALTER USER rsProdQueryUser", "RENAME USER rsProdQueryUser to rsProdQueryUserNew", + "GRANT SELECT ON rsProdQueryDb.* TO rsProdQueryUser", "REVOKE ALL ON *.* FROM rsProdQueryUser", "DROP USER rsProdQuery", + // Database Administration Statements/Component, Plugin, and Loadable Function Statements: + "INSTALL COMPONENT 'rsProdQuery'", "UNINSTALL COMPONENT 'rsProdQuery'", + // Database Administration Statements/CLONE Statement & SET Statements: + "CLONE LOCAL DATA DIRECTORY '/tmp'", "SET @rsProdQuery = 'rsProdQuery'", + // Database Administration Statements/Other Administrative Statements: + "BINLOG 'rsProdQuery'", "CACHE INDEX rsProdQueryIdx IN rsProdQueryCache", "FLUSH STATUS", "KILL 0", "RESTART", "SHUTDOWN", + // Utility Statements + "USE rsProdQueryDb" }; + for (String query : notOkQueries) { + assertThrows("Query: " + query, SQLException.class, "Statement\\.executeQuery\\(\\) cannot issue statements that do not produce result sets\\.", + () -> { + this.stmt.executeQuery(query); + return null; + }); + } + } + + @Test + public void testReadOnlySafeStatements() throws Exception { + assumeTrue(versionMeetsMinimum(8, 0, 19), "MySQL 8.0.19+ is required to run this test."); + + // Prepare testing entities and data. + createTable("roSafeTest", "(col1 INT, col2 VARCHAR(100))"); + createProcedure("roSafeTestProc", "() BEGIN SELECT * FROM roSafeTest; END"); + assertEquals(2, this.stmt.executeUpdate("INSERT INTO roSafeTest VALUES (1, 'test1'), (2, 'test2')")); + assertFalse(this.stmt.execute("PREPARE roSafeTestPS FROM \"SELECT * FROM roSafeTest\"")); + + Connection testConn = getConnectionWithProps(""); + Statement testStmt = testConn.createStatement(); + testConn.setReadOnly(true); + + String[] okQueries = new String[] { + // Data Manipulation Statements: + "SELECT * FROM roSafeTest", "TABLE roSafeTest", "VALUES ROW (1, 'test1'), ROW (2, 'test2')", "CALL roSafeTestProc()", + "WITH cte1 AS (TABLE roSafeTest), cte2 AS (TABLE roSafeTest) SELECT * FROM cte1", "WITH cte1 AS (TABLE roSafeTest) TABLE cte1", + "WITH cte1 AS (TABLE roSafeTest) VALUES ROW (1, 'test1'), ROW (2, 'test2')", "DO 1 + 1", "HANDLER roSafeTest OPEN AS hroSafeTest", + // Transactional and Locking Statements: + "BEGIN", "START TRANSACTION", "SAVEPOINT roSafeTest", "RELEASE SAVEPOINT roSafeTest", "ROLLBACK", "COMMIT", "LOCK INSTANCE FOR BACKUP", + "UNLOCK INSTANCE", "XA START 'roSafeTest'", "XA END 'roSafeTest'", "XA ROLLBACK 'roSafeTest'", + // Replication Statements: + "PURGE BINARY LOGS TO 'roSafeTest'", "STOP REPLICA", + // Prepared Statements: + "PREPARE roSafeTestPS FROM 'TABLE roSafeTest'", "EXECUTE roSafeTestPS", "DEALLOCATE PREPARE roSafeTestPS", + // Compound Statement Syntax/Condition Handling: + "SIGNAL SQLSTATE '01000'", "RESIGNAL", "GET DIAGNOSTICS @n = NUMBER", + // Database Administration Statements/Table Maintenance Statements: + "ANALYZE TABLE roSafeTest", "CHECK TABLE roSafeTest", "CHECKSUM TABLE roSafeTest", + // Database Administration Statements/CLONE Statement, SET & SHOW Statements: + "CLONE LOCAL DATA DIRECTORY '/tmp'", "SET @roSafeTest = 'roSafeTest'", "SHOW CREATE TABLE roSafeTest", + // Database Administration Statements/Other Administrative Statements: + "BINLOG 'roSafeTest'", "CACHE INDEX roSafeTestIdx IN roSafeTestCache", "FLUSH STATUS", "KILL 0", + // "RESTART", it's safe but can't be executed in this test + // "SHUTDOWN", it's safe but can't be executed in this test + // Utility Statements + "USE roSafeTestDb", + // Utility Statements: + "DESC roSafeTest", "DESCRIBE roSafeTest", "EXPLAIN roSafeTest", "HELP 'SELECT'" }; + for (String query : okQueries) { + try { + testStmt.execute(query); + } catch (SQLException e) { + assertNotEquals("Connection is read-only. Queries leading to data modification are not allowed.", e.getMessage()); + } + } + + String[] notOkQueries = new String[] { + // Data Manipulation Statements: + "INSERT INTO roSafeTest VALUES (99, 'test99')", "REPLACE INTO roSafeTest VALUES (99, 'test99')", "UPDATE roSafeTest SET col1 = col1 + 1", + "DELETE FROM roSafeTest", "TRUNCATE TABLE roSafeTest", "IMPORT TABLE FROM 'roSafeTest'", "LOAD DATA INFILE 'roSafeTest' INTO TABLE roSafeTest", + "WITH cte1 AS (TABLE roSafeTest) UPDATE roSafeTest SET c = c + 1", "WITH cte1 AS (TABLE roSafeTest) DELETE FROM roSafeTest", + // Replication Statements: + "CHANGE REPLICATION SOURCE TO SOURCE_DELAY=0", "RESET REPLICA", + // Database Administration Statements/Account Management Statements: + "CREATE USER roSafeTestUser", "ALTER USER roSafeTestUser", "RENAME USER roSafeTestUser to roSafeTestUserNew", + "GRANT SELECT ON roSafeTestDb.* TO roSafeTestUser", "REVOKE ALL ON *.* FROM roSafeTestUser", "DROP USER roSafeTest", + // Database Administration Statements/Table Maintenance Statements: + "OPTIMIZE TABLE roSafeTest", "REPAIR TABLE roSafeTest", + // Database Administration Statements/Component, Plugin, and Loadable Function Statements: + "INSTALL COMPONENT 'roSafeTest'", "UNINSTALL COMPONENT 'roSafeTest'", }; + for (String query : notOkQueries) { + assertThrows("Query: " + query, SQLException.class, "Connection is read-only\\. Queries leading to data modification are not allowed\\.", () -> { + testStmt.execute(query); + return null; + }); + } + } } diff --git a/src/test/java/testsuite/simple/UpdatabilityTest.java b/src/test/java/testsuite/simple/UpdatabilityTest.java index d8adc6cc4..8ad58edd1 100644 --- a/src/test/java/testsuite/simple/UpdatabilityTest.java +++ b/src/test/java/testsuite/simple/UpdatabilityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. + * Copyright (c) 2002, 2021, 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 @@ -30,7 +30,6 @@ package testsuite.simple; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.sql.ResultSet; import java.sql.SQLException; @@ -118,12 +117,10 @@ public void testBogusTable() throws SQLException { scrollableStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); this.rs = scrollableStmt.executeQuery("SELECT * FROM BOGUS_UPDATABLE"); - try { + assertThrows("ResultSet.moveToInsertRow() should not succeed on non-updatable table", NotUpdatable.class, () -> { this.rs.moveToInsertRow(); - fail("ResultSet.moveToInsertRow() should not succeed on non-updatable table"); - } catch (NotUpdatable noUpdate) { - // ignore - } + return null; + }); } finally { if (scrollableStmt != null) { try { @@ -153,12 +150,10 @@ public void testMultiKeyTable() throws SQLException { scrollableStmt = this.conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); this.rs = scrollableStmt.executeQuery("SELECT field1 FROM MULTI_UPDATABLE"); - try { + assertThrows("ResultSet.moveToInsertRow() should not succeed on query that does not select all primary keys", NotUpdatable.class, () -> { this.rs.moveToInsertRow(); - fail("ResultSet.moveToInsertRow() should not succeed on query that does not select all primary keys"); - } catch (NotUpdatable noUpdate) { - // ignore - } + return null; + }); } finally { if (scrollableStmt != null) { try { diff --git a/src/test/java/testsuite/simple/XATest.java b/src/test/java/testsuite/simple/XATest.java index b401acaa1..9e571b423 100644 --- a/src/test/java/testsuite/simple/XATest.java +++ b/src/test/java/testsuite/simple/XATest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. + * Copyright (c) 2005, 2021, 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 @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -65,6 +66,8 @@ public class XATest extends BaseTestCase { public void setup() { this.xaDs = new MysqlXADataSource(); this.xaDs.setUrl(BaseTestCase.dbUrl); + this.xaDs.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + this.xaDs.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); this.xaDs.getProperty(PropertyKey.rollbackOnPooledClose).setValue(true); } @@ -178,11 +181,10 @@ protected XAConnection getXAConnection() throws Exception { */ @Test public void testRecover() throws Exception { - if (versionMeetsMinimum(5, 7) && !versionMeetsMinimum(5, 7, 5)) { - // Test is broken in 5.7.0 - 5.7.4 after server bug#14670465 fix which changed the XA RECOVER output format. - // Fixed in 5.7.5 server version - return; - } + // Test is broken in 5.7.0 - 5.7.4 after server bug#14670465 fix which changed the XA RECOVER output format. + // Fixed in 5.7.5 server version + assumeFalse(versionMeetsMinimum(5, 7) && !versionMeetsMinimum(5, 7, 5), + "This test doesn't work with MySQL 5.7.0-5.7.4 because of server Bug#14670465."); XAConnection xaConn = null, recoverConn = null; @@ -375,6 +377,8 @@ public void testSuspendableTx() throws Exception { MysqlXADataSource suspXaDs = new MysqlXADataSource(); suspXaDs.setUrl(BaseTestCase.dbUrl); + suspXaDs.getStringProperty(PropertyKey.sslMode.getKeyName()).setValue("DISABLED"); + suspXaDs.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName()).setValue(true); suspXaDs.getProperty(PropertyKey.pinGlobalTxToPhysicalConnection).setValue(true); suspXaDs.getProperty(PropertyKey.rollbackOnPooledClose).setValue(true); diff --git a/src/test/java/testsuite/x/TestXDevAPIRequirements.java b/src/test/java/testsuite/x/TestXDevAPIRequirements.java deleted file mode 100644 index 7d118872b..000000000 --- a/src/test/java/testsuite/x/TestXDevAPIRequirements.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (c) 2015, 2020, 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 also distributed with certain software (including but not - * limited to OpenSSL) 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 included with MySQL. - * - * 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 testsuite.x; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.HashMap; -import java.util.Properties; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import com.mysql.cj.conf.ConnectionUrl; -import com.mysql.cj.conf.PropertyKey; -import com.mysql.cj.xdevapi.Collection; -import com.mysql.cj.xdevapi.Schema; -import com.mysql.cj.xdevapi.Session; -import com.mysql.cj.xdevapi.SessionImpl; -import com.mysql.cj.xdevapi.Table; - -/** - * Tests for X DevAPI requirements. - */ -public class TestXDevAPIRequirements extends BaseXDevAPITestCase { - /** - * Session [11] - * Session.Connect.Single [6] - * Session.Connect.DataSource [7] - * Session.Connect.Mysqls [8] [9] - not supported in first version - * - * @throws Exception - */ - @Test - public void testSessionCreation() throws Exception { - if (!this.isSetForXTests) { - return; - } - Session sess; - - String url = this.baseUrl; - sess = getSession(url); - sess.close(); - - // TODO test different URLs - - ConnectionUrl conUrl = ConnectionUrl.getConnectionUrlInstance(url, null); - - Properties props = conUrl.getMainHost().exposeAsProperties(); - sess = getSession(props); - sess.close(); - - // test connection without port specification - if (props.getProperty(PropertyKey.PORT.getKeyName()).equals("33060")) { - props.remove(PropertyKey.PORT.getKeyName()); - sess = getSession(props); - ConnectionUrl conUrl1 = ConnectionUrl.getConnectionUrlInstance(sess.getUri(), null); - assertEquals("33060", conUrl1.getMainHost().exposeAsProperties().getProperty(PropertyKey.PORT.getKeyName())); - sess.close(); - } - // TODO test different properties - } - - /** - * Test session methods. - * - * @throws Exception - */ - @Test - public void testSessionMethods() throws Exception { - if (!this.isSetForXTests) { - return; - } - - Session sess = getSession(this.baseUrl); - assertNotNull(sess); - assertTrue(sess instanceof SessionImpl); - - Schema sch = sess.getDefaultSchema(); - sch.getName(); - - sess.getSchema(""); // TODO set name - - sess.close(); - - // out of requirements - //sess.createSchema("name"); // TODO set name - //sess.dropSchema("name"); // TODO set name - //sess.getSchemas(); - //sess.getUri(); - - //String sql = ""; // TODO set query - //sess.executeSql(sql); - } - - /** - * Schema browsing Schema.getCollections() [44] - * Schema browsing Schema.getTables() [45] - * Schema access Schema.getCollection() [47] - * Schema access Schema.getCollectionAsTable() [50] - * Schema access Schema.getTable() [48] - * Schema - who am I? [51] - * Schema - am I real? [52] - * Schema - DDL create [55] - * Schema.drop [53] - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testSchemaMethods() throws Exception { - if (!this.isSetForXTests) { - return; - } - - // TODO fill in the next pattern - - Schema schema = getSession("").getDefaultSchema(); // TODO set URL - - // Schema browsing Schema.getCollections() [44] - schema.getCollections(); - - // Schema browsing Schema.getTables() [45] - schema.getTables(); - - // Schema access Schema.getCollection() [47] - schema.getCollection(""); // TODO set name - - // Schema access Schema.getCollectionAsTable() [50] - schema.getCollectionAsTable(""); // TODO set name - - // Schema access Schema.getTable() [48] - schema.getTable(""); // TODO set name - - // Schema - who am I? [51] - schema.getName(); - - // Schema - am I real? [52] - schema.existsInDatabase(); - - // Schema - DDL create [55] - schema.createCollection(""); // TODO set name - - // inherited - schema.getSchema(); // "this" ??? - schema.getSession(); // ??? - } - - /** - * Collection.createCollection [16] - * Collection Index Creation [59] - * Collection.getCollection [16] - * Collection.add [17] - * Collection.find basics [18] - * Collection.modify (incl. all array_*) [21] - * Collection.remove [22] - * Collection.as [41] - * Collection.count [43] - * Collection - who am I? [51] - * Collection - am I real? [52] - * Collection.drop [53] - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testCollectionMethods() throws Exception { - if (!this.isSetForXTests) { - return; - } - - // TODO fill in the next pattern - - // Collection.createCollection [16] - Collection collection = getSession("").getDefaultSchema().createCollection(""); // TODO set URL and collection name - - // Collection Index Creation [59] - // TODO spec in progress - - // Collection.getCollection [16] - collection = getSession("").getDefaultSchema().getCollection(""); // TODO set URL and collection name - - // Collection.add [17] - collection.add(new HashMap()); // TODO set correct parameter - collection.add("jsonString"); // TODO set correct parameter - - // Collection.find basics [18] - collection.find("searchCondition"); // TODO set correct parameter - - // Collection.modify (incl. all array_*) [21] - collection.modify("searchCondition"); // TODO set correct parameter - - // Collection.remove [22] - collection.remove("searchCondition"); // TODO set correct parameter - - // Collection.as [41] - // collection.as("alias"); // TODO set correct parameter - - // Collection.count [43] - collection.count(); - - // Collection - who am I? [51] - collection.getName(); - - // Collection - am I real? [52] - collection.existsInDatabase(); - - // inherited - collection.getSchema(); - collection.getSession(); - - // poor spec - collection.newDoc(); - } - - /** - * Table.createTable [26] - not supported in first version - * Table Index Creation [60] - not supported in first version - * Table.insert [28] - * Table.select basics [27] - * Table.update [29] - * Table.delete [30] - * Table.alter [31] - not supported in first version - * Table.join (tables) [40] - not supported in first version - * Table.as [42] - * Table.count [43] - * Table - who am I? [51] - * Table - am I real? [52] - * Table.drop [53] - not supported in first version - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testTableMethods() throws Exception { - if (!this.isSetForXTests) { - return; - } - - // TODO fill in the next pattern - - Table table = getSession("").getDefaultSchema().getCollectionAsTable("name"); // TODO set URL and collection name - - // Table.insert [28] - // Object fieldsAndValues = null; - // table.insert(fieldsAndValues); // TODO set correct parameter, expand statements - table.insert("fields"); // TODO set correct parameter, expand statements - - // Table.select basics [27] - table.select("searchFields"); // TODO set correct parameter, expand statements - - // Table.update [29] - table.update(); // TODO expand statements - - // Table.delete [30] - table.delete(); // TODO expand statements - - // Table.as [42] - // table.as("alias"); // TODO set correct parameter - - // Table.count [43] - table.count(); - - // Table - who am I? [51] - table.getName(); - - // Table - am I real? [52] - table.existsInDatabase(); - - // inherited - table.getSchema(); - table.getSession(); - } - - /** - * View.select [54] - * View.count [43] - * View - who am I? [51] - * View - am I real? [52] - * View.drop [53] - not supported in first version - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testViewMethods() throws Exception { - if (!this.isSetForXTests) { - return; - } - - // TODO fill in the next pattern, Views are treated as Tables - - Table view = getSession("").getDefaultSchema().getTable("name"); // getView("name"); // TODO set URL and collection name - - view.isView(); - - // View.select [54] - view.select("searchFields"); // TODO set correct parameter, expand statements - - // View.count [43] - view.count(); - - // View - who am I? [51] - view.getName(); - - // View - am I real? [52] - view.existsInDatabase(); - - // inherited - view.getSchema(); - view.getSession(); - } - - /** - * Context.Session [33] - not supported in first version - * Context.Transaction [34] - not supported in first version - * Context.Batch.Collection [35] - not supported in first version - * Context.Batch.Table [35] - not supported in first version - * Context.Batch.SQL / executeSql() [35] - not supported in first version - * Context Nesting [36] - not supported in first version - * Context option Custom error handling [56] - not supported in first version - * Context option Consistency [57] - not supported in first version - * Context option Replication Factor [58] - not supported in first version - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testExecutionContext() throws Exception { - if (!this.isSetForXTests) { - return; - } - } - - /** - * Result.Basics [38] - * Result client side buffering - * Results.Multi Resultset [38] - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testResultMethods() throws Exception { - if (!this.isSetForXTests) { - return; - } - } - - /** - * CRUD.Synchronous execution [14] - * CRUD.Asynchronous execution [14] - * CRUD.Parameter Binding [15] - * Document class DbDoc [25] - * INSERT.Streaming [37] - * - * @throws Exception - */ - @Test - @Disabled("needs implemented") - public void testExecution() throws Exception { - if (!this.isSetForXTests) { - return; - } - } -} diff --git a/src/test/java/testsuite/x/devapi/AsyncQueryTest.java b/src/test/java/testsuite/x/devapi/AsyncQueryTest.java index 5ab562efd..5a6516844 100644 --- a/src/test/java/testsuite/x/devapi/AsyncQueryTest.java +++ b/src/test/java/testsuite/x/devapi/AsyncQueryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -62,9 +62,6 @@ public class AsyncQueryTest extends BaseCollectionTestCase { @Test public void basicAsyncQuery() throws Exception { - if (!this.isSetForXTests) { - return; - } String json = "{'firstName':'Frank', 'middleName':'Lloyd', 'lastName':'Wright'}".replaceAll("'", "\""); if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { json = json.replace("{", "{\"_id\": \"1\", "); // Inject an _id. @@ -85,58 +82,61 @@ public void basicAsyncQuery() throws Exception { @Test public void overlappedAsyncQueries() throws Exception { - if (!this.isSetForXTests) { - return; - } final int NUMBER_OF_QUERIES = 1000; - Session sess = new SessionFactory().getSession(this.baseUrl); - Collection coll = sess.getSchema(this.schema.getName()).getCollection(this.collection.getName()); + Session sess = null; - String json1 = "{'mode': 'sync'}".replaceAll("'", "\""); - String json2 = "{'mode': 'async'}".replaceAll("'", "\""); - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { - // Inject an _id. - json1 = json1.replace("{", "{\"_id\": \"1\", "); - json2 = json2.replace("{", "{\"_id\": \"2\", "); - } - AddResult res = coll.add(json1).add(json2).execute(); - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { - assertTrue(res.getGeneratedIds().get(0).matches("[a-f0-9]{28}")); - assertTrue(res.getGeneratedIds().get(1).matches("[a-f0-9]{28}")); - } else { - assertEquals(0, res.getGeneratedIds().size()); - } + try { + sess = new SessionFactory().getSession(this.baseUrl); + Collection coll = sess.getSchema(this.schema.getName()).getCollection(this.collection.getName()); - List> futures = new ArrayList<>(); - for (int i = 0; i < NUMBER_OF_QUERIES; ++i) { - if (i % 5 == 0) { - futures.add(CompletableFuture.completedFuture(coll.find("mode = 'sync'").execute())); + String json1 = "{'mode': 'sync'}".replaceAll("'", "\""); + String json2 = "{'mode': 'async'}".replaceAll("'", "\""); + if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { + // Inject an _id. + json1 = json1.replace("{", "{\"_id\": \"1\", "); + json2 = json2.replace("{", "{\"_id\": \"2\", "); + } + AddResult res = coll.add(json1).add(json2).execute(); + if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { + assertTrue(res.getGeneratedIds().get(0).matches("[a-f0-9]{28}")); + assertTrue(res.getGeneratedIds().get(1).matches("[a-f0-9]{28}")); } else { - futures.add(coll.find("mode = 'async'").executeAsync()); + assertEquals(0, res.getGeneratedIds().size()); } - } - for (int i = 0; i < NUMBER_OF_QUERIES; ++i) { - try { - DocResult docs = futures.get(i).get(); - DbDoc d = docs.next(); - JsonString mode = (JsonString) d.get("mode"); + List> futures = new ArrayList<>(); + for (int i = 0; i < NUMBER_OF_QUERIES; ++i) { if (i % 5 == 0) { - assertEquals("sync", mode.getString(), "i = " + i); + futures.add(CompletableFuture.completedFuture(coll.find("mode = 'sync'").execute())); } else { - assertEquals("async", mode.getString(), "i = " + i); + futures.add(coll.find("mode = 'async'").executeAsync()); + } + } + + for (int i = 0; i < NUMBER_OF_QUERIES; ++i) { + try { + DocResult docs = futures.get(i).get(); + DbDoc d = docs.next(); + JsonString mode = (JsonString) d.get("mode"); + if (i % 5 == 0) { + assertEquals("sync", mode.getString(), "i = " + i); + } else { + assertEquals("async", mode.getString(), "i = " + i); + } + } catch (Throwable t) { + throw new Exception("Error on i = " + i, t); } - } catch (Throwable t) { - throw new Exception("Error on i = " + i, t); + } + } finally { + if (sess != null) { + sess.close(); + sess = null; } } } @Test public void syntaxErrorEntireResult() throws Exception { - if (!this.isSetForXTests) { - return; - } CompletableFuture res = this.collection.find("NON_EXISTING_FUNCTION()").executeAsync(); try { res.get(); @@ -150,9 +150,6 @@ public void syntaxErrorEntireResult() throws Exception { @Test public void insertDocs() throws Exception { - if (!this.isSetForXTests) { - return; - } String json = "{'firstName':'Frank', 'middleName':'Lloyd', 'lastName':'Wright'}".replaceAll("'", "\""); if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { json = json.replace("{", "{\"_id\": \"1\", "); // Inject an _id. @@ -174,9 +171,6 @@ public void insertDocs() throws Exception { @Test public void manyModifications() throws Exception { - if (!this.isSetForXTests) { - return; - } // we guarantee serial execution String json = "{'n':1}".replaceAll("'", "\""); if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { @@ -200,9 +194,6 @@ public void manyModifications() throws Exception { @Test public void sqlUpdate() throws Exception { - if (!this.isSetForXTests) { - return; - } CompletableFuture resF = this.session.sql("set @cjTestVar = 1").executeAsync(); resF.thenAccept(res -> { assertFalse(res.hasData()); @@ -215,9 +206,6 @@ public void sqlUpdate() throws Exception { @Test public void sqlQuery() throws Exception { - if (!this.isSetForXTests) { - return; - } CompletableFuture resF = this.session.sql("select 1,2,3 from dual").executeAsync(); resF.thenAccept(res -> { assertTrue(res.hasData()); @@ -234,16 +222,8 @@ public void sqlQuery() throws Exception { @Test public void sqlError() throws Exception { - if (!this.isSetForXTests) { - return; - } - try { - CompletableFuture resF = this.session.sql("select x from dont_create_this_table").executeAsync(); - resF.get(); - fail("Should throw an exception"); - } catch (Exception ex) { - // expected - } + CompletableFuture resF = this.session.sql("select x from dont_create_this_table").executeAsync(); + assertThrows(Exception.class, () -> resF.get()); } /** @@ -253,9 +233,6 @@ public void sqlError() throws Exception { */ @Test public void manyFutures() throws Exception { - if (!this.isSetForXTests) { - return; - } int MANY = 10;//100000; Collection coll = this.collection; List> futures = new ArrayList<>(); diff --git a/src/test/java/testsuite/x/devapi/BaseCollectionTestCase.java b/src/test/java/testsuite/x/devapi/BaseCollectionTestCase.java index 0064a1014..2b7c26584 100644 --- a/src/test/java/testsuite/x/devapi/BaseCollectionTestCase.java +++ b/src/test/java/testsuite/x/devapi/BaseCollectionTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. + * Copyright (c) 2017, 2021, 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 @@ -29,11 +29,14 @@ package testsuite.x.devapi; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import java.util.Random; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.xdevapi.Collection; public class BaseCollectionTestCase extends DevApiBaseTestCase { @@ -43,6 +46,7 @@ public class BaseCollectionTestCase extends DevApiBaseTestCase { @BeforeEach public void setupCollectionTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); if (setupTestSession()) { this.collectionName = "CollectionTest-" + new Random().nextInt(1000); dropCollection(this.collectionName); diff --git a/src/test/java/testsuite/x/devapi/BaseTableTestCase.java b/src/test/java/testsuite/x/devapi/BaseTableTestCase.java index ed78b1358..b239655a9 100644 --- a/src/test/java/testsuite/x/devapi/BaseTableTestCase.java +++ b/src/test/java/testsuite/x/devapi/BaseTableTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. + * Copyright (c) 2017, 2021, 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 @@ -29,15 +29,20 @@ package testsuite.x.devapi; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import com.mysql.cj.conf.PropertyDefinitions; + /** * @todo */ public class BaseTableTestCase extends DevApiBaseTestCase { @BeforeEach public void setupBaseTableTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); super.setupTestSession(); } diff --git a/src/test/java/testsuite/x/devapi/BindTest.java b/src/test/java/testsuite/x/devapi/BindTest.java index d1be051ef..43ec9e130 100644 --- a/src/test/java/testsuite/x/devapi/BindTest.java +++ b/src/test/java/testsuite/x/devapi/BindTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,7 +32,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.util.HashMap; import java.util.Map; @@ -45,9 +44,6 @@ public class BindTest extends BaseCollectionTestCase { @Test public void removeWithBind() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": 1, \"x\":1}").execute(); this.collection.add("{\"_id\": 2, \"x\":2}").execute(); @@ -68,9 +64,6 @@ public void removeWithBind() { @Test public void removeWithNamedBinds() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": 1, \"x\":1}").execute(); this.collection.add("{\"_id\": 2, \"x\":2}").execute(); @@ -93,9 +86,6 @@ public void removeWithNamedBinds() { @Test public void bug21798850() { - if (!this.isSetForXTests) { - return; - } Map params = new HashMap<>(); params.put("thePlaceholder1", 1); params.put("thePlaceholder2", 2); @@ -106,22 +96,12 @@ public void bug21798850() { @Test public void properExceptionUnboundParams() { - if (!this.isSetForXTests) { - return; - } - try { - this.collection.find("a = :arg1 or b = :arg2").bind("arg1", 1).execute(); - fail("Should raise an exception on unbound placeholder arguments"); - } catch (WrongArgumentException ex) { - assertEquals("Placeholder 'arg2' is not bound", ex.getMessage()); - } + assertThrows(WrongArgumentException.class, "Placeholder 'arg2' is not bound", + () -> this.collection.find("a = :arg1 or b = :arg2").bind("arg1", 1).execute()); } @Test public void bindArgsOrder() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{'_id': 1, 'x':1,'y':2}".replaceAll("'", "\"")).execute(); } else { diff --git a/src/test/java/testsuite/x/devapi/CollectionAddTest.java b/src/test/java/testsuite/x/devapi/CollectionAddTest.java index 77dcee157..30b54c815 100644 --- a/src/test/java/testsuite/x/devapi/CollectionAddTest.java +++ b/src/test/java/testsuite/x/devapi/CollectionAddTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.math.BigDecimal; import java.util.HashMap; @@ -48,7 +49,10 @@ import com.mysql.cj.protocol.x.XProtocolError; import com.mysql.cj.xdevapi.AddResult; import com.mysql.cj.xdevapi.DbDoc; +import com.mysql.cj.xdevapi.DbDocImpl; import com.mysql.cj.xdevapi.DocResult; +import com.mysql.cj.xdevapi.JsonArray; +import com.mysql.cj.xdevapi.JsonLiteral; import com.mysql.cj.xdevapi.JsonNumber; import com.mysql.cj.xdevapi.JsonString; import com.mysql.cj.xdevapi.Result; @@ -57,9 +61,6 @@ public class CollectionAddTest extends BaseCollectionTestCase { @Test public void testBasicAddString() { - if (!this.isSetForXTests) { - return; - } String json = "{'firstName':'Frank', 'middleName':'Lloyd', 'lastName':'Wright'}".replaceAll("'", "\""); if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { json = json.replace("{", "{\"_id\": \"1\", "); // Inject an _id. @@ -79,9 +80,6 @@ public void testBasicAddString() { @Test public void testBasicAddStringArray() { - if (!this.isSetForXTests) { - return; - } this.collection.add("{\"_id\": 1}", "{\"_id\": 2}").execute(); assertEquals(true, this.collection.find("_id = 1").execute().hasNext()); assertEquals(true, this.collection.find("_id = 2").execute().hasNext()); @@ -97,9 +95,6 @@ public void testBasicAddStringArray() { @Test public void testBasicAddDoc() { - if (!this.isSetForXTests) { - return; - } DbDoc doc = this.collection.newDoc().add("firstName", new JsonString().setValue("Georgia")); doc.add("middleName", new JsonString().setValue("Totto")); doc.add("lastName", new JsonString().setValue("O'Keeffe")); @@ -121,10 +116,6 @@ public void testBasicAddDoc() { @Test public void testBasicAddDocArray() { - if (!this.isSetForXTests) { - return; - } - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion(("8.0.5")))) { AddResult res1 = this.collection.add(this.collection.newDoc().add("f1", new JsonString().setValue("doc1")), this.collection.newDoc().add("f1", new JsonString().setValue("doc2"))).execute(); @@ -157,11 +148,8 @@ public void testBasicAddDocArray() { } @Test - @Disabled("needs implemented") + @Disabled("Collection.add(Map doc) is not implemented yet.") public void testBasicAddMap() { - if (!this.isSetForXTests) { - return; - } Map doc = new HashMap<>(); doc.put("x", 1); doc.put("y", "this is y"); @@ -177,9 +165,6 @@ public void testBasicAddMap() { @Test public void testAddWithAssignedId() { - if (!this.isSetForXTests) { - return; - } String json1 = "{'_id': 'Id#1', 'name': 'assignedId'}".replaceAll("'", "\""); String json2 = "{'name': 'autoId'}".replaceAll("'", "\""); AddResult res; @@ -210,9 +195,6 @@ public void testAddWithAssignedId() { @Test public void testChainedAdd() { - if (!this.isSetForXTests) { - return; - } String json = "{'_id': 1}".replaceAll("'", "\""); this.collection.add(json).add(json.replaceAll("1", "2")).execute(); @@ -223,9 +205,6 @@ public void testChainedAdd() { @Test public void testAddLargeDocument() { - if (!this.isSetForXTests) { - return; - } int docSize = 255 * 1024; StringBuilder b = new StringBuilder("{\"_id\": \"large_doc\", \"large_field\":\""); for (int i = 0; i < docSize; ++i) { @@ -241,9 +220,6 @@ public void testAddLargeDocument() { @Test public void testAddNoDocs() throws Exception { - if (!this.isSetForXTests) { - return; - } Result res = this.collection.add(new DbDoc[] {}).execute(); assertEquals(0, res.getAffectedItemsCount()); assertEquals(0, res.getWarningsCount()); @@ -256,9 +232,8 @@ public void testAddNoDocs() throws Exception { @Test public void testAddOrReplaceOne() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3")), "MySQL 8.0.3+ is required to run this test."); + this.collection.add("{\"_id\": \"id1\", \"a\": 1}").execute(); // new _id @@ -285,7 +260,7 @@ public void testAddOrReplaceOne() { assertTrue(this.collection.find("a = 4").execute().hasNext()); // a new document with _id field that doesn't match id parameter - assertThrows(XDevAPIError.class, "Document already has an _id that doesn't match to id parameter", new Callable() { + assertThrows(XDevAPIError.class, "Replacement document has an _id that is different than the matched document\\.", new Callable() { public Void call() throws Exception { CollectionAddTest.this.collection.addOrReplaceOne("id2", CollectionAddTest.this.collection.newDoc().add("_id", new JsonString().setValue("id111"))); @@ -328,10 +303,6 @@ public Void call() throws Exception { */ @Test public void testBug21914769() { - if (!this.isSetForXTests) { - return; - } - assertThrows(WrongArgumentException.class, "Invalid whitespace character ']'.", new Callable() { public Void call() throws Exception { CollectionAddTest.this.collection.add("{\"_id\":\"1004\",\"F1\": ] }").execute(); @@ -347,10 +318,6 @@ public Void call() throws Exception { */ @Test public void testBug92264() throws Exception { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\":\"1\",\"dataCreated\": 1546300800000}").execute(); DocResult docs = this.collection.find("dataCreated = 1546300800000").execute(); @@ -365,14 +332,494 @@ public void testBug92264() throws Exception { */ @Test public void testBug92819() { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\":\"1\",\"emptyArray\": []}").execute(); DocResult docs = this.collection.find("_id = '1'").execute(); assertTrue(docs.hasNext()); DbDoc doc = docs.next(); assertEquals("[]", doc.get("emptyArray").toString()); } + + @Test + public void testCollectionAddBasic() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 100; + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonString().setValue("Field-2-Data-" + i)); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(300 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + /* add(DbDoc doc) */ + DbDoc newDoc = new DbDocImpl(); + newDoc.add("_id", new JsonString().setValue(String.valueOf(maxrec + 1000))); + newDoc.add("F1", new JsonString().setValue("Field-1-Data-" + maxrec)); + newDoc.add("F2", new JsonString().setValue("Field-2-Data-" + maxrec)); + newDoc.add("F3", new JsonNumber().setValue(String.valueOf(300 + maxrec))); + this.collection.add(newDoc).execute(); + + /* add(String jsonString) */ + String json = "{'_id':'" + (maxrec + 1000 + 1) + "','F1':'Field-1-Data-" + (maxrec + 1) + "','F2':'Field-2-Data-" + (maxrec + 1) + "','F3':" + + (300 + maxrec + 1) + "}"; + json = json.replaceAll("'", "\""); + this.collection.add(json).execute(); + + /* No _Id Field and chained add() */ + json = "{'F1': 'Field-1-Data-9999','F2': 'Field-2-Data-9999','F3': 'Field-3-Data-9999'}".replaceAll("'", "\""); + this.collection.add(json).add(json.replaceAll("9", "8")).execute(); + + assertEquals((maxrec + 4), this.collection.count()); + DocResult docs = this.collection.find("$._id = '1000'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + DbDoc doc = null; + doc = docs.next(); + assertEquals("1000", ((JsonString) doc.get("_id")).getString()); + System.out.println("ID :" + ((JsonString) doc.get("_id")).getString()); + System.out.println("F1 :" + ((JsonString) doc.get("f1")).getString()); + System.out.println("F2 :" + ((JsonString) doc.get("f2")).getString()); + System.out.println("F3 :" + ((JsonNumber) doc.get("f3")).getInteger()); + } + + @Test + public void testCollectionAddStrings() throws Exception { + DbDoc doc = null; + DocResult docs = null; + String json = ""; + + json = "{'_id':'1001','F1':'{Open Brace','F2':'}Close Brace','F3':'$Dollor Sign'}".replaceAll("'", "\""); + this.collection.add(json).execute(); + json = "{'_id':'1002','F1':'{Open and }Close Brace','F2':'}Close and {Open Brace','F3':'$Dollor and << Shift Sign'}".replaceAll("'", "\""); + this.collection.add(json).execute(); + json = "{'_id':'1003','F1':'{{2Open and }}2Close Brace','F2':'}}2Close and {{2Open Brace','F3':'$.Dollor dot and $$2Dollor'}".replaceAll("'", "\""); + this.collection.add(json).execute(); + json = "{'_id':'1004','F1':'{{{3Open and }}}3Close Brace','F2':'}}}3Close and {{{3Open Brace','F3':'$.Dollor dot and ,Comma'}".replaceAll("'", "\""); + this.collection.add(json).execute(); + + json = "{'_id':'1005','F1':'[Square Open','F2':']Square Close','F3':'$.Dollor dot and :Colon'}".replaceAll("'", "\""); + this.collection.add(json).execute(); + + json = "{'_id':'1006','F1':'[Square Open ]Square Close','F2':']Square Close [Square Open','F3':'$.,:{[}] '}".replaceAll("'", "\""); + this.collection.add(json).execute(); + + /* find with Condition */ + docs = this.collection.find("$.F1 Like '{{2%}%2%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals("{{2Open and }}2Close Brace", (((JsonString) doc.get("f1")).getString())); + assertEquals("}}2Close and {{2Open Brace", (((JsonString) doc.get("f2")).getString())); + assertEquals("$.Dollor dot and $$2Dollor", (((JsonString) doc.get("f3")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.F1 Like '[%]%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals("[Square Open ]Square Close", (((JsonString) doc.get("f1")).getString())); + assertEquals("]Square Close [Square Open", (((JsonString) doc.get("f2")).getString())); + assertEquals("$.,:{[}] ", (((JsonString) doc.get("f3")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.F3 Like '$%]%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals("[Square Open ]Square Close", (((JsonString) doc.get("f1")).getString())); + assertEquals("]Square Close [Square Open", (((JsonString) doc.get("f2")).getString())); + assertEquals("$.,:{[}] ", (((JsonString) doc.get("f3")).getString())); + assertFalse(docs.hasNext()); + } + + @Test + public void testCollectionAddBigKeys() throws Exception { + int i = 0, j = 0; + int maxkey = 10; + int maxrec = 5; + int keylen = (1024); + String key_sub = buildString(keylen, 'X'); + String data_sub = "Data"; + + /* Insert maxrec records with maxkey (key,value) pairs with key length=keylen */ + String key, data, query; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc = new DbDocImpl(); + newDoc.add("_id", new JsonNumber().setValue(String.valueOf(i))); + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + data = data_sub + j; + newDoc.add(key, new JsonString().setValue(data)); + } + this.collection.add(newDoc).execute(); + newDoc = null; + } + assertEquals((maxrec), this.collection.count()); + + /* Fetch all keys */ + query = "$._id as _id"; + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + query = query + ",$." + key + " as " + key; + } + DocResult docs = this.collection.find().orderBy("$._id").fields(query).execute(); + DbDoc doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + data = data_sub + j; + assertEquals((data), ((JsonString) doc.get(key)).getString()); + } + i++; + } + assertEquals((maxrec), i); + } + + @Test + public void testCollectionAddBigKeyData() throws Exception { + int i = 0, j = 0; + int maxkey = 10; + int maxrec = 5; + int keylen = (10); + int datalen = (1 * 5); + String key_sub = buildString(keylen, 'X'); + String data_sub = buildString(datalen, 'X'); + + /* Insert maxrec records with maxkey (key,value) pairs with key length=keylen and datalength=datalen */ + String key, data, query; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc = new DbDocImpl(); + newDoc.add("_id", new JsonNumber().setValue(String.valueOf(i))); + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + data = data_sub + j; + newDoc.add(key, new JsonString().setValue(data)); + } + this.collection.add(newDoc).execute(); + newDoc = null; + } + assertEquals((maxrec), this.collection.count()); + + /* Fetch all keys */ + query = "$._id as _id"; + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + query = query + ",$." + key + " as " + key; + } + DocResult docs = this.collection.find().orderBy("$._id").fields(query).execute(); + DbDoc doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + data = data_sub + j; + assertEquals((data), ((JsonString) doc.get(key)).getString()); + } + i++; + } + assertEquals((maxrec), i); + } + + @Test + public void testCollectionAddBigKeyDataString() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0; + int maxrec = 5; + int datalen = (1 * 5); + String longdata = ""; + String json = ""; + + /* Insert maxrec (key,value) pairs with datalength=datalen */ + AddResult res = null; + for (i = 0; i < maxrec; i++) { + json = "{\"F1\":\"Field-6-Data-" + (i) + "\",\"F2\":\""; + longdata = buildString(datalen + i, 'X'); + json = json + longdata + "\"}"; + res = this.collection.add(json).add(json.replaceAll("6", "7")).add(json.replaceAll("6", "8")).execute(); + System.out.println("getGeneratedIds: " + res.getGeneratedIds()); + + } + assertEquals((maxrec * 3), this.collection.count()); + + /* Fetch all keys */ + DocResult docs = this.collection.find("$.F1 like '%-6-%'").orderBy("$.F2 asc").fields("$._id as _id, $.F1 as fld1, $.F2 as fld2").execute(); + DbDoc doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + longdata = buildString(datalen + i, 'X'); + assertEquals((longdata), ((JsonString) doc.get("fld2")).getString()); + i++; + } + assertEquals((maxrec), i); + } + + @Test + public void testCollectionAddManyKeys() throws Exception { + int i = 0, j = 0; + int maxkey = 500; + int maxrec = 5; + String key_sub = "keyname_"; + String data_sub = "Data"; + + /* Insert maxrec each with maxkey number of (key,value) pairs */ + String key, data, query; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc = new DbDocImpl(); + newDoc.add("_id", new JsonNumber().setValue(String.valueOf(i))); + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + data = data_sub + j; + newDoc.add(key, new JsonString().setValue(data)); + } + this.collection.add(newDoc).execute(); + newDoc = null; + } + assertEquals((maxrec), this.collection.count()); + + /* Fetch all keys */ + query = "$._id as _id"; + for (j = 0; j < maxkey; j++) { + key = key_sub + j; + query = query + ",$." + key + " as " + key; + } + DocResult docs = this.collection.find().orderBy("$._id").fields(query).execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* fetch maxrec-1 records */ + docs = this.collection.find("$._id < " + (maxrec - 1)).orderBy("$._id").fields("$._id as _id, $." + key_sub + (maxkey - 1) + " as Key1").execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + assertEquals((maxrec - 1), i); + } + + @Test + public void testCollectionAddManyRecords() throws Exception { + int i = 0, maxrec = 10; + + /* add(DbDoc[] docs) -> Insert maxrec number of records in ne execution */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonNumber().setValue(String.valueOf(i))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(300 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + DocResult docs = this.collection.find("$._id >= 0").orderBy("$._id").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + DbDoc doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) i, (long) (((JsonNumber) doc.get("_id")).getInteger())); + i++; + } + assertEquals((maxrec), i); + } + + /**/ + @Test + public void testCollectionAddArray() throws Exception { + int i = 0, j = 0, k = 0, maxrec = 5, arraySize = 9; + + /* add(DbDoc[] docs) -> Array data */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + JsonArray jarray1 = new JsonArray(); + JsonArray jarray2 = new JsonArray(); + JsonArray jarray3 = new JsonArray(); + JsonArray jarray4 = new JsonArray(); + JsonArray jarray5 = new JsonArray(); + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonNumber().setValue(String.valueOf(i))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + for (j = 0; j < (arraySize); j++) { + jarray1.addValue(new JsonNumber().setValue(String.valueOf((j)))); + } + newDoc2.add("ARR_INT", jarray1); + + for (j = 0; j < (arraySize); j++) { + jarray2.addValue(new JsonString().setValue("Data-" + j)); + } + newDoc2.add("ARR_STR", jarray2); + + for (j = 0; j < (arraySize); j++) { + if (j % 3 == 2) { + jarray3.addValue(JsonLiteral.FALSE); + } else if (j % 3 == 1) { + jarray3.addValue(JsonLiteral.TRUE); + } else { + jarray3.addValue(JsonLiteral.NULL); + } + } + newDoc2.add("ARR_LIT", jarray3); + + for (j = 0; j < (arraySize); j++) { + JsonArray subarray = new JsonArray(); + + for (k = 0; k < 5; k++) { + subarray.addValue(new JsonNumber().setValue(String.valueOf((j)))); + } + jarray4.addValue(subarray); + subarray = null; + } + newDoc2.add("ARR_ARR", jarray4); + + for (j = 0; j < (arraySize); j++) { + if (j % 3 == 2) { + jarray5.addValue(JsonLiteral.FALSE); + } else if (j % 3 == 1) { + jarray5.addValue(new JsonString().setValue("Data-" + j)); + } else { + jarray5.addValue(new JsonNumber().setValue(String.valueOf((j)))); + } + + } + newDoc2.add("ARR_MIX", jarray5); + this.collection.add(newDoc2).execute(); + jsonlist[i] = newDoc2; + newDoc2 = null; + jarray1 = null; + jarray2 = null; + jarray3 = null; + jarray4 = null; + jarray5 = null; + } + //coll.add(jsonlist).execute(); + jsonlist = null; + assertEquals((maxrec), this.collection.count()); + DocResult docs = this.collection.find("$._id >= 0").orderBy("$._id").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + DbDoc doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) i, (long) (((JsonNumber) doc.get("_id")).getInteger())); + i++; + } + assertEquals((maxrec), i); + } + + @Test + public void testGetGeneratedIds() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + AddResult res = null; + DbDoc doc = null; + DocResult docs = null; + int i = 0; + + //One record using String + String json = "{\"FLD1\":\"Data1\"}"; + res = this.collection.add(json).execute(); + List docIds = res.getGeneratedIds(); + assertTrue(docIds.get(0).matches("[a-f0-9]{28}")); + assertEquals(1, this.collection.count()); + assertEquals(1, docIds.size()); + + //More than One record using String + json = "{\"FLD1\":\"Data2\"}"; + res = this.collection.add(json).add("{}").add("{\"_id\":\"id1\"}").add("{\"FLD1\":\"Data3\"}").execute(); + docIds = res.getGeneratedIds(); + assertEquals(5, this.collection.count()); + assertEquals(3, docIds.size()); + + //More than One record using String, and single add() + json = "{\"FLD1\":\"Data15\"}"; + res = this.collection.add(json, "{}", "{\"_id\":\"id2\"}", "{\"FLD1\":\"Data16\"}").execute(); + docIds = res.getGeneratedIds(); + assertEquals(9, this.collection.count()); + assertEquals(3, docIds.size()); + + //One record using DbDoc + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("FLD1", new JsonString().setValue("Data4")); + res = this.collection.add(newDoc2).execute(); + docIds = res.getGeneratedIds(); + assertEquals(10, this.collection.count()); + assertEquals(1, docIds.size()); + assertTrue(docIds.get(0).matches("[a-f0-9]{28}")); + + //More Than One record using DbDoc + newDoc2.clear(); + newDoc2.add("FLD1", new JsonString().setValue("Data5")); + DbDoc newDoc3 = new DbDocImpl(); + newDoc3.add("FLD1", new JsonString().setValue("Data6")); + res = this.collection.add(newDoc2).add(newDoc3).execute(); + docIds = res.getGeneratedIds(); + assertEquals(12, this.collection.count()); + assertEquals(2, docIds.size()); + assertTrue(docIds.get(0).compareTo(docIds.get(1)) < 0); + + //One record using DbDoc[] + DbDoc[] jsonlist1 = new DbDocImpl[1]; + newDoc2.clear(); + newDoc2.add("FLD1", new JsonString().setValue("Data7")); + jsonlist1[0] = newDoc2; + res = this.collection.add(jsonlist1).execute(); + docIds = res.getGeneratedIds(); + assertEquals(13, this.collection.count()); + assertEquals(1, docIds.size()); + assertTrue(docIds.get(0).matches("[a-f0-9]{28}")); + + //More Than One record using DbDoc[] + DbDoc[] jsonlist = new DbDocImpl[5]; + for (i = 0; i < 5; i++) { + DbDoc newDoc = new DbDocImpl(); + newDoc.add("FLD1", new JsonString().setValue("Data" + (i + 8))); + if (i % 2 == 0) { + newDoc.add("_id", new JsonString().setValue("id-" + (i + 8))); + } + jsonlist[i] = newDoc; + newDoc = null; + } + res = this.collection.add(jsonlist).execute(); + docIds = res.getGeneratedIds(); + assertEquals(18, this.collection.count()); + assertEquals(2, docIds.size()); + + json = "{}"; + res = this.collection.add(json).execute(); + docIds = res.getGeneratedIds(); + assertTrue(docIds.get(0).matches("[a-f0-9]{28}")); + assertEquals(19, this.collection.count()); + assertEquals(1, docIds.size()); + + //Verify that when _id is provided by client, getGeneratedIds() will return empty + res = this.collection.add("{\"_id\":\"00001273834abcdfe\",\"FLD1\":\"Data1\",\"name\":\"name1\"}", + "{\"_id\":\"000012738uyie98rjdeje\",\"FLD2\":\"Data2\",\"name\":\"name1\"}", + "{\"_id\":\"00001273y834uhf489fe\",\"FLD3\":\"Data3\",\"name\":\"name1\"}").execute(); + docIds = res.getGeneratedIds(); + assertEquals(22, this.collection.count()); + assertEquals(0, docIds.size()); + + res = this.collection.add("{\"_id\":null,\"FLD1\":\"nulldata\"}").execute(); + docIds = res.getGeneratedIds(); + assertEquals(23, this.collection.count()); + assertEquals(0, docIds.size()); + docs = this.collection.find("$.FLD1 == 'nulldata'").execute(); + doc = docs.next(); + assertEquals("null", ((JsonLiteral) doc.get("_id")).toString()); + + //Try inserting duplicate _ids. User should get error + assertThrows(XProtocolError.class, "ERROR 5116 \\(HY000\\) Document contains a field value that is not unique but required to be", + () -> this.collection.add("{\"_id\":\"abcd1234\",\"FLD1\":\"Data1\"}").add("{\"_id\":\"abcd1234\",\"FLD1\":\"Data2\"}").execute()); + } } diff --git a/src/test/java/testsuite/x/devapi/CollectionFindTest.java b/src/test/java/testsuite/x/devapi/CollectionFindTest.java index e9408d252..8152b8d3d 100644 --- a/src/test/java/testsuite/x/devapi/CollectionFindTest.java +++ b/src/test/java/testsuite/x/devapi/CollectionFindTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -39,8 +39,14 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -56,41 +62,35 @@ import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.exceptions.WrongArgumentException; import com.mysql.cj.protocol.x.XProtocolError; +import com.mysql.cj.xdevapi.AddResult; import com.mysql.cj.xdevapi.Collection; import com.mysql.cj.xdevapi.DbDoc; +import com.mysql.cj.xdevapi.DbDocImpl; import com.mysql.cj.xdevapi.DocResult; import com.mysql.cj.xdevapi.FindStatement; import com.mysql.cj.xdevapi.FindStatementImpl; +import com.mysql.cj.xdevapi.JsonArray; import com.mysql.cj.xdevapi.JsonLiteral; import com.mysql.cj.xdevapi.JsonNumber; import com.mysql.cj.xdevapi.JsonString; +import com.mysql.cj.xdevapi.Result; import com.mysql.cj.xdevapi.Row; +import com.mysql.cj.xdevapi.RowResult; import com.mysql.cj.xdevapi.Session; import com.mysql.cj.xdevapi.SessionFactory; +import com.mysql.cj.xdevapi.SessionImpl; +import com.mysql.cj.xdevapi.SqlResult; import com.mysql.cj.xdevapi.Statement; import com.mysql.cj.xdevapi.Table; +import com.mysql.cj.xdevapi.Warning; /** * @todo */ public class CollectionFindTest extends BaseCollectionTestCase { - // @AfterEach - // @Override - // public void teardownCollectionTest() { - // try { - // super.teardownCollectionTest(); - // } catch (Exception ex) { - // // expected-to-fail tests may destroy the connection, don't penalize them here - // System.err.println("Exception during teardown:"); - // ex.printStackTrace(); - // } - // } @Test public void testProjection() { - if (!this.isSetForXTests) { - return; - } // TODO: the "1" is coming back from the server as a string. checking with xplugin team if this is ok this.collection.add("{\"_id\":\"the_id\",\"g\":1}").execute(); @@ -110,9 +110,6 @@ public void testProjection() { @Test public void testDocumentProjection() { - if (!this.isSetForXTests) { - return; - } // use a document as a projection this.collection.add("{\"_id\":\"the_id\",\"g\":1}").execute(); @@ -128,9 +125,6 @@ public void testDocumentProjection() { */ @Test public void outOfRange() { - if (!this.isSetForXTests) { - return; - } try { this.collection.add("{\"_id\": \"1\"}").execute(); DocResult docs = this.collection.find().fields(expr( @@ -148,10 +142,6 @@ public void outOfRange() { */ @Test public void testIterable() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").execute(); // Requires manual _id. this.collection.add("{\"_id\": \"2\"}").execute(); @@ -173,9 +163,6 @@ public void testIterable() { @Test public void basicCollectionAsTable() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"xyz\":1}").execute(); // Requires manual _id. } else { @@ -190,9 +177,6 @@ public void basicCollectionAsTable() { @SuppressWarnings("deprecation") @Test public void testLimitOffset() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").execute(); // Requires manual _id. this.collection.add("{\"_id\": \"2\"}").execute(); @@ -234,10 +218,6 @@ public void testLimitOffset() { @Test public void testNumericExpressions() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":1, \"y\":2}").execute(); // Requires manual _id. } else { @@ -272,10 +252,6 @@ public void testNumericExpressions() { @Test public void testBitwiseExpressions() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x1\":31, \"x2\":13, \"x3\":8, \"x4\":\"18446744073709551614\"}").execute(); // Requires manual _id. } else { @@ -300,9 +276,6 @@ public void testBitwiseExpressions() { @Test public void testIntervalExpressions() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"aDate\":\"2000-01-01\", \"aDatetime\":\"2000-01-01 12:00:01\"}").execute(); // Requires manual _id. } else { @@ -356,9 +329,6 @@ public void testIntervalExpressions() { @Test // these are important to test the "operator" (BETWEEN/REGEXP/etc) to function representation in the protocol public void testIlriExpressions() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"a\":\"some text with 5432\", \"b\":\"100\", \"c\":true}").execute(); // Requires manual _id. } else { @@ -434,9 +404,6 @@ public void testIlriExpressions() { @Test public void cast() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":100}").execute(); } else { @@ -449,9 +416,6 @@ public void cast() { @Test public void testOrderBy() { - if (!this.isSetForXTests) { - return; - } this.collection.add("{\"_id\":1, \"x\":20, \"y\":22}").execute(); this.collection.add("{\"_id\":2, \"x\":20, \"y\":21}").execute(); this.collection.add("{\"_id\":3, \"x\":10, \"y\":40}").execute(); @@ -483,9 +447,7 @@ public void testOrderBy() { @Test public void testCollectionRowLocks() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3")), "MySQL 8.0.3+ is required to run this test."); this.collection.add("{\"_id\":\"1\", \"a\":1}").execute(); this.collection.add("{\"_id\":\"2\", \"a\":1}").execute(); @@ -585,9 +547,7 @@ public Void call() throws Exception { @Test public void testCollectionRowLockOptions() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5")), "MySQL 8.0.5+ is required to run this test."); Function> asStringList = rr -> rr.fetchAll().stream().map(d -> ((JsonString) d.get("_id")).getString()) .collect(Collectors.toList()); @@ -875,10 +835,6 @@ public void testCollectionRowLockOptions() throws Exception { @Test public void getOne() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"a\":1}").execute(); this.collection.add("{\"_id\": \"2\", \"a\":2}").execute(); @@ -901,10 +857,6 @@ public void getOne() { @Test public void testGroupingQuery() { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\": \"01\", \"name\":\"Mamie\", \"age\":11, \"something\":0}").execute(); this.collection.add("{\"_id\": \"02\", \"name\":\"Eulalia\", \"age\":11, \"something\":0}").execute(); this.collection.add("{\"_id\": \"03\", \"name\":\"Polly\", \"age\":12, \"something\":0}").execute(); @@ -953,10 +905,6 @@ public void testGroupingQuery() { */ @Test public void testBug21921956() { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\": \"1004\", \"F1\": 123}").execute(); DocResult res = this.collection.find().fields(expr("{'X':4<< -(1-2)}")).execute(); @@ -988,9 +936,7 @@ public void testBug21921956() { @Test public void testPreparedStatements() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); // Prepare test data. this.collection.add("{\"_id\":\"1\", \"ord\": 1}", "{\"_id\":\"2\", \"ord\": 2}", "{\"_id\":\"3\", \"ord\": 3}", "{\"_id\":\"4\", \"ord\": 4}", @@ -1205,10 +1151,6 @@ private void assertTestPreparedStatementsResult(DocResult res, int expectedMin, @Test @SuppressWarnings("deprecation") public void testDeprecateWhere() throws Exception { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\":\"1\", \"ord\": 1}", "{\"_id\":\"2\", \"ord\": 2}", "{\"_id\":\"3\", \"ord\": 3}", "{\"_id\":\"4\", \"ord\": 4}", "{\"_id\":\"5\", \"ord\": 5}", "{\"_id\":\"6\", \"ord\": 6}", "{\"_id\":\"7\", \"ord\": 7}", "{\"_id\":\"8\", \"ord\": 8}").execute(); @@ -1222,10 +1164,6 @@ public void testDeprecateWhere() throws Exception { @Test public void testOverlaps() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.17"))) { // TSFR6 assertThrows(XProtocolError.class, "ERROR 5150 \\(HY000\\) Invalid operator overlaps", @@ -1313,4 +1251,2681 @@ public void testOverlaps() { res = this.collection.find("['21', '2', '3'] OVERLAPS $.age").execute(); assertEquals(0, res.count()); } + + @Test + public void testCollectionFindInSanity() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + DbDoc doc = null; + DocResult docs = null; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 - i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* find with single element IN which uses json_contains */ + docs = this.collection.find("'1001' in $._id").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with multiple IN which uses json_contains */ + String findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not in $._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + docs = this.collection.find(findCond).execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with single IN for string with orderBy */ + docs = this.collection.find("'Field-1-Data-2' in $.F1").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1002), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with single IN for numeric with orderBy */ + docs = this.collection.find("10000 in $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with single IN for float with orderBy */ + docs = this.collection.find("20.1234 in $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1001), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* Testing with table */ + Table table = this.schema.getCollectionAsTable(this.collectionName); + + /* find with single IN for string */ + RowResult rows = table.select("doc->$._id as _id").where("'1001' in doc->$._id").execute(); + Row r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + assertFalse(rows.hasNext()); + + /* find with multiple IN in single select */ + findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not in doc->$._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + /* find with single IN for string */ + rows = table.select("doc->$._id as _id").where(findCond).execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1000\""); + assertFalse(rows.hasNext()); + + /* find with single IN for float */ + rows = table.select("doc->$._id as _id").where("20.1234 in doc->$.F2").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + assertFalse(rows.hasNext()); + + /* find with single IN for string */ + rows = table.select("doc->$._id as _id").where("'Field-1-Data-2' in doc->$.F1").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1002\""); + assertFalse(rows.hasNext()); + + /* find with single IN for numeric */ + rows = table.select("doc->$._id as _id").where("10000 in doc->$.F4").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1000\""); + assertFalse(rows.hasNext()); + } + + @Test + public void testCollectionFindInValidArray() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, j = 0, maxrec = 8, minArraySize = 3; + DbDoc doc = null; + DocResult docs = null; + long l3 = 2147483647; + double d1 = 1000.1234; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + jarray.addValue(new JsonNumber().setValue(String.valueOf((l3 + j + i)))); + } + newDoc2.add("ARR1", jarray); + + JsonArray karray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + karray.addValue(new JsonNumber().setValue(String.valueOf((d1 + j + i)))); + } + newDoc2.add("ARR2", karray); + JsonArray larray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + larray.addValue(new JsonString().setValue("St_" + i + "_" + j)); + } + newDoc2.add("ARR3", larray); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + + assertEquals((maxrec), this.collection.count()); + + /* find with single IN in array */ + docs = this.collection.find("2147483647 in $.ARR1").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with array IN array */ + docs = this.collection.find("[2147483647, 2147483648, 2147483649] in $.ARR1").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with array IN array with orderBy */ + docs = this.collection.find("[2147483648, 2147483648, 2147483649] in $.ARR1").orderBy("_id").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with NULL IN array */ + docs = this.collection.find("NULL in $.ARR1").execute(); + assertFalse(docs.hasNext()); + } + + @Test + public void testCollectionFindInValidMax() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, j = 0, maxFld = 100; + DbDoc doc = null; + DocResult docs = null; + String json = ""; + this.session.startTransaction(); + + //Max depth N records + + //Insert and Find max array + int maxdepth = 10;//97 + json = "{\"_id\":\"1002\",\"XYZ\":1111"; + for (j = 0; j < maxFld; j++) { + json = json + ",\"ARR" + j + "\":["; + for (i = 0; i < maxdepth; i++) { + json = json + i + ",["; + } + json = json + i; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "]," + i; + } + json = json + "]"; + } + json = json + "}"; + this.collection.add(json).execute(); + + json = "{\"_id\":\"1003\",\"XYZ\":2222"; + //maxdepth = 4; + for (j = 0; j < maxFld; j++) { + json = json + ",\"DATAX" + j + "\":"; + for (i = 0; i < maxdepth; i++) { + json = json + "{\"D" + i + "\":"; + } + json = json + maxdepth; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "}"; + } + } + json = json + "}"; + this.collection.add(json).execute(); + + // Both arrays and many {} + //maxdepth = 4; + json = "{\"_id\":\"1001\",\"XYZ\":3333"; + for (j = 0; j < maxFld; j++) { + json = json + ",\"ARR" + j + "\":["; + for (i = 0; i < maxdepth; i++) { + json = json + i + ",["; + } + json = json + i; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "]," + i; + } + json = json + "]"; + } + + for (j = 0; j < maxFld; j++) { + json = json + ",\"DATAX" + j + "\":"; + for (i = 0; i < maxdepth; i++) { + json = json + "{\"D" + i + "\":"; + } + json = json + maxdepth; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "}"; + } + } + json = json + "}"; + this.collection.add(json).execute(); + + /* find with single IN in array with max depth */ + docs = this.collection.find("10 in $.ARR0").orderBy("_id").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + json = ""; + json = json + "["; + for (i = 0; i < maxdepth; i++) { + json = json + i + ",["; + } + json = json + i; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "]," + i; + } + json = json + "] in $.ARR0"; + + /* find with single IN in array's max depth - 1 element */ + docs = this.collection.find(json).orderBy("_id").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + json = ""; + for (i = 0; i < maxdepth; i++) { + json = json + "{\"D" + i + "\":"; + } + json = json + maxdepth; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "}"; + } + + json = json + " in $.DATAX0"; + + /* find with single IN in Document */ + docs = this.collection.find(json).orderBy("_id").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1003", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + Table table = this.schema.getCollectionAsTable(this.collectionName); + + json = ""; + json = json + "["; + for (i = 0; i < maxdepth; i++) { + json = json + i + ",["; + } + json = json + i; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "]," + i; + } + json = json + "] in doc->$.ARR0"; + + /* find with single IN in max depth Document */ + RowResult rows = table.select("doc->$._id as _id").where(json).execute(); + Row r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1002\""); + assertFalse(rows.hasNext()); + + json = ""; + for (i = 0; i < maxdepth; i++) { + json = json + "{\"D" + i + "\":"; + } + json = json + maxdepth; + for (i = maxdepth - 1; i >= 0; i--) { + json = json + "}"; + } + + json = json + " in doc->$.DATAX0"; + + /* find with single IN in max depth Document */ + rows = table.select("doc->$._id as _id").where(json).execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1003\""); + assertFalse(rows.hasNext()); + + rows = table.select("doc->$._id as _id").where("10 in doc->$.ARR0").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1002\""); + assertFalse(rows.hasNext()); + } + + @Test + public void testCollectionFindInValidFunction() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + DbDoc doc = null; + DocResult docs = null; + + this.collection.add("{\"_id\": \"1001\", \"ARR\":[1,1,2], \"ARR1\":[\"name1\", \"name2\", \"name3\"]}").execute(); + this.collection.add("{\"_id\": \"1002\", \"ARR\":[1,2,3], \"ARR1\":[\"name4\", \"name5\", \"name6\"]}").execute(); + this.collection.add("{\"_id\": \"1003\", \"ARR\":[1,4,5], \"ARR1\":[\"name1\", \"name1\", \"name5\"]}").execute(); + + docs = this.collection.find("[1,1,3] in $.ARR").execute(); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("[2,5] in $.ARR").execute(); + assertFalse(docs.hasNext()); + + docs = this.collection.find("(1+2) in (1, 2, 3)").execute(); + doc = docs.next(); + + docs = this.collection.find("concat('name', '6') in ('name1', 'name2', 'name6')").execute(); + doc = docs.next(); + + Table tabNew = this.schema.getCollectionAsTable(this.collectionName); + + RowResult rows = tabNew.select("doc->$._id as _id").where("(1+2) in (1, 2, 3)").execute(); + rows.next(); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) CONT_IN expression requires operator that produce a JSON value\\.", + () -> tabNew.select("doc->$._id as _id").where("(1+2) in [1, 2, 3]").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) CONT_IN expression requires operator that produce a JSON value\\.", + () -> tabNew.select("doc->$._id as _id").where("(1+2) in doc->$.ARR").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) CONT_IN expression requires function that produce a JSON value\\.", + () -> this.collection.find("concat('name', '6') in ['name1', 'name2', 'name6']").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) CONT_IN expression requires operator that produce a JSON value\\.", + () -> this.collection.find("(1+2) in $.ARR").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) CONT_IN expression requires function that produce a JSON value\\.", + () -> this.collection.find("concat('name', '6') in $.ARR1").execute()); + } + + @Test + public void testCollectionFindInValidMix() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, j = 0, maxrec = 8, minArraySize = 3; + DbDoc doc = null; + DocResult docs = null; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + jarray.addValue(new JsonString().setValue("Field-1-Data-" + i)); + } + newDoc2.add("ARR1", jarray); + + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + + assertEquals((maxrec), this.collection.count()); + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1100))); + newDoc2.add("ARR1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonString().setValue("10-15-201" + i)); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + /* find with IN for key having mixture of value and array */ + docs = this.collection.find("\"10-15-2017\" in $.F2").execute(); + doc = docs.next(); + assertEquals("1107", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with NULL IN in key having mix of array and string */ + docs = this.collection.find("NULL in $.ARR1").execute(); + assertFalse(docs.hasNext()); + } + + @Test + public void testCollectionFindInInvalid() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, j = 0, maxrec = 8, minArraySize = 3; + DocResult docs = null; + String json = ""; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + jarray.addValue(new JsonString().setValue("Field-1-Data-" + i)); + } + newDoc2.add("ARR1", jarray); + + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + + assertEquals((maxrec), this.collection.count()); + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1100))); + newDoc2.add("ARR1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonString().setValue("10-15-201" + i)); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + json = "{\"_id\":\"1201\",\"XYZ\":2222, \"DATAX\":{\"D1\":1, \"D2\":2, \"D3\":3}}"; + this.collection.add(json).execute(); + + /* find with invalid IN in document */ + try { + docs = this.collection.find("{\"D1\":3, \"D2\":2, \"D3\":3} in $.DATAX").execute(); + assertFalse(docs.hasNext()); + } catch (XProtocolError Ex) { + Ex.printStackTrace(); + if (Ex.getErrorCode() != MysqlErrorNumbers.ER_BAD_NULL_ERROR) { + throw Ex; + } + } + + /* find with IN that does not match */ + docs = this.collection.find("\"2222\" in $.XYZ").execute(); + assertFalse(docs.hasNext()); + + /* find with NULL IN */ + docs = this.collection.find("NULL in $.ARR1").execute(); + assertFalse(docs.hasNext()); + + /* find with NULL IN */ + docs = this.collection.find("NULL in $.DATAX").execute(); + assertFalse(docs.hasNext()); + + /* find with IN for non existant key */ + docs = this.collection.find("\"ABC\" in $.nonexistant").execute(); + assertFalse(docs.hasNext()); + } + + @Test + public void testCollectionFindInUpdate() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + DbDoc doc = null; + DocResult docs = null; + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 - i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* modify with single IN */ + Result res = this.collection.modify("'1001' in $._id").set("$.F1", "Data_New").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("'1001' in $._id").execute(); + doc = docs.next(); + assertEquals("Data_New", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + /* find with = Condition and fetchAll() keyword */ + String findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not in $._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + /* modify with multiple IN */ + res = this.collection.modify(findCond).set("$.F1", "Data_New_1").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find(findCond).execute(); + doc = docs.next(); + assertEquals("Data_New_1", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + /* modify with single IN and sort */ + res = this.collection.modify("10000 in $.F4").set("$.F1", "Data_New_2").sort("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("10000 in $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals("Data_New_2", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + /* modify with single IN and sort */ + res = this.collection.modify("20.1234 in $.F2").set("$.F1", "Data_New_3").sort("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("20.1234 in $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals("Data_New_3", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + Table table = this.schema.getCollectionAsTable(this.collectionName); + + /* update with single IN */ + String toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_4\")"); + res = table.update().set("doc", expr(toUpdate)).where("'1001' in doc->$._id").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + RowResult rows = table.select("doc->$.F1 as F1").where("'1001' in doc->$._id").execute(); + Row r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_4\""); + assertFalse(rows.hasNext()); + + findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not in doc->$._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + /* update with multiple IN */ + toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_5\")"); + res = table.update().set("doc", expr(toUpdate)).where(findCond).execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where(findCond).execute(); + r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_5\""); + assertFalse(rows.hasNext()); + + /* update with single IN for float */ + toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_6\")"); + res = table.update().set("doc", expr(toUpdate)).where("20.1234 in doc->$.F2").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("20.1234 in doc->$.F2").execute(); + r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_6\""); + assertFalse(rows.hasNext()); + + /* update with single IN for int */ + toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_7\")"); + res = table.update().set("doc", expr(toUpdate)).where("10000 in doc->$.F4").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("10000 in doc->$.F4").execute(); + r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_7\""); + assertFalse(rows.hasNext()); + } + + @SuppressWarnings("deprecation") + @Test + public void testCollectionFindInDelete() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + DocResult docs = null; + Result res = null; + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* remove with single IN */ + res = this.collection.remove("'1001' in $._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("'1001' in $._id").execute(); + assertFalse(docs.hasNext()); + + /* remove with mulltiple IN */ + String findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not in $._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + res = this.collection.remove(findCond).execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find(findCond).execute(); + assertFalse(docs.hasNext()); + + /* remove with single IN */ + res = this.collection.remove("10004 in $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("10004 in $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + assertFalse(docs.hasNext()); + + /* remove with single IN for float */ + res = this.collection.remove("30.1234 in $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("30.1234 in $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + assertFalse(docs.hasNext()); + + res = this.collection.remove("true").execute(); + + jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + Table table = this.schema.getCollectionAsTable(this.collectionName); + + /* delete with single IN */ + res = table.delete().where("'1001' in doc->$._id").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + RowResult rows = table.select("doc->$.F1 as _id").where("'1001' in doc->$._id").execute(); + assertFalse(rows.hasNext()); + + /* delete with multiple IN */ + findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not in doc->$._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + res = table.delete().where(findCond).execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as _id").where(findCond).execute(); + assertFalse(rows.hasNext()); + + /* delete with single IN for float */ + res = table.delete().where("30.1234 in doc->$.F2").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("30.1234 in doc->$.F2").execute(); + assertFalse(rows.hasNext()); + + /* delete with single IN for int */ + res = table.delete().where("10004 in doc->$.F4").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("10004 in doc->$.F4").execute(); + assertFalse(rows.hasNext()); + } + + @Test + public void testCollectionFindOverlapsSanity() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + DbDoc doc = null; + DocResult docs = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 - i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* find with single element OVERLAPS which uses json_overlaps */ + docs = this.collection.find("'1001' overlaps $._id").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with multiple OVERLAPS which uses json_overlaps */ + String findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not overlaps $._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + docs = this.collection.find(findCond).execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with single OVERLAPS for string with orderBy */ + docs = this.collection.find("'Field-1-Data-2' overlaps $.F1").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1002), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with single OVERLAPS for numeric with orderBy */ + docs = this.collection.find("10000 overlaps $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with single OVERLAPS for float with orderBy */ + docs = this.collection.find("20.1234 overlaps $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1001), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* Testing with table */ + Table table = this.schema.getCollectionAsTable(this.collectionName); + + /* find with single OVERLAPS for string */ + RowResult rows = table.select("doc->$._id as _id").where("'1001' overlaps doc->$._id").execute(); + Row r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + assertFalse(rows.hasNext()); + + /* find with multiple OVERLAPS in single select */ + findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not overlaps doc->$._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + /* find with single OVERLAPS for string */ + rows = table.select("doc->$._id as _id").where(findCond).execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1000\""); + assertFalse(rows.hasNext()); + + /* find with single OVERLAPS for float */ + rows = table.select("doc->$._id as _id").where("20.1234 overlaps doc->$.F2").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1001\""); + assertFalse(rows.hasNext()); + + /* find with single OVERLAPS for string */ + rows = table.select("doc->$._id as _id").where("'Field-1-Data-2' overlaps doc->$.F1").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1002\""); + assertFalse(rows.hasNext()); + + /* find with single OVERLAPS for numeric */ + rows = table.select("doc->$._id as _id").where("10000 overlaps doc->$.F4").execute(); + r = rows.next(); + assertEquals(r.getString("_id"), "\"1000\""); + assertFalse(rows.hasNext()); + } + + @Test + public void testCollectionFindOverlaps() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + try { + int i = 0, j = 0, maxrec = 8, minArraySize = 3; + DbDoc doc = null; + DocResult docs = null; + long l3 = 2147483647; + double d1 = 1000.1234; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + jarray.addValue(new JsonNumber().setValue(String.valueOf((l3 + j + i)))); + } + newDoc2.add("ARR1", jarray); + + JsonArray karray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + karray.addValue(new JsonNumber().setValue(String.valueOf((d1 + j + i)))); + } + newDoc2.add("ARR2", karray); + JsonArray larray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + larray.addValue(new JsonString().setValue("St_" + i + "_" + j)); + } + newDoc2.add("ARR3", larray); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + + assertEquals((maxrec), this.collection.count()); + + /* find with single OVERLAPS in array */ + docs = this.collection.find("2147483647 overlaps $.ARR1").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + Table table = this.schema.getCollectionAsTable(this.collectionName); + RowResult rows = table.select("doc->$._id as _id").where("2147483647 overlaps $.ARR1").execute(); + Row r = rows.next(); + assertEquals("\"1000\"", r.getString("_id")); + assertFalse(rows.hasNext()); + + /* find with array OVERLAPS array */ + docs = this.collection.find("[2147483647, 2147483648, 2147483649] overlaps $.ARR1").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + rows = table.select("doc->$._id as _id").where("[2147483647, 2147483648, 2147483649] overlaps $.ARR1").execute(); + r = rows.next(); + assertEquals("\"1000\"", r.getString("_id")); + r = rows.next(); + assertEquals("\"1001\"", r.getString("_id")); + r = rows.next(); + assertEquals("\"1002\"", r.getString("_id")); + assertFalse(rows.hasNext()); + + /* find with array OVERLAPS array with orderBy */ + docs = this.collection.find("[2147483648, 2147483648, 2147483649] overlaps $.ARR1").orderBy("_id").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* */ + docs = this.collection.find("[!false && true] OVERLAPS [true]").execute(); + assertEquals(maxrec, docs.count()); + + /* Overlaps with NULL */ + docs = this.collection.find("NULL overlaps $.ARR1").execute(); + assertFalse(docs.hasNext()); + + rows = table.select("doc->$._id as _id").where("NULL overlaps $.ARR1").execute(); + assertFalse(rows.hasNext()); + + docs = this.collection.find("$.ARR1 overlaps null").execute(); + assertFalse(docs.hasNext()); + + rows = table.select("doc->$._id as _id").where("$.ARR1 overlaps NULL").execute(); + assertFalse(rows.hasNext()); + + /* Not Overlaps with NULL */ + docs = this.collection.find("NULL not overlaps $.ARR1").execute(); + assertTrue(docs.hasNext()); + assertEquals(maxrec, docs.count()); + + rows = table.select("doc->$._id as _id").where("NULL not overlaps $.ARR1").execute(); + assertTrue(rows.hasNext()); + assertEquals(maxrec, docs.count()); + + docs = this.collection.find("$.ARR1 not overlaps null").execute(); + assertTrue(docs.hasNext()); + assertEquals(maxrec, docs.count()); + + rows = table.select("doc->$._id as _id").where("$.ARR1 not overlaps null").execute(); + assertTrue(rows.hasNext()); + assertEquals(maxrec, docs.count()); + + /* Test OVERLAPS/NOT OVERLAPS with empty array - Expected to pass, though the array is empty but still valid */ + docs = this.collection.find("[] Overlaps $.ARR1").execute(); //checking the case insensitivity as well + assertFalse(docs.hasNext()); + + rows = table.select().where("[] ovErlaps $.ARR1").execute(); + assertFalse(rows.hasNext()); + + docs = this.collection.find("$.ARR1 overlapS []").execute(); //checking the case insensitivity as well + assertFalse(docs.hasNext()); + + rows = table.select().where("$.ARR1 ovErlaps []").execute(); + assertFalse(rows.hasNext()); + + docs = this.collection.find("[] not overlaps $.ARR1").execute(); + assertTrue(docs.hasNext()); + assertEquals(maxrec, docs.count()); + + rows = table.select().where("[] not overlaps $.ARR1").execute(); + assertTrue(rows.hasNext()); + assertEquals(maxrec, docs.count()); + + docs = this.collection.find("$.ARR1 not oveRlaps []").execute(); //checking the case insensitivity as well + assertTrue(docs.hasNext()); + assertEquals(maxrec, docs.count()); + + rows = table.select().where("$.ARR1 not overlaps []").execute(); + assertTrue(rows.hasNext()); + assertEquals(maxrec, docs.count()); + + /* When the right number of operands are not provided - error should be thrown */ + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 0", + () -> this.collection.find("overlaps $.ARR1").execute()); + assertThrows(WrongArgumentException.class, "No more tokens when expecting one at token pos 4", + () -> this.collection.find("$.ARR1 OVERLAPS").execute()); + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 0", () -> this.collection.find("OVERLAPS").execute()); + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 1", + () -> this.collection.find("not overlaps $.ARR1").execute()); + assertThrows(WrongArgumentException.class, "No more tokens when expecting one at token pos 5", + () -> this.collection.find("$.ARR1 NOT OVERLAPS").execute()); + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 1", () -> this.collection.find("not OVERLAPS").execute()); + + final Table table1 = this.schema.getCollectionAsTable(this.collectionName); + + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 0", + () -> table1.select().where("overlaps $.ARR1").execute()); + assertThrows(WrongArgumentException.class, "No more tokens when expecting one at token pos 4", + () -> table1.select().where("$.ARR1 OVERLAPS").execute()); + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 0", () -> table1.select().where("OVERLAPS").execute()); + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 1", + () -> table1.select().where("not overlaps $.ARR1").execute()); + assertThrows(WrongArgumentException.class, "No more tokens when expecting one at token pos 5", + () -> table1.select().where("$.ARR1 NOT OVERLAPS").execute()); + assertThrows(WrongArgumentException.class, "Cannot find atomic expression at token pos: 1", () -> table1.select().where("not OVERLAPS").execute()); + + /* invalid criteria, e.g. .find("[1, 2, 3] OVERLAPS $.age") . where $.age is atomic value */ + dropCollection("coll2"); + Collection coll2 = this.schema.createCollection("coll2", true); + coll2.add("{ \"_id\": \"1\", \"name\": \"nonjson\", \"age\": \"50\",\"arrayField\":[1,[7]]}").execute(); + //The below command should give exception, but X-plugin doesn't return any error + docs = coll2.find("[1,2,3] overlaps $.age").execute(); + assertEquals(0, docs.count()); + docs = coll2.find("arrayField OVERLAPS [7]").execute(); + assertEquals(0, docs.count()); + + docs = coll2.find("arrayField[1] OVERLAPS [7]").execute(); + assertEquals(1, docs.count()); + + table = this.schema.getCollectionAsTable("coll2"); + rows = table.select().where("[1,2,3] overlaps $.age").execute(); + assertEquals(0, rows.count()); + + /* Test with empty spaces */ + dropCollection("coll3"); + Collection coll3 = this.schema.createCollection("coll3", true); + coll3.add("{ \"_id\":1, \"name\": \"Record1\",\"list\":[\"\"], \"age\":15, \"intList\":[1,2,3] }").execute();//List contains an array without any space + coll3.add("{ \"_id\":2, \"name\": \"overlaps\",\"list\":[\" \"],\"age\":24}").execute();//List contains an array with space + coll3.add("{ \"_id\":3, \"overlaps\": \"overlaps\",\"age\":30}").execute(); + docs = coll3.find("[''] OVERLAPS $.list").execute(); + assertEquals(1, docs.count()); + assertEquals(new Integer(1), ((JsonNumber) docs.next().get("_id")).getInteger()); + + table = this.schema.getCollectionAsTable("coll3"); + rows = table.select("doc->$._id as _id").where("[''] overlaps $.list").execute(); + r = rows.next(); + assertEquals("1", r.getString("_id")); + + docs = coll3.find("[' '] OVERLAPS $.list").execute(); + assertEquals(1, docs.count()); + assertEquals(new Integer(2), ((JsonNumber) docs.next().get("_id")).getInteger()); + + rows = table.select("doc->$._id as _id").where("[' '] overlaps $.list").execute(); + r = rows.next(); + assertEquals("2", r.getString("_id")); + + docs = coll3.find("'overlaps' OVERLAPS $.name").execute(); + assertEquals(1, docs.count()); + assertEquals(new Integer(2), ((JsonNumber) docs.next().get("_id")).getInteger()); + + rows = table.select("doc->$._id as _id").where("'overlaps' overlaps $.name").execute(); + r = rows.next(); + assertEquals(1, docs.count()); + assertEquals("2", r.getString("_id")); + + docs = coll3.find("[3] OVERLAPS $.intList").execute(); + assertEquals(1, docs.count()); + + rows = table.select().where("[3] overlaps $.intList").execute(); + assertEquals(1, rows.count()); + + /* Escape the keyword, to use it as identifier */ + docs = coll3.find("`overlaps` OVERLAPS $.`overlaps`").execute(); + assertEquals(1, docs.count()); + + rows = table.select().where("'overlaps' overlaps $.`overlaps`").execute(); + assertEquals(1, rows.count()); + + docs = coll3.find("$.`overlaps` OVERLAPS `overlaps`").execute(); + assertEquals(1, docs.count()); + + rows = table.select().where("$.`overlaps` overlaps 'overlaps'").execute(); + assertEquals(1, rows.count()); + + dropCollection("coll4"); + Collection coll4 = this.schema.createCollection("coll4", true); + coll4.add("{\"overlaps\":{\"one\":1, \"two\":2, \"three\":3},\"list\":{\"one\":1, \"two\":2, \"three\":3},\"name\":\"one\"}").execute(); + coll4.add("{\"overlaps\":{\"one\":1, \"two\":2, \"three\":3},\"list\":{\"four\":4, \"five\":5, \"six\":6},\"name\":\"two\"}").execute(); + coll4.add("{\"overlaps\":{\"one\":1, \"three\":3, \"five\":5},\"list\":{\"two\":2, \"four\":4, \"six\":6},\"name\":\"three\"}").execute(); + coll4.add("{\"overlaps\":{\"one\":1, \"three\":3, \"five\":5},\"list\":{\"three\":3, \"six\":9, \"nine\":9},\"name\":\"four\"}").execute(); + coll4.add("{\"overlaps\":{\"one\":1, \"three\":3, \"five\":5},\"list\":{\"three\":6, \"six\":12, \"nine\":18},\"name\":\"five\"}").execute(); + coll4.add("{\"overlaps\":{\"one\":[1,2,3]}, \"list\":{\"one\":[3,4,5]}, \"name\":\"six\"}").execute(); + coll4.add("{\"overlaps\":{\"one\":[1,2,3]}, \"list\":{\"one\":[1,2,3]}, \"name\":\"seven\"}").execute(); + + docs = coll4.find("`overlaps` OVERLAPS `list`").execute(); + assertEquals(3, docs.count()); + doc = docs.fetchOne(); + assertEquals("one", (((JsonString) doc.get("name")).getString())); + doc = docs.fetchOne(); + assertEquals("four", (((JsonString) doc.get("name")).getString())); + doc = docs.fetchOne(); + assertEquals("seven", (((JsonString) doc.get("name")).getString())); + + table = this.schema.getCollectionAsTable("coll4"); + rows = table.select("doc->$.name as name").where("$.`overlaps` OVERLAPS $.`list`").execute(); + assertEquals(3, rows.count()); + r = rows.next(); + assertEquals("\"one\"", r.getString("name")); + } finally { + dropCollection("coll4"); + dropCollection("coll3"); + dropCollection("coll2"); + } + } + + @Test + public void testCollectionFindOverlapsWithExpr() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + DbDoc doc = null; + DocResult docs = null; + + this.collection.add("{\"_id\": \"1001\", \"ARR\":[1,1,2], \"ARR1\":[\"name1\", \"name2\", \"name3\"]}").execute(); + this.collection.add("{\"_id\": \"1002\", \"ARR\":[1,2,3], \"ARR1\":[\"name4\", \"name5\", \"name6\"]}").execute(); + this.collection.add("{\"_id\": \"1003\", \"ARR\":[1,4,5], \"ARR1\":[\"name1\", \"name1\", \"name5\"]}").execute(); + + docs = this.collection.find("[1,1,3] overlaps $.ARR").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1003", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("[2,5] overlaps $.ARR").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals("1003", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("1 overlaps [1, 2, 3]").execute(); + assertEquals(3, docs.count()); + + docs = this.collection.find("'name6' overlaps ['name1', 'name2', 'name6']").execute(); + assertEquals(3, docs.count()); + + docs = this.collection.find("cast((1+6) AS JSON) OVERLAPS [2,3,7]").execute(); + assertEquals(3, docs.count()); + + docs = this.collection.find("[(1+2)] overlaps [1, 2, 3]").execute(); + assertEquals(3, docs.count()); + + docs = this.collection.find("[concat('name', '6')] overlaps ['name1', 'name2', 'name6']").execute(); + assertEquals(3, docs.count()); + + docs = this.collection.find("[CURDATE()] overlaps ['2019-16-05','2018-16-05']").execute(); + assertEquals(0, docs.count()); + + docs = this.collection.find("true overlaps [2]").execute(); + assertEquals(0, docs.count()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires operator that produce a JSON value\\.", + () -> this.collection.find("[1,2,3] overlaps $.ARR overlaps [2]").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires operator that produce a JSON value\\.", + () -> this.collection.find("(1+2) overlaps [1, 2, 3]").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires function that produce a JSON value\\.", + () -> this.collection.find("concat('name', '6') overlaps ['name1', 'name2', 'name6']").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires operator that produce a JSON value\\.", + () -> this.collection.find("(1+2) overlaps $.ARR").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires function that produce a JSON value\\.", + () -> this.collection.find("concat('name', '6') overlaps $.ARR1").execute()); + + Table tabNew = this.schema.getCollectionAsTable(this.collectionName); + RowResult rows = tabNew.select("doc->$._id as _id").where("[(1+2)] overlaps [1, 2, 3]").execute(); + rows.next(); + assertEquals(3, rows.count()); + + rows = tabNew.select("doc->$._id as _id").where("[concat('name', '6')] overlaps ['name1', 'name2', 'name6']").execute(); + rows.next(); + assertEquals(3, rows.count()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires function that produce a JSON value\\.", + () -> tabNew.select("doc->$._id as _id").where("expr(1+2) overlaps [1, 2, 3]").execute()); + + assertThrows(XProtocolError.class, "ERROR 5154 \\(HY000\\) OVERLAPS expression requires operator that produce a JSON value\\.", + () -> tabNew.select("doc->$._id as _id").where("(1+2) overlaps doc->$.ARR").execute()); + } + + @Test + public void testCollectionFindOverlapsValidMix() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, j = 0, maxrec = 8, minArraySize = 3; + DbDoc doc = null; + DocResult docs = null; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + jarray.addValue(new JsonString().setValue("Field-1-Data-" + i)); + } + newDoc2.add("ARR1", jarray); + + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + + assertEquals((maxrec), this.collection.count()); + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1100))); + newDoc2.add("ARR1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonString().setValue("10-15-201" + i)); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + /* find with OVERLAPS for key having mixture of value and array */ + docs = this.collection.find("\"10-15-2017\" OVERLAPS $.F2").execute(); + doc = docs.next(); + assertEquals("1107", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("JSON_TYPE($.ARR1) = 'ARRAY' AND \"Field-1-Data-0\" OVERLAPS $.ARR1").execute(); + doc = docs.next(); + assertEquals("1000", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with NULL OVERLAPS in key having mix of array and string */ + docs = this.collection.find("NULL OVERLAPS $.ARR1").execute(); + assertFalse(docs.hasNext()); + } + + @Test + public void testCollModifyTabUpdateWithOverlaps() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + DbDoc doc = null; + DocResult docs = null; + Result res = null; + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 - i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* modify with single OVERLAPS */ + res = this.collection.modify("'1001' overlaps $._id").set("$.F1", "Data_New").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("'1001' overlaps $._id").execute(); + doc = docs.next(); + assertEquals("Data_New", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + /* find with = Condition and fetchAll() keyword */ + String findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not overlaps $._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + /* modify with multiple overlaps */ + res = this.collection.modify(findCond).set("$.F1", "Data_New_1").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find(findCond).execute(); + doc = docs.next(); + assertEquals("Data_New_1", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + /* modify with single overlaps and sort */ + res = this.collection.modify("10000 overlaps $.F4").set("$.F1", "Data_New_2").sort("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("10000 overlaps $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals("Data_New_2", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + /* modify with single overlaps and sort */ + res = this.collection.modify("20.1234 overlaps $.F2").set("$.F1", "Data_New_3").sort("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("20.1234 overlaps $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + doc = docs.next(); + assertEquals("Data_New_3", (((JsonString) doc.get("F1")).getString())); + assertFalse(docs.hasNext()); + + Table table = this.schema.getCollectionAsTable(this.collectionName); + + /* update with single OVERLAPS */ + String toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_4\")"); + res = table.update().set("doc", expr(toUpdate)).where("'1001' overlaps doc->$._id").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + RowResult rows = table.select("doc->$.F1 as F1").where("'1001' overlaps doc->$._id").execute(); + Row r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_4\""); + assertFalse(rows.hasNext()); + + findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not overlaps doc->$._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + /* update with multiple OVERLAPS */ + toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_5\")"); + res = table.update().set("doc", expr(toUpdate)).where(findCond).execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where(findCond).execute(); + r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_5\""); + assertFalse(rows.hasNext()); + + /* update with single OVERLAPS for float */ + toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_6\")"); + res = table.update().set("doc", expr(toUpdate)).where("20.1234 overlaps doc->$.F2").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("20.1234 overlaps doc->$.F2").execute(); + r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_6\""); + assertFalse(rows.hasNext()); + + /* update with single OVERLAPS for int */ + toUpdate = String.format("JSON_REPLACE(doc, \"$.F1\", \"Data_New_7\")"); + res = table.update().set("doc", expr(toUpdate)).where("10000 overlaps doc->$.F4").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("10000 overlaps doc->$.F4").execute(); + r = rows.next(); + assertEquals(r.getString("F1"), "\"Data_New_7\""); + assertFalse(rows.hasNext()); + } + + @SuppressWarnings("deprecation") + @Test + public void testCollRemoveTabDeleteWithOverlaps() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + DocResult docs = null; + Result res = null; + + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + assertEquals((maxrec), i); + + /* remove with single OVERLAPS */ + res = this.collection.remove("'1001' overlaps $._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("'1001' overlaps $._id").execute(); + assertFalse(docs.hasNext()); + + /* remove with mulltiple OVERLAPS */ + String findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not overlaps $._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + res = this.collection.remove(findCond).execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find(findCond).execute(); + assertFalse(docs.hasNext()); + + /* remove with single OVERLAPS */ + res = this.collection.remove("10004 overlaps $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("10004 overlaps $.F4").orderBy("CAST($.F4 as SIGNED)").execute(); + assertFalse(docs.hasNext()); + + /* remove with single OVERLAPS for float */ + res = this.collection.remove("30.1234 overlaps $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("30.1234 overlaps $.F2").orderBy("CAST($.F4 as SIGNED)").execute(); + assertFalse(docs.hasNext()); + + res = this.collection.remove("true").execute(); + + jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1) + 0.1234))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + Table table = this.schema.getCollectionAsTable(this.collectionName); + + /* delete with single OVERLAPS */ + res = table.delete().where("'1001' overlaps doc->$._id").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + RowResult rows = table.select("doc->$.F1 as _id").where("'1001' overlaps doc->$._id").execute(); + assertFalse(rows.hasNext()); + + /* delete with multiple OVERLAPS */ + findCond = ""; + for (i = 1; i < maxrec; i++) { + findCond = findCond + "'"; + findCond = findCond + String.valueOf(i + 1000) + "' not overlaps doc->$._id"; + if (i != maxrec - 1) { + findCond = findCond + " and "; + } + } + + res = table.delete().where(findCond).execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as _id").where(findCond).execute(); + assertFalse(rows.hasNext()); + + /* delete with single OVERLAPS for float */ + res = table.delete().where("30.1234 overlaps doc->$.F2").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("30.1234 overlaps doc->$.F2").execute(); + assertFalse(rows.hasNext()); + + /* delete with single OVERLAPS for int */ + res = table.delete().where("10004 overlaps doc->$.F4").execute(); + assertEquals(res.getAffectedItemsCount(), 1); + rows = table.select("doc->$.F1 as F1").where("10004 overlaps doc->$.F4").execute(); + assertFalse(rows.hasNext()); + } + + /* 64k length key */ + @Test + public void testCollectionFindStress_002() throws Exception { + int i = 0, maxrec = 5; + int maxLen = 1024 * 64 - 1; + SqlResult res1 = null; + Session tmpSess = null; + Row r = null; + int defXPackLen = 0; + int defPackLen = 0; + try { + tmpSess = new SessionFactory().getSession(this.baseUrl); + res1 = tmpSess.sql("show variables like 'mysqlx_max_allowed_packet'").execute(); + r = res1.next(); + defXPackLen = Integer.parseInt(r.getString("Value")); + res1 = tmpSess.sql("show variables like 'max_allowed_packet'").execute(); + r = res1.next(); + defPackLen = Integer.parseInt(r.getString("Value")); + + tmpSess.sql("set Global mysqlx_max_allowed_packet=128*1024*1024 ").execute(); + tmpSess.sql("set Global max_allowed_packet=128*1024*1024 ").execute(); + + String s1 = ""; + + /* max+1 key length --> Expect error */ + s1 = buildString(maxLen + 1, 'q'); + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add(s1, new JsonString().setValue("Data_1" + i)); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + + assertThrows(XProtocolError.class, "ERROR 3151 \\(22032\\) The JSON object contains a key name that is too long\\.", + () -> this.collection.add(jsonlist).execute()); + + /* With Max Keysize */ + s1 = buildString(maxLen - 1, 'q'); + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add(s1 + "1", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add(s1 + "2", new JsonString().setValue("Data_1" + i)); + //jsonlist[i]=newDoc2; + this.collection.add(newDoc2).execute(); + newDoc2 = null; + } + //coll.add(jsonlist).execute(); + + DocResult docs0 = this.collection.find("$._id= '1001'").fields("$._id as _id, $." + s1 + "1 as " + s1 + "X, $." + s1 + "2 as " + s1 + "Y") + .execute(); + DbDoc doc0 = docs0.next(); + assertEquals(String.valueOf(1 + 1000), (((JsonString) doc0.get("_id")).getString())); + + } finally { + if (tmpSess != null) { + tmpSess.sql("set Global mysqlx_max_allowed_packet=" + defXPackLen).execute(); + tmpSess.sql("set Global max_allowed_packet=" + defPackLen).execute(); + tmpSess.close(); + } + } + } + + /* Large Data */ + @Test + //@Ignore("Wait for 1M Data issue Fix in Plugin") + public void testCollectionFindStress_003() throws Exception { + int i = 0, maxrec = 5; + int maxLen = 1024 * 1024 + 4; + SqlResult res1 = null; + Session tmpSess = null; + Row r = null; + int defPackLen = 0; + int defXPackLen = 0; + + try { + tmpSess = new SessionFactory().getSession(this.baseUrl); + res1 = tmpSess.sql("show variables like 'mysqlx_max_allowed_packet'").execute(); + r = res1.next(); + defXPackLen = Integer.parseInt(r.getString("Value")); + + res1 = tmpSess.sql("show variables like 'max_allowed_packet'").execute(); + r = res1.next(); + defPackLen = Integer.parseInt(r.getString("Value")); + + tmpSess.sql("set Global mysqlx_max_allowed_packet=128*1024*1024 ").execute(); + tmpSess.sql("set Global max_allowed_packet=128*1024*1024 ").execute(); + ((SessionImpl) this.session).getSession().getProtocol().setMaxAllowedPacket(128 * 1024 * 1024); + + String s1 = ""; + + /* maxLen Data length */ + s1 = buildString(maxLen + 1, 'q'); + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add("F1", new JsonString().setValue(s1 + i)); + newDoc2.add("F2", new JsonString().setValue(s1 + i)); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + } + + DocResult docs0 = this.collection.find("$._id= '1001'").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + DbDoc doc0 = docs0.next(); + assertEquals(String.valueOf(1 + 1000), (((JsonString) doc0.get("_id")).getString())); + } finally { + if (tmpSess != null) { + tmpSess.sql("set Global mysqlx_max_allowed_packet=" + defXPackLen).execute(); + tmpSess.sql("set Global max_allowed_packet=" + defPackLen).execute(); + tmpSess.close(); + } + } + } + + /* + * Many Keys + * Issue : Hangs when maxKey > 7K + */ + @Test + public void testCollectionFindStress_004() throws Exception { + int i = 0, j = 0, maxrec = 5; + int maxKey = 1024 * 8; + String key, key_sub = "key_", query; + String data, data_sub = "data_"; + DocResult docs = null; + DbDoc doc = null; + String json = ""; + for (i = 0; i < maxrec; i++) { + StringBuilder b = new StringBuilder("{'_id':'" + (1000 + i) + "'"); + for (j = 0; j < maxKey; j++) { + key = key_sub + j; + data = data_sub + i + "_" + j; + b.append(",'").append(key).append("':'").append(data).append("'"); + } + + json = b.append("}").toString(); + json = json.replaceAll("'", "\""); + this.collection.add(json).execute(); + b = null; + } + + /* Inserting maxKey (key,data) pair */ + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 2000))); + for (j = 0; j < maxKey; j++) { + key = key_sub + j; + data = data_sub + i + "_" + j; + newDoc2.add(key, new JsonString().setValue(data)); + } + + this.collection.add(newDoc2).execute(); + newDoc2 = null; + } + + assertEquals((maxrec * 2), this.collection.count()); + /* Select All Keys */ + query = "$._id as _id"; + for (j = 0; j < maxKey; j++) { + key = key_sub + j; + query = query + ",$." + key + " as " + key; + } + docs = this.collection.find("$._id= '1001'").fields(query).orderBy(key_sub + (maxKey - 1)).execute(); + doc = docs.next(); + assertEquals(String.valueOf(1001), (((JsonString) doc.get("_id")).getString())); + assertEquals(data_sub + "1_" + (maxKey - 1), (((JsonString) doc.get(key_sub + (maxKey - 1))).getString())); + } + + /** + * Bigint,Double, Date data CAST Operator + * + * @throws Exception + */ + @Test + public void testCollectionFindDatatypes() throws Exception { + int i = 0, maxrec = 10; + DbDoc doc = null; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE, l3 = 2147483647; + double d1 = 100.4567; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d1 + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(l1 - i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(l2 + i))); + newDoc2.add("F5", new JsonNumber().setValue(String.valueOf(l3 + i))); + newDoc2.add("F6", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* Compare Big Int */ + DocResult docs = this.collection.find("CAST($.F3 as SIGNED) =" + l1).fields("$._id as _id, $.F3 as f3, $.F3 as f3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(l1, (((JsonNumber) doc.get("f3")).getBigDecimal().longValue())); + i++; + } + assertEquals((1), i); + + /* Compare Big Int */ + docs = this.collection.find("CAST($.F5 as SIGNED) =" + l3 + "+" + 3).fields("$._id as _id, $.F1 as f1, $.F5 as f5").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(l3 + 3, (((JsonNumber) doc.get("f5")).getBigDecimal().longValue())); + i++; + } + assertEquals((1), i); + + /* CAST in Order By */ + docs = this.collection.find("CAST($.F5 as SIGNED) < " + l3 + "+" + 5).fields("$._id as _id, $.F1 as f1, $.F5 as f5").orderBy("CAST($.F5 as SIGNED) asc") + .execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(l3 + i, (((JsonNumber) doc.get("f5")).getBigDecimal().longValue())); + i++; + } + assertEquals((5), i); + + docs = this.collection.find("CAST($.F4 as SIGNED) < " + l2 + "+" + 5).fields("$._id as _id, $.F1 as f1, $.F4 as f4") + .orderBy("CAST($.F4 as SIGNED) desc").execute(); + i = 4; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(l2 + i, (((JsonNumber) doc.get("f4")).getBigDecimal().longValue())); + i--; + } + assertEquals((-1), i); + + /* Compare Double */ + docs = this.collection.find("CAST($.F2 as DECIMAL(10,4)) =" + d1).fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(d1, (((JsonNumber) doc.get("f2")).getBigDecimal().doubleValue())); + i++; + } + assertEquals((1), i); + } + + /* OPerators =,!=,<,>,<=,>= IN, NOT IN,Like , Not Like, Between, REGEXP,NOT REGEXP , interval,|,&,^,<<,>>,~ */ + @Test + public void testCollectionFindBasic() throws Exception { + int i = 0, maxrec = 10; + DbDoc doc = null; + DocResult docs = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1)))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 - i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find().fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3,$.F2/10 as tmp1,1/2 as tmp2").orderBy("$.F3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) (i + 1), (long) (((JsonNumber) doc.get("f3")).getInteger())); + assertEquals((((JsonNumber) doc.get("f3")).getInteger()), (((JsonNumber) doc.get("tmp1")).getInteger())); + assertEquals(new BigDecimal("0.500000000"), (((JsonNumber) doc.get("tmp2")).getBigDecimal())); + + i++; + } + assertEquals((maxrec), i); + + /* find with = Condition and fetchAll() keyword */ + docs = this.collection.find("$._id = '1001'").execute(); + doc = docs.next(); + assertEquals("1001", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with order by and condition */ + docs = this.collection.find("$.F3 > 1").orderBy("CAST($.F4 as SIGNED)").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + assertEquals(String.valueOf(1000 + maxrec - i), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (10000 - maxrec + i), (long) (((JsonNumber) doc.get("F4")).getInteger())); + + } + assertEquals(i, (maxrec - 1)); + + /* find with order by and limit with condition */ + docs = this.collection.find("$._id > 1001").orderBy("CAST($.F4 as SIGNED)").limit(1).execute(); + doc = docs.next(); + assertEquals(String.valueOf(1000 + maxrec - 1), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with order by limit and offset with condition */ + docs = this.collection.find("$.F3 > 2").orderBy("CAST($.F4 as SIGNED)").fields("$._id as _id, $.F2/$.F3 as f1,$.F4 as f4").limit(10).offset(2) + .execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + assertEquals(String.valueOf(1000 + maxrec - i - 2), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (10000 - maxrec + i + 2), (long) (((JsonNumber) doc.get("f4")).getInteger())); + assertEquals((long) 10, (long) (((JsonNumber) doc.get("f1")).getInteger())); + } + assertEquals(i, (maxrec - 4)); + } + + @Test + public void testCollectionFindGroupBy() throws Exception { + int i = 0, j = 0, maxrec = 10, grpcnt = 50; + DbDoc doc = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + for (j = 1; j <= grpcnt; j++) { + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + (1000 * j)))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(10 * (i + 1)))); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(100 * (i + 1)))); + newDoc2.add("F4", new JsonString().setValue(buildString(8192 + i, 'X'))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + } + assertEquals((maxrec * grpcnt), this.collection.count()); + + /* find with groupBy (Sum) with Having Clause */ + DocResult docs = this.collection.find().fields("sum($.F1) as sum_f1, sum($.F2) as sum_f2, sum($.F3) as sum_f3,Max($.F4) as max_f4 ").groupBy("$.F1") + .having("MIn($.F1) > 10").orderBy("sum($.F1)").execute(); + i = 1; + while (docs.hasNext()) { + i++; + doc = docs.next(); + assertEquals((long) (i * grpcnt * 10), (long) (((JsonNumber) doc.get("sum_f1")).getInteger())); + assertEquals((long) (i * grpcnt), (long) (((JsonNumber) doc.get("sum_f2")).getInteger())); + assertEquals((long) (i * grpcnt * 100), (long) (((JsonNumber) doc.get("sum_f3")).getInteger())); + //assertEquals((buildString(10+i,'X')), (((JsonString) doc.get("max_f4")).getString())); + } + assertEquals((maxrec), i); + + /* find with groupBy (Max) with Having Clause on String */ + docs = this.collection.find().fields("max($.F1) as max_f1, max($.F2) as max_f2, max($.F3) as max_f3,max($.F4) as max_f4 ").groupBy("$.F4") + .having("max($.F1) > 20").execute(); + i = 2; + while (docs.hasNext()) { + i++; + doc = docs.next(); + assertEquals(String.valueOf(i * 10), (((JsonString) doc.get("max_f1")).getString())); + assertEquals(String.valueOf(i), (((JsonString) doc.get("max_f2")).getString())); + assertEquals(String.valueOf(i * 100), (((JsonString) doc.get("max_f3")).getString())); + //assertEquals((buildString(10+i,'X')), (((JsonString) doc.get("max_f4")).getString())); + } + assertEquals((maxrec), i); + + docs = this.collection.find().fields("max($.F1) as max_f1, max($.F2) as max_f2, max($.F3) as max_f3,max($.F4) as max_f4max_f4").groupBy("$.F4") + .having("max($.F1) > 20").orderBy("$.F4").execute(); + //docs = coll.find().fields("max($.F4) as max_f4,$.F4 as f4").groupBy("$.F4").having("max($.F1) > 20").orderBy("$.F4").execute(); + i = 2; + while (docs.hasNext()) { + i++; + doc = docs.next(); + assertEquals(String.valueOf(i * 10), (((JsonString) doc.get("max_f1")).getString())); + assertEquals(String.valueOf(i), (((JsonString) doc.get("max_f2")).getString())); + assertEquals(String.valueOf(i * 100), (((JsonString) doc.get("max_f3")).getString())); + //assertEquals((buildString(10+i,'X')), (((JsonString) doc.get("max_f4")).getString())); + } + assertEquals((maxrec), i); + + docs = this.collection.find().fields("max($.F1) as max_f1, max($.F2) as max_f2, max($.F3) as max_f3,max($.F4) as max_f4max_f4").groupBy("$.F4") + .having("max($.F1) > 20").orderBy("$.F4").limit(1).offset(1).execute(); + doc = docs.next(); + assertEquals(String.valueOf(40), (((JsonString) doc.get("max_f1")).getString())); + assertEquals(String.valueOf(4), (((JsonString) doc.get("max_f2")).getString())); + assertEquals(String.valueOf(400), (((JsonString) doc.get("max_f3")).getString())); + assertFalse(docs.hasNext()); + } + + @SuppressWarnings("deprecation") + @Test + public void testCollectionFindSkipWarning() throws Exception { + int i = 0, maxrec = 10; + DbDoc doc = null; + DocResult docs = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1)))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(10000 - i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find with order by and condition */ + docs = this.collection.find("$.F3 > 1").orderBy("CAST($.F4 as SIGNED)").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + assertEquals(String.valueOf(1000 + maxrec - i), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (10000 - maxrec + i), (long) (((JsonNumber) doc.get("F4")).getInteger())); + + } + assertEquals(i, (maxrec - 1)); + + /* find with order by and limit with condition */ + docs = this.collection.find("$._id > 1001").orderBy("CAST($.F4 as SIGNED)").limit(1).skip(2).execute(); + + doc = docs.next(); + assertEquals(String.valueOf(1000 + maxrec - 3), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + } + + /* OPerators =,!=,<,>,<=,>= IN, NOT IN,Like , Not Like, Between, REGEXP,NOT REGEXP , interval,|,&,^,<<,>>,~ */ + /* REGEXP,NOT REGEXP,LIKE, NOT LIKE, */ + @Test + public void testCollectionFindWithStringComparison() throws Exception { + int i = 0, maxrec = 10; + int SLen = 1024; + DbDoc doc = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(10 * (i + 1)))); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F3", new JsonString().setValue(buildString(SLen + i + 1, 'q') + buildString(1 + i, 'X') + buildString(SLen + i + 1, '#'))); + // newDoc2.add("F3", new JsonString().setValue("?????")); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find With REGEXP Condition */ + DocResult docs = this.collection.find("$.F3 REGEXP 'q'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(buildString(SLen + i + 1, 'q') + buildString(1 + i, 'X') + buildString(SLen + i + 1, '#'), (((JsonString) doc.get("f3")).getString())); + i++; + } + assertEquals((maxrec), i); + + /* find With REGEXP Condition */ + docs = this.collection.find("$.F3 REGEXP 'qXX#'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals(buildString(SLen + 2, 'q') + buildString(2, 'X') + buildString(SLen + 2, '#'), (((JsonString) doc.get("f3")).getString())); + assertFalse(docs.hasNext()); + + /* find With Not REGEXP Condition */ + docs = this.collection.find("$.F3 NOT REGEXP 'qX*#'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + i = 0; + assertFalse(docs.hasNext()); + + /* find With REGEXP Condition */ + docs = this.collection.find("$.F3 REGEXP 'qXXXX#'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals(buildString(SLen + 4, 'q') + buildString(4, 'X') + buildString(SLen + 4, '#'), (((JsonString) doc.get("f3")).getString())); + assertFalse(docs.hasNext()); + + /* find With Like Condition */ + docs = this.collection.find("$.F3 like '%q_X_#%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").orderBy("CAST($.F2 as SIGNED)") + .execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(buildString(SLen + i + 1, 'q') + buildString(1 + i, 'X') + buildString(SLen + i + 1, '#'), (((JsonString) doc.get("f3")).getString())); + i++; + } + assertEquals(3, i); + + /* find With Not Like Condition */ + docs = this.collection.find("$.F3 Like '%qX#%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals(buildString(SLen + 1, 'q') + buildString(1, 'X') + buildString(SLen + 1, '#'), (((JsonString) doc.get("f3")).getString())); + assertFalse(docs.hasNext()); + + /* find With Like and NOT REGEXP Condition */ + docs = this.collection.find("$.F3 NOT REGEXP 'qqX##' and $.F3 like '%q_X_#%' ").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3") + .orderBy("CAST($.F2 as SIGNED)").execute(); + i = 0; + while (docs.hasNext()) { + i++; + doc = docs.next(); + assertEquals(buildString(SLen + i + 1, 'q') + buildString(1 + i, 'X') + buildString(SLen + i + 1, '#'), (((JsonString) doc.get("f3")).getString())); + + } + assertEquals(2, i); + + /* find With Like , NOT REGEXP and between Condition */ + docs = this.collection.find("$.F3 NOT REGEXP 'qqX##' and $.F3 like '%q_X_#%' and $.F1 between 21 and 31") + .fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").orderBy("CAST($.F2 as SIGNED)").execute(); + doc = docs.next(); + assertEquals(buildString(SLen + 3, 'q') + buildString(3, 'X') + buildString(SLen + 3, '#'), (((JsonString) doc.get("f3")).getString())); + assertEquals((long) 30, (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertFalse(docs.hasNext()); + } + + /* |,&,^,<<,>>,~ */ + @Test + public void testCollectionFindWithBitOperation() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, maxrec = 10; + int SLen = 1; + DbDoc doc = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf((int) Math.pow(2, (i + 1))))); + newDoc2.add("F3", new JsonString().setValue(buildString(SLen + i, 'q'))); + // newDoc2.add("F3", new JsonString().setValue("?????")); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find With bitwise | Condition */ + DocResult docs = this.collection.find("CAST($.F2 as SIGNED) | pow(2,$.F1) = $.F2 ") + .fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 , $.F2 | pow(2,$.F1) as tmp").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1 + 1000), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (i + 1), (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertEquals((long) ((int) Math.pow(2, (i + 1))), (long) (((JsonNumber) doc.get("f2")).getInteger())); + assertEquals(buildString(SLen + i, 'q'), (((JsonString) doc.get("f3")).getString())); + assertEquals((long) Math.pow(2, (i + 1)), (long) (((JsonNumber) doc.get("tmp")).getInteger())); + i++; + } + assertEquals((maxrec), i); + + /* find With bitwise & Condition */ + docs = this.collection.find("CAST($.F2 as SIGNED) & 64 ").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 , $.F2 & 64 as tmp").execute(); + doc = docs.next(); + assertEquals((long) 64, (long) (((JsonNumber) doc.get("f2")).getInteger())); + assertEquals((long) 64, (long) (((JsonNumber) doc.get("tmp")).getInteger())); + assertFalse(docs.hasNext()); + + /* find With bitwise | Condition */ + docs = this.collection.find("CAST($.F2 as SIGNED) | $.F1 = 37 ").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 ,$.F2 | $.F1 as tmp") + .execute(); + doc = docs.next(); + assertEquals((long) 32, (long) (((JsonNumber) doc.get("f2")).getInteger())); + assertEquals((long) 37, (long) (((JsonNumber) doc.get("tmp")).getInteger())); + assertFalse(docs.hasNext()); + + /* find With bitwise << Condition */ + docs = this.collection.find("CAST($.F2 as SIGNED) = 1<<4 ").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 , 1<<4 as tmp").execute(); + doc = docs.next(); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("f2")).getInteger())); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("tmp")).getInteger())); + assertFalse(docs.hasNext()); + + /* find With bitwise >> Condition */ + docs = this.collection.find("CAST($.F2 as SIGNED) = 32>>4 ").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 , 32>>4 as tmp").execute(); + doc = docs.next(); + assertEquals((long) 2, (long) (((JsonNumber) doc.get("f2")).getInteger())); + assertEquals((long) 2, (long) (((JsonNumber) doc.get("tmp")).getInteger())); + assertFalse(docs.hasNext()); + + /* find With bitwise ^ Condition */ + docs = this.collection.find("CAST($.F2 as SIGNED) ^ 1 = 17").fields("$._id as _id,$.F2 as f2, $.F2 ^ 1 as tmp").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("f2")).getInteger())); + assertEquals((long) 17, (long) (((JsonNumber) doc.get("tmp")).getInteger())); + i++; + } + assertFalse(docs.hasNext()); + this.collection.add("{\"x1\":\"31\", \"x2\":\"13\", \"x3\":\"8\", \"x4\":\"18446744073709551614\"}").execute(); + + /* find With bitwise ~ Condition **********FAILING************ */ + docs = this.collection.find("~16 = ~CAST($.F2 as SIGNED)").fields("$._id as _id,$.F2 as f2, ~1 as tmp").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("f2")).getInteger())); + //assertEquals(17, (((JsonNumber) doc.get("tmp")).getInteger())); + i++; + } + assertFalse(docs.hasNext()); + } + + /* interval, In, NOT IN */ + @Test + public void testCollectionFindWithIntervalOperation() throws Exception { + int i = 0, maxrec = 10; + int SLen = 1; + DbDoc doc = null; + + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add("dt", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + newDoc2.add("dtime", new JsonString().setValue((2000 + i) + "-02-01 12:" + (i + 10) + ":01")); + newDoc2.add("str", new JsonString().setValue(buildString(SLen + i, 'q'))); + newDoc2.add("ival", new JsonNumber().setValue(String.valueOf((int) Math.pow(2, (i + 1))))); + if (maxrec - 1 == i) { + newDoc2.add("ival", new JsonNumber().setValue(String.valueOf(-2147483648))); + } + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + assertEquals((maxrec), this.collection.count()); + + /* find With bitwise | Condition */ + DocResult docs = this.collection.find("CAST($.ival as SIGNED)>1 ") + .fields("$._id as _id, $.dt as f1, $.dtime as f2, $.str as f3 , $.ival as f4,$.dt - interval 25 day as tmp").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1 + 1000), (((JsonString) doc.get("_id")).getString())); + assertEquals(((2000 + i) + "-02-" + (i * 2 + 10)), (((JsonString) doc.get("f1")).getString())); + assertEquals((2000 + i) + "-02-01 12:" + (i + 10) + ":01", (((JsonString) doc.get("f2")).getString())); + assertEquals(buildString(SLen + i, 'q'), (((JsonString) doc.get("f3")).getString())); + assertEquals((long) ((int) Math.pow(2, (i + 1))), (long) (((JsonNumber) doc.get("f4")).getInteger())); + i++; + } + + assertEquals((maxrec - 1), i); + /* find With bitwise interval Condition */ + docs = this.collection.find("$.dt + interval 6 day = '2007-03-02' ").fields("$._id as _id, $.dt as f1 ").execute(); + doc = docs.next(); + assertEquals("2007-02-24", (((JsonString) doc.get("f1")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.dt + interval 1 month = '2006-03-22' ").fields("$._id as _id, $.dt as f1 ").execute(); + doc = docs.next(); + assertEquals("2006-02-22", (((JsonString) doc.get("f1")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.dt + interval 1 year = '2010-02-28' ").fields("$._id as _id, $.dt as f1 ").execute(); + doc = docs.next(); + assertEquals("2009-02-28", (((JsonString) doc.get("f1")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.dt - interval 1 year = '2008-02-28' ").fields("$._id as _id, $.dt as f1 ").execute(); + doc = docs.next(); + assertEquals("2009-02-28", (((JsonString) doc.get("f1")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.dt - interval 25 day = '2007-01-30' ").fields("$._id as _id, $.dt as f1 ").execute(); + doc = docs.next(); + assertEquals("2007-02-24", (((JsonString) doc.get("f1")).getString())); + assertFalse(docs.hasNext()); + + /* Between */ + docs = this.collection.find("CAST($.ival as SIGNED) between 65 and 128 ").fields("$._id as _id, $.ival as f1 ").execute(); + doc = docs.next(); + assertEquals((long) 128, (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("$.dt between '2006-01-28' and '2007-02-01' ").fields("$._id as _id, $.dt as f1 ").execute(); + doc = docs.next(); + assertEquals("2006-02-22", (((JsonString) doc.get("f1")).getString())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("CAST($.ival as SIGNED) <0 ").fields("$._id as _id, $.ival as f1 ").execute(); + doc = docs.next(); + assertEquals((long) -2147483648, (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("(CAST($.ival as SIGNED) between 9 and 31) or (CAST($.ival as SIGNED) between 65 and 128)") + .fields("$._id as _id, $.ival as f1 ").orderBy("CAST($.ival as SIGNED) asc").execute(); + doc = docs.next(); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("f1")).getInteger())); + doc = docs.next(); + assertEquals((long) 128, (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("(CAST($.ival as SIGNED) between 9 and 31) and (CAST($.ival as SIGNED) between 65 and 128)") + .fields("$._id as _id, $.ival as f1 ").orderBy("CAST($.ival as SIGNED)").execute(); + assertFalse(docs.hasNext()); + + docs = this.collection.find("CAST($.ival as SIGNED) in (20,NULL,31.5,'17',16,CAST($.ival as SIGNED)+1) ").fields("$._id as _id, $.ival as f1 ") + .execute(); + doc = docs.next(); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("(CAST($.ival as SIGNED) in (16,32,4,256,512)) and ($.dt - interval 25 day = '2007-01-30' ) and ($.ival not in(2,4))") + .fields("$._id as _id, $.ival as f1 ").execute(); + doc = docs.next(); + assertEquals((long) 256, (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertFalse(docs.hasNext()); + } + + /** + * Issue : in orderBy all values are treated as string + * : bind() with Map Fails + * + * @throws Exception + */ + @Test + public void testCollectionFindWithBind() throws Exception { + int i = 0, maxrec = 15; + int SLen = 500; + DbDoc doc = null; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1 + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf((int) Math.pow(2, (i + 1))))); + newDoc2.add("F3", new JsonString().setValue(buildString(SLen + i, 'q'))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* find all */ + DocResult docs = this.collection.find("CAST($.F2 as SIGNED) > ? ").bind(new Object[] { 1 }).fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3") + .execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1 + 1000), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (i + 1), (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertEquals((long) ((int) Math.pow(2, (i + 1))), (long) (((JsonNumber) doc.get("f2")).getInteger())); + // assertEquals(buildString(SLen+i,'q'), (((JsonString) doc.get("f3")).getString())); + i++; + } + assertEquals((maxrec), i); + + docs = this.collection.find("CAST($.F2 as SIGNED) = ?").bind(new Object[] { 32 }).execute(); + doc = docs.next(); + assertEquals((long) 32, (long) (((JsonNumber) doc.get("F2")).getInteger())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("CAST($.F2 as SIGNED) between ? and ?").bind(new Object[] { 10, 17 }).execute(); + doc = docs.next(); + assertEquals((long) 16, (long) (((JsonNumber) doc.get("F2")).getInteger())); + assertFalse(docs.hasNext()); + + docs = this.collection.find("CAST($.F2 as SIGNED) in(?,?,?,?,?,?,?,?,?,?,?+1,?-1)").bind(new Object[] { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 31, 129 }) + .orderBy("CAST($.F2 as SIGNED)").execute(); + doc = docs.next(); + assertEquals((long) 32, (long) (((JsonNumber) doc.get("F2")).getInteger())); + doc = docs.next(); + assertEquals((long) 128, (long) (((JsonNumber) doc.get("F2")).getInteger())); + assertFalse(docs.hasNext()); + + Object[] tmp = new Object[maxrec]; + String q = "CAST($.F2 as SIGNED) in("; + for (i = 0; i < maxrec; i++) { + if (i > 0) { + q = q + ","; + } + q = q + "?"; + tmp[i] = (int) Math.pow(2, (i + 1)); + } + q = q + ")"; + + docs = this.collection.find(q).bind(tmp).orderBy("CAST($.F2 as SIGNED) asc").execute(); + // docs= coll.find(q).bind(tmp).orderBy("$.F2").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) Math.pow(2, i + 1), (long) (((JsonNumber) doc.get("F2")).getInteger())); + i++; + } + assertEquals(maxrec, i); + assertFalse(docs.hasNext()); + //tmp =null; + tmp = new Object[maxrec]; + q = "$.F3 in("; + for (i = 0; i < maxrec; i++) { + if (i > 0) { + q = q + ","; + } + q = q + "?"; + tmp[i] = buildString(SLen + i, 'q'); + } + q = q + ")"; + + docs = this.collection.find(q).bind(tmp).fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 ").orderBy("$.F3 asc").execute(); + //docs= coll.find("$.F3 not in('dd')").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3 ").orderBy("$.F3 asc").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1 + 1000), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (i + 1), (long) (((JsonNumber) doc.get("f1")).getInteger())); + assertEquals((long) ((int) Math.pow(2, (i + 1))), (long) (((JsonNumber) doc.get("f2")).getInteger())); + i++; + } + assertEquals((maxrec), i); + + /* With Map */ + Map params = new HashMap<>(); + params.put("thePlaceholder", 32); + params.put("thePlaceholder2", 2); + docs = this.collection.find("CAST($.F2 as SIGNED) = :thePlaceholder or CAST($.F2 as SIGNED) = :thePlaceholder2") + .fields("$._id as _id, $.F1 as f1, $.F2 as f2").bind(params).orderBy("CAST($.F2 as SIGNED) desc").execute(); + doc = docs.next(); + assertEquals(String.valueOf(1005), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (32), (long) (((JsonNumber) doc.get("f2")).getInteger())); + doc = docs.next(); + assertEquals(String.valueOf(1001), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (2), (long) (((JsonNumber) doc.get("f2")).getInteger())); + + assertFalse(docs.hasNext()); + + q = ""; + params.clear(); + for (i = 0; i < maxrec; i++) { + params.put("thePlaceholder" + i, (int) Math.pow(2, (i + 1))); + if (i > 0) { + q = q + " or "; + } + q = q + "CAST($.F2 as SIGNED) =:thePlaceholder" + i + " "; + } + + docs = this.collection.find(q).fields("$._id as _id, $.F1 as f1, $.F2 as f2").bind(params).execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) ((int) Math.pow(2, (i + 1))), (long) (((JsonNumber) doc.get("f2")).getInteger())); + i++; + } + assertEquals((maxrec), i); + } + + @Test + public void testCollectionFindArray() throws Exception { + int i = 0, j = 0, maxrec = 8, minArraySize = 3; + JsonArray yArray = null; + DbDoc doc = null; + DocResult ddoc = null; + long l3 = 2147483647; + double d1 = 1000.1234; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + jarray.addValue(new JsonNumber().setValue(String.valueOf((l3 + j + i)))); + } + newDoc2.add("ARR1", jarray); + + JsonArray karray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + karray.addValue(new JsonNumber().setValue(String.valueOf((d1 + j + i)))); + } + newDoc2.add("ARR2", karray); + JsonArray larray = new JsonArray(); + for (j = 0; j < (minArraySize + i); j++) { + larray.addValue(new JsonString().setValue("St_" + i + "_" + j)); + } + newDoc2.add("ARR3", larray); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + + assertEquals((maxrec), this.collection.count()); + + ddoc = this.collection.find("CAST($.F1 as SIGNED) > 1").orderBy("$._id").execute(); + i = 1; + while (ddoc.hasNext()) { + doc = ddoc.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(minArraySize + i, yArray.size()); + for (j = 0; j < yArray.size(); j++) { + assertEquals(new BigDecimal(String.valueOf(l3 + j + i)), (((JsonNumber) yArray.get(j)).getBigDecimal())); + } + i++; + } + assertEquals((maxrec), i); + + /* Condition On Array(int) field */ + ddoc = this.collection.find("CAST($.ARR1[1] as SIGNED) > 2147483650").orderBy("CAST($.ARR1[1] as SIGNED)").execute(); + i = 0; + while (ddoc.hasNext()) { + doc = ddoc.next(); + assertEquals((long) (i + 4), (long) (((JsonNumber) doc.get("F1")).getInteger())); + i++; + } + assertEquals((maxrec - 3), i); + + /* Condition On Array(String) field */ + ddoc = this.collection.find("$.ARR3[1] = 'St_3_1'").orderBy("$.ARR3[1]").execute(); + doc = ddoc.next(); + assertEquals((long) (4), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(ddoc.hasNext()); + + /* Multiple Condition On Array(String) field */ + ddoc = this.collection.find("$.ARR3[1] in ('St_3_1','St_2_1','St_4_1')and $.ARR3[2] in ('St_3_2','St_4_2')").orderBy("$.ARR3[1]").execute(); + doc = ddoc.next(); + assertEquals((long) (4), (long) (((JsonNumber) doc.get("F1")).getInteger())); + doc = ddoc.next(); + assertEquals((long) (5), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(ddoc.hasNext()); + } + + /** + * Checks getWarningsCount and getWarnings APIs + * + * @throws Exception + */ + @Test + public void testGetWarningsFromCollection() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + String collname = "coll1"; + Collection coll = null; + Warning w = null; + DocResult docs = null; + int i = 0; + try { + this.session.sql("set max_error_count=20000").execute(); + dropCollection(collname); + coll = this.schema.createCollection(collname, true); + for (i = 1; i <= 10; i++) { + if (i % 2 == 0) { + coll.add("{\"X\":" + i + ",\"Y\":" + (i + 1000) + "}").execute(); + } else { + coll.add("{\"X\":0,\"Y\":0}").execute(); + } + } + docs = coll.find().fields("1/$.X as col1,1/$.Y as col2").execute(); + assertEquals(10, docs.getWarningsCount()); + i = 0; + for (Iterator warn = docs.getWarnings(); warn.hasNext();) { + w = warn.next(); + assertEquals("Division by 0", w.getMessage()); + assertEquals(2, w.getLevel()); + assertEquals(1365, w.getCode()); + i++; + } + this.schema.dropCollection(collname); + coll = this.schema.createCollection(collname, true); + coll.add("{\"X\":1}").execute(); + String s = ""; + for (i = 1; i <= 10000; i++) { + if (i > 1) { + s = s + ","; + } + if (i % 2 == 0) { + s = s + "1/$.X as col" + i; + } else { + s = s + "$.X/0 as col1" + i; + } + } + docs = coll.find().fields(s).execute(); + assertEquals(5000, docs.getWarningsCount()); + i = 0; + for (Iterator warn = docs.getWarnings(); warn.hasNext();) { + w = warn.next(); + assertEquals("Division by 0", w.getMessage()); + assertEquals(2, w.getLevel()); + assertEquals(1365, w.getCode()); + i++; + } + this.schema.dropCollection(collname); + } finally { + if (this.session != null) { + this.session.close(); + } + } + } + + @Test + public void testCollectionFindAsyncMany() throws Exception { + int i = 0, maxrec = 10; + int NUMBER_OF_QUERIES = 1000; + DbDoc doc = null; + AddResult res = null; + CompletableFuture asyncRes = null; + DocResult docs = null; + + double d = 100.123; + + /* add().executeAsync() maxrec num of records */ + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(100 + i))); + + asyncRes = this.collection.add(newDoc2).executeAsync(); + res = asyncRes.get(); + + assertEquals(1, res.getAffectedItemsCount()); + newDoc2 = null; + } + + assertEquals((maxrec), this.collection.count()); + + List> futures = new ArrayList<>(); + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 3 == 0) { + futures.add(this.collection.find("F1 like '%Field%-5'").fields("$._id as _id, $.F1 as F1, $.F2 as F2, $.F3 as F3").executeAsync()); + } else if (i % 3 == 1) { + futures.add(this.collection.find("NON_EXISTING_FUNCTION()").fields("$._id as _id, $.F1 as F1, $.F2 as F2, $.F3 as F3").executeAsync()); //Error + } else { + futures.add(this.collection.find("F3 = ?").bind(106).executeAsync()); + } + } + + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 3 == 0) { + docs = futures.get(i).get(); + doc = docs.next(); + assertEquals((long) 105, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertEquals("1005", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + } else if (i % 3 == 1) { + final int i1 = i; + assertThrows(ExecutionException.class, "com.mysql.cj.protocol.x.XProtocolError: ERROR 1305 \\(42000\\) FUNCTION " + this.schema.getName() + + ".NON_EXISTING_FUNCTION does not exist", () -> futures.get(i1).get()); + } else { + docs = futures.get(i).get(); + doc = docs.next(); + assertEquals((long) 106, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertEquals("1006", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + } + } + + final CompletableFuture asyncDocs = this.collection.find("F3 > ? and F3 < ?").bind(102, 106).fields(expr("{'_id':$._id,'X':sleep(1)}")) + .executeAsync(); + assertThrows(java.util.concurrent.TimeoutException.class, () -> asyncDocs.get(2, TimeUnit.SECONDS)); + } + + @Test + public void testCollectionFindAsyncExt() throws Exception { + int i = 0, maxrec = 10; + DbDoc doc = null; + AddResult res = null; + CompletableFuture asyncRes = null; + CompletableFuture asyncDocs = null; + DocResult docs = null; + try { + double d = 100.123; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + /* add().executeAsync() with JsonList,DbDoc and JsonString */ + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(100 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + + asyncRes = this.collection.add(jsonlist).executeAsync(); + res = asyncRes.get(); + assertEquals(maxrec, res.getAffectedItemsCount()); + + /* With Bind,fields and orderBy */ + asyncDocs = this.collection.find("$.F3 < ? and $.F3 > ? and $.F3 != ?").bind(105, 101, 103).fields("$._id as _id, $.F1 as f1, $.F3 as f3") + .orderBy("$.F3 asc").executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) 102, (long) (((JsonNumber) doc.get("f3")).getInteger())); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + doc = docs.next(); + assertEquals((long) 104, (long) (((JsonNumber) doc.get("f3")).getInteger())); + assertEquals("1004", (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* find with groupBy with Having Clause */ + asyncDocs = this.collection.find("$.F3 > ? and $.F3 < ?").fields("max($.F1) as max_f1, sum($.F2) as max_f2, sum($.F3) as max_f3 ").groupBy("$.F3") + .having("max($.F3) > 105 and max($.F3) < 107 ").bind(104, 108).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals("Field-1-Data-6", (((JsonString) doc.get("max_f1")).getString())); + assertEquals(new BigDecimal(String.valueOf(d + 6)), (((JsonNumber) doc.get("max_f2")).getBigDecimal())); + assertEquals((long) (106), (long) (((JsonNumber) doc.get("max_f3")).getInteger())); + assertFalse(docs.hasNext()); + + sqlUpdate("drop function if exists abcd"); + sqlUpdate( + "CREATE FUNCTION abcd (`p1 col1` CHAR(20)) RETURNS ENUM('YES','NO') COMMENT 'Sample Function abcd' DETERMINISTIC RETURN IF(EXISTS(SELECT 1 ), 'YES', 'NO' )"); + + /* execute Function */ + asyncDocs = this.collection.find("$.F1 like ? and $.F1 not like ? and $.F1 not like ?") + .bind(new Object[] { ("%Fie%-2"), ("%Fie%-1"), ("%Fie%-3") }).fields(expr("{'_id':$._id,'F3':$.F3,'X': abcd('S')}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) 102, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertEquals("YES", (((JsonString) doc.get("X")).getString())); + assertFalse(docs.hasNext()); + + /* Map */ + Map params = new HashMap<>(); + params.put("namedParam10000", "%Fie%-2"); + params.put("namedParam10001", "%Fie%-3"); + params.put("namedParam10002", 102); + asyncDocs = this.collection.find("($.F1 like :namedParam10000 OR $.F1 not like :namedParam10001) and $.F3 = :namedParam10002").bind(params) + .fields(expr("{'_id':$._id,'F3':$.F3,'X': abcd('S')}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) 102, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertEquals("1002", (((JsonString) doc.get("_id")).getString())); + assertEquals("YES", (((JsonString) doc.get("X")).getString())); + assertFalse(docs.hasNext()); + } finally { + sqlUpdate("drop function if exists abcd"); + + } + } } diff --git a/src/test/java/testsuite/x/devapi/CollectionModifyTest.java b/src/test/java/testsuite/x/devapi/CollectionModifyTest.java index 69bd7d578..935ae38ff 100644 --- a/src/test/java/testsuite/x/devapi/CollectionModifyTest.java +++ b/src/test/java/testsuite/x/devapi/CollectionModifyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -35,10 +35,17 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.StringReader; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.mysql.cj.ServerVersion; @@ -48,6 +55,7 @@ import com.mysql.cj.xdevapi.DbDocImpl; import com.mysql.cj.xdevapi.DocResult; import com.mysql.cj.xdevapi.JsonArray; +import com.mysql.cj.xdevapi.JsonLiteral; import com.mysql.cj.xdevapi.JsonNumber; import com.mysql.cj.xdevapi.JsonParser; import com.mysql.cj.xdevapi.JsonString; @@ -64,10 +72,6 @@ public class CollectionModifyTest extends BaseCollectionTestCase { @Test public void testSet() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").execute(); // Requires manual _id. } else { @@ -89,9 +93,6 @@ public void testSet() { @Test public void testUnset() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":\"100\", \"y\":\"200\", \"z\":1}").execute(); // Requires manual _id. this.collection.add("{\"_id\": \"2\", \"a\":\"100\", \"b\":\"200\", \"c\":1}").execute(); @@ -113,9 +114,6 @@ public void testUnset() { @Test public void testReplace() { - if (!this.isSetForXTests) { - return; - } if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":100}").execute(); // Requires manual _id. } else { @@ -130,10 +128,6 @@ public void testReplace() { @Test public void testArrayAppend() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":[8,16,32]}").execute(); // Requires manual _id. } else { @@ -154,10 +148,6 @@ public void testArrayAppend() { @Test public void testArrayInsert() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":[1,2]}").execute(); // Requires manual _id. } else { @@ -179,10 +169,6 @@ public void testArrayInsert() { @Test public void testJsonModify() { - if (!this.isSetForXTests) { - return; - } - DbDoc nestedDoc = new DbDocImpl().add("z", new JsonNumber().setValue("100")); DbDoc doc = new DbDocImpl().add("x", new JsonNumber().setValue("3")).add("y", nestedDoc); @@ -243,10 +229,6 @@ public Void call() throws Exception { @Test public void testArrayModify() { - if (!this.isSetForXTests) { - return; - } - JsonArray xArray = new JsonArray().addValue(new JsonString().setValue("a")).addValue(new JsonNumber().setValue("1")); DbDoc doc = new DbDocImpl().add("x", new JsonNumber().setValue("3")).add("y", xArray); @@ -284,10 +266,6 @@ public void testArrayModify() { */ @Test public void testBug24471057() throws Exception { - if (!this.isSetForXTests) { - return; - } - String docStr = "{\"B\" : 2, \"ID\" : 1, \"KEY\" : [1]}"; if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { docStr = docStr.replace("{", "{\"_id\": \"1\", "); // Inject an _id. @@ -321,9 +299,7 @@ public void testBug24471057() throws Exception { @Test public void testMergePatch() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3")), "MySQL 8.0.3+ is required to run this test."); // 1. Update the name and zip code of match this.collection.add("{\"_id\": \"1\", \"name\": \"Alice\", \"address\": {\"zip\": \"12345\", \"street\": \"32 Main str\"}}").execute(); @@ -509,9 +485,7 @@ public void testMergePatch() throws Exception { */ @Test public void testBug27185332() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3")), "MySQL 8.0.3+ is required to run this test."); DbDoc doc = JsonParser.parseDoc("{\"_id\": \"qqq\", \"nullfield\": {}, \"theme\": { }}"); assertEquals("qqq", ((JsonString) doc.get("_id")).getString()); @@ -563,9 +537,7 @@ public void testBug27185332() throws Exception { @Test public void testReplaceOne() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3")), "MySQL 8.0.3+ is required to run this test."); Result res = this.collection.replaceOne("someId", "{\"_id\":\"someId\",\"a\":3}"); assertEquals(0, res.getAffectedItemsCount()); @@ -577,42 +549,23 @@ public void testReplaceOne() { DbDoc doc = this.collection.getOne("existingId"); assertNotNull(doc); - assertEquals(new Integer(2), ((JsonNumber) doc.get("a")).getInteger()); + assertEquals(2, ((JsonNumber) doc.get("a")).getInteger()); - res = this.collection.replaceOne("notExistingId", "{\"_id\":\"existingId\",\"a\":3}"); - assertEquals(0, res.getAffectedItemsCount()); - - res = this.collection.replaceOne("", "{\"_id\":\"existingId\",\"a\":3}"); - assertEquals(0, res.getAffectedItemsCount()); - - /* - * FR5.1 The id of the document must remain immutable: - * - * Use a collection with some documents - * Fetch a document - * Modify _id: _new_id_ and modify any other field of the document - * Call replaceOne() giving original ID and modified document: expect affected = 1 - * Fetch the document again, ensure other document modifications took place - * Ensure no document with _new_id_ was added to the collection - */ - this.collection.remove("1=1").execute(); - assertEquals(0, this.collection.count()); - this.collection.add("{\"_id\":\"id1\",\"a\":1}").execute(); - - doc = this.collection.getOne("id1"); - assertNotNull(doc); - ((JsonString) doc.get("_id")).setValue("id2"); - ((JsonNumber) doc.get("a")).setValue("2"); - res = this.collection.replaceOne("id1", doc); - assertEquals(1, res.getAffectedItemsCount()); - - doc = this.collection.getOne("id1"); - assertNotNull(doc); - assertEquals("id1", ((JsonString) doc.get("_id")).getString()); - assertEquals(new Integer(2), ((JsonNumber) doc.get("a")).getInteger()); + // Original behavior changed by Bug#32770013. + assertThrows(XDevAPIError.class, "Replacement document has an _id that is different than the matched document\\.", new Callable() { + public Void call() throws Exception { + CollectionModifyTest.this.collection.replaceOne("nonExistingId", "{\"_id\":\"existingId\",\"a\":3}"); + return null; + } + }); - doc = this.collection.getOne("id2"); - assertNull(doc); + // Original behavior changed by Bug#32770013. + assertThrows(XDevAPIError.class, "Replacement document has an _id that is different than the matched document\\.", new Callable() { + public Void call() throws Exception { + CollectionModifyTest.this.collection.replaceOne("", "{\"_id\":\"existingId\",\"a\":3}"); + return null; + } + }); /* * FR5.2 The id of the document must remain immutable: @@ -624,6 +577,10 @@ public void testReplaceOne() { * Fetch the document again, ensure other document modifications took place * Ensure the number of documents in the collection is unaltered */ + this.collection.remove("true").execute(); + assertEquals(0, this.collection.count()); + this.collection.add("{\"_id\":\"id1\",\"a\":1}").execute(); + doc = this.collection.getOne("id1"); assertNotNull(doc); doc.remove("_id"); @@ -673,10 +630,6 @@ public Void call() throws Exception { */ @Test public void testBug27226293() { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{ \"_id\" : \"doc1\" , \"name\" : \"bob\" , \"age\": 45 }").execute(); DocResult result = this.collection.find("name = 'bob'").execute(); @@ -693,9 +646,7 @@ public void testBug27226293() { @Test public void testPreparedStatements() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); try { // Prepare test data. @@ -941,10 +892,6 @@ private void assertTestPreparedStatementsResult(Result res, int expectedAffected @Test @SuppressWarnings("deprecation") public void testDeprecateWhere() throws Exception { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\":\"1\", \"ord\": 1}", "{\"_id\":\"2\", \"ord\": 2}", "{\"_id\":\"3\", \"ord\": 3}", "{\"_id\":\"4\", \"ord\": 4}", "{\"_id\":\"5\", \"ord\": 5}", "{\"_id\":\"6\", \"ord\": 6}", "{\"_id\":\"7\", \"ord\": 7}", "{\"_id\":\"8\", \"ord\": 8}").execute(); @@ -955,4 +902,992 @@ public void testDeprecateWhere() throws Exception { assertEquals(2, testModify.set("$.one", "1").execute().getAffectedItemsCount()); assertEquals(4, ((ModifyStatementImpl) testModify).where("$.ord > 4").set("$.two", "2").execute().getAffectedItemsCount()); } + + @Test + public void testCollectionModifyBasic() throws Exception { + int i = 0, maxrec = 30, recCnt = 0; + DbDoc doc = null; + Result res = null; + String s1 = buildString((10), 'X'); + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1)))); + if (i % 3 == 2) { + newDoc2.add("F3", JsonLiteral.TRUE); + } else if (i % 3 == 1) { + newDoc2.add("F3", JsonLiteral.NULL); + } else { + newDoc2.add("F3", JsonLiteral.FALSE); + } + newDoc2.add("tmp1", new JsonString().setValue("tempdata-" + i)); + newDoc2.add("tmp2", new JsonString().setValue("tempForChange-" + i)); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* fetch all */ + DocResult docs = this.collection.find("CAST($.F2 as SIGNED)> 0").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1000), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (10 * (i + 1)), (long) (((JsonNumber) doc.get("f2")).getInteger())); + i++; + } + assertEquals((maxrec), i); + + /* Modify using empty Condition */ + assertThrows(XDevAPIError.class, "Parameter 'criteria' must not be null or empty.", + () -> CollectionModifyTest.this.collection.modify("").set("$.F1", "Data_New").execute()); + + /* Modify using null Condition */ + assertThrows(XDevAPIError.class, "Parameter 'criteria' must not be null or empty.", + () -> CollectionModifyTest.this.collection.modify(null).set("$.F1", "Data_New").execute()); + + /* Modify with true Condition using Set */ + res = this.collection.modify("true").set("$.F1", "Data_True").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 Like '%True'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + recCnt = count_data(docs); + assertEquals(maxrec, recCnt); + + res = this.collection.modify("1 == 1").set("$.F1", "Data_New").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 Like '%New'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + recCnt = count_data(docs); + assertEquals(maxrec, recCnt); + + /* Modify with a false Condition */ + res = this.collection.modify("false").set("$.F1", "False_Data").execute(); + assertEquals(0, res.getAffectedItemsCount()); + // Modify with a Condition which results to false + res = this.collection.modify("0 == 1").set("$.F1", "False_Data").execute(); + assertEquals(0, res.getAffectedItemsCount()); + + /* Un Set */ + // Test UnSet with condition + docs = this.collection.find("$.tmp1 Like 'tempdata%'").fields("$._id as _id, $.tmp1 as tp").execute(); + recCnt = count_data(docs); + // Total Rec with $.tmp1 Like 'tempdata%' + assertEquals(maxrec, recCnt); + res = this.collection.modify("CAST($._id as SIGNED) % 2").unset("$.tmp1").execute(); + assertEquals(maxrec / 2, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp1 Like 'tempdata%'").fields("$._id as _id, $.tmp1 as tp").execute(); + recCnt = count_data(docs); + // Total records after Unset(with condition) + assertEquals(maxrec / 2, recCnt); + // Test true condition with unset + res = this.collection.modify("1 == 1").unset("$.tmp1").execute(); + assertEquals(maxrec / 2, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp1 Like 'tempdata%'").fields("$._id as _id, $.tmp1 as tp").execute(); + recCnt = count_data(docs); + // Total records after Unset(without condition) + assertEquals(0, recCnt); + + /* Test for Change().unset tmp2 for half of the total records.Call change without condition. */ + res = this.collection.modify("CAST($._id as SIGNED) % 2").unset("$.tmp2").execute(); + assertEquals(maxrec / 2, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp2 Like 'tempForChange%'").fields("$._id as _id, $.tmp2 as tp").execute(); + recCnt = count_data(docs); + // Total records after Unset(with condition) + assertEquals((maxrec / 2), recCnt); + + // Test for Change() + res = this.collection.modify("true").change("$.tmp2", "Changedata").execute(); + assertEquals(maxrec / 2, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp2 Like 'Changedata'").fields("$._id as _id, $.tmp2 as tp").execute(); + recCnt = count_data(docs); + // Total records Changed after modify().change(without condition) + assertEquals((maxrec / 2), recCnt); + + // Test for set () after unset + res = this.collection.modify("true").set("$.tmp2", "Changedata1").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp2 Like 'Changedata1'").fields("$._id as _id, $.tmp2 as tp").execute(); + recCnt = count_data(docs); + // Total Records Set when Half of the records were unset + assertEquals(maxrec, recCnt); + + // Test for set () after unset All + res = this.collection.modify("true").unset("$.tmp2").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp2 IS Not NULL").fields("$._id as _id, $.tmp2 as tp").execute(); + recCnt = count_data(docs); + // Total Records After unsetting all + assertEquals(0, recCnt); + + res = this.collection.modify("1 == 1").set("$.tmp2", "Changedata3").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp2 Like 'Changedata3'").fields("$._id as _id, $.tmp2 as tp").execute(); + recCnt = count_data(docs); + // Total Records Set when All the records were unset + assertEquals(maxrec, recCnt); + + // Modify with Condition using Set + res = this.collection.modify("$._id = '1001' and $.F1 Like '%New' ").set("$.F1", s1).execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 = '" + s1 + "'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + recCnt = count_data(docs); + assertEquals(1, recCnt); + } + + @Test + public void testCollectionModifySortLimit() throws Exception { + int i = 0, maxrec = 30, recCnt = 0; + DbDoc doc = null; + Result res = null; + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1001))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(10 * (i + 1)))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(1 + i))); + newDoc2.add("tmp1", new JsonString().setValue("tempdata-" + i)); + newDoc2.add("tmp2", new JsonNumber().setValue(String.valueOf(-1))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((maxrec), this.collection.count()); + + /* fetch all */ + DocResult docs = this.collection.find("CAST($.F3 as SIGNED)>= 0").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1001), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (10 * (i + 1)), (long) (((JsonNumber) doc.get("f2")).getInteger())); + i++; + } + assertEquals((maxrec), i); + + /* With Sort and limit */ + res = this.collection.modify("$.F3 < 10 and $.F1 Like 'Field-1%' and CAST($.F3 as SIGNED) > 2").set("$.tmp1", "UpdData").sort("$.F1 asc").limit(5) + .execute(); + assertEquals(5, res.getAffectedItemsCount()); + docs = this.collection.find("$.tmp1 = 'UpdData'").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3 as f3").execute(); + i = 2; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1001), (((JsonString) doc.get("_id")).getString())); + assertEquals((long) (i + 1), (long) (((JsonNumber) doc.get("f3")).getInteger())); + i++; + } + + // Unset with Condition using Set With Sort and limit + res = this.collection.modify("$.F3 < 10 and $.F1 Like 'Field-1%' and CAST($.F3 as SIGNED) > 2").unset("$.tmp1").sort("$.F1 asc").limit(5).execute(); + assertEquals(5, res.getAffectedItemsCount()); + // Unset keys which is already unset + res = this.collection.modify("$.F3 < 11 and $.F1 Like 'Field-1%' and CAST($.F3 as SIGNED) > 2").unset("$.tmp1").sort("$.F1 asc").limit(6).execute(); + assertEquals(1, res.getAffectedItemsCount()); + // set keys which is already unset + res = this.collection.modify("$.F3 < 11 and $.F1 Like 'Field-1%' and CAST($.F3 as SIGNED) > 2").set("$.tmp1", "UpdData").sort("$.F1 asc").limit(6) + .execute(); + assertEquals(6, res.getAffectedItemsCount()); + + // set keys which is already unset + res = this.collection.modify("$.F3 < 11 and $.F1 Like 'Field-1%' and CAST($.F3 as SIGNED) > 2").set("$.F2", -2147483648).sort("$.F1 asc").limit(6) + .execute(); + assertEquals(6, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F2 as SIGNED) = -2147483648").fields("$._id as _id, $.F2 as f2").execute(); + recCnt = count_data(docs); + assertEquals(6, recCnt); + res = this.collection.modify("CAST($.F3 as SIGNED) < 11 and $.F1 Like 'Field-1%' and CAST($.F3 as SIGNED) > 2").set("$.tmp2", 2147483647) + .sort("$.F1 asc").limit(6).execute(); + assertEquals(6, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F2 as SIGNED) = (CAST($.tmp2 as SIGNED)*-1)-1").fields("$._id as _id, $.F2 as f2").execute(); + recCnt = count_data(docs); + assertEquals(6, recCnt); + docs = this.collection.find("(CAST($.F2 as SIGNED) * -1) - 1 = CAST($.tmp2 as SIGNED)").fields("$._id as _id, $.F2 as f2").execute(); + recCnt = count_data(docs); + assertEquals(6, recCnt); + + res = this.collection.modify("CAST($.F3 as SIGNED) < 11 and $.F1 Like 'Field-1%'").set("$.tmp2", 9999).set("$.F2", 9999).sort("$.F1 asc").execute(); + assertEquals(10, res.getAffectedItemsCount()); + + /* set,unset,change together */ + // res = coll.modify("CAST($.F3 as SIGNED) < 11").set("$.tmp2",$.tmp2+1).change("$.F1","concat($.F1,'Rajesh')").unset("$.tmp1").sort("$.F1 asc").execute(); + res = this.collection.modify("CAST($.F3 as SIGNED) < 11").set("$.tmp2", 9898).change("$.F1", "'Rajesh'").unset("$.tmp1").sort("$.F1 asc").execute(); + assertEquals(10, res.getAffectedItemsCount()); + + docs = this.collection.find("CAST($.tmp2 as SIGNED) = 9898").fields("$._id as _id, $.F2 as f2").execute(); + recCnt = count_data(docs); + assertEquals(10, recCnt); + + docs = this.collection.find("$.F1 like '%Rajesh'''").fields("$._id as _id, $.F1 as f1").execute(); + recCnt = count_data(docs); + assertEquals(10, recCnt); + + res = this.collection.modify("CAST($.F3 as SIGNED) < 11").unset("$.tmp1").set("$.tmp2", 9897).change("$.F1", "Rajesh").sort("$.F1 asc").execute(); + assertEquals(10, res.getAffectedItemsCount()); + + res = this.collection.modify("true").unset("$.tmp1").set("$.tmp2", 9897).change("$.F1", "Rajesh").sort("$.F1 asc").limit(5).execute(); + assertEquals(5, res.getAffectedItemsCount()); + + res = this.collection.modify("false").unset("$.tmp1").set("$.tmp2", 9897).change("$.F1", "Rajesh").sort("$.F1 asc").limit(5).execute(); + assertEquals(0, res.getAffectedItemsCount()); + + } + + @Test + @Disabled("$.F4 = 9223372036854775807 condition without quotes bind() not supported with modify.") + public void testCollectionModifyBind() throws Exception { + int i = 0, maxrec = 10, recCnt = 0; + Result res = null; + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE, l3 = 2147483647; + System.out.println("l = ===" + l1); + + double d1 = 100.4567; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d1 + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(l1 - i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(l2 + i))); + newDoc2.add("F5", new JsonNumber().setValue(String.valueOf(l3 + i))); + newDoc2.add("F6", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + assertEquals((maxrec), this.collection.count()); + + /* find */ + DocResult docs = this.collection.find("CAST($.F3 as SIGNED)=2147483649").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3+0 as f3").execute(); + recCnt = count_data(docs); + assertEquals(maxrec, recCnt); + + // /* + res = this.collection.modify("$.F4 = ?").set("$.F4", 1).bind(new Object[] { l2 }).sort("$.F1 asc").execute(); + assertEquals(1, res.getAffectedItemsCount()); + // */ + } + + /* + * Using Big int and Double + */ + @Test + public void testCollectionModifyDataTypes() throws Exception { + int i = 0, maxrec = 10, recCnt = 0; + DbDoc doc = null; + Result res = null; + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE, l3 = 2147483647; + System.out.println("l = ===" + l1); + + double d1 = 100.4567; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d1 + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(l1 - i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(l2 + i))); + newDoc2.add("F5", new JsonNumber().setValue(String.valueOf(l3 + i))); + newDoc2.add("F6", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + DocResult docs = this.collection.find("CAST($.F5 as SIGNED)=2147483649").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3+0 as f3").execute(); + recCnt = count_data(docs); + assertEquals(1, recCnt); + + /* condition on Double */ + res = this.collection.modify("CAST($.F2 as DECIMAL(10,4)) =" + d1).set("$.F1", "UpdData1").sort("CAST($.F2 as DECIMAL(10,4))").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'UpdData1'").fields("$._id as _id, $.F2 as f2").execute(); + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(d1)), (((JsonNumber) doc.get("f2")).getBigDecimal())); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + + /* condition on Big Int */ + res = this.collection.modify("CAST($.F3 as SIGNED) =" + l1).set("$.F1", "UpdData2").sort("CAST($.F3 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'UpdData2'").fields("$._id as _id, $.F3 as f3").execute(); + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(l1)), (((JsonNumber) doc.get("f3")).getBigDecimal())); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + + /* condition on Big Int */ + res = this.collection.modify("CAST($.F5 as SIGNED) >= " + l3 + " and CAST($.F5 as SIGNED) < " + l1 + " and CAST($.F5 as SIGNED) > " + l2) + .set("$.F1", "AllUpd").sort("CAST($.F5 as SIGNED)").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'AllUpd'").fields("$._id as _id, $.F5 as f5").orderBy(" CAST($.F5 as SIGNED)").execute(); + + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals(String.valueOf(i + 1000), (((JsonString) doc.get("_id")).getString())); + assertEquals(new BigDecimal(String.valueOf(i + l3)), (((JsonNumber) doc.get("f5")).getBigDecimal())); + i++; + } + assertEquals(maxrec, i); + + /* condition on Double */ + res = this.collection.modify("CAST($.F5 as SIGNED) =" + l3 + "+" + 3).set("$.F1", "UpdData3").sort("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'UpdData3'").fields("$._id as _id, $.F5 as f5").execute(); + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(3 + l3)), (((JsonNumber) doc.get("f5")).getBigDecimal())); + assertEquals(String.valueOf(1000 + 3), (((JsonString) doc.get("_id")).getString())); + + /* condition on Double */ + res = this.collection.modify("CAST($.F5 as SIGNED) - 3 =" + l3).set("$.F1", "UpdData4").sort("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'UpdData4'").fields("$._id as _id, $.F5 as f5").execute(); + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(3 + l3)), (((JsonNumber) doc.get("f5")).getBigDecimal())); + assertEquals(String.valueOf(1000 + 3), (((JsonString) doc.get("_id")).getString())); + + /* condition on date */ + res = this.collection.modify("$.F6 + interval 6 day = '2007-03-02' ").set("$.F1", "UpdData5").sort("$.F6").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'UpdData5'").fields("$._id as _id, $.F6 as f6").execute(); + doc = docs.next(); + assertEquals("2007-02-24", (((JsonString) doc.get("f6")).getString())); + assertEquals(String.valueOf(1000 + 7), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + } + + /* + * Update Using Expressions + */ + @Test + public void testCollectionModifyExpr() throws Exception { + int i = 0, maxrec = 10; + DbDoc doc = null; + Result res = null; + DocResult docs = null; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE, l3 = 2147483647; + double d1 = 100.4567; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d1 + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(l1 - i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(l2 + i))); + newDoc2.add("F5", new JsonNumber().setValue(String.valueOf(l3 + i))); + newDoc2.add("F6", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + assertEquals((maxrec), this.collection.count()); + + /* condition on Double */ + res = this.collection.modify("CAST($.F2 as DECIMAL(10,4)) =" + d1).set("$.F1", expr("concat('data',$.F1,'UpdData1')")) + .sort("CAST($.F2 as DECIMAL(10,4))").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 like 'data%UpdData1'").fields("$._id as _id, $.F2 as f2").execute(); + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(d1)), (((JsonNumber) doc.get("f2")).getBigDecimal())); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + + res = this.collection.modify("CAST($.F2 as DECIMAL(10,4)) =" + d1).set("$.F6", expr("$.F6 + interval 6 day")).sort("CAST($.F2 as DECIMAL(10,4))") + .execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F6 + interval 6 day = '2000-02-22'").fields("$._id as _id, $.F6 as f6").execute(); + doc = docs.next(); + assertEquals("2000-02-16", (((JsonString) doc.get("f6")).getString())); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + + res = this.collection.modify("$.F6= '2004-02-18'").set("$.F6", expr("$.F6 + interval 11 day")).set("$.F1", "NewData") + .sort("CAST($.F2 as DECIMAL(10,4))").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("$.F1 = 'NewData'").fields("$._id as _id, $.F6 as f6").execute(); + doc = docs.next(); + assertEquals("2004-02-29", (((JsonString) doc.get("f6")).getString())); + assertEquals(String.valueOf(1004), (((JsonString) doc.get("_id")).getString())); + + /* condition on Big Int */ + res = this.collection.modify("CAST($.F3 as SIGNED) =" + l1).set("$.F3", expr("CAST($.F3 as SIGNED) -1")).sort("CAST($.F3 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + res = this.collection.modify("CAST($.F3 as SIGNED) + 1 =" + l1).set("$.F3", expr("CAST($.F3 as SIGNED) + 1")).sort("CAST($.F3 as SIGNED)").execute(); + assertEquals(2, res.getAffectedItemsCount()); + + docs = this.collection.find("CAST($.F3 as SIGNED)=" + l1).fields("$._id as _id, $.F3 as f3").orderBy("$._id asc").execute(); + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(l1)), (((JsonNumber) doc.get("f3")).getBigDecimal())); + assertEquals(String.valueOf(1000), (((JsonString) doc.get("_id")).getString())); + + doc = docs.next(); + assertEquals(new BigDecimal(String.valueOf(l1)), (((JsonNumber) doc.get("f3")).getBigDecimal())); + assertEquals(String.valueOf(1001), (((JsonString) doc.get("_id")).getString())); + assertFalse(docs.hasNext()); + + /* condition on Big Int.Compex Expression */ + res = this.collection.modify("CAST($.F4 as SIGNED) < 0").set("$.F1", "Abcd") + .set("$.F4", expr("((CAST($.F4 as SIGNED) + CAST($.F3 as SIGNED)) * 1)/1.1 + 1 ")).execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + + res = this.collection.modify("true").set("$.F1", expr("concat('data',$.F1,'UpdData1')")).sort("CAST($.F2 as DECIMAL(10,4))").execute(); + assertEquals(10, res.getAffectedItemsCount()); + + res = this.collection.modify("false").set("$.F1", "Abcd").set("$.F4", expr("((CAST($.F4 as SIGNED) + CAST($.F3 as SIGNED)) * 1)/1.1 + 1 ")).execute(); + assertEquals(0, res.getAffectedItemsCount()); + } + + @Test + public void testCollectionModifyArray() throws Exception { + int i = 0, j = 0, maxrec = 8, arraySize = 30; + int lStr = 1024 * 800; + JsonArray yArray = null; + DbDoc doc = null; + DocResult docs = null; + Result res = null; + String s1 = buildString((lStr), 'X'); + long l3 = 2147483647; + double d1 = 1000.1234; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + jarray.addValue(new JsonNumber().setValue(String.valueOf((l3 + j + i)))); + } + newDoc2.add("ARR1", jarray); + + JsonArray karray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + karray.addValue(new JsonNumber().setValue(String.valueOf((d1 + j + i)))); + } + newDoc2.add("ARR2", karray); + JsonArray larray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + larray.addValue(new JsonString().setValue("St_" + i + "_" + j)); + } + newDoc2.add("ARR3", larray); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + assertEquals((maxrec), this.collection.count()); + + //Update Array data using expr + res = this.collection.modify("$.F1 = 1").change("$.ARR1[1]", expr("$.ARR1[1] / $.ARR1[1]")).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + + docs = this.collection.find("CAST($.ARR1[1] as SIGNED) = 1").orderBy("$._id").execute(); + doc = docs.next(); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + /* Unset Array element */ + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").unset("$.ARR1[1]").sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 = 1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize - 1, yArray.size()); + assertEquals(new BigDecimal("2147483647"), (((JsonNumber) yArray.get(0)).getBigDecimal())); + assertEquals(new BigDecimal("2147483649"), (((JsonNumber) yArray.get(1)).getBigDecimal())); + assertFalse(docs.hasNext()); + + /* set Array element */ + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").set("$.ARR1[1]", 90).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 = 1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize - 1, yArray.size()); + assertEquals(new BigDecimal("2147483647"), (((JsonNumber) yArray.get(0)).getBigDecimal())); + assertEquals(new BigDecimal("90"), (((JsonNumber) yArray.get(1)).getBigDecimal())); + assertFalse(docs.hasNext()); + + /* set Array element */ + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").set("$.ARR1[2]", 91).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 = 1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize - 1, yArray.size()); + assertEquals(new BigDecimal("2147483647"), (((JsonNumber) yArray.get(0)).getBigDecimal())); + assertEquals(new BigDecimal("90"), (((JsonNumber) yArray.get(1)).getBigDecimal())); + assertEquals(new BigDecimal("91"), (((JsonNumber) yArray.get(2)).getBigDecimal())); + assertFalse(docs.hasNext()); + + /* set Array element (String) */ + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").set("$.ARR3[1]", s1).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("length($.ARR3[1]) = " + lStr).orderBy("$._id").execute(); + doc = docs.next(); + assertEquals((long) 1, (long) (((JsonNumber) doc.get("F1")).getInteger())); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize, yArray.size()); + assertEquals("St_0_0", (((JsonString) yArray.get(0)).getString())); + assertEquals("St_0_2", (((JsonString) yArray.get(2)).getString())); + assertFalse(docs.hasNext()); + + /* set Array element (String) */ + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").set("$.ARR3[1]", expr("concat($.ARR3[1], $.ARR3[1])")).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("length($.ARR3[1]) = " + (lStr * 2)).orderBy("$._id").execute(); + doc = docs.next(); + assertEquals((long) 1, (long) (((JsonNumber) doc.get("F1")).getInteger())); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize, yArray.size()); + assertEquals("St_0_0", (((JsonString) yArray.get(0)).getString())); + assertEquals("St_0_2", (((JsonString) yArray.get(2)).getString())); + assertFalse(docs.hasNext()); + + /* Change Array elements of all rows (String) */ + res = this.collection.modify("CAST($.F1 as SIGNED) >= 1").set("$.ARR3[1]", expr("concat($.ARR3[1], '" + s1 + "')")).sort("$._id").execute(); + assertEquals(maxrec, res.getAffectedItemsCount()); + docs = this.collection.find("length($.ARR3[1]) > " + (lStr)).orderBy("$._id").fields(expr("{'cnt':count($._id)}")).execute(); + doc = docs.next(); + assertEquals((long) maxrec, (long) ((JsonNumber) doc.get("cnt")).getInteger()); + assertFalse(docs.hasNext()); + + /* Unset Array element(String) */ + res = this.collection.modify("$.F1 = 1").unset("$.ARR3[1]").sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F1 as SIGNED) = 1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize - 1, yArray.size()); + assertEquals("St_0_0", (((JsonString) yArray.get(0)).getString())); + assertEquals("St_0_2", (((JsonString) yArray.get(1)).getString())); + assertFalse(docs.hasNext()); + + /* Unset Array element(String) */ + res = this.collection.modify("CAST($.F1 as SIGNED) > 1").unset("$.ARR3[1]").sort("$._id").execute(); + assertEquals((maxrec - 1), res.getAffectedItemsCount()); + docs = this.collection.find("length($.ARR3[1]) < " + (lStr)).orderBy("$._id").fields(expr("{'cnt':count($._id)}")).execute(); + doc = docs.next(); + assertEquals((long) maxrec, (long) ((JsonNumber) doc.get("cnt")).getInteger()); + assertFalse(docs.hasNext()); + } + + /* ArrayAppend() for int double and string */ + @Test + public void testCollectionModifyArrayAppend() throws Exception { + int i = 0, j = 0, maxrec = 8, arraySize = 30; + int lStr = 10; + JsonArray yArray = null; + DbDoc doc = null; + DocResult docs = null; + Result res = null; + String s1 = buildString((lStr), '.'); + long l3 = 2147483647; + double d1 = 1000.1234; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + jarray.addValue(new JsonNumber().setValue(String.valueOf((l3 + j + i)))); + } + newDoc2.add("ARR1", jarray); + + JsonArray karray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + karray.addValue(new JsonNumber().setValue(String.valueOf((d1 + j + i)))); + } + newDoc2.add("ARR2", karray); + JsonArray larray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + larray.addValue(new JsonString().setValue("St_" + i + "_" + j)); + } + newDoc2.add("ARR3", larray); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + assertEquals((maxrec), this.collection.count()); + + // Append 1 number in the array (ARR1) where $.F1 = 1 + res = this.collection.modify("$.F1 = 1").arrayAppend("$.ARR1", -1).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR1[" + (arraySize) + "] as SIGNED) = -1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize + 1, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Append 3 numbers in the array (ARR1) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayAppend("$.ARR1", -2).arrayAppend("$.ARR1", -3).arrayAppend("$.ARR1", -4).sort("$._id") + .execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR1[" + (arraySize) + "] as SIGNED) = -1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize + 4, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Append 1 number in the array (ARR2) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayAppend("$.ARR2", -4321.4321).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR2[" + (arraySize) + "] as DECIMAL(10,4)) = -4321.4321").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR2"); + assertEquals(arraySize + 1, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Append 3 number in the array (ARR2) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayAppend("$.ARR2", 4321.1234).arrayAppend("$.ARR2", 4321.9847) + .arrayAppend("$.ARR2", -4321.9888).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR2[" + (arraySize) + "] as DECIMAL(10,4)) = -4321.4321").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR2"); + assertEquals(arraySize + 4, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Append 1 String in the array (ARR3) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayAppend("$.ARR3", s1).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR3[" + (arraySize) + "] = '" + s1 + "'").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize + 1, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Append 5 Strings in the array (ARR3) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayAppend("$.ARR3", s1 + "1").arrayAppend("$.ARR3", s1 + "2").arrayAppend("$.ARR3", s1 + "3") + .arrayAppend("$.ARR3", s1 + "4").arrayAppend("$.ARR3", s1 + "5").sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR3[" + (arraySize) + "] = '" + s1 + "'").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize + 6, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + } + + /* ArrayInsert() for int , double and string */ + @Test + public void testCollectionModifyArrayInsert() throws Exception { + int i = 0, j = 0, maxrec = 8, arraySize = 30; + int lStr = 10; + JsonArray yArray = null; + DbDoc doc = null; + DocResult docs = null; + Result res = null; + String s1 = buildString((lStr), '.'); + long l3 = 2147483647; + double d1 = 1000.1234; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i + 1))); + + JsonArray jarray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + jarray.addValue(new JsonNumber().setValue(String.valueOf((l3 + j + i)))); + } + newDoc2.add("ARR1", jarray); + + JsonArray karray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + karray.addValue(new JsonNumber().setValue(String.valueOf((d1 + j + i)))); + } + newDoc2.add("ARR2", karray); + JsonArray larray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + larray.addValue(new JsonString().setValue("St_" + i + "_" + j)); + } + newDoc2.add("ARR3", larray); + this.collection.add(newDoc2).execute(); + newDoc2 = null; + jarray = null; + } + assertEquals((maxrec), this.collection.count()); + + // Insert to a aposistion > arraySize Shld Work same as Append + // Insert 1 number in the array (ARR1) after position arraySize where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayInsert("$.ARR1[" + (arraySize * 2) + "]", -1).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR1[" + (arraySize) + "] as SIGNED) = -1").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize + 1, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Insert 3 numbers in the array (ARR1) where $.F1 = 2 + res = this.collection.modify("CAST($.F1 as SIGNED) = 2").arrayInsert("$.ARR1[0]", -2).arrayInsert("$.ARR1[1]", -3).arrayInsert("$.ARR1[2]", -4) + .sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR1[0] as SIGNED) = -2").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals((long) (arraySize + 3), (long) yArray.size()); + assertEquals((long) (2), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertEquals((long) (-2), (long) (((JsonNumber) yArray.get(0)).getInteger())); + assertEquals((long) (-3), (long) (((JsonNumber) yArray.get(1)).getInteger())); + assertEquals((long) (-4), (long) (((JsonNumber) yArray.get(2)).getInteger())); + assertFalse(docs.hasNext()); + + // Insert 3 numbers in the array (ARR1) where $.F1 = 3 + res = this.collection.modify("CAST($.F1 as SIGNED) = 3").arrayInsert("$.ARR1[2]", -4).arrayInsert("$.ARR1[1]", -3).arrayInsert("$.ARR1[0]", -2) + .sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR1[2] as SIGNED) = -3").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR1"); + assertEquals(arraySize + 3, yArray.size()); + assertEquals((3), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertEquals((long) (-2), (long) (((JsonNumber) yArray.get(0)).getInteger())); + assertEquals((long) (-3), (long) (((JsonNumber) yArray.get(2)).getInteger())); + assertEquals((long) (-4), (long) (((JsonNumber) yArray.get(4)).getInteger())); + assertFalse(docs.hasNext()); + + // Insert 1 number in the array (ARR2) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayInsert("$.ARR2[1]", -4321.4321).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR2[1] as DECIMAL(10,4)) = -4321.4321").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR2"); + assertEquals((long) (arraySize + 1), (long) yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Insert 3 number in the array (ARR2) where $.F1 = 2 + res = this.collection.modify("CAST($.F1 as SIGNED) = 2").arrayInsert("$.ARR2[2]", 4321.1234).arrayInsert("$.ARR2[0]", 4321.9847) + .arrayInsert("$.ARR2[1]", -4321.9888).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.ARR2[0] as DECIMAL(10,4)) = 4321.9847").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR2"); + assertEquals(arraySize + 3, yArray.size()); + assertEquals((long) (2), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertEquals(new BigDecimal(String.valueOf("4321.1234")), (((JsonNumber) yArray.get(4)).getBigDecimal())); + assertEquals(new BigDecimal(String.valueOf("4321.9847")), (((JsonNumber) yArray.get(0)).getBigDecimal())); + assertEquals(new BigDecimal(String.valueOf("-4321.9888")), (((JsonNumber) yArray.get(1)).getBigDecimal())); + assertFalse(docs.hasNext()); + + // Insert 1 String in the array (ARR3) where $.F1 = 1 + res = this.collection.modify("CAST($.F1 as SIGNED) = 1").arrayInsert("$.ARR3[1]", s1).sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR3[1] = '" + s1 + "'").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize + 1, yArray.size()); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Insert 3 Strings in the array (ARR3) where $.F1 = 2 + res = this.collection.modify("CAST($.F1 as SIGNED) = 2").arrayInsert("$.ARR3[1]", s1).arrayInsert("$.ARR3[2]", s1).arrayInsert("$.ARR3[0]", "") + .sort("$._id").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR3[0] = ''").orderBy("$._id").execute(); + doc = docs.next(); + yArray = (JsonArray) doc.get("ARR3"); + assertEquals(arraySize + 3, yArray.size()); + assertEquals((long) (2), (long) (((JsonNumber) doc.get("F1")).getInteger())); + assertFalse(docs.hasNext()); + + // Insert 3 Strings in the array (ARR3) using an empty condition + assertThrows(XDevAPIError.class, "Parameter 'criteria' must not be null or empty.", new Callable() { + public Void call() throws Exception { + CollectionModifyTest.this.collection.modify(" ").arrayInsert("$.ARR3[1]", s1).arrayInsert("$.ARR3[2]", s1).arrayInsert("$.ARR3[0]", "") + .sort("$._id").execute(); + return null; + } + }); + + // Insert 3 Strings in the array (ARR3) using null condition + assertThrows(XDevAPIError.class, "Parameter 'criteria' must not be null or empty.", new Callable() { + public Void call() throws Exception { + CollectionModifyTest.this.collection.modify(null).arrayInsert("$.ARR3[1]", s1).arrayInsert("$.ARR3[2]", s1).arrayInsert("$.ARR3[0]", "") + .sort("$._id").execute(); + return null; + } + }); + } + + @Test + public void testCollectionModifyAsync() throws Exception { + int i = 0, j = 0, maxrec = 10; + DbDoc doc = null; + AddResult res = null; + Result res2 = null; + CompletableFuture asyncRes = null; + CompletableFuture asyncRes2 = null; + CompletableFuture asyncDocs = null; + DocResult docs = null; + double d = 100.123; + + /* add().executeAsync() maxrec num of records */ + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(100 + i))); + newDoc2.add("T", new JsonNumber().setValue(String.valueOf(i))); + asyncRes = this.collection.add(newDoc2).executeAsync(); + res = asyncRes.get(); + assertEquals(1, res.getAffectedItemsCount()); + newDoc2 = null; + } + + assertEquals((maxrec), this.collection.count()); + + asyncDocs = this.collection.find("F3 >= ? and F3 < ?").bind(100, 100006).fields(expr("{'cnt':count($.F1)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) maxrec, (long) (((JsonNumber) doc.get("cnt")).getInteger())); + + /* Simple Update with executeAsync */ + asyncRes2 = this.collection.modify("$.F3 > 100").unset("$.T").sort("$.F3 desc").executeAsync(); + res2 = asyncRes2.get(); + assertEquals((maxrec - 1), res2.getAffectedItemsCount()); + + asyncRes2 = this.collection.modify("$.F3 >= 100").change("$.T", expr("10000+1")).sort("$.F3 desc").executeAsync(); + res2 = asyncRes2.get(); + assertEquals((1), res2.getAffectedItemsCount()); + + asyncDocs = this.collection.find("$.T >= ? ").bind(10000).fields(expr("{'cnt':count($.T)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("cnt")).getInteger())); + + asyncRes2 = this.collection.modify("$.F3 >= 100").unset("$.T").sort("$.F3 desc").executeAsync(); + res2 = asyncRes.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + asyncRes2 = this.collection.modify("$.F3 >= 100").set("$.T", expr("10000+3")).sort("$.F3 desc").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(maxrec, res2.getAffectedItemsCount()); + + asyncDocs = this.collection.find("$.T >= ? ").bind(10000).fields(expr("{'cnt':count($.T)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) maxrec, (long) (((JsonNumber) doc.get("cnt")).getInteger())); + + CompletableFuture futures[] = new CompletableFuture[501]; + //List futures = new ArrayList(); + j = 0; + for (i = 0; i < 500; ++i, j++) { + if (j >= maxrec) { + j = 0; + } + futures[i] = this.collection.modify("$.F3 = " + (100 + j)).change("$.T", i).executeAsync(); + } + for (i = 0; i < 500; ++i, j++) { + // res = ((CompletableFuture) futures.get(i)).get(); + res2 = (Result) futures[i].get(); + assertEquals(1, res2.getAffectedItemsCount()); + } + + futures[i] = this.collection.modify("true").change("$.T", -1).executeAsync(); + + // wait for them all to finish + CompletableFuture.allOf(futures).get(); + + asyncDocs = this.collection.find("$.T = ? ").bind(-1).fields(expr("{'cnt':count($.T)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) maxrec, (long) (((JsonNumber) doc.get("cnt")).getInteger())); + } + + @SuppressWarnings("unchecked") + @Test + public void testCollectionModifyAsyncMany() throws Exception { + int i = 0, maxrec = 10; + int NUMBER_OF_QUERIES = 1000; + DbDoc doc = null; + AddResult res = null; + Result res2 = null; + CompletableFuture asyncRes = null; + CompletableFuture asyncDocs = null; + DocResult docs = null; + double d = 100.123; + + /* add().executeAsync() maxrec num of records */ + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(1000 + i))); + newDoc2.add("T", new JsonNumber().setValue(String.valueOf(i))); + asyncRes = this.collection.add(newDoc2).executeAsync(); + res = asyncRes.get(); + assertEquals(1, res.getAffectedItemsCount()); + newDoc2 = null; + } + + assertEquals((maxrec), this.collection.count()); + + List futures = new ArrayList<>(); + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 3 == 0) { + futures.add(this.collection.modify("$.F3 % 2 = 0 ").change("$.T", expr("1000000+" + i)).sort("$.F3 desc").executeAsync()); + } else if (i % 3 == 1) { + futures.add(this.collection.modify("$.F3 = " + (1000 + i)).change("$.T", expr("NON_EXISTING_FUNCTION()")).sort("$.F3 desc").executeAsync());//Error + } else { + futures.add(this.collection.modify("$.F3 % 2 = 1 ").change("$.T", expr("$.F3+" + i)).sort("$.F3 desc").executeAsync()); + } + } + + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 3 == 0) { + res2 = ((CompletableFuture) futures.get(i)).get(); + assertEquals((maxrec) / 2, res2.getAffectedItemsCount()); + } else if (i % 3 == 1) { + int i1 = i; + assertThrows(ExecutionException.class, ".*FUNCTION " + this.schema.getName() + ".NON_EXISTING_FUNCTION does not exist.*", + () -> ((CompletableFuture) futures.get(i1)).get()); + } else { + res2 = ((CompletableFuture) futures.get(i)).get(); + assertEquals((maxrec) / 2, res2.getAffectedItemsCount()); + } + } + + asyncDocs = this.collection.find("$.T > :X ").bind("X", 1000000).fields(expr("{'cnt':count($.T)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) (maxrec) / 2, (long) (((JsonNumber) doc.get("cnt")).getInteger())); + + asyncDocs = this.collection.find("$.T > :X and $.T < :Y").bind("X", 1000).bind("Y", 1000000).fields(expr("{'cnt':count($.T)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) (maxrec) / 2, (long) (((JsonNumber) doc.get("cnt")).getInteger())); + } } diff --git a/src/test/java/testsuite/x/devapi/CollectionRemoveTest.java b/src/test/java/testsuite/x/devapi/CollectionRemoveTest.java index 14bdb01c5..0eb550e92 100644 --- a/src/test/java/testsuite/x/devapi/CollectionRemoveTest.java +++ b/src/test/java/testsuite/x/devapi/CollectionRemoveTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,15 +32,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import org.junit.jupiter.api.Test; import com.mysql.cj.ServerVersion; +import com.mysql.cj.protocol.x.XProtocolError; import com.mysql.cj.xdevapi.Collection; +import com.mysql.cj.xdevapi.DbDoc; +import com.mysql.cj.xdevapi.DbDocImpl; import com.mysql.cj.xdevapi.DocResult; +import com.mysql.cj.xdevapi.JsonArray; import com.mysql.cj.xdevapi.JsonNumber; +import com.mysql.cj.xdevapi.JsonString; import com.mysql.cj.xdevapi.RemoveStatement; import com.mysql.cj.xdevapi.RemoveStatementImpl; import com.mysql.cj.xdevapi.Result; @@ -54,10 +64,6 @@ public class CollectionRemoveTest extends BaseCollectionTestCase { @Test public void deleteAll() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").execute(); // Requires manual _id. this.collection.add("{\"_id\": \"2\"}").execute(); @@ -96,10 +102,6 @@ public Void call() throws Exception { @Test public void deleteSome() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").execute(); // Requires manual _id. this.collection.add("{\"_id\": \"2\"}").execute(); @@ -117,10 +119,6 @@ public void deleteSome() { @Test public void removeOne() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\", \"x\":1}").execute(); // Requires manual _id. this.collection.add("{\"_id\": \"2\", \"x\":2}").execute(); @@ -154,9 +152,7 @@ public void removeOne() { @Test public void testPreparedStatements() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); try { // Prepare test data. @@ -402,10 +398,6 @@ private void assertTestPreparedStatementsResult(Result res, int expectedAffected @Test @SuppressWarnings("deprecation") public void testDeprecateWhere() throws Exception { - if (!this.isSetForXTests) { - return; - } - this.collection.add("{\"_id\":\"1\", \"ord\": 1}", "{\"_id\":\"2\", \"ord\": 2}", "{\"_id\":\"3\", \"ord\": 3}", "{\"_id\":\"4\", \"ord\": 4}", "{\"_id\":\"5\", \"ord\": 5}", "{\"_id\":\"6\", \"ord\": 6}", "{\"_id\":\"7\", \"ord\": 7}", "{\"_id\":\"8\", \"ord\": 8}").execute(); @@ -416,4 +408,310 @@ public void testDeprecateWhere() throws Exception { assertEquals(2, testRemove.execute().getAffectedItemsCount()); assertEquals(4, ((RemoveStatementImpl) testRemove).where("$.ord > 4").execute().getAffectedItemsCount()); } + + @SuppressWarnings("deprecation") + @Test + public void testCollectionRemoveBasic() throws Exception { + int i = 0, j = 0, maxrec = 100, recCnt = 0, arraySize = 30; + Result res = null; + DocResult docs = null; + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE; + double d1 = 100.4567; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d1 + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(l1 - i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(l2 + i))); + newDoc2.add("F5", new JsonNumber().setValue(String.valueOf(1 + i))); + newDoc2.add("F6", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + JsonArray jarray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + jarray.addValue(new JsonString().setValue("String-" + i + "-" + j)); + } + newDoc2.add("ARR1", jarray); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + assertEquals((maxrec), this.collection.count()); + + /* find without Condition */ + docs = this.collection.find("$.F4<0").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + recCnt = count_data(docs); + assertEquals(maxrec, recCnt); + + /* remove with condition */ + res = this.collection.remove("CAST($.F5 as SIGNED) = 1").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) = 1 ").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* remove with condition, limit and orderBy */ + res = this.collection.remove("CAST($.F5 as SIGNED) < 10").limit(1).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) = 2 ").fields("$._id as _id, $.F1 as f1, $.F2 as f2, $.F3+0 as f3").execute(); + assertFalse(docs.hasNext()); + + /* remove with condition on string */ + res = this.collection.remove("$.F1 = 'Field-1-Data-2'").limit(10).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.F1 = 'Field-1-Data-2'").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* remove with condition on BigInt */ + res = this.collection.remove("CAST($.F3 as SIGNED) = " + (l1 - 3)).limit(10).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F3 as SIGNED) = " + (l1 - 3)).fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* remove with condition on Double */ + res = this.collection.remove("CAST($.F2 as DECIMAL(10,5)) = " + (d1 + 4)).limit(10).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F2 as SIGNED) = " + (d1 + 4)).fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* remove with condition on Array */ + res = this.collection.remove("$.ARR1[1] like 'String-5-1' OR $.ARR1[0] like 'String-5-0' AND $.ARR1[2] like 'String-5-2'").limit(10) + .orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR1[1] like 'String-5-%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* Try to remove non-existing row with condition on Array */ + res = this.collection.remove("$.ARR1[1] like 'String-5-1' OR $.ARR1[0] like 'String-5-0' AND $.ARR1[2] like 'String-5-2'").limit(10) + .orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(0, res.getAffectedItemsCount()); + + /* remove with condition on Array */ + res = this.collection.remove("$.ARR1[1] like concat(substr($.ARR1[0],1,7),'6','-1')").limit(10).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR1[1] like 'String-6-%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* remove with null condition */ + i = (int) this.collection.count(); + assertThrows(XDevAPIError.class, "Parameter 'criteria' must not be null or empty.", () -> this.collection.remove(null).execute()); + + /* remove with empty condition */ + i = (int) this.collection.count(); + assertThrows(XDevAPIError.class, "Parameter 'criteria' must not be null or empty.", () -> this.collection.remove(" ").execute()); + + /* remove All with a true condition */ + i = (int) this.collection.count(); + res = this.collection.remove("true").limit((maxrec * 10)).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(i, res.getAffectedItemsCount()); + docs = this.collection.find("$.ARR1[1] like 'S%'").fields("$._id as _id, $.F1 as f1, $.F2 as f2").execute(); + assertFalse(docs.hasNext()); + + /* remove with a false condition */ + i = (int) this.collection.count(); + res = this.collection.remove("false").limit((maxrec * 10)).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(0, res.getAffectedItemsCount()); + } + + @SuppressWarnings("deprecation") + @Test + public void testCollectionRemoveBindComplex() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0, j = 0, maxrec = 20, arraySize = 3; + DbDoc doc = null; + Result res = null; + DocResult docs = null; + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE; + double d1 = 100.4567; + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf(d1 + i))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(l1 - i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(l2 + i))); + newDoc2.add("F5", new JsonNumber().setValue(String.valueOf(1 + i))); + newDoc2.add("F6", new JsonString().setValue((2000 + i) + "-02-" + (i * 2 + 10))); + JsonArray jarray = new JsonArray(); + for (j = 0; j < (arraySize); j++) { + if (j == 1) { + jarray.addValue(new JsonString().setValue("String-" + j)); + } else { + jarray.addValue(new JsonString().setValue("String-" + i + "-" + j)); + } + } + newDoc2.add("ARR1", jarray); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + /* + * coll.createIndex("index1",true).field(".F1","TEXT(128)",true).execute(); + * coll.createIndex("index2",true).field(".ARR1["+(arraySize-1)+"]","TEXT(256)",true).execute(); + * coll.createIndex("index3",true).field(".F5","INT",true).execute(); + * coll.createIndex("index4",true).field(".F3","BIGINT",true).execute(); + */ + + this.collection.createIndex("index1", "{\"fields\": [{\"field\": \"$.F1\", \"type\": \"TEXT(120)\", \"required\": true}], \"type\" : \"INDEX\"}"); + this.collection.createIndex("index2", + "{\"fields\": [{\"field\": \"$.ARR1[" + (arraySize - 1) + "]\", \"type\": \"TEXT(120)\", \"required\": true}], \"type\" : \"INDEX\"}"); + this.collection.createIndex("index3", "{\"fields\": [{\"field\": \"$.F5\", \"type\": \"INT\", \"required\": true}], \"type\" : \"INDEX\"}"); + this.collection.createIndex("index4", "{\"fields\": [{\"field\": \"$.F3\", \"type\": \"BIGINT\", \"required\": true}], \"type\" : \"INDEX\"}"); + + assertEquals((maxrec), this.collection.count()); + + assertThrows(XProtocolError.class, "ERROR 5115 \\(HY000\\) Document is missing a required field", + () -> this.collection.modify("CAST($.F5 as SIGNED) = 1").unset("$.ARR1[0]").sort("$._id").execute()); //dropping an empty string as collection + + // With Named parameter + docs = this.collection.find("CAST($.F5 as SIGNED) > :A AND CAST($.F5 as SIGNED) < :B").bind("B", 2).bind("A", -1).orderBy(" CAST($.F5 as SIGNED) asc ") + .fields("$.F5 as F5").execute(); + i = 1; + while (docs.hasNext()) { + doc = docs.next(); + assertEquals((long) (i), (long) (((JsonNumber) doc.get("F5")).getInteger())); + } + + /* Named Param */ + res = this.collection.remove("CAST($.F5 as SIGNED) > :A AND CAST($.F5 as SIGNED) < :B AND CAST($.F5 as SIGNED) > :C").bind("B", 2).bind("C", -2) + .bind("A", -1).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) ? and CAST($.F5 as SIGNED) < ?").bind(new Object[] { (-1), (3) }).orderBy("CAST($.F5 as SIGNED)") + .execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) params = new HashMap<>(); + params.put("namedParam10001", -1); + params.put("namedParam10000", 4); + + res = this.collection.remove("CAST($.F5 as SIGNED) < :namedParam10000 and CAST($.F5 as SIGNED) > :namedParam10001").bind(params) + .orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) :" + buildString(1000, 'Y') + " AND CAST($.F5 as SIGNED) <= :B AND CAST($.F5 as SIGNED) > :" + + buildString(1001, 'Y')) + .bind("B", 6).bind(buildString(1001, 'Y'), -2).bind(buildString(1000, 'Y'), -1).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) ldata = new ArrayList<>(); + ldata.add(-1); + ldata.add(7); + ldata.add(8); + ldata.add(-2147483648); + res = this.collection.remove("CAST($.F5 as SIGNED) > ? AND CAST($.F5 as SIGNED) <= ? AND CAST($.F5 as SIGNED) < ? AND CAST($.F5 as SIGNED) > ?") + .bind(ldata).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) 0) { + q = q + " and "; + } + q = q + "CAST($.F5 as SIGNED) < ?"; + } + + res = this.collection.remove(q).bind(ldata).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) 0) { + q = q + " AND "; + } + q = q + "CAST($.F5 as SIGNED) < :thePlaceholderName" + i + " "; + } + + res = this.collection.remove(q).bind(params).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) 0) { + q = q + " AND "; + } + q = q + "CAST($.F5 as SIGNED) < ? "; + } + + res = this.collection.remove(q).bind(adata).orderBy("CAST($.F5 as SIGNED)").execute(); + assertEquals(1, res.getAffectedItemsCount()); + docs = this.collection.find("CAST($.F5 as SIGNED) { + this.schema.dropCollection(null); + return null; + }); + try { + this.schema.dropCollection(null); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("must not be null")); + } + + // successfully dropped an invalid and null collection + + assertThrows(XProtocolError.class, "ERROR 5113 \\(HY000\\) Invalid collection name", () -> { + this.schema.dropCollection("");//dropping an empty string as collection + return null; + }); + + assertThrows(XProtocolError.class, "ERROR 1103 \\(42000\\) Incorrect table name ' '", () -> { + this.schema.dropCollection(" "); + return null; + }); + } } diff --git a/src/test/java/testsuite/x/devapi/CollectionTest.java b/src/test/java/testsuite/x/devapi/CollectionTest.java index c6eeab917..61ecc6da5 100644 --- a/src/test/java/testsuite/x/devapi/CollectionTest.java +++ b/src/test/java/testsuite/x/devapi/CollectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -29,37 +29,46 @@ package testsuite.x.devapi; +import static com.mysql.cj.xdevapi.Expression.expr; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; import com.mysql.cj.ServerVersion; import com.mysql.cj.exceptions.WrongArgumentException; import com.mysql.cj.protocol.x.XProtocolError; +import com.mysql.cj.xdevapi.AddResult; import com.mysql.cj.xdevapi.Collection; import com.mysql.cj.xdevapi.DatabaseObject.DbObjectStatus; import com.mysql.cj.xdevapi.DbDoc; import com.mysql.cj.xdevapi.DbDocImpl; +import com.mysql.cj.xdevapi.DocResult; import com.mysql.cj.xdevapi.JsonArray; import com.mysql.cj.xdevapi.JsonLiteral; import com.mysql.cj.xdevapi.JsonNumber; import com.mysql.cj.xdevapi.JsonString; +import com.mysql.cj.xdevapi.Result; import com.mysql.cj.xdevapi.Row; import com.mysql.cj.xdevapi.RowResult; +import com.mysql.cj.xdevapi.Schema; +import com.mysql.cj.xdevapi.Session; +import com.mysql.cj.xdevapi.SessionFactory; import com.mysql.cj.xdevapi.SqlResult; import com.mysql.cj.xdevapi.XDevAPIError; public class CollectionTest extends BaseCollectionTestCase { @Test public void testCount() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{'_id': '1', 'a':'a'}".replaceAll("'", "\"")).execute(); // Requires manual _id. this.collection.add("{'_id': '2', 'b':'b'}".replaceAll("'", "\"")).execute(); @@ -86,9 +95,6 @@ public Void call() throws Exception { @Test public void testGetSchema() { - if (!this.isSetForXTests) { - return; - } String collName = "testExists"; dropCollection(collName); Collection coll = this.schema.getCollection(collName); @@ -98,9 +104,6 @@ public void testGetSchema() { @Test public void testGetSession() { - if (!this.isSetForXTests) { - return; - } String collName = "testExists"; dropCollection(collName); Collection coll = this.schema.getCollection(collName); @@ -110,9 +113,6 @@ public void testGetSession() { @Test public void testExists() { - if (!this.isSetForXTests) { - return; - } String collName = "testExists"; dropCollection(collName); Collection coll = this.schema.getCollection(collName); @@ -124,9 +124,6 @@ public void testExists() { @Test public void getNonExistentCollectionWithRequireExistsShouldThrow() { - if (!this.isSetForXTests) { - return; - } String collName = "testRequireExists"; dropCollection(collName); assertThrows(WrongArgumentException.class, () -> this.schema.getCollection(collName, true)); @@ -134,9 +131,6 @@ public void getNonExistentCollectionWithRequireExistsShouldThrow() { @Test public void getNonExistentCollectionWithoutRequireExistsShouldNotThrow() { - if (!this.isSetForXTests) { - return; - } String collName = "testRequireExists"; dropCollection(collName); this.schema.getCollection(collName, false); @@ -144,9 +138,6 @@ public void getNonExistentCollectionWithoutRequireExistsShouldNotThrow() { @Test public void getExistentCollectionWithRequireExistsShouldNotThrow() { - if (!this.isSetForXTests) { - return; - } String collName = "testRequireExists"; dropCollection(collName); this.schema.createCollection(collName); @@ -155,9 +146,7 @@ public void getExistentCollectionWithRequireExistsShouldNotThrow() { @Test public void createIndex() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4")), "MySQL 8.0.4+ is required to run this test."); /* * WL#11208 - DevAPI: Collection.createIndex @@ -368,10 +357,6 @@ public Void call() throws Exception { @Test public void createArrayIndex() throws Exception { - if (!this.isSetForXTests) { - return; - } - /* * WL#12247 - DevAPI: indexing array fields */ @@ -654,14 +639,11 @@ private void validateIndex(String keyName, String collName, int sequence, boolea } else if (array && row.getString("Expression") != null) { String expr = row.getString("Expression"); int typePos = expr.indexOf(" as "); - if (typePos >= 0) { - expr = expr.substring(typePos + 4, expr.length() - 1); - assertTrue(expr.endsWith("array")); - expr = expr.substring(0, expr.indexOf(" array")); - assertEquals(dataType, expr); - } else { - fail("Not an array index?"); - } + assertTrue(typePos >= 0, "Not an array index?"); + expr = expr.substring(typePos + 4, expr.length() - 1); + assertTrue(expr.endsWith("array")); + expr = expr.substring(0, expr.indexOf(" array")); + assertEquals(dataType, expr); } else { fail("Unexpected type of index"); } @@ -677,4 +659,1850 @@ private void validateIndex(String keyName, String collName, int sequence, boolea throw new Exception("Index not found."); } } + + private void validateArrayIndex(String keydName, String collName, int noFields) throws Exception { + int indexFound = 0; + boolean arrayExpr = false; + + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + SqlResult res = sess.sql("show index from `" + collName + "`").execute(); + assertTrue(res.hasNext()); + + for (Row row : res.fetchAll()) { + if (keydName.equals(row.getString("Key_name"))) { + indexFound++; + assertEquals(collName, row.getString("Table")); + String expr = row.getString("Expression"); + System.out.println(expr); + if (expr != null) { + arrayExpr = true; + } + } + } + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + + if ((indexFound != noFields) || (!arrayExpr)) { + throw new Exception("Index not matching"); + } + + } + + /** + * START testArrayIndexBasic tests + * + * @throws Exception + */ + @Test + public void testArrayIndex001() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + int i = 0; + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": true}]}"); + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": true}]}"); + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": true}]}"); + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": true}]}"); + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true}]}"); + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": true}]}"); + + validateArrayIndex("intArrayIndex", "coll1", 1); + validateArrayIndex("uintArrayIndex", "coll1", 1); + validateArrayIndex("floatArrayIndex", "coll1", 1); + validateArrayIndex("dateArrayIndex", "coll1", 1); + validateArrayIndex("datetimeArrayIndex", "coll1", 1); + validateArrayIndex("timeArrayIndex", "coll1", 1); + validateArrayIndex("charArrayIndex", "coll1", 1); + validateArrayIndex("binaryArrayIndex", "coll1", 1); + + coll.remove("true").execute(); + + coll.add( + "{\"intField\" : [1,2,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-29 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.1,52.9,53.0]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[1,2,3]\", \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + DocResult docs = coll.find(":intField in $.intField").bind("intField", 12).execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + + assertTrue(i == 2); + + docs = coll.find(":uintField in $.uintField").bind("uintField", 52).execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + + assertTrue(i == 3); + + docs = coll.find(":charField in $.charField").bind("charField", "abcd1").execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + + assertTrue(i == 3); + + docs = coll.find(":binaryField in $.binaryField").bind("binaryField", "abcd1").execute(); + i = 0; + while (docs.hasNext()) { + docs.next(); + i++; + } + + assertTrue(i == 3); + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + /** + * START testArrayIndexBasic tests + * + * @throws Exception + */ + + //@Test + public void testArrayIndex002() throws Exception { + System.out.println("testCreateIndexSanity"); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": true, \"required\": true}]}"); + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": true, \"required\": true}]}"); + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true, \"required\": true}]}"); + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": true, \"required\": true}]}"); + + validateArrayIndex("intArrayIndex", "coll1", 1); + validateArrayIndex("dateArrayIndex", "coll1", 1); + validateArrayIndex("charArrayIndex", "coll1", 1); + validateArrayIndex("timeArrayIndex", "coll1", 1); + + coll.add( + "{\"intField\" : [1,2,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + + try { + coll.add( + "{\"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Document is missing a required field")); + } + + try { + coll.add( + "{\"intField\" : [1,2,3], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Document is missing a required field")); + } + + try { + coll.add( + "{\"intField\" : [11,12,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Document is missing a required field")); + } + + try { + coll.add( + "{\"intField\" : [11,12,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Document is missing a required field")); + } + + try { + coll.add( + "{\"intField\" : \"[12,23,34]\", \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + try { + coll.add( + "{\"intField\" : 12, \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex003() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + try { + coll.createIndex("multiArrayIndex", + "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": true}, {\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": true}, {\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true}, {\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": true}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("This version of MySQL doesn't yet support")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex004() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + String indexString = "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}," + + "{\"field\": \"$.charField\", \"type\": \"CHAR(255)\", \"array\": false}," + + "{\"field\": \"$.decimalField\", \"type\": \"DECIMAL\", \"array\": false}," + + "{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": false}," + + "{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": false}," + + "{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": false}]}"; + + coll.createIndex("multiArrayIndex", indexString); + + //coll.createIndex("multiArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"INT\", \"array\": false}, {\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": false}, {\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true}, {\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": false}]}"); + + validateArrayIndex("multiArrayIndex", "coll1", 6); + + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : [12,25,34], \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : [12,23,35], \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : [18,23,34], \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + + try { + coll.add( + "{\"intField\" : [12,23,34], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : \"10.30\"}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Incorrect date value")); + } + + try { + coll.add( + "{\"intField\" : 35, \"dateField\" : \"2019-1-1\", \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Incorrect time value")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex005() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("multiArrayIndex", + "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"INTEGER\", \"array\": false}," + + "{\"field\": \"$.charField\", \"type\": \"CHAR(255)\", \"array\": true}," + + "{\"field\": \"$.decimalField\", \"type\": \"DECIMAL\", \"array\": false}," + + "{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": false}," + + "{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": false}," + + "{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": false}]}"); + + validateArrayIndex("multiArrayIndex", "coll1", 6); + + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : [\"abcd1\", \"abcd2\", \"abcd3\", \"abcd4\"], \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : [\"abcd1\", \"abcd2\", \"abcd3\", \"abcd4\"], \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : [\"abcd1\", \"abcd2\", \"abcd3\", \"abcd4\"], \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : 18, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : [\"abcd1\", \"abcd2\", \"abcd3\", \"abcd4\"], \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[12,23,34]\", \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Invalid JSON value for CAST to INTEGER from column json_extract at row")); + } + + try { + coll.add( + "{\"intField\" : 12, \"dateField\" : \"2019-1-1\", \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : \"10.30\"}") + .execute(); + // Behavior documented : assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Incorrect char value")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex006() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("multiArrayIndex", + "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"INTEGER\", \"array\": false}," + + "{\"field\": \"$.charField\", \"type\": \"CHAR(255)\", \"array\": false}," + + "{\"field\": \"$.decimalField\", \"type\": \"DECIMAL\", \"array\": true}," + + "{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": false}," + + "{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": false}," + + "{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": false}]}"); + + validateArrayIndex("multiArrayIndex", "coll1", 6); + + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : [51.2, 57.6, 55.8]}") + .execute(); + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : [51.2, 57.6, 55.8]}") + .execute(); + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : [51.2, 57.6, 55.8]}") + .execute(); + coll.add( + "{\"intField\" : 18, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : \"9999-12-31 23:59:59\", \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : [51.2, 57.6, 55.8]}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[12,23,34]\", \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Invalid JSON value for CAST to INTEGER from column json_extract at row")); + } + + try { + coll.add( + "{\"intField\" : 12, \"dateField\" : \"2019-1-1\", \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Incorrect time value")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex007() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("multiArrayIndex", + "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"INTEGER\", \"array\": false}," + + "{\"field\": \"$.charField\", \"type\": \"CHAR(255)\", \"array\": false}," + + "{\"field\": \"$.decimalField\", \"type\": \"DECIMAL\", \"array\": false}," + + "{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": false}," + + "{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": false}," + + "{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": true}]}"); + + validateArrayIndex("multiArrayIndex", "coll1", 6); + + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : 12, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + coll.add( + "{\"intField\" : 18, \"uintField\" : [51,52,53], \"dateField\" : \"2019-1-1\", \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : \"abcd1\", \"binaryField\" : \"abcd1\", \"timeField\" : \"10.30\", \"decimalField\" : 51.2}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[12,23,34]\", \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Invalid JSON value for CAST to INTEGER from column json_extract at row")); + } + + try { + coll.add( + "{\"intField\" : 12, \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Incorrect date value")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex008() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + try { + coll.createIndex("textArrayIndex", "{\"fields\": [{\"field\": \"$.textField\", \"type\": \"TEXT\", \"array\": true}]}"); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("index")); + } + + try { + coll.createIndex("boolArrayIndex", "{\"fields\": [{\"field\": \"$.boolField\", \"type\": \"BOOL\", \"array\": true}]}"); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("index")); + } + + try { + coll.createIndex("blobIndex", "{\"fields\": [{\"field\": \"$.blobField\", \"type\": \"BLOB\", \"array\": true}]}"); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("index")); + } + + try { + coll.createIndex("sintIndex", "{\"fields\": [{\"field\": \"$.sinField\", \"type\": \"SMALLINT\", \"array\": true}]}"); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("index")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex009() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"UNSIGNED\", \"array\" : true}], \"type\" : \"INDEX\"}"); + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\" : true}], \"type\" : \"INDEX\"}"); + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(255)\", \"array\" : true}], \"type\" : \"INDEX\"}"); + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\" : true}], \"type\" : \"INDEX\"}"); + + validateArrayIndex("intArrayIndex", "coll1", 1); + validateArrayIndex("dateArrayIndex", "coll1", 1); + validateArrayIndex("charArrayIndex", "coll1", 1); + validateArrayIndex("timeArrayIndex", "coll1", 1); + + coll.add( + "{\"intField\" : [1,2,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [1,2,3], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"]}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[12,23,34]\", \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex010() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + try { + coll.createIndex("multiArrayIndex1", "{\"fields\": [{\"field\": \"$.charField\", \"type\":\"CHAR(128)\", \"array\": true}," + + "{\"field\": \"$.binaryField\", \"type\":\"BINARY(128)\"}," + "{\"field\": \"$.intField\", \"type\":\"SIGNED INTEGER\"}," + + "{\"field\": \"$.intField2\", \"type\":\"SIGNED\"}," + "{\"field\": \"$.uintField\", \"type\":\"UNSIGNED\"}," + + "{\"field\": \"$.uintField2\", \"type\":\"UNSIGNED INTEGER\"}," + "{\"field\": \"$.dateField\", \"type\":\"DATE\"}," + + "{\"field\": \"$.datetimeField\", \"type\":\"DATETIME\"}," + "{\"field\": \"$.decimalField\", \"type\":\"DECIMAL(20,9)\"}" + "]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Invalid or unsupported type specification 'BINARY(128)'")); + } + + coll.add("{\"intField\" : 1, \"dateField\" : \"2019-3-1\", \"charField\" : \"abcd1\", \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add("{\"intField\" : 2, \"dateField\" : \"2019-5-1\", \"charField\" : \"abcd2\", \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + coll.add("{\"intField\" : 3, \"dateField\" : \"2019-7-1\", \"charField\" : \"abcd3\", \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"]}") + .execute(); + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex011() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": true}]}"); + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": true}]}"); + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": true}]}"); + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": true}]}"); + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true}]}"); + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": true}]}"); + + validateArrayIndex("intArrayIndex", "coll1", 1); + validateArrayIndex("uintArrayIndex", "coll1", 1); + validateArrayIndex("floatArrayIndex", "coll1", 1); + validateArrayIndex("dateArrayIndex", "coll1", 1); + validateArrayIndex("datetimeArrayIndex", "coll1", 1); + validateArrayIndex("timeArrayIndex", "coll1", 1); + validateArrayIndex("charArrayIndex", "coll1", 1); + validateArrayIndex("binaryArrayIndex", "coll1", 1); + + coll.add( + "{\"intField\" : [], \"uintField\" : [], \"dateField\" : [], \"datetimeField\" : [], \"charField\" : [], \"binaryField\" : [],\"timeField\" : [], \"floatField\" : []}") + .execute(); + coll.add( + "{\"intField\" : [], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-29 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.1,52.9,53.0]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : []}") + .execute(); + + try { + coll.add( + "{\"intField\" : [1,[2, 3],4], \"uintField\" : [51,[52, 53],54], \"dateField\" : [\"2019-1-1\", [\"2019-2-1\", \"2019-3-1\"], \"2019-4-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", [\"9999-12-31 23:59:59\"], \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", [\"abcd1\", \"abcd2\"], \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,[52.4],53.6]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Cannot store an array or an object in a scalar key part of the index")); + } + + try { + coll.add( + "{\"intField\" : \"\", \"uintField\" : [51,[52, 53],54], \"dateField\" : [\"2019-1-1\", [\"2019-2-1\", \"2019-3-1\"], \"2019-4-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + if (mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.18"))) { + assertTrue(e.getMessage().contains("Cannot store an array or an object in a scalar key part of the index")); + } else { + assertTrue(e.getMessage().contains("functional index")); + } + } + + try { + coll.add( + "{\"intField\" : [1,5,4], \"dateField\" : \"\", \"datetimeField\" : \"\", \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : \"\", \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + try { + coll.add( + "{\"intField\" : [1,5,4], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\", \"2019-4-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : \"\", \"binaryField\" : \"\", \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + /** + * START testArrayIndexBasic tests + * + * @throws Exception + */ + @Test + public void testArrayIndex012() throws Exception { + System.out.println("testCreateIndexSanity"); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + try { + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": \"\"}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": null}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + try { + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": []}]}"); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("Index field 'array' member must be boolean.")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex013() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + coll.add( + "{\"intField\" : [1,2,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-29 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.1,52.9,53.0]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + + /* create basic index */ + + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": true}]}"); + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": true}]}"); + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": true}]}"); + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": true}]}"); + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true}]}"); + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": true}]}"); + + validateArrayIndex("intArrayIndex", "coll1", 1); + validateArrayIndex("uintArrayIndex", "coll1", 1); + validateArrayIndex("floatArrayIndex", "coll1", 1); + validateArrayIndex("dateArrayIndex", "coll1", 1); + validateArrayIndex("datetimeArrayIndex", "coll1", 1); + validateArrayIndex("timeArrayIndex", "coll1", 1); + validateArrayIndex("charArrayIndex", "coll1", 1); + validateArrayIndex("binaryArrayIndex", "coll1", 1); + + coll.remove("true").execute(); + + coll.add( + "{\"intField\" : [1,2,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-29 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.1,52.9,53.0]}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6]}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[1,2,3]\", \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex014() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + DbDoc doc = null; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.createIndex("intArrayIndex", "{\"fields\": [{\"field\": \"$.intField\", \"type\": \"SIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("uintArrayIndex", "{\"fields\": [{\"field\": \"$.uintField\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("floatArrayIndex", "{\"fields\": [{\"field\": \"$.floatField\", \"type\": \"DECIMAL(10,2)\", \"array\": true}]}"); + coll.createIndex("dateArrayIndex", "{\"fields\": [{\"field\": \"$.dateField\", \"type\": \"DATE\", \"array\": true}]}"); + coll.createIndex("datetimeArrayIndex", "{\"fields\": [{\"field\": \"$.datetimeField\", \"type\": \"DATETIME\", \"array\": true}]}"); + coll.createIndex("timeArrayIndex", "{\"fields\": [{\"field\": \"$.timeField\", \"type\": \"TIME\", \"array\": true}]}"); + coll.createIndex("charArrayIndex", "{\"fields\": [{\"field\": \"$.charField\", \"type\": \"CHAR(256)\", \"array\": true}]}"); + coll.createIndex("binaryArrayIndex", "{\"fields\": [{\"field\": \"$.binaryField\", \"type\": \"BINARY(256)\", \"array\": true}]}"); + + validateArrayIndex("intArrayIndex", "coll1", 1); + validateArrayIndex("uintArrayIndex", "coll1", 1); + validateArrayIndex("floatArrayIndex", "coll1", 1); + validateArrayIndex("dateArrayIndex", "coll1", 1); + validateArrayIndex("datetimeArrayIndex", "coll1", 1); + validateArrayIndex("timeArrayIndex", "coll1", 1); + validateArrayIndex("charArrayIndex", "coll1", 1); + validateArrayIndex("binaryArrayIndex", "coll1", 1); + + coll.remove("true").execute(); + + coll.add( + "{\"intField\" : [1,2,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6],\"dateFieldWOI\" : \"2019-1-1\"}") + .execute(); + coll.add( + "{\"intField\" : [11,12,3], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-29 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.1,52.9,53.0],\"dateFieldWOI\" : \"2019-1-1\"}") + .execute(); + coll.add( + "{\"intField\" : [12,23,34], \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-7 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"timeField\" : [\"10.30\", \"7.30\", \"12.30\"], \"floatField\" : [51.2,52.7,53.6],\"dateFieldWOI\" : \"2019-2-1\"}") + .execute(); + + try { + coll.add( + "{\"intField\" : \"[1,2,3]\", \"uintField\" : [51,52,53], \"dateField\" : [\"2019-1-1\", \"2019-2-1\", \"2019-3-1\"], \"datetimeField\" : [\"9999-12-30 23:59:59\", \"9999-12-31 23:59:59\", \"9999-12-31 23:59:59\"], \"charField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"], \"binaryField\" : [\"abcd1\", \"abcd1\", \"abcd2\", \"abcd4\"],\"timeField\" : [\"10.30\", \"11.30\", \"12.30\"], \"floatField\" : [51.2,52.4,53.6]}") + .execute(); + assertTrue(false); + } catch (Exception e) { + System.out.println("ERROR : " + e.getMessage()); + assertTrue(e.getMessage().contains("functional index")); + } + + DocResult docs = coll.find(":intField in $.intField").bind("intField", 12).execute(); + doc = null; + int i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + assertTrue(i == 2); + + docs = coll.find(":uintField in $.uintField").bind("uintField", 52).execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + assertTrue(i == 3); + + docs = coll.find(":charField in $.charField").bind("charField", "abcd1").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + assertTrue(i == 3); + + docs = coll.find(":binaryField in $.binaryField").bind("binaryField", "abcd1").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + assertTrue(i == 3); + + docs = coll.find(":floatField in $.floatField").bind("floatField", 51.2).execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + System.out.println("Count = " + i); + assertTrue(i == 2); + + docs = coll.find("CAST(CAST('2019-2-1' as DATE) as JSON) in $.dateField").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + System.out.println("Count = " + i); + assertTrue(i == 3); + + docs = coll.find("CAST(CAST('2019-2-1' as DATE) as JSON) not in $.dateField").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + System.out.println("Using NOT IN"); + System.out.println("Count = " + i); + //assertTrue(i == 0); + + docs = coll.find("'2019-1-1' not in $.dateFieldWOI").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + System.out.println((((JsonString) doc.get("dateFieldWOI")).getString())); + i++; + } + System.out.println("Using NOT IN Without Index"); + System.out.println("Count = " + i); + + docs = coll.find("CAST(CAST('2019-2-1' as DATE) as JSON) overlaps $.dateField").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + System.out.println("Count = " + i); + assertTrue(i == 3); + + docs = coll.find("CAST(CAST('2019-2-1' as DATE) as JSON) not overlaps $.dateField").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + System.out.println("Using NOT OVERLAPS"); + System.out.println("Count = " + i); + //assertTrue(i == 0); + + docs = coll.find("CAST(CAST(:datetimeField as DATETIME) as JSON) in $.datetimeField").bind("datetimeField", "9999-12-30 23:59:59").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + System.out.println("Count = " + i); + assertTrue(i == 2); + + docs = coll.find("CAST(CAST(:timeField as TIME) as JSON) in $.timeField").bind("timeField", "7.30").execute(); + doc = null; + i = 0; + while (docs.hasNext()) { + doc = docs.next(); + i++; + } + + System.out.println("Count = " + i); + assertTrue(i == 1); + + //Integration scenarios between Index and Overlaps. Explicit casting added due to server Bug#29752056. NOT IN and NOT OVERLAPS doesn't require explicit casting + docs = coll.find("CAST(CAST('2019-2-1' as DATE) as JSON) in $.dateField").execute(); + //System.out.println("Number of rows using IN with indexed array = "+docs.count()); + assertTrue(docs.count() == 3); + + docs = coll.find("'2019-2-1' not in $.dateField").execute(); + //System.out.println("Number of rows using NOT IN without casting = "+docs.count()); + assertTrue(docs.count() == 0); + + docs = coll.find("CAST(CAST('2019-2-1' as DATE) as JSON) overlaps $.dateField").execute(); + //System.out.println("Number of rows using OVERLAPS with indexed array = "+docs.count()); + assertTrue(docs.count() == 3); + + docs = coll.find("'2019-2-1' not overlaps $.dateField").execute(); + //System.out.println("Number of rows using NOT OVERLAPS without casting = "+docs.count()); + assertTrue(docs.count() == 0); + + //Integration scenarios for time + docs = coll.find("CAST(CAST(:timeField as TIME) as JSON) in $.timeField").bind("timeField", "7.30").execute(); + assertTrue(docs.count() == 1); + + docs = coll.find(":timeField not in $.timeField").bind("timeField", "7.30").execute(); + assertTrue(docs.count() == 2); + + docs = coll.find("CAST(CAST(:timeField as TIME) as JSON) overlaps $.timeField").bind("timeField", "7.30").execute(); + assertTrue(docs.count() == 1); + + docs = coll.find(":timeField not overlaps $.timeField").bind("timeField", "7.30").execute(); + assertTrue(docs.count() == 2); + + //Integration scenarios for datetime + docs = coll.find("CAST(CAST(:datetimeField as DATETIME) as JSON) in $.datetimeField").bind("datetimeField", "9999-12-30 23:59:59").execute(); + assertTrue(docs.count() == 2); + + docs = coll.find(":datetimeField NOT IN $.datetimeField").bind("datetimeField", "9999-12-30 23:59:59").execute(); + assertTrue(docs.count() == 1); + + docs = coll.find("CAST(CAST(:datetimeField as DATETIME) as JSON) OVERLAPS $.datetimeField").bind("datetimeField", "9999-12-30 23:59:59").execute(); + assertTrue(docs.count() == 2); + + docs = coll.find(":datetimeField NOT OVERLAPS $.datetimeField").bind("datetimeField", "9999-12-30 23:59:59").execute(); + assertTrue(docs.count() == 1); + + //Integration scenaris of Integer + docs = coll.find(":intField not in $.intField").bind("intField", 12).execute(); + assertTrue(docs.count() == 1); + + docs = coll.find(":intField overlaps $.intField").bind("intField", 12).execute(); + assertTrue(docs.count() == 2); + + docs = coll.find(":intField not overlaps $.intField").bind("intField", 12).execute(); + assertTrue(docs.count() == 1); + + //Integration scenaris of unsigned integer + docs = coll.find(":uintField not in $.uintField").bind("uintField", 52).execute(); + assertTrue(docs.count() == 0); + + docs = coll.find(":uintField overlaps $.uintField").bind("uintField", 52).execute(); + assertTrue(docs.count() == 3); + + docs = coll.find(":uintField not overlaps $.uintField").bind("uintField", 52).execute(); + assertTrue(docs.count() == 0); + + //Integration scenaris of character type + docs = coll.find(":charField not in $.charField").bind("charField", "abcd1").execute(); + assertTrue(docs.count() == 0); + + docs = coll.find(":charField overlaps $.charField").bind("charField", "abcd1").execute(); + assertTrue(docs.count() == 3); + + docs = coll.find(":charField not overlaps $.charField").bind("charField", "abcd1").execute(); + assertTrue(docs.count() == 0); + + //Integration scenarios of binary type + docs = coll.find(":binaryField not in $.binaryField").bind("binaryField", "abcd1").execute(); + assertTrue(docs.count() == 0); + + docs = coll.find(":binaryField overlaps $.binaryField").bind("binaryField", "abcd1").execute(); + assertTrue(docs.count() == 3); + + docs = coll.find(":binaryField not overlaps $.binaryField").bind("binaryField", "abcd1").execute(); + assertTrue(docs.count() == 0); + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testArrayIndex015() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(this.baseUrl, ServerVersion.parseVersion("8.0.17")), "MySQL 8.0.17+ is required to run this test."); + + String collname = "coll1"; + DbDoc doc = null; + Session sess = null; + try { + sess = new SessionFactory().getSession(this.baseUrl); + Schema sch = sess.getDefaultSchema(); + sch.dropCollection(collname); + Collection coll = sch.createCollection(collname, true); + + /* create basic index */ + + coll.remove("true").execute(); + + coll.add( + "{\"_id\":1,\"name\":\"decimalArray1\",\"decimalField1\":[835975.76,87349829932749.67,89248481498149882498141.12],\"decimalField2\":[835975.76,87349829932.839],\"decimalField3\":[835977.76,87349829932.839]}") + .execute(); + coll.add( + "{\"_id\":2,\"name\":\"dateArray1\",\"dateField1\" : [\"2017-12-12\",\"2018-12-12\",\"2019-12-12\"],\"dateField2\" : [\"2017-12-12\",\"2018-11-11\",\"2019-11-11\"],\"dateField3\" : [\"2017-12-12\",\"2018-10-10\",\"2019-10-10\"]}") + .execute(); + coll.add( + "{\"_id\":3,\"name\":\"timeArray1\",\"timeField1\" : [\"12:20\",\"11:20\",\"10:20\"], \"timeField2\" : [\"12:00\",\"11:00\",\"10:20\"], \"timeField3\" : [\"12:10\",\"11:10\",\"10:00\"]}") + .execute(); + coll.add( + "{\"_id\":4,\"name\":\"timestampArray1\",\"timestampField1\" : [\"2017-12-12 20:12:07\", \"2018-12-12 20:12:07\",\"2019-12-12 20:12:07\"], \"timestampField2\" : [\"2017-12-12 20:12:07\", \"2018-11-11 20:12:07\",\"2019-11-11 20:12:07\"], \"timestampField3\" : [\"2017-12-12 20:12:07\", \"2018-10-11 20:12:07\",\"2019-12-12 20:12:07\"]}") + .execute(); + coll.add( + "{\"_id\":5,\"name\":\"datetimeArray1\", \"datetimeField1\" : [\"2017-12-12 20:12:07\", \"2018-12-12 20:12:07\",\"2019-12-12 20:12:07\"], \"datetimeField2\" : [\"2017-12-12 20:12:07\", \"2018-11-11 20:12:07\",\"2019-11-11 20:12:07\"],\"datetimeField3\" : [\"2017-10-10 20:12:07\", \"2018-10-10 20:12:07\",\"2019-10-10 20:12:07\"]}") + .execute(); + coll.add( + "{\"_id\":6,\"name\":\"binaryArray1\", \"binaryField1\":[\"0xe240\",\"0x0001e240\"],\"binaryField2\":[\"0xe240\",\"0x0001e240\"],\"binaryField3\":[\"0xe240\",\"0x0001e240\"]}") + .execute(); + coll.add( + "{\"_id\":7,\"name\":\"dateArray2\",\"dateField1\" : [\"2017-12-12\",\"2018-12-12\",\"2019-12-12\"],\"dateField2\" : [\"2017-11-11\",\"2018-11-11\",\"2019-11-11\"],\"dateField3\" : [\"2017-10-10\",\"2018-10-10\",\"2019-10-10\"]}") + .execute(); + coll.add( + "{\"_id\":8,\"name\":\"timeArray2\",\"timeField1\" : [\"12:20\",\"11:20\",\"10:20\"], \"timeField2\" : [\"12:00\",\"11:00\",\"10:00\"], \"timeField3\" : [\"12:10\",\"11:10\",\"10:10\"]}") + .execute(); + coll.add( + "{\"_id\":9,\"name\":\"datetimeArray2\", \"datetimeField1\" : [\"2017-12-12 20:12:07\", \"2018-12-12 20:12:07\",\"2019-12-12 20:12:07\"], \"datetimeField2\" : [\"2017-11-11 20:12:07\", \"2018-11-11 20:12:07\",\"2019-11-11 20:12:07\"],\"datetimeField3\" : [\"2017-10-10 20:12:07\", \"2018-10-10 20:12:07\",\"2019-10-10 20:12:07\"]}") + .execute(); + coll.add( + "{\"_id\":10,\"name\":\"binaryArray2\", \"binaryField1\":[\"0xe240\",\"0x0001e240\"],\"binaryField2\":[\"0xe2040\",\"0x0001e2040\"],\"binaryField3\":[\"0xe02040\",\"0x0001e02040\"]}") + .execute(); + coll.add( + "{\"_id\":11,\"name\":\"charArray1\", \"charField1\":[\"char1\",\"char2\"],\"charField2\":[\"char1\",\"char3\"],\"charField3\":[\"char1\",\"char2\"]}") + .execute(); + coll.add( + "{\"_id\":12,\"name\":\"charArray2\", \"charField1\":[\"char1\",\"char2\"],\"charField2\":[\"char3\",\"char4\"],\"charField3\":[\"char5\",\"char6\"]}") + .execute(); + coll.add("{\"_id\":13,\"name\":\"intArray1\", \"intField1\":[-15,-25],\"intField2\":[-15,-20],\"intField3\":[-10,-20]}").execute(); + coll.add("{\"_id\":14,\"name\":\"intArray2\", \"intField1\":[-10,-20],\"intField2\":[-30,-40],\"intField3\":[-50,-60]}").execute(); + + coll.add("{\"_id\":15,\"name\":\"uintArray1\", \"uintField1\":[15,25],\"uintField2\":[15,20],\"uintField3\":[10,20]}").execute(); + coll.add("{\"_id\":16,\"name\":\"uintArray2\", \"uintField1\":[10,20],\"uintField2\":[30,40],\"uintField3\":[50,60]}").execute(); + + coll.createIndex("intArrayIndex1", "{\"fields\": [{\"field\": \"$.intField1\", \"type\": \"SIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("intArrayIndex2", "{\"fields\": [{\"field\": \"$.intField2\", \"type\": \"SIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("uintArrayIndex1", "{\"fields\": [{\"field\": \"$.uintField1\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("uintArrayIndex2", "{\"fields\": [{\"field\": \"$.uintField2\", \"type\": \"UNSIGNED INTEGER\", \"array\": true}]}"); + coll.createIndex("decimalArrayIndex1", "{\"fields\": [{\"field\": \"$.decimalField1\", \"type\": \"DECIMAL(65,2)\", \"array\": true}]}"); + coll.createIndex("decimalArrayIndex2", "{\"fields\": [{\"field\": \"$.decimalField2\", \"type\": \"DECIMAL(65,2)\", \"array\": true}]}"); + coll.createIndex("dateArrayIndex1", "{\"fields\": [{\"field\": \"$.dateField1\", \"type\": \"DATE\", \"array\": true}]}"); + coll.createIndex("dateArrayIndex2", "{\"fields\": [{\"field\": \"$.dateField2\", \"type\": \"DATE\", \"array\": true}]}"); + coll.createIndex("datetimeArrayIndex1", "{\"fields\": [{\"field\": \"$.datetimeField1\", \"type\": \"DATETIME\", \"array\": true}]}"); + coll.createIndex("datetimeArrayIndex2", "{\"fields\": [{\"field\": \"$.datetimeField2\", \"type\": \"DATETIME\", \"array\": true}]}"); + coll.createIndex("timeArrayIndex1", "{\"fields\": [{\"field\": \"$.timeField1\", \"type\": \"TIME\", \"array\": true}]}"); + coll.createIndex("timeArrayIndex2", "{\"fields\": [{\"field\": \"$.timeField2\", \"type\": \"TIME\", \"array\": true}]}"); + coll.createIndex("charArrayIndex1", "{\"fields\": [{\"field\": \"$.charField1\", \"type\": \"CHAR(256)\", \"array\": true}]}"); + coll.createIndex("charArrayIndex2", "{\"fields\": [{\"field\": \"$.charField2\", \"type\": \"CHAR(256)\", \"array\": true}]}"); + coll.createIndex("binaryArrayIndex1", "{\"fields\": [{\"field\": \"$.binaryField1\", \"type\": \"BINARY(256)\", \"array\": true}]}"); + coll.createIndex("binaryArrayIndex2", "{\"fields\": [{\"field\": \"$.binaryField2\", \"type\": \"BINARY(256)\", \"array\": true}]}"); + validateArrayIndex("intArrayIndex1", "coll1", 1); + validateArrayIndex("uintArrayIndex1", "coll1", 1); + validateArrayIndex("decimalArrayIndex1", "coll1", 1); + validateArrayIndex("dateArrayIndex1", "coll1", 1); + validateArrayIndex("datetimeArrayIndex1", "coll1", 1); + validateArrayIndex("timeArrayIndex1", "coll1", 1); + validateArrayIndex("charArrayIndex1", "coll1", 1); + validateArrayIndex("binaryArrayIndex1", "coll1", 1); + validateArrayIndex("intArrayIndex2", "coll1", 1); + validateArrayIndex("uintArrayIndex2", "coll1", 1); + validateArrayIndex("decimalArrayIndex2", "coll1", 1); + validateArrayIndex("dateArrayIndex2", "coll1", 1); + validateArrayIndex("datetimeArrayIndex2", "coll1", 1); + validateArrayIndex("timeArrayIndex2", "coll1", 1); + validateArrayIndex("charArrayIndex2", "coll1", 1); + validateArrayIndex("binaryArrayIndex2", "coll1", 1); + + DocResult docs = coll.find("$.intField1 overlaps $.intField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("intArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.intField1 not overlaps $.intField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("intArray2", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.uintField1 overlaps $.uintField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("uintArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.uintField1 not overlaps $.uintField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("uintArray2", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.charField1 overlaps $.charField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("charArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.charField1 not overlaps $.charField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("charArray2", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.decimalField1 overlaps $.decimalField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("decimalArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.decimalField1 not overlaps $.decimalField2").execute(); + assertTrue(docs.count() == 0); + + docs = coll.find("$.binaryField1 overlaps $.binaryField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("binaryArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.binaryField1 not overlaps $.binaryField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("binaryArray2", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.timeField1 overlaps $.timeField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("timeArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.timeField1 not overlaps $.timeField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("timeArray2", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.datetimeField1 overlaps $.datetimeField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("datetimeArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.datetimeField1 not overlaps $.datetimeField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("datetimeArray2", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.dateField1 overlaps $.dateField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("dateArray1", ((JsonString) doc.get("name")).getString()); + + docs = coll.find("$.dateField1 not overlaps $.dateField2").execute(); + assertTrue(docs.count() == 1); + doc = docs.next(); + assertEquals("dateArray2", ((JsonString) doc.get("name")).getString()); + + sch.dropCollection(collname); + } finally { + if (sess != null) { + sess.close(); + sess = null; + } + } + } + + @Test + public void testAsyncBind() throws Exception { + int i = 0, maxrec = 10; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf((100 + i) * 10))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(100 + i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(1 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + + CompletableFuture asyncRes = this.collection.add(jsonlist).executeAsync(); + asyncRes.get(); + + // 1. Incorrect PlaceHolder Name in bind + assertThrows(WrongArgumentException.class, "Unknown placeholder: F", () -> this.collection.find("F1 like :X").bind("F", "Field-1-%-3").executeAsync()); + + // 2. PlaceHolder Name in small letter + assertThrows(WrongArgumentException.class, "Unknown placeholder: x", () -> this.collection.find("F1 like :X").bind("x", "Field-1-%-3").executeAsync()); + + // 3. No bind + assertThrows(WrongArgumentException.class, "Placeholder 'X' is not bound", () -> this.collection.find("F1 like :X").executeAsync()); + + // 4. bind 2 times + assertThrows(WrongArgumentException.class, "Unknown placeholder: x", + () -> this.collection.find("F1 like :X").bind("X", "Field-1-%-4").bind("x", "Field-1-%-3").bind("X", "Field-1-%-3").executeAsync()); + + // 5. bind same variable 2 times (Success) + CompletableFuture asyncDocs = this.collection.find("F1 like :X").bind("X", "Field-1-%-4").bind("X", "Field-1-%-3").executeAsync(); + + DocResult docs = asyncDocs.get(); + DbDoc doc = docs.next(); + assertEquals((long) 103, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertFalse(docs.hasNext()); + + // 6. bind In different order (Success) + asyncDocs = this.collection.find("F1 like :X and $.F3 =:Y and $.F3 !=:Z").bind("Y", 103).bind("Z", 104).bind("X", "Field-1-%-3").executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) 103, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertFalse(docs.hasNext()); + + // 7. Using same Bind Variables many times(Success) + asyncDocs = this.collection.find("F1 like :F1 and $.F3 in (:F3,:F2,:F3) and $.F2 =(:F3*10)").bind("F3", 103).bind("F2", 102).bind("F1", "Field-1-%-3") + .executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) 103, (long) (((JsonNumber) doc.get("F3")).getInteger())); + assertFalse(docs.hasNext()); + + // 1. Incorrect PlaceHolder Name in bind + assertThrows(WrongArgumentException.class, "Unknown placeholder: F", + () -> this.collection.modify("F1 like :X").set("$.F4", 1).bind("F", "Field-1-%-3").executeAsync()); + + // 2. PlaceHolder Name in small letter + assertThrows(WrongArgumentException.class, "Unknown placeholder: x", + () -> this.collection.modify("F1 like :X").set("$.F4", 1).bind("x", "Field-1-%-3").executeAsync()); + + // 3. No bind + assertThrows(WrongArgumentException.class, "Placeholder 'X' is not bound", () -> this.collection.modify("F1 like :X").set("$.F4", 1).executeAsync()); + + // 4. bind 2 times + assertThrows(WrongArgumentException.class, "Unknown placeholder: x", () -> this.collection.modify("F1 like :X").set("$.F4", 1).bind("X", "Field-1-%-4") + .bind("x", "Field-1-%-3").bind("X", "Field-1-%-3").executeAsync()); + + // 5. bind same variable 2 times (Success) + CompletableFuture asyncRes2 = this.collection.modify("F1 like :X").set("$.F4", -1).bind("X", "Field-1-%-4").bind("X", "Field-1-%-3") + .executeAsync(); + Result res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + // 6. bind In different order (Success) + asyncRes2 = this.collection.modify("F1 like :X and $.F3 =:Y and $.F3 !=:Z").set("$.F4", -2).bind("Y", 103).bind("Z", 104).bind("X", "Field-1-%-3") + .executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + // 7. Using same Bind Variables many times(Success) + asyncRes2 = this.collection.modify("F1 like :F1 and $.F3 in (:F3,:F2,:F3) and $.F2 =(:F3*10)").set("$.F4", -3).bind("F3", 103).bind("F2", 102) + .bind("F1", "Field-1-%-3").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + asyncRes2 = this.collection.modify("$.F3 = :X").set("$.F4", 1).bind("X", 101).sort("$.F1 asc").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + asyncRes2 = this.collection.modify("$.F3 = :X+:Y+:X+:Y").set("$.F4", -4).bind("X", 50).bind("Y", 2).sort("$.F1 asc").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + // 1. Incorrect PlaceHolder Name in bind + assertThrows(WrongArgumentException.class, "Unknown placeholder: F", + () -> this.collection.remove("F1 like :X").bind("F", "Field-1-%-3").executeAsync()); + + // 2. PlaceHolder Name in small letter + assertThrows(WrongArgumentException.class, "Unknown placeholder: x", + () -> this.collection.remove("F1 like :X").bind("x", "Field-1-%-3").executeAsync()); + + // 3. No bind + assertThrows(WrongArgumentException.class, "Placeholder 'X' is not bound", () -> this.collection.remove("F1 like :X").executeAsync()); + + // 4. bind 2 times + assertThrows(WrongArgumentException.class, "Unknown placeholder: x", + () -> this.collection.remove("F1 like :X").bind("X", "Field-1-%-4").bind("x", "Field-1-%-3").bind("X", "Field-1-%-3").executeAsync()); + + // 5. bind same variable 2 times (Success) + asyncRes2 = this.collection.remove("F1 like :X").bind("X", "Field-1-%-4").bind("X", "Field-1-%-0").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + // 6. bind In different order (Success) + asyncRes2 = this.collection.remove("F1 like :X and $.F3 =:Y and $.F3 !=:Z").bind("Y", 101).bind("Z", 104).bind("X", "Field-1-%-1").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + + // 7. Using same Bind Variables many times(Success) + asyncRes2 = this.collection.remove("F1 like :F1 and $.F3 in (:F3,:F2,:F3) and $.F2 =(:F3*10)").bind("F3", 102).bind("F2", 107) + .bind("F1", "Field-1-%-2").executeAsync(); + res2 = asyncRes2.get(); + assertEquals(1, res2.getAffectedItemsCount()); + } + + @Test + public void testFetchOneFetchAllAsync() throws Exception { + int i = 0, maxrec = 10; + CompletableFuture asyncDocs = null; + List rowDoc = null; + DbDoc[] jsonlist = new DbDocImpl[maxrec]; + + for (i = 0; i < maxrec; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(i + 1000))); + newDoc2.add("F1", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F2", new JsonNumber().setValue(String.valueOf((100 + i) * 10))); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(100 + i))); + newDoc2.add("F4", new JsonNumber().setValue(String.valueOf(1 + i))); + jsonlist[i] = newDoc2; + newDoc2 = null; + } + + CompletableFuture asyncRes = this.collection.add(jsonlist).executeAsync(); + asyncRes.get(); + + // fetchAll() after fetchOne (Error) + asyncDocs = this.collection.find("F1 like :X and $.F3 <=:Y and $.F3 !=:Z").bind("Y", 105).bind("Z", 105).bind("X", "Field-1-%").orderBy("F1 asc") + .executeAsync(); + DocResult docs1 = asyncDocs.get(); + DbDoc doc = docs1.fetchOne(); + assertThrows(WrongArgumentException.class, "Cannot fetchAll\\(\\) after starting iteration", () -> docs1.fetchAll()); + + i = 0; + while (doc != null) { + assertEquals((long) (100 + i), (long) (((JsonNumber) doc.get("F3")).getInteger())); + i = i + 1; + doc = docs1.fetchOne(); + } + assertThrows(WrongArgumentException.class, "Cannot fetchAll\\(\\) after starting iteration", () -> docs1.fetchAll()); + + // fetchAll() after next (Error) + asyncDocs = this.collection.find("F1 like :X and $.F3 <=:Y and $.F3 !=:Z").bind("Y", 105).bind("Z", 105).bind("X", "Field-1-%").orderBy("F1 asc") + .executeAsync(); + DocResult docs2 = asyncDocs.get(); + doc = docs2.next(); + assertThrows(WrongArgumentException.class, "Cannot fetchAll\\(\\) after starting iteration", () -> docs2.fetchAll()); + i = 0; + do { + assertEquals((long) (100 + i), (long) (((JsonNumber) doc.get("F3")).getInteger())); + i = i + 1; + doc = docs2.next(); + } while (docs2.hasNext()); + assertThrows(WrongArgumentException.class, "Cannot fetchAll\\(\\) after starting iteration", () -> docs2.fetchAll()); + + // fetchOne() and next(Success) + asyncDocs = this.collection.find("F1 like :X and $.F3 <=:Y and $.F3 !=:Z").bind("Y", 108).bind("Z", 108).bind("X", "Field-1-%").orderBy("F3 asc") + .executeAsync(); + DocResult docs3 = asyncDocs.get(); + i = 0; + while (docs3.hasNext()) { + if (i % 2 == 1) { + doc = docs3.next(); + } else { + doc = docs3.fetchOne(); + } + assertEquals((long) (100 + i), (long) (((JsonNumber) doc.get("F3")).getInteger())); + i = i + 1; + + } + assertThrows(WrongArgumentException.class, "Cannot fetchAll\\(\\) after starting iteration", () -> docs3.fetchAll()); + + //fetchAll (Success) + asyncDocs = this.collection.find("F1 like :X and $.F3 <=:Y and $.F3 !=:Z").bind("Y", 105).bind("Z", 105).bind("X", "Field-1-%").orderBy("F1 asc") + .executeAsync(); + DocResult docs = asyncDocs.get(); + rowDoc = docs.fetchAll(); + assertEquals((long) 5, (long) rowDoc.size()); + for (i = 0; i < rowDoc.size(); i++) { + doc = rowDoc.get(i); + assertEquals((long) (100 + i), (long) (((JsonNumber) doc.get("F3")).getInteger())); + } + doc = docs.fetchOne(); + } + + @SuppressWarnings({ "deprecation", "unchecked" }) + @Test + public void testCollectionAddModifyRemoveAsync() throws Exception { + int i = 0; + int NUMBER_OF_QUERIES = 5000; + DbDoc doc = null; + AddResult res = null; + SqlResult res1 = null; + CompletableFuture asyncDocs = null; + DocResult docs = null; + DbDoc newDoc1 = new DbDocImpl(); + newDoc1.add("_id", new JsonString().setValue(String.valueOf(1000))); + newDoc1.add("F1", new JsonString().setValue("Field-1-Data-1")); + newDoc1.add("F2", new JsonNumber().setValue(String.valueOf(1000))); + newDoc1.add("T", new JsonNumber().setValue(String.valueOf(10))); + + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("_id", new JsonString().setValue(String.valueOf(1000))); + + // add(), modify(),find(), remove() Success And Failure + List futures = new ArrayList<>(); + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 10 == 0) { + futures.add(this.collection.add(newDoc1).executeAsync()); + } else if (i % 10 == 1) { + futures.add(this.collection.find("NON_EXISTING_FUNCTION1()").fields("$._id as _id, $.F1 as F1, $.F2 as F2, $.F3 as F3").executeAsync()); //Error + } else if (i % 10 == 2) { + futures.add(this.collection.find("$.F2 = 1000").fields("$._id as _id, $.F1 as F1, $.F2 as F2, $.T as T").executeAsync()); + } else if (i % 10 == 3) { + futures.add(this.collection.add(newDoc2).executeAsync()); //Error + } else if (i % 10 == 4) { + futures.add(this.collection.modify("$.F2 = 1000").change("$.T", expr("$.T+1")).sort("$.F3 desc").executeAsync()); + } else if (i % 10 == 5) { + futures.add(this.collection.modify("$.F2 = 1000").change("$.T", expr("NON_EXISTING_FUNCTION2()")).sort("$.F3 desc").executeAsync());//Error + } else if (i % 10 == 6) { + futures.add(this.collection.remove("NON_EXISTING_FUNCTION3()=:PARAM").bind("PARAM", 1000).orderBy("$.F3 desc").executeAsync());//Error + } else if (i % 10 == 7) { + futures.add(this.session.sql( + "SELECT JSON_OBJECT('_id', JSON_EXTRACT(doc,'$._id'),'F1', JSON_EXTRACT(doc,'$.F1'),'F2', JSON_EXTRACT(doc,'$.F2'),'T', JSON_EXTRACT(doc,'$.T')) AS doc FROM `" + + this.collectionName + "` WHERE (JSON_EXTRACT(doc,'$.F2') = 1000) ") + .executeAsync()); + } else if (i % 10 == 8) { + futures.add(this.session.sql("select non_existingfun() /* loop : " + i + "*/").executeAsync());//Error + } else { + futures.add(this.collection.remove("$.F2 = :PARAM").bind("PARAM", 1000).orderBy("$.F3 desc").executeAsync()); + } + } + + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + int i1 = i; + if (i % 10 == 0) { + res = ((CompletableFuture) futures.get(i)).get(); + assertEquals(1, res.getAffectedItemsCount()); + } else if (i % 10 == 1) { + assertThrows(ExecutionException.class, ".*FUNCTION " + this.schema.getName() + ".NON_EXISTING_FUNCTION1 does not exist.*", + () -> ((CompletableFuture) futures.get(i1)).get()); + } else if (i % 10 == 2) { + docs = ((CompletableFuture) futures.get(i)).get(); + assertTrue(docs.hasNext()); + doc = docs.next(); + assertEquals((long) (10), (long) (((JsonNumber) doc.get("T")).getInteger())); + assertFalse(docs.hasNext()); + } else if (i % 10 == 3) { + assertThrows(ExecutionException.class, ".*Document contains a field value that is not unique but required to be.*", + () -> ((CompletableFuture) futures.get(i1)).get()); + } else if (i % 10 == 4) { + Result res2 = ((CompletableFuture) futures.get(i)).get(); + assertEquals(1, res2.getAffectedItemsCount()); + } else if (i % 10 == 5) { + assertThrows(ExecutionException.class, ".*FUNCTION " + this.schema.getName() + ".NON_EXISTING_FUNCTION2 does not exist.*", + () -> ((CompletableFuture) futures.get(i1)).get()); + } else if (i % 10 == 6) { + assertThrows(ExecutionException.class, ".*FUNCTION " + this.schema.getName() + ".NON_EXISTING_FUNCTION3 does not exist.*", + () -> ((CompletableFuture) futures.get(i1)).get()); + } else if (i % 10 == 7) { + res1 = ((CompletableFuture) futures.get(i)).get(); + res1.next(); + assertFalse(res1.hasNext()); + } else if (i % 10 == 8) { + assertThrows(ExecutionException.class, ".*FUNCTION " + this.schema.getName() + ".non_existingfun does not exist.*", + () -> ((CompletableFuture) futures.get(i1)).get()); + } else { + Result res2 = ((CompletableFuture) futures.get(i)).get(); + assertEquals(1, res2.getAffectedItemsCount()); + } + } + + if ((NUMBER_OF_QUERIES - 1) % 10 < 9) { + assertEquals((1), this.collection.count()); + asyncDocs = this.collection.find("$.T = :X ").bind("X", 10).fields(expr("{'cnt':count($.T)}")).executeAsync(); + docs = asyncDocs.get(); + doc = docs.next(); + assertEquals((long) (1), (long) (((JsonNumber) doc.get("cnt")).getInteger())); + } else { + assertEquals((0), this.collection.count()); + } + } } diff --git a/src/test/java/testsuite/x/devapi/CompressionTest.java b/src/test/java/testsuite/x/devapi/CompressionTest.java index 4f2451ce0..2bdbdcb0f 100644 --- a/src/test/java/testsuite/x/devapi/CompressionTest.java +++ b/src/test/java/testsuite/x/devapi/CompressionTest.java @@ -34,6 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.FilterInputStream; import java.io.InputStream; @@ -228,6 +229,7 @@ private boolean isCompressionEnabled(Session sess) { @BeforeEach public void setupCompressionTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); if (setupTestSession()) { this.compressFreeTestProperties.remove(PropertyKey.xdevapiCompression.getKeyName()); this.compressFreeTestProperties.remove(PropertyKey.xdevapiCompressionAlgorithms.getKeyName()); @@ -264,9 +266,7 @@ public void teardownCompressionTest() { */ @Test public void compressionNegotiationServerSideRestricted() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); String[] algorithms = new String[] { "", "zstd_stream", "lz4_message", "deflate_stream" }; boolean[] expected = new boolean[] { false, false, false, true }; // Only "deflate_stream" is supported by default. @@ -321,9 +321,7 @@ public void compressionNegotiationServerSideRestricted() { */ @Test public void compressionNegotiationClientSideSelectionNativelySupported() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); /* * Default negotiation is always "deflate_stream" as only "deflate_stream" is supported by default. @@ -346,9 +344,7 @@ public void compressionNegotiationClientSideSelectionNativelySupported() { */ @Test public void compressionNegotiationClientSideSelectionOtherThanNative() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); String[] algorithmsOpts = new String[] { "zstd_stream,lz4_message,deflate_stream", "lz4_message,zstd_stream,deflate_stream" }; for (String algorithms : algorithmsOpts) { @@ -371,9 +367,7 @@ public void compressionNegotiationClientSideSelectionOtherThanNative() { */ @Test public void compressionNegotiationClientSideSelectionUnknownIds() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); String[] algorithmsOpts = new String[] { "foo_message,bar_stream,deflate_stream", "foo_message,deflate_stream,bar_stream", "deflate_stream,foo_message,bar_stream" }; @@ -392,9 +386,7 @@ public void compressionNegotiationClientSideSelectionUnknownIds() { */ @Test public void compressionNegotiationClientSideSelectionNoCommon() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); String[] algorithmsOpts = new String[] { "", "foo_message,bar_stream" }; for (String algorithms : algorithmsOpts) { @@ -436,9 +428,7 @@ public void compressionNegotiationClientSideSelectionNoCommon() { */ @Test public void compressionDisabled() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); dropCollection("compressionDisabled"); this.schema.createCollection("compressionDisabled"); @@ -475,9 +465,7 @@ public void compressionDisabled() { */ @Test public void downlinkCompression() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); dropCollection("downlinkCompression"); this.schema.createCollection("downlinkCompression"); @@ -519,9 +507,7 @@ public void downlinkCompression() { */ @Test public void uplinkCompression() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); dropCollection("uplinkCompression"); @@ -564,9 +550,7 @@ public void uplinkCompression() { */ @Test public void compressionThreshold() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); dropCollection("compressionThreshold"); this.schema.createCollection("compressionThreshold"); @@ -617,9 +601,7 @@ public void compressionThreshold() { */ @Test public void invalidCompressionOptions() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); assertThrows(WrongArgumentException.class, "The connection property 'xdevapi.compression' acceptable values are: 'DISABLED', 'PREFERRED' or 'REQUIRED'\\. The value 'true' is not acceptable\\.", @@ -701,9 +683,7 @@ public void invalidCompressionOptions() { */ @Test public void validCompressionExtensionsOption() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); dropCollection("validCompressionAlgorithmOption"); this.schema.createCollection("validCompressionAlgorithmOption"); @@ -746,9 +726,7 @@ public void validCompressionExtensionsOption() { */ @Test public void compressionNegotiationClientSideSelectionWithAliases() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); Session testSession = this.fact.getSession(this.compressFreeBaseUrl + makeParam(PropertyKey.xdevapiCompressionAlgorithms, "zstd,lz4,deflate") + makeParam(PropertyKey.xdevapiCompressionExtensions, @@ -815,26 +793,26 @@ public void compressionNegotiationClientSideSelectionWithAliases() { */ @Test public void testBug99708() { - if (!this.isSetForXTests || !this.compressionSettings.serverSupportsCompression()) { - return; - } + assumeTrue(this.compressionSettings.serverSupportsCompression(), "Server variable mysqlx_compression_algorithms must be configured to run this test."); - dropCollection("testBug99708"); - Collection col = this.schema.createCollection("testBug99708"); - String docId = "1"; - DbDoc doc = JsonParser.parseDoc("{ \"product\": \"MySQL Connector/J\" }"); - col.addOrReplaceOne(docId, doc); - - for (int i = 1; i <= 150; i++) { - Session testSession = this.fact.getSession(this.compressFreeBaseUrl + makeParam(PropertyKey.xdevapiCompressionAlgorithms, "deflate") - + makeParam(PropertyKey.xdevapiCompression, "required")); - col = testSession.getDefaultSchema().getCollection("testBug99708"); - doc = col.find("_id = :id").bind("id", docId).execute().fetchOne(); - doc.add("Iteration-" + i, new JsonString().setValue("" + System.nanoTime())); + try { + dropCollection("testBug99708"); + Collection col = this.schema.createCollection("testBug99708"); + String docId = "1"; + DbDoc doc = JsonParser.parseDoc("{ \"product\": \"MySQL Connector/J\" }"); col.addOrReplaceOne(docId, doc); - testSession.close(); - } - dropCollection("TestBug99708"); + for (int i = 1; i <= 150; i++) { + Session testSession = this.fact.getSession(this.compressFreeBaseUrl + makeParam(PropertyKey.xdevapiCompressionAlgorithms, "deflate") + + makeParam(PropertyKey.xdevapiCompression, "required")); + col = testSession.getDefaultSchema().getCollection("testBug99708"); + doc = col.find("_id = :id").bind("id", docId).execute().fetchOne(); + doc.add("Iteration-" + i, new JsonString().setValue("" + System.nanoTime())); + col.addOrReplaceOne(docId, doc); + testSession.close(); + } + } finally { + dropCollection("testBug99708"); + } } } diff --git a/src/test/java/testsuite/x/devapi/DevApiBaseTestCase.java b/src/test/java/testsuite/x/devapi/DevApiBaseTestCase.java index 318790ec0..88d68a8f6 100644 --- a/src/test/java/testsuite/x/devapi/DevApiBaseTestCase.java +++ b/src/test/java/testsuite/x/devapi/DevApiBaseTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -35,12 +35,18 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.SQLException; +import java.util.Arrays; import com.mysql.cj.MysqlxSession; +import com.mysql.cj.ServerVersion; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.protocol.x.XProtocolError; +import com.mysql.cj.util.StringUtils; +import com.mysql.cj.util.Util; +import com.mysql.cj.xdevapi.DocResult; import com.mysql.cj.xdevapi.PreparableStatement; +import com.mysql.cj.xdevapi.Row; import com.mysql.cj.xdevapi.Schema; import com.mysql.cj.xdevapi.Session; import com.mysql.cj.xdevapi.SessionImpl; @@ -72,6 +78,32 @@ public boolean setupTestSession() { this.dbCharset = rs.fetchOne().getString(1); rs = this.session.sql("SHOW VARIABLES LIKE 'collation_database'").execute(); this.dbCollation = rs.fetchOne().getString(1); + + // ensure max_connections value is enough to run tests + int maxConnections = 0; + int mysqlxMaxConnections = 0; + + rs = this.session.sql("SHOW VARIABLES LIKE '%max_connections'").execute(); + Row r = rs.fetchOne(); + if (r.getString(0).contains("mysqlx")) { + mysqlxMaxConnections = r.getInt(1); + maxConnections = rs.fetchOne().getInt(1); + } else { + maxConnections = r.getInt(1); + mysqlxMaxConnections = rs.fetchOne().getInt(1); + } + + rs = this.session.sql("show status like 'threads_connected'").execute(); + int usedConnections = rs.fetchOne().getInt(1); + + if (maxConnections - usedConnections < 200) { + maxConnections += 200; + this.session.sql("SET GLOBAL max_connections=" + maxConnections).execute(); + if (mysqlxMaxConnections < maxConnections) { + this.session.sql("SET GLOBAL mysqlx_max_connections=" + maxConnections).execute(); + } + } + return true; } return false; @@ -187,6 +219,21 @@ int getPreparedStatementExecutionsCount(Session sess, int prepStmtId) { return -1; } + protected boolean supportsTestCertificates(Session sess) { + SqlResult res = sess.sql("SELECT @@mysqlx_ssl_ca, @@ssl_ca").execute(); + if (res.hasNext()) { + Row r = res.next(); + return (StringUtils.isNullOrEmpty(r.getString(1)) && r.getString(2).contains("ssl-test-certs") || r.getString(1).contains("ssl-test-certs")); + } + return false; + } + + protected boolean supportsTLSv1_2(ServerVersion version) throws Exception { + return version.meetsMinimum(new ServerVersion(5, 7, 28)) + || version.meetsMinimum(new ServerVersion(5, 6, 46)) && !version.meetsMinimum(new ServerVersion(5, 7, 0)) + || version.meetsMinimum(new ServerVersion(5, 6, 0)) && Util.isEnterpriseEdition(version.toString()); + } + int getPreparedStatementId(PreparableStatement stmt) { try { Field prepStmtId = PreparableStatement.class.getDeclaredField("preparedStatementId"); @@ -238,4 +285,26 @@ protected static void assertSecureSession(Session sess, String user) { SqlResult res = sess.sql("SELECT CURRENT_USER()").execute(); assertEquals(user, res.fetchOne().getString(0).split("@")[0]); } + + public String buildString(int length, char charToFill) { + if (length > 0) { + char[] array = new char[length]; + Arrays.fill(array, charToFill); + return new String(array); + } + return ""; + } + + public int count_data(DocResult docs1) { + int recCnt = 0; + while (true) { + try { + docs1.next(); + recCnt++; + } catch (java.util.NoSuchElementException sqlEx) { + break; + } + } + return recCnt; + } } diff --git a/src/test/java/testsuite/x/devapi/Ipv6SupportTest.java b/src/test/java/testsuite/x/devapi/Ipv6SupportTest.java index 249e4e73c..0ffc109d0 100644 --- a/src/test/java/testsuite/x/devapi/Ipv6SupportTest.java +++ b/src/test/java/testsuite/x/devapi/Ipv6SupportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -30,7 +30,6 @@ package testsuite.x.devapi; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.net.Inet6Address; @@ -53,6 +52,7 @@ public class Ipv6SupportTest extends DevApiBaseTestCase { @BeforeEach public void setupIpv6SupportTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); if (setupTestSession()) { List ipv6List = isMysqlRunningLocally() ? TestUtils.getIpv6List() : TestUtils.getIpv6List(getTestHost()); this.ipv6Addrs = ipv6List.stream().map((e) -> e.getHostAddress()).collect(Collectors.toList()); @@ -80,8 +80,6 @@ public void teardownIpv6SupportTest() { */ @Test public void testIpv6SupportInSession() { - assumeTrue(this.isSetForXTests, - "Not set to run X tests. Set the url to the X server using the property " + PropertyDefinitions.SYSP_testsuite_url_mysqlx); assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("5.7.17")), "Server version 5.7.17 or higher is required."); // Although per specification IPv6 addresses must be enclosed by square brackets, we actually support them directly. @@ -107,11 +105,8 @@ public void testIpv6SupportInSession() { String errMsg = "None of the tested hosts have server sockets listening on the port " + port + ". This test requires a MySQL server with X Protocol running in local host with IPv6 support enabled " + "(set '--mysqlx-bind-address = *' if needed."; - if (isMysqlRunningLocally()) { - fail(errMsg); - } else { - System.err.println(errMsg); - } + assertFalse(isMysqlRunningLocally(), errMsg); + System.err.println(errMsg); } } } diff --git a/src/test/java/testsuite/x/devapi/MetadataTest.java b/src/test/java/testsuite/x/devapi/MetadataTest.java index a9fcb7061..cc5478422 100644 --- a/src/test/java/testsuite/x/devapi/MetadataTest.java +++ b/src/test/java/testsuite/x/devapi/MetadataTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -30,9 +30,13 @@ package testsuite.x.devapi; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,6 +44,7 @@ import com.mysql.cj.x.protobuf.MysqlxResultset.ColumnMetaData; import com.mysql.cj.xdevapi.Column; import com.mysql.cj.xdevapi.RowResult; +import com.mysql.cj.xdevapi.SqlResult; import com.mysql.cj.xdevapi.Table; import com.mysql.cj.xdevapi.Type; @@ -55,11 +60,15 @@ public void setupTableTest() { } } + @AfterEach + public void teardownTableTest() { + if (this.isSetForXTests) { + sqlUpdate("drop table if exists example_metadata"); + } + } + @Test public void example_metadata() { - if (!this.isSetForXTests) { - return; - } Table table = this.schema.getTable("example_metadata"); RowResult rows = table.select("_id, name, birthday, age").execute(); List metadata = rows.getColumns(); @@ -172,9 +181,6 @@ public void example_metadata() { @Test public void renameCol() { - if (!this.isSetForXTests) { - return; - } Table table = this.schema.getTable("example_metadata"); RowResult rows = table.select("_id as TheId").execute(); List metadata = rows.getColumns(); @@ -216,9 +222,6 @@ public void renameCol() { @Test public void derivedCol() { - if (!this.isSetForXTests) { - return; - } Table table = this.schema.getTable("example_metadata"); RowResult rows = table.select("_id + 1 as TheId").execute(); List metadata = rows.getColumns(); @@ -246,69 +249,70 @@ public void derivedCol() { @Test public void docAsTableIsJSON() { - if (!this.isSetForXTests) { - return; - } String collName = "doc_as_table"; - dropCollection(collName); - this.schema.createCollection(collName); - Table table = this.schema.getCollectionAsTable(collName); - RowResult rows = table.select("_id, doc").execute(); - List metadata = rows.getColumns(); - assertEquals(2, metadata.size()); + try { + dropCollection(collName); + this.schema.createCollection(collName); + Table table = this.schema.getCollectionAsTable(collName); + RowResult rows = table.select("_id, doc").execute(); + List metadata = rows.getColumns(); + assertEquals(2, metadata.size()); - Column idCol = metadata.get(0); - assertEquals(this.schema.getName(), idCol.getSchemaName()); - assertEquals(collName, idCol.getTableName()); - assertEquals(collName, idCol.getTableLabel()); - assertEquals("_id", idCol.getColumnName()); - assertEquals("_id", idCol.getColumnLabel()); - assertEquals(Type.STRING, idCol.getType()); - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { - assertEquals(32, idCol.getLength()); - } else { - assertEquals(128, idCol.getLength()); - } - assertEquals(0, idCol.getFractionalDigits()); - assertEquals(false, idCol.isNumberSigned()); + Column idCol = metadata.get(0); + assertEquals(this.schema.getName(), idCol.getSchemaName()); + assertEquals(collName, idCol.getTableName()); + assertEquals(collName, idCol.getTableLabel()); + assertEquals("_id", idCol.getColumnName()); + assertEquals("_id", idCol.getColumnLabel()); + assertEquals(Type.STRING, idCol.getType()); + if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { + assertEquals(32, idCol.getLength()); + } else { + assertEquals(128, idCol.getLength()); + } + assertEquals(0, idCol.getFractionalDigits()); + assertEquals(false, idCol.isNumberSigned()); - // Unlike ordinary tables, collections are always created in uft8mb4 charset, but collation was changed in 8.0.1. - // Since MySQL 8.0.5 the _id column has collation 'binary'. - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { - assertEquals("binary", idCol.getCollationName()); - assertEquals("binary", idCol.getCharacterSetName()); - } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.1"))) { - assertEquals("utf8mb4_0900_ai_ci", idCol.getCollationName()); - assertEquals("utf8mb4", idCol.getCharacterSetName()); - } else { - assertEquals("utf8mb4_general_ci", idCol.getCollationName()); - assertEquals("utf8mb4", idCol.getCharacterSetName()); - } - assertEquals(false, idCol.isPadded()); - assertEquals(false, idCol.isNullable()); - assertEquals(false, idCol.isAutoIncrement()); - assertEquals(true, idCol.isPrimaryKey()); - assertEquals(false, idCol.isUniqueKey()); - assertEquals(false, idCol.isPartKey()); + // Unlike ordinary tables, collections are always created in uft8mb4 charset, but collation was changed in 8.0.1. + // Since MySQL 8.0.5 the _id column has collation 'binary'. + if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { + assertEquals("binary", idCol.getCollationName()); + assertEquals("binary", idCol.getCharacterSetName()); + } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.1"))) { + assertEquals("utf8mb4_0900_ai_ci", idCol.getCollationName()); + assertEquals("utf8mb4", idCol.getCharacterSetName()); + } else { + assertEquals("utf8mb4_general_ci", idCol.getCollationName()); + assertEquals("utf8mb4", idCol.getCharacterSetName()); + } + assertEquals(false, idCol.isPadded()); + assertEquals(false, idCol.isNullable()); + assertEquals(false, idCol.isAutoIncrement()); + assertEquals(true, idCol.isPrimaryKey()); + assertEquals(false, idCol.isUniqueKey()); + assertEquals(false, idCol.isPartKey()); - Column docCol = metadata.get(1); - assertEquals(this.schema.getName(), docCol.getSchemaName()); - assertEquals(collName, docCol.getTableName()); - assertEquals(collName, docCol.getTableLabel()); - assertEquals("doc", docCol.getColumnName()); - assertEquals("doc", docCol.getColumnLabel()); - assertEquals(Type.JSON, docCol.getType()); - assertEquals(4294967295L, docCol.getLength()); - assertEquals(0, docCol.getFractionalDigits()); - assertEquals(false, docCol.isNumberSigned()); - assertEquals("binary", docCol.getCollationName()); - assertEquals("binary", docCol.getCharacterSetName()); - assertEquals(false, docCol.isPadded()); - assertEquals(true, docCol.isNullable()); - assertEquals(false, docCol.isAutoIncrement()); - assertEquals(false, docCol.isPrimaryKey()); - assertEquals(false, docCol.isUniqueKey()); - assertEquals(false, docCol.isPartKey()); + Column docCol = metadata.get(1); + assertEquals(this.schema.getName(), docCol.getSchemaName()); + assertEquals(collName, docCol.getTableName()); + assertEquals(collName, docCol.getTableLabel()); + assertEquals("doc", docCol.getColumnName()); + assertEquals("doc", docCol.getColumnLabel()); + assertEquals(Type.JSON, docCol.getType()); + assertEquals(4294967295L, docCol.getLength()); + assertEquals(0, docCol.getFractionalDigits()); + assertEquals(false, docCol.isNumberSigned()); + assertEquals("binary", docCol.getCollationName()); + assertEquals("binary", docCol.getCharacterSetName()); + assertEquals(false, docCol.isPadded()); + assertEquals(true, docCol.isNullable()); + assertEquals(false, docCol.isAutoIncrement()); + assertEquals(false, docCol.isPrimaryKey()); + assertEquals(false, docCol.isUniqueKey()); + assertEquals(false, docCol.isPartKey()); + } finally { + dropCollection(collName); + } } /** @@ -348,299 +352,1305 @@ public void docAsTableIsJSON() { */ @Test public void exhaustTypes() { - if (!this.isSetForXTests) { - return; - } String tableName = "exhaust_types"; - sqlUpdate("drop table if exists " + tableName); - sqlUpdate("create table exhaust_types (a bit, b char(20) not null, c int, d tinyint unsigned primary key, e bigint, " - + "f double, g decimal(20, 3), h time, i datetime, j timestamp, k date, l set('1','2'), m enum('1','2'), unique (a), key(b, c))"); - Table table = this.schema.getTable(tableName); - RowResult rows = table.select("a,b,c,d,e,f,g,h,i,j,k,l,m").execute(); - List metadata = rows.getColumns(); - assertEquals(13, metadata.size()); - - Column c; - - c = metadata.get(0); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("a", c.getColumnName()); - assertEquals("a", c.getColumnLabel()); - assertEquals(Type.BIT, c.getType()); - // assertEquals(1, c.getLength()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(1); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("b", c.getColumnName()); - assertEquals("b", c.getColumnLabel()); - assertEquals(Type.STRING, c.getType()); - if ("utf8mb4_0900_ai_ci".equals(this.dbCollation)) { - assertEquals(80, c.getLength()); // TODO is it an xplugin bug after changing default charset to utf8mb4? - } else { + try { + sqlUpdate("drop table if exists " + tableName); + sqlUpdate("create table " + tableName + " (a bit, b char(20) not null, c int, d tinyint unsigned primary key, e bigint, " + + "f double, g decimal(20, 3), h time, i datetime, j timestamp, k date, l set('1','2'), m enum('1','2'), unique (a), key(b, c))"); + Table table = this.schema.getTable(tableName); + RowResult rows = table.select("a,b,c,d,e,f,g,h,i,j,k,l,m").execute(); + List metadata = rows.getColumns(); + assertEquals(13, metadata.size()); + + Column c; + + c = metadata.get(0); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("a", c.getColumnName()); + assertEquals("a", c.getColumnLabel()); + assertEquals(Type.BIT, c.getType()); + // assertEquals(1, c.getLength()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(1); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("b", c.getColumnName()); + assertEquals("b", c.getColumnLabel()); + assertEquals(Type.STRING, c.getType()); + if ("utf8mb4_0900_ai_ci".equals(this.dbCollation)) { + assertEquals(80, c.getLength()); // TODO is it an xplugin bug after changing default charset to utf8mb4? + } else { + assertEquals(20, c.getLength()); + } + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isNumberSigned()); + if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.20"))) { + // after Bug#30516849 fix + assertEquals("utf8mb4_0900_ai_ci", c.getCollationName()); + assertEquals("utf8mb4", c.getCharacterSetName()); + } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { + // after Bug#28180155 fix + assertEquals("utf8mb4_general_ci", c.getCollationName()); + assertEquals("utf8mb4", c.getCharacterSetName()); + } else { + assertEquals(this.dbCollation, c.getCollationName()); + assertEquals(this.dbCharset, c.getCharacterSetName()); + } + assertEquals(true, c.isPadded()); + assertEquals(false, c.isNullable()); + assertEquals(false, c.isAutoIncrement()); + assertEquals(false, c.isPrimaryKey()); + assertEquals(false, c.isUniqueKey()); + assertEquals(true, c.isPartKey()); + + c = metadata.get(2); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("c", c.getColumnName()); + assertEquals("c", c.getColumnLabel()); + assertEquals(Type.INT, c.getType()); + assertEquals(11, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + assertEquals(true, c.isNumberSigned()); + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(3); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("d", c.getColumnName()); + assertEquals("d", c.getColumnLabel()); + assertEquals(Type.TINYINT, c.getType()); + assertEquals(3, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isNumberSigned()); + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPadded()); + assertEquals(false, c.isNullable()); + assertEquals(false, c.isAutoIncrement()); + assertEquals(true, c.isPrimaryKey()); + assertEquals(false, c.isUniqueKey()); + assertEquals(false, c.isPartKey()); + + c = metadata.get(4); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("e", c.getColumnName()); + assertEquals("e", c.getColumnLabel()); + assertEquals(Type.BIGINT, c.getType()); assertEquals(20, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + assertEquals(true, c.isNumberSigned()); + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(5); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("f", c.getColumnName()); + assertEquals("f", c.getColumnLabel()); + assertEquals(Type.DOUBLE, c.getType()); + assertEquals(22, c.getLength()); + assertEquals(0, c.getFractionalDigits()); + assertEquals(true, c.isNumberSigned()); + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPadded()); + assertEquals(true, c.isNullable()); + assertEquals(false, c.isAutoIncrement()); + assertEquals(false, c.isPrimaryKey()); + assertEquals(false, c.isUniqueKey()); + assertEquals(false, c.isPartKey()); + + c = metadata.get(6); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("g", c.getColumnName()); + assertEquals("g", c.getColumnLabel()); + assertEquals(Type.DECIMAL, c.getType()); + assertEquals(22, c.getLength()); + assertEquals(3, c.getFractionalDigits()); + assertEquals(true, c.isNumberSigned()); + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + assertEquals(false, c.isPadded()); + assertEquals(true, c.isNullable()); + assertEquals(false, c.isAutoIncrement()); + assertEquals(false, c.isPrimaryKey()); + assertEquals(false, c.isUniqueKey()); + assertEquals(false, c.isPartKey()); + + c = metadata.get(7); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("h", c.getColumnName()); + assertEquals("h", c.getColumnLabel()); + assertEquals(Type.TIME, c.getType()); + assertEquals(10, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(8); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("i", c.getColumnName()); + assertEquals("i", c.getColumnLabel()); + assertEquals(Type.DATETIME, c.getType()); + assertEquals(19, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(9); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("j", c.getColumnName()); + assertEquals("j", c.getColumnLabel()); + assertEquals(Type.TIMESTAMP, c.getType()); + assertEquals(19, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(10); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("k", c.getColumnName()); + assertEquals("k", c.getColumnLabel()); + assertEquals(Type.DATE, c.getType()); + assertEquals(10, c.getLength()); + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(11); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("l", c.getColumnName()); + assertEquals("l", c.getColumnLabel()); + assertEquals(Type.SET, c.getType()); + // assertEquals(3, c.getLength()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.20"))) { + // after Bug#30516849 fix + assertEquals("utf8mb4_0900_ai_ci", c.getCollationName()); + assertEquals("utf8mb4", c.getCharacterSetName()); + } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { + // after Bug#28180155 fix + assertEquals("utf8mb4_general_ci", c.getCollationName()); + assertEquals("utf8mb4", c.getCharacterSetName()); + } else { + assertEquals(this.dbCollation, c.getCollationName()); + assertEquals(this.dbCharset, c.getCharacterSetName()); + } + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + + c = metadata.get(12); + assertEquals(this.schema.getName(), c.getSchemaName()); + assertEquals(tableName, c.getTableName()); + assertEquals(tableName, c.getTableLabel()); + assertEquals("m", c.getColumnName()); + assertEquals("m", c.getColumnLabel()); + assertEquals(Type.ENUM, c.getType()); + // assertEquals(1, c.getLength()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value + if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.20"))) { + // after Bug#30516849 fix + assertEquals("utf8mb4_0900_ai_ci", c.getCollationName()); + assertEquals("utf8mb4", c.getCharacterSetName()); + } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { + // after Bug#28180155 fix + assertEquals("utf8mb4_general_ci", c.getCollationName()); + assertEquals("utf8mb4", c.getCharacterSetName()); + } else { + assertEquals(this.dbCollation, c.getCollationName()); + assertEquals(this.dbCharset, c.getCharacterSetName()); + } + // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value + // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value + } finally { + sqlUpdate("drop table if exists " + tableName); } - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isNumberSigned()); - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.20"))) { - // after Bug#30516849 fix - assertEquals("utf8mb4_0900_ai_ci", c.getCollationName()); - assertEquals("utf8mb4", c.getCharacterSetName()); - } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - // after Bug#28180155 fix - assertEquals("utf8mb4_general_ci", c.getCollationName()); - assertEquals("utf8mb4", c.getCharacterSetName()); - } else { - assertEquals(this.dbCollation, c.getCollationName()); - assertEquals(this.dbCharset, c.getCharacterSetName()); + } + + @Test + public void testGetColumnInfoFromnSession() throws Exception { + Column myCol = null; + List metadata = null; + try { + sqlUpdate("drop table if exists xyz"); + sqlUpdate( + "create table xyz (i int auto_increment primary key,j bigint not null,k tinyint unsigned unique,l char(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci,m decimal(16,10),key(l))"); + sqlUpdate("insert into xyz (j,k,l,m) values (1000,1,'a',10.12)"); + + sqlUpdate("drop database if exists qadatabase"); + sqlUpdate("create database qadatabase"); + sqlUpdate("create table qadatabase.xyz (d date)"); + sqlUpdate("insert into qadatabase.xyz values ('2016-03-07')"); + + SqlResult sRes = this.session.sql("select * from " + this.schema.getName() + ".xyz , qadatabase.xyz mytable").execute(); + metadata = sRes.getColumns(); + + assertEquals(6, metadata.size()); + for (int i = 0; i < metadata.size(); i++) { + myCol = metadata.get(i); + } + + myCol = metadata.get(0); + assertEquals("i", myCol.getColumnName()); + assertEquals("i", myCol.getColumnLabel()); + assertEquals(this.schema.getName(), myCol.getSchemaName()); + assertEquals("xyz", myCol.getTableName()); + assertEquals("xyz", myCol.getTableLabel()); + assertEquals(Type.INT, myCol.getType()); + assertEquals(11, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(true, myCol.isNumberSigned()); + assertEquals(null, myCol.getCollationName()); + assertEquals(null, myCol.getCharacterSetName()); + assertEquals(false, myCol.isPadded()); + assertEquals(false, myCol.isNullable()); + assertEquals(true, myCol.isAutoIncrement()); + assertEquals(true, myCol.isPrimaryKey()); + assertEquals(false, myCol.isUniqueKey()); + assertEquals(false, myCol.isPartKey()); + + myCol = metadata.get(1); + assertEquals("j", myCol.getColumnName()); + assertEquals("j", myCol.getColumnLabel()); + assertEquals(this.schema.getName(), myCol.getSchemaName()); + assertEquals("xyz", myCol.getTableName()); + assertEquals("xyz", myCol.getTableLabel()); + assertEquals(Type.BIGINT, myCol.getType()); + assertEquals(20, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(true, myCol.isNumberSigned()); + assertEquals(null, myCol.getCollationName()); + assertEquals(null, myCol.getCharacterSetName()); + assertEquals(false, myCol.isPadded()); + assertEquals(false, myCol.isNullable()); + assertEquals(false, myCol.isAutoIncrement()); + assertEquals(false, myCol.isPrimaryKey()); + assertEquals(false, myCol.isUniqueKey()); + assertEquals(false, myCol.isPartKey()); + + myCol = metadata.get(2); + assertEquals("k", myCol.getColumnName()); + assertEquals("k", myCol.getColumnLabel()); + assertEquals(this.schema.getName(), myCol.getSchemaName()); + assertEquals("xyz", myCol.getTableName()); + assertEquals("xyz", myCol.getTableLabel()); + assertEquals(Type.TINYINT, myCol.getType()); + assertEquals(3, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(null, myCol.getCollationName()); + assertEquals(null, myCol.getCharacterSetName()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + assertEquals(false, myCol.isAutoIncrement()); + assertEquals(false, myCol.isPrimaryKey()); + assertEquals(true, myCol.isUniqueKey()); + assertEquals(false, myCol.isPartKey()); + + myCol = metadata.get(3); + assertEquals("l", myCol.getColumnName()); + assertEquals("l", myCol.getColumnLabel()); + assertEquals(this.schema.getName(), myCol.getSchemaName()); + assertEquals("xyz", myCol.getTableName()); + assertEquals("xyz", myCol.getTableLabel()); + assertEquals(Type.STRING, myCol.getType()); + assertEquals(800, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals("utf8mb4_spanish2_ci", myCol.getCollationName()); + assertEquals("utf8mb4", myCol.getCharacterSetName()); + assertEquals(true, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + assertEquals(false, myCol.isAutoIncrement()); + assertEquals(false, myCol.isPrimaryKey()); + assertEquals(false, myCol.isUniqueKey()); + assertEquals(true, myCol.isPartKey()); + + myCol = metadata.get(4); + assertEquals("m", myCol.getColumnName()); + assertEquals("m", myCol.getColumnLabel()); + assertEquals(this.schema.getName(), myCol.getSchemaName()); + assertEquals("xyz", myCol.getTableName()); + assertEquals("xyz", myCol.getTableLabel()); + assertEquals(Type.DECIMAL, myCol.getType()); + assertEquals(18, myCol.getLength()); + assertEquals(10, myCol.getFractionalDigits()); + assertEquals(true, myCol.isNumberSigned()); + assertEquals(null, myCol.getCollationName()); + assertEquals(null, myCol.getCharacterSetName()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + assertEquals(false, myCol.isAutoIncrement()); + assertEquals(false, myCol.isPrimaryKey()); + assertEquals(false, myCol.isUniqueKey()); + assertEquals(false, myCol.isPartKey()); + + myCol = metadata.get(5); + assertEquals("d", myCol.getColumnName()); + assertEquals("d", myCol.getColumnLabel()); + assertEquals("qadatabase", myCol.getSchemaName()); + assertEquals("xyz", myCol.getTableName()); + assertEquals("mytable", myCol.getTableLabel()); + assertEquals(Type.DATE, myCol.getType()); + assertEquals(10, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(null, myCol.getCollationName()); + assertEquals(null, myCol.getCharacterSetName()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + assertEquals(false, myCol.isAutoIncrement()); + assertEquals(false, myCol.isPrimaryKey()); + assertEquals(false, myCol.isUniqueKey()); + assertEquals(false, myCol.isPartKey()); + } finally { + sqlUpdate("drop table if exists xyz"); + sqlUpdate("drop database if exists qadatabase"); } - assertEquals(true, c.isPadded()); - assertEquals(false, c.isNullable()); - assertEquals(false, c.isAutoIncrement()); - assertEquals(false, c.isPrimaryKey()); - assertEquals(false, c.isUniqueKey()); - assertEquals(true, c.isPartKey()); - - c = metadata.get(2); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("c", c.getColumnName()); - assertEquals("c", c.getColumnLabel()); - assertEquals(Type.INT, c.getType()); - assertEquals(11, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - assertEquals(true, c.isNumberSigned()); - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(3); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("d", c.getColumnName()); - assertEquals("d", c.getColumnLabel()); - assertEquals(Type.TINYINT, c.getType()); - assertEquals(3, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isNumberSigned()); - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPadded()); - assertEquals(false, c.isNullable()); - assertEquals(false, c.isAutoIncrement()); - assertEquals(true, c.isPrimaryKey()); - assertEquals(false, c.isUniqueKey()); - assertEquals(false, c.isPartKey()); - - c = metadata.get(4); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("e", c.getColumnName()); - assertEquals("e", c.getColumnLabel()); - assertEquals(Type.BIGINT, c.getType()); - assertEquals(20, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - assertEquals(true, c.isNumberSigned()); - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(5); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("f", c.getColumnName()); - assertEquals("f", c.getColumnLabel()); - assertEquals(Type.DOUBLE, c.getType()); - assertEquals(22, c.getLength()); - assertEquals(0, c.getFractionalDigits()); - assertEquals(true, c.isNumberSigned()); - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPadded()); - assertEquals(true, c.isNullable()); - assertEquals(false, c.isAutoIncrement()); - assertEquals(false, c.isPrimaryKey()); - assertEquals(false, c.isUniqueKey()); - assertEquals(false, c.isPartKey()); - - c = metadata.get(6); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("g", c.getColumnName()); - assertEquals("g", c.getColumnLabel()); - assertEquals(Type.DECIMAL, c.getType()); - assertEquals(22, c.getLength()); - assertEquals(3, c.getFractionalDigits()); - assertEquals(true, c.isNumberSigned()); - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - assertEquals(false, c.isPadded()); - assertEquals(true, c.isNullable()); - assertEquals(false, c.isAutoIncrement()); - assertEquals(false, c.isPrimaryKey()); - assertEquals(false, c.isUniqueKey()); - assertEquals(false, c.isPartKey()); - - c = metadata.get(7); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("h", c.getColumnName()); - assertEquals("h", c.getColumnLabel()); - assertEquals(Type.TIME, c.getType()); - assertEquals(10, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(8); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("i", c.getColumnName()); - assertEquals("i", c.getColumnLabel()); - assertEquals(Type.DATETIME, c.getType()); - assertEquals(19, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(9); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("j", c.getColumnName()); - assertEquals("j", c.getColumnLabel()); - assertEquals(Type.TIMESTAMP, c.getType()); - assertEquals(19, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(10); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("k", c.getColumnName()); - assertEquals("k", c.getColumnLabel()); - assertEquals(Type.DATE, c.getType()); - assertEquals(10, c.getLength()); - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCollationName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(null, c.getCharacterSetName()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(11); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("l", c.getColumnName()); - assertEquals("l", c.getColumnLabel()); - assertEquals(Type.SET, c.getType()); - // assertEquals(3, c.getLength()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.20"))) { - // after Bug#30516849 fix - assertEquals("utf8mb4_0900_ai_ci", c.getCollationName()); - assertEquals("utf8mb4", c.getCharacterSetName()); - } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - // after Bug#28180155 fix - assertEquals("utf8mb4_general_ci", c.getCollationName()); - assertEquals("utf8mb4", c.getCharacterSetName()); - } else { - assertEquals(this.dbCollation, c.getCollationName()); - assertEquals(this.dbCharset, c.getCharacterSetName()); + } + + @Test + public void testGetSchemaName() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a varchar(20), b date, c int)"); + sqlUpdate("insert into qatable values ('X', 'Abcd', '2016-03-07',10)"); + sqlUpdate("drop database if exists qadatabase"); + sqlUpdate("create database qadatabase"); + sqlUpdate("create table qadatabase.qatable (_id varchar(32), a varchar(20), b date, c int)"); + sqlUpdate("insert into qadatabase.qatable values ('X', 'Abcd', '2016-03-07',10)"); + + table = this.schema.getTable("qatable"); + rows = table.select("_id, a, b, c").execute(); + List metadata = rows.getColumns(); + assertEquals(4, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals(this.schema.getName(), idCol.getSchemaName()); + + Column aCol = metadata.get(1); + assertEquals(this.schema.getName(), aCol.getSchemaName()); + + Column bCol = metadata.get(2); + assertEquals(this.schema.getName(), bCol.getSchemaName()); + + Column cCol = metadata.get(3); + assertEquals(this.schema.getName(), cCol.getSchemaName()); + + SqlResult sRes = this.session.sql("select * from qadatabase.qatable").execute(); + metadata = sRes.getColumns(); + assertEquals(4, metadata.size()); + + idCol = metadata.get(0); + assertEquals("qadatabase", idCol.getSchemaName()); + + aCol = metadata.get(1); + assertEquals("qadatabase", aCol.getSchemaName()); + + bCol = metadata.get(2); + assertEquals("qadatabase", bCol.getSchemaName()); + + cCol = metadata.get(3); + assertEquals("qadatabase", cCol.getSchemaName()); + } finally { + sqlUpdate("drop database if exists qadatabase"); } - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value - - c = metadata.get(12); - assertEquals(this.schema.getName(), c.getSchemaName()); - assertEquals(tableName, c.getTableName()); - assertEquals(tableName, c.getTableLabel()); - assertEquals("m", c.getColumnName()); - assertEquals("m", c.getColumnLabel()); - assertEquals(Type.ENUM, c.getType()); - // assertEquals(1, c.getLength()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(0, c.getFractionalDigits()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isNumberSigned()); // irrelevant, we shouldn't expect any concrete value - if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.20"))) { - // after Bug#30516849 fix - assertEquals("utf8mb4_0900_ai_ci", c.getCollationName()); - assertEquals("utf8mb4", c.getCharacterSetName()); - } else if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - // after Bug#28180155 fix - assertEquals("utf8mb4_general_ci", c.getCollationName()); - assertEquals("utf8mb4", c.getCharacterSetName()); - } else { - assertEquals(this.dbCollation, c.getCollationName()); - assertEquals(this.dbCharset, c.getCharacterSetName()); + } + + @Test + public void testGetTableName() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a varchar(20), b date, c int)"); + sqlUpdate("insert into qatable values ('X', 'Abcd', '2016-03-07',10)"); + + table = this.schema.getTable("qatable"); + rows = table.select("_id, a, b, c").execute(); + List metadata = rows.getColumns(); + assertEquals(4, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals("qatable", idCol.getTableName()); + + Column aCol = metadata.get(1); + assertEquals("qatable", aCol.getTableName()); + + Column bCol = metadata.get(2); + assertEquals("qatable", bCol.getTableName()); + + Column cCol = metadata.get(3); + assertEquals("qatable", cCol.getTableName()); + assertEquals(table.getName(), cCol.getTableName()); + + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testGetTableLabel() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a varchar(20), b date, c int)"); + sqlUpdate("insert into qatable values ('X', 'Abcd', '2016-03-07',10)"); + + table = this.schema.getTable("qatable"); + rows = table.select("_id, a, b, c").execute(); + List metadata = rows.getColumns(); + assertEquals(4, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals("qatable", idCol.getTableLabel()); + + Column aCol = metadata.get(1); + assertEquals("qatable", aCol.getTableLabel()); + + Column bCol = metadata.get(2); + assertEquals("qatable", bCol.getTableLabel()); + + Column cCol = metadata.get(3); + assertEquals("qatable", cCol.getTableLabel()); + assertEquals(table.getName(), cCol.getTableLabel()); + + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testGetColumnName() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a varchar(20), b date, c int)"); + sqlUpdate("insert into qatable values ('X', 'Abcd', '2016-03-07',10)"); + + table = this.schema.getTable("qatable"); + rows = table.select("_id, a, b, c").execute(); + List metadata = rows.getColumns(); + assertEquals(4, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals("_id", idCol.getColumnName()); + + Column aCol = metadata.get(1); + assertEquals("a", aCol.getColumnName()); + + Column bCol = metadata.get(2); + assertEquals("b", bCol.getColumnName()); + + Column cCol = metadata.get(3); + assertEquals("c", cCol.getColumnName()); + + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testGetColumnLabel() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a varchar(20), b date, c int)"); + sqlUpdate("insert into qatable values ('X', 'Abcd', '2016-03-07',10)"); + + table = this.schema.getTable("qatable"); + rows = table.select("_id as col1, a as `a+1`, b as `a 1 1`, c as `a``q`").execute(); + List metadata = rows.getColumns(); + assertEquals(4, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals("_id", idCol.getColumnName()); + assertEquals("col1", idCol.getColumnLabel()); + + Column aCol = metadata.get(1); + assertEquals("a", aCol.getColumnName()); + assertEquals("a+1", aCol.getColumnLabel()); + + Column bCol = metadata.get(2); + assertEquals("b", bCol.getColumnName()); + assertEquals("a 1 1", bCol.getColumnLabel()); + + Column cCol = metadata.get(3); + assertEquals("c", cCol.getColumnName()); + assertEquals("a`q", cCol.getColumnLabel()); + + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testGetType() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a char(20), b date, c int,d double,e datetime,f time," + + "g linestring,h tinyint,i mediumint,j bigint,k float, l set('1','2'), m enum('1','2'),n decimal(20,10),o bit)"); + + table = this.schema.getTable("qatable"); + table.insert("j").values(10).execute(); + + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(16, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals(Type.STRING, idCol.getType()); + + Column aCol = metadata.get(1); + assertEquals(Type.STRING, aCol.getType()); + + Column bCol = metadata.get(2); + assertEquals(Type.DATE, bCol.getType()); + + Column cCol = metadata.get(3); + assertEquals(Type.INT, cCol.getType()); + + Column dCol = metadata.get(4); + assertEquals(Type.DOUBLE, dCol.getType()); + + Column eCol = metadata.get(5); + assertEquals(Type.DATETIME, eCol.getType()); + + Column fCol = metadata.get(6); + assertEquals(Type.TIME, fCol.getType()); + + Column gCol = metadata.get(7); + assertEquals(Type.GEOMETRY, gCol.getType()); + + Column hCol = metadata.get(8); + assertEquals(Type.TINYINT, hCol.getType()); + + Column iCol = metadata.get(9); + assertEquals(Type.MEDIUMINT, iCol.getType()); + + Column jCol = metadata.get(10); + assertEquals(Type.BIGINT, jCol.getType()); + + Column kCol = metadata.get(11); + assertEquals(Type.FLOAT, kCol.getType()); + + Column lCol = metadata.get(12); + assertEquals(Type.SET, lCol.getType()); + + Column mCol = metadata.get(13); + assertEquals(Type.ENUM, mCol.getType()); + + Column nCol = metadata.get(14); + assertEquals(Type.DECIMAL, nCol.getType()); + + Column oCol = metadata.get(15); + assertEquals(Type.BIT, oCol.getType()); + + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testGetFractionalDigits() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a char(20), b date, c int,d double,e datetime,f time," + + "g linestring,h tinyint,i mediumint,j bigint,k float, l set('1','2'), m enum('1','2'),n decimal(20,10),o bit)"); + + table = this.schema.getTable("qatable"); + table.insert("h").values(10).execute(); + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(16, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals(0, idCol.getFractionalDigits()); + + Column aCol = metadata.get(1); + assertEquals(0, aCol.getFractionalDigits()); + + Column bCol = metadata.get(2); + assertEquals(0, bCol.getFractionalDigits()); + + Column cCol = metadata.get(3); + assertEquals(0, cCol.getFractionalDigits()); + + Column dCol = metadata.get(4); + assertEquals(0, dCol.getFractionalDigits()); + + Column eCol = metadata.get(5); + assertEquals(0, eCol.getFractionalDigits()); + + Column fCol = metadata.get(6); + assertEquals(0, fCol.getFractionalDigits()); + + Column gCol = metadata.get(7); + assertEquals(0, gCol.getFractionalDigits()); + + Column hCol = metadata.get(8); + assertEquals(0, hCol.getFractionalDigits()); + + Column iCol = metadata.get(9); + assertEquals(0, iCol.getFractionalDigits()); + + Column jCol = metadata.get(10); + assertEquals(0, jCol.getFractionalDigits()); + + Column kCol = metadata.get(11); + assertEquals(0, kCol.getFractionalDigits()); + + Column lCol = metadata.get(12); + assertEquals(0, lCol.getFractionalDigits()); + + Column mCol = metadata.get(13); + assertEquals(0, mCol.getFractionalDigits()); + + Column nCol = metadata.get(14); + assertEquals(10, nCol.getFractionalDigits()); + + Column oCol = metadata.get(15); + assertEquals(0, oCol.getFractionalDigits()); + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testIsNumberSigned() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (_id varchar(32), a char(20), b date, c int,d double signed,e datetime,f time," + + "g linestring,h tinyint unsigned,i mediumint,j bigint unsigned,k float, l set('1','2'), m enum('1','2'),n decimal(20,10),o bit)"); + + table = this.schema.getTable("qatable"); + table.insert("i").values(10).execute(); + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(16, metadata.size()); + + Column idCol = metadata.get(0); + assertEquals(false, idCol.isNumberSigned()); + + Column aCol = metadata.get(1); + assertEquals(false, aCol.isNumberSigned()); + + Column bCol = metadata.get(2); + assertEquals(false, bCol.isNumberSigned()); + + Column cCol = metadata.get(3); + assertEquals(true, cCol.isNumberSigned()); + + Column dCol = metadata.get(4); + assertEquals(true, dCol.isNumberSigned()); + + Column eCol = metadata.get(5); + assertEquals(false, eCol.isNumberSigned()); + + Column fCol = metadata.get(6); + assertEquals(false, fCol.isNumberSigned()); + + Column gCol = metadata.get(7); + assertEquals(false, gCol.isNumberSigned()); + + Column hCol = metadata.get(8); + assertEquals(false, hCol.isNumberSigned()); + + Column iCol = metadata.get(9); + assertEquals(true, iCol.isNumberSigned()); + + Column jCol = metadata.get(10); + assertEquals(false, jCol.isNumberSigned()); + + Column kCol = metadata.get(11); + assertEquals(true, kCol.isNumberSigned()); + + Column lCol = metadata.get(12); + assertEquals(false, lCol.isNumberSigned()); + + Column mCol = metadata.get(13); + assertEquals(false, mCol.isNumberSigned()); + + Column nCol = metadata.get(14); + assertEquals(true, nCol.isNumberSigned()); + + Column oCol = metadata.get(15); + assertEquals(false, oCol.isNumberSigned()); + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testGetLength() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop database if exists lengthTest"); + sqlUpdate("create database lengthTest DEFAULT CHARACTER SET latin1"); + sqlUpdate("drop table if exists lengthTest.qatable"); + sqlUpdate("create table lengthTest.qatable (_id varchar(32), a char(20), b date, c int,d double,e datetime,f time," + + "g linestring,h tinyint,i mediumint,j bigint,k float, l set('1','2'), m enum('1','2'),n decimal(20,10),o bit(3))"); + + table = this.session.getSchema("lengthTest").getTable("qatable"); + table.insert("k").values(10).execute(); + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(16, metadata.size()); + Column myCol = null; + long[] fLen = { 32, 20, 10, 11, 22, 19, 10, 0, 4, 9, 20, 12, 0, 0, 22, 3 }; + for (int i = 0; i < 16; i++) { + myCol = metadata.get(i); + assertEquals(fLen[i], myCol.getLength()); + } + } finally { + sqlUpdate("drop database if exists lengthTest"); + } + } + + /* + * Create table with 3 fields with primary key on 2 fields.Insert a record.Select the record.Get and Validate primary key property using isPrimaryKey() + * Create table with 2 fields.Mention 1st field as unique.Insert a record.Select the record.Get and Validate unique property using isUniqueKey() + * Create table with 2 fields.Mention key on field.Insert a record.Select the record.Get and Validate whether column is part of key or not isPartKey() + */ + @Test + public void testIsPrimaryKeyAndisUniqueKeyAndisPartKey() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (a int Not Null,b bigint unique,c char(30) Not Null unique,d tinyint,e json ," + + "f INT GENERATED ALWAYS AS (JSON_EXTRACT(e, '$.id')),g BIGINT GENERATED ALWAYS AS (JSON_EXTRACT(e, '$.id2')) STORED NOT NULL ," + + "key(b,g,f),primary key(a,c,d), INDEX i (f))"); + + table = this.schema.getTable("qatable"); + table.insert("a", "c", "d", "e").values(1, "S", 10, "{\"id2\": \"12345677890123\", \"name\": \"Fred\"}").execute(); + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(7, metadata.size()); + + Column aCol = metadata.get(0); + assertEquals(true, aCol.isPrimaryKey()); + assertEquals(false, aCol.isNullable()); + assertEquals(false, aCol.isUniqueKey()); + assertEquals(false, aCol.isPartKey()); + + Column bCol = metadata.get(1); + assertEquals(false, bCol.isPrimaryKey()); + assertEquals(true, bCol.isNullable()); + assertEquals(true, bCol.isUniqueKey()); + assertEquals(true, bCol.isPartKey()); + + Column cCol = metadata.get(2); + assertEquals(true, cCol.isPrimaryKey()); + assertEquals(false, cCol.isNullable()); + assertEquals(true, cCol.isUniqueKey()); + assertEquals(false, cCol.isPartKey()); + + Column dCol = metadata.get(3); + assertEquals(true, dCol.isPrimaryKey()); + assertEquals(false, dCol.isNullable()); + assertEquals(false, dCol.isUniqueKey()); + assertEquals(false, dCol.isPartKey()); + + Column eCol = metadata.get(4); + assertEquals(false, eCol.isPrimaryKey()); + assertEquals(true, eCol.isNullable()); + assertEquals(false, eCol.isUniqueKey()); + assertEquals(false, eCol.isPartKey()); + + Column fCol = metadata.get(5); + assertEquals(false, fCol.isPrimaryKey()); + assertEquals(true, fCol.isNullable()); + assertEquals(false, fCol.isUniqueKey()); + assertEquals(true, fCol.isPartKey()); + + Column gCol = metadata.get(6); + assertEquals(false, gCol.isPrimaryKey()); + assertEquals(false, gCol.isNullable()); + assertEquals(false, gCol.isUniqueKey()); + assertEquals(false, gCol.isPartKey()); + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + /* + * Create a table with int datatypes.Insert a record.Do some operation on column and select the column using alias(eg: select x+y AS sum from tab).Get and + * Validate the column info + * Create a table with valid datatypes.Insert a record.Do some operation on column and select the column without using alias name(eg: select x+y from + * tab).Get and Validate the column info + */ + @Test + public void testGetColumnNameAndgetColumnLabel() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (a varchar(32), b bigint, c double, d int,`b+c+d` DOUBLE AS (c+b+d))"); + sqlUpdate("set sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'"); + + table = this.schema.getTable("qatable"); + table.insert("a", "b", "c", "d").values("AB", 12345677890123L, -12345677890123.9, 1).execute(); + rows = table.select("a as col1, b as c, c as b,d as col1,d+1 as `sum()`, `b+c+d`,sum(c),count(*)/-1").execute(); + List metadata = rows.getColumns(); + assertEquals(8, metadata.size()); + + Column aCol = metadata.get(0); + assertEquals("a", aCol.getColumnName()); + assertEquals("col1", aCol.getColumnLabel()); + + Column bCol = metadata.get(1); + assertEquals("b", bCol.getColumnName()); + assertEquals("c", bCol.getColumnLabel()); + + Column cCol = metadata.get(2); + assertEquals("c", cCol.getColumnName()); + assertEquals("b", cCol.getColumnLabel()); + + Column dCol = metadata.get(3); + assertEquals("d", dCol.getColumnName()); + assertEquals("col1", dCol.getColumnLabel()); + + Column eCol = metadata.get(4); + assertEquals("", eCol.getColumnName()); + assertEquals("sum()", eCol.getColumnLabel()); + + Column fCol = metadata.get(5); + assertEquals("b+c+d", fCol.getColumnName()); + assertEquals("b+c+d", fCol.getColumnLabel()); + + Column gCol = metadata.get(6); + assertEquals("sum(`c`)", gCol.getColumnLabel()); + assertEquals("", gCol.getColumnName()); + + Column hCol = metadata.get(7); + assertEquals("(count(*) / -1)", hCol.getColumnLabel()); + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testMultiSelects() throws Exception { + RowResult rows = null; + Table table1 = null; + Table table2 = null; + List metadata1 = null; + List metadata2 = null; + Column col1 = null; + try { + sqlUpdate("drop table if exists t1"); + sqlUpdate("create table t1 (a varchar(32), b bigint)"); + sqlUpdate("drop table if exists t2"); + sqlUpdate("create table t2 (a1 int, b1 double)"); + + table1 = this.schema.getTable("t1"); + table2 = this.schema.getTable("t2"); + + table1.insert("a", "b").values("AB", 12345677890123L).values("CD", 123456778901234L).values("EF", 1234567789012345L).execute(); + table2.insert("a1", "b1").values(1234, 4321.123).values(12345, 54321.123).values(123456, 654321.123).execute(); + + //Using different table Object + rows = table1.select("a,b as bigintcol").execute(); + metadata1 = rows.getColumns(); + assertEquals(2, metadata1.size()); + col1 = metadata1.get(0); + assertEquals("a", col1.getColumnName()); + assertEquals("a", col1.getColumnLabel()); + + rows = table2.select("a1 as intcol ,b1, a1+b1 as sum").execute(); + metadata2 = rows.getColumns(); + assertEquals(3, metadata2.size()); + col1 = metadata2.get(0); + assertEquals("a1", col1.getColumnName()); + assertEquals("intcol", col1.getColumnLabel()); + + col1 = metadata2.get(1); + assertEquals("b1", col1.getColumnName()); + assertEquals("b1", col1.getColumnLabel()); + + col1 = metadata1.get(1); + assertEquals("b", col1.getColumnName()); + assertEquals("bigintcol", col1.getColumnLabel()); + + col1 = metadata2.get(2); + assertEquals("sum", col1.getColumnLabel()); + assertEquals("", col1.getColumnName()); + + //Using Same table Object + rows = table1.select("a,b as bigintcol").execute(); + metadata1 = rows.getColumns(); + assertEquals(2, metadata1.size()); + col1 = metadata1.get(0); + assertEquals("a", col1.getColumnName()); + assertEquals("a", col1.getColumnLabel()); + + rows = table1.select("a as bigintcol2 , a+b as sum2,b").execute(); + metadata2 = rows.getColumns(); + assertEquals(3, metadata2.size()); + col1 = metadata2.get(0); + assertEquals("a", col1.getColumnName()); + assertEquals("bigintcol2", col1.getColumnLabel()); + + col1 = metadata2.get(1); + assertEquals("", col1.getColumnName()); + assertEquals("sum2", col1.getColumnLabel()); + + col1 = metadata1.get(1); + assertEquals("b", col1.getColumnName()); + assertEquals("bigintcol", col1.getColumnLabel()); + + col1 = metadata2.get(2); + assertEquals("b", col1.getColumnLabel()); + assertEquals("b", col1.getColumnName()); + } finally { + sqlUpdate("drop table if exists t1"); + sqlUpdate("drop table if exists t2"); + } + } + + @Test + public void testMultiSelectsAsync() throws Exception { + RowResult rows = null; + Table table1 = null; + Table table2 = null; + List metadata1 = null; + List metadata2 = null; + Column col1 = null; + CompletableFuture asyncRowRes = null; + + try { + sqlUpdate("drop table if exists t1"); + sqlUpdate("create table t1 (a varchar(32), b bigint)"); + sqlUpdate("drop table if exists t2"); + sqlUpdate("create table t2 (a1 int, b1 double)"); + + table1 = this.schema.getTable("t1"); + table2 = this.schema.getTable("t2"); + + table1.insert("a", "b").values("AB", 12345677890123L).values("CD", 123456778901234L).values("EF", 1234567789012345L).execute(); + table2.insert("a1", "b1").values(1234, 4321.123).values(12345, 54321.123).values(123456, 654321.123).execute(); + + //Using different table Object + asyncRowRes = table1.select("a,b as bigintcol").executeAsync(); + rows = asyncRowRes.get(); + metadata1 = rows.getColumns(); + assertEquals(2, metadata1.size()); + col1 = metadata1.get(0); + assertEquals("a", col1.getColumnName()); + assertEquals("a", col1.getColumnLabel()); + + asyncRowRes = table2.select("a1 as intcol ,b1, a1+b1 as sum").executeAsync(); + rows = asyncRowRes.get(); + metadata2 = rows.getColumns(); + assertEquals(3, metadata2.size()); + col1 = metadata2.get(0); + assertEquals("a1", col1.getColumnName()); + assertEquals("intcol", col1.getColumnLabel()); + + col1 = metadata2.get(1); + assertEquals("b1", col1.getColumnName()); + assertEquals("b1", col1.getColumnLabel()); + + col1 = metadata1.get(1); + assertEquals("b", col1.getColumnName()); + assertEquals("bigintcol", col1.getColumnLabel()); + + col1 = metadata2.get(2); + assertEquals("sum", col1.getColumnLabel()); + assertEquals("", col1.getColumnName()); + + //Using Same table Object + asyncRowRes = table1.select("a,b as bigintcol").executeAsync(); + rows = asyncRowRes.get(); + metadata1 = rows.getColumns(); + assertEquals(2, metadata1.size()); + col1 = metadata1.get(0); + assertEquals("a", col1.getColumnName()); + assertEquals("a", col1.getColumnLabel()); + + asyncRowRes = table1.select("a as bigintcol2 , a+b as sum2,b").executeAsync(); + rows = asyncRowRes.get(); + metadata2 = rows.getColumns(); + assertEquals(3, metadata2.size()); + col1 = metadata2.get(0); + assertEquals("a", col1.getColumnName()); + assertEquals("bigintcol2", col1.getColumnLabel()); + + col1 = metadata2.get(1); + assertEquals("", col1.getColumnName()); + assertEquals("sum2", col1.getColumnLabel()); + + col1 = metadata1.get(1); + assertEquals("b", col1.getColumnName()); + assertEquals("bigintcol", col1.getColumnLabel()); + + col1 = metadata2.get(2); + assertEquals("b", col1.getColumnLabel()); + assertEquals("b", col1.getColumnName()); + + } finally { + sqlUpdate("drop table if exists t1"); + sqlUpdate("drop table if exists t2"); + } + } + + /* + * Create table with char and int field.Insert a record.Select the record.Get and Validate the column collation name using getCollationName() + * Create table with char and int field.Insert a record.Select the record.Get and Validate the column charset name using getCharacterSetName() + * Create table with a auto-increment and a normal column.Insert a record.Select the record.Get and Validate auto increment property using isAutoIncrement() + */ + @Test + public void testIsPaddedAndisNullableAndisAutoIncrement() throws Exception { + RowResult rows = null; + Table table = null; + try { + sqlUpdate("drop table if exists qatable"); + sqlUpdate("create table qatable (a int Not Null auto_increment,b char(10),c char(30) Not Null,d bigint zerofill not null,primary key(a))"); + + table = this.schema.getTable("qatable"); + table.insert("b", "c", "d").values("a", "s", 12345677890123L).execute(); + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(4, metadata.size()); + + Column aCol = metadata.get(0); + assertEquals(false, aCol.isPadded()); + assertEquals(false, aCol.isNullable()); + assertEquals(true, aCol.isAutoIncrement()); + + Column bCol = metadata.get(1); + assertEquals(true, bCol.isPadded()); + assertEquals(true, bCol.isNullable()); + assertEquals(false, bCol.isAutoIncrement()); + + Column cCol = metadata.get(2); + assertEquals(true, cCol.isPadded()); + assertEquals(false, cCol.isNullable()); + assertEquals(false, cCol.isAutoIncrement()); + + Column dCol = metadata.get(3); + assertEquals(true, dCol.isPadded()); + assertEquals(false, dCol.isNullable()); + assertEquals(false, dCol.isAutoIncrement()); + } finally { + sqlUpdate("drop table if exists qatable"); + } + } + + @Test + public void testWithUnsignedData() throws Exception { + RowResult rows = null; + Table table = null; + Column myCol = null; + try { + char[] array = new char[1024 * 1024]; + Arrays.fill(array, 'X'); + char[] array2 = new char[256]; + Arrays.fill(array2, 'X'); + sqlUpdate("drop table if exists qatable"); + sqlUpdate( + "create table qatable (a int unsigned ,b bigint unsigned,c tinyint unsigned,d smallint unsigned, e float unsigned,f double unsigned, g TEXT,h MEDIUMINT unsigned)"); + + table = this.schema.getTable("qatable"); + table.insert("a", "b", "c", "d").values(1, 10, 1, 321).execute(); + rows = table.select("*").execute(); + List metadata = rows.getColumns(); + assertEquals(8, metadata.size()); + + myCol = metadata.get(0); + assertEquals("a", myCol.getColumnName()); + assertEquals("a", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.INT, myCol.getType()); + assertEquals(10, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(1); + assertEquals("b", myCol.getColumnName()); + assertEquals("b", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.BIGINT, myCol.getType()); + assertEquals(20, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(2); + assertEquals("c", myCol.getColumnName()); + assertEquals("c", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.TINYINT, myCol.getType()); + assertEquals(3, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(3); + assertEquals("d", myCol.getColumnName()); + assertEquals("d", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.SMALLINT, myCol.getType()); + assertEquals(5, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(4); + assertEquals("e", myCol.getColumnName()); + assertEquals("e", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.FLOAT, myCol.getType()); + assertEquals(12, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(5); + assertEquals("f", myCol.getColumnName()); + assertEquals("f", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.DOUBLE, myCol.getType()); + assertEquals(22, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(6); + assertEquals("g", myCol.getColumnName()); + assertEquals("g", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.STRING, myCol.getType()); + assertEquals(65535, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + + myCol = metadata.get(7); + assertEquals("h", myCol.getColumnName()); + assertEquals("h", myCol.getColumnLabel()); + assertEquals(table.getName(), myCol.getTableLabel()); + assertEquals(table.getName(), myCol.getTableName()); + assertEquals(Type.MEDIUMINT, myCol.getType()); + assertEquals(8, myCol.getLength()); + assertEquals(0, myCol.getFractionalDigits()); + assertEquals(false, myCol.isNumberSigned()); + assertEquals(false, myCol.isPadded()); + assertEquals(true, myCol.isNullable()); + } finally { + sqlUpdate("drop table if exists qatable"); } - // assertEquals(false, c.isPadded()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(true, c.isNullable()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isAutoIncrement()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPrimaryKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isUniqueKey()); // irrelevant, we shouldn't expect any concrete value - // assertEquals(false, c.isPartKey()); // irrelevant, we shouldn't expect any concrete value } } diff --git a/src/test/java/testsuite/x/devapi/ResultTest.java b/src/test/java/testsuite/x/devapi/ResultTest.java index 2dab6dd6f..56277b7db 100644 --- a/src/test/java/testsuite/x/devapi/ResultTest.java +++ b/src/test/java/testsuite/x/devapi/ResultTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -31,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.sql.Date; import java.sql.Time; @@ -44,6 +44,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.exceptions.DataReadException; import com.mysql.cj.util.TimeUtil; import com.mysql.cj.xdevapi.Row; @@ -54,6 +55,7 @@ public class ResultTest extends DevApiBaseTestCase { @BeforeEach public void setupTableTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); super.setupTestSession(); } @@ -64,101 +66,100 @@ public void teardownTableTest() { @Test public void testForceBuffering() { - if (!this.isSetForXTests) { - return; - } - sqlUpdate("drop table if exists testx"); - sqlUpdate("create table testx (x int)"); - sqlUpdate("insert into testx values (1), (2), (3)"); - Table table = this.schema.getTable("testx"); - RowResult rows = table.select("x/0 as bad_x").execute(); - // get warnings IMMEDIATELY - assertEquals(3, rows.getWarningsCount()); - Iterator warnings = rows.getWarnings(); - assertEquals(1365, warnings.next().getCode()); - assertEquals(1365, warnings.next().getCode()); - assertEquals(1365, warnings.next().getCode()); - Row r = rows.next(); - assertEquals(null, r.getString("bad_x")); - r = rows.next(); - assertEquals(null, r.getString("bad_x")); - r = rows.next(); - assertEquals(null, r.getString("bad_x")); try { - rows.next(); - fail("should throw"); - } catch (NoSuchElementException ex) { - // expected, end of results + sqlUpdate("drop table if exists testx"); + sqlUpdate("create table testx (x int)"); + sqlUpdate("insert into testx values (1), (2), (3)"); + Table table = this.schema.getTable("testx"); + RowResult rows = table.select("x/0 as bad_x").execute(); + // get warnings IMMEDIATELY + assertEquals(3, rows.getWarningsCount()); + Iterator warnings = rows.getWarnings(); + assertEquals(1365, warnings.next().getCode()); + assertEquals(1365, warnings.next().getCode()); + assertEquals(1365, warnings.next().getCode()); + Row r = rows.next(); + assertEquals(null, r.getString("bad_x")); + r = rows.next(); + assertEquals(null, r.getString("bad_x")); + r = rows.next(); + assertEquals(null, r.getString("bad_x")); + assertThrows(NoSuchElementException.class, () -> rows.next()); + } finally { + sqlUpdate("drop table if exists testx"); } } @Test public void testMars() { - if (!this.isSetForXTests) { - return; - } - sqlUpdate("drop table if exists testx"); - sqlUpdate("create table testx (x int)"); - sqlUpdate("insert into testx values (1), (2), (3)"); - Table table = this.schema.getTable("testx"); - RowResult rows = table.select("x").orderBy("x").execute(); - int i = 1; - while (rows.hasNext()) { - assertEquals(String.valueOf(i++), rows.next().getString("x")); - RowResult rows2 = table.select("x").orderBy("x").execute(); - assertEquals("1", rows2.next().getString("x")); + try { + sqlUpdate("drop table if exists testx"); + sqlUpdate("create table testx (x int)"); + sqlUpdate("insert into testx values (1), (2), (3)"); + Table table = this.schema.getTable("testx"); + RowResult rows = table.select("x").orderBy("x").execute(); + int i = 1; + while (rows.hasNext()) { + assertEquals(String.valueOf(i++), rows.next().getString("x")); + RowResult rows2 = table.select("x").orderBy("x").execute(); + assertEquals("1", rows2.next().getString("x")); + } + } finally { + sqlUpdate("drop table if exists testx"); } } @Test public void exceptionForNonExistingColumns() { - if (!this.isSetForXTests) { - return; - } - sqlUpdate("drop table if exists testx"); - sqlUpdate("create table testx (x int)"); - sqlUpdate("insert into testx values (1), (2), (3)"); - Table table = this.schema.getTable("testx"); - RowResult rows = table.select("x").orderBy("x").execute(); - Row r = rows.next(); - r.getString("x"); try { - r.getString("non_existing"); - } catch (DataReadException ex) { - assertTrue(ex.getMessage().contains("Invalid column")); - } - r.getString(0); - try { - r.getString(1); - } catch (DataReadException ex) { - assertTrue(ex.getMessage().contains("Invalid column")); + sqlUpdate("drop table if exists testx"); + sqlUpdate("create table testx (x int)"); + sqlUpdate("insert into testx values (1), (2), (3)"); + Table table = this.schema.getTable("testx"); + RowResult rows = table.select("x").orderBy("x").execute(); + Row r = rows.next(); + r.getString("x"); + try { + r.getString("non_existing"); + } catch (DataReadException ex) { + assertTrue(ex.getMessage().contains("Invalid column")); + } + r.getString(0); + try { + r.getString(1); + } catch (DataReadException ex) { + assertTrue(ex.getMessage().contains("Invalid column")); + } + } finally { + sqlUpdate("drop table if exists testx"); } } @Test public void testDateTimeTypes() throws Exception { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop table if exists testx"); + sqlUpdate("create table testx (w date, x datetime(6), y timestamp(6), z time)"); + Table table = this.schema.getTable("testx"); + SimpleDateFormat df = TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd'T'HH:mm:ss.S", null); + java.util.Date theDate = df.parse("2015-09-22T12:31:16.136"); + Date w = new Date(theDate.getTime()); + Timestamp y = new Timestamp(theDate.getTime()); + Time z = new Time(theDate.getTime()); + table.insert().values(w, theDate, y, z).execute(); + RowResult rows = table.select("w, x, y, z").execute(); + Row r = rows.next(); + assertEquals("2015-09-22", r.getString("w")); + // use string comparison for java.sql.Date objects + assertEquals(w.toString(), r.getDate("w").toString()); + assertEquals("2015-09-22 12:31:16.136000", r.getString("x")); + assertEquals(theDate, r.getTimestamp("x")); + assertEquals("2015-09-22 12:31:16.136000", r.getString("y")); + assertEquals(y.toString(), r.getTimestamp("y").toString()); + assertEquals("12:31:16", r.getString("z")); + assertEquals(z.toString(), r.getTime("z").toString()); + } finally { + sqlUpdate("drop table if exists testx"); } - sqlUpdate("drop table if exists testx"); - sqlUpdate("create table testx (w date, x datetime(6), y timestamp(6), z time)"); - Table table = this.schema.getTable("testx"); - SimpleDateFormat df = TimeUtil.getSimpleDateFormat(null, "yyyy-MM-dd'T'HH:mm:ss.S", null); - java.util.Date theDate = df.parse("2015-09-22T12:31:16.136"); - Date w = new Date(theDate.getTime()); - Timestamp y = new Timestamp(theDate.getTime()); - Time z = new Time(theDate.getTime()); - table.insert().values(w, theDate, y, z).execute(); - RowResult rows = table.select("w, x, y, z").execute(); - Row r = rows.next(); - assertEquals("2015-09-22", r.getString("w")); - // use string comparison for java.sql.Date objects - assertEquals(w.toString(), r.getDate("w").toString()); - assertEquals("2015-09-22 12:31:16.136000", r.getString("x")); - assertEquals(theDate, r.getTimestamp("x")); - assertEquals("2015-09-22 12:31:16.136000", r.getString("y")); - assertEquals(y.toString(), r.getTimestamp("y").toString()); - assertEquals("12:31:16", r.getString("z")); - assertEquals(z.toString(), r.getTime("z").toString()); } } diff --git a/src/test/java/testsuite/x/devapi/RowLockingTest.java b/src/test/java/testsuite/x/devapi/RowLockingTest.java new file mode 100644 index 000000000..991f8c6a3 --- /dev/null +++ b/src/test/java/testsuite/x/devapi/RowLockingTest.java @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2021, 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 also distributed with certain software (including but not + * limited to OpenSSL) 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 included with MySQL. + * + * 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 testsuite.x.devapi; + +import static com.mysql.cj.xdevapi.Expression.expr; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.mysql.cj.ServerVersion; +import com.mysql.cj.xdevapi.Collection; +import com.mysql.cj.xdevapi.DbDoc; +import com.mysql.cj.xdevapi.DbDocImpl; +import com.mysql.cj.xdevapi.DocResult; +import com.mysql.cj.xdevapi.JsonNumber; +import com.mysql.cj.xdevapi.JsonString; +import com.mysql.cj.xdevapi.RowResult; +import com.mysql.cj.xdevapi.Session; +import com.mysql.cj.xdevapi.SessionFactory; +import com.mysql.cj.xdevapi.Table; + +public class RowLockingTest extends BaseCollectionTestCase { + + int CheckFlag = 0; + + static Throwable initException[] = null; + + static class MyUncaughtExceptionHandler implements UncaughtExceptionHandler { + private int index = 0; + + MyUncaughtExceptionHandler(int n) { + this.index = n; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + System.out.println("I caught the exception"); + initException[this.index] = e; + } + } + + public class SelectRowLock extends Thread { + private int action; + private int lock; + private int shouldWait; + private int bindVal; + private String condition; + + SelectRowLock(int action, int lock, int shouldWait, int bindVal, String condition) { + this.action = action; + this.lock = lock; + this.shouldWait = shouldWait; + this.bindVal = bindVal; + this.condition = condition; + } + + @Override + public void run() { + System.out.println("Action is " + this.action); + System.out.println("Lock is " + this.lock); + System.out.println("Condition is " + this.condition); + System.out.println("CheckFlag is " + RowLockingTest.this.CheckFlag); + + RowResult rows = null; + Session sess = null; + + try { + sess = new SessionFactory().getSession(RowLockingTest.this.baseUrl); + Table table = sess.getDefaultSchema().getCollectionAsTable(RowLockingTest.this.collectionName); + + if (this.shouldWait == 1) { + System.out.println("wait started"); + do { + Thread.sleep(1000); + } while (RowLockingTest.this.CheckFlag != 1); + System.out.println("wait ended"); + } + + sess.startTransaction(); + switch (this.action) { + case 1: { + if (this.lock == 1) { + rows = table.select("doc->$.F2").where(this.condition).bind("bVal", this.bindVal).lockExclusive().execute(); + } else if (this.lock == 2) { + rows = table.select("doc->$.F2").where(this.condition).bind("bVal", this.bindVal).lockShared().execute(); + } + rows.next(); + break; + } + case 2: { + Map params = new HashMap<>(); + params.put("bVal", this.bindVal); + if (this.lock == 1) { + rows = table.select("doc->$.F2").where(this.condition).bind(params).lockExclusive().execute(); + } else if (this.lock == 2) { + rows = table.select("doc->$.F2").where(this.condition).bind(params).lockShared().execute(); + } + rows.next(); + break; + } + case 3: { + table.update().set("doc", expr("JSON_REPLACE(doc, \"$.F2\", \"NewData\")")).where(this.condition).bind("bVal", this.bindVal).execute(); + break; + } + case 4: { + table.delete().where(this.condition).bind("bVal", this.bindVal).execute(); + break; + } + } + + if (this.shouldWait == 0) { + RowLockingTest.this.CheckFlag = 1; + Thread.sleep(10000); + if (2 == RowLockingTest.this.CheckFlag) { + throw new RuntimeException("Second thread moved ahead"); + } + } else if (this.shouldWait == 1) { + RowLockingTest.this.CheckFlag = 2; + } else if (this.shouldWait == 2) { + RowLockingTest.this.CheckFlag = 1; + Thread.sleep(10000); + if (2 == RowLockingTest.this.CheckFlag) { + throw new RuntimeException("Second thread stuck even thoung different conditions executed"); + } + } + + sess.commit(); + + } catch (Exception e) { + System.err.print("InterruptedException: "); + System.err.println(e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + if (this.shouldWait != 1) { + RowLockingTest.this.CheckFlag = 1; + } + if (sess != null) { + sess.close(); + } + } + + } + + } + + public class FindRowLock extends Thread { + private int action; + private int lock; + private int shouldWait; + private int bindVal; + private String condition; + + FindRowLock(int action, int lock, int shouldWait, int bindVal, String condition) { + this.action = action; + this.lock = lock; + this.shouldWait = shouldWait; + this.bindVal = bindVal; + this.condition = condition; + } + + @SuppressWarnings("deprecation") + @Override + public void run() { + System.out.println("Action is " + this.action); + System.out.println("Lock is " + this.lock); + System.out.println("Condition is " + this.condition); + System.out.println("CheckFlag is " + RowLockingTest.this.CheckFlag); + + DocResult docs = null; + Session sess = null; + + try { + sess = new SessionFactory().getSession(RowLockingTest.this.baseUrl); + Collection coll = sess.getDefaultSchema().getCollection(RowLockingTest.this.collectionName); + + if (this.shouldWait == 1) { + System.out.println("wait started"); + do { + Thread.sleep(1000); + } while (RowLockingTest.this.CheckFlag != 1); + System.out.println("wait ended"); + } + + sess.startTransaction(); + switch (this.action) { + case 1: { + if (this.lock == 1) { + docs = coll.find(this.condition).bind(new Object[] { this.bindVal }).fields("$.F2 as F2").orderBy("$.F1").lockExclusive().execute(); + } else if (this.lock == 2) { + docs = coll.find(this.condition).bind(new Object[] { this.bindVal }).fields("$.F2 as F2").orderBy("$.F1").lockShared().execute(); + } + docs.next(); + break; + } + case 2: { + coll.modify(this.condition).set("$.F2", "Data_New").bind(new Object[] { this.bindVal }).sort("$.F1 asc").execute(); + break; + } + case 3: { + coll.remove(this.condition).bind(new Object[] { this.bindVal }).orderBy("$.F1 asc").execute(); + break; + } + } + + if (this.shouldWait == 0) { + RowLockingTest.this.CheckFlag = 1; + Thread.sleep(10000); + if (2 == RowLockingTest.this.CheckFlag) { + throw new RuntimeException("Second thread moved ahead"); + } + } else if (this.shouldWait == 1) { + RowLockingTest.this.CheckFlag = 2; + } else if (this.shouldWait == 2) { + RowLockingTest.this.CheckFlag = 1; + Thread.sleep(10000); + if (2 == RowLockingTest.this.CheckFlag) { + throw new RuntimeException("Second thread stuck even thoung different conditions executed"); + } + } + + sess.commit(); + + } catch (Exception e) { + System.err.print("InterruptedException: "); + System.err.println(e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + if (this.shouldWait != 1) { + RowLockingTest.this.CheckFlag = 1; + } + if (sess != null) { + sess.close(); + } + } + + } + + } + + public class SelectRowDeadLock extends Thread { + private int action; + private int lock; + private String condition; + + SelectRowDeadLock(int action, int lock, String condition) { + this.action = action; + this.lock = lock; + this.condition = condition; + } + + @Override + public void run() { + System.out.println("Action is " + this.action); + System.out.println("Lock is " + this.lock); + System.out.println("Condition is " + this.condition); + System.out.println("CheckFlag is " + RowLockingTest.this.CheckFlag); + + String tabname = "newtable"; + Session sess = null; + + try { + sess = new SessionFactory().getSession(RowLockingTest.this.baseUrl); + Table table = sess.getDefaultSchema().getTable(tabname); + + sess.startTransaction(); + switch (this.action) { + case 1: { + if (this.lock == 1) { + table.select("F1").where("F0 = 1").lockExclusive().execute(); + } else if (this.lock == 2) { + table.select("F1").where("F0 = 1").lockShared().execute(); + } + + RowLockingTest.this.CheckFlag = 1; + do { + Thread.sleep(1000); + } while (RowLockingTest.this.CheckFlag != 2); + + try { + if (this.lock == 1) { + table.select("F1").where("F0 = 2").lockExclusive().execute(); + } else if (this.lock == 2) { + table.select("F1").where("F0 = 2").lockShared().execute(); + } + } catch (Exception e) { + System.out.println("ERROR(3) " + e.getMessage()); + assertTrue(e.getMessage().contains("Deadlock")); + } + + break; + } + case 2: { + do { + Thread.sleep(1000); + } while (RowLockingTest.this.CheckFlag != 1); + + if (this.lock == 1) { + table.select("F1").where("F0 = 2").lockExclusive().execute(); + } else if (this.lock == 2) { + table.select("F1").where("F0 = 2").lockShared().execute(); + } + + RowLockingTest.this.CheckFlag = 2; + + try { + if (this.lock == 1) { + table.select("F1").where("F0 = 1").lockExclusive().execute(); + } else if (this.lock == 2) { + table.select("F1").where("F0 = 1").lockShared().execute(); + } + } catch (Exception e) { + System.out.println("ERROR(3) " + e.getMessage()); + assertTrue(e.getMessage().contains("Deadlock")); + } + + break; + } + } + + sess.commit(); + + } catch (Exception e) { + System.err.print("InterruptedException: "); + System.err.println(e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + if (sess != null) { + sess.close(); + } + } + + } + + } + + public class FindRowDeadLock extends Thread { + private int action; + private int lock; + private String condition; + + FindRowDeadLock(int action, int lock, String condition) { + this.action = action; + this.lock = lock; + this.condition = condition; + } + + @Override + public void run() { + System.out.println("Action is " + this.action); + System.out.println("Lock is " + this.lock); + System.out.println("Condition is " + this.condition); + System.out.println("CheckFlag is " + RowLockingTest.this.CheckFlag); + + Session sess = null; + + try { + sess = new SessionFactory().getSession(RowLockingTest.this.baseUrl); + Collection coll = RowLockingTest.this.schema.getCollection(RowLockingTest.this.collectionName); + + sess.startTransaction(); + switch (this.action) { + case 1: { + if (this.lock == 1) { + coll.find("$.F1 = 1").fields("$.F2 as F2").lockExclusive().execute(); + } else if (this.lock == 2) { + coll.find("$.F1 = 1").fields("$.F2 as F2").lockShared().execute(); + } + + RowLockingTest.this.CheckFlag = 1; + do { + Thread.sleep(1000); + } while (RowLockingTest.this.CheckFlag != 2); + + if (this.lock == 1) { + coll.find("$.F1 = 2").fields("$.F2 as F2").lockExclusive().execute(); + } else if (this.lock == 2) { + coll.find("$.F1 = 2").fields("$.F2 as F2").lockShared().execute(); + } + + break; + } + case 2: { + do { + Thread.sleep(1000); + } while (RowLockingTest.this.CheckFlag != 1); + + if (this.lock == 1) { + coll.find("$.F1 = 2").fields("$.F2 as F2").lockExclusive().execute(); + } else if (this.lock == 2) { + coll.find("$.F1 = 2").fields("$.F2 as F2").lockShared().execute(); + } + + RowLockingTest.this.CheckFlag = 2; + + if (this.lock == 1) { + coll.find("$.F1 = 1").fields("$.F2 as F2").lockExclusive().execute(); + } else if (this.lock == 2) { + coll.find("$.F1 = 1").fields("$.F2 as F2").lockShared().execute(); + } + + break; + } + } + + sess.commit(); + + } catch (Exception e) { + System.err.print("InterruptedException: "); + System.err.println(e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + if (sess != null) { + sess.close(); + } + } + + } + + } + + /** + * START collection.find() tests + * + * @throws Exception + */ + + @Test + public void testFindRowLockingValid() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0; + try { + + DbDoc[] jsonlist = new DbDocImpl[10]; + + for (i = 1; i <= 10; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i))); + newDoc2.add("F2", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(10 * (i) + 0.1234))); + jsonlist[i - 1] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((10), this.collection.count()); + + /* Two threads with same conditions, select for update in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + FindRowLock[] Thrd = new FindRowLock[2]; + + Thrd[0] = new FindRowLock(1, 1, 0, 5, "$.F1 = ?"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new FindRowLock(2, 0, 1, 5, "$.F1 = ?"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions, select for share in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new FindRowLock(1, 2, 0, 5, "$.F1 = ?"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new FindRowLock(2, 0, 1, 5, "$.F1 = ?"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions selecting multiple records, select for update in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new FindRowLock(1, 2, 0, 5, "$.F1 < ?"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new FindRowLock(2, 0, 1, 5, "$.F1 < ?"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions selecting multiple rows, select for share in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new FindRowLock(1, 1, 0, 5, "$.F1 < ?"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new FindRowLock(2, 0, 1, 5, "$.F1 < ?"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions, select for update in one and select for share in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new FindRowLock(1, 2, 0, 5, "$.F1 < ?"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new FindRowLock(1, 1, 1, 5, "$.F1 < ?"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + } catch (RuntimeException e) { + System.out.print("**************RuntimeException: " + i); + System.out.println(e.getMessage()); + throw e; + } catch (InterruptedException e) { + System.out.print("InterruptedException: " + i); + System.out.println(e.getMessage()); + throw e; + } + } + + @Test + public void testSelectRowLockingValid() throws Exception { + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.0")), "MySQL 8.0+ is required to run this test."); + + int i = 0; + try { + /* add(DbDoc[] docs) */ + DbDoc[] jsonlist = new DbDocImpl[10]; + + for (i = 1; i <= 10; i++) { + DbDoc newDoc2 = new DbDocImpl(); + newDoc2.add("F1", new JsonNumber().setValue(String.valueOf(i))); + newDoc2.add("F2", new JsonString().setValue("Field-1-Data-" + i)); + newDoc2.add("F3", new JsonNumber().setValue(String.valueOf(10 * (i) + 0.1234))); + jsonlist[i - 1] = newDoc2; + newDoc2 = null; + } + this.collection.add(jsonlist).execute(); + + assertEquals((10), this.collection.count()); + this.CheckFlag = 0; + initException = new Throwable[2]; + SelectRowLock[] Thrd = new SelectRowLock[2]; + + /* Two threads with same conditions, select for update in one and update in second */ + Thrd[0] = new SelectRowLock(1, 1, 0, 5, "doc->$.F1 = :bVal"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowLock(3, 0, 1, 5, "doc->$.F1 = :bVal"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions, select for share in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new SelectRowLock(1, 2, 0, 5, "doc->$.F1 = :bVal"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowLock(3, 0, 1, 5, "doc->$.F1 = :bVal"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions, select for update in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new SelectRowLock(2, 1, 0, 5, "doc->$.F1 = :bVal"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowLock(3, 0, 1, 5, "doc->$.F1 = :bVal"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions, select for share in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new SelectRowLock(2, 2, 0, 5, "doc->$.F1 = :bVal"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowLock(3, 0, 1, 5, "doc->$.F1 = :bVal"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions selecting multiple records, select for update in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new SelectRowLock(2, 2, 0, 5, "doc->$.F1 < :bVal"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowLock(3, 0, 1, 5, "doc->$.F1 < :bVal"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + /* Two threads with same conditions selecting multiple records, select for share in one and update in second */ + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new SelectRowLock(2, 1, 0, 5, "doc->$.F1 < :bVal"); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowLock(3, 0, 1, 5, "doc->$.F1 < :bVal"); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + } catch (RuntimeException e) { + System.out.print("**************RuntimeException: " + i); + System.out.println(e.getMessage()); + throw e; + } catch (InterruptedException e) { + System.out.print("InterruptedException: " + i); + System.out.println(e.getMessage()); + throw e; + } + } + + /* Simulate deadlock using table select */ + @Test + public void testSelectRowLockingDeadlock() throws Exception { + int i = 0; + try { + this.session.sql("drop table if exists newtable").execute(); + + this.session.sql("create table newtable(F0 int auto_increment, F1 varchar(1024), PRIMARY KEY (f0))").execute(); + Table table = this.schema.getTable("newtable"); + + for (i = 1; i <= 10; i++) { + table.insert().values(i, "Data").execute(); + } + + this.CheckFlag = 0; + initException = new Throwable[2]; + SelectRowDeadLock[] Thrd = new SelectRowDeadLock[2]; + + Thrd[0] = new SelectRowDeadLock(1, 1, ""); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowDeadLock(2, 1, ""); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + this.CheckFlag = 0; + initException = new Throwable[2]; + + Thrd[0] = new SelectRowDeadLock(1, 2, ""); + Thrd[0].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(0)); + Thrd[0].start(); + + Thrd[1] = new SelectRowDeadLock(2, 1, ""); + Thrd[1].setUncaughtExceptionHandler(new MyUncaughtExceptionHandler(1)); + Thrd[1].start(); + + for (i = 0; i < 2; i++) { + Thrd[i].join(); + } + for (i = 0; i < 2; i++) { + if (initException[i] != null) { + throw new RuntimeException(initException[i]); + } + } + + } catch (RuntimeException e) { + System.out.print("**************RuntimeException: " + i); + System.out.println(e.getMessage()); + throw e; + } catch (InterruptedException e) { + System.out.print("InterruptedException: " + i); + System.out.println(e.getMessage()); + throw e; + } finally { + this.session.sql("drop table if exists newtable").execute(); + } + } + +} diff --git a/src/test/java/testsuite/x/devapi/SchemaTest.java b/src/test/java/testsuite/x/devapi/SchemaTest.java index 2ade3d5ec..e37b053a4 100644 --- a/src/test/java/testsuite/x/devapi/SchemaTest.java +++ b/src/test/java/testsuite/x/devapi/SchemaTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -33,6 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.util.ArrayList; import java.util.List; @@ -44,6 +45,7 @@ import com.mysql.cj.Messages; import com.mysql.cj.ServerVersion; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.exceptions.WrongArgumentException; import com.mysql.cj.protocol.x.XProtocolError; @@ -63,6 +65,7 @@ public class SchemaTest extends DevApiBaseTestCase { @BeforeEach public void setupCollectionTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); setupTestSession(); } @@ -71,11 +74,14 @@ public void teardownCollectionTest() { destroyTestSession(); } + @Test + public void testBasics() { + assertEquals(this.schema, this.schema.getSchema()); + assertEquals(this.session, this.schema.getSession()); + } + @Test public void testEquals() { - if (!this.isSetForXTests) { - return; - } Schema otherDefaultSchema = this.session.getDefaultSchema(); assertFalse(otherDefaultSchema == this.schema); assertTrue(otherDefaultSchema.equals(this.schema)); @@ -91,9 +97,6 @@ public void testEquals() { @Test public void testToString() { - if (!this.isSetForXTests) { - return; - } // this will pass as long as the test database doesn't require identifier quoting assertEquals("Schema(" + getTestDatabase() + ")", this.schema.toString()); Schema needsQuoted = this.session.getSchema("terrible'schema`name"); @@ -102,30 +105,29 @@ public void testToString() { @Test public void testListCollections() { - if (!this.isSetForXTests) { - return; - } String collName1 = "test_list_collections1"; String collName2 = "test_list_collections2"; - dropCollection(collName1); - dropCollection(collName2); - Collection coll1 = this.schema.createCollection(collName1); - Collection coll2 = this.schema.createCollection(collName2); + try { + dropCollection(collName1); + dropCollection(collName2); + Collection coll1 = this.schema.createCollection(collName1); + Collection coll2 = this.schema.createCollection(collName2); - List colls = this.schema.getCollections(); - assertTrue(colls.contains(coll1)); - assertTrue(colls.contains(coll2)); + List colls = this.schema.getCollections(); + assertTrue(colls.contains(coll1)); + assertTrue(colls.contains(coll2)); - colls = this.schema.getCollections("%ions2"); - assertFalse(colls.contains(coll1)); - assertTrue(colls.contains(coll2)); + colls = this.schema.getCollections("%ions2"); + assertFalse(colls.contains(coll1)); + assertTrue(colls.contains(coll2)); + } finally { + dropCollection(collName1); + dropCollection(collName2); + } } @Test public void testExists() { - if (!this.isSetForXTests) { - return; - } assertEquals(DbObjectStatus.EXISTS, this.schema.existsInDatabase()); Schema nonExistingSchema = this.session.getSchema(getTestDatabase() + "_SHOULD_NOT_EXIST_0xCAFEBABE"); assertEquals(DbObjectStatus.NOT_EXISTS, nonExistingSchema.existsInDatabase()); @@ -133,35 +135,33 @@ public void testExists() { @Test public void testCreateCollection() { - if (!this.isSetForXTests) { - return; - } String collName = "testCreateCollection"; - dropCollection(collName); - Collection coll = this.schema.createCollection(collName); try { - this.schema.createCollection(collName); - fail("Exception should be thrown trying to create a collection that already exists"); - } catch (XProtocolError ex) { - // expected - assertEquals(MysqlErrorNumbers.ER_TABLE_EXISTS_ERROR, ex.getErrorCode()); - } - try { - this.schema.createCollection(collName, false); - fail("Exception should be thrown trying to create a collection that already exists"); - } catch (XProtocolError ex) { - // expected - assertEquals(MysqlErrorNumbers.ER_TABLE_EXISTS_ERROR, ex.getErrorCode()); + dropCollection(collName); + Collection coll = this.schema.createCollection(collName); + try { + this.schema.createCollection(collName); + fail("Exception should be thrown trying to create a collection that already exists"); + } catch (XProtocolError ex) { + // expected + assertEquals(MysqlErrorNumbers.ER_TABLE_EXISTS_ERROR, ex.getErrorCode()); + } + try { + this.schema.createCollection(collName, false); + fail("Exception should be thrown trying to create a collection that already exists"); + } catch (XProtocolError ex) { + // expected + assertEquals(MysqlErrorNumbers.ER_TABLE_EXISTS_ERROR, ex.getErrorCode()); + } + Collection coll2 = this.schema.createCollection(collName, true); + assertEquals(coll, coll2); + } finally { + dropCollection(collName); } - Collection coll2 = this.schema.createCollection(collName, true); - assertEquals(coll, coll2); } @Test public void testDropCollection() { - if (!this.isSetForXTests) { - return; - } String collName = "testDropCollection"; dropCollection(collName); Collection coll = this.schema.getCollection(collName); @@ -188,9 +188,6 @@ public Void call() throws Exception { @Test public void testListTables() { - if (!this.isSetForXTests) { - return; - } String collName = "test_list_tables_collection"; String tableName = "test_list_tables_table"; String viewName = "test_list_tables_view"; @@ -228,9 +225,6 @@ public void testListTables() { @Test public void testCreateCollectionWithOptions() { - if (!this.isSetForXTests) { - return; - } String collName1 = "testCreateCollection1"; String collName2 = "testCreateCollection2"; dropCollection(collName1); diff --git a/src/test/java/testsuite/x/devapi/SecureSessionTest.java b/src/test/java/testsuite/x/devapi/SecureSessionTest.java index 85a967394..e5c631b7a 100644 --- a/src/test/java/testsuite/x/devapi/SecureSessionTest.java +++ b/src/test/java/testsuite/x/devapi/SecureSessionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. + * Copyright (c) 2017, 2021, 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 @@ -34,6 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.lang.reflect.Field; import java.util.Arrays; @@ -57,6 +58,7 @@ import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.PropertySet; import com.mysql.cj.exceptions.CJCommunicationsException; +import com.mysql.cj.exceptions.SSLParamsException; import com.mysql.cj.exceptions.WrongArgumentException; import com.mysql.cj.protocol.x.XAuthenticationProvider; import com.mysql.cj.protocol.x.XProtocol; @@ -78,12 +80,11 @@ public class SecureSessionTest extends DevApiBaseTestCase { final String clientKeyStorePassword = "password"; final Properties sslFreeTestProperties = (Properties) this.testProperties.clone(); - final Properties sslFreeTestPropertiesOpenSSL = (Properties) this.testPropertiesOpenSSL.clone(); String sslFreeBaseUrl = this.baseUrl; - String opensslTlsFreeBaseUrl = this.baseOpensslUrl; @BeforeEach public void setupSecureSessionTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); if (setupTestSession()) { System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStoreType"); @@ -109,8 +110,8 @@ public void setupSecureSessionTest() { this.sslFreeTestProperties.remove(PropertyKey.clientCertificateKeyStoreUrl.getKeyName()); this.sslFreeTestProperties.remove(PropertyKey.clientCertificateKeyStoreType.getKeyName()); this.sslFreeTestProperties.remove(PropertyKey.clientCertificateKeyStorePassword.getKeyName()); - this.sslFreeTestProperties.remove(PropertyKey.enabledSSLCipherSuites.getKeyName()); - this.sslFreeTestProperties.remove(PropertyKey.enabledTLSProtocols.getKeyName()); + this.sslFreeTestProperties.remove(PropertyKey.tlsCiphersuites.getKeyName()); + this.sslFreeTestProperties.remove(PropertyKey.tlsVersions.getKeyName()); this.sslFreeBaseUrl = this.baseUrl; this.sslFreeBaseUrl = this.sslFreeBaseUrl.replaceAll(PropertyKey.xdevapiSslMode.getKeyName() + "=", @@ -158,31 +159,6 @@ public void setupSecureSessionTest() { this.sslFreeBaseUrl += "?"; } } - if (this.isSetForOpensslXTests) { - this.sslFreeTestPropertiesOpenSSL.remove(PropertyKey.xdevapiSslMode.getKeyName()); - this.sslFreeTestPropertiesOpenSSL.remove(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName()); - this.sslFreeTestPropertiesOpenSSL.remove(PropertyKey.xdevapiSslTrustStorePassword.getKeyName()); - this.sslFreeTestPropertiesOpenSSL.remove(PropertyKey.xdevapiSslTrustStoreType.getKeyName()); - this.sslFreeTestPropertiesOpenSSL.remove(PropertyKey.xdevapiTlsCiphersuites.getKeyName()); - this.sslFreeTestPropertiesOpenSSL.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); - - this.opensslTlsFreeBaseUrl = this.baseOpensslUrl; - this.opensslTlsFreeBaseUrl = this.opensslTlsFreeBaseUrl.replaceAll(PropertyKey.xdevapiSslMode.getKeyName() + "=", - PropertyKey.xdevapiSslMode.getKeyName() + "VOID="); - this.opensslTlsFreeBaseUrl = this.opensslTlsFreeBaseUrl.replaceAll(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName() + "=", - PropertyKey.xdevapiSslTrustStoreUrl.getKeyName() + "VOID="); - this.opensslTlsFreeBaseUrl = this.opensslTlsFreeBaseUrl.replaceAll(PropertyKey.xdevapiSslTrustStorePassword.getKeyName() + "=", - PropertyKey.xdevapiSslTrustStorePassword.getKeyName() + "VOID="); - this.opensslTlsFreeBaseUrl = this.opensslTlsFreeBaseUrl.replaceAll(PropertyKey.xdevapiSslTrustStoreType.getKeyName() + "=", - PropertyKey.xdevapiSslTrustStoreType.getKeyName() + "VOID="); - this.opensslTlsFreeBaseUrl = this.opensslTlsFreeBaseUrl.replaceAll(PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "=", - PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "VOID="); - this.opensslTlsFreeBaseUrl = this.opensslTlsFreeBaseUrl.replaceAll(PropertyKey.xdevapiTlsVersions.getKeyName() + "=", - PropertyKey.xdevapiTlsVersions.getKeyName() + "VOID="); - if (!this.opensslTlsFreeBaseUrl.contains("?")) { - this.opensslTlsFreeBaseUrl += "?"; - } - } } @AfterEach @@ -201,9 +177,6 @@ public void teardownSecureSessionTest() { */ @Test public void testNonSecureSession() { - if (!this.isSetForXTests) { - return; - } try { Session testSession = this.fact.getSession(this.baseUrl); testSession.sql("CREATE USER IF NOT EXISTS 'testPlainAuth'@'%' IDENTIFIED WITH mysql_native_password BY 'pwd'").execute(); @@ -242,10 +215,6 @@ public void testNonSecureSession() { */ @Test public void testSecureSessionDefaultAndRequired() { - if (!this.isSetForXTests) { - return; - } - Session testSession = this.fact.getSession(this.sslFreeBaseUrl); assertSecureSession(testSession); testSession.close(); @@ -270,9 +239,8 @@ public void testSecureSessionDefaultAndRequired() { */ @Test public void testSecureSessionDefaultAndRequiredWithSystemPropsPresent() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); System.setProperty("javax.net.ssl.trustStore", this.trustStorePath); System.setProperty("javax.net.ssl.trustStorePassword", this.trustStorePassword); @@ -301,9 +269,8 @@ public void testSecureSessionDefaultAndRequiredWithSystemPropsPresent() { */ @Test public void testSecureSessionVerifyServerCertificate() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Session testSession = this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.VERIFY_CA) + makeParam(PropertyKey.xdevapiSslTrustStoreUrl, this.trustStoreUrl) @@ -325,9 +292,8 @@ public void testSecureSessionVerifyServerCertificate() { */ @Test public void testSecureSessionVerifyServerCertificateUsingSystemProps() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); System.setProperty("javax.net.ssl.trustStore", this.trustStorePath); System.setProperty("javax.net.ssl.trustStorePassword", this.trustStorePassword); @@ -350,10 +316,6 @@ public void testSecureSessionVerifyServerCertificateUsingSystemProps() { @Test @Disabled("requires a certificate with CN= equals to the host name in the test URL") public void testSecureSessionVerifyServerCertificateIdentity() { - if (!this.isSetForXTests) { - return; - } - Session testSession = this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.VERIFY_IDENTITY) + makeParam(PropertyKey.xdevapiSslTrustStoreUrl, this.trustStoreUrl) + makeParam(PropertyKey.xdevapiSslTrustStorePassword, this.trustStorePassword)); @@ -374,10 +336,6 @@ public void testSecureSessionVerifyServerCertificateIdentity() { */ @Test public void testSecureSessionMissingTrustStore() { - if (!this.isSetForXTests) { - return; - } - assertThrows(CJCommunicationsException.class, "No truststore provided to verify the Server certificate\\.", () -> this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiFallbackToSystemTrustStore, "false") + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.VERIFY_CA))); @@ -401,10 +359,6 @@ public void testSecureSessionMissingTrustStore() { */ @Test public void testSecureSessionVerifyServerCertificateIdentityFailure() { - if (!this.isSetForXTests) { - return; - } - // Meaningful error message is deep inside the stack trace. assertThrows(CJCommunicationsException.class, () -> this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.VERIFY_IDENTITY) @@ -424,10 +378,6 @@ public void testSecureSessionVerifyServerCertificateIdentityFailure() { */ @Test public void testSecureSessionIncompatibleSettings() { - if (!this.isSetForXTests) { - return; - } - String expectedError = "Incompatible security settings\\. " + "The property 'xdevapi.ssl-truststore' requires 'xdevapi.ssl-mode' as 'VERIFY_CA' or 'VERIFY_IDENTITY'\\."; assertThrows(CJCommunicationsException.class, expectedError, @@ -436,9 +386,6 @@ public void testSecureSessionIncompatibleSettings() { assertThrows(CJCommunicationsException.class, expectedError, () -> this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.REQUIRED) + makeParam(PropertyKey.xdevapiSslTrustStoreUrl, this.trustStoreUrl))); - assertThrows(CJCommunicationsException.class, expectedError, () -> this.fact.getSession(this.sslFreeBaseUrl - + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.DISABLED) + makeParam(PropertyKey.xdevapiSslTrustStoreUrl, this.trustStoreUrl))); - Properties props = new Properties(this.sslFreeTestProperties); props.setProperty(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName(), this.trustStoreUrl); assertThrows(CJCommunicationsException.class, expectedError, () -> this.fact.getSession(props)); @@ -446,10 +393,6 @@ public void testSecureSessionIncompatibleSettings() { props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), XdevapiSslMode.REQUIRED.toString()); props.setProperty(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName(), this.trustStoreUrl); assertThrows(CJCommunicationsException.class, expectedError, () -> this.fact.getSession(props)); - - props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), XdevapiSslMode.DISABLED.toString()); - props.setProperty(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName(), this.trustStoreUrl); - assertThrows(CJCommunicationsException.class, expectedError, () -> this.fact.getSession(props)); } /** @@ -459,10 +402,6 @@ public void testSecureSessionIncompatibleSettings() { */ @Test public void testAuthMechanisms() throws Throwable { - if (!this.isSetForXTests) { - return; - } - try { this.session.sql("CREATE USER IF NOT EXISTS 'testAuthMechNative'@'%' IDENTIFIED WITH mysql_native_password BY 'mysqlnative'").execute(); this.session.sql("GRANT SELECT ON *.* TO 'testAuthMechNative'@'%'").execute(); @@ -787,36 +726,24 @@ public void testAuthMechanisms() throws Throwable { /** * Tests TLSv1.2 * - * This test requires two server instances: - * 1) main xplugin server pointed to by the com.mysql.cj.testsuite.mysqlx.url variable, - * compiled with yaSSL - * 2) additional xplugin server instance pointed to by com.mysql.cj.testsuite.mysqlx.url.openssl, - * variable compiled with OpenSSL. - * - * For example, add these variables to the ant call: - * -Dcom.mysql.cj.testsuite.mysqlx.url=mysqlx://localhost:33060/cjtest_5_1?user=root&password=pwd - * -Dcom.mysql.cj.testsuite.mysqlx.url.openssl=mysqlx://localhost:33070/cjtest_5_1?user=root&password=pwd - * * @throws Exception */ @Test public void testTLSv1_2() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); // newer GPL servers, like 8.0.4+, are using OpenSSL and can use RSA encryption, while old ones compiled with yaSSL cannot - boolean gplWithRSA = allowsRsa(this.fact.getSession(this.sslFreeBaseUrl)); - - String highestCommonTlsVersion = getHighestCommonTlsVersion(this.fact.getSession(this.sslFreeBaseUrl)); + Session sess = this.fact.getSession(this.sslFreeBaseUrl); + boolean gplWithRSA = allowsRsa(sess); + String highestCommonTlsVersion = getHighestCommonTlsVersion(sess); + sess.close(); Properties props = new Properties(this.sslFreeTestProperties); props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.VERIFY_CA.toString()); props.setProperty(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName(), this.trustStoreUrl); props.setProperty(PropertyKey.xdevapiSslTrustStorePassword.getKeyName(), this.trustStorePassword); - /* Against yaSSL server */ - // defaults to TLSv1.1 Session testSession = this.fact.getSession(props); assertSecureSession(testSession); @@ -824,14 +751,12 @@ public void testTLSv1_2() throws Exception { testSession.close(); // restricted to TLSv1 - props.setProperty(PropertyKey.enabledTLSProtocols.getKeyName(), "TLSv1"); - testSession = this.fact.getSession(props); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1"); - testSession.close(); + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(props)); - // TLSv1.2 should fail - props.setProperty(PropertyKey.enabledTLSProtocols.getKeyName(), "TLSv1.2,TLSv1"); + // TLSv1.2 should fail with servers compiled with yaSSL + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1.2,TLSv1.1,TLSv1"); if (gplWithRSA) { testSession = this.fact.getSession(props); assertSecureSession(testSession); @@ -841,34 +766,6 @@ public void testTLSv1_2() throws Exception { assertThrows(CJCommunicationsException.class, "javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake", () -> this.fact.getSession(props)); } - - /* Against OpenSSL server */ - if (this.baseOpensslUrl != null && this.baseOpensslUrl.length() > 0) { - Properties propsOpenSSL = new Properties(this.sslFreeTestPropertiesOpenSSL); - propsOpenSSL.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.VERIFY_CA.toString()); - propsOpenSSL.setProperty(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName(), this.trustStoreUrl); - propsOpenSSL.setProperty(PropertyKey.xdevapiSslTrustStorePassword.getKeyName(), this.trustStorePassword); - - // defaults to TLSv1.1 - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, highestCommonTlsVersion); - testSession.close(); - - // restricted to TLSv1 - propsOpenSSL.setProperty(PropertyKey.enabledTLSProtocols.getKeyName(), "TLSv1"); - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1"); - testSession.close(); - - // TLSv1.2 - propsOpenSSL.setProperty(PropertyKey.enabledTLSProtocols.getKeyName(), "TLSv1.2,TLSv1.1,TLSv1"); - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); - } } private boolean allowsRsa(Session sess) { @@ -925,9 +822,8 @@ private String getHighestCommonTlsVersion(Session sess) throws Exception { */ @Test public void testBug25494338() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Session testSession = null; @@ -945,13 +841,13 @@ public void testBug25494338() { props.setProperty(PropertyKey.clientCertificateKeyStorePassword.getKeyName(), this.clientKeyStorePassword); // 1. Allow only TLS_DHE_RSA_WITH_AES_128_CBC_SHA cipher - props.setProperty(PropertyKey.enabledSSLCipherSuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + props.setProperty(PropertyKey.tlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); Session sess = this.fact.getSession(props); assertSessionStatusEquals(sess, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); sess.close(); // 2. Allow only TLS_RSA_WITH_AES_128_CBC_SHA cipher - props.setProperty(PropertyKey.enabledSSLCipherSuites.getKeyName(), "TLS_RSA_WITH_AES_128_CBC_SHA"); + props.setProperty(PropertyKey.tlsCiphersuites.getKeyName(), "TLS_RSA_WITH_AES_128_CBC_SHA"); sess = this.fact.getSession(props); assertSessionStatusEquals(sess, "mysqlx_ssl_cipher", "AES128-SHA"); assertSessionStatusEquals(sess, "ssl_cipher", ""); @@ -980,9 +876,8 @@ public void testBug25494338() { */ @Test public void testBug23597281() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Properties props = new Properties(this.sslFreeTestProperties); props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.VERIFY_CA.toString()); @@ -1004,10 +899,6 @@ public void testBug23597281() { */ @Test public void testBug26227653() { - if (!this.isSetForXTests) { - return; - } - System.setProperty("javax.net.ssl.trustStore", "dummy_truststore"); System.setProperty("javax.net.ssl.trustStorePassword", "some_password"); System.setProperty("javax.net.ssl.trustStoreType", "wrong_type"); @@ -1036,9 +927,8 @@ public void testBug26227653() { */ @Test public void testBug27629553() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Session testSession = this.fact.getSession(this.baseUrl); testSession.sql("CREATE USER IF NOT EXISTS 'testBug27629553'@'%' IDENTIFIED WITH mysql_native_password").execute(); @@ -1054,19 +944,20 @@ public void testBug27629553() { props.setProperty(PropertyKey.clientCertificateKeyStoreUrl.getKeyName(), this.clientKeyStoreUrl); props.setProperty(PropertyKey.clientCertificateKeyStorePassword.getKeyName(), this.clientKeyStorePassword); - this.fact.getSession(props); + testSession = this.fact.getSession(props); + testSession.close(); } @Test public void testXdevapiTlsVersionsAndCiphersuites() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + assumeTrue(supportsTestCertificates(this.session), "This test requires the server with RSA support."); // newer GPL servers, like 8.0.4+, are using OpenSSL and can use RSA encryption, while old ones compiled with yaSSL cannot - boolean gplWithRSA = allowsRsa(this.fact.getSession(this.sslFreeBaseUrl)); - - String highestCommonTlsVersion = getHighestCommonTlsVersion(this.fact.getSession(this.sslFreeBaseUrl)); + Session sess = this.fact.getSession(this.sslFreeBaseUrl); + String highestCommonTlsVersion = getHighestCommonTlsVersion(sess); + sess.close(); Properties props = new Properties(this.sslFreeTestProperties); props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.VERIFY_CA.toString()); @@ -1074,369 +965,337 @@ public void testXdevapiTlsVersionsAndCiphersuites() throws Exception { props.setProperty(PropertyKey.xdevapiSslTrustStorePassword.getKeyName(), this.trustStorePassword); Session testSession; - - props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); - - /* Against GPL server */ - - // defaults to TLSv1.1 - testSession = this.fact.getSession(props); + final ClientFactory cf = new ClientFactory(); + + // TS.FR.1_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions with a single TLS protocol. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). + // + // UPD: Behaviour was changed by WL#14805. + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1"))); + + // TS.FR.1_2. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions with a valid list of TLS protocols. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). + testSession = this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2,TLSv1.1,TLSv1")); assertSecureSession(testSession); - assertTlsVersion(testSession, highestCommonTlsVersion); + assertTlsVersion(testSession, "TLSv1.2"); testSession.close(); - // restricted to TLSv1 + // TS.FR.1_3. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a single TLS protocol. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). + // + // UPD: Behaviour was changed by WL#14805. props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1"); + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(props)); + + // TS.FR.1_4. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a valid list of TLS protocols. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2,TLSv1.1,TLSv1"); testSession = this.fact.getSession(props); assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1"); + assertTlsVersion(testSession, "TLSv1.2"); testSession.close(); - // TLSv1.2 should fail - props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2,TLSv1"); - if (gplWithRSA) { - testSession = this.fact.getSession(props); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); - } else { - assertThrows(CJCommunicationsException.class, "javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake", - () -> this.fact.getSession(props)); - } - - /* Against Commercial server */ - if (this.baseOpensslUrl != null && this.baseOpensslUrl.length() > 0) { - Properties propsOpenSSL = new Properties(this.sslFreeTestPropertiesOpenSSL); - propsOpenSSL.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.VERIFY_CA.toString()); - propsOpenSSL.setProperty(PropertyKey.xdevapiSslTrustStoreUrl.getKeyName(), this.trustStoreUrl); - propsOpenSSL.setProperty(PropertyKey.xdevapiSslTrustStorePassword.getKeyName(), this.trustStorePassword); - - final ClientFactory cf = new ClientFactory(); - - // TS.FR.1_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions with a single TLS protocol. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1")); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1"); - testSession.close(); - - // TS.FR.1_2. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions with a valid list of TLS protocols. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2,TLSv1.1,TLSv1")); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); - - // TS.FR.1_3. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a single TLS protocol. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1"); - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1"); - testSession.close(); - - // TS.FR.1_4. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a valid list of TLS protocols. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_version for details). - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2,TLSv1.1,TLSv1"); - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); - - // TS.FR.1_5. Repeat the tests TS.FR.1_1 and TS.FR.1_2 using a ClientFactory instead of a SessionFactory. - Client cli = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1"), "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1"); - cli.close(); + // TS.FR.1_5. Repeat the tests TS.FR.1_1 and TS.FR.1_2 using a ClientFactory instead of a SessionFactory. + // + // UPD: Behaviour was changed by WL#14805. + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.", () -> { + Client cli1 = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1"), "{\"pooling\": {\"enabled\": true}}"); + cli1.getSession(); + return null; + }); + + Client cli = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2,TLSv1.1,TLSv1"), + "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSecureSession(testSession); + assertTlsVersion(testSession, "TLSv1.2"); + cli.close(); + + // TS.FR.2_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions without any value. + // Assess that the code terminates with a WrongArgumentException containing the defined message. + // + // UPD: Behaviour was changed by WL#14805. + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, ""))); + + // TS.FR.2_2. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions without any value. + // Assess that the code terminates with a WrongArgumentException containing the defined message. + // + // UPD: Behaviour was changed by WL#14805. + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), ""); + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(props)); + + // TS.FR.2_3. Repeat the test TS.FR.2_1 using a ClientFactory instead of a SessionFactory. + // + // UPD: Behaviour was changed by WL#14805. + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.", () -> { + Client cli1 = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, ""), "{\"pooling\": {\"enabled\": true}}"); + cli1.getSession(); + return null; + }); + + // TS.FR.3_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions with + // an invalid value, for example SSLv3. Assess that the code terminates with a WrongArgumentException containing the defined message. + // + // UPD: Behaviour was changed by WL#14805. + assertThrows(SSLParamsException.class, "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "SSLv3"))); + + // TS.FR.3_2. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with + // an invalid value, for example SSLv3. Assess that the code terminates with a WrongArgumentException containing the defined message. + // + // UPD: Behaviour was changed by WL#14805. + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "SSLv3"); + assertThrows(SSLParamsException.class, "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.", + () -> this.fact.getSession(props)); + + // TS.FR.3_3. Repeat the test TS.FR.3_1 using a ClientFactory instead of a SessionFactory. + // + // UPD: Behaviour was changed by WL#14805. + assertThrows(SSLParamsException.class, "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.", + () -> { + Client cli1 = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "SSLv3"), "{\"pooling\": {\"enabled\": true}}"); + cli1.getSession(); + return null; + }); + + // TS.FR.4_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a single valid cipher-suite. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). + testSession = this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA")); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - cli = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2,TLSv1.1,TLSv1"), - "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - cli.close(); - - // TS.FR.2_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions without any value. - // Assess that the code terminates with a WrongArgumentException containing the defined message. - assertThrows(WrongArgumentException.class, "At least one TLS protocol version must be specified in 'xdevapi.tls-versions' list.", - () -> this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, ""))); - - // TS.FR.2_2. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions without any value. - // Assess that the code terminates with a WrongArgumentException containing the defined message. - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), ""); - assertThrows(WrongArgumentException.class, "At least one TLS protocol version must be specified in 'xdevapi.tls-versions' list.", - () -> this.fact.getSession(propsOpenSSL)); - - // TS.FR.2_3. Repeat the test TS.FR.2_1 using a ClientFactory instead of a SessionFactory. - assertThrows(WrongArgumentException.class, "At least one TLS protocol version must be specified in 'xdevapi.tls-versions' list.", () -> { - Client cli1 = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, ""), "{\"pooling\": {\"enabled\": true}}"); - cli1.getSession(); - return null; - }); - - // TS.FR.3_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-versions with - // an invalid value, for example SSLv3. Assess that the code terminates with a WrongArgumentException containing the defined message. - assertThrows(WrongArgumentException.class, - "'SSLv3' not recognized as a valid TLS protocol version \\(should be one of TLSv1.3, TLSv1.2, TLSv1.1, TLSv1\\).", - () -> this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "SSLv3"))); - - // TS.FR.3_2. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with - // an invalid value, for example SSLv3. Assess that the code terminates with a WrongArgumentException containing the defined message. - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "SSLv3"); - assertThrows(WrongArgumentException.class, - "'SSLv3' not recognized as a valid TLS protocol version \\(should be one of TLSv1.3, TLSv1.2, TLSv1.1, TLSv1\\).", - () -> this.fact.getSession(propsOpenSSL)); - - // TS.FR.3_3. Repeat the test TS.FR.3_1 using a ClientFactory instead of a SessionFactory. - assertThrows(WrongArgumentException.class, - "'SSLv3' not recognized as a valid TLS protocol version \\(should be one of TLSv1.3, TLSv1.2, TLSv1.1, TLSv1\\).", () -> { - Client cli1 = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "SSLv3"), - "{\"pooling\": {\"enabled\": true}}"); - cli1.getSession(); - return null; - }); - - // TS.FR.4_1. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a single valid cipher-suite. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA")); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + // TS.FR.4_2. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a valid list of cipher-suites. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). + testSession = this.fact + .getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,AES256-SHA256")); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - // TS.FR.4_2. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a valid list of cipher-suites. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). - testSession = this.fact - .getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,AES256-SHA256")); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + // TS.FR.4_3 Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a list of valid and invalid cipher-suites, + // starting with an invalid one. Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). + testSession = this.fact.getSession( + this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,TLS_DHE_RSA_WITH_AES_128_CBC_SHA")); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - // TS.FR.4_3 Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a list of valid and invalid cipher-suites, - // starting with an invalid one. Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl - + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,TLS_DHE_RSA_WITH_AES_128_CBC_SHA")); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + // TS.FR.4_4. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a single invalid cipher-suite. + // Assess that the connection property is initialized with the correct values and that the connection fails with an SSL error. + Throwable ex = assertThrows(CJCommunicationsException.class, "Unable to connect to any of the target hosts\\.", () -> { + this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5")); + return null; + }); + assertNotNull(ex.getCause()); + assertEquals("javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)", + ex.getCause().getMessage()); + + // TS.FR.4_5. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a single valid cipher-suite. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + testSession = this.fact.getSession(props); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + testSession.close(); - // TS.FR.4_4. Create an X DevAPI session using a connection string containing the connection property xdevapi.tls-ciphersuites with a single invalid cipher-suite. - // Assess that the connection property is initialized with the correct values and that the connection fails with an SSL error. - Throwable ex = assertThrows(CJCommunicationsException.class, "Unable to connect to any of the target hosts\\.", () -> { - this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5")); - return null; - }); - assertNotNull(ex.getCause()); - assertEquals("javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)", - ex.getCause().getMessage()); - - // TS.FR.4_5. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a single valid cipher-suite. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). - propsOpenSSL.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); - testSession = this.fact.getSession(propsOpenSSL); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - testSession.close(); + // TS.FR.4_6. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a valid list of cipher-suites. + // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,AES256-SHA256"); + testSession = this.fact.getSession(props); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + testSession.close(); - // TS.FR.4_6. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a valid list of cipher-suites. - // Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,AES256-SHA256"); - testSession = this.fact.getSession(propsOpenSSL); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - testSession.close(); + // TS.FR.4_7. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a list of valid and invalid cipher-suites, + // starting with an invalid one. Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + testSession = this.fact.getSession(props); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + testSession.close(); - // TS.FR.4_7. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-versions with a list of valid and invalid cipher-suites, - // starting with an invalid one. Assess that the connection property is initialized with the correct values and that the correct protocol was used (consult status variable ssl_cipher for details). - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); - testSession = this.fact.getSession(propsOpenSSL); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - testSession.close(); + // TS.FR.4_8. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-ciphersuites with a single invalid cipher-suite. + // Assess that the connection property is initialized with the correct values and that the connection fails with an SSL error. + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5"); + assertThrows(CJCommunicationsException.class, + "javax.net.ssl.SSLHandshakeException: No appropriate protocol \\(protocol is disabled or cipher suites are inappropriate\\)", () -> { + this.fact.getSession(props); + return null; + }); + + // TS.FR.4_9. Repeat the tests TS.FR.4_1 to TS.FR.4_4 using a ClientFactory instead of a SessionFactory. + cli = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), + "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - // TS.FR.4_8. Create an X DevAPI session using a connection properties map containing the connection property xdevapi.tls-ciphersuites with a single invalid cipher-suite. - // Assess that the connection property is initialized with the correct values and that the connection fails with an SSL error. - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5"); - assertThrows(CJCommunicationsException.class, - "javax.net.ssl.SSLHandshakeException: No appropriate protocol \\(protocol is disabled or cipher suites are inappropriate\\)", () -> { - this.fact.getSession(propsOpenSSL); - return null; - }); - - // TS.FR.4_9. Repeat the tests TS.FR.4_1 to TS.FR.4_4 using a ClientFactory instead of a SessionFactory. - cli = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), - "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + cli = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,AES256-SHA256"), + "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - cli = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,AES256-SHA256"), - "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + cli = cf.getClient( + this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), + "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - cli = cf.getClient( - this.opensslTlsFreeBaseUrl - + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), + ex = assertThrows(CJCommunicationsException.class, "Unable to connect to any of the target hosts\\.", () -> { + Client cli1 = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5"), "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); - - ex = assertThrows(CJCommunicationsException.class, "Unable to connect to any of the target hosts\\.", () -> { - Client cli1 = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5"), - "{\"pooling\": {\"enabled\": true}}"); - cli1.getSession(); - return null; - }); - assertNotNull(ex.getCause()); - assertEquals("javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)", - ex.getCause().getMessage()); - - // TS.FR.5_1. Create an X DevAPI session using a connection string without the connection properties xdevapi.tls-versions and xdevapi.tls-ciphersuites. - // Assess that the session is created successfully and the connection properties are initialized with the expected values. - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl); - assertSecureSession(testSession); - assertTlsVersion(testSession, highestCommonTlsVersion); - testSession.close(); - - // TS.FR.5_2. Create an X DevAPI session using a connection string with the connection property xdevapi.tls-versions but without xdevapi.tls-ciphersuites. - // Assess that the session is created successfully and the connection property xdevapi.tls-versions is initialized with the expected values. - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2")); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + cli1.getSession(); + return null; + }); + assertNotNull(ex.getCause()); + assertEquals("javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)", + ex.getCause().getMessage()); + + // TS.FR.5_1. Create an X DevAPI session using a connection string without the connection properties xdevapi.tls-versions and xdevapi.tls-ciphersuites. + // Assess that the session is created successfully and the connection properties are initialized with the expected values. + testSession = this.fact.getSession(this.sslFreeBaseUrl); + assertSecureSession(testSession); + assertTlsVersion(testSession, highestCommonTlsVersion); + testSession.close(); - // TS.FR.5_3. Create an X DevAPI session using a connection string with the connection property xdevapi.tls-ciphersuites but without xdevapi.tls-versions. - // Assess that the session is created successfully and the connection property xdevapi.tls-ciphersuites is initialized with the expected values. - testSession = this.fact.getSession(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA")); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + // TS.FR.5_2. Create an X DevAPI session using a connection string with the connection property xdevapi.tls-versions but without xdevapi.tls-ciphersuites. + // Assess that the session is created successfully and the connection property xdevapi.tls-versions is initialized with the expected values. + testSession = this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2")); + assertSecureSession(testSession); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - // TS.FR.5_4. Create an X DevAPI session using a connection properties map without the connection properties xdevapi.tls-versions and xdevapi.tls-ciphersuites. - // Assess that the session is created successfully and the connection properties are initialized with the expected values. - propsOpenSSL.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); - propsOpenSSL.remove(PropertyKey.xdevapiTlsCiphersuites.getKeyName()); - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, highestCommonTlsVersion); - testSession.close(); + // TS.FR.5_3. Create an X DevAPI session using a connection string with the connection property xdevapi.tls-ciphersuites but without xdevapi.tls-versions. + // Assess that the session is created successfully and the connection property xdevapi.tls-ciphersuites is initialized with the expected values. + testSession = this.fact.getSession(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA")); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - // TS.FR.5_5. Create an X DevAPI session using a connection properties map with the connection property xdevapi.tls-versions but without xdevapi.tls-ciphersuites. - // Assess that the session is created successfully and the connection property xdevapi.tls-versions is initialized with the expected values. - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); - testSession = this.fact.getSession(propsOpenSSL); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - testSession.close(); + // TS.FR.5_4. Create an X DevAPI session using a connection properties map without the connection properties xdevapi.tls-versions and xdevapi.tls-ciphersuites. + // Assess that the session is created successfully and the connection properties are initialized with the expected values. + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.remove(PropertyKey.xdevapiTlsCiphersuites.getKeyName()); + testSession = this.fact.getSession(props); + assertSecureSession(testSession); + assertTlsVersion(testSession, highestCommonTlsVersion); + testSession.close(); - // TS.FR.5_6. Create an X DevAPI session using a connection properties map with the connection property xdevapi.tls-ciphersuites but without xdevapi.tls-versions. - // Assess that the session is created successfully and the connection property xdevapi.tls-ciphersuites is initialized with the expected values. - propsOpenSSL.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); - testSession = this.fact.getSession(propsOpenSSL); - assertTlsVersion(testSession, "TLSv1.2"); - assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); - testSession.close(); + // TS.FR.5_5. Create an X DevAPI session using a connection properties map with the connection property xdevapi.tls-versions but without xdevapi.tls-ciphersuites. + // Assess that the session is created successfully and the connection property xdevapi.tls-versions is initialized with the expected values. + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); + testSession = this.fact.getSession(props); + assertSecureSession(testSession); + assertTlsVersion(testSession, "TLSv1.2"); + testSession.close(); - // TS.FR.5_7. Repeat the tests TS.FR.5_1 to TS.FR.5_3 using a ClientFactory instead of a SessionFactory. - cli = cf.getClient(this.opensslTlsFreeBaseUrl, "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSecureSession(testSession); - assertTlsVersion(testSession, highestCommonTlsVersion); - cli.close(); + // TS.FR.5_6. Create an X DevAPI session using a connection properties map with the connection property xdevapi.tls-ciphersuites but without xdevapi.tls-versions. + // Assess that the session is created successfully and the connection property xdevapi.tls-ciphersuites is initialized with the expected values. + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + testSession = this.fact.getSession(props); + assertTlsVersion(testSession, "TLSv1.2"); + assertSessionStatusEquals(testSession, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + testSession.close(); - cli = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2"), "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - cli.close(); + // TS.FR.5_7. Repeat the tests TS.FR.5_1 to TS.FR.5_3 using a ClientFactory instead of a SessionFactory. + cli = cf.getClient(this.sslFreeBaseUrl, "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSecureSession(testSession); + assertTlsVersion(testSession, highestCommonTlsVersion); + cli.close(); - cli = cf.getClient(this.opensslTlsFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), - "{\"pooling\": {\"enabled\": true}}"); - testSession = cli.getSession(); - assertSecureSession(testSession); - assertTlsVersion(testSession, "TLSv1.2"); - cli.close(); - - // TS.FR.6_1. Create an X DevAPI session using a connection string with the connection property xdevapi.ssl-mode=DISABLED and both the connection properties - // xdevapi.tls-versions and xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. - String xdevapiSSLMode = makeParam(PropertyKey.xdevapiSslMode, PropertyDefinitions.XdevapiSslMode.DISABLED.toString()); - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", - () -> this.fact.getSession(this.opensslTlsFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2") - + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"))); - - // TS.FR.6_2. Create an X DevAPI session using a connection string with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-versions - // but not xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", - () -> this.fact.getSession(this.opensslTlsFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2"))); - - // - // TS.FR.6_3. Create an X DevAPI session using a connection string with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-ciphersuites - // but not xdevapi.tls-versions. Assess that the code terminates with a WrongArgumentException containing the defined message. - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "' can not be specified when SSL connections are disabled.", - () -> this.fact.getSession( - this.opensslTlsFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"))); - - // - // TS.FR.6_4. Create an X DevAPI session using a connection properties map with the connection property xdevapi.ssl-mode=DISABLED and both the connection properties xdevapi.tls-versions - // and xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. - propsOpenSSL.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.DISABLED.toString()); - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", - () -> this.fact.getSession(propsOpenSSL)); - - // TS.FR.6_5. Create an X DevAPI session using a connection properties map with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-versions - // but not xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); - propsOpenSSL.remove(PropertyKey.xdevapiTlsCiphersuites.getKeyName()); - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", - () -> this.fact.getSession(propsOpenSSL)); - - // TS.FR.6_6. Create an X DevAPI session using a connection properties map with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-ciphersuites - // but not xdevapi.tls-versions. Assess that the code terminates with a WrongArgumentException containing the defined message. - propsOpenSSL.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); - propsOpenSSL.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "' can not be specified when SSL connections are disabled.", - () -> this.fact.getSession(propsOpenSSL)); - - // TS.FR.6_7. Repeat the tests TS.FR.6_1 to TS.FR.6_3 using a ClientFactory instead of a SessionFactory. - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> { - Client cli1 = cf.getClient(this.opensslTlsFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2"), - "{\"pooling\": {\"enabled\": true}}"); - cli1.getSession(); - return null; - }); - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> { - Client cli1 = cf.getClient( - this.opensslTlsFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2") - + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), - "{\"pooling\": {\"enabled\": true}}"); - cli1.getSession(); - return null; - }); - assertThrows(WrongArgumentException.class, - "Option '" + PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> { - Client cli1 = cf.getClient( - this.opensslTlsFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), - "{\"pooling\": {\"enabled\": true}}"); - cli1.getSession(); - return null; - }); - } + cli = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2"), "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSecureSession(testSession); + assertTlsVersion(testSession, "TLSv1.2"); + cli.close(); + cli = cf.getClient(this.sslFreeBaseUrl + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), + "{\"pooling\": {\"enabled\": true}}"); + testSession = cli.getSession(); + assertSecureSession(testSession); + assertTlsVersion(testSession, "TLSv1.2"); + cli.close(); + + // TS.FR.6_1. Create an X DevAPI session using a connection string with the connection property xdevapi.ssl-mode=DISABLED and both the connection properties + // xdevapi.tls-versions and xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. + String xdevapiSSLMode = makeParam(PropertyKey.xdevapiSslMode, PropertyDefinitions.XdevapiSslMode.DISABLED.toString()); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", + () -> this.fact.getSession(this.sslFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2") + + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"))); + + // TS.FR.6_2. Create an X DevAPI session using a connection string with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-versions + // but not xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", + () -> this.fact.getSession(this.sslFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2"))); + + // + // TS.FR.6_3. Create an X DevAPI session using a connection string with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-ciphersuites + // but not xdevapi.tls-versions. Assess that the code terminates with a WrongArgumentException containing the defined message. + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> this.fact + .getSession(this.sslFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"))); + + // + // TS.FR.6_4. Create an X DevAPI session using a connection properties map with the connection property xdevapi.ssl-mode=DISABLED and both the connection properties xdevapi.tls-versions + // and xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. + props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.DISABLED.toString()); + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", + () -> this.fact.getSession(props)); + + // TS.FR.6_5. Create an X DevAPI session using a connection properties map with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-versions + // but not xdevapi.tls-ciphersuites. Assess that the code terminates with a WrongArgumentException containing the defined message. + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); + props.remove(PropertyKey.xdevapiTlsCiphersuites.getKeyName()); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", + () -> this.fact.getSession(props)); + + // TS.FR.6_6. Create an X DevAPI session using a connection properties map with the connection property xdevapi.ssl-mode=DISABLED and the connection property xdevapi.tls-ciphersuites + // but not xdevapi.tls-versions. Assess that the code terminates with a WrongArgumentException containing the defined message. + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "' can not be specified when SSL connections are disabled.", + () -> this.fact.getSession(props)); + + // TS.FR.6_7. Repeat the tests TS.FR.6_1 to TS.FR.6_3 using a ClientFactory instead of a SessionFactory. + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> { + Client cli1 = cf.getClient(this.sslFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2"), + "{\"pooling\": {\"enabled\": true}}"); + cli1.getSession(); + return null; + }); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> { + Client cli1 = cf.getClient(this.sslFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsVersions, "TLSv1.2") + + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), "{\"pooling\": {\"enabled\": true}}"); + cli1.getSession(); + return null; + }); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsCiphersuites.getKeyName() + "' can not be specified when SSL connections are disabled.", () -> { + Client cli1 = cf.getClient( + this.sslFreeBaseUrl + xdevapiSSLMode + makeParam(PropertyKey.xdevapiTlsCiphersuites, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"), + "{\"pooling\": {\"enabled\": true}}"); + cli1.getSession(); + return null; + }); } /** @@ -1446,9 +1305,8 @@ public void testXdevapiTlsVersionsAndCiphersuites() throws Exception { */ @Test public void testXdevapiSslConnectionOptions() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Session testSess; PropertySet propSet; @@ -1630,9 +1488,8 @@ public void testXdevapiSslConnectionOptions() throws Exception { */ @Test public void testFallbackToSystemTrustStore() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); Session testSess; @@ -1726,9 +1583,8 @@ public void testFallbackToSystemTrustStore() throws Exception { */ @Test public void testFallbackToSystemKeyStore() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); final String user = "testFbToSysKS"; try { @@ -1822,4 +1678,229 @@ public void testFallbackToSystemKeyStore() throws Exception { testSession.close(); } } + + /** + * Tests fix for WL#14805, Remove support for TLS 1.0 and 1.1. + * + * @throws Exception + */ + @Test + public void testTLSVersionRemoval() throws Exception { + assumeTrue(supportsTestCertificates(this.session), + "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs"); + + Session sess = null; + Properties props = new Properties(this.sslFreeTestProperties); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name()); + props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true"); + + // TS.FR.1_1. Create a Connection with the connection property tlsVersions=TLSv1.2. Assess that the connection is created successfully and it is using TLSv1.2. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1.2"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertTlsVersion(sess, "TLSv1.2"); + sess.close(); + + props.remove(PropertyKey.tlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.2"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertTlsVersion(sess, "TLSv1.2"); + sess.close(); + + // TS.FR.1_2. Create a Connection with the connection property enabledTLSProtocols=TLSv1.2. Assess that the connection is created successfully and it is using TLSv1.2. + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.setProperty("enabledTLSProtocols", "TLSv1.2"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertTlsVersion(sess, "TLSv1.2"); + sess.close(); + props.remove("enabledTLSProtocols"); + + // TS.FR.2_1. Create a Connection with the connection property tlsCiphersuites=[valid-cipher-suite]. Assess that the connection is created successfully and it is using the cipher suite specified. + props.setProperty(PropertyKey.tlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertSessionStatusEquals(sess, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + sess.close(); + + props.remove(PropertyKey.tlsCiphersuites.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsCiphersuites.getKeyName(), "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertSessionStatusEquals(sess, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + sess.close(); + + // TS.FR.2_2. Create a Connection with the connection property enabledSSLCipherSuites=[valid-cipher-suite] . Assess that the connection is created successfully and it is using the cipher suite specified. + props.remove(PropertyKey.xdevapiTlsCiphersuites.getKeyName()); + props.setProperty("enabledSSLCipherSuites", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertSessionStatusEquals(sess, "mysqlx_ssl_cipher", "DHE-RSA-AES128-SHA"); + sess.close(); + props.remove("enabledSSLCipherSuites"); + + // TS.FR.3_1. Create a Connection with the connection property tlsVersions=TLSv1. Assess that the connection fails. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.remove(PropertyKey.tlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1"); + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + // TS.FR.3_2. Create a Connection with the connection property tlsVersions=TLSv1.1. Assess that the connection fails. + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1.1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.remove(PropertyKey.tlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1.1"); + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + + // TS.FR.3_3. Create a Connection with the connection property enabledTLSProtocols=TLSv1. Assess that the connection fails. + props.setProperty("enabledTLSProtocols", "TLSv1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + props.remove("enabledTLSProtocols"); + + // TS.FR.3_4. Create a Connection with the connection property enabledTLSProtocols=TLSv1.1. Assess that the connection fails. + props.setProperty("enabledTLSProtocols", "TLSv1.1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + props.remove("enabledTLSProtocols"); + + // TS.FR.4. Create a Connection with the connection property tlsVersions=TLSv1 and sslMode=DISABLED. Assess that the connection is created successfully and it is not using encryption. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1"); + props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), SslMode.DISABLED.name()); + sess = this.fact.getSession(props); + assertNonSecureSession(sess); + sess.close(); + props.remove(PropertyKey.tlsVersions.getKeyName()); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1"); + assertThrows(WrongArgumentException.class, + "Option '" + PropertyKey.xdevapiTlsVersions.getKeyName() + "' can not be specified when SSL connections are disabled.", + () -> this.fact.getSession(props)); + + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + props.remove(PropertyKey.xdevapiSslMode.getKeyName()); + + // TS.FR.5_1. Create a Connection with the connection property tlsVersions=FOO,BAR. + // Assess that the connection fails with the error message "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3." + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,BAR"); + assertThrows(CJCommunicationsException.class, + ".+Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,,,BAR"); + assertThrows(CJCommunicationsException.class, + ".+Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.remove(PropertyKey.tlsVersions.getKeyName()); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "FOO,BAR"); + assertThrows(SSLParamsException.class, + "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "FOO,,,BAR"); + assertThrows(SSLParamsException.class, + "Specified list of TLS versions only contains non valid TLS protocols. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + + // TS.FR.5_2. Create a Connection with the connection property tlsVersions=FOO,TLSv1.1. + // Assess that the connection fails with the error message "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3." + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,TLSv1.1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "FOO,TLSv1.1"); + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + + // TS.FR.5_3. Create a Connection with the connection property tlsVersions=TLSv1,TLSv1.1. + // Assess that the connection fails with the error message "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3." + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "TLSv1,TLSv1.1"); + assertThrows(SSLParamsException.class, "TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + + // TS.FR.6. Create a Connection with the connection property tlsVersions= (empty value). + // Assess that the connection fails with the error message "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv13." + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), ""); + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), " "); + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), ",,,"); + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), ", ,,"); + assertThrows(SSLParamsException.class, "Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + + // TS.FR.7. Create a Connection with the connection property tlsVersions=FOO,TLSv1,TLSv1.1,TLSv1.2. + // Assess that the connection is created successfully and it is using TLSv1.2. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "FOO,TLSv1,TLSv1.1,TLSv1.2"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertTlsVersion(sess, "TLSv1.2"); + sess.close(); + + props.remove(PropertyKey.tlsVersions.getKeyName()); + props.setProperty(PropertyKey.xdevapiTlsVersions.getKeyName(), "FOO,TLSv1,TLSv1.1,TLSv1.2"); + sess = this.fact.getSession(props); + assertSecureSession(sess); + assertTlsVersion(sess, "TLSv1.2"); + sess.close(); + props.remove(PropertyKey.xdevapiTlsVersions.getKeyName()); + + // TS.FR.9_1. Create an X DevAPI session with the property tlsVersions=TLSv1,TLSv1.1. + // Assess that the operation fails with the error message TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1,TLSv1.1"); + assertThrows(CJCommunicationsException.class, ".+TLS protocols TLSv1 and TLSv1.1 are not supported. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + // TS.FR.9_2. Create an Connection X DevAPI session with the property tlsVersions= (empty value). + // Assess that the operation fails with the error message Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv13. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), ""); + assertThrows(CJCommunicationsException.class, ".+Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), " "); + assertThrows(CJCommunicationsException.class, ".+Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), ",,,"); + assertThrows(CJCommunicationsException.class, ".+Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + + props.setProperty(PropertyKey.tlsVersions.getKeyName(), ", ,,"); + assertThrows(CJCommunicationsException.class, ".+Specified list of TLS versions is empty. Accepted values are TLSv1.2 and TLSv1.3.+", + () -> this.fact.getSession(props)); + props.remove(PropertyKey.tlsVersions.getKeyName()); + + // TS.FR.10. Create an X DevAPI session with the property tlsVersions=TLSv1.2 and xdevapi.ssl-mode=DISABLED. + // Assess that the session is created successfully and it is not using encryption. + props.setProperty(PropertyKey.tlsVersions.getKeyName(), "TLSv1.2"); + props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name()); + sess = this.fact.getSession(props); + assertNonSecureSession(sess); + sess.close(); + } } diff --git a/src/test/java/testsuite/x/devapi/SessionFailoverTest.java b/src/test/java/testsuite/x/devapi/SessionFailoverTest.java index 880f62082..8bd96955c 100644 --- a/src/test/java/testsuite/x/devapi/SessionFailoverTest.java +++ b/src/test/java/testsuite/x/devapi/SessionFailoverTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. + * Copyright (c) 2016, 2021, 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 @@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.IOException; import java.io.InputStream; @@ -67,7 +68,7 @@ public class SessionFailoverTest extends DevApiBaseTestCase { */ private String buildConnectionString(String... hosts) { StringBuilder url = new StringBuilder(ConnectionUrl.Type.XDEVAPI_SESSION.getScheme()).append("//"); - url.append(getTestUser()==null ? "" : getTestUser()).append(":").append(getTestPassword() == null ? "" : getTestPassword()).append("@").append("["); + url.append(getTestUser() == null ? "" : getTestUser()).append(":").append(getTestPassword() == null ? "" : getTestPassword()).append("@").append("["); String separator = ""; int priority = 100; for (String h : hosts) { @@ -93,11 +94,10 @@ private String buildConnectionStringNoUser(String... hosts) { @BeforeEach public void setupFailoverTest() { - if (this.isSetForXTests) { - StringBuilder sb = new StringBuilder(); - sb.append(getEncodedTestHost()).append(":").append(getTestPort()); - this.testsHost = sb.toString(); - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + StringBuilder sb = new StringBuilder(); + sb.append(getEncodedTestHost()).append(":").append(getTestPort()); + this.testsHost = sb.toString(); } /** @@ -107,10 +107,6 @@ public void setupFailoverTest() { */ @Test public void testGetSessionForSingleHost() throws Exception { - if (!this.isSetForXTests) { - return; - } - ConnectionsCounterFakeServer fakeServer = new ConnectionsCounterFakeServer(); String fakeHost = fakeServer.getHostPortPair(); @@ -130,10 +126,6 @@ public void testGetSessionForSingleHost() throws Exception { */ @Test public void testGetSessionForMultipleHostsWithFailover() throws Exception { - if (!this.isSetForXTests) { - return; - } - ConnectionsCounterFakeServer fakeServer = new ConnectionsCounterFakeServer(); String fakeHost = fakeServer.getHostPortPair(); @@ -222,10 +214,6 @@ public Void call() { @Test @Disabled("This test doesn't execute deterministically on some systems. It can be run manually in local systems when needed.") public void testConnectionTimeout() throws Exception { - if (!this.isSetForXTests) { - return; - } - String customFakeHost = System.getProperty(PropertyDefinitions.SYSP_testsuite_unavailable_host); String fakeHost = (customFakeHost != null && customFakeHost.trim().length() != 0) ? customFakeHost : "10.77.77.77:37070"; @@ -265,6 +253,7 @@ public void testConnectionTimeout() throws Exception { sess.sql("SELECT SLEEP(11)").execute(); long end = System.currentTimeMillis() - begin; assertTrue(end >= 11000 && end < 12000, "Expected: " + 11000 + ".." + 12000 + ". Got " + end); + sess.close(); // TS11_1 Set connection property xdevapi.connect-timeout=null, try to create Session, check that WrongArgumentException is thrown // with message "The connection property 'xdevapi.connect-timeout' only accepts integer values. The value 'null' can not be converted to an integer." diff --git a/src/test/java/testsuite/x/devapi/SessionTest.java b/src/test/java/testsuite/x/devapi/SessionTest.java index ba887f98e..760199837 100644 --- a/src/test/java/testsuite/x/devapi/SessionTest.java +++ b/src/test/java/testsuite/x/devapi/SessionTest.java @@ -36,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.FileNotFoundException; import java.lang.ref.WeakReference; @@ -45,6 +46,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; @@ -52,6 +54,7 @@ import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -68,6 +71,7 @@ import com.mysql.cj.Constants; import com.mysql.cj.CoreSession; import com.mysql.cj.ServerVersion; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.conf.PropertyDefinitions.Compression; import com.mysql.cj.conf.PropertyDefinitions.XdevapiSslMode; import com.mysql.cj.conf.PropertyKey; @@ -80,13 +84,17 @@ import com.mysql.cj.protocol.x.XProtocolError; import com.mysql.cj.x.protobuf.Mysqlx.ServerMessages; import com.mysql.cj.x.protobuf.MysqlxNotice.Frame; -import com.mysql.cj.x.protobuf.MysqlxNotice.Warning; import com.mysql.cj.xdevapi.Client; import com.mysql.cj.xdevapi.Client.ClientProperty; import com.mysql.cj.xdevapi.ClientFactory; import com.mysql.cj.xdevapi.ClientImpl; import com.mysql.cj.xdevapi.ClientImpl.PooledXProtocol; +import com.mysql.cj.xdevapi.Collection; +import com.mysql.cj.xdevapi.DbDoc; +import com.mysql.cj.xdevapi.DocResult; import com.mysql.cj.xdevapi.FindStatement; +import com.mysql.cj.xdevapi.JsonNumber; +import com.mysql.cj.xdevapi.JsonString; import com.mysql.cj.xdevapi.Row; import com.mysql.cj.xdevapi.RowResult; import com.mysql.cj.xdevapi.Schema; @@ -97,6 +105,7 @@ import com.mysql.cj.xdevapi.SqlMultiResult; import com.mysql.cj.xdevapi.SqlResult; import com.mysql.cj.xdevapi.SqlStatement; +import com.mysql.cj.xdevapi.Warning; import com.mysql.cj.xdevapi.XDevAPIError; import testsuite.InjectedSocketFactory; @@ -105,6 +114,7 @@ public class SessionTest extends DevApiBaseTestCase { @BeforeEach public void setupSessionTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); setupTestSession(); } @@ -137,10 +147,6 @@ private String getRandomTestSchemaName() { @Test public void urlWithDefaultSchema() { - if (!this.isSetForXTests) { - return; - } - try { // Create user with mysql_native_password authentication plugin as it can be used with any of the authentication mechanisms. this.session.sql("CREATE USER IF NOT EXISTS 'testUserN'@'%' IDENTIFIED WITH mysql_native_password BY 'testUserN'").execute(); @@ -193,10 +199,6 @@ public void urlWithDefaultSchema() { @Test public void urlWithoutDefaultSchema() { - if (!this.isSetForXTests) { - return; - } - try { // Create user with mysql_native_password authentication plugin as it can be used with any of the authentication mechanisms. this.session.sql("CREATE USER IF NOT EXISTS 'testUserN'@'%' IDENTIFIED WITH mysql_native_password BY 'testUserN'").execute(); @@ -247,10 +249,6 @@ public void urlWithoutDefaultSchema() { @Test public void invalidDefaultSchema() { - if (!this.isSetForXTests) { - return; - } - try { // Create user with mysql_native_password authentication plugin as it can be used with any of the authentication mechanisms. this.session.sql("CREATE USER IF NOT EXISTS 'testUserN'@'%' IDENTIFIED WITH mysql_native_password BY 'testUserN'").execute(); @@ -296,9 +294,6 @@ public void invalidDefaultSchema() { @Test public void createDropSchema() { - if (!this.isSetForXTests) { - return; - } String testSchemaName = getRandomTestSchemaName(); Schema newSchema = this.session.createSchema(testSchemaName); assertTrue(this.session.getSchemas().contains(newSchema)); @@ -308,9 +303,6 @@ public void createDropSchema() { @Test public void createAndReuseExistingSchema() { - if (!this.isSetForXTests) { - return; - } String testSchemaName = getRandomTestSchemaName(); Schema newSchema = this.session.createSchema(testSchemaName); assertTrue(this.session.getSchemas().contains(newSchema)); @@ -320,9 +312,6 @@ public void createAndReuseExistingSchema() { @Test public void listSchemas() { - if (!this.isSetForXTests) { - return; - } List schemas = this.session.getSchemas(); // we should have visibility of at least these two Schema infoSchema = this.session.getSchema("information_schema"); @@ -333,9 +322,6 @@ public void listSchemas() { @Test public void createExistingSchemaError() { - if (!this.isSetForXTests) { - return; - } String testSchemaName = getRandomTestSchemaName(); Schema newSchema = this.session.createSchema(testSchemaName); assertTrue(this.session.getSchemas().contains(newSchema)); @@ -352,26 +338,22 @@ public void createExistingSchemaError() { */ @Test public void errorOnPacketTooBig() { - if (!this.isSetForXTests) { - return; - } - try { - SqlStatement stmt = this.session.sql("select @@mysqlx_max_allowed_packet"); - SqlResult res = stmt.execute(); - Row r = res.next(); - long mysqlxMaxAllowedPacket = r.getLong(0); + System.gc(); // to free the memory from previous tests artifacts + SqlStatement stmt = this.session.sql("select @@mysqlx_max_allowed_packet"); + SqlResult res = stmt.execute(); + Row r = res.next(); + long mysqlxMaxAllowedPacket = r.getLong(0); - long size = 100 + mysqlxMaxAllowedPacket; - StringBuilder b = new StringBuilder(); - for (int i = 0; i < size; ++i) { - b.append('a'); - } - String s = b.append("\"}").toString(); - this.session.dropSchema(s); - fail("Large packet should cause an exception"); - } catch (CJPacketTooBigException ex) { - // expected + long size = 100 + mysqlxMaxAllowedPacket; + StringBuilder b = new StringBuilder(); + for (int i = 0; i < size; ++i) { + b.append('a'); } + String s = b.append("\"}").toString(); + assertThrows("Large packet should cause an exception", CJPacketTooBigException.class, () -> { + this.session.dropSchema(s); + return null; + }); } /** @@ -379,10 +361,6 @@ public void errorOnPacketTooBig() { */ @Test public void testBug21690043() { - if (!this.isSetForXTests) { - return; - } - try { this.session.sql("CREATE USER 'bug21690043user1'@'%' IDENTIFIED WITH mysql_native_password").execute(); this.session.sql("GRANT SELECT ON *.* TO 'bug21690043user1'@'%'").execute(); @@ -401,9 +379,6 @@ public void testBug21690043() { @Test public void basicSql() { - if (!this.isSetForXTests) { - return; - } SqlStatement stmt = this.session.sql("select 1,2,3 from dual"); SqlResult res = stmt.execute(); assertTrue(res.hasData()); @@ -426,9 +401,6 @@ public Void call() throws Exception { @Test public void sqlUpdate() { - if (!this.isSetForXTests) { - return; - } SqlStatement stmt = this.session.sql("set @cjTestVar = 1"); SqlResult res = stmt.execute(); assertFalse(res.hasData()); @@ -486,9 +458,6 @@ public Void call() throws Exception { @Test public void sqlArguments() { - if (!this.isSetForXTests) { - return; - } SqlStatement stmt = this.session.sql("select ? as a, 40 + ? as b, ? as c"); SqlResult res = stmt.bind(1).bind(2).bind(3).execute(); Row r = res.next(); @@ -499,55 +468,57 @@ public void sqlArguments() { @Test public void basicMultipleResults() { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop procedure if exists basicMultipleResults"); + sqlUpdate("create procedure basicMultipleResults() begin explain select 1; explain select 2; end"); + SqlStatement stmt = this.session.sql("call basicMultipleResults()"); + SqlResult res = stmt.execute(); + assertTrue(res.hasData()); + /* Row r = */ res.next(); + assertFalse(res.hasNext()); + assertTrue(res.nextResult()); + assertTrue(res.hasData()); + assertFalse(res.nextResult()); + assertFalse(res.nextResult()); + assertFalse(res.nextResult()); + } finally { + sqlUpdate("drop procedure if exists basicMultipleResults"); } - sqlUpdate("drop procedure if exists basicMultipleResults"); - sqlUpdate("create procedure basicMultipleResults() begin explain select 1; explain select 2; end"); - SqlStatement stmt = this.session.sql("call basicMultipleResults()"); - SqlResult res = stmt.execute(); - assertTrue(res.hasData()); - /* Row r = */ res.next(); - assertFalse(res.hasNext()); - assertTrue(res.nextResult()); - assertTrue(res.hasData()); - assertFalse(res.nextResult()); - assertFalse(res.nextResult()); - assertFalse(res.nextResult()); } @Test public void smartBufferMultipleResults() { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop procedure if exists basicMultipleResults"); + sqlUpdate("create procedure basicMultipleResults() begin explain select 1; explain select 2; end"); + SqlStatement stmt = this.session.sql("call basicMultipleResults()"); + /* SqlResult res = */ stmt.execute(); + // execute another statement, should work fine + this.session.sql("call basicMultipleResults()"); + this.session.sql("call basicMultipleResults()"); + this.session.sql("call basicMultipleResults()"); + } finally { + sqlUpdate("drop procedure if exists basicMultipleResults"); } - sqlUpdate("drop procedure if exists basicMultipleResults"); - sqlUpdate("create procedure basicMultipleResults() begin explain select 1; explain select 2; end"); - SqlStatement stmt = this.session.sql("call basicMultipleResults()"); - /* SqlResult res = */ stmt.execute(); - // execute another statement, should work fine - this.session.sql("call basicMultipleResults()"); - this.session.sql("call basicMultipleResults()"); - this.session.sql("call basicMultipleResults()"); } @Test public void sqlInsertAutoIncrementValue() { - if (!this.isSetForXTests) { - return; - } - - sqlUpdate("drop table if exists lastInsertId"); - sqlUpdate("create table lastInsertId (id int not null primary key auto_increment, name varchar(20) not null)"); + try { + sqlUpdate("drop table if exists lastInsertId"); + sqlUpdate("create table lastInsertId (id int not null primary key auto_increment, name varchar(20) not null)"); - SqlStatement stmt = this.session.sql("insert into lastInsertId values (null, 'a')"); - SqlResult res = stmt.execute(); + SqlStatement stmt = this.session.sql("insert into lastInsertId values (null, 'a')"); + SqlResult res = stmt.execute(); - assertFalse(res.hasData()); - assertEquals(1, res.getAffectedItemsCount()); - assertEquals(0, res.getWarningsCount()); - assertFalse(res.getWarnings().hasNext()); - assertEquals(new Long(1), res.getAutoIncrementValue()); + assertFalse(res.hasData()); + assertEquals(1, res.getAffectedItemsCount()); + assertEquals(0, res.getWarningsCount()); + assertFalse(res.getWarnings().hasNext()); + assertEquals(new Long(1), res.getAutoIncrementValue()); + } finally { + sqlUpdate("drop table if exists lastInsertId"); + } } /** @@ -557,10 +528,6 @@ public void sqlInsertAutoIncrementValue() { */ @Test public void testBug27652379() throws Exception { - if (!this.isSetForXTests) { - return; - } - Properties props = new Properties(); // Upper case keys. @@ -629,10 +596,6 @@ public void testBug27652379() throws Exception { */ @Test public void testBug23045604() { - if (!this.isSetForXTests) { - return; - } - String url = this.baseUrl; if (!url.contains("?")) { url += "?"; @@ -644,15 +607,12 @@ public void testBug23045604() { assertTrue(uri.contains("connectionTimeZone=Asia/Calcutta")); assertTrue(uri.contains("serverConfigCacheFactory=")); assertFalse(uri.contains(",")); + sess.close(); } @SuppressWarnings("unchecked") @Test public void testPooledSessions() throws Exception { - if (!this.isSetForXTests) { - return; - } - final ClientFactory cf = new ClientFactory(); final String url = this.baseUrl; final Properties props = new Properties(); @@ -1027,8 +987,8 @@ public void run() { cli0 = cf.getClient(this.baseUrl, "{\"pooling\": {\"enabled\": true, \"maxSize\" : 2}}"); s0 = cli0.getSession(); s1 = cli0.getSession(); - long id0 = ((SessionImpl) s0).getSession().getServerSession().getThreadId(); - long id1 = ((SessionImpl) s1).getSession().getServerSession().getThreadId(); + long id0 = ((SessionImpl) s0).getSession().getServerSession().getCapabilities().getThreadId(); + long id1 = ((SessionImpl) s1).getSession().getServerSession().getCapabilities().getThreadId(); s0.sql("SET @a='s0'").execute(); s0.sql("CREATE TEMPORARY TABLE testpooledsessionstmps0(x int)").execute(); @@ -1056,8 +1016,8 @@ public void run() { Session s0_new = cli0.getSession(); Session s1_new = cli0.getSession(); - assertEquals(id0, ((SessionImpl) s0_new).getSession().getServerSession().getThreadId()); - assertEquals(id1, ((SessionImpl) s1_new).getSession().getServerSession().getThreadId()); + assertEquals(id0, ((SessionImpl) s0_new).getSession().getServerSession().getCapabilities().getThreadId()); + assertEquals(id1, ((SessionImpl) s1_new).getSession().getServerSession().getCapabilities().getThreadId()); res = s0_new.sql("SELECT @a as a").execute(); assertTrue(res.hasNext()); @@ -1108,10 +1068,6 @@ private void testPooledSessions_assertFailureTimeout(Clie @Test public void testBug28616573() throws Exception { - if (!this.isSetForXTests) { - return; - } - RowResult res = this.session.sql( "select @@global.mysqlx_max_connections, VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Mysqlx_worker_threads_active'") .execute(); @@ -1150,9 +1106,8 @@ public void testBug28616573() throws Exception { @Test public void testBug28606708() throws Exception { - if (!this.isSetForXTests || !isServerRunningOnWindows()) { - return; - } + assumeTrue(isServerRunningOnWindows() && isMysqlRunningLocally(), + "This test can run only when client and server are running on the same Windows host."); for (String path : new String[] { null, "\\\\.\\pipe\\MySQL80" }) { String url = this.baseUrl + makeParam(PropertyKey.socketFactory, "com.mysql.cj.protocol.NamedPipeSocketFactory"); @@ -1182,9 +1137,8 @@ public void testBug28606708() throws Exception { @Test public void testSessionAttributes() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.16"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.16")), "MySQL 8.0.16+ is required to run this test."); + ClientFactory cf = new ClientFactory(); Map userAttributes = new HashMap<>(); @@ -1368,9 +1322,7 @@ private void testSessionAttributes_checkSession(Session s, Map u Row r = res.next(); String key = r.getString(1); String val = r.getString(2); - if (!matchValues.containsKey(key)) { - fail("Unexpected connection attribute key: " + key); - } + assertTrue(matchValues.containsKey(key), "Unexpected connection attribute key: " + key); Integer cnt = matchedCounts.get(key); matchedCounts.put(key, cnt == null ? 1 : cnt++); @@ -1382,11 +1334,8 @@ private void testSessionAttributes_checkSession(Session s, Map u assertEquals(expected, val); } for (String key : matchValues.keySet()) { - if (!matchedCounts.containsKey(key)) { - fail("Incorrect number of entries for key \"" + key + "\": 0"); - } else if (matchedCounts.get(key) != 1) { - fail("Incorrect number of entries for key \"" + key + "\": " + matchedCounts.get(key)); - } + assertTrue(matchedCounts.containsKey(key), "Incorrect number of entries for key \"" + key + "\": 0"); + assertTrue(matchedCounts.get(key) == 1, "Incorrect number of entries for key \"" + key + "\": " + matchedCounts.get(key)); } } @@ -1414,9 +1363,7 @@ private void testSessionAttributes_checkClient(String url, Map u @Test public void testPreparedStatementsCleanup() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); try { // Prepare test data. @@ -1525,9 +1472,7 @@ public void testPreparedStatementsCleanup() { @Test public void testPreparedStatementsPooledConnections() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); Properties props = new Properties(); props.setProperty(ClientProperty.POOLING_ENABLED.getKeyName(), "true"); @@ -1606,9 +1551,6 @@ public void testPreparedStatementsPooledConnections() { @Test public void testBug23721537() throws Exception { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists testBug23721537"); sqlUpdate("create table testBug23721537 (id int, name varchar(20) not null)"); @@ -1697,9 +1639,6 @@ public void testBug23721537() throws Exception { @Test public void basicSessionFailoverRandomSort() { - if (!this.isSetForXTests) { - return; - } UnreliableSocketFactory.flushAllStaticData(); final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + PropertyKey.xdevapiConnectTimeout.getKeyName() + "=100&" @@ -1740,9 +1679,6 @@ public void basicSessionFailoverRandomSort() { @Test public void basicSessionFailoverByPriorities() { - if (!this.isSetForXTests) { - return; - } UnreliableSocketFactory.flushAllStaticData(); final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + PropertyKey.xdevapiConnectTimeout.getKeyName() + "=100&" @@ -1791,9 +1727,6 @@ public void basicSessionFailoverByPriorities() { @Test public void pooledSessionFailoverRandomSortAndPooling() { - if (!this.isSetForXTests) { - return; - } UnreliableSocketFactory.flushAllStaticData(); final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + PropertyKey.xdevapiConnectTimeout.getKeyName() + "=100&" @@ -1849,9 +1782,6 @@ public void pooledSessionFailoverRandomSortAndPooling() { @Test public void pooledSessionFailoverRandomSortAndNoPooling() { - if (!this.isSetForXTests) { - return; - } UnreliableSocketFactory.flushAllStaticData(); final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + PropertyKey.xdevapiConnectTimeout.getKeyName() + "=100&" @@ -1901,9 +1831,6 @@ public void pooledSessionFailoverRandomSortAndNoPooling() { @Test public void pooledSessionFailoverByPrioritiesAndPooling() { - if (!this.isSetForXTests) { - return; - } UnreliableSocketFactory.flushAllStaticData(); final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + PropertyKey.xdevapiConnectTimeout.getKeyName() + "=100&" @@ -1974,9 +1901,6 @@ public void pooledSessionFailoverByPrioritiesAndPooling() { @Test public void pooledSessionFailoverByPrioritiesAndNoPooling() { - if (!this.isSetForXTests) { - return; - } UnreliableSocketFactory.flushAllStaticData(); final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + PropertyKey.xdevapiConnectTimeout.getKeyName() + "=100&" @@ -2039,24 +1963,22 @@ public void pooledSessionFailoverByPrioritiesAndNoPooling() { client.close(); } - @SuppressWarnings("unchecked") @Test public void testConnectionCloseNotification() throws Exception { - if (!this.isSetForXTests) { - return; - } - String shutdownMessage = "Server shutdown in progress"; String ioReadErrorMessage = "IO Read error: read_timeout exceeded"; String sessionWasKilledMessage = "Session was killed"; // create notice message buffers Frame.Builder shutdownNotice = Frame.newBuilder().setScope(Frame.Scope.GLOBAL).setType(Frame.Type.WARNING_VALUE) - .setPayload(Warning.newBuilder().setCode(MysqlErrorNumbers.ER_SERVER_SHUTDOWN).setMsg(shutdownMessage).build().toByteString()); + .setPayload(com.mysql.cj.x.protobuf.MysqlxNotice.Warning.newBuilder().setCode(MysqlErrorNumbers.ER_SERVER_SHUTDOWN).setMsg(shutdownMessage) + .build().toByteString()); Frame.Builder ioReadErrorNotice = Frame.newBuilder().setScope(Frame.Scope.GLOBAL).setType(Frame.Type.WARNING_VALUE) - .setPayload(Warning.newBuilder().setCode(MysqlErrorNumbers.ER_IO_READ_ERROR).setMsg(ioReadErrorMessage).build().toByteString()); + .setPayload(com.mysql.cj.x.protobuf.MysqlxNotice.Warning.newBuilder().setCode(MysqlErrorNumbers.ER_IO_READ_ERROR).setMsg(ioReadErrorMessage) + .build().toByteString()); Frame.Builder sessionWasKilledNotice = Frame.newBuilder().setScope(Frame.Scope.GLOBAL).setType(Frame.Type.WARNING_VALUE) - .setPayload(Warning.newBuilder().setCode(MysqlErrorNumbers.ER_SESSION_WAS_KILLED).setMsg(sessionWasKilledMessage).build().toByteString()); + .setPayload(com.mysql.cj.x.protobuf.MysqlxNotice.Warning.newBuilder().setCode(MysqlErrorNumbers.ER_SESSION_WAS_KILLED) + .setMsg(sessionWasKilledMessage).build().toByteString()); byte[] shutdownNoticeBytes = makeNoticeBytes(shutdownNotice.build()); byte[] ioReadErrorNoticeBytes = makeNoticeBytes(ioReadErrorNotice.build()); @@ -2170,120 +2092,123 @@ public void testConnectionCloseNotification() throws Exception { assertFalse(res1.hasNext()); assertThrows(CJCommunicationsException.class, sessionWasKilledMessage, () -> sess34.getSchemas()); - /* - * With pooling. - */ - - this.fact.getSession(this.baseOpensslUrl); - - final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + makeParam(PropertyKey.socketFactory, InjectedSocketFactory.class.getName()) - + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.DISABLED.toString()) + makeParam(PropertyKey.allowPublicKeyRetrieval, "true") - + makeParam(PropertyKey.xdevapiCompression, Compression.DISABLED.toString()) - // to allow injection between result rows - + makeParam(PropertyKey.useReadAheadInput, "false"); - - String testHosts = "(address=" + getTestHost() + ":" + getTestPort() + "),(address=" + getTestSslHost() + ":" + getTestSslPort() + ")"; - url = String.format(testUriPattern, getTestUser() == null ? "" : getTestUser(), getTestPassword() == null ? "" : getTestPassword(), testHosts, - getTestDatabase()); - - Field fProtocol = CoreSession.class.getDeclaredField("protocol"); - fProtocol.setAccessible(true); - - Field idleProtocols = ClientImpl.class.getDeclaredField("idleProtocols"); - idleProtocols.setAccessible(true); - Field activeProtocols = ClientImpl.class.getDeclaredField("activeProtocols"); - activeProtocols.setAccessible(true); - Field nonPooledSessions = ClientImpl.class.getDeclaredField("nonPooledSessions"); - nonPooledSessions.setAccessible(true); - - String host1 = getTestHost(); - String host2 = getTestSslHost(); - int port1 = getTestPort(); - int port2 = getTestSslPort(); - - final ClientFactory cf = new ClientFactory(); - Client cli0 = cf.getClient(url, "{\"pooling\": {\"enabled\": true}}"); - - InjectedSocketFactory.downHost(getTestSslHost() + ":" + getTestSslPort()); - Session s1_1 = cli0.getSession(); - Session s1_2 = cli0.getSession(); - Session s1_3 = cli0.getSession(); - - assertEquals(host1, ((SessionImpl) s1_1).getSession().getProcessHost()); - assertEquals(host1, ((SessionImpl) s1_2).getSession().getProcessHost()); - assertEquals(host1, ((SessionImpl) s1_3).getSession().getProcessHost()); - assertEquals(port1, ((SessionImpl) s1_1).getSession().getPort()); - assertEquals(port1, ((SessionImpl) s1_2).getSession().getPort()); - assertEquals(port1, ((SessionImpl) s1_3).getSession().getPort()); - - InjectedSocketFactory.dontDownHost(getTestSslHost() + ":" + getTestSslPort()); - InjectedSocketFactory.downHost(getTestHost() + ":" + getTestPort()); - Session s2_1 = cli0.getSession(); - Session s2_2 = cli0.getSession(); - Session s2_3 = cli0.getSession(); - - assertEquals(host2, ((SessionImpl) s2_1).getSession().getProcessHost()); - assertEquals(host2, ((SessionImpl) s2_2).getSession().getProcessHost()); - assertEquals(host2, ((SessionImpl) s2_3).getSession().getProcessHost()); - assertEquals(port2, ((SessionImpl) s2_1).getSession().getPort()); - assertEquals(port2, ((SessionImpl) s2_2).getSession().getPort()); - assertEquals(port2, ((SessionImpl) s2_3).getSession().getPort()); - - InjectedSocketFactory.dontDownHost(getTestHost() + ":" + getTestPort()); - InjectedSocketFactory.downHost(getTestSslHost() + ":" + getTestSslPort()); - Session s1 = cli0.getSession(); - - assertEquals(host1, ((SessionImpl) s1).getSession().getProcessHost()); - assertEquals(port1, ((SessionImpl) s1).getSession().getPort()); - - s1_1.close(); - s1_2.close(); - s1_3.close(); - s2_1.close(); - s2_2.close(); - s2_3.close(); - - assertEquals(1, ((Set>) activeProtocols.get(cli0)).size()); - assertEquals(6, ((BlockingQueue) idleProtocols.get(cli0)).size()); - - // ER_IO_READ_ERROR - - InjectedSocketFactory.injectedBuffer = ioReadErrorNoticeBytes; - assertThrows(CJCommunicationsException.class, ioReadErrorMessage, () -> s1.sql("SELECT 1").execute()); - - assertEquals(0, ((Set>) activeProtocols.get(cli0)).size()); - assertEquals(6, ((BlockingQueue) idleProtocols.get(cli0)).size()); - assertThrows(CJCommunicationsException.class, "Unable to write message", () -> s1.sql("SELECT 1").execute()); - - // ER_SESSION_WAS_KILLED - - Session s2 = cli0.getSession(); - assertEquals(host1, ((SessionImpl) s2).getSession().getProcessHost()); - assertEquals(port1, ((SessionImpl) s2).getSession().getPort()); - assertEquals(1, ((Set>) activeProtocols.get(cli0)).size()); - assertEquals(5, ((BlockingQueue) idleProtocols.get(cli0)).size()); - - InjectedSocketFactory.injectedBuffer = sessionWasKilledNoticeBytes; - assertThrows(CJCommunicationsException.class, sessionWasKilledMessage, () -> s2.sql("SELECT 1").execute()); - - assertEquals(0, ((Set>) activeProtocols.get(cli0)).size()); - assertEquals(5, ((BlockingQueue) idleProtocols.get(cli0)).size()); - assertThrows(CJCommunicationsException.class, "Unable to write message", () -> s2.sql("SELECT 1").execute()); - - // ER_SERVER_SHUTDOWN - - Session s3 = cli0.getSession(); - assertEquals(host1, ((SessionImpl) s3).getSession().getProcessHost()); - assertEquals(port1, ((SessionImpl) s3).getSession().getPort()); - assertEquals(1, ((Set>) activeProtocols.get(cli0)).size()); - assertEquals(4, ((BlockingQueue) idleProtocols.get(cli0)).size()); - - InjectedSocketFactory.injectedBuffer = shutdownNoticeBytes; - assertThrows(CJCommunicationsException.class, shutdownMessage, () -> s3.sql("SELECT 1").execute()); - - assertEquals(0, ((Set>) activeProtocols.get(cli0)).size()); - assertEquals(3, ((BlockingQueue) idleProtocols.get(cli0)).size()); - + // TODO use a mock server instead of a real second server instance + // /* + // * With pooling. + // */ + // + // if (this.isSetForOpensslXTests) { + // + // this.fact.getSession(this.baseOpensslUrl); + // + // final String testUriPattern = "mysqlx://%s:%s@[%s]/%s?" + makeParam(PropertyKey.socketFactory, InjectedSocketFactory.class.getName()) + // + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.DISABLED.toString()) + makeParam(PropertyKey.allowPublicKeyRetrieval, "true") + // + makeParam(PropertyKey.xdevapiCompression, Compression.DISABLED.toString()) + // // to allow injection between result rows + // + makeParam(PropertyKey.useReadAheadInput, "false"); + // + // String testHosts = "(address=" + getTestHost() + ":" + getTestPort() + "),(address=" + getTestSslHost() + ":" + getTestSslPort() + ")"; + // url = String.format(testUriPattern, getTestUser() == null ? "" : getTestUser(), getTestPassword() == null ? "" : getTestPassword(), testHosts, + // getTestDatabase()); + // + // Field fProtocol = CoreSession.class.getDeclaredField("protocol"); + // fProtocol.setAccessible(true); + // + // Field idleProtocols = ClientImpl.class.getDeclaredField("idleProtocols"); + // idleProtocols.setAccessible(true); + // Field activeProtocols = ClientImpl.class.getDeclaredField("activeProtocols"); + // activeProtocols.setAccessible(true); + // Field nonPooledSessions = ClientImpl.class.getDeclaredField("nonPooledSessions"); + // nonPooledSessions.setAccessible(true); + // + // String host1 = getTestHost(); + // String host2 = getTestSslHost(); + // int port1 = getTestPort(); + // int port2 = getTestSslPort(); + // + // final ClientFactory cf = new ClientFactory(); + // Client cli0 = cf.getClient(url, "{\"pooling\": {\"enabled\": true}}"); + // + // InjectedSocketFactory.downHost(getTestSslHost() + ":" + getTestSslPort()); + // Session s1_1 = cli0.getSession(); + // Session s1_2 = cli0.getSession(); + // Session s1_3 = cli0.getSession(); + // + // assertEquals(host1, ((SessionImpl) s1_1).getSession().getProcessHost()); + // assertEquals(host1, ((SessionImpl) s1_2).getSession().getProcessHost()); + // assertEquals(host1, ((SessionImpl) s1_3).getSession().getProcessHost()); + // assertEquals(port1, ((SessionImpl) s1_1).getSession().getPort()); + // assertEquals(port1, ((SessionImpl) s1_2).getSession().getPort()); + // assertEquals(port1, ((SessionImpl) s1_3).getSession().getPort()); + // + // InjectedSocketFactory.dontDownHost(getTestSslHost() + ":" + getTestSslPort()); + // InjectedSocketFactory.downHost(getTestHost() + ":" + getTestPort()); + // Session s2_1 = cli0.getSession(); + // Session s2_2 = cli0.getSession(); + // Session s2_3 = cli0.getSession(); + // + // assertEquals(host2, ((SessionImpl) s2_1).getSession().getProcessHost()); + // assertEquals(host2, ((SessionImpl) s2_2).getSession().getProcessHost()); + // assertEquals(host2, ((SessionImpl) s2_3).getSession().getProcessHost()); + // assertEquals(port2, ((SessionImpl) s2_1).getSession().getPort()); + // assertEquals(port2, ((SessionImpl) s2_2).getSession().getPort()); + // assertEquals(port2, ((SessionImpl) s2_3).getSession().getPort()); + // + // InjectedSocketFactory.dontDownHost(getTestHost() + ":" + getTestPort()); + // InjectedSocketFactory.downHost(getTestSslHost() + ":" + getTestSslPort()); + // Session s1 = cli0.getSession(); + // + // assertEquals(host1, ((SessionImpl) s1).getSession().getProcessHost()); + // assertEquals(port1, ((SessionImpl) s1).getSession().getPort()); + // + // s1_1.close(); + // s1_2.close(); + // s1_3.close(); + // s2_1.close(); + // s2_2.close(); + // s2_3.close(); + // + // assertEquals(1, ((Set>) activeProtocols.get(cli0)).size()); + // assertEquals(6, ((BlockingQueue) idleProtocols.get(cli0)).size()); + // + // // ER_IO_READ_ERROR + // + // InjectedSocketFactory.injectedBuffer = ioReadErrorNoticeBytes; + // assertThrows(CJCommunicationsException.class, ioReadErrorMessage, () -> s1.sql("SELECT 1").execute()); + // + // assertEquals(0, ((Set>) activeProtocols.get(cli0)).size()); + // assertEquals(6, ((BlockingQueue) idleProtocols.get(cli0)).size()); + // assertThrows(CJCommunicationsException.class, "Unable to write message", () -> s1.sql("SELECT 1").execute()); + // + // // ER_SESSION_WAS_KILLED + // + // Session s2 = cli0.getSession(); + // assertEquals(host1, ((SessionImpl) s2).getSession().getProcessHost()); + // assertEquals(port1, ((SessionImpl) s2).getSession().getPort()); + // assertEquals(1, ((Set>) activeProtocols.get(cli0)).size()); + // assertEquals(5, ((BlockingQueue) idleProtocols.get(cli0)).size()); + // + // InjectedSocketFactory.injectedBuffer = sessionWasKilledNoticeBytes; + // assertThrows(CJCommunicationsException.class, sessionWasKilledMessage, () -> s2.sql("SELECT 1").execute()); + // + // assertEquals(0, ((Set>) activeProtocols.get(cli0)).size()); + // assertEquals(5, ((BlockingQueue) idleProtocols.get(cli0)).size()); + // assertThrows(CJCommunicationsException.class, "Unable to write message", () -> s2.sql("SELECT 1").execute()); + // + // // ER_SERVER_SHUTDOWN + // + // Session s3 = cli0.getSession(); + // assertEquals(host1, ((SessionImpl) s3).getSession().getProcessHost()); + // assertEquals(port1, ((SessionImpl) s3).getSession().getPort()); + // assertEquals(1, ((Set>) activeProtocols.get(cli0)).size()); + // assertEquals(4, ((BlockingQueue) idleProtocols.get(cli0)).size()); + // + // InjectedSocketFactory.injectedBuffer = shutdownNoticeBytes; + // assertThrows(CJCommunicationsException.class, shutdownMessage, () -> s3.sql("SELECT 1").execute()); + // + // assertEquals(0, ((Set>) activeProtocols.get(cli0)).size()); + // assertEquals(3, ((BlockingQueue) idleProtocols.get(cli0)).size()); + // } } private byte[] makeNoticeBytes(MessageLite msg) { @@ -2302,10 +2227,6 @@ private byte[] makeNoticeBytes(MessageLite msg) { */ @Test public void testBug97730() throws Throwable { - if (!this.isSetForXTests) { - return; - } - for (String pooling : new String[] { "false", "true" }) { Properties props = new Properties(); props.setProperty(ClientProperty.POOLING_ENABLED.getKeyName(), pooling); @@ -2339,4 +2260,337 @@ public void testBug97730() throws Throwable { } } } + + @Test + public void testExecAsync() throws Exception { + int i = 0; + int NUMBER_OF_QUERIES = 5000; + SqlResult sqlRes = null; + Row r = null; + + CompletableFuture asyncRes = null; + long l1 = Long.MAX_VALUE, l2 = Long.MIN_VALUE; + try { + asyncRes = this.session.sql("drop Procedure if exists testExecAsyncProc").executeAsync(); + sqlRes = asyncRes.get(); + + asyncRes = this.session.sql("create procedure testExecAsyncProc (in p1 int,in p2 int) begin select 1; select p1; select p2; end;").executeAsync(); + sqlRes = asyncRes.get(); + + asyncRes = this.session.sql("drop table if exists testExecAsync").executeAsync(); + sqlRes = asyncRes.get(); + + asyncRes = this.session.sql( + "create table testExecAsync (c1 int,c2 bigint,c3 double,c4 bool,c5 year,c6 float,c7 blob,c8 enum('xs','s','m','l','xl'),c9 set('a','b','c','d'))") + .executeAsync(); + sqlRes = asyncRes.get(); + + asyncRes = this.session.sql("insert into testExecAsync values(1," + l1 + ",100.11,true,2000,100.101,'v1','xs',('a,b'))").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(1, sqlRes.getAffectedItemsCount()); + + asyncRes = this.session.sql("insert into testExecAsync values(2," + (l1 - 1) + ",101.11,false,2001,101.101,'v2','s',('b,c')),(3," + (l2 - 2) + + ",102.11,true,2002,102.101,'v3','m',('a,b,c'))").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(2, sqlRes.getAffectedItemsCount()); + + asyncRes = this.session.sql("insert into testExecAsync values(4," + (l1 - 4) + ",104.11,false,2004,104.101,'v4','s',('b,c,d')),(5," + (l2 - 5) + + ",105.11,true,2005,105.101,'v5','m',('a,c'))").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(2, sqlRes.getAffectedItemsCount()); + + this.session.startTransaction(); + + asyncRes = this.session.sql("insert into testExecAsync select * from testExecAsync").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(5, sqlRes.getAffectedItemsCount()); + + asyncRes = this.session.sql( + "select c1 as 'col1',c2 as 'col2', c3 as 'col3',c4 as 'col4',c5 as 'col5', c6 as 'col6', c7 as 'col7', c8 as 'col8' , c9 as 'col9' from testExecAsync order by c1 asc") + .executeAsync(); + sqlRes = asyncRes.get(); + assertTrue(sqlRes.hasData()); + + while (sqlRes.hasNext()) { + r = sqlRes.next(); + // System.out.println("col1 :" + r.getInt("col1")); + // System.out.println("col2 :" + r.getLong("col2")); + // System.out.println("col3 :" + r.getBigDecimal("col3")); + // System.out.println("col4 :" + r.getBoolean("col4")); + // System.out.println("col5 :" + r.getInt("col5")); + // System.out.println("col6 :" + r.getDouble("col6")); + // System.out.println("col7 :" + r.getString("col7")); + // System.out.println("col8 :" + r.getString("col8")); + // System.out.println("col9 :" + r.getString("col9")); + } + + asyncRes = this.session.sql("update testExecAsync set c2=c2-1").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(10, sqlRes.getAffectedItemsCount()); + + this.session.rollback(); + asyncRes = this.session.sql("create unique index idx on testExecAsync(c1)").executeAsync(); + sqlRes = asyncRes.get(); + + asyncRes = this.session.sql("select count(*) from testExecAsync").executeAsync(); + sqlRes = asyncRes.get(); + r = sqlRes.next(); + assertEquals(5, r.getInt(0)); + + asyncRes = this.session.sql("Delete from testExecAsync where c1=5 ").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(1, sqlRes.getAffectedItemsCount()); + + /* WIth Bind */ + asyncRes = this.session.sql("insert into testExecAsync values (?,?,?,?,?,?,?,?,?)").bind(6).bind(l1 - 6).bind(106.11).bind(true).bind(2006) + .bind(106.101).bind("v6").bind("xl").bind("a,a,a,a,a,a,a,a,a").executeAsync(); + sqlRes = asyncRes.get(); + assertEquals(1, sqlRes.getAffectedItemsCount()); + + List> futures = new ArrayList<>(); + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 5 == 0) { + futures.add(this.session.sql("insert into testExecAsync (c1,c2,c9) values (?,?,?)").bind(10).bind(l1 - 10).bind("a,d,c").executeAsync()); + } else if (i % 5 == 1) { + futures.add(this.session.sql("REPLACE DELAYED into testExecAsync (c1,c2,c9) values (?,?,?)").bind(10).bind(l1 - 100).bind("a,c") + .executeAsync()); + } else if (i % 5 == 2) { + futures.add(this.session.sql("update testExecAsync set c9 =? where c1 = (?+?+?)").bind("a,d,c,b").bind(3).bind(5).bind(2).executeAsync()); + } else if (i % 5 == 3) { + futures.add(this.session.sql("select * from testExecAsync where c9&8 and c9&1 and c1 = (?+?)").bind(9).bind(1).executeAsync()); + } else { + futures.add(this.session.sql("delete from testExecAsync where c9 & ? and c9&1 and c1 = (?+?)").bind(8).bind(9).bind(1).executeAsync()); + } + + } + + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 5 == 0) { + sqlRes = futures.get(i).get(); + assertEquals(1, sqlRes.getAffectedItemsCount()); + } else if (i % 5 == 1) { + sqlRes = futures.get(i).get(); + assertEquals(2, sqlRes.getAffectedItemsCount()); + } else if (i % 5 == 2) { + sqlRes = futures.get(i).get(); + assertEquals(1, sqlRes.getAffectedItemsCount()); + } else if (i % 5 == 3) { + sqlRes = futures.get(i).get(); + r = sqlRes.next(); + assertEquals(10, r.getInt(0)); + assertFalse(sqlRes.hasNext()); + } else { + sqlRes = futures.get(i).get(); + assertEquals(1, sqlRes.getAffectedItemsCount()); + } + } + } finally { + sqlUpdate("drop Procedure if exists testExecAsyncProc"); + sqlUpdate("drop table if exists testExecAsync"); + } + } + + @Test + public void testFetchOneFetchAllAsync() throws Exception { + Row r = null; + List rowList = null; + try { + CompletableFuture asyncSqlRes = this.session.sql("drop table if exists testFetchOneFetchAllAsync").executeAsync(); + SqlResult sqlRes = asyncSqlRes.get(); + asyncSqlRes = this.session.sql("create table testFetchOneFetchAllAsync(a int,b bigint,c double,d blob)").executeAsync(); + sqlRes = asyncSqlRes.get(); + asyncSqlRes = this.session.sql("insert into testFetchOneFetchAllAsync values(?,?,?,?)").bind(1, 11).bind(21, "A").executeAsync(); + sqlRes = asyncSqlRes.get(); + asyncSqlRes = this.session.sql("insert into testFetchOneFetchAllAsync values(?,?,?,?)").bind(2, 12).bind(22, "B").executeAsync(); + sqlRes = asyncSqlRes.get(); + asyncSqlRes = this.session.sql("insert into testFetchOneFetchAllAsync values(?,?,?,?)").bind(3, 13).bind(23, "C").executeAsync(); + sqlRes = asyncSqlRes.get(); + asyncSqlRes = this.session.sql("insert into testFetchOneFetchAllAsync values(?,?,?,?)").bind(4, 14).bind(23, "D").executeAsync(); + sqlRes = asyncSqlRes.get(); + + //With FetchOne() + asyncSqlRes = this.session.sql("select * from testFetchOneFetchAllAsync where a<=? order by a asc").bind(5).executeAsync(); + SqlResult sqlRes1 = asyncSqlRes.get(); + int i = 0; + while (sqlRes1.hasNext()) { + r = sqlRes1.fetchOne(); + assertEquals((long) (i + 1), r.getInt(0)); + i++; + } + assertThrows(WrongArgumentException.class, "Cannot fetchAll\\(\\) after starting iteration", () -> sqlRes1.fetchAll()); + + asyncSqlRes = this.session.sql("select * from testFetchOneFetchAllAsync where a<=? order by a asc").bind(3).executeAsync(); + sqlRes = asyncSqlRes.get(); + rowList = sqlRes.fetchAll(); + assertEquals((long) 3, (long) rowList.size()); + for (i = 0; i < rowList.size(); i++) { + r = rowList.get(i); + assertEquals((long) (i + 1), r.getInt(0)); + i++; + } + } finally { + sqlUpdate("drop table if exists testFetchOneFetchAllAsync"); + } + } + + /** + * Few Negative Scenarios + * + * @throws Exception + */ + @Test + public void testExecAsyncNegative() throws Exception { + int i = 0; + SqlResult sqlRes = null; + Row r = null; + try { + assertThrows(ExecutionException.class, ".*Unknown table '" + this.schema.getName() + ".non_existing'.*", () -> { + CompletableFuture res = this.session.sql("drop table non_existing").executeAsync(); + res.get(); + return null; + }); + + assertThrows(ExecutionException.class, ".* BIGINT value is out of range .*", () -> { + CompletableFuture res = this.session.sql("select 123456*123456722323289").executeAsync(); + res.get(); + return null; + }); + + sqlUpdate("drop table if exists testExecAsyncNegative"); + sqlUpdate( + "create table testExecAsyncNegative(a int,b bigint ,c bigint GENERATED ALWAYS AS (b*1000) VIRTUAL COMMENT '1',d bigint GENERATED ALWAYS AS (c*100000) STOred COMMENT '2')"); + sqlUpdate("Insert into testExecAsyncNegative (a,b) values(1,100)"); + sqlUpdate("create index id on testExecAsyncNegative(d)"); + sqlUpdate("create unique index id2 on testExecAsyncNegative(a)"); + + int NUMBER_OF_QUERIES = 5000; + List> futures = new ArrayList<>(); + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + if (i % 6 == 0) { + futures.add(this.session.sql("replace into testExecAsyncNegative (a,b) values(?,?)").bind(1).bind(1555666000000L).executeAsync()); + } else if (i % 6 == 1) { + futures.add(this.session.sql("insert into testExecAsyncNegative (a,b) values (?,?) ON DUPLICATE KEY UPDATE b= ?").bind(1).bind(2) + .bind(1555666009990L).executeAsync()); + } else if (i % 6 == 2) { + futures.add(this.session.sql("alter table testExecAsyncNegative add d point").executeAsync()); + } else if (i % 6 == 3) { + futures.add(this.session.sql("insert into testExecAsyncNegative (a,b) values (?,?) ON DUPLICATE KEY UPDATE b=b/?").bind(1).bind(2).bind(0) + .executeAsync()); + } else if (i % 6 == 4) { + futures.add(this.session.sql("SELECT /*+ max_execution_time (100) bad_hint */ SLEEP(0.5)").executeAsync()); + } else { + futures.add(this.session.sql("select /*+*/ * from testExecAsyncNegative").executeAsync()); + } + } + + for (i = 0; i < NUMBER_OF_QUERIES; ++i) { + int i1 = i; + if (i % 6 == 0) { + assertThrows(ExecutionException.class, ".* BIGINT value is out of range .*", () -> futures.get(i1).get()); + } else if (i % 6 == 1) { + assertThrows(ExecutionException.class, ".* BIGINT value is out of range .*", () -> futures.get(i1).get()); + } else if (i % 6 == 2) { + assertThrows(ExecutionException.class, ".*Duplicate column name 'd'.*", () -> futures.get(i1).get()); + } else if (i % 6 == 3) { + assertThrows(ExecutionException.class, ".*Division by 0.*", () -> futures.get(i1).get()); + } else { + sqlRes = futures.get(i).get(); + r = sqlRes.next(); + assertEquals(1, r.getInt(0)); + assertFalse(sqlRes.hasNext()); + Iterator w = sqlRes.getWarnings(); + while (w.hasNext()) { + Warning element = w.next(); + assertTrue(element.getMessage().contains("Optimizer hint syntax error")); + } + } + } + } finally { + sqlUpdate("drop table if exists testExecAsyncNegative"); + } + } + + /** + * Test fix for Bug#97269 (30438500), POSSIBLE BUG IN COM.MYSQL.CJ.XDEVAPI.STREAMINGDOCRESULTBUILDER. + * + * @throws Exception + */ + @Test + public void testBug97269() throws Exception { + Session sess = null; + try { + String message1 = "W1"; + String message2 = "W2"; + + // create notice message buffers + Frame.Builder notice1 = Frame.newBuilder().setScope(Frame.Scope.LOCAL).setType(Frame.Type.WARNING_VALUE) + .setPayload(com.mysql.cj.x.protobuf.MysqlxNotice.Warning.newBuilder().setCode(MysqlErrorNumbers.ER_BAD_DB_ERROR).setMsg(message1).build() + .toByteString()); + Frame.Builder notice2 = Frame.newBuilder().setScope(Frame.Scope.GLOBAL).setType(Frame.Type.WARNING_VALUE) + .setPayload(com.mysql.cj.x.protobuf.MysqlxNotice.Warning.newBuilder().setCode(MysqlErrorNumbers.ER_BAD_DB_ERROR).setMsg(message2).build() + .toByteString()); + + byte[] notice1Bytes = makeNoticeBytes(notice1.build()); + byte[] notice2Bytes = makeNoticeBytes(notice2.build()); + int size = notice1Bytes.length + notice2Bytes.length; + byte[] noticesBytes = new byte[size]; + System.arraycopy(notice1Bytes, 0, noticesBytes, 0, notice1Bytes.length); + System.arraycopy(notice2Bytes, 0, noticesBytes, notice1Bytes.length, notice2Bytes.length); + + InjectedSocketFactory.flushAllStaticData(); + + String url = this.baseUrl + (this.baseUrl.contains("?") ? "" : "?") + + makeParam(PropertyKey.socketFactory, InjectedSocketFactory.class.getName(), !this.baseUrl.contains("?") || this.baseUrl.endsWith("?")) + + makeParam(PropertyKey.xdevapiSslMode, XdevapiSslMode.DISABLED.toString()) + + makeParam(PropertyKey.xdevapiCompression, Compression.DISABLED.toString()) + // to allow injection between result rows + + makeParam(PropertyKey.useReadAheadInput, "false"); + + sess = this.fact.getSession(url); + SocketFactory sf = ((SessionImpl) sess).getSession().getProtocol().getSocketConnection().getSocketFactory(); + assertTrue(InjectedSocketFactory.class.isAssignableFrom(sf.getClass())); + + Collection collection = sess.getDefaultSchema().createCollection("testBug97269"); + collection.add("{\"_id\":\"the_id\",\"g\":1}").execute(); + + // StreamingDocResultBuilder + InjectedSocketFactory.injectedBuffer = noticesBytes; + DocResult docs = collection.find().fields("$._id as _id, $.g as g, 1 + 1 as q").execute(); + DbDoc doc = docs.next(); + assertEquals("the_id", ((JsonString) doc.get("_id")).getString()); + assertEquals(new Integer(1), ((JsonNumber) doc.get("g")).getInteger()); + assertEquals(new Integer(2), ((JsonNumber) doc.get("q")).getInteger()); + + int cnt = 0; + for (Iterator warn = docs.getWarnings(); warn.hasNext();) { + Warning w = warn.next(); + if (w.getMessage().equals(message1) || w.getMessage().equals(message2)) { + cnt++; + } + } + assertEquals(2, cnt); + + InjectedSocketFactory.flushAllStaticData(); + InjectedSocketFactory.injectedBuffer = noticesBytes; + + SqlResult rs1 = sess.sql("select 1").execute(); + assertEquals(1, rs1.fetchOne().getInt(0)); + cnt = 0; + for (Iterator warn = rs1.getWarnings(); warn.hasNext();) { + Warning w = warn.next(); + if (w.getMessage().equals(message1) || w.getMessage().equals(message2)) { + cnt++; + } + } + assertEquals(2, cnt); + + } finally { + InjectedSocketFactory.flushAllStaticData(); + dropCollection("testBug97269"); + if (sess != null) { + sess.close(); + } + } + + } } diff --git a/src/test/java/testsuite/x/devapi/TableDeleteTest.java b/src/test/java/testsuite/x/devapi/TableDeleteTest.java index 673d302b3..e4f67fdb6 100644 --- a/src/test/java/testsuite/x/devapi/TableDeleteTest.java +++ b/src/test/java/testsuite/x/devapi/TableDeleteTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -30,6 +30,7 @@ package testsuite.x.devapi; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.Test; @@ -47,9 +48,6 @@ public class TableDeleteTest extends BaseTableTestCase { @Test public void testDelete() { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists testDelete"); sqlUpdate("drop view if exists testDeleteView"); @@ -80,9 +78,7 @@ public void testDelete() { @Test public void testPreparedStatements() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); try { // Prepare test data. diff --git a/src/test/java/testsuite/x/devapi/TableInsertTest.java b/src/test/java/testsuite/x/devapi/TableInsertTest.java index 62cce56b2..c5448b290 100644 --- a/src/test/java/testsuite/x/devapi/TableInsertTest.java +++ b/src/test/java/testsuite/x/devapi/TableInsertTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -29,6 +29,7 @@ package testsuite.x.devapi; +import static com.mysql.cj.xdevapi.Expression.expr; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -44,34 +45,30 @@ import com.mysql.cj.xdevapi.InsertResult; import com.mysql.cj.xdevapi.JsonParser; import com.mysql.cj.xdevapi.JsonString; +import com.mysql.cj.xdevapi.Result; import com.mysql.cj.xdevapi.Row; import com.mysql.cj.xdevapi.RowResult; +import com.mysql.cj.xdevapi.SqlResult; import com.mysql.cj.xdevapi.Table; -/** - * @todo - */ public class TableInsertTest extends BaseTableTestCase { @Test public void lastInsertId() { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop table if exists lastInsertId"); + sqlUpdate("create table lastInsertId (id int not null primary key auto_increment, name varchar(20) not null)"); + Table table = this.schema.getTable("lastInsertId"); + InsertResult res = table.insert("name").values("a").values("b").values("c").execute(); + assertEquals(3, res.getAffectedItemsCount()); + // the *first* ID + assertEquals(new Long(1), res.getAutoIncrementValue()); + } finally { + sqlUpdate("drop table if exists lastInsertId"); } - String tableName = "lastInsertId"; - sqlUpdate("drop table if exists lastInsertId"); - sqlUpdate("create table lastInsertId (id int not null primary key auto_increment, name varchar(20) not null)"); - Table table = this.schema.getTable(tableName); - InsertResult res = table.insert("name").values("a").values("b").values("c").execute(); - assertEquals(3, res.getAffectedItemsCount()); - // the *first* ID - assertEquals(new Long(1), res.getAutoIncrementValue()); } @Test public void basicInsert() { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists basicInsert"); sqlUpdate("drop view if exists basicInsertView"); @@ -140,9 +137,6 @@ public void basicInsert() { @Test public void jsonInsert() throws IOException { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists jsonInsert"); sqlUpdate("create table jsonInsert (_id varchar(32), doc JSON)"); @@ -183,4 +177,168 @@ public void jsonInsert() throws IOException { sqlUpdate("drop table if exists jsonInsert"); } } + + @Test + public void testGetAutoIncrementValueAsync() throws Exception { + try { + SqlResult res = this.session.sql("drop table if exists mytab").executeAsync().get(); + res = this.session.sql("create table mytab (x bigint auto_increment primary key,y int)").executeAsync().get(); + res = this.session.sql("drop table if exists mytabtmp").executeAsync().get(); + res = this.session.sql("create table mytabtmp (x bigint,y int)").executeAsync().get(); + res = this.session.sql("insert into mytabtmp values(NULL,8)").executeAsync().get(); + res = this.session.sql("insert into mytabtmp values(1111,9)").executeAsync().get(); + + res = this.session.sql("ALTER TABLE mytab AUTO_INCREMENT = 111").executeAsync().get(); + res = this.session.sql("insert into mytab values(NULL,1)").executeAsync().get(); + assertEquals((long) 111, (long) res.getAutoIncrementValue()); + + res = this.session.sql("insert into mytab values(-100,2)").executeAsync().get(); + assertEquals((long) -100, (long) res.getAutoIncrementValue()); + + res = this.session.sql("insert into mytab (y)values(3)").executeAsync().get(); + assertEquals((long) 112, (long) res.getAutoIncrementValue()); + + res = this.session.sql("insert into mytab values(NULL,4),(NULL,5),(887,6),(NULL,7)").executeAsync().get(); + assertEquals((long) 113, (long) res.getAutoIncrementValue()); + + res = this.session.sql("insert into mytab select * from mytabtmp").executeAsync().get(); + assertEquals((long) 889, (long) res.getAutoIncrementValue()); + + res = this.session.sql("insert into mytab (y) select (y+1) from mytabtmp").executeAsync().get(); + assertEquals((long) 1112, (long) res.getAutoIncrementValue()); + + //Ignore duplicate + res = this.session.sql("insert IGNORE mytab select * from mytabtmp").executeAsync().get(); + assertEquals((long) 1115, (long) res.getAutoIncrementValue()); + + // ON DUPLICATE KEY + res = this.session.sql("insert into mytab values(-100,2) ON DUPLICATE KEY UPDATE Y=Y*-1").executeAsync().get(); + assertEquals((long) -100, (long) res.getAutoIncrementValue()); + + // ON DUPLICATE KEY + res = this.session.sql("insert into mytab values(-100,2) ON DUPLICATE KEY UPDATE X=X*-2").executeAsync().get(); + assertEquals((long) 200, (long) res.getAutoIncrementValue()); + + //Replace + res = this.session.sql("Replace into mytab (y)values(100000)").executeAsync().get(); + assertEquals((long) 1116, (long) res.getAutoIncrementValue()); + } finally { + sqlUpdate("drop table if exists mytabtmp"); + sqlUpdate("drop table if exists mytab"); + } + } + + @Test + public void testExprInInsert() { + try { + sqlUpdate("drop table if exists qatablex"); + sqlUpdate("create table qatablex (x char(100),y bigint,z int)"); + + Table table = this.schema.getTable("qatablex"); + Result res = table.insert("x", "y", "z").values(expr("concat('A','-1')"), expr("concat('1','100')"), 1).execute(); + assertEquals(1L, res.getAffectedItemsCount()); + + res = table.insert("x", "y", "z").values("expr(\"concat('A','-1)\")", expr("length(concat('1','000'))+100"), 2).execute(); + assertEquals(1L, res.getAffectedItemsCount()); + + res = table.insert("x", "y", "z").values(expr("STR_TO_DATE('1,1,2014','%d,%m,%Y')"), expr("length(concat('1','000'))+101"), 3).execute(); + assertEquals(1L, res.getAffectedItemsCount()); + + res = table.insert("x", "y", "z").values("expr(\"concat('A','-2)\")", expr("length(concat('1','000'))+101"), 4) + .values("expr(\"concat('A','-3)\")", expr("length(concat('1','000'))+102"), 5).execute(); + assertEquals(2L, res.getAffectedItemsCount()); + + res = table.insert("x", "y", "z").values(expr("concat('A',length('abcd'))"), expr("length(concat('1','000'))+103"), 3).execute(); + assertEquals(1L, res.getAffectedItemsCount()); + + Map row = new HashMap<>(); + row.put("x", expr("concat('A','''\n-10\"')")); + row.put("y", expr("concat('1','000')+103")); + row.put("z", 6); + res = table.insert(row).execute(); + assertEquals(1, res.getAffectedItemsCount()); + row.clear(); + row.put("x", "expr(\"concat('A','\n''-10\")\")"); + row.put("y", expr("concat('1','000')+104")); + row.put("z", 7); + res = table.insert(row).execute(); + assertEquals(1L, res.getAffectedItemsCount()); + } finally { + sqlUpdate("drop table if exists qatablex"); + } + } + + @Test + public void testGetAutoIncrementValue() { + Table table = null; + InsertResult res = null; + try { + sqlUpdate("drop table if exists qatable1"); + sqlUpdate("drop table if exists qatable2"); + sqlUpdate("create table qatable1 (x bigint auto_increment primary key,y double)"); + sqlUpdate("create table qatable2 (x double auto_increment primary key,y bigint)"); + + table = this.schema.getTable("qatable1", true); + res = table.insert("y").values(101.1).execute(); + assertEquals(1L, (long) res.getAutoIncrementValue()); + assertEquals(1L, res.getAffectedItemsCount()); + + res = table.insert("y").values(102.1).values(103.1).values(104.1).execute(); + assertEquals(2L, (long) res.getAutoIncrementValue()); + assertEquals(3L, res.getAffectedItemsCount()); + + Map row = new HashMap<>(); + row.put("y", expr("concat('1','05.1')")); + + res = table.insert(row).execute(); + assertEquals(5L, (long) res.getAutoIncrementValue()); + assertEquals(1L, res.getAffectedItemsCount()); + + res = table.insert("y").values(102.1).values(103.1).values(104.1).execute(); + assertEquals(6L, (long) res.getAutoIncrementValue()); + assertEquals(3L, res.getAffectedItemsCount()); + + res = table.insert("y").values(102.1).values(103.1).values(104.1).execute(); + assertEquals(9L, (long) res.getAutoIncrementValue()); + assertEquals(3L, res.getAffectedItemsCount()); + + this.session.sql("ALTER TABLE qatable1 AUTO_INCREMENT = 9223372036854775800").execute(); + res = table.insert("y").values(102.1).values(103.1).values(104.1).execute(); + assertEquals(9223372036854775800L, (long) res.getAutoIncrementValue()); + assertEquals(3L, res.getAffectedItemsCount()); + + res = table.insert(row).execute(); + assertEquals(9223372036854775803L, (long) res.getAutoIncrementValue()); + assertEquals(1L, res.getAffectedItemsCount()); + + table = this.schema.getTable("qatable2"); + res = table.insert("y").values(101.1).execute(); + assertEquals(1L, (long) res.getAutoIncrementValue()); + assertEquals(1L, res.getAffectedItemsCount()); + + res = table.insert("y").values(102.1).values(103.1).values(104.1).execute(); + assertEquals(2L, (long) res.getAutoIncrementValue()); + assertEquals(3L, res.getAffectedItemsCount()); + + row = new HashMap<>(); + row.put("y", expr("concat('1','05.1')")); + + res = table.insert(row).execute(); + assertEquals(5L, (long) res.getAutoIncrementValue()); + assertEquals(1L, res.getAffectedItemsCount()); + + this.session.sql("ALTER TABLE qatable2 AUTO_INCREMENT = 4294967299000000").execute(); + res = table.insert("y").values(102.1).values(103.1).values(104.1).execute(); + assertEquals(4294967299000000L, (long) res.getAutoIncrementValue()); + assertEquals(3L, res.getAffectedItemsCount()); + + this.session.sql("ALTER TABLE qatable2 AUTO_INCREMENT = 4294967299000000").execute(); + res = table.insert(row).execute(); + assertEquals(4294967299000003L, (long) res.getAutoIncrementValue()); + assertEquals(1L, res.getAffectedItemsCount()); + } finally { + sqlUpdate("drop table if exists qatable1"); + sqlUpdate("drop table if exists qatable2"); + } + } } diff --git a/src/test/java/testsuite/x/devapi/TableSelectTest.java b/src/test/java/testsuite/x/devapi/TableSelectTest.java index 8989c3462..0b68f14fc 100644 --- a/src/test/java/testsuite/x/devapi/TableSelectTest.java +++ b/src/test/java/testsuite/x/devapi/TableSelectTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -34,6 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.lang.reflect.Field; import java.math.BigDecimal; @@ -75,119 +76,123 @@ public class TableSelectTest extends BaseTableTestCase { @Test public void basicQuery() { - if (!this.isSetForXTests) { - return; - } - sqlUpdate("drop table if exists basicQuery"); - sqlUpdate("create table basicQuery (_id varchar(32), name varchar(20), birthday date, age int)"); - sqlUpdate("insert into basicQuery values ('some long UUID', 'Sakila', '2000-05-27', 14)"); - Table table = this.schema.getTable("basicQuery"); - Map params = new HashMap<>(); - params.put("name", "Saki%"); - params.put("age", 20); - RowResult rows = table.select("birthday, `_id`, name").where("name like :name AND age < :age").bind(params).execute(); - - // verify metadata - List columnNames = rows.getColumnNames(); - assertEquals("birthday", columnNames.get(0)); - assertEquals("_id", columnNames.get(1)); - assertEquals("name", columnNames.get(2)); + try { + sqlUpdate("drop table if exists basicQuery"); + sqlUpdate("create table basicQuery (_id varchar(32), name varchar(20), birthday date, age int)"); + sqlUpdate("insert into basicQuery values ('some long UUID', 'Sakila', '2000-05-27', 14)"); + Table table = this.schema.getTable("basicQuery"); + Map params = new HashMap<>(); + params.put("name", "Saki%"); + params.put("age", 20); + RowResult rows = table.select("birthday, `_id`, name").where("name like :name AND age < :age").bind(params).execute(); - Row row = rows.next(); - assertEquals("2000-05-27", row.getString(0)); - assertEquals("2000-05-27", row.getString("birthday")); - assertEquals("Sakila", row.getString(2)); - assertEquals("Sakila", row.getString("name")); - assertEquals("some long UUID", row.getString(1)); - assertEquals("some long UUID", row.getString("_id")); - - // select with multiple projection params - rows = table.select("`_id`", "name", "birthday").where("name like :name AND age < :age").bind(params).execute(); - // verify metadata - columnNames = rows.getColumnNames(); - assertEquals("_id", columnNames.get(0)); - assertEquals("name", columnNames.get(1)); - assertEquals("birthday", columnNames.get(2)); + // verify metadata + List columnNames = rows.getColumnNames(); + assertEquals("birthday", columnNames.get(0)); + assertEquals("_id", columnNames.get(1)); + assertEquals("name", columnNames.get(2)); + + Row row = rows.next(); + assertEquals("2000-05-27", row.getString(0)); + assertEquals("2000-05-27", row.getString("birthday")); + assertEquals("Sakila", row.getString(2)); + assertEquals("Sakila", row.getString("name")); + assertEquals("some long UUID", row.getString(1)); + assertEquals("some long UUID", row.getString("_id")); + + // select with multiple projection params + rows = table.select("`_id`", "name", "birthday").where("name like :name AND age < :age").bind(params).execute(); + // verify metadata + columnNames = rows.getColumnNames(); + assertEquals("_id", columnNames.get(0)); + assertEquals("name", columnNames.get(1)); + assertEquals("birthday", columnNames.get(2)); + } finally { + sqlUpdate("drop table if exists basicQuery"); + } } @Test public void testComplexQuery() { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop table if exists complexQuery"); + sqlUpdate("create table complexQuery (name varchar(32), age int, something int)"); + sqlUpdate("insert into complexQuery values ('Mamie', 11, 0)"); + sqlUpdate("insert into complexQuery values ('Eulalia', 11, 0)"); + sqlUpdate("insert into complexQuery values ('Polly', 12, 0)"); + sqlUpdate("insert into complexQuery values ('Rufus', 12, 0)"); + sqlUpdate("insert into complexQuery values ('Cassidy', 13, 0)"); + sqlUpdate("insert into complexQuery values ('Olympia', 14, 0)"); + sqlUpdate("insert into complexQuery values ('Lev', 14, 0)"); + sqlUpdate("insert into complexQuery values ('Tierney', 15, 0)"); + sqlUpdate("insert into complexQuery values ('Octavia', 15, 0)"); + sqlUpdate("insert into complexQuery values ('Vesper', 16, 0)"); + sqlUpdate("insert into complexQuery values ('Caspian', 17, 0)"); + sqlUpdate("insert into complexQuery values ('Romy', 17, 0)"); + Table table = this.schema.getTable("complexQuery"); + // Result: + // age_group | cnt + // 11 | 2 <-- filtered out by where + // 12 | 2 <-- filtered out by limit + // 13 | 1 <-- filtered out by having + // 14 | 2 * second row in result + // 15 | 2 * first row in result + // 16 | 1 <-- filtered out by having + // 17 | 2 <-- filtered out by offset + SelectStatement stmt = table.select("age as age_group, count(name) as cnt, something"); + stmt.where("age > 11 and 1 < 2 and 40 between 30 and 900"); + stmt.groupBy("something", "age_group"); + stmt.having("cnt > 1"); + stmt.orderBy("age_group desc"); + RowResult rows = stmt.limit(2).offset(1).execute(); + Row row = rows.next(); + assertEquals(15, row.getInt(0)); + assertEquals(2, row.getInt(1)); + assertEquals(2, row.getByte(1)); + assertEquals(2, row.getLong(1)); + assertEquals(new BigDecimal("2"), row.getBigDecimal(1)); + assertEquals(true, row.getBoolean(1)); + row = rows.next(); + assertEquals(14, row.getInt(0)); + assertEquals(2, row.getInt(1)); + assertFalse(rows.hasNext()); + } finally { + sqlUpdate("drop table if exists complexQuery"); } - sqlUpdate("drop table if exists complexQuery"); - sqlUpdate("create table complexQuery (name varchar(32), age int, something int)"); - sqlUpdate("insert into complexQuery values ('Mamie', 11, 0)"); - sqlUpdate("insert into complexQuery values ('Eulalia', 11, 0)"); - sqlUpdate("insert into complexQuery values ('Polly', 12, 0)"); - sqlUpdate("insert into complexQuery values ('Rufus', 12, 0)"); - sqlUpdate("insert into complexQuery values ('Cassidy', 13, 0)"); - sqlUpdate("insert into complexQuery values ('Olympia', 14, 0)"); - sqlUpdate("insert into complexQuery values ('Lev', 14, 0)"); - sqlUpdate("insert into complexQuery values ('Tierney', 15, 0)"); - sqlUpdate("insert into complexQuery values ('Octavia', 15, 0)"); - sqlUpdate("insert into complexQuery values ('Vesper', 16, 0)"); - sqlUpdate("insert into complexQuery values ('Caspian', 17, 0)"); - sqlUpdate("insert into complexQuery values ('Romy', 17, 0)"); - Table table = this.schema.getTable("complexQuery"); - // Result: - // age_group | cnt - // 11 | 2 <-- filtered out by where - // 12 | 2 <-- filtered out by limit - // 13 | 1 <-- filtered out by having - // 14 | 2 * second row in result - // 15 | 2 * first row in result - // 16 | 1 <-- filtered out by having - // 17 | 2 <-- filtered out by offset - SelectStatement stmt = table.select("age as age_group, count(name) as cnt, something"); - stmt.where("age > 11 and 1 < 2 and 40 between 30 and 900"); - stmt.groupBy("something", "age_group"); - stmt.having("cnt > 1"); - stmt.orderBy("age_group desc"); - RowResult rows = stmt.limit(2).offset(1).execute(); - Row row = rows.next(); - assertEquals(15, row.getInt(0)); - assertEquals(2, row.getInt(1)); - assertEquals(2, row.getByte(1)); - assertEquals(2, row.getLong(1)); - assertEquals(new BigDecimal("2"), row.getBigDecimal(1)); - assertEquals(true, row.getBoolean(1)); - row = rows.next(); - assertEquals(14, row.getInt(0)); - assertEquals(2, row.getInt(1)); - assertFalse(rows.hasNext()); } @Test public void allColumns() { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop table if exists allColumns"); + sqlUpdate("create table allColumns (x int, y int, z int)"); + sqlUpdate("insert into allColumns values (1,2,3)"); + Table table = this.schema.getTable("allColumns"); + // * must come first, as with SQL + SelectStatement stmt = table.select("*, 42 as a_number, '43' as a_string"); + Row row = stmt.execute().next(); + assertEquals(42, row.getInt("a_number")); + assertEquals(1, row.getInt("x")); + assertEquals(2, row.getInt("y")); + assertEquals(3, row.getInt("z")); + assertEquals("43", row.getString("a_string")); + } finally { + sqlUpdate("drop table if exists allColumns"); } - sqlUpdate("drop table if exists allColumns"); - sqlUpdate("create table allColumns (x int, y int, z int)"); - sqlUpdate("insert into allColumns values (1,2,3)"); - Table table = this.schema.getTable("allColumns"); - // * must come first, as with SQL - SelectStatement stmt = table.select("*, 42 as a_number, '43' as a_string"); - Row row = stmt.execute().next(); - assertEquals(42, row.getInt("a_number")); - assertEquals(1, row.getInt("x")); - assertEquals(2, row.getInt("y")); - assertEquals(3, row.getInt("z")); - assertEquals("43", row.getString("a_string")); } @Test public void countAllColumns() { - if (!this.isSetForXTests) { - return; + try { + sqlUpdate("drop table if exists countAllColumns"); + sqlUpdate("create table countAllColumns(x int, y int)"); + sqlUpdate("insert into countAllColumns values (1,1), (2,2), (3,3), (4,4)"); + Table table = this.schema.getTable("countAllColumns"); + Row row = table.select("count(*) + 10").execute().next(); + assertEquals(14, row.getInt(0)); + } finally { + sqlUpdate("drop table if exists countAllColumns"); } - sqlUpdate("drop table if exists countAllColumns"); - sqlUpdate("create table countAllColumns(x int, y int)"); - sqlUpdate("insert into countAllColumns values (1,1), (2,2), (3,3), (4,4)"); - Table table = this.schema.getTable("countAllColumns"); - Row row = table.select("count(*) + 10").execute().next(); - assertEquals(14, row.getInt(0)); } /** @@ -195,9 +200,6 @@ public void countAllColumns() { */ @Test public void testBug22931433() { - if (!this.isSetForXTests) { - return; - } sqlUpdate("drop table if exists testBug22931433"); sqlUpdate( "create table testBug22931433(c1 bit(8), c2 bit(16), c3 bit(24), c4 bit(32), c5 bit(40), c6 bit(48), c7 bit(56), c8 bit(64), cb1 bit(1), cb2 bit(64))"); @@ -355,13 +357,11 @@ public Void call() throws Exception { assertEquals(false, row2.getBoolean("cb2")); sqlUpdate("drop table if exists testBug22931433"); + s1.close(); } @Test public void basicViewQuery() { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists basicTable1"); sqlUpdate("drop table if exists basicTable2"); @@ -403,35 +403,33 @@ public void basicViewQuery() { @Test public void testOrderBy() { - if (!this.isSetForXTests) { - return; - } - sqlUpdate("drop table if exists testOrderBy"); - sqlUpdate("create table testOrderBy (_id int, x int, y int)"); - sqlUpdate("insert into testOrderBy values (2,20,21), (1,20,22), (4,10,40), (3,10,50)"); - Table table = this.schema.getTable("testOrderBy"); - - RowResult rows = table.select("_id").orderBy("x desc, y desc").execute(); - int i = 1; - while (rows.hasNext()) { - assertEquals(i++, rows.next().getInt("_id")); - } - assertEquals(5, i); + try { + sqlUpdate("drop table if exists testOrderBy"); + sqlUpdate("create table testOrderBy (_id int, x int, y int)"); + sqlUpdate("insert into testOrderBy values (2,20,21), (1,20,22), (4,10,40), (3,10,50)"); + Table table = this.schema.getTable("testOrderBy"); + + RowResult rows = table.select("_id").orderBy("x desc, y desc").execute(); + int i = 1; + while (rows.hasNext()) { + assertEquals(i++, rows.next().getInt("_id")); + } + assertEquals(5, i); - // multiple SortExprStr - rows = table.select("_id").orderBy("x desc", "y desc").execute(); - i = 1; - while (rows.hasNext()) { - assertEquals(i++, rows.next().getInt("_id")); + // multiple SortExprStr + rows = table.select("_id").orderBy("x desc", "y desc").execute(); + i = 1; + while (rows.hasNext()) { + assertEquals(i++, rows.next().getInt("_id")); + } + assertEquals(5, i); + } finally { + sqlUpdate("drop table if exists testOrderBy"); } - assertEquals(5, i); } @Test public void testBug22988922() { - if (!this.isSetForXTests) { - return; - } sqlUpdate("drop table if exists testBug22988922"); sqlUpdate("create table testBug22988922 (g point,l longblob,t longtext)"); @@ -451,34 +449,33 @@ public void testBug22988922() { */ @Test public void testBug22931277() { - if (!this.isSetForXTests) { - return; - } - sqlUpdate("drop table if exists testBug22931277"); - sqlUpdate("create table testBug22931277 (j year,k datetime(3))"); - Table table = this.schema.getTable("testBug22931277"); + try { + sqlUpdate("drop table if exists testBug22931277"); + sqlUpdate("create table testBug22931277 (j year,k datetime(3))"); + Table table = this.schema.getTable("testBug22931277"); - table = this.schema.getTable("testBug22931277"); - table.insert("j", "k").values(2000, "2016-03-15 12:13:14").execute(); + table = this.schema.getTable("testBug22931277"); + table.insert("j", "k").values(2000, "2016-03-15 12:13:14").execute(); - RowResult rows = table.select("1,j,k").execute(); - List metadata = rows.getColumns(); + RowResult rows = table.select("1,j,k").execute(); + List metadata = rows.getColumns(); - Column myCol = metadata.get(0); - assertEquals(Type.TINYINT, myCol.getType()); + Column myCol = metadata.get(0); + assertEquals(Type.TINYINT, myCol.getType()); - myCol = metadata.get(1); - assertEquals(Type.SMALLINT, myCol.getType()); + myCol = metadata.get(1); + assertEquals(Type.SMALLINT, myCol.getType()); - myCol = metadata.get(2); - assertEquals(Type.DATETIME, myCol.getType()); + myCol = metadata.get(2); + assertEquals(Type.DATETIME, myCol.getType()); + } finally { + sqlUpdate("drop table if exists testBug22931277"); + } } @Test public void testTableRowLocks() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3")), "MySQL 8.0.3+ is required to run this test."); sqlUpdate("drop table if exists testTableRowLocks"); sqlUpdate("create table testTableRowLocks (_id varchar(32), a varchar(20))"); @@ -582,9 +579,7 @@ public Void call() throws Exception { @Test public void testTableRowLockOptions() throws Exception { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5")), "MySQL 8.0.5+ is required to run this test."); Function> asStringList = rr -> rr.fetchAll().stream().map(r -> r.getString(0)).collect(Collectors.toList()); @@ -881,10 +876,6 @@ public void testTableRowLockOptions() throws Exception { */ @Test public void testBug22038729() throws Exception { - if (!this.isSetForXTests) { - return; - } - final Field pf = CoreSession.class.getDeclaredField("protocol"); pf.setAccessible(true); @@ -947,9 +938,7 @@ public void testBug22038729() throws Exception { @Test public void testPreparedStatements() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); try { // Prepare test data. diff --git a/src/test/java/testsuite/x/devapi/TableTest.java b/src/test/java/testsuite/x/devapi/TableTest.java index c334f58ec..d23c6d0ae 100644 --- a/src/test/java/testsuite/x/devapi/TableTest.java +++ b/src/test/java/testsuite/x/devapi/TableTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -36,6 +36,8 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; @@ -43,6 +45,7 @@ import com.mysql.cj.xdevapi.DatabaseObject.DbObjectStatus; import com.mysql.cj.xdevapi.Row; import com.mysql.cj.xdevapi.RowResult; +import com.mysql.cj.xdevapi.SqlResult; import com.mysql.cj.xdevapi.Table; /** @@ -51,15 +54,13 @@ public class TableTest extends BaseTableTestCase { @Test public void tableBasics() { - if (!this.isSetForXTests) { - return; - } sqlUpdate("drop table if exists tableBasics"); Table table = this.schema.getTable("tableBasics"); assertEquals(DbObjectStatus.NOT_EXISTS, table.existsInDatabase()); sqlUpdate("create table tableBasics (name varchar(32), age int)"); assertEquals(DbObjectStatus.EXISTS, table.existsInDatabase()); assertEquals("Table(" + getTestDatabase() + ".tableBasics)", table.toString()); + assertEquals(this.schema, table.getSchema()); assertEquals(this.session, table.getSession()); Table table2 = this.schema.getTable("tableBasics"); assertFalse(table == table2); @@ -68,10 +69,6 @@ public void tableBasics() { @Test public void viewBasics() { - if (!this.isSetForXTests) { - return; - } - try { sqlUpdate("drop table if exists tableBasics"); sqlUpdate("drop view if exists viewBasics"); @@ -123,9 +120,6 @@ public void viewBasics() { @Test public void testCount() { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists testCount"); sqlUpdate("create table testCount (_id varchar(32), name varchar(20), birthday date, age int)"); @@ -154,9 +148,6 @@ public Void call() throws Exception { @Test public void testBug25650912() throws Exception { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists testBug25650912"); sqlUpdate("create table testBug25650912 (x bigint,y char(220))"); @@ -196,4 +187,52 @@ public Void call() throws Exception { sqlUpdate("drop table if exists testBug25650912"); } } + + @Test + public void testAsyncBind() throws Exception { + try { + sqlUpdate("drop table if exists testAsyncBind"); + sqlUpdate("create table testAsyncBind(a int,b bigint,c double,d blob)"); + + CompletableFuture asyncSqlRes = null; + SqlResult sqlRes = null; + Row r = null; + + //execute without bind() + assertThrows(ExecutionException.class, ".*You have an error in your SQL syntax.*", + () -> this.session.sql("insert into testAsyncBind values(?,?,?,?)").executeAsync().get()); + + //execute with more bind() + assertThrows(ExecutionException.class, ".*Too many arguments.*", + () -> this.session.sql("insert into testAsyncBind values(?,?,?,?)").bind(1, 2, 3, 4, 5).executeAsync().get()); + + //execute with less bind() + assertThrows(ExecutionException.class, ".*You have an error in your SQL syntax.*", + () -> this.session.sql("insert into testAsyncBind values(?,?,?,?)").bind(1, 2, 3).executeAsync().get()); + + //Success + asyncSqlRes = this.session.sql("insert into testAsyncBind values(?,?,?,?)").bind(10, 2).bind(3, "S").executeAsync(); + sqlRes = asyncSqlRes.get(); + asyncSqlRes = this.session.sql("select * from testAsyncBind where a=?").bind(10).executeAsync(); + sqlRes = asyncSqlRes.get(); + r = sqlRes.next(); + assertTrue(r.getBoolean(0)); + assertEquals(10, r.getInt(0)); + assertEquals(2, r.getLong(1)); + assertEquals(3.0, r.getDouble(2), 1); + assertEquals("S", r.getString(3)); + assertFalse(sqlRes.hasNext()); + + //bind in where and having + asyncSqlRes = this.session.sql("select b+? as Temp,a as Temp1 from testAsyncBind where a=?+? having a>?").bind(100, 9, 1, 0).executeAsync(); + sqlRes = asyncSqlRes.get(); + r = sqlRes.next(); + assertTrue(r.getBoolean(0)); + assertEquals(102, r.getInt("Temp")); + assertFalse(sqlRes.hasNext()); + + } finally { + sqlUpdate("drop table if exists testAsyncBind"); + } + } } diff --git a/src/test/java/testsuite/x/devapi/TableUpdateTest.java b/src/test/java/testsuite/x/devapi/TableUpdateTest.java index b652b586d..b095132bf 100644 --- a/src/test/java/testsuite/x/devapi/TableUpdateTest.java +++ b/src/test/java/testsuite/x/devapi/TableUpdateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,6 +32,7 @@ import static com.mysql.cj.xdevapi.Expression.expr; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.Test; @@ -50,9 +51,6 @@ public class TableUpdateTest extends BaseTableTestCase { @Test public void testUpdates() { - if (!this.isSetForXTests) { - return; - } try { sqlUpdate("drop table if exists updates"); sqlUpdate("drop view if exists updatesView"); @@ -87,9 +85,7 @@ public void testUpdates() { @Test public void testPreparedStatements() { - if (!this.isSetForXTests || !mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14"))) { - return; - } + assumeTrue(mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.14")), "MySQL 8.0.14+ is required to run this test."); try { // Prepare test data. diff --git a/src/test/java/testsuite/x/devapi/TransactionTest.java b/src/test/java/testsuite/x/devapi/TransactionTest.java index 1c8450c79..06bdae2f0 100644 --- a/src/test/java/testsuite/x/devapi/TransactionTest.java +++ b/src/test/java/testsuite/x/devapi/TransactionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.util.concurrent.Callable; @@ -40,6 +41,7 @@ import org.junit.jupiter.api.Test; import com.mysql.cj.ServerVersion; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.xdevapi.Collection; import com.mysql.cj.xdevapi.XDevAPIError; @@ -48,6 +50,7 @@ public class TransactionTest extends DevApiBaseTestCase { @BeforeEach public void setupCollectionTest() { + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); if (setupTestSession()) { dropCollection("txTest"); this.collection = this.schema.createCollection("txTest"); @@ -64,10 +67,6 @@ public void teardownCollectionTest() { @Test public void basicRollback() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").add("{\"_id\": \"2\"}").execute(); // Requires manual _id. } else { @@ -87,10 +86,6 @@ public void basicRollback() { @Test public void basicCommit() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").add("{\"_id\": \"2\"}").execute(); // Requires manual _id. } else { @@ -110,10 +105,6 @@ public void basicCommit() { @Test public void basicSavepoint() { - if (!this.isSetForXTests) { - return; - } - if (!mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.5"))) { this.collection.add("{\"_id\": \"1\"}").execute(); // Requires manual _id. } else { diff --git a/src/test/java/testsuite/x/internal/InternalXBaseTestCase.java b/src/test/java/testsuite/x/internal/InternalXBaseTestCase.java index 73cdc0308..8c42e82bc 100644 --- a/src/test/java/testsuite/x/internal/InternalXBaseTestCase.java +++ b/src/test/java/testsuite/x/internal/InternalXBaseTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -30,7 +30,9 @@ package testsuite.x.internal; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.Properties; @@ -42,20 +44,18 @@ import com.mysql.cj.conf.DefaultPropertySet; import com.mysql.cj.conf.HostInfo; import com.mysql.cj.conf.PropertyDefinitions; -import com.mysql.cj.conf.PropertyDefinitions.AuthMech; import com.mysql.cj.conf.PropertyKey; import com.mysql.cj.conf.PropertySet; -import com.mysql.cj.exceptions.WrongArgumentException; +import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.protocol.x.StatementExecuteOkBuilder; import com.mysql.cj.protocol.x.XMessageBuilder; import com.mysql.cj.protocol.x.XProtocol; import com.mysql.cj.protocol.x.XProtocolError; -import com.mysql.cj.protocol.x.XServerCapabilities; +import com.mysql.cj.util.StringUtils; import com.mysql.cj.xdevapi.Session; import com.mysql.cj.xdevapi.SessionFactory; import com.mysql.cj.xdevapi.SessionImpl; import com.mysql.cj.xdevapi.SqlResult; -import com.mysql.cj.xdevapi.XDevAPIError; import testsuite.TestUtils; @@ -69,27 +69,31 @@ public class InternalXBaseTestCase { */ protected static final String DEFAULT_METADATA_CHARSET = "latin1"; - protected String baseUrl = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_mysqlx); - protected String baseOpensslUrl = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_mysqlx_openssl); + public String baseUrl = System.getProperty(PropertyDefinitions.SYSP_testsuite_url_mysqlx); protected boolean isSetForXTests = this.baseUrl != null && this.baseUrl.length() > 0; - protected boolean isSetForOpensslXTests = this.baseOpensslUrl != null && this.baseOpensslUrl.length() > 0; protected SessionFactory fact = new SessionFactory(); public HostInfo testHostInfo; public Properties testProperties = new Properties(); - public Properties testPropertiesOpenSSL = new Properties(); private ServerVersion mysqlVersion; public InternalXBaseTestCase() { - if (this.isSetForXTests) { - ConnectionUrl conUrl = ConnectionUrl.getConnectionUrlInstance(this.baseUrl, null); - this.testHostInfo = conUrl.getMainHost(); - this.testProperties = conUrl.getMainHost().exposeAsProperties(); - } - if (this.isSetForOpensslXTests) { - ConnectionUrl conUrl = ConnectionUrl.getConnectionUrlInstance(this.baseOpensslUrl, null); - this.testPropertiesOpenSSL = conUrl.getMainHost().exposeAsProperties(); + try { + if (this.isSetForXTests) { + ConnectionUrl conUrl = ConnectionUrl.getConnectionUrlInstance(this.baseUrl, null); + this.testHostInfo = conUrl.getMainHost(); + this.testProperties = conUrl.getMainHost().exposeAsProperties(); + + // connecting without database to create it if it doesn't exist + String dbName = (String) this.testProperties.remove(PropertyKey.DBNAME.getKeyName()); + XProtocol prot = createAuthenticatedTestProtocol(createTestProtocol(), this.testProperties); + createTestSchema(prot, dbName); + this.testProperties.setProperty(PropertyKey.DBNAME.getKeyName(), dbName); + prot.close(); + } + } catch (Exception e) { + throw new RuntimeException(e); } } @@ -113,14 +117,6 @@ public String getTestDatabase() { return this.testProperties.getProperty(PropertyKey.DBNAME.getKeyName()); } - public String getTestSslHost() { - return this.testPropertiesOpenSSL.getProperty(PropertyKey.HOST.getKeyName()); - } - - public int getTestSslPort() { - return Integer.valueOf(this.testPropertiesOpenSSL.getProperty(PropertyKey.PORT.getKeyName())); - } - public String getEncodedTestHost() { return TestUtils.encodePercent(getTestHost()); } @@ -131,69 +127,15 @@ public String getEncodedTestHost() { * @return an XProtocol instance */ public XProtocol createTestProtocol() { - // TODO pass prop. set - XProtocol protocol = new XProtocol(getTestHost(), getTestPort(), getTestDatabase(), new DefaultPropertySet()); - protocol.beforeHandshake(); - return protocol; + PropertySet ps = new DefaultPropertySet(); + ps.initializeProperties(this.testProperties); + return new XProtocol(this.testHostInfo, ps); } - /** - * Create a new {@link XProtocol} that is part of an authenticated session. - * - * @return an X Protocol instance - */ - public XProtocol createAuthenticatedTestProtocol() { - XProtocol protocol = createTestProtocol(); - XMessageBuilder messageBuilder = (XMessageBuilder) protocol.getMessageBuilder(); - - AuthMech authMech = protocol.getPropertySet().getEnumProperty(PropertyKey.xdevapiAuth).getValue(); - boolean overTLS = ((XServerCapabilities) protocol.getServerSession().getCapabilities()).getTls(); - - // Choose the best default auth mechanism. - if (!overTLS) { - authMech = AuthMech.MYSQL41; - } else if (authMech != AuthMech.PLAIN) { - authMech = AuthMech.PLAIN; - } - - while (true) { - switch (authMech) { - case SHA256_MEMORY: - protocol.send(messageBuilder.buildSha256MemoryAuthStart(), 0); - byte[] nonce = protocol.readAuthenticateContinue(); - protocol.send(messageBuilder.buildSha256MemoryAuthContinue(getTestUser(), getTestPassword(), nonce, getTestDatabase()), 0); - break; - case MYSQL41: - protocol.send(messageBuilder.buildMysql41AuthStart(), 0); - byte[] salt = protocol.readAuthenticateContinue(); - protocol.send(messageBuilder.buildMysql41AuthContinue(getTestUser(), getTestPassword(), salt, getTestDatabase()), 0); - break; - case PLAIN: - if (overTLS) { - protocol.send(messageBuilder.buildPlainAuthStart(getTestUser(), getTestPassword(), getTestDatabase()), 0); - } else { - throw new XDevAPIError("PLAIN authentication is not allowed via unencrypted connection."); - } - break; - case EXTERNAL: - protocol.send(messageBuilder.buildExternalAuthStart(getTestDatabase()), 0); - break; - default: - throw new WrongArgumentException("Unknown authentication mechanism '" + authMech + "'."); - } - - try { - protocol.readAuthenticateOk(); - protocol.afterHandshake(); - return protocol; - } catch (XProtocolError e) { - if (authMech == AuthMech.MYSQL41) { - authMech = AuthMech.SHA256_MEMORY; // try again using SHA256_MEMORY - } else { - throw e; - } - } - } + public XProtocol createAuthenticatedTestProtocol(XProtocol protocol, Properties props) { + protocol.connect(props.getProperty(PropertyKey.USER.getKeyName()), props.getProperty(PropertyKey.PASSWORD.getKeyName()), + props.getProperty(PropertyKey.DBNAME.getKeyName())); + return protocol; } public MysqlxSession createTestSession() { @@ -203,6 +145,21 @@ public MysqlxSession createTestSession() { return session; } + public void createTestSchema(XProtocol protocol, String schemaName) { + XMessageBuilder messageBuilder = (XMessageBuilder) protocol.getMessageBuilder(); + + try { + StringBuilder stmtString = new StringBuilder("CREATE DATABASE "); + stmtString.append(StringUtils.quoteIdentifier(schemaName, true)); + protocol.send(messageBuilder.buildSqlStatement(stmtString.toString()), 0); + protocol.readQueryResult(new StatementExecuteOkBuilder()); + } catch (XProtocolError ex) { + if (ex.getErrorCode() != MysqlErrorNumbers.ER_DB_CREATE_EXISTS) { + throw ex; + } + } + } + /** * Create a temporary collection for testing. * @@ -226,6 +183,18 @@ public String createTempTestCollection(XProtocol protocol) { return collName; } + public void dropTempTestCollection(XProtocol protocol) { + String collName = "protocol_test_collection"; + XMessageBuilder messageBuilder = (XMessageBuilder) protocol.getMessageBuilder(); + + try { + protocol.send(messageBuilder.buildDropCollection(getTestDatabase(), collName), 0); + protocol.readQueryResult(new StatementExecuteOkBuilder()); + } catch (XProtocolError err) { + // ignore + } + } + protected static EX assertThrows(Class throwable, Callable testRoutine) { return assertThrows("", throwable, null, testRoutine); } @@ -245,15 +214,10 @@ protected static EX assertThrows(String message, ClassgetMessageBuilder(); try { @@ -113,9 +114,8 @@ public void testCreateDropCollection() { @Test public void testGetObjects() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + XMessageBuilder builder = (XMessageBuilder) this.session.getMessageBuilder(); ValueFactory svf = new StringValueFactory(new DefaultPropertySet()); String collName = "test_get_objects"; @@ -145,67 +145,75 @@ public void testGetObjects() { @Test public void testInterleavedResults() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + XMessageBuilder builder = (XMessageBuilder) this.session.getMessageBuilder(); String collName = "testInterleavedResults"; try { - this.session.query(builder.buildDropCollection(getTestDatabase(), collName), new UpdateResultBuilder<>()); - } catch (XProtocolError e) { - if (e.getErrorCode() != MysqlErrorNumbers.ER_BAD_TABLE_ERROR) { - throw e; + try { + this.session.query(builder.buildDropCollection(getTestDatabase(), collName), new UpdateResultBuilder<>()); + } catch (XProtocolError e) { + if (e.getErrorCode() != MysqlErrorNumbers.ER_BAD_TABLE_ERROR) { + throw e; + } + } + this.session.query(builder.buildCreateCollection(getTestDatabase(), collName), new UpdateResultBuilder<>()); + + List stringDocs = new ArrayList<>(); + stringDocs.add("{'_id':'0'}"); + stringDocs.add("{'_id':'1'}"); + stringDocs.add("{'_id':'2'}"); + stringDocs.add("{'_id':'3'}"); + stringDocs.add("{'_id':'4'}"); + stringDocs = stringDocs.stream().map(s -> s.replaceAll("'", "\"")).collect(Collectors.toList()); + this.session.query(builder.buildDocInsert(getTestDatabase(), collName, stringDocs, false), new UpdateResultBuilder<>()); + + FilterParams filterParams = new DocFilterParams(getTestDatabase(), collName); + filterParams.setOrder("$._id"); + + DocResult docs1 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), + new StreamingDocResultBuilder(this.session)); + DocResult docs2 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), + new StreamingDocResultBuilder(this.session)); + DocResult docs3 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), + new StreamingDocResultBuilder(this.session)); + DocResult docs4 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), + new StreamingDocResultBuilder(this.session)); + DocResult docs5 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), + new StreamingDocResultBuilder(this.session)); + assertTrue(docs5.hasNext()); + assertTrue(docs4.hasNext()); + assertTrue(docs3.hasNext()); + assertTrue(docs2.hasNext()); + assertTrue(docs1.hasNext()); + for (int i = 0; i < 5; ++i) { + assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs1.next().toFormattedString()); + assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs2.next().toFormattedString()); + assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs3.next().toFormattedString()); + assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs4.next().toFormattedString()); + assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs5.next().toFormattedString()); + } + assertFalse(docs5.hasNext()); + assertFalse(docs4.hasNext()); + assertFalse(docs3.hasNext()); + assertFalse(docs2.hasNext()); + assertFalse(docs1.hasNext()); + // let the session be closed with all of these "open" + } finally { + try { + this.session.query(builder.buildDropCollection(getTestDatabase(), collName), new UpdateResultBuilder<>()); + } catch (XProtocolError e) { + if (e.getErrorCode() != MysqlErrorNumbers.ER_BAD_TABLE_ERROR) { + throw e; + } } } - this.session.query(builder.buildCreateCollection(getTestDatabase(), collName), new UpdateResultBuilder<>()); - - List stringDocs = new ArrayList<>(); - stringDocs.add("{'_id':'0'}"); - stringDocs.add("{'_id':'1'}"); - stringDocs.add("{'_id':'2'}"); - stringDocs.add("{'_id':'3'}"); - stringDocs.add("{'_id':'4'}"); - stringDocs = stringDocs.stream().map(s -> s.replaceAll("'", "\"")).collect(Collectors.toList()); - this.session.query(builder.buildDocInsert(getTestDatabase(), collName, stringDocs, false), new UpdateResultBuilder<>()); - - FilterParams filterParams = new DocFilterParams(getTestDatabase(), collName); - filterParams.setOrder("$._id"); - - DocResult docs1 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), - new StreamingDocResultBuilder(this.session)); - DocResult docs2 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), - new StreamingDocResultBuilder(this.session)); - DocResult docs3 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), - new StreamingDocResultBuilder(this.session)); - DocResult docs4 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), - new StreamingDocResultBuilder(this.session)); - DocResult docs5 = this.session.query(((XMessageBuilder) this.session.getMessageBuilder()).buildFind(filterParams), - new StreamingDocResultBuilder(this.session)); - assertTrue(docs5.hasNext()); - assertTrue(docs4.hasNext()); - assertTrue(docs3.hasNext()); - assertTrue(docs2.hasNext()); - assertTrue(docs1.hasNext()); - for (int i = 0; i < 5; ++i) { - assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs1.next().toFormattedString()); - assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs2.next().toFormattedString()); - assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs3.next().toFormattedString()); - assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs4.next().toFormattedString()); - assertEquals("{\n\"_id\" : \"" + i + "\"\n}", docs5.next().toFormattedString()); - } - assertFalse(docs5.hasNext()); - assertFalse(docs4.hasNext()); - assertFalse(docs3.hasNext()); - assertFalse(docs2.hasNext()); - assertFalse(docs1.hasNext()); - // let the session be closed with all of these "open" } @Test public void testGenericQuery() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + XMessageBuilder builder = (XMessageBuilder) this.session.getMessageBuilder(); List ints = this.session.query(builder.buildSqlStatement("select 2 union select 1"), null, r -> r.getValue(0, new IntegerValueFactory(new DefaultPropertySet())), Collectors.toList()); diff --git a/src/test/java/testsuite/x/internal/XProtocolAsyncTest.java b/src/test/java/testsuite/x/internal/XProtocolAsyncTest.java index 85b372924..14acd55dd 100644 --- a/src/test/java/testsuite/x/internal/XProtocolAsyncTest.java +++ b/src/test/java/testsuite/x/internal/XProtocolAsyncTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.IOException; import java.util.ArrayList; @@ -44,6 +45,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.ProtocolEntity; import com.mysql.cj.protocol.ResultBuilder; @@ -68,7 +70,7 @@ public class XProtocolAsyncTest extends InternalXBaseTestCase { @BeforeEach public void setupTestProtocol() { if (this.isSetForXTests) { - this.protocol = createAuthenticatedTestProtocol(); + this.protocol = createAuthenticatedTestProtocol(createTestProtocol(), this.testProperties); this.messageBuilder = (XMessageBuilder) this.protocol.getMessageBuilder(); } } @@ -99,66 +101,69 @@ public T get() { @Test public void simpleSuccessfulQuery() throws Exception { - if (!this.isSetForXTests) { - return; - } - String collName = createTempTestCollection(this.protocol); + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); - String json = "{'_id': '85983efc2a9a11e5b345feff819cdc9f', 'testVal': 1, 'insertedBy': 'Jess'}".replaceAll("'", "\""); - this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, Arrays.asList(new String[] { json }), false), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + try { + String collName = createTempTestCollection(this.protocol); - final ValueHolder metadataHolder = new ValueHolder<>(); - final ValueHolder> rowHolder = new ValueHolder<>(); - rowHolder.accept(new ArrayList<>()); - final ValueHolder okHolder = new ValueHolder<>(); - final ValueHolder excHolder = new ValueHolder<>(); + String json = "{'_id': '85983efc2a9a11e5b345feff819cdc9f', 'testVal': 1, 'insertedBy': 'Jess'}".replaceAll("'", "\""); + this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, Arrays.asList(new String[] { json }), false), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - this.protocol.queryAsync(this.messageBuilder.buildFind(new DocFilterParams(getTestDatabase(), collName)), new ResultBuilder() { + final ValueHolder metadataHolder = new ValueHolder<>(); + final ValueHolder> rowHolder = new ValueHolder<>(); + rowHolder.accept(new ArrayList<>()); + final ValueHolder okHolder = new ValueHolder<>(); + final ValueHolder excHolder = new ValueHolder<>(); - private ArrayList fields = new ArrayList<>(); - private ColumnDefinition metadata; + this.protocol.queryAsync(this.messageBuilder.buildFind(new DocFilterParams(getTestDatabase(), collName)), new ResultBuilder() { - @Override - public boolean addProtocolEntity(ProtocolEntity entity) { - if (entity instanceof Field) { - this.fields.add((Field) entity); + private ArrayList fields = new ArrayList<>(); + private ColumnDefinition metadata; - } else if (entity instanceof ColumnDefinition) { - this.metadata = (ColumnDefinition) entity; - metadataHolder.accept(this.metadata); + @Override + public boolean addProtocolEntity(ProtocolEntity entity) { + if (entity instanceof Field) { + this.fields.add((Field) entity); - } else if (entity instanceof Row) { - if (this.metadata == null) { - this.metadata = new DefaultColumnDefinition(this.fields.toArray(new Field[] {})); + } else if (entity instanceof ColumnDefinition) { + this.metadata = (ColumnDefinition) entity; metadataHolder.accept(this.metadata); - } - rowHolder.get().add((Row) entity); - } else if (entity instanceof StatementExecuteOk) { - okHolder.accept((StatementExecuteOk) entity); - synchronized (XProtocolAsyncTest.this) { - XProtocolAsyncTest.this.notify(); + } else if (entity instanceof Row) { + if (this.metadata == null) { + this.metadata = new DefaultColumnDefinition(this.fields.toArray(new Field[] {})); + metadataHolder.accept(this.metadata); + } + rowHolder.get().add((Row) entity); + + } else if (entity instanceof StatementExecuteOk) { + okHolder.accept((StatementExecuteOk) entity); + synchronized (XProtocolAsyncTest.this) { + XProtocolAsyncTest.this.notify(); + } + return true; } - return true; + return false; } - return false; - } - @Override - public RowResult build() { - return null; + @Override + public RowResult build() { + return null; + } + }); + + synchronized (this) { + // timeout in case we get stuck + this.wait(5000); } - }); - synchronized (this) { - // timeout in case we get stuck - this.wait(5000); + assertEquals(1, metadataHolder.get().getFields().length); + assertEquals(1, rowHolder.get().size()); + assertNotNull(okHolder.get()); + assertNull(excHolder.get()); + } finally { + dropTempTestCollection(this.protocol); } - - assertEquals(1, metadataHolder.get().getFields().length); - assertEquals(1, rowHolder.get().size()); - assertNotNull(okHolder.get()); - assertNull(excHolder.get()); } } diff --git a/src/test/java/testsuite/x/internal/XProtocolAuthTest.java b/src/test/java/testsuite/x/internal/XProtocolAuthTest.java index fc7595daf..cc4665870 100644 --- a/src/test/java/testsuite/x/internal/XProtocolAuthTest.java +++ b/src/test/java/testsuite/x/internal/XProtocolAuthTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -31,12 +31,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.protocol.x.OkBuilder; import com.mysql.cj.protocol.x.XMessageBuilder; @@ -55,6 +57,7 @@ public class XProtocolAuthTest extends InternalXBaseTestCase { public void setupTestProtocol() throws Exception { if (this.isSetForXTests) { protocol = createTestProtocol(); + protocol.beforeHandshake(); this.messageBuilder = (XMessageBuilder) protocol.getMessageBuilder(); } } @@ -74,34 +77,24 @@ public void destroyTestProtocol() throws Exception { */ @Test public void testBadAuthMessage() throws Exception { - if (!this.isSetForXTests) { - return; - } - try { - protocol.send(this.messageBuilder.buildCreateCollection(getTestDatabase(), "wont_be_Created"), 0); - protocol.readQueryResult(new OkBuilder()); - fail("Should fail after first message is sent"); - } catch (XProtocolError err) { - // expected - //ex.printStackTrace(); - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + protocol.send(this.messageBuilder.buildCreateCollection(getTestDatabase(), "wont_be_Created"), 0); + assertThrows("Should fail after first message is sent", XProtocolError.class, () -> protocol.readQueryResult(new OkBuilder())); } @Test @Disabled("PLAIN only supported over SSL") public void testBasicSaslPlainAuth() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + protocol.send(this.messageBuilder.buildPlainAuthStart(getTestUser(), getTestPassword(), getTestDatabase()), 0); protocol.readAuthenticateOk(); } @Test public void testBasicSaslMysql41Auth() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + try { Session testSession = this.fact.getSession(this.baseUrl); testSession.sql("CREATE USER IF NOT EXISTS 'testPlainAuth'@'%' IDENTIFIED WITH mysql_native_password BY 'pwd'").execute(); @@ -124,9 +117,8 @@ public void testBasicSaslMysql41Auth() throws Exception { @Test @Disabled("PLAIN only supported over SSL") public void testBasicSaslPlainAuthFailure() throws Exception { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + try { protocol.send(this.messageBuilder.buildPlainAuthStart(getTestUser(), "com.mysql.cj.theWrongPassword", getTestDatabase()), 0); protocol.readAuthenticateOk(); @@ -142,9 +134,7 @@ public void testBasicSaslPlainAuthFailure() throws Exception { */ @Test public void testEmptyDatabaseMYSQL41() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); try { Session testSession = this.fact.getSession(this.baseUrl); @@ -170,9 +160,8 @@ public void testEmptyDatabaseMYSQL41() { @Test @Disabled("PLAIN only supported over SSL") public void testEmptyDatabasePLAIN() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + protocol.send(this.messageBuilder.buildPlainAuthStart(getTestUser(), getTestPassword(), null), 0); protocol.readAuthenticateOk(); } diff --git a/src/test/java/testsuite/x/internal/XProtocolTest.java b/src/test/java/testsuite/x/internal/XProtocolTest.java index 07be31d0a..7620f32fe 100644 --- a/src/test/java/testsuite/x/internal/XProtocolTest.java +++ b/src/test/java/testsuite/x/internal/XProtocolTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. + * Copyright (c) 2015, 2021, 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 @@ -32,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.IOException; import java.util.ArrayList; @@ -49,6 +50,7 @@ import org.junit.jupiter.api.Test; import com.mysql.cj.MysqlType; +import com.mysql.cj.conf.PropertyDefinitions; import com.mysql.cj.exceptions.MysqlErrorNumbers; import com.mysql.cj.protocol.ColumnDefinition; import com.mysql.cj.protocol.x.StatementExecuteOkBuilder; @@ -81,7 +83,7 @@ public class XProtocolTest extends InternalXBaseTestCase { @BeforeEach public void setupTestProtocol() { if (this.isSetForXTests) { - this.protocol = createAuthenticatedTestProtocol(); + this.protocol = createAuthenticatedTestProtocol(createTestProtocol(), this.testProperties); this.messageBuilder = (XMessageBuilder) this.protocol.getMessageBuilder(); } } @@ -103,9 +105,8 @@ public void destroyTestProtocol() throws IOException { */ @Test public void testCreateAndDropCollection() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + try { this.protocol.send(this.messageBuilder.buildCreateCollection(getTestDatabase(), "testCreateAndDropCollection"), 0); this.protocol.readQueryResult(new StatementExecuteOkBuilder()); @@ -128,9 +129,8 @@ public void testCreateAndDropCollection() { @Test public void testTrivialSqlQuery() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + this.protocol.send(this.messageBuilder.buildSqlStatement("select 'x' as y"), 0); assertTrue(this.protocol.hasResults()); ColumnDefinition metadata = this.protocol.readMetadata(); @@ -148,9 +148,8 @@ public void testTrivialSqlQuery() { @Test public void testAnotherBasicSqlQuery() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + this.protocol.send(this.messageBuilder .buildSqlStatement("select 'x' as a_string, 42 as a_long, 7.6 as a_decimal union select 'y' as a_string, 11 as a_long, .1111 as a_decimal"), 0); assertTrue(this.protocol.hasResults()); @@ -193,105 +192,110 @@ public void testAnotherBasicSqlQuery() { */ @Test public void testDecodingAllTypes() { - if (!this.isSetForXTests) { - return; - } - // some types depend on this table - this.protocol.send(this.messageBuilder.buildSqlStatement("drop table if exists xprotocol_types_test"), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - String testTable = "create table xprotocol_types_test ("; - testTable += " a_float float"; - testTable += ",a_set SET('abc', 'def', 'xyz')"; - testTable += ",an_enum ENUM('enum value a', 'enum value b')"; - testTable += ",an_unsigned_int bigint unsigned"; - this.protocol.send(this.messageBuilder.buildSqlStatement(testTable + ")"), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - this.protocol.send( - this.messageBuilder.buildSqlStatement("insert into xprotocol_types_test values ('2.42', 'xyz,def', 'enum value a', 9223372036854775808)"), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); - Map> tests = new HashMap<>(); - tests.put("'some string' as a_string", (metadata, row) -> { - assertEquals("a_string", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_VARCHAR, metadata.getFields()[0].getMysqlTypeId()); - assertEquals("some string", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("date('2015-03-22') as a_date", (metadata, row) -> { - assertEquals("a_date", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_DATETIME, metadata.getFields()[0].getMysqlTypeId()); - assertEquals("2015-03-22", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("curtime() as curtime, cast(curtime() as char(8)) as curtime_string", (metadata, row) -> { - assertEquals("curtime", metadata.getFields()[0].getColumnLabel()); - assertEquals("curtime_string", metadata.getFields()[1].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_TIME, metadata.getFields()[0].getMysqlTypeId()); - String curtimeString = row.getValue(1, new StringValueFactory(this.protocol.getPropertySet())); - assertEquals(curtimeString, row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("timestamp('2015-05-01 12:01:32') as a_datetime", (metadata, row) -> { - assertEquals("a_datetime", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_DATETIME, metadata.getFields()[0].getMysqlTypeId()); - assertEquals("2015-05-01 12:01:32", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("cos(1) as a_double", (metadata, row) -> { - assertEquals("a_double", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_DOUBLE, metadata.getFields()[0].getMysqlTypeId()); - // value is 0.5403023058681398. Test most of it - assertTrue(row.getValue(0, new StringValueFactory(this.protocol.getPropertySet())).startsWith("0.540302305868139")); - }); - tests.put("2142 as an_int", (metadata, row) -> { - assertEquals("an_int", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_LONGLONG, metadata.getFields()[0].getMysqlTypeId()); - assertEquals("2142", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("21.424 as decimal1, -1.0 as decimal2, -0.1 as decimal3, 1000.0 as decimal4", (metadata, row) -> { - assertEquals("decimal1", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[0].getMysqlTypeId()); - assertEquals("21.424", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - - assertEquals("decimal2", metadata.getFields()[1].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[1].getMysqlTypeId()); - assertEquals("-1.0", row.getValue(1, new StringValueFactory(this.protocol.getPropertySet()))); - - assertEquals("decimal3", metadata.getFields()[2].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[2].getMysqlTypeId()); - assertEquals("-0.1", row.getValue(2, new StringValueFactory(this.protocol.getPropertySet()))); - - assertEquals("decimal4", metadata.getFields()[3].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[3].getMysqlTypeId()); - assertEquals("1000.0", row.getValue(3, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("9223372036854775807 as a_large_integer", (metadata, row) -> { - // max signed 64bit integer - assertEquals("a_large_integer", metadata.getFields()[0].getColumnLabel()); - assertEquals(MysqlType.FIELD_TYPE_LONGLONG, metadata.getFields()[0].getMysqlTypeId()); - assertEquals("9223372036854775807", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("a_float, a_set, an_enum from xprotocol_types_test", (metadata, row) -> { - assertEquals("a_float", metadata.getFields()[0].getColumnLabel()); - assertEquals("xprotocol_types_test", metadata.getFields()[0].getTableName()); - assertEquals("2.4200000762939453", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - - assertEquals("a_set", metadata.getFields()[1].getColumnLabel()); - assertEquals("xprotocol_types_test", metadata.getFields()[1].getTableName()); - assertEquals("def,xyz", row.getValue(1, new StringValueFactory(this.protocol.getPropertySet()))); - - assertEquals("an_enum", metadata.getFields()[2].getColumnLabel()); - assertEquals("xprotocol_types_test", metadata.getFields()[2].getTableName()); - assertEquals("enum value a", row.getValue(2, new StringValueFactory(this.protocol.getPropertySet()))); - }); - tests.put("an_unsigned_int from xprotocol_types_test", (metadata, row) -> { - assertEquals("an_unsigned_int", metadata.getFields()[0].getColumnLabel()); - assertEquals("9223372036854775808", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - }); + try { + // some types depend on this table + this.protocol.send(this.messageBuilder.buildSqlStatement("drop table if exists xprotocol_types_test"), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + String testTable = "create table xprotocol_types_test ("; + testTable += " a_float float"; + testTable += ",a_set SET('abc', 'def', 'xyz')"; + testTable += ",an_enum ENUM('enum value a', 'enum value b')"; + testTable += ",an_unsigned_int bigint unsigned"; + this.protocol.send(this.messageBuilder.buildSqlStatement(testTable + ")"), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + this.protocol.send( + this.messageBuilder.buildSqlStatement("insert into xprotocol_types_test values ('2.42', 'xyz,def', 'enum value a', 9223372036854775808)"), + 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - // runner for above tests - for (Map.Entry> t : tests.entrySet()) { - this.protocol.send(this.messageBuilder.buildSqlStatement("select " + t.getKey()), 0); - assertTrue(this.protocol.hasResults()); - ColumnDefinition metadata = this.protocol.readMetadata(); - Iterator rowInputStream = new XProtocolRowInputStream(metadata, this.protocol, null); - t.getValue().accept(metadata, rowInputStream.next()); + Map> tests = new HashMap<>(); + tests.put("'some string' as a_string", (metadata, row) -> { + assertEquals("a_string", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_VARCHAR, metadata.getFields()[0].getMysqlTypeId()); + assertEquals("some string", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("date('2015-03-22') as a_date", (metadata, row) -> { + assertEquals("a_date", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_DATETIME, metadata.getFields()[0].getMysqlTypeId()); + assertEquals("2015-03-22", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("curtime() as curtime, cast(curtime() as char(8)) as curtime_string", (metadata, row) -> { + assertEquals("curtime", metadata.getFields()[0].getColumnLabel()); + assertEquals("curtime_string", metadata.getFields()[1].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_TIME, metadata.getFields()[0].getMysqlTypeId()); + String curtimeString = row.getValue(1, new StringValueFactory(this.protocol.getPropertySet())); + assertEquals(curtimeString, row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("timestamp('2015-05-01 12:01:32') as a_datetime", (metadata, row) -> { + assertEquals("a_datetime", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_DATETIME, metadata.getFields()[0].getMysqlTypeId()); + assertEquals("2015-05-01 12:01:32", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("cos(1) as a_double", (metadata, row) -> { + assertEquals("a_double", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_DOUBLE, metadata.getFields()[0].getMysqlTypeId()); + // value is 0.5403023058681398. Test most of it + assertTrue(row.getValue(0, new StringValueFactory(this.protocol.getPropertySet())).startsWith("0.540302305868139")); + }); + tests.put("2142 as an_int", (metadata, row) -> { + assertEquals("an_int", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_LONGLONG, metadata.getFields()[0].getMysqlTypeId()); + assertEquals("2142", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("21.424 as decimal1, -1.0 as decimal2, -0.1 as decimal3, 1000.0 as decimal4", (metadata, row) -> { + assertEquals("decimal1", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[0].getMysqlTypeId()); + assertEquals("21.424", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + + assertEquals("decimal2", metadata.getFields()[1].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[1].getMysqlTypeId()); + assertEquals("-1.0", row.getValue(1, new StringValueFactory(this.protocol.getPropertySet()))); + + assertEquals("decimal3", metadata.getFields()[2].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[2].getMysqlTypeId()); + assertEquals("-0.1", row.getValue(2, new StringValueFactory(this.protocol.getPropertySet()))); + + assertEquals("decimal4", metadata.getFields()[3].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_NEWDECIMAL, metadata.getFields()[3].getMysqlTypeId()); + assertEquals("1000.0", row.getValue(3, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("9223372036854775807 as a_large_integer", (metadata, row) -> { + // max signed 64bit integer + assertEquals("a_large_integer", metadata.getFields()[0].getColumnLabel()); + assertEquals(MysqlType.FIELD_TYPE_LONGLONG, metadata.getFields()[0].getMysqlTypeId()); + assertEquals("9223372036854775807", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("a_float, a_set, an_enum from xprotocol_types_test", (metadata, row) -> { + assertEquals("a_float", metadata.getFields()[0].getColumnLabel()); + assertEquals("xprotocol_types_test", metadata.getFields()[0].getTableName()); + assertEquals("2.4200000762939453", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + + assertEquals("a_set", metadata.getFields()[1].getColumnLabel()); + assertEquals("xprotocol_types_test", metadata.getFields()[1].getTableName()); + assertEquals("def,xyz", row.getValue(1, new StringValueFactory(this.protocol.getPropertySet()))); + + assertEquals("an_enum", metadata.getFields()[2].getColumnLabel()); + assertEquals("xprotocol_types_test", metadata.getFields()[2].getTableName()); + assertEquals("enum value a", row.getValue(2, new StringValueFactory(this.protocol.getPropertySet()))); + }); + tests.put("an_unsigned_int from xprotocol_types_test", (metadata, row) -> { + assertEquals("an_unsigned_int", metadata.getFields()[0].getColumnLabel()); + assertEquals("9223372036854775808", row.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + }); + + // runner for above tests + for (Map.Entry> t : tests.entrySet()) { + this.protocol.send(this.messageBuilder.buildSqlStatement("select " + t.getKey()), 0); + assertTrue(this.protocol.hasResults()); + ColumnDefinition metadata = this.protocol.readMetadata(); + Iterator rowInputStream = new XProtocolRowInputStream(metadata, this.protocol, null); + t.getValue().accept(metadata, rowInputStream.next()); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + } + } finally { + this.protocol.send(this.messageBuilder.buildSqlStatement("drop table if exists xprotocol_types_test"), 0); this.protocol.readQueryResult(new StatementExecuteOkBuilder()); } } @@ -301,9 +305,8 @@ public void testDecodingAllTypes() { */ @Test public void testSqlDml() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + this.protocol.send(this.messageBuilder.buildSqlStatement("drop table if exists mysqlx_sqlDmlTest"), 0); assertFalse(this.protocol.hasResults()); SqlResult res = this.protocol @@ -329,88 +332,96 @@ public void testSqlDml() { @Test public void testBasicCrudInsertFind() { - if (!this.isSetForXTests) { - return; - } - String collName = createTempTestCollection(this.protocol); + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); - String json = "{'_id': '85983efc2a9a11e5b345feff819cdc9f', 'testVal': 1, 'insertedBy': 'Jess'}".replaceAll("'", "\""); - this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, Arrays.asList(new String[] { json }), false), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + try { + String collName = createTempTestCollection(this.protocol); - FilterParams filterParams = new DocFilterParams(getTestDatabase(), collName); - filterParams.setCriteria("$.testVal = 2-1"); - this.protocol.send(this.messageBuilder.buildFind(filterParams), 0); + String json = "{'_id': '85983efc2a9a11e5b345feff819cdc9f', 'testVal': 1, 'insertedBy': 'Jess'}".replaceAll("'", "\""); + this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, Arrays.asList(new String[] { json }), false), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - ColumnDefinition metadata = this.protocol.readMetadata(); - Iterator ris = new XProtocolRowInputStream(metadata, this.protocol, null); - Row r = ris.next(); - assertEquals(json, r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + FilterParams filterParams = new DocFilterParams(getTestDatabase(), collName); + filterParams.setCriteria("$.testVal = 2-1"); + this.protocol.send(this.messageBuilder.buildFind(filterParams), 0); + + ColumnDefinition metadata = this.protocol.readMetadata(); + Iterator ris = new XProtocolRowInputStream(metadata, this.protocol, null); + Row r = ris.next(); + assertEquals(json, r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + } finally { + dropTempTestCollection(this.protocol); + } } @Test public void testMultiInsert() { - if (!this.isSetForXTests) { - return; - } - String collName = createTempTestCollection(this.protocol); - - List stringDocs = new ArrayList<>(); - stringDocs.add("{'a': 'A', 'a1': 'A1', '_id': 'a'}"); - stringDocs.add("{'b': 'B', 'b2': 'B2', '_id': 'b'}"); - stringDocs.add("{'c': 'C', 'c3': 'C3', '_id': 'c'}"); - stringDocs = stringDocs.stream().map(s -> s.replaceAll("'", "\"")).collect(Collectors.toList()); - this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, stringDocs, false), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); - FilterParams filterParams = new DocFilterParams(getTestDatabase(), collName); - filterParams.setOrder("_id"); - this.protocol.send(this.messageBuilder.buildFind(filterParams), 0); + try { + String collName = createTempTestCollection(this.protocol); + + List stringDocs = new ArrayList<>(); + stringDocs.add("{'a': 'A', 'a1': 'A1', '_id': 'a'}"); + stringDocs.add("{'b': 'B', 'b2': 'B2', '_id': 'b'}"); + stringDocs.add("{'c': 'C', 'c3': 'C3', '_id': 'c'}"); + stringDocs = stringDocs.stream().map(s -> s.replaceAll("'", "\"")).collect(Collectors.toList()); + this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, stringDocs, false), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - ColumnDefinition metadata = this.protocol.readMetadata(); - Iterator ris = new XProtocolRowInputStream(metadata, this.protocol, null); - Row r = ris.next(); - assertEquals(stringDocs.get(0), r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - r = ris.next(); - assertEquals(stringDocs.get(1), r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - r = ris.next(); - assertEquals(stringDocs.get(2), r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + FilterParams filterParams = new DocFilterParams(getTestDatabase(), collName); + filterParams.setOrder("_id"); + this.protocol.send(this.messageBuilder.buildFind(filterParams), 0); + + ColumnDefinition metadata = this.protocol.readMetadata(); + Iterator ris = new XProtocolRowInputStream(metadata, this.protocol, null); + Row r = ris.next(); + assertEquals(stringDocs.get(0), r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + r = ris.next(); + assertEquals(stringDocs.get(1), r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + r = ris.next(); + assertEquals(stringDocs.get(2), r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + } finally { + dropTempTestCollection(this.protocol); + } } @Test public void testDocUpdate() { - if (!this.isSetForXTests) { - return; - } - String collName = createTempTestCollection(this.protocol); + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); - String json = "{'_id': '85983efc2a9a11e5b345feff819cdc9f', 'testVal': '1', 'insertedBy': 'Jess'}".replaceAll("'", "\""); - this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, Arrays.asList(new String[] { json }), false), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + try { + String collName = createTempTestCollection(this.protocol); - List updates = new ArrayList<>(); - updates.add(new UpdateSpec(UpdateType.ITEM_SET, "$.a").setValue("lemon")); - updates.add(new UpdateSpec(UpdateType.ITEM_REMOVE, "$.insertedBy")); - this.protocol.send(this.messageBuilder.buildDocUpdate(new DocFilterParams(getTestDatabase(), collName), updates), 0); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + String json = "{'_id': '85983efc2a9a11e5b345feff819cdc9f', 'testVal': '1', 'insertedBy': 'Jess'}".replaceAll("'", "\""); + this.protocol.send(this.messageBuilder.buildDocInsert(getTestDatabase(), collName, Arrays.asList(new String[] { json }), false), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); - // verify - this.protocol.send(this.messageBuilder.buildFind(new DocFilterParams(getTestDatabase(), collName)), 0); - ColumnDefinition metadata = this.protocol.readMetadata(); - Iterator ris = new XProtocolRowInputStream(metadata, this.protocol, null); - Row r = ris.next(); - assertEquals("{\"a\": \"lemon\", \"_id\": \"85983efc2a9a11e5b345feff819cdc9f\", \"testVal\": \"1\"}", - r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); - this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + List updates = new ArrayList<>(); + updates.add(new UpdateSpec(UpdateType.ITEM_SET, "$.a").setValue("lemon")); + updates.add(new UpdateSpec(UpdateType.ITEM_REMOVE, "$.insertedBy")); + this.protocol.send(this.messageBuilder.buildDocUpdate(new DocFilterParams(getTestDatabase(), collName), updates), 0); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + + // verify + this.protocol.send(this.messageBuilder.buildFind(new DocFilterParams(getTestDatabase(), collName)), 0); + ColumnDefinition metadata = this.protocol.readMetadata(); + Iterator ris = new XProtocolRowInputStream(metadata, this.protocol, null); + Row r = ris.next(); + assertEquals("{\"a\": \"lemon\", \"_id\": \"85983efc2a9a11e5b345feff819cdc9f\", \"testVal\": \"1\"}", + r.getValue(0, new StringValueFactory(this.protocol.getPropertySet()))); + this.protocol.readQueryResult(new StatementExecuteOkBuilder()); + } finally { + dropTempTestCollection(this.protocol); + } } @Test public void tableInsert() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + this.protocol.send(this.messageBuilder.buildSqlStatement("drop table if exists tableInsert"), 0); this.protocol.readQueryResult(new StatementExecuteOkBuilder()); this.protocol.send(this.messageBuilder.buildSqlStatement("create table tableInsert (x int, y varchar(20), z decimal(10, 2))"), 0); @@ -447,9 +458,8 @@ public void tableInsert() { @Test public void testWarnings() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + this.protocol.send(this.messageBuilder.buildSqlStatement("explain select 1"), 0); this.protocol.readMetadata(); this.protocol.drainRows(); @@ -468,9 +478,8 @@ public void testWarnings() { @Test public void testEnableDisableNotices() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + this.protocol.send(this.messageBuilder.buildDisableNotices("warnings"), 0); // TODO currently only "warnings" are allowed to be disabled this.protocol.readQueryResult(new StatementExecuteOkBuilder()); @@ -496,9 +505,8 @@ public void testEnableDisableNotices() { */ @Test public void testResultSet() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); + // begin "send" stage, change this as necessary this.protocol.send(this.messageBuilder.buildListNotices(), 0); @@ -524,9 +532,7 @@ public void testResultSet() { @Test public void testCapabilities() { - if (!this.isSetForXTests) { - return; - } + assumeTrue(this.isSetForXTests, PropertyDefinitions.SYSP_testsuite_url_mysqlx + " must be set to run this test."); XServerCapabilities capabilities = (XServerCapabilities) this.protocol.getServerSession().getCapabilities();