diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/constants/Constants.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/constants/Constants.java index 2c212a5789..0213eb2cc2 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/constants/Constants.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/constants/Constants.java @@ -20,6 +20,7 @@ public class Constants { /* The source type value for MySql databases */ public static final String MYSQL_SOURCE_TYPE = "mysql"; + public static final String CASSANDRA_SOURCE_TYPE = "cassandra"; /* The value for Oracle databases in the source type key */ public static final String ORACLE_SOURCE_TYPE = "oracle"; diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/schema/Schema.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/schema/Schema.java index 72da70d584..b434177017 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/schema/Schema.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/schema/Schema.java @@ -30,7 +30,7 @@ */ public class Schema implements Serializable { /** Maps the HarbourBridge table ID to the Spanner table details. */ - private final Map spSchema; + private Map spSchema; /** Maps the Spanner table ID to the synthetic PK. */ private final Map syntheticPKeys; @@ -80,6 +80,10 @@ public Map getSpSchema() { return spSchema; } + public Map setSpSchema(Map spSchema) { + return this.spSchema = spSchema; + } + public Map getSyntheticPks() { return syntheticPKeys; } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java new file mode 100644 index 0000000000..dda47af064 --- /dev/null +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.spanner.migrations.shard; + +import java.util.Objects; + +public class CassandraShard extends Shard { + private String keyspace; + private String consistencyLevel = "LOCAL_QUORUM"; + private boolean sslOptions = false; + private String protocolVersion = "v5"; + private String dataCenter = "datacenter1"; + private int localPoolSize = 1024; + private int remotePoolSize = 256; + + public String getKeySpaceName() { + return keyspace; + } + + public String getConsistencyLevel() { + return consistencyLevel; + } + + public Boolean getSSLOptions() { + return sslOptions; + } + + public String getProtocolVersion() { + return protocolVersion; + } + + public String getDataCenter() { + return dataCenter; + } + + public Integer getLocalPoolSize() { + return localPoolSize; + } + + public Integer getRemotePoolSize() { + return remotePoolSize; + } + + public void setKeySpaceName(String keySpaceName) { + this.keyspace = keySpaceName; + } + + public void setConsistencyLevel(String consistencyLevel) { + this.consistencyLevel = consistencyLevel; + } + + public void setSslOptions(boolean sslOptions) { + this.sslOptions = sslOptions; + } + + public void setProtocolVersion(String protocolVersion) { + this.protocolVersion = protocolVersion; + } + + public void setDataCenter(String dataCenter) { + this.dataCenter = dataCenter; + } + + public void setLocalPoolSize(int localPoolSize) { + this.localPoolSize = localPoolSize; + } + + public void setRemotePoolSize(int remotePoolSize) { + this.remotePoolSize = remotePoolSize; + } + + public void validate() throws IllegalArgumentException { + if (getHost() == null || getHost().isEmpty()) { + throw new IllegalArgumentException("Host is required"); + } + if (getPort() == null || getPort().isEmpty()) { + throw new IllegalArgumentException("Port is required"); + } + if (getUserName() == null || getUserName().isEmpty()) { + throw new IllegalArgumentException("Username is required"); + } + if (getPassword() == null || getUserName().isEmpty()) { + throw new IllegalArgumentException("Password is required"); + } + if (keyspace == null || keyspace.isEmpty()) { + throw new IllegalArgumentException("Keyspace is required"); + } + } + + @Override + public String toString() { + return "CassandraShard{" + + "logicalShardId='" + + getLogicalShardId() + + '\'' + + ", host='" + + getHost() + + '\'' + + ", port='" + + getPort() + + '\'' + + ", user='" + + getUserName() + + '\'' + + ", keySpaceName='" + + keyspace + + '\'' + + ", datacenter='" + + dataCenter + + '\'' + + ", consistencyLevel='" + + consistencyLevel + + '\'' + + ", protocolVersion=" + + protocolVersion + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CassandraShard)) { + return false; + } + CassandraShard cassandraShard = (CassandraShard) o; + return Objects.equals(getLogicalShardId(), cassandraShard.getLogicalShardId()) + && Objects.equals(getHost(), cassandraShard.getHost()) + && Objects.equals(getPort(), cassandraShard.getPort()) + && Objects.equals(getUserName(), cassandraShard.getUserName()) + && Objects.equals(getPassword(), cassandraShard.getPassword()) + && Objects.equals(keyspace, cassandraShard.keyspace) + && Objects.equals(dataCenter, cassandraShard.dataCenter) + && Objects.equals(consistencyLevel, cassandraShard.consistencyLevel) + && Objects.equals(protocolVersion, cassandraShard.protocolVersion) + && Objects.equals(sslOptions, cassandraShard.sslOptions) + && Objects.equals(localPoolSize, cassandraShard.localPoolSize) + && Objects.equals(remotePoolSize, cassandraShard.remotePoolSize); + } + + @Override + public int hashCode() { + return Objects.hash( + getLogicalShardId(), + getHost(), + getPort(), + getUserName(), + getPassword(), + keyspace, + dataCenter, + consistencyLevel, + protocolVersion, + sslOptions, + localPoolSize, + remotePoolSize); + } +} diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/spanner/SpannerSchema.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/spanner/SpannerSchema.java index 0b2357b34f..3e15b14a37 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/spanner/SpannerSchema.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/spanner/SpannerSchema.java @@ -20,8 +20,19 @@ import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.TimestampBound; +import com.google.cloud.teleport.v2.spanner.ddl.Column; import com.google.cloud.teleport.v2.spanner.ddl.Ddl; import com.google.cloud.teleport.v2.spanner.ddl.InformationSchemaScanner; +import com.google.cloud.teleport.v2.spanner.ddl.Table; +import com.google.cloud.teleport.v2.spanner.migrations.schema.ColumnPK; +import com.google.cloud.teleport.v2.spanner.migrations.schema.NameAndCols; +import com.google.cloud.teleport.v2.spanner.migrations.schema.SpannerColumnDefinition; +import com.google.cloud.teleport.v2.spanner.migrations.schema.SpannerColumnType; +import com.google.cloud.teleport.v2.spanner.migrations.schema.SpannerTable; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.apache.beam.sdk.io.gcp.spanner.SpannerAccessor; import org.apache.beam.sdk.io.gcp.spanner.SpannerConfig; @@ -43,4 +54,65 @@ public static Ddl getInformationSchemaAsDdl(SpannerConfig spannerConfig) { spannerAccessor.close(); return ddl; } + + public static Map convertDDLTableToSpannerTable(Collection tables) { + return tables.stream() + .collect( + Collectors.toMap( + Table::name, // Use the table name as the key + SpannerSchema::convertTableToSpannerTable // Convert Table to SpannerTable + )); + } + + public static Map convertDDLTableToSpannerNameAndColsTable( + Collection
tables) { + return tables.stream() + .collect( + Collectors.toMap( + Table::name, // Use the table name as the key + SpannerSchema + ::convertTableToSpannerTableNameAndCols // Convert Table to SpannerTable + )); + } + + private static NameAndCols convertTableToSpannerTableNameAndCols(Table table) { + return new NameAndCols( + table.name(), + table.columns().stream() + .collect( + Collectors.toMap( + Column::name, // Use column IDs as keys + Column::name))); + } + + private static SpannerTable convertTableToSpannerTable(Table table) { + String name = table.name(); // Table name + // Extract column IDs + String[] colIds = + table.columns().stream() + .map(Column::name) // Assuming Column name as ID + .toArray(String[]::new); + + // Build column definitions + Map colDefs = + table.columns().stream() + .collect( + Collectors.toMap( + Column::name, // Use column IDs as keys + column -> + new SpannerColumnDefinition( + column.name(), + new SpannerColumnType( + column.typeString(), // Type Code name (e.g., STRING, INT64) + false)))); + + // Extract primary keys + AtomicInteger orderCounter = new AtomicInteger(1); + ColumnPK[] primaryKeys = + table.primaryKeys().stream() + .map(pk -> new ColumnPK(pk.name(), orderCounter.getAndIncrement())) + .toArray(ColumnPK[]::new); + + return new SpannerTable(name, colIds, colDefs, primaryKeys, table.name()); + } } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/utils/CassandraConfigFileReader.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/utils/CassandraConfigFileReader.java new file mode 100644 index 0000000000..cd4b85aaed --- /dev/null +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/utils/CassandraConfigFileReader.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.spanner.migrations.utils; + +import com.google.cloud.teleport.v2.spanner.migrations.shard.CassandraShard; +import com.google.cloud.teleport.v2.spanner.migrations.shard.Shard; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.GsonBuilder; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import org.apache.beam.sdk.io.FileSystems; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to read the Cassandra configuration file in GCS and convert it into a CassandraConfig + * object. + */ +public class CassandraConfigFileReader { + + private static final Logger LOG = LoggerFactory.getLogger(CassandraConfigFileReader.class); + + public List getCassandraShard(String cassandraConfigFilePath) { + try (InputStream stream = + Channels.newInputStream( + FileSystems.open(FileSystems.matchNewResource(cassandraConfigFilePath, false)))) { + + String result = IOUtils.toString(stream, StandardCharsets.UTF_8); + Shard iShard = + new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) + .create() + .fromJson(result, CassandraShard.class); + + LOG.info("The Cassandra config is: {}", iShard); + return Collections.singletonList(iShard); + + } catch (IOException e) { + LOG.error( + "Failed to read Cassandra config file. Make sure it is ASCII or UTF-8 encoded and contains a well-formed JSON string.", + e); + throw new RuntimeException( + "Failed to read Cassandra config file. Make sure it is ASCII or UTF-8 encoded and contains a well-formed JSON string.", + e); + } + } +} diff --git a/v2/spanner-to-sourcedb/pom.xml b/v2/spanner-to-sourcedb/pom.xml index fd7896b923..63bd047ed6 100644 --- a/v2/spanner-to-sourcedb/pom.xml +++ b/v2/spanner-to-sourcedb/pom.xml @@ -88,6 +88,11 @@ ${project.version} test + + com.datastax.oss + java-driver-core + 4.17.0 + com.google.cloud.teleport diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/SpannerToSourceDb.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/SpannerToSourceDb.java index d33ec69b4e..e917327ac7 100644 --- a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/SpannerToSourceDb.java +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/SpannerToSourceDb.java @@ -15,6 +15,8 @@ */ package com.google.cloud.teleport.v2.templates; +import static com.google.cloud.teleport.v2.spanner.migrations.constants.Constants.CASSANDRA_SOURCE_TYPE; + import com.google.cloud.Timestamp; import com.google.cloud.teleport.metadata.Template; import com.google.cloud.teleport.metadata.TemplateCategory; @@ -30,6 +32,7 @@ import com.google.cloud.teleport.v2.spanner.migrations.shard.Shard; import com.google.cloud.teleport.v2.spanner.migrations.spanner.SpannerSchema; import com.google.cloud.teleport.v2.spanner.migrations.transformation.CustomTransformation; +import com.google.cloud.teleport.v2.spanner.migrations.utils.CassandraConfigFileReader; import com.google.cloud.teleport.v2.spanner.migrations.utils.SecretManagerAccessorImpl; import com.google.cloud.teleport.v2.spanner.migrations.utils.SessionFileReader; import com.google.cloud.teleport.v2.spanner.migrations.utils.ShardFileReader; @@ -415,6 +418,15 @@ public interface Options extends PipelineOptions, StreamingOptions { String getFilterEventsDirectoryName(); void setFilterEventsDirectoryName(String value); + + @TemplateParameter.GcsReadFile( + order = 10, + optional = false, + description = "Path to GCS file containing the the Cassandra Config details", + helpText = "Path to GCS file containing connection profile info for cassandra.") + String getCassandraConfigFilePath(); + + void setCassandraConfigFilePath(String value); } /** @@ -511,17 +523,41 @@ public static PipelineResult run(Options options) { shadowTableCreator.createShadowTablesInSpanner(); Ddl ddl = SpannerSchema.getInformationSchemaAsDdl(spannerConfig); - ShardFileReader shardFileReader = new ShardFileReader(new SecretManagerAccessorImpl()); - List shards = shardFileReader.getOrderedShardDetails(options.getSourceShardsFilePath()); - String shardingMode = Constants.SHARDING_MODE_MULTI_SHARD; - if (shards.size() == 1) { - shardingMode = Constants.SHARDING_MODE_SINGLE_SHARD; - - Shard singleShard = shards.get(0); - if (singleShard.getLogicalShardId() == null) { - singleShard.setLogicalShardId(Constants.DEFAULT_SHARD_ID); - LOG.info( - "Logical shard id was not found, hence setting it to : " + Constants.DEFAULT_SHARD_ID); + if (options.getSourceType().equals(CASSANDRA_SOURCE_TYPE)) { + schema.setSpSchema(SpannerSchema.convertDDLTableToSpannerTable(ddl.allTables())); + schema.setToSpanner(SpannerSchema.convertDDLTableToSpannerNameAndColsTable(ddl.allTables())); + } + List shards = new ArrayList<>(); + String shardingMode = Constants.SHARDING_MODE_SINGLE_SHARD; + if ("mysql".equals(options.getSourceType())) { + ShardFileReader shardFileReader = new ShardFileReader(new SecretManagerAccessorImpl()); + shards = shardFileReader.getOrderedShardDetails(options.getSourceShardsFilePath()); + shardingMode = Constants.SHARDING_MODE_MULTI_SHARD; + if (shards.size() == 1) { + shardingMode = Constants.SHARDING_MODE_SINGLE_SHARD; + Shard singleMySqlShard = shards.get(0); + if (singleMySqlShard.getLogicalShardId() == null) { + singleMySqlShard.setLogicalShardId(Constants.DEFAULT_SHARD_ID); + LOG.info( + "Logical shard id was not found, hence setting it to : " + + Constants.DEFAULT_SHARD_ID); + } + } + } else { + CassandraConfigFileReader cassandraConfigFileReader = new CassandraConfigFileReader(); + shards = cassandraConfigFileReader.getCassandraShard(options.getCassandraConfigFilePath()); + LOG.info("Cassandra config is: {}", shards.get(0)); + if (shards.size() == 1) { + shardingMode = Constants.SHARDING_MODE_SINGLE_SHARD; + Shard singleCassandraShard = shards.get(0); + if (singleCassandraShard.getLogicalShardId() == null) { + singleCassandraShard.setLogicalShardId(Constants.DEFAULT_SHARD_ID); + LOG.info( + "Logical shard id was not found, hence setting it to : " + + Constants.DEFAULT_SHARD_ID); + } + } else { + throw new IllegalArgumentException("Not Supporting more than one shard for cassandra"); } } boolean isRegularMode = "regular".equals(options.getRunMode()); @@ -613,8 +649,12 @@ public static PipelineResult run(Options options) { options.getShardingCustomJarPath(), options.getShardingCustomClassName(), options.getShardingCustomParameters(), - options.getMaxShardConnections() - * shards.size()))) // currently assuming that all shards accept the same + options.getMaxShardConnections() * shards.size(), + options.getSourceType(), + options + .getMaxShardConnections()))) // currently assuming that all mySqlShards + // accept the same// currently assuming + // that all shards accept the same // number of max connections .setCoder( KvCoder.of( diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/constants/Constants.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/constants/Constants.java index 1368a46fe3..35c354f267 100644 --- a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/constants/Constants.java +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/constants/Constants.java @@ -75,6 +75,7 @@ public class Constants { public static final String DEFAULT_SHARD_ID = "single_shard"; public static final String SOURCE_MYSQL = "mysql"; + public static final String SOURCE_CASSANDRA = "cassandra"; // Message written to the file for filtered records public static final String FILTERED_TAG_MESSAGE = diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/connection/CassandraConnectionHelper.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/connection/CassandraConnectionHelper.java new file mode 100644 index 0000000000..8e11fdfb81 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/connection/CassandraConnectionHelper.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.dbutils.connection; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.CqlSessionBuilder; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder; +import com.datastax.oss.driver.api.core.config.TypedDriverOption; +import com.google.cloud.teleport.v2.spanner.migrations.shard.CassandraShard; +import com.google.cloud.teleport.v2.spanner.migrations.shard.Shard; +import com.google.cloud.teleport.v2.templates.exceptions.ConnectionException; +import com.google.cloud.teleport.v2.templates.models.ConnectionHelperRequest; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CassandraConnectionHelper implements IConnectionHelper { + + private static final Logger LOG = LoggerFactory.getLogger(CassandraConnectionHelper.class); + private static Map connectionPoolMap = null; + + @Override + public synchronized void init(ConnectionHelperRequest connectionHelperRequest) { + if (connectionPoolMap != null) { + return; + } + LOG.info( + "Initializing Cassandra connection pool with size: {}", + connectionHelperRequest.getMaxConnections()); + connectionPoolMap = new HashMap<>(); + List shards = connectionHelperRequest.getShards(); + + for (Shard shard : shards) { + CassandraShard cassandraShard = (CassandraShard) shard; + cassandraShard.validate(); + + CqlSessionBuilder builder = + CqlSession.builder() + .addContactPoint( + new InetSocketAddress( + cassandraShard.getHost(), Integer.parseInt(cassandraShard.getPort()))) + .withAuthCredentials(cassandraShard.getUserName(), cassandraShard.getPassword()) + .withKeyspace(cassandraShard.getKeySpaceName()); + + ProgrammaticDriverConfigLoaderBuilder configLoaderBuilder = + DriverConfigLoader.programmaticBuilder(); + configLoaderBuilder.withInt( + (DriverOption) TypedDriverOption.CONNECTION_POOL_LOCAL_SIZE, + cassandraShard.getLocalPoolSize()); + configLoaderBuilder.withInt( + (DriverOption) TypedDriverOption.CONNECTION_POOL_REMOTE_SIZE, + cassandraShard.getRemotePoolSize()); + builder.withConfigLoader(configLoaderBuilder.build()); + + CqlSession session = builder.build(); + String connectionKey = + cassandraShard.getHost() + + ":" + + cassandraShard.getPort() + + "/" + + cassandraShard.getUserName() + + "/" + + cassandraShard.getKeySpaceName(); + connectionPoolMap.put(connectionKey, session); + } + } + + @Override + public CqlSession getConnection(String connectionRequestKey) throws ConnectionException { + try { + if (connectionPoolMap == null) { + LOG.warn("Connection pool not initialized"); + return null; + } + CqlSession session = connectionPoolMap.get(connectionRequestKey); + if (session == null) { + LOG.warn("Connection pool not found for source connection: {}", connectionRequestKey); + return null; + } + return session; + } catch (Exception e) { + throw new ConnectionException(e); + } + } + + @Override + public boolean isConnectionPoolInitialized() { + return false; + } + + // for unit testing + public void setConnectionPoolMap(Map inputMap) { + connectionPoolMap = inputMap; + } +} diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dao/source/CassandraDao.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dao/source/CassandraDao.java new file mode 100644 index 0000000000..f11c5e13ac --- /dev/null +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dao/source/CassandraDao.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.dbutils.dao.source; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.google.cloud.teleport.v2.templates.dbutils.connection.IConnectionHelper; +import com.google.cloud.teleport.v2.templates.exceptions.ConnectionException; +import com.google.cloud.teleport.v2.templates.models.DMLGeneratorResponse; +import com.google.cloud.teleport.v2.templates.models.PreparedStatementGeneratedResponse; +import com.google.cloud.teleport.v2.templates.models.PreparedStatementValueObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CassandraDao implements IDao { + private static final Logger LOG = LoggerFactory.getLogger(CassandraDao.class); + private final String cassandraUrl; + private final String cassandraUser; + private final IConnectionHelper connectionHelper; + + public CassandraDao( + String cassandraUrl, String cassandraUser, IConnectionHelper connectionHelper) { + this.cassandraUrl = cassandraUrl; + this.cassandraUser = cassandraUser; + this.connectionHelper = connectionHelper; + } + + @Override + public void write(DMLGeneratorResponse dmlGeneratorResponse) throws Exception { + try (CqlSession session = + (CqlSession) + connectionHelper.getConnection(this.cassandraUrl)) { // Ensure connection is obtained + if (session == null) { + throw new ConnectionException("Connection is null"); + } + if (dmlGeneratorResponse instanceof PreparedStatementGeneratedResponse) { + PreparedStatementGeneratedResponse preparedStatementGeneratedResponse = + (PreparedStatementGeneratedResponse) dmlGeneratorResponse; + try { + String dmlStatement = preparedStatementGeneratedResponse.getDmlStatement(); + PreparedStatement preparedStatement = session.prepare(dmlStatement); + BoundStatement boundStatement = + preparedStatement.bind( + preparedStatementGeneratedResponse.getValues().stream() + .map(PreparedStatementValueObject::getValue) + .toArray()); + session.execute(boundStatement); + } catch (Exception e) { + LOG.error(e.getMessage()); + } + + } else { + String simpleStatement = dmlGeneratorResponse.getDmlStatement(); + session.execute(simpleStatement); + } + } + } +} diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraDMLGenerator.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraDMLGenerator.java new file mode 100644 index 0000000000..1b91d75170 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraDMLGenerator.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.dbutils.dml; + +import com.google.cloud.teleport.v2.spanner.migrations.schema.*; +import com.google.cloud.teleport.v2.templates.models.DMLGeneratorRequest; +import com.google.cloud.teleport.v2.templates.models.DMLGeneratorResponse; +import com.google.cloud.teleport.v2.templates.models.PreparedStatementGeneratedResponse; +import com.google.cloud.teleport.v2.templates.models.PreparedStatementValueObject; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Creates DML statements For Cassandra */ +public class CassandraDMLGenerator implements IDMLGenerator { + private static final Logger LOG = LoggerFactory.getLogger(CassandraDMLGenerator.class); + + /** + * @param dmlGeneratorRequest the request containing necessary information to construct the DML + * statement, including modification type, table schema, new values, and key values. + * @return DMLGeneratorResponse + */ + @Override + public DMLGeneratorResponse getDMLStatement(DMLGeneratorRequest dmlGeneratorRequest) { + if (dmlGeneratorRequest + .getSchema() + .getSpannerToID() + .get(dmlGeneratorRequest.getSpannerTableName()) + == null) { + LOG.warn( + "The spanner table {} was not found in session file, dropping the record", + dmlGeneratorRequest.getSpannerTableName()); + return new DMLGeneratorResponse(""); + } + + String spannerTableId = + dmlGeneratorRequest + .getSchema() + .getSpannerToID() + .get(dmlGeneratorRequest.getSpannerTableName()) + .getName(); + SpannerTable spannerTable = dmlGeneratorRequest.getSchema().getSpSchema().get(spannerTableId); + + if (spannerTable == null) { + LOG.warn( + "The spanner table {} was not found in session file, dropping the record", + dmlGeneratorRequest.getSpannerTableName()); + return new DMLGeneratorResponse(""); + } + + SourceTable sourceTable = dmlGeneratorRequest.getSchema().getSrcSchema().get(spannerTableId); + if (sourceTable == null) { + LOG.warn("The table {} was not found in source", dmlGeneratorRequest.getSpannerTableName()); + return new DMLGeneratorResponse(""); + } + + if (sourceTable.getPrimaryKeys() == null || sourceTable.getPrimaryKeys().length == 0) { + LOG.warn( + "Cannot reverse replicate for table {} without primary key, skipping the record", + sourceTable.getName()); + return new DMLGeneratorResponse(""); + } + + Map> pkColumnNameValues = + getPkColumnValues( + spannerTable, + sourceTable, + dmlGeneratorRequest.getNewValuesJson(), + dmlGeneratorRequest.getKeyValuesJson(), + dmlGeneratorRequest.getSourceDbTimezoneOffset()); + if (pkColumnNameValues == null) { + LOG.warn( + "Cannot reverse replicate for table {} without primary key, skipping the record", + sourceTable.getName()); + return new DMLGeneratorResponse(""); + } + + if ("INSERT".equals(dmlGeneratorRequest.getModType()) + || "UPDATE".equals(dmlGeneratorRequest.getModType())) { + return generateUpsertStatement( + spannerTable, sourceTable, dmlGeneratorRequest, pkColumnNameValues); + + } else if ("DELETE".equals(dmlGeneratorRequest.getModType())) { + return getDeleteStatementCQL( + sourceTable.getName(), pkColumnNameValues, Instant.now().toEpochMilli() * 1000); + } else { + LOG.warn("Unsupported modType: {}", dmlGeneratorRequest.getModType()); + return new DMLGeneratorResponse(""); + } + } + + private static DMLGeneratorResponse generateUpsertStatement( + SpannerTable spannerTable, + SourceTable sourceTable, + DMLGeneratorRequest dmlGeneratorRequest, + Map> pkColumnNameValues) { + Map> columnNameValues = + getColumnValues( + spannerTable, + sourceTable, + dmlGeneratorRequest.getNewValuesJson(), + dmlGeneratorRequest.getKeyValuesJson(), + dmlGeneratorRequest.getSourceDbTimezoneOffset()); + return getUpsertStatementCQL( + sourceTable.getName(), + Instant.now().toEpochMilli() * 1000, + columnNameValues, + pkColumnNameValues); + } + + private static DMLGeneratorResponse getUpsertStatementCQL( + String tableName, + long timestamp, + Map> columnNameValues, + Map> pkColumnNameValues) { + + StringBuilder allColumns = new StringBuilder(); + StringBuilder placeholders = new StringBuilder(); + List> values = new ArrayList<>(); + + for (Map.Entry> entry : pkColumnNameValues.entrySet()) { + String colName = entry.getKey(); + PreparedStatementValueObject colValue = entry.getValue(); + if (colValue.getValue() != null) { + allColumns.append(colName).append(", "); + placeholders.append("?, "); + values.add(colValue); + } + } + + for (Map.Entry> entry : columnNameValues.entrySet()) { + String colName = entry.getKey(); + PreparedStatementValueObject colValue = entry.getValue(); + if (colValue.getValue() != null) { + allColumns.append(colName).append(", "); + placeholders.append("?, "); + values.add(colValue); + } + } + + if (allColumns.length() > 0) { + allColumns.setLength(allColumns.length() - 2); + } + if (placeholders.length() > 0) { + placeholders.setLength(placeholders.length() - 2); + } + + String preparedStatement = + "INSERT INTO " + + tableName + + " (" + + allColumns + + ") VALUES (" + + placeholders + + ") USING TIMESTAMP ?;"; + + PreparedStatementValueObject timestampObj = + new PreparedStatementValueObject<>("USING_TIMESTAMP", timestamp); + values.add(timestampObj); + + return new PreparedStatementGeneratedResponse(preparedStatement, values); + } + + private static DMLGeneratorResponse getDeleteStatementCQL( + String tableName, + Map> pkColumnNameValues, + long timestamp) { + + StringBuilder deleteConditions = new StringBuilder(); + List> values = new ArrayList<>(); + + for (Map.Entry> entry : pkColumnNameValues.entrySet()) { + String colName = entry.getKey(); + PreparedStatementValueObject colValue = entry.getValue(); + if (colValue.getValue() != null) { + deleteConditions.append(colName).append(" = ? AND "); + values.add(entry.getValue()); + } + } + + if (deleteConditions.length() > 0) { + deleteConditions.setLength(deleteConditions.length() - 5); + } + + String preparedStatement = + "DELETE FROM " + tableName + " WHERE " + deleteConditions + " USING TIMESTAMP ?;"; + + PreparedStatementValueObject timestampObj = + new PreparedStatementValueObject<>("USING_TIMESTAMP", timestamp); + values.add(timestampObj); + + return new PreparedStatementGeneratedResponse(preparedStatement, values); + } + + private static Map> getColumnValues( + SpannerTable spannerTable, + SourceTable sourceTable, + JSONObject newValuesJson, + JSONObject keyValuesJson, + String sourceDbTimezoneOffset) { + Map> response = new HashMap<>(); + + /* + Get all non-primary key col ids from source table + For each - get the corresponding column name from spanner Schema + if the column cannot be found in spanner schema - continue to next, + as the column will be stored with default/null values + check if the column name found in Spanner schema exists in keyJson - + if so, get the string value + else + check if the column name found in Spanner schema exists in valuesJson - + if so, get the string value + if the column does not exist in any of the JSON - continue to next, + as the column will be stored with default/null values + */ + Set sourcePKs = sourceTable.getPrimaryKeySet(); + for (Map.Entry entry : sourceTable.getColDefs().entrySet()) { + SourceColumnDefinition sourceColDef = entry.getValue(); + + String colName = sourceColDef.getName(); + if (sourcePKs.contains(colName)) { + continue; // we only need non-primary keys + } + + String colId = entry.getKey(); + SpannerColumnDefinition spannerColDef = spannerTable.getColDefs().get(colId); + if (spannerColDef == null) { + continue; + } + String spannerColumnName = spannerColDef.getName(); + PreparedStatementValueObject columnValue; + if (keyValuesJson.has(spannerColumnName)) { + // get the value based on Spanner and Source type + if (keyValuesJson.isNull(spannerColumnName)) { + continue; + } + columnValue = + getMappedColumnValue( + spannerColDef, sourceColDef, keyValuesJson, sourceDbTimezoneOffset); + } else if (newValuesJson.has(spannerColumnName)) { + // get the value based on Spanner and Source type + if (newValuesJson.isNull(spannerColumnName)) { + continue; + } + columnValue = + getMappedColumnValue( + spannerColDef, sourceColDef, newValuesJson, sourceDbTimezoneOffset); + } else { + continue; + } + + response.put(sourceColDef.getName(), columnValue); + } + + return response; + } + + private static Map> getPkColumnValues( + SpannerTable spannerTable, + SourceTable sourceTable, + JSONObject newValuesJson, + JSONObject keyValuesJson, + String sourceDbTimezoneOffset) { + Map> response = new HashMap<>(); + /* + Get all primary key col ids from source table + For each - get the corresponding column name from spanner Schema + if the column cannot be found in spanner schema - return null + check if the column name found in Spanner schema exists in keyJson - + if so, get the string value + else + check if the column name found in Spanner schema exists in valuesJson - + if so, get the string value + if the column does not exist in any of the JSON - return null + */ + ColumnPK[] sourcePKs = sourceTable.getPrimaryKeys(); + + for (ColumnPK currentSourcePK : sourcePKs) { + String colId = currentSourcePK.getColId(); + SourceColumnDefinition sourceColDef = sourceTable.getColDefs().get(colId); + SpannerColumnDefinition spannerColDef = spannerTable.getColDefs().get(colId); + if (spannerColDef == null) { + LOG.warn( + "The corresponding primary key column {} was not found in Spanner", + sourceColDef.getName()); + return null; + } + String spannerColumnName = spannerColDef.getName(); + PreparedStatementValueObject columnValue; + if (keyValuesJson.has(spannerColumnName)) { + // get the value based on Spanner and Source type + if (keyValuesJson.isNull(spannerColumnName)) { + continue; + } + columnValue = + getMappedColumnValue( + spannerColDef, sourceColDef, keyValuesJson, sourceDbTimezoneOffset); + } else if (newValuesJson.has(spannerColumnName)) { + // get the value based on Spanner and Source type + if (newValuesJson.isNull(spannerColumnName)) { + continue; + } + columnValue = + getMappedColumnValue( + spannerColDef, sourceColDef, newValuesJson, sourceDbTimezoneOffset); + } else { + LOG.warn("The column {} was not found in input record", spannerColumnName); + return null; + } + + response.put(sourceColDef.getName(), columnValue); + } + + return response; + } + + private static PreparedStatementValueObject getMappedColumnValue( + SpannerColumnDefinition spannerColDef, + SourceColumnDefinition sourceColDef, + JSONObject valuesJson, + String sourceDbTimezoneOffset) { + return CassandraTypeHandler.getColumnValueByType( + spannerColDef, sourceColDef, valuesJson, sourceDbTimezoneOffset); + } +} diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraTypeHandler.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraTypeHandler.java new file mode 100644 index 0000000000..caa7d64dda --- /dev/null +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraTypeHandler.java @@ -0,0 +1,1089 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.dbutils.dml; + +import com.google.cloud.teleport.v2.spanner.migrations.schema.SourceColumnDefinition; +import com.google.cloud.teleport.v2.spanner.migrations.schema.SpannerColumnDefinition; +import com.google.cloud.teleport.v2.templates.models.PreparedStatementValueObject; +import com.google.common.net.InetAddresses; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import org.eclipse.jetty.util.StringUtil; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +class CassandraTypeHandler { + + @FunctionalInterface + public interface TypeParser { + T parse(Object value); + } + + /** + * Converts a {@link String} to an ASCII representation for Cassandra's {@link String} or other + * ASCII-based types. + * + *

This method ensures that the string contains only valid ASCII characters (0-127). If any + * non-ASCII characters are found, an exception is thrown. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing all the key-value pairs for the current + * incoming stream. + * @return A {@link String} representing the ASCII value for the column in Cassandra. + * @throws IllegalArgumentException If the string contains non-ASCII characters. + */ + public static String handleCassandraAsciiType(String colName, JSONObject valuesJson) { + Object value = valuesJson.get(colName); + if (value instanceof String) { + String stringValue = (String) value; + if (isAscii(stringValue)) { + return stringValue; + } else { + throw new IllegalArgumentException( + "Invalid ASCII format for column: " + + colName + + ". String contains non-ASCII characters."); + } + } + return null; + } + + /** + * Generates a {@link BigInteger} based on the provided {@link CassandraTypeHandler}. + * + *

This method fetches the value associated with the given column name ({@code colName}) from + * the {@code valuesJson} object, and converts it to a {@link BigInteger}. The value can either be + * a string representing a number or a binary representation of a large integer (varint). + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing all the key-value pairs for the current + * incoming stream. + * @return A {@link BigInteger} object representing the varint value from the Cassandra data. + * @throws IllegalArgumentException If the value is not a valid format for varint (neither a valid + * number string nor a byte array). + */ + public static BigInteger handleCassandraVarintType(String colName, JSONObject valuesJson) { + Object value = valuesJson.get(colName); + + if (value instanceof String) { + try { + return new BigInteger((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid varint format (string) for column: " + colName, e); + } + } else if (value instanceof byte[]) { + try { + return new BigInteger((byte[]) value); + } catch (Exception e) { + throw new IllegalArgumentException( + "Invalid varint format (byte array) for column: " + colName, e); + } + } else { + return null; + } + } + + /** + * Generates a {@link Duration} based on the provided {@link CassandraTypeHandler}. + * + *

This method fetches a string value from the provided {@code valuesJson} object using the + * column name {@code colName}, and converts it into a {@link Duration} object. The string value + * should be in the ISO-8601 duration format (e.g., "PT20.345S"). + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing all the key-value pairs for the current + * incoming stream. + * @return A {@link Duration} object representing the duration value from the Cassandra data. + * @throws IllegalArgumentException if the value is not a valid duration string. + */ + public static Duration handleCassandraDurationType(String colName, JSONObject valuesJson) { + String durationString = valuesJson.optString(colName, null); + if (durationString == null) { + return null; + } + try { + return Duration.parse(durationString); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid duration format for column: " + colName, e); + } + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link InetAddress} object containing InetAddress as value represented in cassandra + * type. + */ + public static InetAddress handleCassandraInetAddressType(String colName, JSONObject valuesJson) { + return InetAddresses.forString(valuesJson.getString(colName)); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link Boolean} object containing the value represented in cassandra type. + */ + public static Boolean handleCassandraBoolType(String colName, JSONObject valuesJson) { + return valuesJson.optBoolean(colName, false); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link Float} object containing the value represented in cassandra type. + */ + public static Float handleCassandraFloatType(String colName, JSONObject valuesJson) { + try { + return valuesJson.getBigDecimal(colName).floatValue(); + } catch (JSONException e) { + return null; + } + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link Double} object containing the value represented in cassandra type. + */ + public static Double handleCassandraDoubleType(String colName, JSONObject valuesJson) { + try { + return valuesJson.getBigDecimal(colName).doubleValue(); + } catch (JSONException e) { + return null; + } + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link ByteBuffer} object containing the value represented in cassandra type. + */ + public static ByteBuffer handleCassandraBlobType(String colName, JSONObject valuesJson) { + Object colValue = valuesJson.opt(colName); + if (colValue == null) { + return null; + } + return parseBlobType(colValue); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colValue - contains all the key value for current incoming stream. + * @return a {@link ByteBuffer} object containing the value represented in cassandra type. + */ + public static ByteBuffer parseBlobType(Object colValue) { + byte[] byteArray; + + if (colValue instanceof byte[]) { + byteArray = (byte[]) colValue; + } else if (colValue instanceof String) { + String strValue = (String) colValue; + if (StringUtil.isHex(strValue, 0, strValue.length())) { + byteArray = convertHexStringToByteArray(strValue); + } else { + byteArray = java.util.Base64.getDecoder().decode((String) colValue); + } + } else { + throw new IllegalArgumentException("Unsupported type for column"); + } + + return ByteBuffer.wrap(byteArray); + } + + private static byte[] convertHexStringToByteArray(String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = + (byte) + ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } + + /** + * Generates a {@link LocalDate} based on the provided {@link CassandraTypeHandler}. + * + *

This method processes the given JSON object to extract a date value using the specified + * column name and formatter. It specifically handles the "Cassandra Date" format (yyyy-MM-dd). + * The resulting {@link LocalDate} represents the date value associated with the column. + * + * @param colName - the key used to fetch the value from the provided {@link JSONObject}. + * @param valuesJson - the JSON object containing all key-value pairs for the current incoming + * data stream. + * @return a {@link LocalDate} object containing the date value represented in Cassandra type + * format. If the column is missing or contains an invalid value, this will return {@code + * null}. + */ + public static LocalDate handleCassandraDateType(String colName, JSONObject valuesJson) { + return handleCassandraGenericDateType(colName, valuesJson, "yyyy-MM-dd"); + } + + /** + * Parses a timestamp value from a JSON object and returns it as an {@link Instant} in UTC. + * + *

This method extracts a timestamp value associated with the given column name from the + * provided {@link JSONObject}. The timestamp is expected to be in an ISO-8601 compatible format + * (e.g., "yyyy-MM-dd'T'HH:mm:ss.SSSZ"). The method ensures that the returned {@link Instant} is + * always in UTC, regardless of the time zone present in the input. + * + *

If the input timestamp cannot be parsed directly as an {@link Instant}, the method attempts + * to parse it as a {@link ZonedDateTime} and normalizes it to UTC before converting it to an + * {@link Instant}. If parsing fails, an {@link IllegalArgumentException} is thrown. + * + *

This method is particularly useful for processing timestamp data stored in Cassandra, where + * timestamps are often stored as ISO-8601 strings. + * + * @param colName the key used to fetch the value from the provided {@link JSONObject}. + * @param valuesJson the JSON object containing key-value pairs, including the timestamp value. + * @return an {@link Instant} representing the parsed timestamp value in UTC. + * @throws IllegalArgumentException if the column value is missing, empty, or cannot be parsed as + * a valid timestamp. + */ + public static Instant handleCassandraTimestampType(String colName, JSONObject valuesJson) { + String timestampValue = valuesJson.optString(colName, null); + if (timestampValue == null || timestampValue.isEmpty()) { + throw new IllegalArgumentException( + "Timestamp value for column " + colName + " is null or empty."); + } + return convertToCassandraTimestamp(timestampValue); + } + + /** + * A helper method that handles the conversion of a given column value to a {@link LocalDate} + * based on the specified date format (formatter). + * + *

This method extracts the value for the given column name from the provided JSON object and + * parses it into a {@link LocalDate} based on the provided date format. If the value is in an + * unsupported type or format, an exception is thrown. + * + * @param colName - the key used to fetch the value from the provided {@link JSONObject}. + * @param valuesJson - the JSON object containing all key-value pairs for the current incoming + * data stream. + * @param formatter - the date format pattern used to parse the value (e.g., "yyyy-MM-dd"). + * @return a {@link LocalDate} object containing the parsed date value. If the column is missing + * or invalid, this method returns {@code null}. + */ + private static LocalDate handleCassandraGenericDateType( + String colName, JSONObject valuesJson, String formatter) { + Object colValue = valuesJson.opt(colName); + if (colValue == null) { + return null; + } + + if (formatter == null) { + formatter = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + } + + return parseDate(colName, colValue, formatter); + } + + /** + * Parses a column value (String, {@link java.util.Date}, or {@code Long}) into a {@link + * LocalDate} using the specified date format. + * + *

This method handles different data types (String, Date, Long) by converting them into a + * {@link LocalDate}. The provided formatter is used to parse date strings, while other types are + * converted based on their corresponding representations. + * + * @param colName - the key used to fetch the value from the provided {@link JSONObject}. + * @param colValue - the value to be parsed into a {@link LocalDate}. + * @param formatter - the date format pattern used to parse date strings. + * @return a {@link LocalDate} object parsed from the given value. + * @throws IllegalArgumentException if the value cannot be parsed or is of an unsupported type. + */ + private static LocalDate parseDate(String colName, Object colValue, String formatter) { + LocalDate localDate; + if (colValue instanceof String) { + try { + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(formatter); + localDate = LocalDate.parse((String) colValue, dateFormatter); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date format for column " + colName, e); + } + } else if (colValue instanceof java.util.Date) { + localDate = + ((java.util.Date) colValue) + .toInstant() + .atZone(java.time.ZoneId.systemDefault()) + .toLocalDate(); + } else if (colValue instanceof Long) { + localDate = + java.time.Instant.ofEpochMilli((Long) colValue) + .atZone(java.time.ZoneId.systemDefault()) + .toLocalDate(); + } else { + throw new IllegalArgumentException("Unsupported type for column " + colName); + } + return localDate; + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link String} object containing String as value represented in cassandra type. + */ + public static String handleCassandraTextType(String colName, JSONObject valuesJson) { + return valuesJson.optString( + colName, null); // Get the value or null if the key is not found or the value is null + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link UUID} object containing UUID as value represented in cassandra type. + */ + public static UUID handleCassandraUuidType(String colName, JSONObject valuesJson) { + String uuidString = + valuesJson.optString( + colName, null); // Get the value or null if the key is not found or the value is null + + if (uuidString == null) { + return null; + } + + return UUID.fromString(uuidString); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link Long} object containing Long as value represented in cassandra type. + */ + public static Long handleCassandraBigintType(String colName, JSONObject valuesJson) { + try { + return valuesJson.getBigInteger(colName).longValue(); + } catch (JSONException e) { + return null; + } + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link Integer} object containing Integer as value represented in cassandra type. + */ + public static Integer handleCassandraIntType(String colName, JSONObject valuesJson) { + try { + return valuesJson.getBigInteger(colName).intValue(); + } catch (JSONException e) { + return null; + } + } + + /** + * Generates a {@link List} object containing a list of long values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of long values represented in Cassandra. + */ + public static List handleInt64ArrayType(String colName, JSONObject valuesJson) { + return handleArrayType( + colName, + valuesJson, + obj -> { + if (obj instanceof Long) { + return (Long) obj; + } else if (obj instanceof Number) { + return ((Number) obj).longValue(); + } else if (obj instanceof String) { + try { + return Long.getLong((String) obj); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid number format for column " + colName, e); + } + } else { + throw new IllegalArgumentException("Unsupported type for column " + colName); + } + }); + } + + /** + * Generates a {@link Set} object containing a set of long values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of long values represented in Cassandra. + */ + public static Set handleInt64SetType(String colName, JSONObject valuesJson) { + return new HashSet<>(handleInt64ArrayType(colName, valuesJson)); + } + + /** + * Generates a {@link List} object containing a list of integer values from Cassandra by + * converting long values to int. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of integer values represented in Cassandra. + */ + public static List handleInt64ArrayAsInt32Array(String colName, JSONObject valuesJson) { + return handleInt64ArrayType(colName, valuesJson).stream() + .map(Long::intValue) + .collect(Collectors.toList()); + } + + /** + * Generates a {@link Set} object containing a set of integer values from Cassandra by converting + * long values to int. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of integer values represented in Cassandra. + */ + public static Set handleInt64ArrayAsInt32Set(String colName, JSONObject valuesJson) { + return handleInt64ArrayType(colName, valuesJson).stream() + .map(Long::intValue) + .collect(Collectors.toSet()); + } + + /** + * Generates a {@link Set} object containing a set of string values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of string values represented in Cassandra. + */ + public static Set handleStringSetType(String colName, JSONObject valuesJson) { + return new HashSet<>(handleStringArrayType(colName, valuesJson)); + } + + /** + * Generates a {@link List} object containing a list of string values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of string values represented in Cassandra. + */ + public static List handleStringArrayType(String colName, JSONObject valuesJson) { + return handleArrayType(colName, valuesJson, String::valueOf); + } + + /** + * Generates a {@link List} object containing a list of boolean values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of boolean values represented in Cassandra. + */ + public static List handleBoolArrayType(String colName, JSONObject valuesJson) { + return handleArrayType( + colName, valuesJson, obj -> obj instanceof String && Boolean.parseBoolean((String) obj)); + } + + /** + * Generates a {@link Set} object containing a set of boolean values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of boolean values represented in Cassandra. + */ + public static Set handleBoolSetTypeString(String colName, JSONObject valuesJson) { + return new HashSet<>(handleBoolArrayType(colName, valuesJson)); + } + + /** + * Generates a {@link List} object containing a list of double values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of double values represented in Cassandra. + */ + public static List handleFloat64ArrayType(String colName, JSONObject valuesJson) { + return handleArrayType( + colName, + valuesJson, + obj -> { + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } else if (obj instanceof String) { + try { + return Double.valueOf((String) obj); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid number format for column " + colName, e); + } + } else { + throw new IllegalArgumentException("Unsupported type for column " + colName); + } + }); + } + + private static PreparedStatementValueObject getCollectionTypeFromCqlType( + String columnType, JSONObject jsonObject) { + String columnTypeLower = columnType.toLowerCase(); + if (columnTypeLower.contains("map<")) { + String typeParams = columnType.substring(4, columnType.length() - 1); // Remove map<> part + String[] typeParts = typeParams.split(","); // Split by comma + String keyType = typeParts[0].trim(); + String valueType = typeParts[1].trim(); + // TODO + // ADD LOGIC TO CONVERT BASED ON SAMPLE JSON + throw new UnsupportedOperationException("Map type handling not implemented yet."); + } else if (columnTypeLower.contains("set<")) { + // Handle Set Type (set) + String keyType = columnType.substring(4, columnType.length() - 1); + // TODO + // ADD LOGIC TO CONVERT BASED ON SAMPLE JSON + throw new UnsupportedOperationException("Set type handling not implemented yet."); + } else if (columnTypeLower.contains("list<")) { + // Handle List Type (list) + String keyType = columnType.substring(5, columnType.length() - 1); + // TODO + // ADD LOGIC TO CONVERT BASED ON SAMPLE JSON + throw new UnsupportedOperationException("List type handling not implemented yet."); + } else { + throw new IllegalArgumentException("Unsupported type for column: " + columnType); + } + } + + public static PreparedStatementValueObject getColumnValueByType( + SpannerColumnDefinition spannerColDef, + SourceColumnDefinition sourceColDef, + JSONObject valuesJson, + String sourceDbTimezoneOffset) { + + String columnType = sourceColDef.getType().getName(); + Object colValue = null; + String colType = spannerColDef.getType().getName(); + String colName = spannerColDef.getName(); + + switch (colType.toLowerCase()) { + case "bigint": + case "int64": + colValue = CassandraTypeHandler.handleCassandraBigintType(colName, valuesJson); + break; + case "string": + String inputValue = CassandraTypeHandler.handleCassandraTextType(colName, valuesJson); + if (isValidUUID(inputValue)) { + colValue = CassandraTypeHandler.handleCassandraUuidType(colName, valuesJson); + } else if (isValidIPAddress(inputValue)) { + try { + colValue = CassandraTypeHandler.handleCassandraInetAddressType(colName, valuesJson); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if (isValidJSONArray(inputValue)) { + colValue = new JSONArray(inputValue); + } else if (isValidJSONObject(inputValue)) { + colValue = new JSONObject(inputValue); + } else if (StringUtil.isHex(inputValue, 0, inputValue.length())) { + colValue = CassandraTypeHandler.handleCassandraBlobType(colName, valuesJson); + } else if (isAscii(inputValue)) { + colValue = CassandraTypeHandler.handleCassandraAsciiType(colName, valuesJson); + } else if (isDurationString(inputValue)) { + colValue = CassandraTypeHandler.handleCassandraDurationType(colName, valuesJson); + } else { + colValue = CassandraTypeHandler.handleCassandraTextType(colName, valuesJson); + } + break; + case "timestamp": + colValue = + convertToCassandraTimestamp( + String.valueOf( + CassandraTypeHandler.handleCassandraTimestampType(colName, valuesJson)), + sourceDbTimezoneOffset); + break; + + case "date": + case "datetime": + colValue = + convertToCassandraTimestamp( + String.valueOf(CassandraTypeHandler.handleCassandraDateType(colName, valuesJson)), + sourceDbTimezoneOffset); + break; + + case "boolean": + colValue = CassandraTypeHandler.handleCassandraBoolType(colName, valuesJson); + break; + + case "float64": + colValue = CassandraTypeHandler.handleCassandraDoubleType(colName, valuesJson); + break; + + case "numeric": + case "float": + colValue = CassandraTypeHandler.handleCassandraFloatType(colName, valuesJson); + break; + + case "bytes": + case "bytes(max)": + colValue = CassandraTypeHandler.handleCassandraBlobType(colName, valuesJson); + break; + + case "integer": + colValue = CassandraTypeHandler.handleCassandraIntType(colName, valuesJson); + break; + } + + if (colValue == null) { + return new PreparedStatementValueObject<>(columnType.toLowerCase(), null); + } + return parseAndGenerateCassandraType(columnType, colValue, colName, valuesJson); + } + + private static PreparedStatementValueObject parseAndGenerateCassandraType( + String columnType, Object colValue, String colName, JSONObject valuesJson) { + switch (columnType.toLowerCase()) { + case "ascii": + case "text": + case "varchar": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (String) colValue); + case "bigint": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (Long) colValue); + case "boolean": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (Boolean) colValue); + case "decimal": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (BigDecimal) colValue); + case "double": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (Double) colValue); + case "float": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (Float) colValue); + case "inet": + return new PreparedStatementValueObject<>( + columnType.toLowerCase(), (java.net.InetAddress) colValue); + case "int": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (Integer) colValue); + case "smallint": + return new PreparedStatementValueObject<>( + columnType.toLowerCase(), convertToSmallInt((Integer) colValue)); + case "time": + case "timestamp": + return new PreparedStatementValueObject<>( + columnType.toLowerCase(), convertToCassandraTimestamp((String) colValue)); + case "timeuuid": + case "uuid": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (UUID) colValue); + case "tinyint": + return new PreparedStatementValueObject<>( + columnType.toLowerCase(), convertToTinyInt((Integer) colValue)); + case "varint": + return new PreparedStatementValueObject<>( + columnType.toLowerCase(), new BigInteger(((ByteBuffer) colValue).array())); + case "duration": + return new PreparedStatementValueObject<>(columnType.toLowerCase(), (Duration) colValue); + default: + if (colValue instanceof JSONObject || colValue instanceof JSONArray) { + return new PreparedStatementValueObject<>( + columnType.toLowerCase(), + null); // we need to see sample here to implement actual logic + } else { + return new PreparedStatementValueObject<>(columnType.toLowerCase(), colValue); + } + } + } + + /** + * Generates a {@link Set} object containing a set of double values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of double values represented in Cassandra. + */ + public static Set handleFloat64SetType(String colName, JSONObject valuesJson) { + return new HashSet<>(handleFloat64ArrayType(colName, valuesJson)); + } + + /** + * Generates a {@link List} object containing a list of float values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of float values represented in Cassandra. + */ + public static List handleFloatArrayType(String colName, JSONObject valuesJson) { + return handleFloat64ArrayType(colName, valuesJson).stream() + .map(Double::floatValue) + .collect(Collectors.toList()); + } + + /** + * Generates a {@link Set} object containing a set of float values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of float values represented in Cassandra. + */ + public static Set handleFloatSetType(String colName, JSONObject valuesJson) { + return handleFloat64SetType(colName, valuesJson).stream() + .map(Double::floatValue) + .collect(Collectors.toSet()); + } + + /** + * Generates a {@link List} object containing a list of LocalDate values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of LocalDate values represented in Cassandra. + */ + public static List handleDateArrayType(String colName, JSONObject valuesJson) { + return handleArrayType( + colName, valuesJson, obj -> LocalDate.parse(obj.toString(), DateTimeFormatter.ISO_DATE)); + } + + /** + * Generates a {@link Set} object containing a set of LocalDate values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of LocalDate values represented in Cassandra. + */ + public static Set handleDateSetType(String colName, JSONObject valuesJson) { + return new HashSet<>(handleDateArrayType(colName, valuesJson)); + } + + /** + * Generates a {@link List} object containing a list of Timestamp values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link List} object containing a list of Timestamp values represented in Cassandra. + */ + public static List handleTimestampArrayType(String colName, JSONObject valuesJson) { + return handleArrayType( + colName, + valuesJson, + value -> + Timestamp.valueOf( + parseDate(colName, value, "yyyy-MM-dd'T'HH:mm:ss.SSSX").atStartOfDay())); + } + + /** + * Generates a {@link Set} object containing a set of Timestamp values from Cassandra. + * + * @param colName - The column name used to fetch the key from {@code valuesJson}. + * @param valuesJson - The {@link JSONObject} containing key-value pairs for the current incoming + * stream. + * @return a {@link Set} object containing a set of Timestamp values represented in Cassandra. + */ + public static Set handleTimestampSetType(String colName, JSONObject valuesJson) { + return new HashSet<>(handleTimestampArrayType(colName, valuesJson)); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link List} object containing List of ByteBuffer as value represented in cassandra + * type. + */ + public static List handleByteArrayType(String colName, JSONObject valuesJson) { + return handleArrayType(colName, valuesJson, CassandraTypeHandler::parseBlobType); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link List} object containing List of Type T as value represented in cassandra type + * which will be assigned runtime. + */ + public static List handleArrayType( + String colName, JSONObject valuesJson, TypeParser parser) { + return valuesJson.getJSONArray(colName).toList().stream() + .map(parser::parse) + .collect(Collectors.toList()); + } + + /** + * Generates a Type based on the provided {@link CassandraTypeHandler}. + * + * @param colName - which is used to fetch Key from valueJSON. + * @param valuesJson - contains all the key value for current incoming stream. + * @return a {@link Set} object containing Set of ByteBuffer as value represented in cassandra + * type. + */ + public static Set handleByteSetType(String colName, JSONObject valuesJson) { + return new HashSet<>(handleByteArrayType(colName, valuesJson)); + } + + /** + * Converts an {@link Integer} to a {@code short} (SmallInt). + * + *

This method checks if the {@code integerValue} is within the valid range for a {@code + * smallint} (i.e., between {@link Short#MIN_VALUE} and {@link Short#MAX_VALUE}). If the value is + * out of range, it throws an {@link IllegalArgumentException}. + * + * @param integerValue The integer value to be converted. + * @return The converted {@code short} value. + * @throws IllegalArgumentException If the {@code integerValue} is out of range for a {@code + * smallint}. + */ + public static short convertToSmallInt(Integer integerValue) { + if (integerValue < Short.MIN_VALUE || integerValue > Short.MAX_VALUE) { + throw new IllegalArgumentException("Value is out of range for smallint."); + } + return integerValue.shortValue(); + } + + /** + * Converts an {@link Integer} to a {@code byte} (TinyInt). + * + *

This method checks if the {@code integerValue} is within the valid range for a {@code + * tinyint} (i.e., between {@link Byte#MIN_VALUE} and {@link Byte#MAX_VALUE}). If the value is out + * of range, it throws an {@link IllegalArgumentException}. + * + * @param integerValue The integer value to be converted. + * @return The converted {@code byte} value. + * @throws IllegalArgumentException If the {@code integerValue} is out of range for a {@code + * tinyint}. + */ + public static byte convertToTinyInt(Integer integerValue) { + if (integerValue < Byte.MIN_VALUE || integerValue > Byte.MAX_VALUE) { + throw new IllegalArgumentException("Value is out of range for tinyint."); + } + return integerValue.byteValue(); + } + + /** + * Escapes single quotes in a Cassandra string by replacing them with double single quotes. + * + *

This method is commonly used to sanitize strings before inserting them into Cassandra + * queries, where single quotes need to be escaped by doubling them (i.e., `'` becomes `''`). + * + * @param value The string to be escaped. + * @return The escaped string where single quotes are replaced with double single quotes. + */ + public static String escapeCassandraString(String value) { + return value.replace("'", "''"); + } + + /** + * Converts a string representation of a timestamp to a Cassandra-compatible timestamp. + * + *

The method parses the {@code value} as a {@link ZonedDateTime}, applies the given timezone + * offset to adjust the time, and converts the result into a UTC timestamp string that is + * compatible with Cassandra. + * + * @param value The timestamp string in ISO-8601 format (e.g., "2024-12-05T10:15:30+01:00"). + * @param timezoneOffset The timezone offset (e.g., "+02:00") to apply to the timestamp. + * @return A string representation of the timestamp in UTC that is compatible with Cassandra. + * @throws RuntimeException If the timestamp string is invalid or the conversion fails. + */ + public static String convertToCassandraTimestamp(String value, String timezoneOffset) { + try { + ZonedDateTime dateTime = ZonedDateTime.parse(value); + ZoneOffset offset = ZoneOffset.of(timezoneOffset); + dateTime = dateTime.withZoneSameInstant(offset); + return "'" + dateTime.withZoneSameInstant(ZoneOffset.UTC).toString() + "'"; + } catch (DateTimeParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Converts a string representation of a date to a {@link LocalDate} compatible with Cassandra. + * + *

The method parses the {@code dateString} into an {@link Instant}, converts it to a {@link + * Date}, and then retrieves the corresponding {@link LocalDate} from the system's default time + * zone. + * + * @param dateString The date string in ISO-8601 format (e.g., "2024-12-05T00:00:00Z"). + * @return The {@link LocalDate} representation of the date. + */ + public static LocalDate convertToCassandraDate(String dateString) { + Instant instant = convertToCassandraTimestamp(dateString); + ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault()); + return zonedDateTime.toLocalDate(); + } + + /** + * Converts a string representation of a timestamp to an {@link Instant} compatible with + * Cassandra. + * + *

The method parses the {@code dateString} into an {@link Instant}, which represents an + * instantaneous point in time and is compatible with Cassandra timestamp types. + * + * @param timestampValue The timestamp string in ISO-8601 format (e.g., "2024-12-05T10:15:30Z"). + * @return The {@link Instant} representation of the timestamp. + */ + public static Instant convertToCassandraTimestamp(String timestampValue) { + try { + return Instant.parse(timestampValue); + } catch (DateTimeParseException e) { + try { + return ZonedDateTime.parse(timestampValue) + .withZoneSameInstant(java.time.ZoneOffset.UTC) + .toInstant(); + } catch (DateTimeParseException nestedException) { + throw new IllegalArgumentException( + "Failed to parse timestamp value" + timestampValue, nestedException); + } + } + } + + /** + * Validates if the given string represents a valid UUID. + * + *

This method attempts to parse the provided string as a UUID using {@link + * UUID#fromString(String)}. If parsing is successful, it returns {@code true}, indicating that + * the string is a valid UUID. Otherwise, it returns {@code false}. + * + * @param value The string to check if it represents a valid UUID. + * @return {@code true} if the string is a valid UUID, {@code false} otherwise. + */ + public static boolean isValidUUID(String value) { + try { + UUID.fromString(value); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Validates if the given string represents a valid IP address. + * + *

This method attempts to resolve the provided string as an {@link InetAddresses} using {@link + * InetAddresses#forString(String)}. If successful, it returns {@code true}, indicating that the + * string is a valid IP address. Otherwise, it returns {@code false}. + * + * @param value The string to check if it represents a valid IP address. + * @return {@code true} if the string is a valid IP address, {@code false} otherwise. + */ + public static boolean isValidIPAddress(String value) { + try { + InetAddresses.forString(value); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Validates if the given string is a valid JSON object. + * + *

This method attempts to parse the string using {@link JSONObject} to check if the value + * represents a valid JSON object. If the string is valid JSON, it returns {@code true}, otherwise + * {@code false}. + * + * @param value The string to check if it represents a valid JSON object. + * @return {@code true} if the string is a valid JSON object, {@code false} otherwise. + */ + public static boolean isValidJSONObject(String value) { + try { + new JSONObject(value); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Validates if the given string is a valid JSONArray. + * + *

This method attempts to parse the string using {@link JSONArray} to check if the value + * represents a valid JSON object. If the string is valid JSON, it returns {@code true}, otherwise + * {@code false}. + * + * @param value The string to check if it represents a valid JSON object. + * @return {@code true} if the string is a valid JSON object, {@code false} otherwise. + */ + public static boolean isValidJSONArray(String value) { + try { + new JSONArray(value); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Helper method to check if a string contains only ASCII characters (0-127). + * + * @param value - The string to check. + * @return true if the string contains only ASCII characters, false otherwise. + */ + public static boolean isAscii(String value) { + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) > 127) { + return false; + } + } + return true; + } + + /** + * Helper method to check if a string contains Duration Character. + * + * @param value - The string to check. + * @return true if the string contains Duration Character, false otherwise. + */ + public static boolean isDurationString(String value) { + try { + Duration.parse(value); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/InputRecordProcessor.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/InputRecordProcessor.java index 9bdfe2bcda..d94496f468 100644 --- a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/InputRecordProcessor.java +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/InputRecordProcessor.java @@ -15,6 +15,8 @@ */ package com.google.cloud.teleport.v2.templates.dbutils.processor; +import static com.google.cloud.teleport.v2.templates.constants.Constants.SOURCE_CASSANDRA; + import com.google.cloud.teleport.v2.spanner.exceptions.InvalidTransformationException; import com.google.cloud.teleport.v2.spanner.migrations.convertors.ChangeEventToMapConvertor; import com.google.cloud.teleport.v2.spanner.migrations.schema.Schema; @@ -53,7 +55,8 @@ public static boolean processRecord( String shardId, String sourceDbTimezoneOffset, IDMLGenerator dmlGenerator, - ISpannerMigrationTransformer spannerToSourceTransformer) + ISpannerMigrationTransformer spannerToSourceTransformer, + String source) throws Exception { try { @@ -102,7 +105,11 @@ public static boolean processRecord( LOG.warn("DML statement is empty for table: " + tableName); return false; } - dao.write(dmlGeneratorResponse.getDmlStatement()); + if (source.equals(SOURCE_CASSANDRA)) { + dao.write(dmlGeneratorResponse); + } else { + dao.write(dmlGeneratorResponse.getDmlStatement()); + } Counter numRecProcessedMetric = Metrics.counter(shardId, "records_written_to_source_" + shardId); diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/SourceProcessorFactory.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/SourceProcessorFactory.java index bde4b10fe7..9df0feb1f4 100644 --- a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/SourceProcessorFactory.java +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/dbutils/processor/SourceProcessorFactory.java @@ -15,12 +15,16 @@ */ package com.google.cloud.teleport.v2.templates.dbutils.processor; +import com.google.cloud.teleport.v2.spanner.migrations.shard.CassandraShard; import com.google.cloud.teleport.v2.spanner.migrations.shard.Shard; import com.google.cloud.teleport.v2.templates.constants.Constants; +import com.google.cloud.teleport.v2.templates.dbutils.connection.CassandraConnectionHelper; import com.google.cloud.teleport.v2.templates.dbutils.connection.IConnectionHelper; import com.google.cloud.teleport.v2.templates.dbutils.connection.JdbcConnectionHelper; +import com.google.cloud.teleport.v2.templates.dbutils.dao.source.CassandraDao; import com.google.cloud.teleport.v2.templates.dbutils.dao.source.IDao; import com.google.cloud.teleport.v2.templates.dbutils.dao.source.JdbcDao; +import com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraDMLGenerator; import com.google.cloud.teleport.v2.templates.dbutils.dml.IDMLGenerator; import com.google.cloud.teleport.v2.templates.dbutils.dml.MySQLDMLGenerator; import com.google.cloud.teleport.v2.templates.exceptions.UnsupportedSourceException; @@ -33,20 +37,43 @@ import java.util.function.Function; public class SourceProcessorFactory { - private static Map dmlGeneratorMap = - Map.of(Constants.SOURCE_MYSQL, new MySQLDMLGenerator()); + private static Map dmlGeneratorMap = new HashMap<>(); - private static Map connectionHelperMap = - Map.of(Constants.SOURCE_MYSQL, new JdbcConnectionHelper()); + private static Map connectionHelperMap = new HashMap<>(); - private static Map driverMap = - Map.of(Constants.SOURCE_MYSQL, "com.mysql.cj.jdbc.Driver"); - - private static Map> connectionUrl = + private static final Map driverMap = Map.of( - Constants.SOURCE_MYSQL, - shard -> - "jdbc:mysql://" + shard.getHost() + ":" + shard.getPort() + "/" + shard.getDbName()); + Constants.SOURCE_MYSQL, "com.mysql.cj.jdbc.Driver", // MySQL JDBC Driver + Constants.SOURCE_CASSANDRA, + "com.datastax.oss.driver.api.core.CqlSession" // Cassandra Session Class + ); + + private static Map> connectionUrl = new HashMap<>(); + + static { + dmlGeneratorMap.put(Constants.SOURCE_MYSQL, new MySQLDMLGenerator()); + dmlGeneratorMap.put(Constants.SOURCE_CASSANDRA, new CassandraDMLGenerator()); + + connectionHelperMap.put(Constants.SOURCE_MYSQL, new JdbcConnectionHelper()); + connectionHelperMap.put(Constants.SOURCE_CASSANDRA, new CassandraConnectionHelper()); + + connectionUrl.put( + Constants.SOURCE_MYSQL, + shard -> + "jdbc:mysql://" + shard.getHost() + ":" + shard.getPort() + "/" + shard.getDbName()); + connectionUrl.put( + Constants.SOURCE_CASSANDRA, + shard -> { + CassandraShard cassandraShard = (CassandraShard) shard; + return cassandraShard.getHost() + + ":" + + cassandraShard.getPort() + + "/" + + cassandraShard.getUserName() + + "/" + + cassandraShard.getKeySpaceName(); + }); + } private static Map, Integer, ConnectionHelperRequest>> connectionHelperRequestFactory = @@ -58,7 +85,16 @@ public class SourceProcessorFactory { null, maxConnections, driverMap.get(Constants.SOURCE_MYSQL), - "SET SESSION net_read_timeout=1200" // to avoid timeouts at network layer + "SET SESSION net_read_timeout=1200" // To avoid timeouts at the network layer + ), + Constants.SOURCE_CASSANDRA, + (shards, maxConnections) -> + new ConnectionHelperRequest( + shards, + null, + maxConnections, + driverMap.get(Constants.SOURCE_CASSANDRA), + null // No specific initialization query for Cassandra )); // for unit testing purposes @@ -132,7 +168,10 @@ private static Map createSourceDaoMap(String source, List s Map sourceDaoMap = new HashMap<>(); for (Shard shard : shards) { String connectionUrl = urlGenerator.apply(shard); - IDao sqlDao = new JdbcDao(connectionUrl, shard.getUserName(), getConnectionHelper(source)); + IDao sqlDao = + source.equals(Constants.SOURCE_MYSQL) + ? new JdbcDao(connectionUrl, shard.getUserName(), getConnectionHelper(source)) + : new CassandraDao(connectionUrl, shard.getUserName(), getConnectionHelper(source)); sourceDaoMap.put(shard.getLogicalShardId(), sqlDao); } return sourceDaoMap; diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/models/PreparedStatementGeneratedResponse.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/models/PreparedStatementGeneratedResponse.java new file mode 100644 index 0000000000..0a7feea80a --- /dev/null +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/models/PreparedStatementGeneratedResponse.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.models; + +import java.util.List; + +public class PreparedStatementGeneratedResponse extends DMLGeneratorResponse { + private List> values; + + public PreparedStatementGeneratedResponse( + String dmlStatement, List> values) { + super(dmlStatement); + this.values = values; + } + + public List> getValues() { + return values; + } + + public void setValues(List> values) { + this.values = values; + } +} diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/models/PreparedStatementValueObject.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/models/PreparedStatementValueObject.java new file mode 100644 index 0000000000..3b9111dc05 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/models/PreparedStatementValueObject.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.models; + +public class PreparedStatementValueObject { + private String dataType; + private T value; + + public PreparedStatementValueObject(String dataType, T value) { + this.dataType = dataType; + this.value = value; + } + + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + // Override toString() for better readability when printing objects + @Override + public String toString() { + return "PreparedStatementValueObject{" + "key='" + dataType + '\'' + ", value=" + value + '}'; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + PreparedStatementValueObject that = (PreparedStatementValueObject) obj; + return dataType.equals(that.dataType) && value.equals(that.value); + } + + @Override + public int hashCode() { + return 31 * dataType.hashCode() + (value != null ? value.hashCode() : 0); + } +} diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFn.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFn.java index 76b78d4a4e..472b273573 100644 --- a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFn.java +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFn.java @@ -90,6 +90,10 @@ public class AssignShardIdFn private final Long maxConnectionsAcrossAllShards; + private final Long maxConnections; + + private String sourceType; + public AssignShardIdFn( SpannerConfig spannerConfig, Schema schema, @@ -100,7 +104,9 @@ public AssignShardIdFn( String customJarPath, String shardingCustomClassName, String shardingCustomParameters, - Long maxConnectionsAcrossAllShards) { + Long maxConnectionsAcrossAllShards, + String sourceType, + Long maxConnections) { this.spannerConfig = spannerConfig; this.schema = schema; this.ddl = ddl; @@ -111,6 +117,8 @@ public AssignShardIdFn( this.shardingCustomClassName = shardingCustomClassName; this.shardingCustomParameters = shardingCustomParameters; this.maxConnectionsAcrossAllShards = maxConnectionsAcrossAllShards; + this.sourceType = sourceType; + this.maxConnections = maxConnections; } // setSpannerAccessor is added to be used by unit tests @@ -181,59 +189,68 @@ public void processElement(ProcessContext c) throws Exception { String qualifiedShard = ""; String tableName = record.getTableName(); String keysJsonStr = record.getMod().getKeysJson(); + Long finalKey; try { - if (shardingMode.equals(Constants.SHARDING_MODE_SINGLE_SHARD)) { - record.setShard(this.shardName); - qualifiedShard = this.shardName; - } else { - // Skip from processing if table not in session File - String shardIdColumn = getShardIdColumnForTableName(tableName); - if (shardIdColumn.isEmpty()) { - LOG.warn( - "Writing record for table {} to skipped directory name {} since table not present in" - + " the session file.", - tableName, - skipDirName); - record.setShard(skipDirName); - qualifiedShard = skipDirName; + if ("mysql".equals(this.sourceType)) { + if (shardingMode.equals(Constants.SHARDING_MODE_SINGLE_SHARD)) { + record.setShard(this.shardName); + qualifiedShard = this.shardName; } else { - - JsonNode keysJson = mapper.readTree(keysJsonStr); - String newValueJsonStr = record.getMod().getNewValuesJson(); - JsonNode newValueJson = mapper.readTree(newValueJsonStr); - Map spannerRecord = new HashMap<>(); - // Query the spanner database in case of a DELETE event - if (record.getModType() == ModType.DELETE) { - spannerRecord = - fetchSpannerRecord( - tableName, - record.getCommitTimestamp(), - record.getServerTransactionId(), - keysJson); + // Skip from processing if table not in session File + String shardIdColumn = getShardIdColumnForTableName(tableName); + if (shardIdColumn.isEmpty()) { + LOG.warn( + "Writing record for table {} to skipped directory name {} since table not present in" + + " the session file.", + tableName, + skipDirName); + record.setShard(skipDirName); + qualifiedShard = skipDirName; } else { - spannerRecord = getSpannerRecordFromChangeStreamData(tableName, keysJson, newValueJson); - } - ShardIdRequest shardIdRequest = new ShardIdRequest(tableName, spannerRecord); - ShardIdResponse shardIdResponse = getShardIdResponse(shardIdRequest); - - qualifiedShard = shardIdResponse.getLogicalShardId(); - if (qualifiedShard == null || qualifiedShard.isEmpty() || qualifiedShard.contains("/")) { - throw new IllegalArgumentException( - "Invalid logical shard id value: " - + qualifiedShard - + " for spanner table: " - + record.getTableName()); + JsonNode keysJson = mapper.readTree(keysJsonStr); + String newValueJsonStr = record.getMod().getNewValuesJson(); + JsonNode newValueJson = mapper.readTree(newValueJsonStr); + Map spannerRecord = new HashMap<>(); + // Query the spanner database in case of a DELETE event + if (record.getModType() == ModType.DELETE) { + spannerRecord = + fetchSpannerRecord( + tableName, + record.getCommitTimestamp(), + record.getServerTransactionId(), + keysJson); + } else { + spannerRecord = + getSpannerRecordFromChangeStreamData(tableName, keysJson, newValueJson); + } + ShardIdRequest shardIdRequest = new ShardIdRequest(tableName, spannerRecord); + + ShardIdResponse shardIdResponse = getShardIdResponse(shardIdRequest); + + qualifiedShard = shardIdResponse.getLogicalShardId(); + if (qualifiedShard == null + || qualifiedShard.isEmpty() + || qualifiedShard.contains("/")) { + throw new IllegalArgumentException( + "Invalid logical shard id value: " + + qualifiedShard + + " for spanner table: " + + record.getTableName()); + } } } - } - record.setShard(qualifiedShard); - String finalKeyString = tableName + "_" + keysJsonStr + "_" + qualifiedShard; - Long finalKey = - finalKeyString.hashCode() % maxConnectionsAcrossAllShards; // The total parallelism is - // maxConnectionsAcrossAllShards + record.setShard(qualifiedShard); + String finalKeyString = tableName + "_" + keysJsonStr + "_" + qualifiedShard; + finalKey = finalKeyString.hashCode() % maxConnectionsAcrossAllShards; + } else { + record.setShard(this.shardName); + qualifiedShard = this.shardName; + String finalKeyString = tableName + "_" + keysJsonStr + "_" + qualifiedShard; + finalKey = finalKeyString.hashCode() % maxConnections; + } c.output(KV.of(finalKey, record)); } catch (Exception e) { @@ -242,7 +259,7 @@ public void processElement(ProcessContext c) throws Exception { LOG.error("Error fetching shard Id column: " + e.getMessage() + ": " + errors.toString()); // The record has no shard hence will be sent to DLQ in subsequent steps String finalKeyString = record.getTableName() + "_" + keysJsonStr + "_" + skipDirName; - Long finalKey = finalKeyString.hashCode() % maxConnectionsAcrossAllShards; + finalKey = finalKeyString.hashCode() % maxConnectionsAcrossAllShards; c.output(KV.of(finalKey, record)); } } diff --git a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/SourceWriterFn.java b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/SourceWriterFn.java index 6b00511bf8..e2c5b7934e 100644 --- a/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/SourceWriterFn.java +++ b/v2/spanner-to-sourcedb/src/main/java/com/google/cloud/teleport/v2/templates/transforms/SourceWriterFn.java @@ -211,7 +211,8 @@ public void processElement(ProcessContext c) { shardId, sourceDbTimezoneOffset, sourceProcessor.getDmlGenerator(), - spannerToSourceTransformer); + spannerToSourceTransformer, + this.source); if (isEventFiltered) { outputWithTag(c, Constants.FILTERED_TAG, Constants.FILTERED_TAG_MESSAGE, spannerRec); } diff --git a/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraDMLGeneratorTest.java b/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraDMLGeneratorTest.java new file mode 100644 index 0000000000..8d6d920887 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraDMLGeneratorTest.java @@ -0,0 +1,913 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.dbutils.dml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.teleport.v2.spanner.migrations.schema.*; +import com.google.cloud.teleport.v2.spanner.migrations.utils.SessionFileReader; +import com.google.cloud.teleport.v2.templates.dbutils.processor.InputRecordProcessor; +import com.google.cloud.teleport.v2.templates.models.DMLGeneratorRequest; +import com.google.cloud.teleport.v2.templates.models.DMLGeneratorResponse; +import com.google.cloud.teleport.v2.templates.models.PreparedStatementGeneratedResponse; +import java.util.*; +import org.json.JSONObject; +import org.junit.Test; + +public class CassandraDMLGeneratorTest { + + @Test + public void tableAndAllColumnNameTypesMatch() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("SingerId")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void tableNameMismatchAllColumnNameTypesMatch() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraTableNameMismatchSession.json"); + String tableName = "leChanteur"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("SingerId")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void tableNameMatchColumnNameTypeMismatch() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraCoulmnNameTypeMismatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"John\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void tableNameMatchSourceColumnNotPresentInSpanner() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraSourceColumnAbsentInSpannerSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void tableNameMatchSpannerColumnNotPresentInSource() { + + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraSpannerColumnAbsentInSourceSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\",\"hb_shardId\":\"shardA\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void primaryKeyNotFoundInJson() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SomeRandomName\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void primaryKeyNotPresentInSourceSchema() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraSourceNoPkSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void primaryKeyMismatch() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"SingerId\":\"999\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"FirstName\":\"kk\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("SingerId")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void updateToNull() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("SingerId")); + assertTrue(sql.contains("FirstName")); + assertEquals(3, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void deleteMultiplePKColumns() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraMultiColmPKSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"LastName\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\",\"FirstName\":\"kk\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "DELETE"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + assertEquals(1, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void testSingleQuoteMatch() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"k\u0027k\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void singleQuoteBytesDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"Jw\u003d\u003d\",\"varchar_column\":\"\u0027\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void twoSingleEscapedQuoteDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"Jyc\u003d\",\"varchar_column\":\"\u0027\u0027\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void threeEscapesAndSingleQuoteDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"XCc\u003d\",\"varchar_column\":\"\\\\\\\u0027\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void tabEscapeDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"CQ==\",\"varchar_column\":\"\\t\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void backSpaceEscapeDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"CA==\",\"varchar_column\":\"\\b\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void newLineEscapeDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"Cg==\",\"varchar_column\":\"\\n\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void carriageReturnEscapeDML() throws Exception { + + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"DQ==\",\"varchar_column\":\"\\r\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void formFeedEscapeDML() throws Exception { + + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"DA==\",\"varchar_column\":\"\\f\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void doubleQuoteEscapeDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"Ig==\",\"varchar_column\":\"\\\"\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void backSlashEscapeDML() throws Exception { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraQuotesSession.json"); + + String tableName = "sample_table"; + String newValuesString = "{\"blob_column\":\"XA==\",\"varchar_column\":\"\\\\\",}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"id\":\"12\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("sample_table")); + assertEquals(2, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void bitColumnSql() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraBitSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"YmlsX2NvbA\u003d\u003d\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void testSpannerTableNotInSchema() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "SomeRandomTableNotInSchema"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void testSpannerKeyIsNull() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":null}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(3, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void testKeyInNewValuesJson() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\",\"SingerId\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SmthingElse\":null}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(3, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void testSourcePKNotInSpanner() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "customer"; + String newValuesString = "{\"Does\":\"not\",\"matter\":\"junk\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"Dont\":\"care\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "DELETE"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void primaryKeyMismatchSpannerNull() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"SingerId\":\"999\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"FirstName\":null}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("SingerId")); + assertTrue(sql.contains("LastName")); + assertEquals(3, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void testUnsupportedModType() { + Schema schema = SessionFileReader.read("src/test/resources/allMatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "JUNK"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void testUpdateModType() { + Schema schema = + SessionFileReader.read( + "src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"999\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "UPDATE"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.contains("SingerId")); + assertTrue(sql.contains("FirstName")); + assertTrue(sql.contains("LastName")); + assertEquals(4, ((PreparedStatementGeneratedResponse) dmlGeneratorResponse).getValues().size()); + } + + @Test + public void testSpannerTableIdMismatch() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraErrorSchemaSession.json"); + String tableName = "Singers"; + String newValuesString = "{\"Does\":\"not\",\"matter\":\"junk\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"Dont\":\"care\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "DELETE"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + assertTrue(sql.isEmpty()); + } + + @Test + public void testSourcePkNull() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraErrorSchemaSession.json"); + String tableName = "Persons"; + String newValuesString = "{\"Does\":\"not\",\"matter\":\"junk\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"Dont\":\"care\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void testSourceTableNotInSchema() { + Schema schema = getSchemaObject(); + String tableName = "contacts"; + String newValuesString = "{\"accountId\": \"Id1\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"Dont\":\"care\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void testSpannerTableNotInSchemaObject() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + schema.getSpSchema().remove(schema.getSpannerToID().get(tableName).getName()); + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\",\"SingerId\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SmthingElse\":null}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + + assertTrue(sql.isEmpty()); + } + + @Test + public void testSpannerColDefsNull() { + Schema schema = + SessionFileReader.read("src/test/resources/CassandraJson/cassandraAllMatchSession.json"); + String tableName = "Singers"; + + String spannerTableId = schema.getSpannerToID().get(tableName).getName(); + SpannerTable spannerTable = schema.getSpSchema().get(spannerTableId); + spannerTable.getColDefs().remove("c5"); + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String keyValueString = "{\"SingerId\":\"23\"}"; + JSONObject keyValuesJson = new JSONObject(keyValueString); + String modType = "INSERT"; + + CassandraDMLGenerator cassandraDMLGenerator = new CassandraDMLGenerator(); + DMLGeneratorResponse dmlGeneratorResponse = + cassandraDMLGenerator.getDMLStatement( + new DMLGeneratorRequest.Builder( + modType, tableName, newValuesJson, keyValuesJson, "+00:00") + .setSchema(schema) + .build()); + String sql = dmlGeneratorResponse.getDmlStatement(); + CassandraDMLGenerator test = new CassandraDMLGenerator(); + InputRecordProcessor test2 = new InputRecordProcessor(); + assertTrue(sql.isEmpty()); + } + + public static Schema getSchemaObject() { + Map syntheticPKeys = new HashMap(); + Map srcSchema = new HashMap(); + Map spSchema = getSampleSpSchema(); + Map spannerToID = getSampleSpannerToId(); + Schema expectedSchema = new Schema(spSchema, syntheticPKeys, srcSchema); + expectedSchema.setSpannerToID(spannerToID); + return expectedSchema; + } + + public static Map getSampleSpSchema() { + Map spSchema = new HashMap(); + Map t1SpColDefs = + new HashMap(); + t1SpColDefs.put( + "c1", new SpannerColumnDefinition("accountId", new SpannerColumnType("STRING", false))); + t1SpColDefs.put( + "c2", new SpannerColumnDefinition("accountName", new SpannerColumnType("STRING", false))); + t1SpColDefs.put( + "c3", + new SpannerColumnDefinition("migration_shard_id", new SpannerColumnType("STRING", false))); + t1SpColDefs.put( + "c4", new SpannerColumnDefinition("accountNumber", new SpannerColumnType("INT", false))); + spSchema.put( + "t1", + new SpannerTable( + "tableName", + new String[] {"c1", "c2", "c3", "c4"}, + t1SpColDefs, + new ColumnPK[] {new ColumnPK("c1", 1)}, + "c3")); + return spSchema; + } + + public static Map getSampleSpannerToId() { + Map spannerToId = new HashMap(); + Map t1ColIds = new HashMap(); + t1ColIds.put("accountId", "c1"); + t1ColIds.put("accountName", "c2"); + t1ColIds.put("migration_shard_id", "c3"); + t1ColIds.put("accountNumber", "c4"); + spannerToId.put("tableName", new NameAndCols("t1", t1ColIds)); + return spannerToId; + } +} diff --git a/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraTypeHandlerTest.java b/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraTypeHandlerTest.java new file mode 100644 index 0000000000..640af210a0 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/dbutils/dml/CassandraTypeHandlerTest.java @@ -0,0 +1,988 @@ +/* + * Copyright (C) 2024 Google LLC + * + * 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 com.google.cloud.teleport.v2.templates.dbutils.dml; + +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.convertToCassandraDate; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.convertToCassandraTimestamp; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.convertToSmallInt; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.convertToTinyInt; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.escapeCassandraString; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleBoolSetTypeString; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleByteArrayType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleByteSetType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraAsciiType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraBigintType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraBlobType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraBoolType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraDateType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraDoubleType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraDurationType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraFloatType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraInetAddressType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraIntType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraTextType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraTimestampType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraUuidType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleCassandraVarintType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleDateSetType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleFloat64ArrayType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleFloatArrayType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleFloatSetType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleInt64ArrayAsInt32Array; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleInt64ArrayAsInt32Set; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleInt64SetType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleStringArrayType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.handleStringSetType; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.isValidIPAddress; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.isValidJSONObject; +import static com.google.cloud.teleport.v2.templates.dbutils.dml.CassandraTypeHandler.isValidUUID; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.json.JSONObject; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CassandraTypeHandlerTest { + + @Test + public void convertSpannerValueJsonToBooleanType() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"isAdmin\":\"true\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "isAdmin"; + Boolean convertedValue = handleCassandraBoolType(colKey, newValuesJson); + assertTrue(convertedValue); + } + + @Test + public void convertSpannerValueJsonToBooleanType_False() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"isAdmin\":\"false\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "isAdmin"; + Boolean convertedValue = handleCassandraBoolType(colKey, newValuesJson); + Assert.assertFalse(convertedValue); + } + + @Test + public void convertSpannerValueJsonToFloatType() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"age\":23.5}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + Float convertedValue = handleCassandraFloatType(colKey, newValuesJson); + assertEquals(23.5f, convertedValue, 0.01f); + } + + @Test + public void convertSpannerValueJsonToDoubleType() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"salary\":100000.75}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "salary"; + Double convertedValue = handleCassandraDoubleType(colKey, newValuesJson); + assertEquals(100000.75, convertedValue, 0.01); + } + + @Test + public void convertSpannerValueJsonToBlobType_FromByteArray() { + String newValuesString = + "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"data\":\"QUJDQDEyMzQ=\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "data"; + ByteBuffer convertedValue = handleCassandraBlobType(colKey, newValuesJson); + byte[] expectedBytes = java.util.Base64.getDecoder().decode("QUJDQDEyMzQ="); + byte[] actualBytes = new byte[convertedValue.remaining()]; + convertedValue.get(actualBytes); + Assert.assertArrayEquals(expectedBytes, actualBytes); + } + + @Rule public ExpectedException expectedEx = ExpectedException.none(); + + @Test + public void testHandleNullBooleanType() { + String newValuesString = "{\"isAdmin\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "isAdmin"; + assertEquals(false, handleCassandraBoolType(colKey, newValuesJson)); + } + + @Test + public void testHandleNullFloatType() { + String newValuesString = "{\"age\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + assertNull(handleCassandraFloatType(colKey, newValuesJson)); + } + + @Test + public void testHandleNullDoubleType() { + String newValuesString = "{\"salary\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "salary"; + Double value = handleCassandraDoubleType(colKey, newValuesJson); + assertNull(value); + } + + @Test + public void testHandleMaxInteger() { + String newValuesString = "{\"age\":2147483647}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + Integer value = handleCassandraIntType(colKey, newValuesJson); + assertEquals(Integer.MAX_VALUE, value.longValue()); + } + + @Test + public void testHandleMinInteger() { + String newValuesString = "{\"age\":-2147483648}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + Integer value = handleCassandraIntType(colKey, newValuesJson); + assertEquals(Integer.MIN_VALUE, value.longValue()); + } + + @Test + public void testHandleMaxLong() { + String newValuesString = "{\"age\":9223372036854775807}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + Long value = handleCassandraBigintType(colKey, newValuesJson); + assertEquals(Long.MAX_VALUE, value.longValue()); + } + + @Test + public void testHandleMinLong() { + String newValuesString = "{\"age\":-9223372036854775808}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + Long value = handleCassandraBigintType(colKey, newValuesJson); + assertEquals(Long.MIN_VALUE, value.longValue()); + } + + @Test + public void testHandleMaxFloat() { + String newValuesString = "{\"value\":3.4028235E38}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "value"; + Float value = handleCassandraFloatType(colKey, newValuesJson); + assertEquals(Float.MAX_VALUE, value, 0.01f); + } + + @Test + public void testHandleMinFloat() { + String newValuesString = "{\"value\":-3.4028235E38}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "value"; + Float value = handleCassandraFloatType(colKey, newValuesJson); + assertEquals(-Float.MAX_VALUE, value, 0.01f); + } + + @Test + public void testHandleMaxDouble() { + String newValuesString = "{\"value\":1.7976931348623157E308}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "value"; + Double value = handleCassandraDoubleType(colKey, newValuesJson); + assertEquals(Double.MAX_VALUE, value, 0.01); + } + + @Test + public void testHandleMinDouble() { + String newValuesString = "{\"value\":-1.7976931348623157E308}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "value"; + Double value = handleCassandraDoubleType(colKey, newValuesJson); + assertEquals(-Double.MAX_VALUE, value, 0.01); + } + + @Test + public void testHandleInvalidIntegerFormat() { + String newValuesString = "{\"age\":\"invalid_integer\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + handleCassandraIntType(colKey, newValuesJson); + } + + @Test + public void testHandleInvalidLongFormat() { + String newValuesString = "{\"age\":\"invalid_long\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + handleCassandraBigintType(colKey, newValuesJson); + } + + @Test + public void testHandleInvalidFloatFormat() { + String newValuesString = "{\"value\":\"invalid_float\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "value"; + handleCassandraFloatType(colKey, newValuesJson); + } + + @Test + public void testHandleInvalidDoubleFormat() { + String newValuesString = "{\"value\":\"invalid_double\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "value"; + handleCassandraDoubleType(colKey, newValuesJson); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleInvalidBlobFormat() { + String newValuesString = "{\"data\":\"not_base64\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "data"; + handleCassandraBlobType(colKey, newValuesJson); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleInvalidDateFormat() { + String newValuesString = "{\"birthdate\":\"invalid_date_format\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "birthdate"; + handleCassandraDateType(colKey, newValuesJson); + } + + @Test + public void testHandleNullTextType() { + String newValuesString = "{\"name\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "name"; + String value = handleCassandraTextType(colKey, newValuesJson); + assertNull(value); + } + + @Test + public void testHandleUnsupportedBooleanType() { + String newValuesString = "{\"values\":[true, false]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + + expectedEx.expect(IllegalArgumentException.class); + expectedEx.expectMessage("Unsupported type for column values"); + + handleFloatSetType("values", newValuesJson); + } + + @Test + public void testHandleUnsupportedListType() { + String newValuesString = "{\"values\":[[1, 2], [3, 4]]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + + expectedEx.expect(IllegalArgumentException.class); + expectedEx.expectMessage("Unsupported type for column values"); + + handleFloatSetType("values", newValuesJson); + } + + @Test + public void testHandleUnsupportedMapType() { + String newValuesString = "{\"values\":[{\"key1\":\"value1\"}, {\"key2\":\"value2\"}]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + + expectedEx.expect(IllegalArgumentException.class); + expectedEx.expectMessage("Unsupported type for column values"); + + handleFloatSetType("values", newValuesJson); + } + + @Test + public void testHandleUnsupportedType() { + String newValuesString = "{\"values\":[true, false]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + + expectedEx.expect(IllegalArgumentException.class); + expectedEx.expectMessage("Unsupported type for column values"); + + handleFloatSetType("values", newValuesJson); + } + + @Test + public void convertSpannerValueJsonToBlobType_FromBase64() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"data\":\"QUJDRA==\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "data"; + ByteBuffer convertedValue = handleCassandraBlobType(colKey, newValuesJson); + byte[] expectedBytes = Base64.getDecoder().decode("QUJDRA=="); + byte[] actualBytes = new byte[convertedValue.remaining()]; + convertedValue.get(actualBytes); + Assert.assertArrayEquals(expectedBytes, actualBytes); + } + + @Test + public void convertSpannerValueJsonToBlobType_EmptyString() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"data\":\"\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "data"; + ByteBuffer convertedValue = handleCassandraBlobType(colKey, newValuesJson); + Assert.assertNotNull(convertedValue); + assertEquals(0, convertedValue.remaining()); + } + + @Test(expected = IllegalArgumentException.class) + public void convertSpannerValueJsonToBlobType_InvalidType() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"data\":12345}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "data"; + handleCassandraBlobType(colKey, newValuesJson); + } + + @Test + public void convertSpannerValueJsonToInvalidFloatType() { + String newValuesString = + "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"age\":\"invalid_value\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "age"; + handleCassandraFloatType(colKey, newValuesJson); + } + + @Test + public void convertSpannerValueJsonToInvalidDoubleType() { + String newValuesString = + "{\"FirstName\":\"kk\",\"LastName\":\"ll\", \"salary\":\"invalid_value\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "salary"; + handleCassandraDoubleType(colKey, newValuesJson); + } + + @Test + public void convertSpannerValueJsonToBlobType_MissingColumn() { + String newValuesString = "{\"FirstName\":\"kk\",\"LastName\":\"ll\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "data"; + ByteBuffer convertedValue = handleCassandraBlobType(colKey, newValuesJson); + Assert.assertNull(convertedValue); + } + + @Test + public void testHandleByteArrayType() { + String newValuesString = "{\"data\":[\"QUJDRA==\", \"RkZIRg==\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + List value = handleByteArrayType("data", newValuesJson); + + List expected = + Arrays.asList( + ByteBuffer.wrap(Base64.getDecoder().decode("QUJDRA==")), + ByteBuffer.wrap(Base64.getDecoder().decode("RkZIRg=="))); + assertEquals(expected, value); + } + + @Test + public void testHandleByteSetType() { + String newValuesString = "{\"data\":[\"QUJDRA==\", \"RkZIRg==\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set value = handleByteSetType("data", newValuesJson); + + Set expected = + new HashSet<>( + Arrays.asList( + ByteBuffer.wrap(Base64.getDecoder().decode("QUJDRA==")), + ByteBuffer.wrap(Base64.getDecoder().decode("RkZIRg==")))); + assertEquals(expected, value); + } + + @Test + public void testHandleStringArrayType() { + String newValuesString = "{\"names\":[\"Alice\", \"Bob\", \"Charlie\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + List value = handleStringArrayType("names", newValuesJson); + + List expected = Arrays.asList("Alice", "Bob", "Charlie"); + assertEquals(expected, value); + } + + @Test + public void testHandleStringSetType() { + String newValuesString = "{\"names\":[\"Alice\", \"Bob\", \"Alice\", \"Charlie\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set valueList = handleStringSetType("names", newValuesJson); + HashSet value = new HashSet<>(valueList); + HashSet expected = new HashSet<>(Arrays.asList("Alice", "Bob", "Charlie")); + assertEquals(expected, value); + } + + @Test + public void testHandleBoolSetTypeString() { + String newValuesString = "{\"flags\":[\"true\", \"false\", \"true\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set value = handleBoolSetTypeString("flags", newValuesJson); + + Set expected = new HashSet<>(Arrays.asList(true, false)); + assertEquals(expected, value); + } + + @Test + public void testHandleFloatArrayType() { + String newValuesString = "{\"values\":[1.1, 2.2, 3.3]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + List value = handleFloatArrayType("values", newValuesJson); + + List expected = Arrays.asList(1.1f, 2.2f, 3.3f); + assertEquals(expected, value); + } + + @Test + public void testHandleFloatSetType() { + String newValuesString = "{\"values\":[1.1, 2.2, 3.3, 2.2]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set value = handleFloatSetType("values", newValuesJson); + + Set expected = new HashSet<>(Arrays.asList(1.1f, 2.2f, 3.3f)); + assertEquals(expected, value); + } + + @Test + public void testHandleFloatSetType_InvalidString() { + String newValuesString = "{\"values\":[\"1.1\", \"2.2\", \"abc\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + try { + handleFloatSetType("values", newValuesJson); + fail("Expected IllegalArgumentException for invalid number format"); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("Invalid number format for column values")); + } + } + + @Test + public void testHandleFloat64ArrayType() { + String newValuesString = "{\"values\":[1.1, \"2.2\", 3.3]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + List value = handleFloat64ArrayType("values", newValuesJson); + + List expected = Arrays.asList(1.1, 2.2, 3.3); + assertEquals(expected, value); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleFloat64ArrayTypeInvalid() { + String newValuesString = "{\"values\":[\"1.1\", \"abc\", \"3.3\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + handleFloat64ArrayType("values", newValuesJson); + } + + @Test + public void testHandleDateSetType() { + String newValuesString = "{\"dates\":[\"2024-12-05\", \"2024-12-06\"]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set value = handleDateSetType("dates", newValuesJson); + Set expected = + new HashSet<>(Arrays.asList(LocalDate.of(2024, 12, 5), LocalDate.of(2024, 12, 6))); + assertEquals(expected, value); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleFloat64ArrayType_WithUnsupportedList() { + String jsonStr = "{\"colName\": [[1, 2, 3], [4, 5, 6]]}"; + JSONObject valuesJson = new JSONObject(jsonStr); + CassandraTypeHandler.handleFloat64ArrayType("colName", valuesJson); + } + + @Test + public void testHandleInt64SetType_ValidLongValues() { + String newValuesString = "{\"numbers\":[1, 2, 3, 4]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set result = handleInt64SetType("numbers", newValuesJson); + Set expected = new HashSet<>(Arrays.asList(1L, 2L, 3L, 4L)); + assertEquals(expected, result); + } + + @Test + public void testHandleCassandraIntType_ValidInteger() { + String newValuesString = "{\"age\":1234}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Integer result = handleCassandraIntType("age", newValuesJson); + Integer expected = 1234; + assertEquals(expected, result); + } + + @Test + public void testHandleCassandraBigintType_ValidConversion() { + String newValuesString = "{\"age\":1234567890123}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Long result = handleCassandraBigintType("age", newValuesJson); + Long expected = 1234567890123L; + assertEquals(expected, result); + } + + @Test + public void testHandleInt64ArrayAsInt32Array() { + String newValuesString = "{\"values\":[1, 2, 3, 4]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + List value = handleInt64ArrayAsInt32Array("values", newValuesJson); + + List expected = Arrays.asList(1, 2, 3, 4); + assertEquals(expected, value); + } + + @Test + public void testHandleInt64ArrayAsInt32Set() { + String newValuesString = "{\"values\":[1, 2, 3, 2]}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + Set value = handleInt64ArrayAsInt32Set("values", newValuesJson); + + Set expected = new HashSet<>(Arrays.asList(1, 2, 3)); + assertEquals(expected, value); + } + + @Test + public void testHandleCassandraUuidTypeNull() { + String newValuesString = "{\"uuid\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + UUID value = handleCassandraUuidType("uuid", newValuesJson); + Assert.assertNull(value); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleCassandraTimestampInvalidFormat() { + String newValuesString = "{\"createdAt\":\"2024-12-05 10:15:30.123\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + handleCassandraTimestampType("createdAt", newValuesJson); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleCassandraTimestampInvalidFormatColNull() { + String newValuesString = "{\"createdAt\":\"2024-12-05 10:15:30.123\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + handleCassandraTimestampType("timestamp", newValuesJson); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleCassandraDateInvalidFormat() { + String newValuesString = "{\"birthdate\":\"2024/12/05\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + handleCassandraDateType("birthdate", newValuesJson); + } + + @Test + public void testHandleCassandraTextTypeNull() { + String newValuesString = "{\"name\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String value = handleCassandraTextType("name", newValuesJson); + Assert.assertNull(value); + } + + @Test + public void testHandleBoolArrayType_ValidBooleanStrings() { + String jsonStr = "{\"colName\": [\"true\", \"false\", \"true\"]}"; + JSONObject valuesJson = new JSONObject(jsonStr); + List result = CassandraTypeHandler.handleBoolArrayType("colName", valuesJson); + assertEquals(3, result.size()); + assertTrue(result.get(0)); + assertFalse(result.get(1)); + assertTrue(result.get(2)); + } + + @Test + public void testHandleBoolArrayType_InvalidBooleanStrings() { + String jsonStr = "{\"colName\": [\"yes\", \"no\", \"true\"]}"; + JSONObject valuesJson = new JSONObject(jsonStr); + List result = CassandraTypeHandler.handleBoolArrayType("colName", valuesJson); + assertEquals(3, result.size()); + assertFalse(result.get(0)); + assertFalse(result.get(1)); + assertTrue(result.get(2)); + } + + @Test + public void testHandleBoolArrayType_EmptyArray() { + String jsonStr = "{\"colName\": []}"; + JSONObject valuesJson = new JSONObject(jsonStr); + List result = CassandraTypeHandler.handleBoolArrayType("colName", valuesJson); + assertTrue(result.isEmpty()); + } + + @Test + public void testHandleTimestampSetType_validArray() { + String jsonString = + "{\"timestamps\": [\"2024-12-04T12:34:56.123Z\", \"2024-12-05T13:45:00.000Z\"]}"; + JSONObject valuesJson = new JSONObject(jsonString); + + Set result = CassandraTypeHandler.handleTimestampSetType("timestamps", valuesJson); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(Timestamp.valueOf("2024-12-04 00:00:00.0"))); + assertTrue(result.contains(Timestamp.valueOf("2024-12-05 00:00:00.0"))); + } + + @Test + public void testHandleValidAsciiString() { + String newValuesString = "{\"name\":\"JohnDoe\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "name"; + assertEquals("JohnDoe", handleCassandraAsciiType(colKey, newValuesJson)); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleNonAsciiString() { + String newValuesString = "{\"name\":\"JoãoDoe\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "name"; + handleCassandraAsciiType(colKey, newValuesJson); + } + + @Test + public void testHandleNullForAsciiColumn() { + String newValuesString = "{\"name\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "name"; + handleCassandraAsciiType(colKey, newValuesJson); + } + + @Test + public void testHandleValidStringVarint() { + String newValuesString = "{\"amount\":\"123456789123456789\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "amount"; + BigInteger expected = new BigInteger("123456789123456789"); + assertEquals(expected, handleCassandraVarintType(colKey, newValuesJson)); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleInvalidStringVarint() { + String newValuesString = "{\"amount\":\"abcxyz\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "amount"; + handleCassandraVarintType(colKey, newValuesJson); + } + + @Test + public void testHandleInvalidTypeVarint() { + String newValuesString = "{\"amount\":12345}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "amount"; + handleCassandraVarintType(colKey, newValuesJson); + } + + @Test + public void testHandleValidDuration() { + String newValuesString = "{\"duration\":\"P1DT1H\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "duration"; + Duration expected = Duration.parse("P1DT1H"); + assertEquals(expected, handleCassandraDurationType(colKey, newValuesJson)); + } + + @Test + public void testHandleNullDuration() { + String newValuesString = "{\"duration\":null}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "duration"; + assertNull(handleCassandraDurationType(colKey, newValuesJson)); + } + + @Test + public void testHandleMissingColumnKey() { + String newValuesString = "{\"otherColumn\":\"P1DT1H\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "duration"; + assertNull(handleCassandraDurationType(colKey, newValuesJson)); + } + + @Test + public void testHandleValidIPv4Address() throws UnknownHostException { + String newValuesString = "{\"ipAddress\":\"192.168.0.1\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "ipAddress"; + InetAddress expected = InetAddress.getByName("192.168.0.1"); + assertEquals(expected, handleCassandraInetAddressType(colKey, newValuesJson)); + } + + @Test + public void testHandleValidIPv6Address() throws UnknownHostException { + String newValuesString = "{\"ipAddress\":\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "ipAddress"; + InetAddress expected = InetAddress.getByName("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assertEquals(expected, handleCassandraInetAddressType(colKey, newValuesJson)); + } + + @Test(expected = IllegalArgumentException.class) + public void testHandleInvalidIPAddressFormat() throws IllegalArgumentException { + String newValuesString = "{\"ipAddress\":\"invalid-ip-address\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "ipAddress"; + handleCassandraInetAddressType(colKey, newValuesJson); + } + + @Test + public void testHandleEmptyStringIPAddress() { + String newValuesString = "{\"ipAddress\":\"192.168.1.1\"}"; + JSONObject newValuesJson = new JSONObject(newValuesString); + String colKey = "ipAddress"; + Object result = handleCassandraInetAddressType(colKey, newValuesJson); + assertTrue("Expected result to be of type InetAddress", result instanceof InetAddress); + assertEquals( + "IP address does not match", "192.168.1.1", ((InetAddress) result).getHostAddress()); + } + + @Test + public void testConvertToSmallIntBelowMinValue() { + Integer invalidValue = Short.MIN_VALUE - 1; + assertThrows(IllegalArgumentException.class, () -> convertToSmallInt(invalidValue)); + } + + @Test + public void testConvertToSmallIntAboveMaxValue() { + Integer invalidValue = Short.MAX_VALUE + 1; + assertThrows(IllegalArgumentException.class, () -> convertToSmallInt(invalidValue)); + } + + @Test + public void testConvertToTinyIntValidInput() { + Integer validValue = 100; + byte result = convertToTinyInt(validValue); + assertEquals(100, result); + } + + @Test + public void testConvertToTinyIntBelowMinValue() { + Integer invalidValue = Byte.MIN_VALUE - 1; + assertThrows(IllegalArgumentException.class, () -> convertToTinyInt(invalidValue)); + } + + @Test + public void testConvertToTinyIntAboveMaxValue() { + Integer invalidValue = Byte.MAX_VALUE + 1; + assertThrows(IllegalArgumentException.class, () -> convertToTinyInt(invalidValue)); + } + + @Test + public void testEscapeCassandraStringNoQuotes() { + String input = "Hello World"; + String expected = "Hello World"; + String result = escapeCassandraString(input); + assertEquals(expected, result); + } + + @Test + public void testEscapeCassandraStringWithSingleQuote() { + String input = "O'Reilly"; + String expected = "O''Reilly"; + String result = escapeCassandraString(input); + assertEquals(expected, result); + } + + @Test + public void testEscapeCassandraStringEmpty() { + String input = ""; + String expected = ""; + String result = escapeCassandraString(input); + assertEquals(expected, result); + } + + @Test + public void testEscapeCassandraStringWithMultipleQuotes() { + String input = "It's John's book."; + String expected = "It''s John''s book."; + String result = escapeCassandraString(input); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraTimestampWithValidOffset() { + String value = "2024-12-12T10:15:30+02:00"; + String timezoneOffset = "+00:00"; + String expected = "'2024-12-12T08:15:30Z'"; + String result = convertToCassandraTimestamp(value, timezoneOffset); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraTimestampWithNonZeroOffset() { + String value = "2024-12-12T10:15:30+02:00"; + String timezoneOffset = "+00:00"; + String expected = "'2024-12-12T08:15:30Z'"; + String result = convertToCassandraTimestamp(value, timezoneOffset); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraTimestampWithNegativeOffset() { + String value = "2024-12-12T10:15:30-05:00"; + String timezoneOffset = "+00:00"; + String expected = "'2024-12-12T15:15:30Z'"; + String result = convertToCassandraTimestamp(value, timezoneOffset); + assertEquals(expected, result); + } + + @Test(expected = RuntimeException.class) + public void testConvertToCassandraTimestampWithInvalidFormat() { + String value = "2024-12-12T25:15:30+02:00"; + String timezoneOffset = "+00:00"; + convertToCassandraTimestamp(value, timezoneOffset); + } + + @Test + public void testConvertToCassandraTimestampWithoutTimezone() { + String value = "2024-12-12T10:15:30Z"; + String timezoneOffset = "+00:00"; + String expected = "'2024-12-12T10:15:30Z'"; + String result = convertToCassandraTimestamp(value, timezoneOffset); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraDateWithValidDate() { + String dateString = "2024-12-12T10:15:30Z"; + LocalDate result = convertToCassandraDate(dateString); + LocalDate expected = LocalDate.of(2024, 12, 12); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraDateLeapYear() { + String dateString = "2024-02-29T00:00:00Z"; + LocalDate result = convertToCassandraDate(dateString); + LocalDate expected = LocalDate.of(2024, 2, 29); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraDateWithDifferentTimeZone() { + String dateString = "2024-12-12T10:15:30+02:00"; + LocalDate result = convertToCassandraDate(dateString); + LocalDate expected = LocalDate.of(2024, 12, 12); + assertEquals(expected, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testConvertToCassandraDateWithInvalidDate() { + String dateString = "2024-13-12T10:15:30Z"; + convertToCassandraDate(dateString); + } + + @Test + public void testConvertToCassandraTimestampWithValidDate() { + String dateString = "2024-12-12T10:15:30Z"; + Instant result = convertToCassandraTimestamp(dateString); + Instant expected = Instant.parse(dateString); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraTimestampWithTimezoneOffset() { + String dateString = "2024-12-12T10:15:30+02:00"; + Instant result = convertToCassandraTimestamp(dateString); + Instant expected = Instant.parse("2024-12-12T08:15:30Z"); + assertEquals(expected, result); + } + + @Test + public void testConvertToCassandraTimestampLeapYear() { + String dateString = "2024-02-29T00:00:00Z"; + Instant result = convertToCassandraTimestamp(dateString); + Instant expected = Instant.parse(dateString); + assertEquals(expected, result); + } + + @Test(expected = IllegalArgumentException.class) + public void testConvertToCassandraTimestampWithInvalidDate() { + String dateString = "2024-13-12T10:15:30Z"; + convertToCassandraTimestamp(dateString); + } + + @Test + public void testIsValidUUIDWithValidUUID() { + String validUUID = "123e4567-e89b-12d3-a456-426614174000"; + boolean result = isValidUUID(validUUID); + assertTrue(result); + } + + @Test + public void testIsValidUUIDWithInvalidUUID() { + String invalidUUID = "123e4567-e89b-12d3-a456-426614174000Z"; + boolean result = isValidUUID(invalidUUID); + assertFalse(result); + } + + @Test + public void testIsValidUUIDWithEmptyString() { + String emptyString = ""; + boolean result = isValidUUID(emptyString); + assertFalse(result); + } + + @Test + public void testIsValidIPAddressWithValidIPv4() { + String validIPv4 = "192.168.1.1"; + boolean result = isValidIPAddress(validIPv4); + assertTrue(result); + } + + @Test + public void testIsValidIPAddressWithValidIPv6() { + String validIPv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; + boolean result = isValidIPAddress(validIPv6); + assertTrue(result); + } + + @Test + public void testIsValidIPAddressWithInvalidFormat() { + String invalidIP = "999.999.999.999"; + boolean result = isValidIPAddress(invalidIP); + assertFalse(result); + } + + @Test + public void testIsValidJSONWithValidJSONObject() { + String validJson = "{\"name\":\"John\", \"age\":30}"; + boolean result = isValidJSONObject(validJson); + assertTrue(result); + } + + @Test + public void testIsValidJSONWithInvalidJSONObject() { + String invalidJson = "{\"name\":\"John\", \"age\":30"; + boolean result = isValidJSONObject(invalidJson); + assertFalse(result); + } + + @Test + public void testIsValidJSONObjectWithEmptyString() { + String emptyString = ""; + boolean result = isValidJSONObject(emptyString); + assertFalse(result); + } + + @Test + public void testIsValidJSONObjectWithNull() { + String nullString = null; + boolean result = isValidJSONObject(nullString); + assertFalse(result); + } +} diff --git a/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFnTest.java b/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFnTest.java index 1413be3d2e..729dd9d2e5 100644 --- a/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFnTest.java +++ b/v2/spanner-to-sourcedb/src/test/java/com/google/cloud/teleport/v2/templates/transforms/AssignShardIdFnTest.java @@ -120,7 +120,9 @@ public void testGetRowAsMap() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); List columns = List.of("accountId", "accountName", "migration_shard_id", "accountNumber"); Map actual = assignShardIdFn.getRowAsMap(mockRow, columns, "tableName"); @@ -145,7 +147,9 @@ public void cannotGetRowAsMap() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); List columns = List.of("accountId", "accountName", "migration_shard_id", "accountNumber", "missingColumn"); assignShardIdFn.getRowAsMap(mockRow, columns, "tableName"); @@ -166,7 +170,9 @@ public void testProcessElementInsertModForMultiShard() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("shard1"); assignShardIdFn.setSpannerAccessor(spannerAccessor); @@ -198,7 +204,9 @@ public void testProcessElementDeleteModForMultiShard() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("shard1"); assignShardIdFn.setSpannerAccessor(spannerAccessor); @@ -230,7 +238,9 @@ public void testProcessElementForSingleShard() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("test"); String keyStr = "tableName" + "_" + record.getMod().getKeysJson() + "_" + "test"; @@ -256,7 +266,9 @@ public void testGetShardIdFetcherImplWithIncorrectCustomJarPath() throws Excepti customJarPath, shardingCustomClassName, "", - 10000L); + 10000L, + "mysql", + 1000L); assignShardIdFn.setShardIdFetcher( ShardingLogicImplFetcher.getShardingLogicImpl( customJarPath, shardingCustomClassName, "", getSchemaObject(), "skip")); @@ -310,7 +322,9 @@ public void testProcessElementDeleteAllDatatypes() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("shard1"); assignShardIdFn.setSpannerAccessor(spannerAccessor); @@ -344,7 +358,9 @@ public void testProcessElementInsertAllDatatypes() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("shard1"); assignShardIdFn.setSpannerAccessor(spannerAccessor); @@ -378,7 +394,9 @@ public void testSkippedShardForTableNotInSchema() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); String keyStr = "tableName" + "_" + record.getMod().getKeysJson() + "_" + "skip"; Long key = keyStr.hashCode() % 10000L; record.setShard("skip"); @@ -405,7 +423,9 @@ public void testNoShardForIncorrectShardColumn() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); String keyStr = "tableName" + "_" + record.getMod().getKeysJson() + "_" + "skip"; Long key = keyStr.hashCode() % 10000L; ShardIdFetcherImpl shardIdFetcher = @@ -431,7 +451,9 @@ public void testNoShardForIncorrectSpToOid() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); String keyStr = "tableName" + "_" + record.getMod().getKeysJson() + "_" + "skip"; Long key = keyStr.hashCode() % 10000L; ShardIdFetcherImpl shardIdFetcher = @@ -457,7 +479,9 @@ public void testNoShardForIncorrectSpSchema() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); String keyStr = "tableName" + "_" + record.getMod().getKeysJson() + "_" + "skip"; Long key = keyStr.hashCode() % 10000L; ShardIdFetcherImpl shardIdFetcher = @@ -482,7 +506,9 @@ public void testInvalidShard() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("shard1"); assignShardIdFn.setSpannerAccessor(spannerAccessor); @@ -519,7 +545,9 @@ public void testProcessElementDeleteNoSpannerRow() throws Exception { "", "", "", - 10000L); + 10000L, + "mysql", + 1000L); record.setShard("shard1"); assignShardIdFn.setSpannerAccessor(spannerAccessor); diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraAllDatatypeSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraAllDatatypeSession.json new file mode 100644 index 0000000000..7e235faf0d --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraAllDatatypeSession.json @@ -0,0 +1,1149 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "test", + "Notes": null, + "Tags": null, + "SpSchema": { + "sample_table": { + "Name": "sample_table", + "ColIds": [ + "id", + "varchar_column", + "tinyint_column", + "text_column", + "date_column", + "smallint_column", + "mediumint_column", + "bigint_column", + "float_column", + "double_column", + "decimal_column", + "datetime_column", + "timestamp_column", + "time_column", + "year_column", + "char_column", + "tinyblob_column", + "tinytext_column", + "blob_column", + "mediumblob_column", + "mediumtext_column", + "longblob_column", + "longtext_column", + "enum_column", + "set_column", + "bool_column", + "binary_column", + "varbinary_column" + ], + "ColDefs": { + "bigint_column": { + "Name": "bigint_column", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: bigint_column bigint", + "Id": "c9" + }, + "binary_column": { + "Name": "binary_column", + "T": { + "Name": "BYTES", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: binary_column binary(20)", + "Id": "c25" + }, + "blob_column": { + "Name": "blob_column", + "T": { + "Name": "BYTES", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: blob_column blob(65535)", + "Id": "c11" + }, + "bool_column": { + "Name": "bool_column", + "T": { + "Name": "BOOL", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: bool_column tinyint(1)", + "Id": "c19" + }, + "char_column": { + "Name": "char_column", + "T": { + "Name": "STRING", + "Len": 10, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: char_column char(10)", + "Id": "c10" + }, + "date_column": { + "Name": "date_column", + "T": { + "Name": "DATE", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: date_column date", + "Id": "c13" + }, + "datetime_column": { + "Name": "datetime_column", + "T": { + "Name": "TIMESTAMP", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: datetime_column datetime", + "Id": "c15" + }, + "decimal_column": { + "Name": "decimal_column", + "T": { + "Name": "NUMERIC", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: decimal_column decimal(10,2)", + "Id": "c28" + }, + "double_column": { + "Name": "double_column", + "T": { + "Name": "FLOAT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: double_column double", + "Id": "c17" + }, + "enum_column": { + "Name": "enum_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: enum_column enum(1)", + "Id": "c24" + }, + "float_column": { + "Name": "float_column", + "T": { + "Name": "FLOAT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: float_column float", + "Id": "c14" + }, + "id": { + "Name": "id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: id int", + "Id": "c2" + }, + "longblob_column": { + "Name": "longblob_column", + "T": { + "Name": "BYTES", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: longblob_column longblob(4294967295)", + "Id": "c23" + }, + "longtext_column": { + "Name": "longtext_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: longtext_column longtext(4294967295)", + "Id": "c12" + }, + "mediumblob_column": { + "Name": "mediumblob_column", + "T": { + "Name": "BYTES", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: mediumblob_column mediumblob(16777215)", + "Id": "c18" + }, + "mediumint_column": { + "Name": "mediumint_column", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: mediumint_column mediumint", + "Id": "c8" + }, + "mediumtext_column": { + "Name": "mediumtext_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: mediumtext_column mediumtext(16777215)", + "Id": "c22" + }, + "set_column": { + "Name": "set_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": true + }, + "NotNull": false, + "Comment": "From: set_column set[]", + "Id": "c5" + }, + "smallint_column": { + "Name": "smallint_column", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: smallint_column smallint", + "Id": "c3" + }, + "text_column": { + "Name": "text_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: text_column text(65535)", + "Id": "c27" + }, + "time_column": { + "Name": "time_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: time_column time", + "Id": "c29" + }, + "timestamp_column": { + "Name": "timestamp_column", + "T": { + "Name": "TIMESTAMP", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: timestamp_column timestamp", + "Id": "c16" + }, + "tinyblob_column": { + "Name": "tinyblob_column", + "T": { + "Name": "BYTES", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: tinyblob_column tinyblob(255)", + "Id": "c4" + }, + "tinyint_column": { + "Name": "tinyint_column", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: tinyint_column tinyint(4)", + "Id": "c26" + }, + "tinytext_column": { + "Name": "tinytext_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: tinytext_column tinytext(255)", + "Id": "c7" + }, + "varbinary_column": { + "Name": "varbinary_column", + "T": { + "Name": "BYTES", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: varbinary_column varbinary(20)", + "Id": "c20" + }, + "varchar_column": { + "Name": "varchar_column", + "T": { + "Name": "STRING", + "Len": 20, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: varchar_column varchar(20)", + "Id": "c21" + }, + "year_column": { + "Name": "year_column", + "T": { + "Name": "STRING", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: year_column year", + "Id": "c6" + } + }, + "PrimaryKeys": [ + { + "ColId": "id", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Parent": "", + "Comment": "Spanner schema for source table sample_table", + "Id": "t1" + } + }, + "SyntheticPKeys": {}, + "SrcSchema": { + "sample_table": { + "Name": "sample_table", + "Schema": "test", + "ColIds": [ + "id", + "varchar_column", + "tinyint_column", + "text_column", + "date_column", + "smallint_column", + "mediumint_column", + "bigint_column", + "float_column", + "double_column", + "decimal_column", + "datetime_column", + "timestamp_column", + "time_column", + "year_column", + "char_column", + "tinyblob_column", + "tinytext_column", + "blob_column", + "mediumblob_column", + "mediumtext_column", + "longblob_column", + "longtext_column", + "enum_column", + "set_column", + "bool_column", + "binary_column", + "varbinary_column" + ], + "ColDefs": { + "bigint_column": { + "Name": "bigint_column", + "Type": { + "Name": "bigint", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c9" + }, + "binary_column": { + "Name": "binary_column", + "Type": { + "Name": "binary", + "Mods": [ + 20 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c25" + }, + "blob_column": { + "Name": "blob_column", + "Type": { + "Name": "blob", + "Mods": [ + 65535 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "bool_column": { + "Name": "bool_column", + "Type": { + "Name": "tinyint", + "Mods": [ + 1 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "char_column": { + "Name": "char_column", + "Type": { + "Name": "varchar", + "Mods": [ + 10 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "date_column": { + "Name": "date_column", + "Type": { + "Name": "timestamp", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "datetime_column": { + "Name": "datetime_column", + "Type": { + "Name": "timestamp", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "decimal_column": { + "Name": "decimal_column", + "Type": { + "Name": "float", + "Mods": [ + 10, + 2 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c28" + }, + "double_column": { + "Name": "double_column", + "Type": { + "Name": "float", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + }, + "enum_column": { + "Name": "enum_column", + "Type": { + "Name": "enum", + "Mods": [ + 1 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c24" + }, + "float_column": { + "Name": "float_column", + "Type": { + "Name": "float", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "id": { + "Name": "id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c2" + }, + "longblob_column": { + "Name": "longblob_column", + "Type": { + "Name": "blob", + "Mods": [ + 4294967295 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c23" + }, + "longtext_column": { + "Name": "longtext_column", + "Type": { + "Name": "varchar", + "Mods": [ + 4294967295 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + }, + "mediumblob_column": { + "Name": "mediumblob_column", + "Type": { + "Name": "blob", + "Mods": [ + 16777215 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "mediumint_column": { + "Name": "mediumint_column", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c8" + }, + "mediumtext_column": { + "Name": "mediumtext_column", + "Type": { + "Name": "varchar", + "Mods": [ + 16777215 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c22" + }, + "set_column": { + "Name": "set_column", + "Type": { + "Name": "set", + "Mods": null, + "ArrayBounds": [ + -1 + ] + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "smallint_column": { + "Name": "smallint_column", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c3" + }, + "text_column": { + "Name": "text_column", + "Type": { + "Name": "varchar", + "Mods": [ + 65535 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c27" + }, + "time_column": { + "Name": "time_column", + "Type": { + "Name": "time", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c29" + }, + "timestamp_column": { + "Name": "timestamp_column", + "Type": { + "Name": "timestamp", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "tinyblob_column": { + "Name": "tinyblob_column", + "Type": { + "Name": "blob", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c4" + }, + "tinyint_column": { + "Name": "tinyint_column", + "Type": { + "Name": "tinyint", + "Mods": [ + 4 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c26" + }, + "tinytext_column": { + "Name": "tinytext_column", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + }, + "varbinary_column": { + "Name": "varbinary_column", + "Type": { + "Name": "varbinary", + "Mods": [ + 20 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "varchar_column": { + "Name": "varchar_column", + "Type": { + "Name": "varchar", + "Mods": [ + 20 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + }, + "year_column": { + "Name": "year_column", + "Type": { + "Name": "year", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + } + }, + "PrimaryKeys": [ + { + "ColId": "id", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t1" + } + }, + "Issues": { + "sample_table": { + "bigint_column": [ + 0 + ], + "binary_column": [ + 0 + ], + "blob_column": [ + 0 + ], + "bool_column": [ + 0 + ], + "char_column": [ + 0 + ], + "date_column": [ + 0 + ], + "datetime_column": [ + 12, + 0 + ], + "decimal_column": [ + 0 + ], + "double_column": [ + 0 + ], + "enum_column": [ + 0 + ], + "float_column": [ + 13, + 0 + ], + "id": [ + 13 + ], + "longblob_column": [ + 0 + ], + "longtext_column": [ + 0 + ], + "mediumblob_column": [ + 0 + ], + "mediumint_column": [ + 13, + 0 + ], + "mediumtext_column": [ + 0 + ], + "set_column": [ + 0 + ], + "smallint_column": [ + 13, + 0 + ], + "text_column": [ + 0 + ], + "time_column": [ + 14, + 0 + ], + "timestamp_column": [ + 0 + ], + "tinyblob_column": [ + 0 + ], + "tinyint_column": [ + 13, + 0 + ], + "tinytext_column": [ + 0 + ], + "varbinary_column": [ + 0 + ], + "varchar_column": [ + 0 + ], + "year_column": [ + 14, + 0 + ] + } + }, + "ToSpanner": { + "sample_table": { + "Name": "sample_table", + "Cols": { + "bigint_column": "bigint_column", + "binary_column": "binary_column", + "blob_column": "blob_column", + "bool_column": "bool_column", + "char_column": "char_column", + "date_column": "date_column", + "datetime_column": "datetime_column", + "decimal_column": "decimal_column", + "double_column": "double_column", + "enum_column": "enum_column", + "float_column": "float_column", + "id": "id", + "longblob_column": "longblob_column", + "longtext_column": "longtext_column", + "mediumblob_column": "mediumblob_column", + "mediumint_column": "mediumint_column", + "mediumtext_column": "mediumtext_column", + "set_column": "set_column", + "smallint_column": "smallint_column", + "text_column": "text_column", + "time_column": "time_column", + "timestamp_column": "timestamp_column", + "tinyblob_column": "tinyblob_column", + "tinyint_column": "tinyint_column", + "tinytext_column": "tinytext_column", + "varbinary_column": "varbinary_column", + "varchar_column": "varchar_column", + "year_column": "year_column" + } + } + }, + "ToSource": { + "sample_table": { + "Name": "sample_table", + "Cols": { + "bigint_column": "bigint_column", + "binary_column": "binary_column", + "blob_column": "blob_column", + "bool_column": "bool_column", + "char_column": "char_column", + "date_column": "date_column", + "datetime_column": "datetime_column", + "decimal_column": "decimal_column", + "double_column": "double_column", + "enum_column": "enum_column", + "float_column": "float_column", + "id": "id", + "longblob_column": "longblob_column", + "longtext_column": "longtext_column", + "mediumblob_column": "mediumblob_column", + "mediumint_column": "mediumint_column", + "mediumtext_column": "mediumtext_column", + "set_column": "set_column", + "smallint_column": "smallint_column", + "text_column": "text_column", + "time_column": "time_column", + "timestamp_column": "timestamp_column", + "tinyblob_column": "tinyblob_column", + "tinyint_column": "tinyint_column", + "tinytext_column": "tinytext_column", + "varbinary_column": "varbinary_column", + "varchar_column": "varchar_column", + "year_column": "year_column" + } + } + }, + "UsedNames": { + "sample_table": true + }, + "Location": {}, + "Stats": { + "Rows": {}, + "GoodRows": {}, + "BadRows": {}, + "Statement": {}, + "Unexpected": {}, + "Reparsed": 0 + }, + "TimezoneOffset": "+00:00", + "TargetDb": "spanner", + "UniquePKey": {}, + "Audit": { + "ToSpannerFkIdx": { + "sample_table": { + "Name": "sample_table", + "ForeignKey": {}, + "Index": {} + } + }, + "ToSourceFkIdx": { + "sample_table": { + "Name": "sample_table", + "ForeignKey": {}, + "Index": {} + } + } + } +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraAllMatchSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraAllMatchSession.json new file mode 100644 index 0000000000..db99bff745 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraAllMatchSession.json @@ -0,0 +1,754 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "cassandra", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraBitSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraBitSession.json new file mode 100644 index 0000000000..a0b95058e5 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraBitSession.json @@ -0,0 +1,208 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "cassandra", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "BYTES", + "Len": 9223372036854775807, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + } + }, + "SyntheticPKeys": { + + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods":[7], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraCoulmnNameTypeMismatchSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraCoulmnNameTypeMismatchSession.json new file mode 100644 index 0000000000..493e76bbdd --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraCoulmnNameTypeMismatchSession.json @@ -0,0 +1,754 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "STRING", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraErrorSchemaSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraErrorSchemaSession.json new file mode 100644 index 0000000000..ad35d16844 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraErrorSchemaSession.json @@ -0,0 +1,754 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "junk" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c11", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": [], + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraMultiColmPKSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraMultiColmPKSession.json new file mode 100644 index 0000000000..5e3620176f --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraMultiColmPKSession.json @@ -0,0 +1,196 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "int", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "varchar", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "varchar", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + } + }, + "SyntheticPKeys": { + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c6", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json new file mode 100644 index 0000000000..1e6bd24a1e --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraPrimarykeyMismatchSession.json @@ -0,0 +1,754 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c6", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraQuotesSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraQuotesSession.json new file mode 100644 index 0000000000..3df6f1f727 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraQuotesSession.json @@ -0,0 +1,219 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "cassandra", + "DatabaseName": "test", + "Notes": null, + "Tags": null, + "SpSchema": { + "sample_table": { + "Name": "sample_table", + "ColIds": [ + "id", + "varchar_column", + "blob_column" + ], + "ColDefs": { + + "blob_column": { + "Name": "blob_column", + "T": { + "Name": "blob", + "Len": 9223372036854776000, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: blob_column blob(65535)", + "Id": "c11" + }, + "id": { + "Name": "id", + "T": { + "Name": "bigint", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: id int", + "Id": "c2" + }, + "varchar_column": { + "Name": "varchar_column", + "T": { + "Name": "varchar", + "Len": 20, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: varchar_column varchar(20)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "id", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Parent": "", + "Comment": "Spanner schema for source table sample_table", + "Id": "t1" + } + }, + "SyntheticPKeys": {}, + "SrcSchema": { + "sample_table": { + "Name": "sample_table", + "Schema": "test", + "ColIds": [ + "id", + "varchar_column", + "blob_column" + ], + "ColDefs": { + + "blob_column": { + "Name": "blob_column", + "Type": { + "Name": "blob", + "Mods": [ + 65535 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + + "id": { + "Name": "id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c2" + }, + + "varchar_column": { + "Name": "varchar_column", + "Type": { + "Name": "String", + "Mods": [ + 20 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": true, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + + } + }, + "PrimaryKeys": [ + { + "ColId": "id", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t1" + } + }, + "Issues": { + "sample_table": { + "blob_column": [ + 0 + ], + "id": [ + 13 + ], + "varchar_column": [ + 0 + ] + } + }, + "ToSpanner": { + "sample_table": { + "Name": "sample_table", + "Cols": { + "blob_column": "blob_column", + "id": "id", + "varchar_column": "varchar_column" + } + } + }, + "ToSource": { + "sample_table": { + "Name": "sample_table", + "Cols": { + + "blob_column": "blob_column", + + "id": "id", + + "varchar_column": "varchar_column" + } + } + }, + "UsedNames": { + "sample_table": true + }, + "Location": {}, + "Stats": { + "Rows": {}, + "GoodRows": {}, + "BadRows": {}, + "Statement": {}, + "Unexpected": {}, + "Reparsed": 0 + }, + "TimezoneOffset": "+00:00", + "TargetDb": "spanner", + "UniquePKey": {}, + "Audit": { + "ToSpannerFkIdx": { + "sample_table": { + "Name": "sample_table", + "ForeignKey": {}, + "Index": {} + } + }, + "ToSourceFkIdx": { + "sample_table": { + "Name": "sample_table", + "ForeignKey": {}, + "Index": {} + } + } + } +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSourceColumnAbsentInSpannerSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSourceColumnAbsentInSpannerSession.json new file mode 100644 index 0000000000..b1ae435f7e --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSourceColumnAbsentInSpannerSession.json @@ -0,0 +1,775 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "cassandra", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7", + "c8" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + }, + "c8": { + "Name": "Age", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c8" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSourceNoPkSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSourceNoPkSession.json new file mode 100644 index 0000000000..1e7bd864aa --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSourceNoPkSession.json @@ -0,0 +1,748 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSpannerColumnAbsentInSourceSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSpannerColumnAbsentInSourceSession.json new file mode 100644 index 0000000000..c50c8b8700 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraSpannerColumnAbsentInSourceSession.json @@ -0,0 +1,766 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6", + "c7", + "c8" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + }, + "c8": { + "Name": "hb_shardId", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "HB shard id", + "Id": "c8" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraTableNameMismatchSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraTableNameMismatchSession.json new file mode 100644 index 0000000000..f0452beae6 --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraTableNameMismatchSession.json @@ -0,0 +1,754 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "leChanteur", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(1024)", + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6", + "c7" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "FirstName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + }, + "c7": { + "Name": "LastName", + "Type": { + "Name": "String", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c7" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file diff --git a/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraTimeZoneSession.json b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraTimeZoneSession.json new file mode 100644 index 0000000000..e319b4bfcc --- /dev/null +++ b/v2/spanner-to-sourcedb/src/test/resources/CassandraJson/cassandraTimeZoneSession.json @@ -0,0 +1,721 @@ +{ + "SessionName": "NewSession", + "EditorName": "", + "DatabaseType": "mysql", + "DatabaseName": "ui_demo", + "Dialect": "google_standard_sql", + "Notes": null, + "Tags": null, + "SpSchema": { + "t1": { + "Name": "Singers", + "ColIds": [ + "c5", + "c6" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: SingerId int", + "Id": "c5" + }, + "c6": { + "Name": "Bday", + "T": { + "Name": "TIMESTAMP", + "Len": 1024, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(1024)", + "Id": "c6" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "TableId": "t1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "ParentId": "", + "Comment": "Spanner schema for source table Singers", + "Id": "t1" + }, + "t2": { + "Name": "contact", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Id int", + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Customer_Info varchar(50)", + "Id": "c20" + }, + "c21": { + "Name": "Type", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Type varchar(50)", + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "Id": "f8" + } + ], + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table contact", + "Id": "t2" + }, + "t3": { + "Name": "customer", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: ID int", + "Id": "c10" + }, + "c11": { + "Name": "Name", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: Name varchar(50)", + "Id": "c11" + }, + "c12": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": true, + "Comment": "From: City varchar(50)", + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table customer", + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17", + "c22" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "T": { + "Name": "INT64", + "Len": 0, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: PersonID int", + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: LastName varchar(255)", + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: FirstName varchar(255)", + "Id": "c15" + }, + "c16": { + "Name": "Address", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: Address varchar(255)", + "Id": "c16" + }, + "c17": { + "Name": "City", + "T": { + "Name": "STRING", + "Len": 255, + "IsArray": false + }, + "NotNull": false, + "Comment": "From: City varchar(255)", + "Id": "c17" + }, + "c22": { + "Name": "synth_id", + "T": { + "Name": "STRING", + "Len": 50, + "IsArray": false + }, + "NotNull": false, + "Comment": "", + "Id": "c22" + } + }, + "PrimaryKeys": [ + { + "ColId": "c22", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "ParentId": "", + "Comment": "Spanner schema for source table Persons", + "Id": "t4" + } + }, + "SyntheticPKeys": { + "t4": { + "ColId": "c22", + "Sequence": 0 + } + }, + "SrcSchema": { + "t1": { + "Name": "Singers", + "Schema": "ui_demo", + "ColIds": [ + "c5", + "c6" + ], + "ColDefs": { + "c5": { + "Name": "SingerId", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c5" + }, + "c6": { + "Name": "Bday", + "Type": { + "Name": "timestamp", + "Mods": [ + 1024 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c6" + } + }, + "PrimaryKeys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": [ + { + "Name": "ind1", + "Unique": false, + "Keys": [ + { + "ColId": "c5", + "Desc": false, + "Order": 1 + } + ], + "Id": "i9", + "StoredColumnIds": null + } + ], + "Id": "t1" + }, + "t2": { + "Name": "contact", + "Schema": "ui_demo", + "ColIds": [ + "c18", + "c19", + "c20", + "c21" + ], + "ColDefs": { + "c18": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c18" + }, + "c19": { + "Name": "Customer_Id", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c19" + }, + "c20": { + "Name": "Customer_Info", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c20" + }, + "c21": { + "Name": "Type", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c21" + } + }, + "PrimaryKeys": [ + { + "ColId": "c18", + "Desc": false, + "Order": 1 + }, + { + "ColId": "c19", + "Desc": false, + "Order": 2 + } + ], + "ForeignKeys": [ + { + "Name": "contact_ibfk_1", + "ColIds": [ + "c18" + ], + "ReferTableId": "t3", + "ReferColumnIds": [ + "c10" + ], + "OnDelete": "", + "OnUpdate": "", + "Id": "f8" + } + ], + "Indexes": null, + "Id": "t2" + }, + "t3": { + "Name": "customer", + "Schema": "ui_demo", + "ColIds": [ + "c10", + "c11", + "c12" + ], + "ColDefs": { + "c10": { + "Name": "ID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c10" + }, + "c11": { + "Name": "Name", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c11" + }, + "c12": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 50 + ], + "ArrayBounds": null + }, + "NotNull": true, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c12" + } + }, + "PrimaryKeys": [ + { + "ColId": "c10", + "Desc": false, + "Order": 1 + } + ], + "ForeignKeys": null, + "Indexes": null, + "Id": "t3" + }, + "t4": { + "Name": "Persons", + "Schema": "ui_demo", + "ColIds": [ + "c13", + "c14", + "c15", + "c16", + "c17" + ], + "ColDefs": { + "c13": { + "Name": "PersonID", + "Type": { + "Name": "int", + "Mods": null, + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c13" + }, + "c14": { + "Name": "LastName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c14" + }, + "c15": { + "Name": "FirstName", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c15" + }, + "c16": { + "Name": "Address", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c16" + }, + "c17": { + "Name": "City", + "Type": { + "Name": "varchar", + "Mods": [ + 255 + ], + "ArrayBounds": null + }, + "NotNull": false, + "Ignored": { + "Check": false, + "Identity": false, + "Default": false, + "Exclusion": false, + "ForeignKey": false, + "AutoIncrement": false + }, + "Id": "c17" + } + }, + "PrimaryKeys": null, + "ForeignKeys": null, + "Indexes": null, + "Id": "t4" + } + }, + "SchemaIssues": { + "t1": { + "c5": [ + 13, + 18 + ] + }, + "t2": { + "c18": [ + 13 + ], + "c19": [ + 13 + ] + }, + "t3": { + "c10": [ + 13 + ] + }, + "t4": { + "c13": [ + 13 + ] + } + }, + "Location": {}, + "TimezoneOffset": "+00:00", + "SpDialect": "google_standard_sql", + "UniquePKey": {}, + "Rules": [] +} \ No newline at end of file