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..0ce4ff898 100644
--- a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java
+++ b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java
@@ -86,6 +86,25 @@ 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) {
+ ClickHouseServerForTest.waitForAllReplicas(table, condition, expectedRows);
+ }
+
+ /**
+ * Same check as {@link #waitForAllReplicas(String, String, int)}. {@code query} runs the
+ * replica SQL with the caller's client and returns one matching-row count per replica host.
+ */
+ protected static void waitForAllReplicas(String table, String condition, int expectedRows,
+ ClickHouseServerForTest.ReplicaQuery query) {
+ ClickHouseServerForTest.waitForAllReplicas(table, condition, expectedRows, query);
+ }
+
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..7567c12f2 100644
--- a/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java
+++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClickHouseServerForTest.java
@@ -4,19 +4,24 @@
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
+import org.testng.Assert;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import com.clickhouse.client.ClickHouseTestEnvironment.ContainerMode;
+import com.clickhouse.data.ClickHouseRecord;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
-import java.util.Locale;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -33,6 +38,8 @@
@SuppressWarnings("squid:S2187")
public class ClickHouseServerForTest {
private static final Logger LOGGER = LoggerFactory.getLogger(ClickHouseServerForTest.class);
+ private static final int REPLICA_CHECK_ATTEMPTS = 100;
+ private static final long REPLICA_CHECK_INTERVAL_MS = 100L;
private static final Network network;
private static final ClickHouseTestEnvironment environment;
@@ -279,6 +286,89 @@ public static boolean runQuery(String sql) {
return false;
}
+ /**
+ * Waits until each replica of the test cluster returns {@code expectedRows} rows of
+ * {@code system.} that match {@code condition}. Does nothing outside cluster mode.
+ * Uses {@link ClickHouseClient}, which resolves to the HTTP client when that module is on the classpath.
+ */
+ public static void waitForAllReplicas(String table, String condition, int expectedRows) {
+ if (!isCluster()) {
+ return;
+ }
+ ClickHouseNode server = getClickHouseNode(ClickHouseProtocol.HTTP, isCloud(), ClickHouseNode.builder().build());
+ try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP)) {
+ waitForAllReplicas(table, condition, expectedRows, sql -> matchingRowCounts(client, server, sql));
+ }
+ }
+
+ /**
+ * Same check as {@link #waitForAllReplicas(String, String, int)}. {@code query} runs the
+ * replica SQL and returns one matching-row count per replica host.
+ */
+ public static void waitForAllReplicas(String table, String condition, int expectedRows, ReplicaQuery query) {
+ if (!isCluster()) {
+ return;
+ }
+ String sql = "SELECT hostName() AS host, toInt64(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 {
+ long[] counts = query.matchingRowCounts(sql);
+ state = Arrays.toString(counts);
+ if (hasRowsOnAllReplicas(counts, expectedRows)) {
+ return;
+ }
+ } catch (Exception 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 " + state);
+ }
+
+ private static long[] matchingRowCounts(ClickHouseClient client, ClickHouseNode server, String sql)
+ throws ClickHouseException {
+ try (ClickHouseResponse response = client.read(server).query(sql).executeAndWait()) {
+ List counts = new ArrayList<>();
+ for (ClickHouseRecord record : response.records()) {
+ counts.add(record.getValue(1).asLong());
+ }
+ long[] result = new long[counts.size()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = counts.get(i);
+ }
+ return result;
+ }
+ }
+
+ private static boolean hasRowsOnAllReplicas(long[] counts, int expectedRows) {
+ if (counts.length != ClusterClickHouseContainerStarter.NODE_COUNT) {
+ return false;
+ }
+ for (long count : counts) {
+ if (count != expectedRows) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Runs the replica-check SQL from {@link #waitForAllReplicas(String, String, int, ReplicaQuery)}.
+ */
+ @FunctionalInterface
+ public interface ReplicaQuery {
+ long[] matchingRowCounts(String sql) throws Exception;
+ }
+
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..97f3603c6 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,46 @@ 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,
+ sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
+ waitForAllReplicas("users", "name = '" + user + "'", 1,
+ sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
+ client.execute("GRANT " + rolesList + " TO " + user).get().close();
+ waitForAllReplicas("role_grants",
+ "user_name = '" + user + "' AND granted_role_name IN (" + roleNames + ")", roles.length,
+ sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
}
@DataProvider(name = "clientSessionRoles")
@@ -466,27 +484,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 +527,16 @@ 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,
+ sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
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..ffb5730fb 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,32 @@ 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,
+ sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
- 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 +1080,47 @@ 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,
+ sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
- 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 +1148,34 @@ 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,
+ sql -> adminClient.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
- 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/JdbcIntegrationTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java
index 0361f28d2..7b66a95fb 100644
--- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java
+++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcIntegrationTest.java
@@ -1,9 +1,12 @@
package com.clickhouse.jdbc;
import com.clickhouse.client.BaseIntegrationTest;
+import com.clickhouse.client.ClickHouseNode;
import com.clickhouse.client.ClickHouseProtocol;
import com.clickhouse.client.ClickHouseServerForTest;
+import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.ClientConfigProperties;
+import com.clickhouse.client.api.enums.Protocol;
import com.clickhouse.client.api.internal.ServerSettings;
import com.clickhouse.data.ClickHouseVersion;
import com.clickhouse.logging.Logger;
@@ -109,4 +112,25 @@ protected String getServerVersion() {
protected boolean isVersionMatch(String versionExpression) {
return ClickHouseVersion.of(getServerVersion()).check(versionExpression);
}
+
+ /**
+ * Uses a client-v2 {@link Client} for the replica check. The v1 {@link com.clickhouse.client.ClickHouseClient}
+ * implementation is not on this module's classpath.
+ */
+ protected static void waitForAllReplicas(String table, String condition, int expectedRows) {
+ if (!isCluster()) {
+ return;
+ }
+ ClickHouseNode node = ClickHouseServerForTest.getClickHouseNode(ClickHouseProtocol.HTTP,
+ ClickHouseServerForTest.isCloud(), ClickHouseNode.builder().build());
+ try (Client client = new Client.Builder()
+ .addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), ClickHouseServerForTest.isCloud())
+ .setUsername(ClickHouseServerForTest.getUsername())
+ .setPassword(ClickHouseServerForTest.getPassword())
+ .setDefaultDatabase(ClickHouseServerForTest.getDatabase())
+ .build()) {
+ BaseIntegrationTest.waitForAllReplicas(table, condition, expectedRows,
+ sql -> client.queryAll(sql).stream().mapToLong(row -> row.getLong("matching_rows")).toArray());
+ }
+ }
}
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