diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java index 046ab46ac..c1a0d72a0 100644 --- a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java +++ b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java @@ -1,9 +1,11 @@ package com.clickhouse.client; import com.clickhouse.client.config.ClickHouseClientOption; +import org.testng.Assert; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; +import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Locale; @@ -13,6 +15,9 @@ * Base class for all integration tests. */ public abstract class BaseIntegrationTest { + private static final int REPLICA_CHECK_ATTEMPTS = 100; + private static final long REPLICA_CHECK_INTERVAL_MS = 100L; + @BeforeTest(groups = {"integration"}) public static void setupClickHouseContainer() { ClickHouseServerForTest.beforeSuite(); @@ -86,6 +91,53 @@ protected static String onCluster() { return ClickHouseServerForTest.isCluster() ? " ON CLUSTER 'test_cluster'" : ""; } + /** + * Waits until each replica of the test cluster returns {@code expectedRows} rows of + * {@code system.} that match {@code condition}, so that the next request sees a + * created or changed user, role, settings profile or grant on any replica. + * Does nothing when the tests do not run on a cluster. + */ + protected static void waitForAllReplicas(String table, String condition, int expectedRows) { + if (!isCluster()) { + return; + } + String sql = "SELECT hostName() AS host, countIf(" + condition + ") AS matching_rows FROM clusterAllReplicas('" + + ClusterClickHouseContainerStarter.CLUSTER_NAME + "', system." + table + ") GROUP BY host"; + String state = ""; + for (int attempt = 1; attempt <= REPLICA_CHECK_ATTEMPTS; attempt++) { + try { + state = ClickHouseServerForTest.queryForResult(sql).trim(); + if (hasRowsOnAllReplicas(state, expectedRows)) { + return; + } + } catch (IOException e) { + state = e.getMessage(); + } + try { + Thread.sleep(REPLICA_CHECK_INTERVAL_MS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + Assert.fail("Expected " + expectedRows + " rows matching [" + condition + "] in system." + table + " on all " + + ClusterClickHouseContainerStarter.NODE_COUNT + " replicas, but got [host, rows]:\n" + state); + } + + private static boolean hasRowsOnAllReplicas(String result, int expectedRows) { + String[] lines = result.split("\n"); + if (lines.length != ClusterClickHouseContainerStarter.NODE_COUNT) { + return false; + } + for (String line : lines) { + String[] columns = line.split("\t"); + if (columns.length != 2 || !columns[1].equals(String.valueOf(expectedRows))) { + return false; + } + } + return true; + } + protected boolean createDatabase(String dbName) { return ClickHouseServerForTest.runQuery("CREATE DATABASE IF NOT EXISTS `" + dbName + "`" + onCluster()); } diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java index bd8bd0871..4e452d087 100644 --- a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java +++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java @@ -9,6 +9,9 @@ import com.clickhouse.client.ClickHouseTestEnvironment.ContainerMode; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; @@ -279,6 +282,48 @@ public static boolean runQuery(String sql) { return false; } + /** + * Runs a query once and returns the response body (TabSeparated). + * + * @throws IOException when the request fails or the server returns an error + */ + public static String queryForResult(String sql) throws IOException { + ClickHouseNode server = getClickHouseNode(ClickHouseProtocol.HTTP, isCloud(), ClickHouseNode.builder().build()); + byte[] postData = sql.getBytes(StandardCharsets.UTF_8); + String authorization = Base64.getEncoder() + .encodeToString((getUsername() + ":" + getPassword()).getBytes(StandardCharsets.UTF_8)); + HttpURLConnection httpConn = (HttpURLConnection) new URL(server.getBaseUri()).openConnection(); + try { + httpConn.setConnectTimeout(10000); + httpConn.setReadTimeout(10000); + httpConn.setRequestMethod("POST"); + httpConn.setDoOutput(true); + httpConn.setRequestProperty("Authorization", "Basic " + authorization); + httpConn.setFixedLengthStreamingMode(postData.length); + try (OutputStream out = httpConn.getOutputStream()) { + out.write(postData, 0, postData.length); + } + + boolean ok = httpConn.getResponseCode() == HttpURLConnection.HTTP_OK; + ByteArrayOutputStream body = new ByteArrayOutputStream(); + try (InputStream in = ok ? httpConn.getInputStream() : httpConn.getErrorStream()) { + if (in != null) { + byte[] buffer = new byte[4096]; + for (int read; (read = in.read(buffer)) != -1; ) { + body.write(buffer, 0, read); + } + } + } + String result = new String(body.toByteArray(), StandardCharsets.UTF_8); + if (!ok) { + throw new IOException("HTTP " + httpConn.getResponseCode() + ": " + result); + } + return result; + } finally { + httpConn.disconnect(); + } + } + private static Endpoint resolve(ClickHouseProtocol protocol, int port, boolean applyOverride) { if (starter != null) { int containerPort = (port == ClickHouseProtocol.HTTP.getDefaultPort()) diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java index 2d8f8ad85..e0c7c5d0c 100644 --- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java @@ -434,28 +434,43 @@ private static Object[][] sessionRoles() { } @Test(groups = {"integration"}, dataProvider = "sessionRoles") - public void testOperationCustomRoles(String[] roles) throws Exception { + public void testOperationCustomRoles(String[] roleNames) throws Exception { if (isVersionMatch("(,24.3]", newClient().build())) { return; } + String suffix = "_" + RandomStringUtils.insecure().next(8, true, true); + String user = "some_user" + suffix; + String[] roles = Arrays.stream(roleNames).map(role -> role + suffix).toArray(String[]::new); String password = "^1A" + RandomStringUtils.random(12, true, true) + "3b$"; final String rolesList = "\"" + Strings.join("\",\"", roles) + "\""; - try (Client client = newClient().build()) { - client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); - client.execute("CREATE ROLE " + rolesList).get().close(); - client.execute("DROP USER IF EXISTS some_user").get().close(); - client.execute("CREATE USER some_user IDENTIFIED BY '" + password + "'").get().close(); - client.execute("GRANT " + rolesList + " TO some_user").get().close(); + try (Client client = newClient().build(); + Client userClient = newClient().setUsername(user).setPassword(password).build()) { + try { + createUserWithRoles(client, user, "IDENTIFIED BY '" + password + "'", roles); + QuerySettings settings = new QuerySettings().setDBRoles(Arrays.asList(roles)); + List resp = userClient.queryAll("SELECT currentRoles()", settings); + Set roleSet = new HashSet<>(Arrays.asList(roles)); + Set currentRoles = new HashSet (resp.get(0).getList(1)); + Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); + } finally { + client.execute("DROP USER IF EXISTS " + user).get().close(); + client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); + } } + } - try (Client userClient = newClient().setUsername("some_user").setPassword(password).build()) { - QuerySettings settings = new QuerySettings().setDBRoles(Arrays.asList(roles)); - List resp = userClient.queryAll("SELECT currentRoles()", settings); - Set roleSet = new HashSet<>(Arrays.asList(roles)); - Set currentRoles = new HashSet (resp.get(0).getList(1)); - Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); - } + private static void createUserWithRoles(Client client, String user, String identification, String[] roles) + throws Exception { + final String rolesList = "\"" + Strings.join("\",\"", roles) + "\""; + final String roleNames = "'" + Strings.join("','", roles) + "'"; + client.execute("CREATE ROLE " + rolesList).get().close(); + client.execute("CREATE USER " + user + " " + identification).get().close(); + waitForAllReplicas("roles", "name IN (" + roleNames + ")", roles.length); + waitForAllReplicas("users", "name = '" + user + "'", 1); + client.execute("GRANT " + rolesList + " TO " + user).get().close(); + waitForAllReplicas("role_grants", "user_name = '" + user + "' AND granted_role_name IN (" + roleNames + ")", + roles.length); } @DataProvider(name = "clientSessionRoles") @@ -466,27 +481,29 @@ private static Object[][] clientSessionRoles() { }; } @Test(groups = {"integration"}, dataProvider = "clientSessionRoles") - public void testClientCustomRoles(String[] roles) throws Exception { + public void testClientCustomRoles(String[] roleNames) throws Exception { if (isVersionMatch("(,24.3]", newClient().build())) { return; } + String suffix = "_" + RandomStringUtils.insecure().next(8, true, true); + String user = "some_user" + suffix; + String[] roles = Arrays.stream(roleNames).map(role -> role + suffix).toArray(String[]::new); String password = "^1A" + RandomStringUtils.insecure().next(12, true, true) + "3B$"; final String rolesList = "\"" + Strings.join("\",\"", roles) + "\""; - try (Client client = newClient().build()) { - client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); - client.execute("CREATE ROLE " + rolesList).get().close(); - client.execute("DROP USER IF EXISTS some_user").get().close(); - client.execute("CREATE USER some_user IDENTIFIED WITH sha256_password BY '" + password + "'").get().close(); - client.execute("GRANT " + rolesList + " TO some_user").get().close(); - } - - try (Client userClient = newClient().setUsername("some_user").setPassword(password).build()) { - userClient.setDBRoles(Arrays.asList(roles)); - List resp = userClient.queryAll("SELECT currentRoles()"); - Set roleSet = new HashSet<>(Arrays.asList(roles)); - Set currentRoles = new HashSet (resp.get(0).getList(1)); - Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); + try (Client client = newClient().build(); + Client userClient = newClient().setUsername(user).setPassword(password).build()) { + try { + createUserWithRoles(client, user, "IDENTIFIED WITH sha256_password BY '" + password + "'", roles); + userClient.setDBRoles(Arrays.asList(roles)); + List resp = userClient.queryAll("SELECT currentRoles()"); + Set roleSet = new HashSet<>(Arrays.asList(roles)); + Set currentRoles = new HashSet (resp.get(0).getList(1)); + Assert.assertEquals(currentRoles, roleSet, "Roles " + roleSet + " not found in " + currentRoles); + } finally { + client.execute("DROP USER IF EXISTS " + user).get().close(); + client.execute("DROP ROLE IF EXISTS " + rolesList).get().close(); + } } } @@ -507,36 +524,15 @@ public void testRuntimeCredentialChange() throws Exception { adminClient.execute("DROP USER IF EXISTS " + user2).get().close(); adminClient.execute("CREATE USER " + user1 + " IDENTIFIED BY '" + password1 + "'").get().close(); adminClient.execute("CREATE USER " + user2 + " IDENTIFIED BY '" + password2 + "'").get().close(); + waitForAllReplicas("users", "name IN ('" + user1 + "', '" + user2 + "')", 2); try (Client userClient = newClient().setUsername(user1).setPassword(password1).build()) { - List firstResponse = null; - for (int i = 0; i < 20; i++) { - try { - firstResponse = userClient.queryAll("SELECT currentUser() AS user"); - break; - } catch (ClientException e) { - if (i == 19) { - throw e; - } - Thread.sleep(250); - } - } + List firstResponse = userClient.queryAll("SELECT currentUser() AS user"); Assert.assertEquals(firstResponse.get(0).getString("user"), user1); userClient.updateUserAndPassword(user2, password2); - List secondResponse = null; - for (int i = 0; i < 20; i++) { - try { - secondResponse = userClient.queryAll("SELECT currentUser() AS user"); - break; - } catch (ClientException e) { - if (i == 19) { - throw e; - } - Thread.sleep(250); - } - } + List secondResponse = userClient.queryAll("SELECT currentUser() AS user"); Assert.assertEquals(secondResponse.get(0).getString("user"), user2); } } finally { diff --git a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java index 533a43268..8a32fcd9b 100644 --- a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java @@ -1044,29 +1044,31 @@ public void testSSLAuthentication() throws Exception { return; // Current test is working only with local server because of self-signed certificates. } ClickHouseNode server = getSecureServer(ClickHouseProtocol.HTTP); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) + String user = "some_user_" + UUID.randomUUID().toString().replace("-", ""); + try (Client adminClient = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) .setUsername("dba") .setPassword("dba") .setRootCertificate("containers/clickhouse-server/certs/localhost.crt") .build()) { - try (CommandResponse resp = client.execute("DROP USER IF EXISTS some_user").get()) { - } - try (CommandResponse resp = client.execute("CREATE USER some_user IDENTIFIED WITH ssl_certificate CN 'some_user'").get()) { - } - } + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) + .useSSLAuthentication(true) + .setUsername(user) + .setRootCertificate("containers/clickhouse-server/certs/localhost.crt") + .setClientCertificate("some_user.crt") + .setClientKey("some_user.key") + .compressServerResponse(false) + .build()) { - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), true) - .useSSLAuthentication(true) - .setUsername("some_user") - .setRootCertificate("containers/clickhouse-server/certs/localhost.crt") - .setClientCertificate("some_user.crt") - .setClientKey("some_user.key") - .compressServerResponse(false) - .build()) { + try (CommandResponse resp = adminClient.execute("CREATE USER " + user + " IDENTIFIED WITH ssl_certificate CN 'some_user'").get()) { + } + waitForAllReplicas("users", "name = '" + user + "'", 1); - try (QueryResponse resp = client.query("SELECT 1").get()) { - Assert.assertEquals(resp.getReadRows(), 1); + try (QueryResponse resp = client.query("SELECT 1").get()) { + Assert.assertEquals(resp.getReadRows(), 1); + } + } finally { + adminClient.execute("DROP USER IF EXISTS " + user).get().close(); } } } @@ -1077,43 +1079,46 @@ public void testPasswordAuthentication(String identifyWith, String identifyBy, b return; // Current test is working only with local server because of self-signed certificates. } ClickHouseNode server = getServer(ClickHouseProtocol.HTTP); + String user = "some_user_" + UUID.randomUUID().toString().replace("-", ""); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + try (Client adminClient = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) .setUsername("dba") .setPassword("dba") .build()) { - try (CommandResponse resp = client.execute("DROP USER IF EXISTS some_user").get()) { - } - try (CommandResponse resp = client.execute("CREATE USER some_user IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { - } - } catch (Exception e) { - Assert.fail("Failed on setup", e); - } - + try { + try (CommandResponse resp = adminClient.execute("CREATE USER " + user + " IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { + } catch (Exception e) { + Assert.fail("Failed on setup", e); + } + waitForAllReplicas("users", "name = '" + user + "'", 1); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) - .setUsername("some_user") - .setPassword(identifyBy) - .build()) { + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + .setUsername(user) + .setPassword(identifyBy) + .build()) { - Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), "some_user"); - } catch (Exception e) { - Assert.fail("Failed to authenticate", e); - } + Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), user); + } catch (Exception e) { + Assert.fail("Failed to authenticate", e); + } - if (failsWithHeaders) { - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) - .setUsername("some_user") - .setPassword(identifyBy) - .useHTTPBasicAuth(false) - .build()) { + if (failsWithHeaders) { + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + .setUsername(user) + .setPassword(identifyBy) + .useHTTPBasicAuth(false) + .build()) { - Assert.expectThrows(ClientException.class, () -> - client.queryAll("SELECT user()").get(0).getString(1)); + Assert.expectThrows(ClientException.class, () -> + client.queryAll("SELECT user()").get(0).getString(1)); - } catch (Exception e) { - Assert.fail("Unexpected exception", e); + } catch (Exception e) { + Assert.fail("Unexpected exception", e); + } + } + } finally { + adminClient.execute("DROP USER IF EXISTS " + user).get().close(); } } } @@ -1141,30 +1146,33 @@ public void testAuthHeaderIsKeptFromUser() throws Exception { String identifyWith = "sha256_password"; String identifyBy = "123ยง"; - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + String user = "some_user_" + UUID.randomUUID().toString().replace("-", ""); + try (Client adminClient = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) .setUsername("dba") .setPassword("dba") .build()) { - try (CommandResponse resp = client.execute("DROP USER IF EXISTS some_user").get()) { - } - try (CommandResponse resp = client.execute("CREATE USER some_user IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { - } - } catch (Exception e) { - Assert.fail("Failed on setup", e); - } - + try { + try (CommandResponse resp = adminClient.execute("CREATE USER " + user + " IDENTIFIED WITH " + identifyWith + " BY '" + identifyBy + "'").get()) { + } catch (Exception e) { + Assert.fail("Failed on setup", e); + } + waitForAllReplicas("users", "name = '" + user + "'", 1); - try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) - .setUsername("some_user") - .setPassword(identifyBy) - .useHTTPBasicAuth(false) // disable basic auth to produce CH headers - .httpHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString(("some_user:" +identifyBy).getBytes())) - .build()) { + try (Client client = new Client.Builder().addEndpoint(Protocol.HTTP, "localhost",server.getPort(), false) + .setUsername(user) + .setPassword(identifyBy) + .useHTTPBasicAuth(false) // disable basic auth to produce CH headers + .httpHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((user + ":" +identifyBy).getBytes())) + .build()) { - Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), "some_user"); - } catch (Exception e) { - Assert.fail("Failed to authenticate", e); + Assert.assertEquals(client.queryAll("SELECT user()").get(0).getString(1), user); + } catch (Exception e) { + Assert.fail("Failed to authenticate", e); + } + } finally { + adminClient.execute("DROP USER IF EXISTS " + user).get().close(); + } } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java index 2c34ed447..0c3322555 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ReadonlyProfileTest.java @@ -18,6 +18,10 @@ public class ReadonlyProfileTest extends JdbcIntegrationTest { private String password; + private String user1; + private String user2; + private String profile1; + private String profile2; @BeforeClass(groups = { "integration" }) public void setup() throws SQLException { @@ -27,18 +31,25 @@ public void setup() throws SQLException { // Append fixed character classes so the random password satisfies server // password-complexity policies (uppercase, lowercase, digit, special). password = UUID.randomUUID().toString() + "Aa1!"; + String suffix = "_" + UUID.randomUUID().toString().replace("-", ""); + user1 = "jdbc_test_user_readonly_1" + suffix; + user2 = "jdbc_test_user_readonly_2" + suffix; + profile1 = "jdbc_test_profile_readonly_1" + suffix; + profile2 = "jdbc_test_profile_readonly_2" + suffix; com.clickhouse.client.ClickHouseServerForTest.beforeSuite(); try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { - stmt.execute("CREATE SETTINGS PROFILE IF NOT EXISTS jdbc_test_profile_readonly_1 SETTINGS readonly=1"); - stmt.execute("CREATE SETTINGS PROFILE IF NOT EXISTS jdbc_test_profile_readonly_2 SETTINGS readonly=2"); - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_1"); - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_2"); - stmt.execute("CREATE USER jdbc_test_user_readonly_1 IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE jdbc_test_profile_readonly_1"); - stmt.execute("CREATE USER jdbc_test_user_readonly_2 IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE jdbc_test_profile_readonly_2"); + stmt.execute("CREATE SETTINGS PROFILE " + profile1 + " SETTINGS readonly=1"); + stmt.execute("CREATE SETTINGS PROFILE " + profile2 + " SETTINGS readonly=2"); + waitForAllReplicas("settings_profiles", "name IN ('" + profile1 + "', '" + profile2 + "')", 2); + stmt.execute("CREATE USER " + user1 + " IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE " + profile1); + stmt.execute("CREATE USER " + user2 + " IDENTIFIED WITH plaintext_password BY '" + password + "' SETTINGS PROFILE " + profile2); + String users = "'" + user1 + "', '" + user2 + "'"; + waitForAllReplicas("users", "name IN (" + users + ")", 2); String db = getDatabase(); - stmt.execute("GRANT SELECT ON " + db + ".* TO jdbc_test_user_readonly_1"); - stmt.execute("GRANT SELECT ON " + db + ".* TO jdbc_test_user_readonly_2"); + stmt.execute("GRANT SELECT ON " + db + ".* TO " + user1); + stmt.execute("GRANT SELECT ON " + db + ".* TO " + user2); + waitForAllReplicas("grants", "user_name IN (" + users + ")", 2); } } @@ -49,10 +60,10 @@ public void teardown() throws SQLException { } try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) { - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_1"); - stmt.execute("DROP USER IF EXISTS jdbc_test_user_readonly_2"); - stmt.execute("DROP SETTINGS PROFILE IF EXISTS jdbc_test_profile_readonly_1"); - stmt.execute("DROP SETTINGS PROFILE IF EXISTS jdbc_test_profile_readonly_2"); + stmt.execute("DROP USER IF EXISTS " + user1); + stmt.execute("DROP USER IF EXISTS " + user2); + stmt.execute("DROP SETTINGS PROFILE IF EXISTS " + profile1); + stmt.execute("DROP SETTINGS PROFILE IF EXISTS " + profile2); } } @@ -62,7 +73,7 @@ public void testReadonly1CannotChangeSettings() throws Exception { throw new SkipException("Should not be tested in cloud because creates users and profiles"); } Properties properties = new Properties(); - properties.setProperty("user", "jdbc_test_user_readonly_1"); + properties.setProperty("user", user1); properties.setProperty("password", password); properties.put(ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), "1"); @@ -86,7 +97,7 @@ public void testReadonly2CanChangeSettings() throws Exception { } Properties properties = new Properties(); - properties.setProperty("user", "jdbc_test_user_readonly_2"); + properties.setProperty("user", user2); properties.setProperty("password", password); properties.put(ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING), "1"); properties.put(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1"); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java index 0657558df..d753f466a 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java @@ -556,118 +556,130 @@ public void testSettingRole(SqlParserFacade.SQLParser parser) throws SQLExceptio return; } - List roles = Arrays.asList("role1", "role2", "role3"); + String suffix = "_" + RandomStringUtils.insecure().next(8, true, true); + String user = "some_user" + suffix; + String role1 = "role1" + suffix; + String role2 = "role2" + suffix; + String role3 = "role3" + suffix; + List roles = Arrays.asList(role1, role2, role3); + String roleNames = "'" + String.join("','", roles) + "'"; final String userPass = "^1A" + RandomStringUtils.random(12, true, true) + "3B$"; Properties properties = new Properties(); properties.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); - try (ConnectionImpl conn = (ConnectionImpl) getJdbcConnection(properties)) { - try (Statement stmt = conn.createStatement()) { - stmt.execute("DROP ROLE IF EXISTS " + String.join(", ", roles)); - stmt.execute("DROP USER IF EXISTS some_user"); - stmt.execute("CREATE ROLE " + String.join(", ", roles)); - stmt.execute("CREATE USER some_user IDENTIFIED BY '" + userPass + "'"); - stmt.execute("GRANT " + String.join(", ", roles) + " TO some_user"); - stmt.execute("SET DEFAULT ROLE NONE TO some_user"); - } - } + try (ConnectionImpl adminConn = (ConnectionImpl) getJdbcConnection(properties); + Statement adminStmt = adminConn.createStatement()) { + try { + adminStmt.execute("CREATE ROLE " + String.join(", ", roles)); + adminStmt.execute("CREATE USER " + user + " IDENTIFIED BY '" + userPass + "'"); + waitForAllReplicas("roles", "name IN (" + roleNames + ")", roles.size()); + waitForAllReplicas("users", "name = '" + user + "'", 1); + adminStmt.execute("GRANT " + String.join(", ", roles) + " TO " + user); + adminStmt.execute("SET DEFAULT ROLE NONE TO " + user); + waitForAllReplicas("role_grants", "user_name = '" + user + "' AND granted_role_is_default = 0", + roles.size()); + + Properties info = new Properties(); + info.setProperty("user", user); + info.setProperty("password", userPass); + info.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); + try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), info)) { + GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); + + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role1); + } - Properties info = new Properties(); - info.setProperty("user", "some_user"); - info.setProperty("password", userPass); - info.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); - try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), info)) { - GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 1); + assertEquals(dataRecord.getList(1).get(0), role1); - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role1"); - } - - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 1); - assertEquals(dataRecord.getList(1).get(0), "role1"); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role2); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role2"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 1); + assertEquals(dataRecord.getList(1).get(0), role2); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 1); - assertEquals(dataRecord.getList(1).get(0), "role2"); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE NONE"); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE NONE"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\""); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 2); + assertEquals(dataRecord.getList(1).get(0), role1); + assertEquals(dataRecord.getList(1).get(1), role2); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 2); - assertEquals(dataRecord.getList(1).get(0), "role1"); - assertEquals(dataRecord.getList(1).get(1), "role2"); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\",\"" + role3 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\",\"role3\""); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 3); + assertEquals(dataRecord.getList(1).get(0), role1); + assertEquals(dataRecord.getList(1).get(1), role2); + assertEquals(dataRecord.getList(1).get(2), role3); + } - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 3); - assertEquals(dataRecord.getList(1).get(0), "role1"); - assertEquals(dataRecord.getList(1).get(1), "role2"); - assertEquals(dataRecord.getList(1).get(2), "role3"); - } + Properties disableSavingRoles = new Properties(); + disableSavingRoles.setProperty("user", user); + disableSavingRoles.setProperty("password", userPass); + disableSavingRoles.setProperty(DriverProperties.REMEMBER_LAST_SET_ROLES.getKey(), "false"); + disableSavingRoles.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); + try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), disableSavingRoles)) { + GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - Properties disableSavingRoles = new Properties(); - disableSavingRoles.setProperty("user", "some_user"); - disableSavingRoles.setProperty("password", userPass); - disableSavingRoles.setProperty(DriverProperties.REMEMBER_LAST_SET_ROLES.getKey(), "false"); - disableSavingRoles.setProperty(DriverProperties.SQL_PARSER.getKey(), parser.name()); - try (ConnectionImpl conn = new ConnectionImpl(getEndpointString(), disableSavingRoles)) { - GenericRecord dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role1); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role1"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE " + role2); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE role2"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE NONE"); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE NONE"); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\""); - } + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); + try (Statement stmt = conn.createStatement()) { + stmt.execute("SET ROLE \"" + role1 + "\",\"" + role2 + "\",\"" + role3 + "\""); + } - try (Statement stmt = conn.createStatement()) { - stmt.execute("SET ROLE \"role1\",\"role2\",\"role3\""); + dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); + assertEquals(dataRecord.getList(1).size(), 0); + } + } finally { + adminStmt.execute("DROP USER IF EXISTS " + user); + adminStmt.execute("DROP ROLE IF EXISTS " + String.join(", ", roles)); } - - dataRecord = conn.getClient().queryAll("SELECT currentRoles()").get(0); - assertEquals(dataRecord.getList(1).size(), 0); } } @@ -1359,18 +1371,24 @@ public void testDDLStatements() throws Exception { if (isCloud()) { return; // skip because we do not want to create extra on cloud instance } + String user = "user011_" + RandomStringUtils.insecure().next(8, true, true); try (Connection conn = getJdbcConnection()) { try (Statement stmt = conn.createStatement()) { - Assert.assertFalse(stmt.execute("CREATE USER IF NOT EXISTS 'user011' IDENTIFIED BY 'password'")); + try { + Assert.assertFalse(stmt.execute("CREATE USER IF NOT EXISTS '" + user + "' IDENTIFIED BY 'password'")); + waitForAllReplicas("users", "name = '" + user + "'", 1); - try (ResultSet rs = stmt.executeQuery("SHOW USERS")) { - boolean found = false; - while (rs.next()) { - if (rs.getString("name").equals("user011")) { - found = true; + try (ResultSet rs = stmt.executeQuery("SHOW USERS")) { + boolean found = false; + while (rs.next()) { + if (rs.getString("name").equals(user)) { + found = true; + } } + Assert.assertTrue(found); } - Assert.assertTrue(found); + } finally { + stmt.execute("DROP USER IF EXISTS '" + user + "'"); } } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java index 73674f3cb..8cc9a660f 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/metadata/ShowStatementDatabaseMetaDataTest.java @@ -211,22 +211,23 @@ public void testGetColumnsForReadonlyUserWithColumnGrants(String flag, List