blob: 5541946dab220c6002309372e2f863d0f7e91888 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.integration;
import static org.junit.jupiter.api.Assertions.*;
import java.sql.*;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Locale;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import org.junit.jupiter.api.*;
import org.mariadb.jdbc.*;
import org.mariadb.jdbc.integration.util.SocketFactoryBasicTest;
import org.mariadb.jdbc.integration.util.SocketFactoryTest;
@DisplayName("Connection Test")
public class ConnectionTest extends Common {
@BeforeAll
public static void beforeAll2() throws SQLException {
createSequenceTables();
}
@Test
public void isValid() throws SQLException {
Connection sharedConn = DriverManager.getConnection(mDefUrl);
assertTrue(sharedConn.isValid(2000));
sharedConn.close();
assertFalse(sharedConn.isValid(2000));
}
@Test
public void tcpKeepAlive() throws SQLException {
try (Connection con = createCon("&tcpKeepAlive=false")) {
con.isValid(1);
}
}
@Test
void isValidWrongValue() {
try {
sharedConn.isValid(-2000);
fail("most have thrown an error");
} catch (SQLException e) {
assertTrue(e.getMessage().contains("the value supplied for timeout is negative"));
}
}
@Test
void missingHost() {
assertThrowsContains(
SQLException.class,
() -> DriverManager.getConnection("jdbc:mariadb:///db"),
"hostname must be set to connect socket if not using local socket or pipe");
assertThrowsContains(
SQLException.class,
() -> DriverManager.getConnection("jdbc:mariadb:///db?socketFactory=test"),
"hostname must be set to connect socket");
}
@Test
void socketTimeout() throws SQLException {
Assumptions.assumeTrue(
!"maxscale".equals(System.getenv("srv"))
&& !"skysql".equals(System.getenv("srv"))
&& !"skysql-ha".equals(System.getenv("srv"))
&& !isXpand());
try (Connection con = createCon("&socketTimeout=50")) {
assertEquals(50, con.getNetworkTimeout());
Statement stmt = con.createStatement();
stmt.execute("SELECT 1");
Common.assertThrowsContains(SQLException.class, () -> stmt.execute("SELECT SLEEP(0.1)"), "");
}
try (Connection con = createCon("&socketTimeout=500")) {
assertEquals(500, con.getNetworkTimeout());
Statement stmt = con.createStatement();
stmt.execute("SELECT SLEEP(0.1)");
Common.assertThrowsContains(SQLException.class, () -> stmt.execute("SELECT SLEEP(1)"), "");
}
try (Connection con = createCon("&socketTimeout=0")) {
assertEquals(0, con.getNetworkTimeout());
Statement stmt = con.createStatement();
stmt.execute("SELECT SLEEP(0.5)");
}
}
@Test
public void autoCommit() throws SQLException {
Connection con = DriverManager.getConnection(mDefUrl);
assertTrue(con.getAutoCommit());
con.setAutoCommit(false);
assertFalse(con.getAutoCommit());
con.setAutoCommit(false);
assertFalse(con.getAutoCommit());
con.setAutoCommit(true);
assertTrue(con.getAutoCommit());
con.setAutoCommit(true);
assertTrue(con.getAutoCommit());
Statement stmt = con.createStatement();
stmt.execute("SET autocommit=false");
assertFalse(con.getAutoCommit());
con.close();
}
@Test
public void initSQL() throws SQLException {
try (Connection con = createCon("&initSql=SET @myVar='YourVar'")) {
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT @myVar");
assertTrue(rs.next());
assertEquals("YourVar", rs.getString(1));
}
}
@Test
public void nativeSQL() throws SQLException {
String[] inputs =
new String[] {
"select {fn TIMESTAMPDIFF ( SQL_TSI_HOUR, {fn convert('SQL_', SQL_INTEGER)})}, {fn"
+ " TIMESTAMPDIFF (HOUR, {fn convert ('sQL_' , SQL_INTEGER)})}",
"{call foo({fn now()})} //end",
"{call foo({fn '{' now()} /* {test}# \"'\" */) \"\\\"'#\" '\"\\''} #{test2}",
"{ call foo({fn now()})}",
"{\r\ncall foo({fn now()})}",
"{\r\n call foo({fn now()})}",
"{call foo(/*{fn now()}*/)}",
"{CALL foo({fn now() /* -- * */ -- test \n })}",
"{?=call foo({fn now()})}",
"SELECT 'David_' LIKE 'David|_' {escape '|'}",
"select {fn dayname ({fn abs({fn now()})})}",
"{d '1997-05-24'}",
"{d'1997-05-24'}",
"{\nt'10:30:29'}",
"{t '10:30:29'}",
"{t'10:30:29'}",
"{ts '1997-05-24 10:30:29.123'}",
"{ts'1997-05-24 10:30:29.123'}",
"'{string data with { or } will not be altered'",
"`{string data with { or } will not be altered`",
"-- Also note that you can safely include { and } in comments",
"SELECT * FROM {oj TABLE1 LEFT OUTER JOIN TABLE2 ON DEPT_NO = 003420930}"
};
String[] outputs =
new String[] {
"select TIMESTAMPDIFF ( HOUR, convert('SQL_', INTEGER)), TIMESTAMPDIFF (HOUR, convert "
+ " ('sQL_' , INTEGER))",
"call foo(now()) //end",
"call foo('{' now() /* {test}# \"'\" */) \"\\\"'#\" '\"\\'' #{test2}",
"call foo(now())",
"call foo(now())",
"call foo(now())",
"call foo(/*{fn now()}*/)",
"CALL foo(now() /* -- * */ -- test \n )",
"?=call foo(now())",
"SELECT 'David_' LIKE 'David|_' escape '|'",
"select dayname (abs(now()))",
"'1997-05-24'",
"'1997-05-24'",
"'10:30:29'",
"'10:30:29'",
"'10:30:29'",
"'1997-05-24 10:30:29.123'",
"'1997-05-24 10:30:29.123'",
"'{string data with { or } will not be altered'",
"`{string data with { or } will not be altered`",
"-- Also note that you can safely include { and } in comments",
"SELECT * FROM TABLE1 LEFT OUTER JOIN TABLE2 ON DEPT_NO = 003420930"
};
for (int i = 0; i < inputs.length; i++) {
assertEquals(outputs[i], sharedConn.nativeSQL(inputs[i]));
}
assertEquals(
"INSERT INTO TEST_SYNTAX_ERROR(str_value, json_value) VALUES ('abc\\\\', '{\"data\":"
+ " \"test\"}')",
sharedConn.nativeSQL(
"INSERT INTO TEST_SYNTAX_ERROR(str_value, json_value) VALUES ('abc\\\\', '{\"data\":"
+ " \"test\"}')"));
try {
sharedConn.nativeSQL("{call foo({fn now())}");
fail("most have thrown an error");
} catch (SQLException e) {
assertTrue(
e.getMessage().contains("Invalid escape sequence , missing closing '}' character in '"));
}
try {
sharedConn.nativeSQL("{call foo({unknown} )}");
fail("most have thrown an error");
} catch (SQLException e) {
assertTrue(e.getMessage().contains("unknown escape sequence {unknown}"));
}
}
@Test
public void nativeSQLNoBackSlash() throws SQLException {
try (Connection con = createCon()) {
java.sql.Statement stmt = con.createStatement();
stmt.execute("SET sql_mode = concat(@@sql_mode,',NO_BACKSLASH_ESCAPES')");
assertEquals("call foo('{' now())", con.nativeSQL("{call foo({fn '{' now()})}"));
}
}
@Test
public void nativeSqlTest() throws SQLException {
String exp;
if (isMariaDBServer() || minVersion(8, 0, 17)) {
exp =
"SELECT convert(foo(a,b,c), SIGNED INTEGER)"
+ ", convert(convert(?, CHAR), SIGNED INTEGER)"
+ ", 1=?"
+ ", 1=?"
+ ", convert(?, SIGNED INTEGER )"
+ ", convert (?, SIGNED INTEGER )"
+ ", convert(?, UNSIGNED INTEGER)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, DOUBLE)"
+ ", convert(?, DOUBLE)"
+ ", convert(?, DECIMAL)"
+ ", convert(?, DECIMAL)"
+ ", convert(?, DECIMAL)"
+ ", convert(?, DATETIME)"
+ ", convert(?, DATETIME)";
} else {
exp =
"SELECT convert(foo(a,b,c), SIGNED INTEGER)"
+ ", convert(convert(?, CHAR), SIGNED INTEGER)"
+ ", 1=?"
+ ", 1=?"
+ ", convert(?, SIGNED INTEGER )"
+ ", convert (?, SIGNED INTEGER )"
+ ", convert(?, UNSIGNED INTEGER)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, BINARY)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", convert(?, CHAR)"
+ ", 0.0+?"
+ ", 0.0+?"
+ ", convert(?, DECIMAL)"
+ ", convert(?, DECIMAL)"
+ ", convert(?, DECIMAL)"
+ ", convert(?, DATETIME)"
+ ", convert(?, DATETIME)";
}
assertEquals(
exp,
sharedConn.nativeSQL(
"SELECT {fn convert(foo(a,b,c), SQL_BIGINT)}"
+ ", {fn convert({fn convert(?, SQL_VARCHAR)}, SQL_BIGINT)}"
+ ", {fn convert(?, SQL_BOOLEAN )}"
+ ", {fn convert(?, BOOLEAN)}"
+ ", {fn convert(?, SMALLINT )}"
+ ", {fn convert (?, TINYINT )}"
+ ", {fn convert(?, SQL_BIT)}"
+ ", {fn convert(?, SQL_BLOB)}"
+ ", {fn convert(?, SQL_VARBINARY)}"
+ ", {fn convert(?, SQL_LONGVARBINARY)}"
+ ", {fn convert(?, SQL_ROWID)}"
+ ", {fn convert(?, SQL_BINARY)}"
+ ", {fn convert(?, SQL_NCHAR)}"
+ ", {fn convert(?, SQL_CLOB)}"
+ ", {fn convert(?, SQL_NCLOB)}"
+ ", {fn convert(?, SQL_DATALINK)}"
+ ", {fn convert(?, SQL_VARCHAR)}"
+ ", {fn convert(?, SQL_NVARCHAR)}"
+ ", {fn convert(?, SQL_LONGVARCHAR)}"
+ ", {fn convert(?, SQL_LONGNVARCHAR)}"
+ ", {fn convert(?, SQL_SQLXML)}"
+ ", {fn convert(?, SQL_LONGNCHAR)}"
+ ", {fn convert(?, SQL_CHAR)}"
+ ", {fn convert(?, SQL_FLOAT)}"
+ ", {fn convert(?, SQL_DOUBLE)}"
+ ", {fn convert(?, SQL_DECIMAL)}"
+ ", {fn convert(?, SQL_REAL)}"
+ ", {fn convert(?, SQL_NUMERIC)}"
+ ", {fn convert(?, SQL_TIMESTAMP)}"
+ ", {fn convert(?, SQL_DATETIME)}"));
}
@Test
public void doubleBackslash() throws SQLException {
java.sql.Statement stmt = sharedConn.createStatement();
stmt.setEscapeProcessing(true);
stmt.execute("DROP TABLE IF EXISTS TEST_SYNTAX_ERROR");
stmt.execute(
"CREATE TABLE TEST_SYNTAX_ERROR("
+ " id INTEGER unsigned NOT NULL AUTO_INCREMENT, "
+ " str_value MEDIUMTEXT CHARACTER SET utf8mb4 NOT NULL,"
+ " json_value MEDIUMTEXT CHARACTER SET utf8mb4 NOT NULL, "
+ " PRIMARY KEY ( id ))");
stmt.execute(
"INSERT INTO TEST_SYNTAX_ERROR(str_value, json_value) VALUES ('abc\\\\', '{\"data\":"
+ " \"test\"}')");
}
@Test
public void databaseStateChange() throws SQLException {
Assumptions.assumeTrue(
(isMariaDBServer() && minVersion(10, 2, 0)) || (!isMariaDBServer() && minVersion(5, 7, 0)));
try (Connection connection = createCon()) {
try (Statement stmt = connection.createStatement()) {
stmt.execute("drop database if exists _test_db");
stmt.execute("create database _test_db");
ResultSet rs = stmt.executeQuery("SELECT DATABASE()");
rs.next();
assertEquals(rs.getString(1), connection.getCatalog());
stmt.execute("USE _test_db");
assertEquals("_test_db", connection.getCatalog());
stmt.execute("drop database _test_db");
}
}
}
@Test
public void catalog() throws SQLException {
Assumptions.assumeTrue(
(isMariaDBServer() && minVersion(10, 2, 0)) || (!isMariaDBServer() && minVersion(5, 7, 0)));
try (Connection connection = createCon()) {
try (Statement stmt = connection.createStatement()) {
stmt.execute("drop database if exists _test_db");
stmt.execute("create database _test_db");
ResultSet rs = stmt.executeQuery("SELECT DATABASE()");
rs.next();
String initialCatalog = connection.getCatalog();
assertEquals(rs.getString(1), initialCatalog);
connection.setCatalog(initialCatalog);
assertEquals(initialCatalog, connection.getCatalog());
connection.setCatalog("_test_db");
assertEquals("_test_db", connection.getCatalog());
stmt.execute("USE _test_db");
assertEquals("_test_db", connection.getCatalog());
connection.setCatalog(null);
assertEquals("_test_db", connection.getCatalog());
connection.setCatalog("_test_db");
assertEquals("_test_db", connection.getCatalog());
stmt.execute("drop database _test_db");
assertTrue(connection.getCatalog() == null || "_test_db".equals(connection.getCatalog()));
}
}
}
@Test
public void checkFixedData() throws SQLException {
sharedConn.unwrap(java.sql.Connection.class);
Common.assertThrowsContains(
SQLException.class,
() -> sharedConn.unwrap(String.class),
"The receiver is not a wrapper for java.lang.String");
assertTrue(sharedConn.createBlob() instanceof Blob);
assertTrue(sharedConn.createClob() instanceof Clob);
assertTrue(sharedConn.createNClob() instanceof NClob);
assertThrows(SQLException.class, () -> sharedConn.createSQLXML());
assertThrows(SQLException.class, () -> sharedConn.createArrayOf("", null));
assertThrows(SQLException.class, () -> sharedConn.createStruct("", null));
assertNull(sharedConn.getSchema());
sharedConn.setSchema("fff");
assertNull(sharedConn.getSchema());
}
@Test
public void clientInfo() throws SQLException {
assertTrue(sharedConn.getClientInfo().isEmpty());
sharedConn.setClientInfo("some", "value");
Properties props = new Properties();
props.put("another", "one");
props.put("and another", "two");
sharedConn.setClientInfo(props);
assertEquals(3, sharedConn.getClientInfo().size());
assertEquals("value", sharedConn.getClientInfo("some"));
assertNull(sharedConn.getClientInfo("some33"));
}
@Test
public void abortTestAlreadyClosed() throws SQLException {
Connection connection = createCon();
connection.close();
Executor executor = Runnable::run;
connection.abort(executor);
}
@Test
public void abortTestNoExecutor() {
try {
sharedConn.abort(null);
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Cannot abort the connection: null executor passed"));
}
}
@Test
public void abortClose() throws Throwable {
Connection connection = createCon();
Statement stmt = connection.createStatement();
stmt.setFetchSize(1);
ResultSet rs =
stmt.executeQuery(
"select * from information_schema.columns as c1, "
+ "information_schema.tables, information_schema.tables as t2");
assertTrue(rs.next());
connection.abort(Runnable::run);
// must still work
Thread.sleep(20);
try {
assertTrue(rs.next());
fail();
} catch (SQLException sqle) {
assertTrue(sqle.getMessage().contains("Operation not permit on a closed resultSet"));
}
}
@Test
public void verificationAbort() throws Throwable {
Timer timer = new Timer();
try (Connection connection = createCon()) {
timer.schedule(
new TimerTask() {
@Override
public void run() {
try {
connection.abort(Runnable::run);
} catch (SQLException sqle) {
fail(sqle.getMessage());
}
}
},
10);
Statement stmt = connection.createStatement();
assertThrows(
SQLException.class,
() ->
stmt.executeQuery(
"select * from information_schema.columns as c1, information_schema.tables,"
+ " information_schema.tables as t2"));
}
}
@Test
public void networkTimeoutTest() throws SQLException {
try (Connection connection = createCon()) {
assertEquals(0, connection.getNetworkTimeout());
int timeout = 1000;
Executor executor = Runnable::run;
connection.setNetworkTimeout(executor, timeout);
connection.isValid(2);
assertEquals(timeout, connection.getNetworkTimeout());
try {
Statement stmt = connection.createStatement();
stmt.execute("select sleep(2)");
fail("Network timeout is " + timeout / 1000 + "sec, but slept for 2 sec");
} catch (SQLException sqlex) {
assertTrue(connection.isClosed());
}
}
}
@Test
public void testConnectionAttributes() throws Exception {
// check if performance_schema is ON
Statement stmt = sharedConn.createStatement();
ResultSet res = stmt.executeQuery("show variables like 'performance_schema'");
if (res.next()) {
Assumptions.assumeFalse(res.getString("Value").equals("OFF"));
try (Connection connection = createCon()) {
Statement attributeStatement = connection.createStatement();
ResultSet result =
attributeStatement.executeQuery(
"select * from performance_schema.session_connect_attrs where"
+ " ATTR_NAME='_server_host' and processlist_id = connection_id()");
while (result.next()) {
String strVal = result.getString("ATTR_VALUE");
assertEquals(Configuration.parse(mDefUrl).addresses().get(0).host, strVal);
}
}
}
}
@Test
public void isolationLevel() throws SQLException {
java.sql.Connection connection = createCon();
int[] levels =
new int[] {
java.sql.Connection.TRANSACTION_READ_UNCOMMITTED,
java.sql.Connection.TRANSACTION_READ_COMMITTED,
java.sql.Connection.TRANSACTION_SERIALIZABLE,
java.sql.Connection.TRANSACTION_REPEATABLE_READ
};
for (int level : levels) {
connection.setTransactionIsolation(level);
assertEquals(level, connection.getTransactionIsolation());
}
connection.close();
assertThrows(
SQLException.class,
() -> connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED));
try (java.sql.Connection con2 = createCon()) {
try {
con2.setTransactionIsolation(10_000);
fail();
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Unsupported transaction isolation level"));
}
}
}
@Test
public void savepointTest() throws SQLException {
try (Connection con = createCon()) {
Statement stmt = con.createStatement();
stmt.execute("CREATE TEMPORARY TABLE spt(test varchar(10))");
con.setAutoCommit(false);
stmt.executeUpdate("INSERT INTO spt values('hej1')");
stmt.executeUpdate("INSERT INTO spt values('hej2')");
Savepoint savepoint = con.setSavepoint("ye`\\\\`p");
stmt.executeUpdate("INSERT INTO spt values('hej3')");
stmt.executeUpdate("INSERT INTO spt values('hej4')");
assertEquals("ye``\\\\``p", savepoint.getSavepointName());
Common.assertThrowsContains(
SQLException.class,
savepoint::getSavepointId,
"Cannot retrieve savepoint id of a named savepoint");
con.rollback(savepoint);
stmt.executeUpdate("INSERT INTO spt values('hej5')");
stmt.executeUpdate("INSERT INTO spt values('hej6')");
con.commit();
ResultSet rs = stmt.executeQuery("SELECT * FROM spt");
assertTrue(rs.next());
assertEquals("hej1", rs.getString(1));
assertTrue(rs.next());
assertEquals("hej2", rs.getString(1));
assertTrue(rs.next());
assertEquals("hej5", rs.getString(1));
assertTrue(rs.next());
assertEquals("hej6", rs.getString(1));
assertFalse(rs.next());
}
}
@Test
public void netWorkTimeout() throws SQLException {
Connection con = createCon();
Common.assertThrowsContains(
SQLException.class,
() -> con.setNetworkTimeout(Runnable::run, -200),
"Connection.setNetworkTimeout cannot be called with a negative timeout");
con.close();
Common.assertThrowsContains(
SQLException.class,
() -> con.setNetworkTimeout(Runnable::run, 200),
"Connection.setNetworkTimeout cannot be called on a closed connection");
}
@Test
public void savepointUnname() throws SQLException {
try (Connection con = createCon()) {
Statement stmt = con.createStatement();
stmt.execute("CREATE TEMPORARY TABLE spt(test varchar(10))");
con.setAutoCommit(false);
stmt.executeUpdate("INSERT INTO spt values('hej1')");
stmt.executeUpdate("INSERT INTO spt values('hej2')");
Savepoint savepoint = con.setSavepoint();
stmt.executeUpdate("INSERT INTO spt values('hej3')");
stmt.executeUpdate("INSERT INTO spt values('hej4')");
assertTrue(savepoint.getSavepointId() > 0);
Common.assertThrowsContains(
SQLException.class,
savepoint::getSavepointName,
"Cannot retrieve savepoint name of an unnamed savepoint");
con.rollback(savepoint);
Common.assertThrowsContains(
SQLException.class, () -> con.rollback(new MySavepoint()), "Unknown savepoint type");
stmt.executeUpdate("INSERT INTO spt values('hej5')");
stmt.executeUpdate("INSERT INTO spt values('hej6')");
con.commit();
con.commit();
ResultSet rs = stmt.executeQuery("SELECT * FROM spt");
assertTrue(rs.next());
assertEquals("hej1", rs.getString(1));
assertTrue(rs.next());
assertEquals("hej2", rs.getString(1));
assertTrue(rs.next());
assertEquals("hej5", rs.getString(1));
assertTrue(rs.next());
assertEquals("hej6", rs.getString(1));
assertFalse(rs.next());
}
}
@Test
public void releaseSavepoint() throws SQLException {
try (Connection con = createCon()) {
Statement stmt = con.createStatement();
stmt.execute("CREATE TEMPORARY TABLE spt(test varchar(10)) ");
con.setAutoCommit(false);
stmt.executeUpdate("INSERT INTO spt values('hej1')");
stmt.executeUpdate("INSERT INTO spt values('hej2')");
Savepoint savepoint = con.setSavepoint();
stmt.executeUpdate("INSERT INTO spt values('hej3')");
stmt.executeUpdate("INSERT INTO spt values('hej4')");
con.releaseSavepoint(savepoint);
Common.assertThrowsContains(
SQLException.class,
() -> con.releaseSavepoint(new MySavepoint()),
"Unknown savepoint type");
stmt.executeUpdate("INSERT INTO spt values('hej5')");
stmt.executeUpdate("INSERT INTO spt values('hej6')");
con.commit();
ResultSet rs = stmt.executeQuery("SELECT * FROM spt");
for (int i = 1; i < 7; i++) {
assertTrue(rs.next());
assertEquals("hej" + i, rs.getString(1));
}
assertFalse(rs.next());
}
}
static class MySavepoint implements Savepoint {
@Override
public int getSavepointId() throws SQLException {
return 0;
}
@Override
public String getSavepointName() throws SQLException {
return null;
}
}
@Nested
@DisplayName("Transaction Test")
class Transaction {
@Test
public void testProperRollback() throws Exception {
java.sql.Statement stmt = sharedConn.createStatement();
try {
stmt.execute("CREATE TABLE tx_prim_key(id int not null primary key) engine=innodb");
stmt.execute(
"CREATE TABLE tx_fore_key (id int not null primary key, id_ref int not null, foreign"
+ " key (id_ref) references tx_prim_key(id) on delete restrict on update restrict) "
+ "engine=innodb");
stmt.executeUpdate("insert into tx_prim_key(id) values(32)");
stmt.executeUpdate("insert into tx_fore_key(id, id_ref) values(42, 32)");
// 2. try to delete entry in Primary table in a transaction - which will fail due
// foreign key.
sharedConn.setAutoCommit(false);
try (java.sql.Statement st = sharedConn.createStatement()) {
st.executeUpdate("delete from tx_prim_key where id = 32");
sharedConn.commit();
fail("Expected SQLException");
} catch (SQLException e) {
// This exception is expected
assertTrue(
e.getMessage().contains("a foreign key constraint fails")
|| e.getMessage().contains("Foreign key constraint violation"));
sharedConn.rollback();
}
try (java.sql.Connection conn2 = createCon();
java.sql.Statement st = conn2.createStatement()) {
st.setQueryTimeout(30000);
st.executeUpdate("delete from tx_fore_key where id = 42");
st.executeUpdate("delete from tx_prim_key where id = 32");
}
} finally {
stmt.execute("drop table if exists tx_fore_key");
stmt.execute("drop table if exists tx_prim_key");
}
}
@Test
public void transactionTest() throws SQLException {
Statement stmt = sharedConn.createStatement();
try {
stmt.execute(
"CREATE TABLE transaction_test "
+ "(id int not null primary key auto_increment, test varchar(20)) "
+ "engine=innodb");
sharedConn.setAutoCommit(false);
stmt.executeUpdate("INSERT INTO transaction_test (test) VALUES ('heja')");
stmt.executeUpdate("INSERT INTO transaction_test (test) VALUES ('japp')");
sharedConn.commit();
ResultSet rs = stmt.executeQuery("SELECT * FROM transaction_test");
assertEquals(true, rs.next());
assertEquals("heja", rs.getString("test"));
assertEquals(true, rs.next());
assertEquals("japp", rs.getString("test"));
assertEquals(false, rs.next());
stmt.executeUpdate(
"INSERT INTO transaction_test (test) VALUES ('rollmeback')",
java.sql.Statement.RETURN_GENERATED_KEYS);
ResultSet rsGen = stmt.getGeneratedKeys();
rsGen.next();
int[] autoInc = setAutoInc();
assertEquals(autoInc[1] + autoInc[0] * 3, rsGen.getInt(1));
sharedConn.rollback();
rs = stmt.executeQuery("SELECT * FROM transaction_test WHERE id=3");
assertEquals(false, rs.next());
sharedConn.setAutoCommit(true);
} finally {
stmt.execute("DROP TABLE IF EXISTS transaction_test");
}
}
/**
* Get current autoincrement value, since Galera values are automatically set.
*
* @throws SQLException if any error occur.
*/
public int[] setAutoInc() throws SQLException {
return setAutoInc(1, 0);
}
/**
* Get current autoincrement value, since Galera values are automatically set.
*
* @param autoIncInit default increment
* @param autoIncOffsetInit default increment offset
* @throws SQLException if any error occur
* @see <a
* href="https://mariadb.org/auto-increments-in-galera/">https://mariadb.org/auto-increments-in-galera/</a>
*/
public int[] setAutoInc(int autoIncInit, int autoIncOffsetInit) throws SQLException {
// in case of galera
// if (isGalera()) {
// ResultSet rs =
// sharedConn.createStatement().executeQuery("show variables like
// '%auto_increment%'");
// while (rs.next()) {
// if ("auto_increment_increment".equals(rs.getString(1))) {
// autoInc = rs.getInt(2);
// }
// if ("auto_increment_offset".equals(rs.getString(1))) {
// autoIncOffset = rs.getInt(2);
// }
// }
// if (autoInc == 1) {
// // galera with one node only, then offset is not used
// autoIncOffset = 0;
// }
// }
return new int[] {autoIncInit, autoIncOffsetInit};
}
}
@Test
public void various() throws SQLException {
assertThrows(SQLException.class, () -> sharedConn.setTypeMap(null));
assertTrue(sharedConn.getTypeMap().isEmpty());
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, sharedConn.getHoldability());
sharedConn.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, sharedConn.getHoldability());
}
@Test
public void verificationEd25519AuthPlugin() throws Throwable {
Assumptions.assumeTrue(
!"maxscale".equals(System.getenv("srv"))
&& !"skysql".equals(System.getenv("srv"))
&& !"skysql-ha".equals(System.getenv("srv"))
&& isMariaDBServer()
&& minVersion(10, 2, 0));
Statement stmt = sharedConn.createStatement();
try {
stmt.execute("INSTALL SONAME 'auth_ed25519'");
} catch (SQLException sqle) {
Assumptions.assumeTrue(false, "server doesn't have ed25519 plugin, cancelling test");
}
try {
stmt.execute("drop user verificationEd25519AuthPlugin@'%'");
} catch (SQLException e) {
// eat
}
try {
if (minVersion(10, 4, 0)) {
stmt.execute(
"CREATE USER IF NOT EXISTS verificationEd25519AuthPlugin@'%' IDENTIFIED "
+ "VIA ed25519 USING PASSWORD('MySup8%rPassw@ord')");
} else {
stmt.execute(
"CREATE USER IF NOT EXISTS verificationEd25519AuthPlugin@'%' IDENTIFIED "
+ "VIA ed25519 USING '6aW9C7ENlasUfymtfMvMZZtnkCVlcb1ssxOLJ0kj/AA'");
}
} catch (SQLException sqle) {
// already existing
}
stmt.execute(
"GRANT SELECT on " + sharedConn.getCatalog() + ".* to verificationEd25519AuthPlugin");
try (Connection connection =
createCon("user=verificationEd25519AuthPlugin&password=MySup8%rPassw@ord")) {
// must have succeeded
connection.getCatalog();
}
stmt.execute("drop user verificationEd25519AuthPlugin@'%'");
}
@Test
public void pamAuthPlugin() throws Throwable {
// https://mariadb.com/kb/en/authentication-plugin-pam/
// only test on travis, because only work on Unix-like operating systems.
// /etc/pam.d/mariadb pam configuration is created beforehand
Assumptions.assumeTrue(
isMariaDBServer()
&& System.getenv("TEST_PAM_USER") != null
&& !"skysql".equals(System.getenv("srv"))
&& !"skysql-ha".equals(System.getenv("srv")));
Statement stmt = sharedConn.createStatement();
try {
stmt.execute("INSTALL PLUGIN pam SONAME 'auth_pam'");
} catch (SQLException sqle) {
// might be already set
}
String pamUser = System.getenv("TEST_PAM_USER");
String pamPwd = System.getenv("TEST_PAM_PWD");
try {
stmt.execute("DROP USER '" + pamUser + "'@'%'");
} catch (SQLException e) {
// eat
}
stmt.execute("CREATE USER '" + pamUser + "'@'%' IDENTIFIED VIA pam USING 'mariadb'");
stmt.execute("GRANT SELECT ON *.* TO '" + pamUser + "'@'%' IDENTIFIED VIA pam");
// mysql 8.0.31 broken public key retrieval, so avoid FLUSHING for now
Assumptions.assumeTrue(!isMariaDBServer() && !exactVersion(8, 0, 31));
stmt.execute("FLUSH PRIVILEGES");
int testPort = port;
if (System.getenv("TEST_PAM_PORT") != null) {
testPort = Integer.parseInt(System.getenv("TEST_PAM_PORT"));
}
String connStr =
String.format(
"jdbc:mariadb://%s:%s/%s?user=%s&password=%s&%s",
hostname, testPort, database, pamUser, pamPwd, defaultOther);
try {
try (Connection connection =
DriverManager.getConnection(connStr + "&restrictedAuth=dialog")) {
// must have succeeded
connection.getCatalog();
}
} catch (SQLException e) {
System.err.println("fail with connectionString : " + connStr + "&restrictedAuth=dialog");
throw e;
}
Common.assertThrowsContains(
SQLException.class,
() -> DriverManager.getConnection(connStr + "&restrictedAuth=other"),
"Client restrict authentication plugin to a limited set of authentication");
stmt.execute("drop user " + pamUser + "@'%'");
}
@Nested
@DisplayName("Compression Test")
class Compression {
@Test
public void testConnection() throws Exception {
try (Connection connection = createCon("useCompression")) {
// must have succeeded
connection.getCatalog();
}
}
}
@Test
public void testNoUseReadAheadInputConnection() throws Exception {
try (Connection connection = createCon("useReadAheadInput=false")) {
// must have succeeded
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM sequence_1_to_10");
int i = 0;
while (rs.next()) i++;
assertTrue(i > 0);
}
}
@Test
public void useNoDatabase() throws SQLException {
try (Connection con = createCon()) {
con.getCatalog();
Statement stmt = con.createStatement();
stmt.execute("CREATE DATABASE IF NOT EXISTS someDb");
con.setCatalog("someDb");
stmt.execute("DROP DATABASE someDb");
if (minVersion(10, 4, 0)
&& !"maxscale".equals(System.getenv("srv"))
&& !"skysql-ha".equals(System.getenv("srv"))) {
assertNull(con.getCatalog());
}
}
}
@Test
public void windowsNamedPipe() throws SQLException {
Assumptions.assumeTrue(isMariaDBServer() || !minVersion(8, 0, 14));
ResultSet rs = null;
try {
rs = sharedConn.createStatement().executeQuery("select @@named_pipe,@@socket");
} catch (SQLException sqle) {
// on non Windows system, named_pipe doesn't exist.
}
if (rs != null) {
assertTrue(rs.next());
System.out.println("named_pipe:" + rs.getString(1));
Assumptions.assumeTrue(rs.getBoolean(1));
String namedPipeName = rs.getString(2);
System.out.println("namedPipeName:" + namedPipeName);
// skip test if no namedPipeName was obtained because then we do not use a socket connection
Assumptions.assumeTrue(namedPipeName != null);
try (Connection connection = createCon("pipe=" + namedPipeName)) {
java.sql.Statement stmt = connection.createStatement();
try (ResultSet rs2 = stmt.executeQuery("SELECT 1")) {
assertTrue(rs2.next());
}
}
// connection without host name
try (java.sql.Connection connection =
DriverManager.getConnection(
String.format(
"jdbc:mariadb:///%s?%s&pipe=%s&tcpAbortiveClose&tcpKeepAlive",
sharedConn.getCatalog(),
mDefUrl.substring(mDefUrl.indexOf("?user=") + 1),
namedPipeName))) {
connection.setNetworkTimeout(null, 300);
java.sql.Statement stmt = connection.createStatement();
try (ResultSet rs2 = stmt.executeQuery("SELECT 1")) {
assertTrue(rs2.next());
}
}
}
}
@Test
public void localSocket() throws Exception {
Assumptions.assumeTrue(
System.getenv("local") != null
&& "1".equals(System.getenv("local"))
&& !System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"));
Statement stmt = sharedConn.createStatement();
ResultSet rs = stmt.executeQuery("select @@version_compile_os,@@socket");
if (!rs.next() || rs.getString(2) == null) {
return;
}
String path = rs.getString(2);
try {
stmt.execute("DROP USER testSocket");
} catch (SQLException e) {
// eat
}
boolean useOldNotation =
(!isMariaDBServer() || !minVersion(10, 2, 0))
&& (isMariaDBServer() || !minVersion(8, 0, 0));
if (useOldNotation) {
stmt.execute("CREATE USER testSocket IDENTIFIED BY 'heyPassw!µ20§rd'");
stmt.execute("GRANT SELECT on *.* to testSocket IDENTIFIED BY 'heyPassw!µ20§rd'");
} else {
stmt.execute("CREATE USER testSocket IDENTIFIED BY 'heyPassw!µ20§rd'");
stmt.execute("GRANT SELECT on *.* to testSocket");
}
// mysql 8.0.31 broken public key retrieval, so avoid FLUSHING for now
Assumptions.assumeTrue(!isMariaDBServer() && !exactVersion(8, 0, 31));
stmt.execute("FLUSH PRIVILEGES");
String url =
String.format(
"jdbc:mariadb:///%s?user=testSocket&password=heyPassw!µ20§rd&localSocket=%s&tcpAbortiveClose&tcpKeepAlive",
sharedConn.getCatalog(), path);
try (java.sql.Connection connection = DriverManager.getConnection(url)) {
connection.setNetworkTimeout(null, 300);
rs = connection.createStatement().executeQuery("select 1");
assertTrue(rs.next());
}
Common.assertThrowsContains(
SQLException.class,
() ->
DriverManager.getConnection(
"jdbc:mariadb:///"
+ sharedConn.getCatalog()
+ "?user=testSocket&password=heyPassw!µ20§rd&localSocket=/wrongPath"),
"Socket fail to connect to host");
if (haveSsl()) {
String serverCertPath = SslTest.retrieveCertificatePath();
if (serverCertPath != null) {
try (Connection con =
DriverManager.getConnection(
"jdbc:mariadb:///"
+ sharedConn.getCatalog()
+ "?sslMode=verify-full&user=testSocket&password=heyPassw!µ20§rd"
+ "&serverSslCert="
+ serverCertPath
+ "&localSocket="
+ path)) {
rs = con.createStatement().executeQuery("select 1");
assertTrue(rs.next());
}
}
}
stmt.execute("DROP USER testSocket");
}
@Test
public void socketFactoryTest() throws SQLException {
try (Connection conn = createCon("socketFactory=" + SocketFactoryBasicTest.class.getName())) {
conn.isValid(1);
}
try (Connection conn = createCon("socketFactory=" + SocketFactoryTest.class.getName())) {
conn.isValid(1);
}
Common.assertThrowsContains(
SQLNonTransientConnectionException.class,
() -> createCon("socketFactory=wrongClass"),
"Socket factory failed to initialized with option \"socketFactory\" set to \"wrongClass\"");
}
@Test
public void socketOption() throws SQLException {
try (Connection con = createCon("tcpKeepAlive=true&tcpAbortiveClose=true")) {
con.isValid(1);
}
}
@Test
public void sslNotSet() throws SQLException {
Assumptions.assumeTrue(
!"skysql".equals(System.getenv("srv")) && !"skysql-ha".equals(System.getenv("srv")));
Assumptions.assumeFalse(haveSsl());
Common.assertThrowsContains(
SQLException.class, () -> createCon("sslMode=trust"), "ssl not enabled in the server");
}
@Test
public void localSocketAddress() throws SQLException {
Assumptions.assumeTrue(
System.getenv("local") != null
&& "1".equals(System.getenv("local"))
&& !System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win"));
Assumptions.assumeTrue(
!"skysql".equals(System.getenv("srv")) && !"skysql-ha".equals(System.getenv("srv")));
Configuration conf = Configuration.parse(mDefUrl);
HostAddress hostAddress = conf.addresses().get(0);
try (Connection con = createCon("localSocketAddress=" + hostAddress.host)) {
con.isValid(1);
}
}
@Test
public void setReadOnly() throws SQLException {
Connection con = createCon();
con.setReadOnly(true);
con.close();
Common.assertThrowsContains(
SQLNonTransientConnectionException.class,
() -> con.setReadOnly(false),
"Connection is closed");
}
@Test
public void timezone() throws SQLException {
try (Connection con = createCon("timezone=GMT-8")) {
Statement statement = con.createStatement();
ResultSet rs = statement.executeQuery("SELECT @@time_zone");
rs.next();
assertEquals("-08:00", rs.getString(1));
}
try (Connection con = createCon("timezone=UTC")) {
Statement statement = con.createStatement();
ResultSet rs = statement.executeQuery("SELECT @@time_zone, @@system_time_zone");
rs.next();
String srvTz = rs.getString(1);
if ("SYSTEM".equals(rs.getString(1))) {
srvTz = rs.getString(2);
}
assertTrue("+00:00".equals(srvTz) || "UTC".equals(srvTz));
}
try (Connection con = createCon("timezone=disable")) {
con.isValid(1);
}
}
@Test
public void createDatabaseIfNotExist() throws SQLException {
Assumptions.assumeTrue(
!"skysql".equals(System.getenv("srv")) && !"skysql-ha".equals(System.getenv("srv")));
// ensure connecting without DB
String connStr =
String.format(
"jdbc:mariadb://%s:%s/?user=%s&password=%s&%s&createDatabaseIfNotExist",
hostname, port, user, password, defaultOther);
try (Connection con = DriverManager.getConnection(connStr)) {
con.createStatement().executeQuery("SELECT 1");
}
sharedConn.createStatement().execute("DROP DATABASE IF EXISTS `bla``f``l`");
String nonExistentDatabase = "bla`f`l";
connStr =
String.format(
"jdbc:mariadb://%s:%s/%s?user=%s&password=%s&%s&createDatabaseIfNotExist",
hostname, port, nonExistentDatabase, user, password, defaultOther);
try (Connection con = DriverManager.getConnection(connStr)) {
ResultSet rs = con.createStatement().executeQuery("select DATABASE()");
assertTrue(rs.next());
assertEquals(nonExistentDatabase, rs.getString(1));
}
nonExistentDatabase = "bla`f`l0";
connStr =
String.format(
"jdbc:mariadb:replication://%s:%s,%s:%s/%s?user=%s&password=%s&%s&createDatabaseIfNotExist",
hostname, port, hostname, port, nonExistentDatabase, user, password, defaultOther);
try (Connection con = DriverManager.getConnection(connStr)) {
ResultSet rs = con.createStatement().executeQuery("select DATABASE()");
assertTrue(rs.next());
assertEquals(nonExistentDatabase, rs.getString(1));
}
sharedConn.createStatement().execute("DROP DATABASE IF EXISTS `bla``f``l`");
sharedConn.createStatement().execute("DROP DATABASE IF EXISTS `bla``f``l0`");
}
@Test
public void loopHost() throws SQLException {
Assumptions.assumeTrue(
!"skysql".equals(System.getenv("srv")) && !"skysql-ha".equals(System.getenv("srv")));
// ensure connecting without DB
String connStr =
String.format(
"jdbc:mariadb://wronghost,%s:%s/%s?user=%s&password=%s&%s",
hostname, port, database, user, password, defaultOther);
try (Connection con = DriverManager.getConnection(connStr)) {
con.createStatement().executeQuery("SELECT 1");
}
}
}