From 0ff1558cbc8a13e86b72e7497bb8b6b071609de7 Mon Sep 17 00:00:00 2001 From: Polyglot AI <293096396+polyglotAI-bot@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:22:13 +0000 Subject: [PATCH] [tests] Wait until users, roles and grants reach all cluster replicas On the 3-replica test cluster, users, roles, settings profiles and grants are kept in the replicated user directory. Each replica updates its own copy from Keeper asynchronously, and nginx sends each request to any replica. A login, GRANT or DROP ... IF EXISTS right after a change can go to a replica that does not have the change yet: AUTHENTICATION_FAILED, UNKNOWN_ROLE, or ACCESS_ENTITY_ALREADY_EXISTS. Add BaseIntegrationTest.waitForAllReplicas(). It checks system. on every replica through clusterAllReplicas('test_cluster', ...) with a limited number of attempts, and does nothing outside cluster mode. Call it after each access change that a later request depends on, and give each test its own user, role and profile names, dropped in finally. Part of: https://github.com/ClickHouse/clickhouse-java/issues/3267 Fixes: https://github.com/ClickHouse/clickhouse-java/issues/3237 Co-Authored-By: Claude Opus 5.5 --- .../client/BaseIntegrationTest.java | 52 +++++ .../client/ClickHouseServerForTest.java | 45 ++++ .../com/clickhouse/client/ClientTests.java | 102 +++++---- .../clickhouse/client/HttpTransportTests.java | 130 +++++------ .../clickhouse/jdbc/ReadonlyProfileTest.java | 39 ++-- .../com/clickhouse/jdbc/StatementTest.java | 202 ++++++++++-------- .../ShowStatementDatabaseMetaDataTest.java | 29 +-- 7 files changed, 365 insertions(+), 234 deletions(-) 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