diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0ef3e5796be5..6d5c404c80a7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -373,7 +373,8 @@ jobs:
!:trino-redis,
!:trino-redshift,
!:trino-resource-group-managers,
- !:trino-server-core,
+ !:trino-scylladb,
+ !:trino-server-core,
!:trino-server,
!:trino-singlestore,
!:trino-snowflake,
@@ -491,6 +492,7 @@ jobs:
- { modules: plugin/trino-redshift, profile: cloud-tests }
- { modules: plugin/trino-redshift, profile: fte-tests }
- { modules: plugin/trino-resource-group-managers }
+ - { modules: plugin/trino-scylladb }
- { modules: plugin/trino-singlestore }
- { modules: plugin/trino-snowflake }
- { modules: plugin/trino-snowflake, profile: cloud-tests }
@@ -913,6 +915,7 @@ jobs:
- suite-delta-lake-oss
- suite-kafka
- suite-cassandra
+ - suite-scylladb
- suite-clickhouse
- suite-mysql
- suite-iceberg
diff --git a/core/trino-server/src/main/provisio/trino.xml b/core/trino-server/src/main/provisio/trino.xml
index 7d2afbd4a6ed..01cf9263a63c 100644
--- a/core/trino-server/src/main/provisio/trino.xml
+++ b/core/trino-server/src/main/provisio/trino.xml
@@ -262,6 +262,12 @@
+
+
+
+
+
+
diff --git a/docs/release-template.md b/docs/release-template.md
index 85bb27954de3..65a9952c07ab 100644
--- a/docs/release-template.md
+++ b/docs/release-template.md
@@ -70,6 +70,8 @@
## Redshift connector
+## ScyllaDB connector
+
## SingleStore connector
## Snowflake connector
diff --git a/docs/src/main/sphinx/connector.md b/docs/src/main/sphinx/connector.md
index 2d9781a69616..30f79928e271 100644
--- a/docs/src/main/sphinx/connector.md
+++ b/docs/src/main/sphinx/connector.md
@@ -37,6 +37,7 @@ PostgreSQL
Prometheus
Redis
Redshift
+ScyllaDB
SingleStore
Snowflake
SQL Server
diff --git a/docs/src/main/sphinx/connector/scylladb.md b/docs/src/main/sphinx/connector/scylladb.md
new file mode 100644
index 000000000000..e2d99eb045bc
--- /dev/null
+++ b/docs/src/main/sphinx/connector/scylladb.md
@@ -0,0 +1,37 @@
+# ScyllaDB connector
+
+```{raw} html
+
+```
+
+The ScyllaDB connector allows querying data stored in
+[ScyllaDB](https://www.scylladb.com/).
+
+## Requirements
+
+To connect to ScyllaDB, you need:
+
+- ScyllaDB version 6.2 or higher.
+- Network access from the Trino coordinator and workers to ScyllaDB.
+ Port 9042 is the default port.
+
+## Configuration
+
+To configure the ScyllaDB connector, create a catalog properties file
+`etc/catalog/example.properties` with the following contents,
+replacing `host1,host2` with a comma-separated list of the ScyllaDB
+nodes, used to discover the cluster topology:
+
+```text
+connector.name=scylladb
+cassandra.contact-points=host1,host2
+```
+
+You also need to set `cassandra.native-protocol-port`, if your
+ScyllaDB nodes are not using the default port 9042.
+
+## Compatibility with Cassandra connector
+
+The ScyllaDB connector is very similar to the Cassandra connector with the
+only difference being the underlying driver.
+See [Cassandra connector](cassandra) for more details.
diff --git a/docs/src/main/sphinx/static/img/scylladb.png b/docs/src/main/sphinx/static/img/scylladb.png
new file mode 100644
index 000000000000..1fb732317ecb
Binary files /dev/null and b/docs/src/main/sphinx/static/img/scylladb.png differ
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java
index e1cf30a22121..5319835f7a46 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java
@@ -114,7 +114,7 @@ public DistributedQueryRunner build()
public static void main(String[] args)
throws Exception
{
- QueryRunner queryRunner = builder(new CassandraServer())
+ QueryRunner queryRunner = builder(new TestingCassandraServer())
.addCoordinatorProperty("http-server.http.port", "8080")
.setInitialTables(TpchTable.getTables())
.build();
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java
index 4f58889cc87c..72faa07d0b39 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java
@@ -13,165 +13,17 @@
*/
package io.trino.plugin.cassandra;
-import com.datastax.oss.driver.api.core.CqlSession;
-import com.datastax.oss.driver.api.core.CqlSessionBuilder;
-import com.datastax.oss.driver.api.core.ProtocolVersion;
-import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
-import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.Resources;
-import io.airlift.json.JsonCodec;
-import io.airlift.log.Logger;
-import io.airlift.units.Duration;
-import org.testcontainers.cassandra.CassandraContainer;
-import org.testcontainers.containers.wait.CassandraQueryWaitStrategy;
-import org.testcontainers.utility.DockerImageName;
-
import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeoutException;
-
-import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT;
-import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES;
-import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.PROTOCOL_VERSION;
-import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.REQUEST_TIMEOUT;
-import static com.google.common.io.Resources.getResource;
-import static io.trino.plugin.cassandra.CassandraTestingUtils.CASSANDRA_TYPE_MANAGER;
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.nio.file.Files.createDirectory;
-import static java.nio.file.Files.createTempDirectory;
-import static java.nio.file.Files.writeString;
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.testcontainers.utility.MountableFile.forHostPath;
-public class CassandraServer
- implements Closeable
+public interface CassandraServer
+ extends Closeable
{
- private static final Logger log = Logger.get(CassandraServer.class);
- private static final Duration REFRESH_SIZE_ESTIMATES_TIMEOUT = new Duration(1, MINUTES);
-
- private final CassandraContainer dockerContainer;
- private final CassandraSession session;
-
- public CassandraServer()
- throws Exception
- {
- this("cassandra:3.0", "cu-cassandra.yaml");
- }
-
- public CassandraServer(String imageName, String configFileName)
- throws Exception
- {
- this(DockerImageName.parse(imageName), ImmutableMap.of(), "/etc/cassandra/cassandra.yaml", configFileName);
- }
-
- @SuppressWarnings("deprecation")
- public CassandraServer(DockerImageName imageName, Map environmentVariables, String configPath, String configFileName)
- throws Exception
- {
- log.debug("Starting cassandra...");
-
- this.dockerContainer = new CassandraContainer(imageName)
- .withCopyFileToContainer(forHostPath(prepareCassandraYaml(configFileName)), configPath)
- .withEnv(environmentVariables)
- .withStartupTimeout(java.time.Duration.ofMinutes(10))
- // TODO: https://github.com/testcontainers/testcontainers-java/issues/9337
- .waitingFor(new CassandraQueryWaitStrategy());
- this.dockerContainer.start();
-
- ProgrammaticDriverConfigLoaderBuilder driverConfigLoaderBuilder = DriverConfigLoader.programmaticBuilder();
- driverConfigLoaderBuilder.withDuration(REQUEST_TIMEOUT, java.time.Duration.ofSeconds(30));
- driverConfigLoaderBuilder.withString(PROTOCOL_VERSION, ProtocolVersion.V3.name());
- driverConfigLoaderBuilder.withDuration(CONTROL_CONNECTION_AGREEMENT_TIMEOUT, java.time.Duration.ofSeconds(30));
- // allow the retrieval of metadata for the system keyspaces
- driverConfigLoaderBuilder.withStringList(METADATA_SCHEMA_REFRESHED_KEYSPACES, List.of());
-
- CqlSessionBuilder cqlSessionBuilder = CqlSession.builder()
- .addContactPoint(dockerContainer.getContactPoint())
- .withLocalDatacenter(dockerContainer.getLocalDatacenter())
- .withConfigLoader(driverConfigLoaderBuilder.build());
-
- session = new CassandraSession(
- CASSANDRA_TYPE_MANAGER,
- JsonCodec.listJsonCodec(ExtraColumnMetadata.class),
- cqlSessionBuilder::build,
- new Duration(1, MINUTES));
- }
-
- private static String prepareCassandraYaml(String fileName)
- throws IOException
- {
- String original = Resources.toString(getResource(fileName), UTF_8);
-
- Path tmpDirPath = createTempDirectory(null);
- Path dataDir = tmpDirPath.resolve("data");
- createDirectory(dataDir);
-
- String modified = original.replaceAll("\\$\\{data_directory\\}", dataDir.toAbsolutePath().toString());
-
- File yamlFile = tmpDirPath.resolve(fileName).toFile();
- yamlFile.deleteOnExit();
- writeString(yamlFile.toPath(), modified, UTF_8);
-
- return yamlFile.getAbsolutePath();
- }
-
- public CassandraSession getSession()
- {
- return session;
- }
-
- public String getHost()
- {
- return dockerContainer.getHost();
- }
-
- public int getPort()
- {
- return dockerContainer.getContactPoint().getPort();
- }
-
- public void refreshSizeEstimates(String keyspace, String table)
- throws Exception
- {
- long deadline = System.nanoTime() + REFRESH_SIZE_ESTIMATES_TIMEOUT.roundTo(NANOSECONDS);
- while (System.nanoTime() - deadline < 0) {
- flushTable(keyspace, table);
- refreshSizeEstimates();
- List sizeEstimates = getSession().getSizeEstimates(keyspace, table);
- if (!sizeEstimates.isEmpty()) {
- log.debug("Size estimates for the table %s.%s have been refreshed successfully: %s", keyspace, table, sizeEstimates);
- return;
- }
- log.debug("Size estimates haven't been refreshed as expected. Retrying ...");
- SECONDS.sleep(1);
- }
- throw new TimeoutException(format("Attempting to refresh size estimates for table %s.%s has timed out after %s", keyspace, table, REFRESH_SIZE_ESTIMATES_TIMEOUT));
- }
+ CassandraSession getSession();
- private void flushTable(String keyspace, String table)
- throws Exception
- {
- dockerContainer.execInContainer("nodetool", "flush", keyspace, table);
- }
+ String getHost();
- private void refreshSizeEstimates()
- throws Exception
- {
- dockerContainer.execInContainer("nodetool", "refreshsizeestimates");
- }
+ int getPort();
- @Override
- public void close()
- {
- session.close();
- dockerContainer.close();
- }
+ void refreshSizeEstimates(String keyspace, String table)
+ throws Exception;
}
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java
index c8209e5617b6..0f794b391def 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java
@@ -54,6 +54,7 @@
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.parallel.Isolated;
+import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
@@ -113,7 +114,7 @@ public class TestCassandraConnector
public void setup()
throws Exception
{
- this.server = new CassandraServer();
+ this.server = new TestingCassandraServer();
String keyspace = "test_connector";
createTestTables(server.getSession(), keyspace, DATE);
@@ -146,6 +147,7 @@ public void setup()
@AfterAll
public void tearDown()
+ throws IOException
{
server.close();
}
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java
index f2a9ca5d3d80..e76dd5e3a465 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java
@@ -77,9 +77,9 @@
public class TestCassandraConnectorTest
extends BaseConnectorTest
{
- private static final ZonedDateTime TIMESTAMP_VALUE = ZonedDateTime.of(1970, 1, 1, 3, 4, 5, 0, ZoneId.of("UTC"));
+ protected static final ZonedDateTime TIMESTAMP_VALUE = ZonedDateTime.of(1970, 1, 1, 3, 4, 5, 0, ZoneId.of("UTC"));
- private CassandraSession session;
+ protected CassandraSession session;
@Override
protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)
@@ -111,7 +111,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)
protected QueryRunner createQueryRunner()
throws Exception
{
- CassandraServer server = closeAfterClass(new CassandraServer());
+ CassandraServer server = closeAfterClass(new TestingCassandraServer());
session = server.getSession();
return CassandraQueryRunner.builder(server)
.setInitialTables(REQUIRED_TPCH_TABLES)
@@ -1245,7 +1245,7 @@ void testPartitioningKeys()
}
@Test
- void testSelectClusteringMaterializedView()
+ protected void testSelectClusteringMaterializedView()
{
try (TestCassandraTable table = testTable(
"test_clustering_materialized_view_base",
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraLatestConnectorSmokeTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraLatestConnectorSmokeTest.java
index 0b7f690b819c..172e06d98c26 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraLatestConnectorSmokeTest.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraLatestConnectorSmokeTest.java
@@ -26,7 +26,7 @@ public class TestCassandraLatestConnectorSmokeTest
protected QueryRunner createQueryRunner()
throws Exception
{
- CassandraServer server = closeAfterClass(new CassandraServer("cassandra:5.0.2", "cu-cassandra-latest.yaml"));
+ CassandraServer server = closeAfterClass(new TestingCassandraServer("cassandra:5.0.2", "cu-cassandra-latest.yaml"));
CassandraSession session = server.getSession();
createTestTables(session, KEYSPACE, Timestamp.from(TIMESTAMP_VALUE.toInstant()));
return CassandraQueryRunner.builder(server)
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java
index 7ba3a1e68795..b80a53408ad2 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java
@@ -30,7 +30,7 @@ public class TestCassandraProtocolVersionV3ConnectorSmokeTest
protected QueryRunner createQueryRunner()
throws Exception
{
- CassandraServer server = closeAfterClass(new CassandraServer());
+ CassandraServer server = closeAfterClass(new TestingCassandraServer());
CassandraSession session = server.getSession();
createTestTables(session, KEYSPACE, Timestamp.from(TIMESTAMP_VALUE.toInstant()));
return CassandraQueryRunner.builder(server)
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplitManager.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplitManager.java
index 7378fd0eb251..5cdab7524d10 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplitManager.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplitManager.java
@@ -50,7 +50,7 @@ final class TestCassandraSplitManager
void setUp()
throws Exception
{
- server = new CassandraServer();
+ server = new TestingCassandraServer();
session = server.getSession();
createKeyspace(session, KEYSPACE);
}
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java
index ecbc83ad3952..63586a78c0a4 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java
@@ -37,7 +37,7 @@ public class TestCassandraTokenSplitManager
private static final String KEYSPACE = "test_cassandra_token_split_manager_keyspace";
private static final int PARTITION_COUNT = 1000;
- private CassandraServer server;
+ private TestingCassandraServer server;
private CassandraSession session;
private CassandraTokenSplitManager splitManager;
@@ -45,7 +45,7 @@ public class TestCassandraTokenSplitManager
public void setUp()
throws Exception
{
- server = new CassandraServer();
+ server = new TestingCassandraServer();
session = server.getSession();
createKeyspace(session, KEYSPACE);
splitManager = new CassandraTokenSplitManager(session, SPLIT_SIZE, Optional.empty());
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeMapping.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeMapping.java
index fd5cbbbd1558..dd39c897ebc1 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeMapping.java
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeMapping.java
@@ -138,7 +138,7 @@ private static void checkIsDoubled(ZoneId zone, LocalDateTime dateTime)
protected QueryRunner createQueryRunner()
throws Exception
{
- server = closeAfterClass(new CassandraServer());
+ server = closeAfterClass(new TestingCassandraServer());
session = server.getSession();
return CassandraQueryRunner.builder(server)
.addConnectorProperties(ImmutableMap.builder()
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestScyllaConnectorSmokeTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestScyllaConnectorSmokeTest.java
deleted file mode 100644
index 34672f15a468..000000000000
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestScyllaConnectorSmokeTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.trino.plugin.cassandra;
-
-import io.trino.testing.QueryRunner;
-
-import java.sql.Timestamp;
-
-import static io.trino.plugin.cassandra.CassandraTestingUtils.createTestTables;
-
-public class TestScyllaConnectorSmokeTest
- extends BaseCassandraConnectorSmokeTest
-{
- @Override
- protected QueryRunner createQueryRunner()
- throws Exception
- {
- TestingScyllaServer server = closeAfterClass(new TestingScyllaServer());
- CassandraSession session = server.getSession();
- createTestTables(session, KEYSPACE, Timestamp.from(TIMESTAMP_VALUE.toInstant()));
- return ScyllaQueryRunner.builder(server)
- .setInitialTables(REQUIRED_TPCH_TABLES)
- .build();
- }
-}
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestingCassandraServer.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestingCassandraServer.java
new file mode 100644
index 000000000000..d5f568672ed5
--- /dev/null
+++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestingCassandraServer.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.plugin.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.CqlSessionBuilder;
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
+import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Resources;
+import io.airlift.json.JsonCodec;
+import io.airlift.log.Logger;
+import io.airlift.units.Duration;
+import org.testcontainers.cassandra.CassandraContainer;
+import org.testcontainers.containers.wait.CassandraQueryWaitStrategy;
+import org.testcontainers.utility.DockerImageName;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT;
+import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.METADATA_SCHEMA_REFRESHED_KEYSPACES;
+import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.PROTOCOL_VERSION;
+import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.REQUEST_TIMEOUT;
+import static com.google.common.io.Resources.getResource;
+import static io.trino.plugin.cassandra.CassandraTestingUtils.CASSANDRA_TYPE_MANAGER;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.Files.createDirectory;
+import static java.nio.file.Files.createTempDirectory;
+import static java.nio.file.Files.writeString;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.testcontainers.utility.MountableFile.forHostPath;
+
+public class TestingCassandraServer
+ implements CassandraServer
+{
+ private static final Logger log = Logger.get(TestingCassandraServer.class);
+ private static final Duration REFRESH_SIZE_ESTIMATES_TIMEOUT = new Duration(1, MINUTES);
+
+ private final CassandraContainer dockerContainer;
+ private final CassandraSession session;
+
+ public TestingCassandraServer()
+ throws Exception
+ {
+ this("cassandra:3.0", "cu-cassandra.yaml");
+ }
+
+ public TestingCassandraServer(String imageName, String configFileName)
+ throws Exception
+ {
+ this(DockerImageName.parse(imageName), ImmutableMap.of(), "/etc/cassandra/cassandra.yaml", configFileName);
+ }
+
+ @SuppressWarnings("deprecation")
+ public TestingCassandraServer(DockerImageName imageName, Map environmentVariables, String configPath, String configFileName)
+ throws Exception
+ {
+ log.debug("Starting cassandra...");
+
+ this.dockerContainer = new CassandraContainer(imageName)
+ .withCopyFileToContainer(forHostPath(prepareCassandraYaml(configFileName)), configPath)
+ .withEnv(environmentVariables)
+ .withStartupTimeout(java.time.Duration.ofMinutes(10))
+ // TODO: https://github.com/testcontainers/testcontainers-java/issues/9337
+ .waitingFor(new CassandraQueryWaitStrategy());
+ this.dockerContainer.start();
+
+ ProgrammaticDriverConfigLoaderBuilder driverConfigLoaderBuilder = DriverConfigLoader.programmaticBuilder();
+ driverConfigLoaderBuilder.withDuration(REQUEST_TIMEOUT, java.time.Duration.ofSeconds(30));
+ driverConfigLoaderBuilder.withString(PROTOCOL_VERSION, ProtocolVersion.V3.name());
+ driverConfigLoaderBuilder.withDuration(CONTROL_CONNECTION_AGREEMENT_TIMEOUT, java.time.Duration.ofSeconds(30));
+ // allow the retrieval of metadata for the system keyspaces
+ driverConfigLoaderBuilder.withStringList(METADATA_SCHEMA_REFRESHED_KEYSPACES, List.of());
+
+ CqlSessionBuilder cqlSessionBuilder = CqlSession.builder()
+ .addContactPoint(dockerContainer.getContactPoint())
+ .withLocalDatacenter(dockerContainer.getLocalDatacenter())
+ .withConfigLoader(driverConfigLoaderBuilder.build());
+
+ session = new CassandraSession(
+ CASSANDRA_TYPE_MANAGER,
+ JsonCodec.listJsonCodec(ExtraColumnMetadata.class),
+ cqlSessionBuilder::build,
+ new Duration(1, MINUTES));
+ }
+
+ private static String prepareCassandraYaml(String fileName)
+ throws IOException
+ {
+ String original = Resources.toString(getResource(fileName), UTF_8);
+
+ Path tmpDirPath = createTempDirectory(null);
+ Path dataDir = tmpDirPath.resolve("data");
+ createDirectory(dataDir);
+
+ String modified = original.replaceAll("\\$\\{data_directory\\}", dataDir.toAbsolutePath().toString());
+
+ File yamlFile = tmpDirPath.resolve(fileName).toFile();
+ yamlFile.deleteOnExit();
+ writeString(yamlFile.toPath(), modified, UTF_8);
+
+ return yamlFile.getAbsolutePath();
+ }
+
+ @Override
+ public CassandraSession getSession()
+ {
+ return session;
+ }
+
+ @Override
+ public String getHost()
+ {
+ return dockerContainer.getHost();
+ }
+
+ @Override
+ public int getPort()
+ {
+ return dockerContainer.getContactPoint().getPort();
+ }
+
+ @Override
+ public void refreshSizeEstimates(String keyspace, String table)
+ throws Exception
+ {
+ long deadline = System.nanoTime() + REFRESH_SIZE_ESTIMATES_TIMEOUT.roundTo(NANOSECONDS);
+ while (System.nanoTime() - deadline < 0) {
+ flushTable(keyspace, table);
+ refreshSizeEstimates();
+ List sizeEstimates = getSession().getSizeEstimates(keyspace, table);
+ if (!sizeEstimates.isEmpty()) {
+ log.debug("Size estimates for the table %s.%s have been refreshed successfully: %s", keyspace, table, sizeEstimates);
+ return;
+ }
+ log.debug("Size estimates haven't been refreshed as expected. Retrying ...");
+ SECONDS.sleep(1);
+ }
+ throw new TimeoutException(format("Attempting to refresh size estimates for table %s.%s has timed out after %s", keyspace, table, REFRESH_SIZE_ESTIMATES_TIMEOUT));
+ }
+
+ private void flushTable(String keyspace, String table)
+ throws Exception
+ {
+ dockerContainer.execInContainer("nodetool", "flush", keyspace, table);
+ }
+
+ private void refreshSizeEstimates()
+ throws Exception
+ {
+ dockerContainer.execInContainer("nodetool", "refreshsizeestimates");
+ }
+
+ @Override
+ public void close()
+ {
+ session.close();
+ dockerContainer.close();
+ }
+}
diff --git a/plugin/trino-scylladb/pom.xml b/plugin/trino-scylladb/pom.xml
new file mode 100644
index 000000000000..5cd0a481b23d
--- /dev/null
+++ b/plugin/trino-scylladb/pom.xml
@@ -0,0 +1,254 @@
+
+
+ 4.0.0
+
+
+ io.trino
+ trino-root
+ 473-SNAPSHOT
+ ../../pom.xml
+
+
+ trino-scylladb
+ trino-plugin
+ Trino - ScyllaDB Connector
+
+
+ ${project.parent.basedir}
+
+
+
+
+
+ com.google.guava
+ guava
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+
+
+
+ io.trino
+ trino-cassandra
+ ${project.version}
+
+
+ com.datastax.oss
+ java-driver-core
+
+
+ io.trino
+ trino-plugin-toolkit
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ provided
+
+
+
+ io.airlift
+ slice
+ provided
+
+
+
+ io.opentelemetry
+ opentelemetry-api
+ provided
+
+
+
+ io.opentelemetry
+ opentelemetry-api-incubator
+ provided
+
+
+
+ io.opentelemetry
+ opentelemetry-context
+ provided
+
+
+
+ io.trino
+ trino-spi
+ provided
+
+
+
+ org.openjdk.jol
+ jol-core
+ provided
+
+
+
+ com.scylladb
+ java-driver-core
+ 4.13.0.0
+ runtime
+
+
+
+ io.airlift
+ json
+ runtime
+
+
+
+ io.airlift
+ log
+ runtime
+
+
+
+ io.airlift
+ log-manager
+ runtime
+
+
+
+ io.airlift
+ units
+ runtime
+
+
+
+ com.google.errorprone
+ error_prone_annotations
+ test
+ true
+
+
+
+ io.airlift
+ junit-extensions
+ test
+
+
+
+ io.airlift
+ testing
+ test
+
+
+
+
+ io.trino
+ trino-cassandra
+ ${project.version}
+ test-jar
+ test
+
+
+ io.trino
+ trino-plugin-toolkit
+
+
+
+
+
+ io.trino
+ trino-main
+ test-jar
+ test
+
+
+
+ io.trino
+ trino-main
+ test
+
+
+
+ io.trino
+ trino-plugin-toolkit
+ test
+
+
+ com.google.errorprone
+ error_prone_annotations
+
+
+
+
+
+ io.trino
+ trino-testing
+ test
+
+
+
+ io.trino
+ trino-tpch
+ test
+
+
+
+ io.trino.tpch
+ tpch
+ test
+
+
+
+ org.apache.thrift
+ libthrift
+ test
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.11.4
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.11.4
+ test
+
+
+
+ org.testcontainers
+ scylladb
+ test
+
+
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+
+
+
+
+ org.basepom.maven
+ duplicate-finder-maven-plugin
+
+
+
+ com.datastax.oss
+ java-driver-core
+
+
+
+
+
+
+
diff --git a/plugin/trino-scylladb/src/main/java/io/trino/plugin/scylladb/ScyllaDbConnectorFactory.java b/plugin/trino-scylladb/src/main/java/io/trino/plugin/scylladb/ScyllaDbConnectorFactory.java
new file mode 100644
index 000000000000..844ae9dc2512
--- /dev/null
+++ b/plugin/trino-scylladb/src/main/java/io/trino/plugin/scylladb/ScyllaDbConnectorFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.plugin.scylladb;
+
+import io.trino.plugin.cassandra.CassandraConnectorFactory;
+
+public class ScyllaDbConnectorFactory
+ extends CassandraConnectorFactory
+{
+ @Override
+ public String getName()
+ {
+ return "scylladb";
+ }
+}
diff --git a/plugin/trino-scylladb/src/main/java/io/trino/plugin/scylladb/ScyllaDbPlugin.java b/plugin/trino-scylladb/src/main/java/io/trino/plugin/scylladb/ScyllaDbPlugin.java
new file mode 100644
index 000000000000..59331a40f4aa
--- /dev/null
+++ b/plugin/trino-scylladb/src/main/java/io/trino/plugin/scylladb/ScyllaDbPlugin.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.plugin.scylladb;
+
+import com.google.common.collect.ImmutableList;
+import io.trino.spi.Plugin;
+import io.trino.spi.connector.ConnectorFactory;
+
+public class ScyllaDbPlugin
+ implements Plugin
+{
+ @Override
+ public Iterable getConnectorFactories()
+ {
+ return ImmutableList.of(new ScyllaDbConnectorFactory());
+ }
+}
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/ScyllaQueryRunner.java b/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/ScyllaDbQueryRunner.java
similarity index 82%
rename from plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/ScyllaQueryRunner.java
rename to plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/ScyllaDbQueryRunner.java
index 572563e55156..78e7f4c3f425 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/ScyllaQueryRunner.java
+++ b/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/ScyllaDbQueryRunner.java
@@ -11,13 +11,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.trino.plugin.cassandra;
+package io.trino.plugin.scylladb;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.airlift.log.Logger;
import io.airlift.log.Logging;
import io.trino.plugin.base.util.Closables;
+import io.trino.plugin.cassandra.CassandraServer;
import io.trino.plugin.tpch.TpchPlugin;
import io.trino.testing.DistributedQueryRunner;
import io.trino.testing.QueryRunner;
@@ -33,11 +34,11 @@
import static io.trino.testing.TestingSession.testSessionBuilder;
import static java.util.Objects.requireNonNull;
-public final class ScyllaQueryRunner
+public final class ScyllaDbQueryRunner
{
- private ScyllaQueryRunner() {}
+ private ScyllaDbQueryRunner() {}
- public static Builder builder(TestingScyllaServer server)
+ public static Builder builder(CassandraServer server)
{
return new Builder(server)
.addConnectorProperty("cassandra.contact-points", server.getHost())
@@ -50,11 +51,11 @@ public static Builder builder(TestingScyllaServer server)
public static class Builder
extends DistributedQueryRunner.Builder
{
- private final TestingScyllaServer server;
+ private final CassandraServer server;
private final Map connectorProperties = new HashMap<>();
private List> initialTables = ImmutableList.of();
- private Builder(TestingScyllaServer server)
+ private Builder(CassandraServer server)
{
super(testSessionBuilder()
.setCatalog("cassandra")
@@ -86,13 +87,14 @@ public DistributedQueryRunner build()
queryRunner.installPlugin(new TpchPlugin());
queryRunner.createCatalog("tpch", "tpch");
- queryRunner.installPlugin(new CassandraPlugin());
- queryRunner.createCatalog("cassandra", "cassandra", connectorProperties);
+ queryRunner.installPlugin(new ScyllaDbPlugin());
+ queryRunner.createCatalog("cassandra", "scylladb", connectorProperties);
- createKeyspace(server.getSession(), "tpch");
+ String schemaName = queryRunner.getDefaultSession().getSchema().orElseThrow();
+ createKeyspace(server.getSession(), schemaName);
copyTpchTables(queryRunner, "tpch", TINY_SCHEMA_NAME, initialTables);
for (TpchTable> table : initialTables) {
- server.refreshSizeEstimates("tpch", table.getTableName());
+ server.refreshSizeEstimates(schemaName, table.getTableName());
}
return queryRunner;
}
@@ -108,12 +110,12 @@ public static void main(String[] args)
{
Logging.initialize();
- QueryRunner queryRunner = builder(new TestingScyllaServer())
+ QueryRunner queryRunner = builder(new TestingScyllaDbServer())
.addCoordinatorProperty("http-server.http.port", "8080")
.setInitialTables(TpchTable.getTables())
.build();
- Logger log = Logger.get(ScyllaQueryRunner.class);
+ Logger log = Logger.get(ScyllaDbQueryRunner.class);
log.info("======== SERVER STARTED ========");
log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl());
}
diff --git a/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/TestScyllaDbConnectorTest.java b/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/TestScyllaDbConnectorTest.java
new file mode 100644
index 000000000000..e77bf8c2f4c3
--- /dev/null
+++ b/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/TestScyllaDbConnectorTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.plugin.scylladb;
+
+import io.trino.plugin.cassandra.CassandraServer;
+import io.trino.plugin.cassandra.TestCassandraConnectorTest;
+import io.trino.testing.QueryRunner;
+import io.trino.testing.TestingConnectorBehavior;
+import org.junit.jupiter.api.Test;
+
+public class TestScyllaDbConnectorTest
+ extends TestCassandraConnectorTest
+{
+ @Override
+ protected QueryRunner createQueryRunner()
+ throws Exception
+ {
+ CassandraServer server = closeAfterClass(new TestingScyllaDbServer());
+ session = server.getSession();
+ return ScyllaDbQueryRunner.builder(server).setInitialTables(REQUIRED_TPCH_TABLES).build();
+ }
+
+ @Test
+ @Override
+ public void testCreateTableWithLongColumnName()
+ {
+ // disable for now as maxColumnName is not applied and test failing: testCreateTableWithLongColumnName
+ }
+
+ @Test
+ @Override
+ protected void testSelectClusteringMaterializedView()
+ {
+ // disable for now
+ }
+
+ @Override
+ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior)
+ {
+ return switch (connectorBehavior) {
+ case SUPPORTS_ADD_COLUMN,
+ SUPPORTS_ARRAY,
+ SUPPORTS_COMMENT_ON_COLUMN,
+ SUPPORTS_COMMENT_ON_TABLE,
+ SUPPORTS_CREATE_SCHEMA,
+ SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT,
+ SUPPORTS_CREATE_TABLE_WITH_TABLE_COMMENT,
+ SUPPORTS_CREATE_VIEW,
+ SUPPORTS_MAP_TYPE,
+ SUPPORTS_MERGE,
+ SUPPORTS_NOT_NULL_CONSTRAINT,
+ SUPPORTS_RENAME_COLUMN,
+ SUPPORTS_RENAME_TABLE,
+ SUPPORTS_ROW_TYPE,
+ SUPPORTS_SET_COLUMN_TYPE,
+ SUPPORTS_TOPN_PUSHDOWN,
+ SUPPORTS_UPDATE -> false;
+ default -> super.hasBehavior(connectorBehavior);
+ };
+ }
+}
diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestingScyllaServer.java b/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/TestingScyllaDbServer.java
similarity index 85%
rename from plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestingScyllaServer.java
rename to plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/TestingScyllaDbServer.java
index ceda79a1b08b..e4a61c9135f3 100644
--- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestingScyllaServer.java
+++ b/plugin/trino-scylladb/src/test/java/io/trino/plugin/scylladb/TestingScyllaDbServer.java
@@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.trino.plugin.cassandra;
+package io.trino.plugin.scylladb;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
@@ -21,9 +21,12 @@
import io.airlift.json.JsonCodec;
import io.airlift.log.Logger;
import io.airlift.units.Duration;
-import org.testcontainers.containers.GenericContainer;
+import io.trino.plugin.cassandra.CassandraServer;
+import io.trino.plugin.cassandra.CassandraSession;
+import io.trino.plugin.cassandra.ExtraColumnMetadata;
+import io.trino.plugin.cassandra.SizeEstimate;
+import org.testcontainers.scylladb.ScyllaDBContainer;
-import java.io.Closeable;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -38,27 +41,28 @@
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
-public class TestingScyllaServer
- implements Closeable
+public class TestingScyllaDbServer
+ implements CassandraServer
{
- private static final Logger log = Logger.get(TestingScyllaServer.class);
+ private static final Logger log = Logger.get(TestingScyllaDbServer.class);
private static final int PORT = 9042;
+ private static final String VERSION = "6.2";
+
private static final Duration REFRESH_SIZE_ESTIMATES_TIMEOUT = new Duration(1, MINUTES);
- private final GenericContainer> container;
+ private final ScyllaDBContainer container;
private final CassandraSession session;
- public TestingScyllaServer()
+ public TestingScyllaDbServer()
{
- this("6.2");
+ this(VERSION);
}
- public TestingScyllaServer(String version)
+ public TestingScyllaDbServer(String version)
{
- container = new GenericContainer<>("scylladb/scylla:" + version)
- .withExposedPorts(PORT);
+ container = new ScyllaDBContainer("scylladb/scylla:" + version);
container.start();
ProgrammaticDriverConfigLoaderBuilder config = DriverConfigLoader.programmaticBuilder();
@@ -81,21 +85,25 @@ public TestingScyllaServer(String version)
new Duration(1, MINUTES));
}
+ @Override
public CassandraSession getSession()
{
return session;
}
+ @Override
public String getHost()
{
return container.getHost();
}
+ @Override
public int getPort()
{
return container.getMappedPort(PORT);
}
+ @Override
public void refreshSizeEstimates(String keyspace, String table)
throws Exception
{
diff --git a/pom.xml b/pom.xml
index 2a2cf090e9ea..f775fd994003 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,6 +106,7 @@
plugin/trino-redis
plugin/trino-redshift
plugin/trino-resource-group-managers
+ plugin/trino-scylladb
plugin/trino-session-property-managers
plugin/trino-singlestore
plugin/trino-snowflake
diff --git a/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java b/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java
index 27aa64aa60f1..a92088a17348 100644
--- a/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java
+++ b/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java
@@ -64,6 +64,7 @@ public final class TestGroups
public static final String AZURE = "azure";
public static final String EXASOL = "exasol";
public static final String CASSANDRA = "cassandra";
+ public static final String SCYLLADB = "scylladb";
public static final String POSTGRESQL = "postgresql";
public static final String SQLSERVER = "sqlserver";
public static final String LDAP = "ldap";
diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java
index ee3de89bd9f0..0f7d54cb4a31 100644
--- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java
+++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java
@@ -72,6 +72,7 @@ public void extendEnvironment(Environment.Builder builder)
"prometheus",
"redis",
"redshift",
+ "scylladb",
"singlestore",
"snowflake",
"sqlserver",
diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeScyllaDB.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeScyllaDB.java
new file mode 100644
index 000000000000..9e611061547c
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeScyllaDB.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.tests.product.launcher.env.environment;
+
+import com.google.inject.Inject;
+import io.trino.tests.product.launcher.docker.DockerFiles;
+import io.trino.tests.product.launcher.docker.DockerFiles.ResourceProvider;
+import io.trino.tests.product.launcher.env.DockerContainer;
+import io.trino.tests.product.launcher.env.Environment.Builder;
+import io.trino.tests.product.launcher.env.EnvironmentProvider;
+import io.trino.tests.product.launcher.env.common.StandardMultinode;
+import io.trino.tests.product.launcher.env.common.TestsEnvironment;
+import io.trino.tests.product.launcher.testcontainers.PortBinder;
+import org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy;
+
+import java.time.Duration;
+
+import static io.trino.tests.product.launcher.docker.ContainerUtil.forSelectedPorts;
+import static java.util.Objects.requireNonNull;
+import static org.testcontainers.utility.MountableFile.forHostPath;
+
+@TestsEnvironment
+public class EnvMultinodeScyllaDB
+ extends EnvironmentProvider
+{
+ public static final int SCYLLA_PORT = 9042;
+
+ private final ResourceProvider configDir;
+ private final PortBinder portBinder;
+
+ @Inject
+ public EnvMultinodeScyllaDB(StandardMultinode standardMultinode, DockerFiles dockerFiles, PortBinder portBinder)
+ {
+ super(standardMultinode);
+ this.configDir = requireNonNull(dockerFiles, "dockerFiles is null").getDockerFilesHostDirectory("conf/environment/multinode-scylladb/");
+ this.portBinder = requireNonNull(portBinder, "portBinder is null");
+ }
+
+ @Override
+ public void extendEnvironment(Builder builder)
+ {
+ builder.addConnector("scylladb", forHostPath(configDir.getPath("scylladb.properties")));
+ builder.addContainer(createScyllaDB());
+ }
+
+ private DockerContainer createScyllaDB()
+ {
+ DockerContainer container = new DockerContainer("scylladb/scylla:6.2", "scylladb")
+ // Limit SMP to run in a machine having many cores https://github.com/scylladb/scylla/issues/5638
+ .withCommand("--smp", "1")
+ .withStartupCheckStrategy(new IsRunningStartupCheckStrategy())
+ .waitingFor(forSelectedPorts(SCYLLA_PORT))
+ .withStartupTimeout(Duration.ofMinutes(5));
+
+ portBinder.exposePort(container, SCYLLA_PORT);
+
+ return container;
+ }
+}
diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteScylladb.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteScylladb.java
new file mode 100644
index 000000000000..4f7d76a98681
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteScylladb.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.tests.product.launcher.suite.suites;
+
+import com.google.common.collect.ImmutableList;
+import io.trino.tests.product.launcher.env.EnvironmentConfig;
+import io.trino.tests.product.launcher.env.environment.EnvMultinodeScyllaDB;
+import io.trino.tests.product.launcher.suite.Suite;
+import io.trino.tests.product.launcher.suite.SuiteTestRun;
+
+import java.util.List;
+
+import static io.trino.tests.product.TestGroups.CONFIGURED_FEATURES;
+import static io.trino.tests.product.TestGroups.SCYLLADB;
+import static io.trino.tests.product.launcher.suite.SuiteTestRun.testOnEnvironment;
+
+public class SuiteScylladb
+ extends Suite
+{
+ @Override
+ public List getTestRuns(EnvironmentConfig config)
+ {
+ return ImmutableList.of(
+ testOnEnvironment(EnvMultinodeScyllaDB.class)
+ .withGroups(CONFIGURED_FEATURES, SCYLLADB)
+ .build());
+ }
+}
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-all/scylladb.properties b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-all/scylladb.properties
new file mode 100644
index 000000000000..0f6ff580fdd6
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-all/scylladb.properties
@@ -0,0 +1,3 @@
+connector.name=scylladb
+cassandra.contact-points=host1.invalid,host2.invalid
+cassandra.load-policy.dc-aware.local-dc=datacenter1
diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-scylladb/scylladb.properties b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-scylladb/scylladb.properties
new file mode 100644
index 000000000000..f389423d8849
--- /dev/null
+++ b/testing/trino-product-tests-launcher/src/main/resources/docker/trino-product-tests/conf/environment/multinode-scylladb/scylladb.properties
@@ -0,0 +1,5 @@
+connector.name=scylladb
+scylladb.contact-points=scylladb
+scylladb.allow-drop-table=true
+scylladb.load-policy.use-dc-aware=true
+scylladb.load-policy.dc-aware.local-dc=datacenter1
diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/scylladb/TestScyllaDB.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/scylladb/TestScyllaDB.java
new file mode 100644
index 000000000000..5b33919c6b50
--- /dev/null
+++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/scylladb/TestScyllaDB.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.trino.tests.product.scylladb;
+
+import io.trino.tempto.ProductTest;
+import io.trino.tempto.query.QueryResult;
+import org.testng.annotations.Test;
+
+import static io.trino.tempto.assertions.QueryAssert.Row.row;
+import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS;
+import static io.trino.tests.product.TestGroups.SCYLLADB;
+import static io.trino.tests.product.utils.QueryExecutors.onTrino;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestScyllaDB
+ extends ProductTest
+{
+ @Test(groups = {SCYLLADB, PROFILE_SPECIFIC_TESTS})
+ public void testCreateTableAsSelect()
+ {
+ onTrino().executeQuery("CALL scylladb.system.execute('CREATE KEYSPACE test WITH REPLICATION = {''class'':''SimpleStrategy'', ''replication_factor'': 1}')");
+ QueryResult result = onTrino().executeQuery("CREATE TABLE scylladb.test.nation AS SELECT * FROM tpch.tiny.nation");
+ try {
+ assertThat(result).updatedRowsCountIsEqualTo(25);
+ assertThat(onTrino().executeQuery("SELECT COUNT(*) FROM scylladb.test.nation"))
+ .containsOnly(row(25));
+ }
+ finally {
+ onTrino().executeQuery("DROP TABLE scylladb.test.nation");
+ }
+ }
+}
diff --git a/testing/trino-product-tests/src/main/resources/tempto-configuration.yaml b/testing/trino-product-tests/src/main/resources/tempto-configuration.yaml
index 0d499429b320..d5f6dfc975c1 100644
--- a/testing/trino-product-tests/src/main/resources/tempto-configuration.yaml
+++ b/testing/trino-product-tests/src/main/resources/tempto-configuration.yaml
@@ -138,6 +138,17 @@ databases:
request:
timeout_seconds: 30
+ scylladb:
+ host: scylladb
+ port: 9042
+ local_datacenter: datacenter1
+ default_schema: test
+ skip_create_schema: false
+ table_manager_type: cassandra
+ basic:
+ request:
+ timeout_seconds: 30
+
sqlserver:
jdbc_driver_class: com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc_url: jdbc:sqlserver://sqlserver;encrypt=false
diff --git a/testing/trino-server-dev/etc/catalog/scylladb.properties b/testing/trino-server-dev/etc/catalog/scylladb.properties
new file mode 100644
index 000000000000..e1254034a07a
--- /dev/null
+++ b/testing/trino-server-dev/etc/catalog/scylladb.properties
@@ -0,0 +1,3 @@
+scylladb.contact-points=localhost
+scylladb.allow-drop-table=true
+scylladb.load-policy.dc-aware.local-dc=datacenter1