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