diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 89047782a..fd28968be 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1897,6 +1897,231 @@ jobs: name: alloydb_16_integration_test_reports_${{ matrix.mode.label }} path: core/build/reports/tests/integrationTestJdbc + integration-test-for-tidb-6-5: + name: TiDB 6.5 integration test (${{ matrix.mode.label }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + mode: + - label: default + group_commit_enabled: false + - label: with_group_commit + group_commit_enabled: true + + steps: + - name: Install TiUP + run: | + curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh + echo "$HOME/.tiup/bin" >> $GITHUB_PATH + + - name: Start TiDB with TiUP Playground + timeout-minutes: 3 + run: | + tiup playground v6.5 --db 1 --pd 1 --kv 1 --tiflash 0 --without-monitor > tiup.log 2>&1 & + # Check if TiDB is running + while true; do + if mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT 1" > /dev/null 2>&1; then + echo "TiDB is ready" + break + fi + echo "Waiting for TiDB to be ready..." + sleep 5 + done + + - uses: actions/checkout@v5 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }}) to run integration test + uses: actions/setup-java@v5 + if: ${{ env.SET_UP_INT_TEST_RUNTIME_NON_ORACLE_JDK == 'true'}} + with: + java-version: ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} + distribution: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }} + + - name: Login to Oracle container registry + uses: docker/login-action@v3 + if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} + with: + registry: container-registry.oracle.com + username: ${{ secrets.OCR_USERNAME }} + password: ${{ secrets.OCR_TOKEN }} + + - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (oracle) to run the integration test + if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} + run: | + container_id=$(docker create "container-registry.oracle.com/java/jdk:${{ env.INT_TEST_JAVA_RUNTIME_VERSION }}") + docker cp -L "$container_id:/usr/java/default" /usr/lib/jvm/oracle-jdk && docker rm "$container_id" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Execute Gradle 'integrationTestJdbc' task + run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mysql://localhost:4000 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password= ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: tidb_6_5_integration_test_reports_${{ matrix.mode.label }} + path: core/build/reports/tests/integrationTestJdbc + + integration-test-for-tidb-v7-5: + name: TiDB 7.5 integration test (${{ matrix.mode.label }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + mode: + - label: default + group_commit_enabled: false + - label: with_group_commit + group_commit_enabled: true + + steps: + - name: Install TiUP + run: | + curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh + echo "$HOME/.tiup/bin" >> $GITHUB_PATH + + - name: Start TiDB with TiUP Playground + timeout-minutes: 3 + run: | + tiup playground v7.5 --db 1 --pd 1 --kv 1 --tiflash 0 --without-monitor > tiup.log 2>&1 & + # Check if TiDB is running + while true; do + if mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT 1" > /dev/null 2>&1; then + echo "TiDB is ready" + break + fi + echo "Waiting for TiDB to be ready..." + sleep 5 + done + + - uses: actions/checkout@v5 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }}) to run integration test + uses: actions/setup-java@v5 + if: ${{ env.SET_UP_INT_TEST_RUNTIME_NON_ORACLE_JDK == 'true'}} + with: + java-version: ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} + distribution: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }} + + - name: Login to Oracle container registry + uses: docker/login-action@v3 + if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} + with: + registry: container-registry.oracle.com + username: ${{ secrets.OCR_USERNAME }} + password: ${{ secrets.OCR_TOKEN }} + + - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (oracle) to run the integration test + if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} + run: | + container_id=$(docker create "container-registry.oracle.com/java/jdk:${{ env.INT_TEST_JAVA_RUNTIME_VERSION }}") + docker cp -L "$container_id:/usr/java/default" /usr/lib/jvm/oracle-jdk && docker rm "$container_id" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Execute Gradle 'integrationTestJdbc' task + run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mysql://localhost:4000 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password= ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: tidb_7_5_integration_test_reports_${{ matrix.mode.label }} + path: core/build/reports/tests/integrationTestJdbc + + integration-test-for-tidb-v8-5: + name: TiDB 8.5 integration test (${{ matrix.mode.label }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + mode: + - label: default + group_commit_enabled: false + - label: with_group_commit + group_commit_enabled: true + + steps: + - name: Install TiUP + run: | + curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh + echo "$HOME/.tiup/bin" >> $GITHUB_PATH + + - name: Start TiDB with TiUP Playground + timeout-minutes: 3 + run: | + tiup playground v8.5 --db 1 --pd 1 --kv 1 --tiflash 0 --without-monitor > tiup.log 2>&1 & + # Check if TiDB is running + while true; do + if mysql -h 127.0.0.1 -P 4000 -u root -e "SELECT 1" > /dev/null 2>&1; then + echo "TiDB is ready" + break + fi + echo "Waiting for TiDB to be ready..." + sleep 5 + done + + - uses: actions/checkout@v5 + + - name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }}) + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_VENDOR }} + + - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }}) to run integration test + uses: actions/setup-java@v5 + if: ${{ env.SET_UP_INT_TEST_RUNTIME_NON_ORACLE_JDK == 'true'}} + with: + java-version: ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} + distribution: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR }} + + - name: Login to Oracle container registry + uses: docker/login-action@v3 + if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} + with: + registry: container-registry.oracle.com + username: ${{ secrets.OCR_USERNAME }} + password: ${{ secrets.OCR_TOKEN }} + + - name: Set up JDK ${{ env.INT_TEST_JAVA_RUNTIME_VERSION }} (oracle) to run the integration test + if: ${{ env.INT_TEST_JAVA_RUNTIME_VENDOR == 'oracle' }} + run: | + container_id=$(docker create "container-registry.oracle.com/java/jdk:${{ env.INT_TEST_JAVA_RUNTIME_VERSION }}") + docker cp -L "$container_id:/usr/java/default" /usr/lib/jvm/oracle-jdk && docker rm "$container_id" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + + - name: Execute Gradle 'integrationTestJdbc' task + run: ./gradlew integrationTestJdbc -Dscalardb.jdbc.url=jdbc:mysql://localhost:4000 -Dscalardb.jdbc.username=root -Dscalardb.jdbc.password= ${{ matrix.mode.group_commit_enabled && env.INT_TEST_GRADLE_OPTIONS_FOR_GROUP_COMMIT || '' }} + + - name: Upload Gradle test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: tidb_8_5_integration_test_reports_${{ matrix.mode.label }} + path: core/build/reports/tests/integrationTestJdbc + integration-test-for-multi-storage: name: Multi-storage integration test (${{ matrix.mode.label }}) runs-on: ubuntu-latest diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java index 2045d48da..103acf6b2 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java @@ -73,11 +73,17 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isTidb() { + return JdbcTestUtils.isTidb(rdbEngine); + } + @SuppressWarnings("unused") private boolean isColumnTypeConversionToTextNotFullySupported() { return JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine) - || JdbcTestUtils.isSqlite(rdbEngine); + || JdbcTestUtils.isSqlite(rdbEngine) + || isTidb(); } @SuppressWarnings("unused") @@ -301,6 +307,95 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @EnabledIf("isTidb") + public void + alterColumnType_Tidb_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addColumn("c11", DataType.TIMESTAMPTZ) + .addColumn("c12", DataType.TIMESTAMP) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))) + .timestampTZValue("c11", Instant.now()) + .timestampValue("c12", LocalDateTime.now(ZoneOffset.UTC)); + transactionalInsert(insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .doesNotThrowAnyException(); + + TableMetadata expectedTableMetadata = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.TEXT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC) + .build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Test @Override @DisabledIf("isWideningColumnTypeConversionNotFullySupported") diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java index 9c983ad1c..1f64112ed 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java @@ -71,11 +71,17 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isTidb() { + return JdbcTestUtils.isTidb(rdbEngine); + } + @SuppressWarnings("unused") private boolean isColumnTypeConversionToTextNotFullySupported() { return JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine) - || JdbcTestUtils.isSqlite(rdbEngine); + || JdbcTestUtils.isSqlite(rdbEngine) + || isTidb(); } @SuppressWarnings("unused") @@ -371,6 +377,129 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @EnabledIf("isTidb") + public void + alterColumnType_Tidb_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException { + try (DistributedStorage storage = storageFactory.getStorage()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata currentTableMetadata = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC) + .build(); + + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))) + .timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)) + .timestampTZValue(getColumnName12(), Instant.now()); + + storage.put(put.build()); + storage.close(); + + // Act Assert + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) + .doesNotThrowAnyException(); + + TableMetadata expectedTableMetadata = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.TEXT) + .addColumn(getColumnName4(), DataType.TEXT) + .addColumn(getColumnName5(), DataType.TEXT) + .addColumn(getColumnName6(), DataType.TEXT) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.TEXT) + .addColumn(getColumnName10(), DataType.TEXT) + .addColumn(getColumnName11(), DataType.TEXT) + .addColumn(getColumnName12(), DataType.TEXT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC) + .build(); + assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) + .isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + @Test @Override @DisabledIf("isWideningColumnTypeConversionNotFullySupported") diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java index 24dbca8db..faab3ab1a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java @@ -88,9 +88,18 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isTidb() { + return testUtils.isTidb(); + } + @SuppressWarnings("unused") private boolean isColumnTypeConversionToTextNotFullySupported() { - return JdbcEnv.isDb2() || JdbcEnv.isSqlServer() || JdbcEnv.isOracle() || JdbcEnv.isSqlite(); + return JdbcEnv.isDb2() + || JdbcEnv.isSqlServer() + || JdbcEnv.isOracle() + || JdbcEnv.isSqlite() + || isTidb(); } @SuppressWarnings("unused") @@ -219,6 +228,54 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx } } + @Test + @EnabledIf("isTidb") + public void + alterColumnType_Tidb_AlterColumnTypeFromEachExistingDataTypeToText_ForImportedTable_ShouldAlterColumnTypesCorrectly() + throws Exception { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + // Act + TableMetadata metadata = testData.getTableMetadata(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + if (metadata.getColumnDataType(column).equals(DataType.BLOB)) { + // Conversion from BLOB to TEXT is not supported in TiDB + continue; + } + admin.alterColumnType(getNamespace(), testData.getTableName(), column, DataType.TEXT); + } + } + + // Assert + TableMetadata newMetadata = admin.getTableMetadata(getNamespace(), testData.getTableName()); + assertThat(newMetadata).isNotNull(); + for (String column : metadata.getColumnNames()) { + if (!metadata.getPartitionKeyNames().contains(column) + && !metadata.getClusteringKeyNames().contains(column)) { + if (metadata.getColumnDataType(column).equals(DataType.BLOB)) { + continue; + } + assertThat(newMetadata.getColumnDataType(column)).isEqualTo(DataType.TEXT); + } + } + } + } + } + @Test @Override @DisabledIf("isWideningColumnTypeConversionNotFullySupported") diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java index 1ac5ee152..92ec1e190 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java @@ -635,6 +635,13 @@ private List createExistingMysqlDatabaseWithAllDataTypes(String namesp UNSUPPORTED_DATA_TYPES_MYSQL.stream() .filter(type -> !type.equalsIgnoreCase("JSON")) .collect(Collectors.toList()))); + } else if (isTidb()) { + data.addAll( + prepareCreateNonImportableTableSql( + namespace, + UNSUPPORTED_DATA_TYPES_MYSQL.stream() + .filter(type -> !type.equalsIgnoreCase("GEOMETRY")) + .collect(Collectors.toList()))); } else { data.addAll(prepareCreateNonImportableTableSql(namespace, UNSUPPORTED_DATA_TYPES_MYSQL)); } @@ -880,7 +887,16 @@ private boolean isMariaDB() { String version = connection.getMetaData().getDatabaseProductVersion(); return version.contains("MariaDB"); } catch (SQLException e) { - throw new RuntimeException("Get database product version failed"); + throw new RuntimeException("Get database product version failed", e); + } + } + + boolean isTidb() { + try (Connection connection = dataSource.getConnection()) { + String version = connection.getMetaData().getDatabaseProductVersion(); + return version.contains("TiDB"); + } catch (SQLException e) { + throw new RuntimeException("Get database product version failed", e); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java index aef7b4c00..a9a5a205a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java @@ -72,11 +72,17 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isTidb() { + return JdbcTestUtils.isTidb(rdbEngine); + } + @SuppressWarnings("unused") private boolean isColumnTypeConversionToTextNotFullySupported() { return JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine) - || JdbcTestUtils.isSqlite(rdbEngine); + || JdbcTestUtils.isSqlite(rdbEngine) + || isTidb(); } @SuppressWarnings("unused") @@ -372,6 +378,129 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @EnabledIf("isTidb") + public void + alterColumnType_Tidb_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException { + try (DistributedStorage storage = storageFactory.getStorage()) { + // Arrange + Map options = getCreationOptions(); + TableMetadata currentTableMetadata = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.INT) + .addColumn(getColumnName4(), DataType.BIGINT) + .addColumn(getColumnName5(), DataType.FLOAT) + .addColumn(getColumnName6(), DataType.DOUBLE) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.DATE) + .addColumn(getColumnName10(), DataType.TIME) + .addColumn(getColumnName11(), DataType.TIMESTAMP) + .addColumn(getColumnName12(), DataType.TIMESTAMPTZ) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC) + .build(); + + admin.createTable(getNamespace1(), getTable4(), currentTableMetadata, options); + PutBuilder.Buildable put = + Put.newBuilder() + .namespace(getNamespace1()) + .table(getTable4()) + .partitionKey(Key.ofInt(getColumnName1(), 1)) + .clusteringKey(Key.ofInt(getColumnName2(), 2)) + .intValue(getColumnName3(), 1) + .bigIntValue(getColumnName4(), 2L) + .floatValue(getColumnName5(), 3.0f) + .doubleValue(getColumnName6(), 4.0d) + .textValue(getColumnName7(), "5") + .blobValue(getColumnName8(), "6".getBytes(StandardCharsets.UTF_8)) + .dateValue(getColumnName9(), LocalDate.now(ZoneId.of("UTC"))) + .timeValue(getColumnName10(), LocalTime.now(ZoneId.of("UTC"))) + .timestampValue(getColumnName11(), LocalDateTime.now(ZoneOffset.UTC)) + .timestampTZValue(getColumnName12(), Instant.now()); + + storage.put(put.build()); + storage.close(); + + // Act Assert + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName3(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName4(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName5(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName6(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName7(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName8(), DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName9(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName10(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName11(), DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode( + () -> + admin.alterColumnType( + getNamespace1(), getTable4(), getColumnName12(), DataType.TEXT)) + .doesNotThrowAnyException(); + + TableMetadata expectedTableMetadata = + TableMetadata.newBuilder() + .addColumn(getColumnName1(), DataType.INT) + .addColumn(getColumnName2(), DataType.INT) + .addColumn(getColumnName3(), DataType.TEXT) + .addColumn(getColumnName4(), DataType.TEXT) + .addColumn(getColumnName5(), DataType.TEXT) + .addColumn(getColumnName6(), DataType.TEXT) + .addColumn(getColumnName7(), DataType.TEXT) + .addColumn(getColumnName8(), DataType.BLOB) + .addColumn(getColumnName9(), DataType.TEXT) + .addColumn(getColumnName10(), DataType.TEXT) + .addColumn(getColumnName11(), DataType.TEXT) + .addColumn(getColumnName12(), DataType.TEXT) + .addPartitionKey(getColumnName1()) + .addClusteringKey(getColumnName2(), Scan.Ordering.Order.ASC) + .build(); + assertThat(admin.getTableMetadata(getNamespace1(), getTable4())) + .isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(getNamespace1(), getTable4(), true); + } + } + @Test @Override @DisabledIf("isWideningColumnTypeConversionNotFullySupported") diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcTestUtils.java index eda8d6843..e3d05afd7 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcTestUtils.java @@ -90,6 +90,10 @@ public static boolean isMariaDB(RdbEngineStrategy rdbEngine) { return rdbEngine instanceof RdbEngineMariaDB; } + public static boolean isTidb(RdbEngineStrategy rdbEngine) { + return rdbEngine instanceof RdbEngineTidb; + } + /** * Filters the data types based on the RDB engine and the excluded data types. * diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java index 84ff64b84..c8c2b0df1 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/SingleCrudOperationTransactionAdminIntegrationTestWithJdbcDatabase.java @@ -73,11 +73,17 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isTidb() { + return JdbcTestUtils.isTidb(rdbEngine); + } + @SuppressWarnings("unused") private boolean isColumnTypeConversionToTextNotFullySupported() { return JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine) - || JdbcTestUtils.isSqlite(rdbEngine); + || JdbcTestUtils.isSqlite(rdbEngine) + || isTidb(); } @SuppressWarnings("unused") @@ -301,6 +307,95 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @EnabledIf("isTidb") + public void + alterColumnType_TiDB_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addColumn("c11", DataType.TIMESTAMPTZ) + .addColumn("c12", DataType.TIMESTAMP) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))) + .timestampTZValue("c11", Instant.now()) + .timestampValue("c12", LocalDateTime.now(ZoneOffset.UTC)); + transactionalInsert(insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .doesNotThrowAnyException(); + + TableMetadata expectedTableMetadata = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.TEXT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC) + .build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Test @Override @DisabledIf("isWideningColumnTypeConversionNotFullySupported") diff --git a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java index b7bc6fe55..33c6b05c3 100644 --- a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java @@ -146,11 +146,17 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @SuppressWarnings("unused") + private boolean isTidb() { + return JdbcTestUtils.isTidb(rdbEngine); + } + @SuppressWarnings("unused") private boolean isColumnTypeConversionToTextNotFullySupported() { return JdbcTestUtils.isDb2(rdbEngine) || JdbcTestUtils.isOracle(rdbEngine) - || JdbcTestUtils.isSqlite(rdbEngine); + || JdbcTestUtils.isSqlite(rdbEngine) + || isTidb(); } @SuppressWarnings("unused") @@ -374,6 +380,95 @@ public void renameColumn_Db2_ForPrimaryOrIndexKeyColumn_ShouldThrowUnsupportedOp } } + @Test + @EnabledIf("isTidb") + public void + alterColumnType_Tidb_AlterColumnTypeFromEachExistingDataTypeToText_ShouldAlterColumnTypesCorrectlyIfSupported() + throws ExecutionException, TransactionException { + try { + // Arrange + Map options = getCreationOptions(); + TableMetadata.Builder currentTableMetadataBuilder = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.INT) + .addColumn("c4", DataType.BIGINT) + .addColumn("c5", DataType.FLOAT) + .addColumn("c6", DataType.DOUBLE) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.DATE) + .addColumn("c10", DataType.TIME) + .addColumn("c11", DataType.TIMESTAMPTZ) + .addColumn("c12", DataType.TIMESTAMP) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC); + TableMetadata currentTableMetadata = currentTableMetadataBuilder.build(); + admin.createTable(namespace1, TABLE4, currentTableMetadata, options); + InsertBuilder.Buildable insert = + Insert.newBuilder() + .namespace(namespace1) + .table(TABLE4) + .partitionKey(Key.ofInt("c1", 1)) + .clusteringKey(Key.ofInt("c2", 2)) + .intValue("c3", 1) + .bigIntValue("c4", 2L) + .floatValue("c5", 3.0f) + .doubleValue("c6", 4.0d) + .textValue("c7", "5") + .blobValue("c8", "6".getBytes(StandardCharsets.UTF_8)) + .dateValue("c9", LocalDate.now(ZoneId.of("UTC"))) + .timeValue("c10", LocalTime.now(ZoneId.of("UTC"))) + .timestampTZValue("c11", Instant.now()) + .timestampValue("c12", LocalDateTime.now(ZoneOffset.UTC)); + transactionalInsert(insert.build()); + + // Act Assert + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c3", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c4", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c5", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c6", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c7", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c8", DataType.TEXT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c9", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c10", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c11", DataType.TEXT)) + .doesNotThrowAnyException(); + assertThatCode(() -> admin.alterColumnType(namespace1, TABLE4, "c12", DataType.TEXT)) + .doesNotThrowAnyException(); + + TableMetadata expectedTableMetadata = + TableMetadata.newBuilder() + .addColumn("c1", DataType.INT) + .addColumn("c2", DataType.INT) + .addColumn("c3", DataType.TEXT) + .addColumn("c4", DataType.TEXT) + .addColumn("c5", DataType.TEXT) + .addColumn("c6", DataType.TEXT) + .addColumn("c7", DataType.TEXT) + .addColumn("c8", DataType.BLOB) + .addColumn("c9", DataType.TEXT) + .addColumn("c10", DataType.TEXT) + .addColumn("c11", DataType.TEXT) + .addColumn("c12", DataType.TEXT) + .addPartitionKey("c1") + .addClusteringKey("c2", Scan.Ordering.Order.ASC) + .build(); + assertThat(admin.getTableMetadata(namespace1, TABLE4)).isEqualTo(expectedTableMetadata); + } finally { + admin.dropTable(namespace1, TABLE4, true); + } + } + @Test @Override @DisabledIf("isWideningColumnTypeConversionNotFullySupported") diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index 5b3c48a5a..fdd11a3d6 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -819,6 +819,12 @@ public enum CoreError implements ScalarDbError { Category.USER_ERROR, "0243", "This batch result doesn't have a get result", "", ""), BATCH_RESULT_DOES_NOT_HAVE_SCAN_RESULT( Category.USER_ERROR, "0244", "This batch result doesn't have a scan result", "", ""), + JDBC_TIDB_UNSUPPORTED_COLUMN_TYPE_CONVERSION( + Category.USER_ERROR, + "0245", + "TiDB does not support column type conversion from %s to %s", + "", + ""), // // Errors for the concurrency error category @@ -1125,6 +1131,8 @@ public enum CoreError implements ScalarDbError { "Altering a column type failed. Table: %s; Column: %s; New column type: %s", "", ""), + JDBC_MYSQL_GETTING_CONNECTION_METADATA_FAILED( + Category.INTERNAL_ERROR, "0063", "Getting the MySQL JDBC connection metadata failed", "", ""), // // Errors for the unknown transaction status error category diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineFactory.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineFactory.java index b8588d6e8..3718411b2 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineFactory.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineFactory.java @@ -1,6 +1,9 @@ package com.scalar.db.storage.jdbc; import com.scalar.db.common.CoreError; +import java.sql.Connection; +import java.sql.SQLException; +import org.apache.commons.dbcp2.BasicDataSource; /** Factory class of subclasses of {@link RdbEngineStrategy} */ public final class RdbEngineFactory { @@ -12,7 +15,7 @@ public static RdbEngineStrategy create(JdbcConfig config) { String jdbcUrl = config.getJdbcUrl(); if (jdbcUrl.startsWith("jdbc:mysql:")) { - return new RdbEngineMysql(config); + return createMysqlOrTidbEngine(config); } else if (jdbcUrl.startsWith("jdbc:postgresql:")) { return new RdbEnginePostgresql(); } else if (jdbcUrl.startsWith("jdbc:oracle:")) { @@ -32,4 +35,30 @@ public static RdbEngineStrategy create(JdbcConfig config) { CoreError.JDBC_RDB_ENGINE_NOT_SUPPORTED.buildMessage(jdbcUrl)); } } + + /** + * This creates a RdbEngine for MySQL or TiDB. Since TiDB uses the same connection string as + * MySQL, we can't determine if the storage is TiDB or MySQL by parsing the connection string, so + * we need to establish a connection and check the metadata to tell them apart. + * + * @param config the config + * @return a {@link RdbEngineMysql} or {@link RdbEngineTidb}. + */ + private static RdbEngineStrategy createMysqlOrTidbEngine(JdbcConfig config) { + RdbEngineMysql mysqlEngine = new RdbEngineMysql(config); + try (BasicDataSource dataSource = JdbcUtils.initDataSourceForAdmin(config, mysqlEngine); + Connection connection = dataSource.getConnection()) { + String version = connection.getMetaData().getDatabaseProductVersion(); + if (version.contains("TiDB")) { + return new RdbEngineTidb(config); + } else { + return mysqlEngine; + } + } catch (SQLException e) { + // We can't throw a checked exception here because it would break backward compatibility since + // the calling method is executed in constructor of JdbcAdmin or JdbcService + throw new RuntimeException( + CoreError.JDBC_MYSQL_GETTING_CONNECTION_METADATA_FAILED.buildMessage(e), e); + } + } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineTidb.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineTidb.java new file mode 100644 index 000000000..6e6680029 --- /dev/null +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineTidb.java @@ -0,0 +1,25 @@ +package com.scalar.db.storage.jdbc; + +import com.scalar.db.common.CoreError; +import com.scalar.db.io.DataType; + +/** + * This implements a RdbEngine for TiDB that extends MySQL one. TiDB is MySQL compatible and uses + * the same connection string, so special handling is needed to instantiate it, cf. {@link + * RdbEngineFactory#create(JdbcConfig)} + */ +public class RdbEngineTidb extends RdbEngineMysql { + + RdbEngineTidb(JdbcConfig config) { + super(config); + } + + @Override + public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { + if (from == DataType.BLOB && to == DataType.TEXT) { + throw new UnsupportedOperationException( + CoreError.JDBC_TIDB_UNSUPPORTED_COLUMN_TYPE_CONVERSION.buildMessage( + from.toString(), to.toString())); + } + } +}