diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..ecbb045 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,123 @@ +# Copyright © 2023 Cask Data, Inc. +# 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. + +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven +# Note: Any changes to this workflow would be used only after merging into develop +name: Build e2e tests + +on: + push: + branches: [ e2e ] + pull_request: + branches: [ e2e ] + types: [ opened, synchronize, reopened, labeled ] + workflow_dispatch: + +jobs: + build: + runs-on: k8s-runner-e2e + # We allow builds: + # 1) When triggered manually + # 2) When it's a merge into a branch + # 3) For PRs that are labeled as build and + # - It's a code change + # - A build label was just added + # A bit complex, but prevents builds when other labels are manipulated + if: > + github.event_name == 'workflow_dispatch' + || github.event_name == 'push' + || (contains(github.event.pull_request.labels.*.name, 'build') + && (github.event.action != 'labeled' || github.event.label.name == 'build') + ) + strategy: + fail-fast: false + + steps: + # Pinned 1.0.0 version + - uses: actions/checkout@v3 + with: + path: plugin + submodules: 'recursive' + ref: ${{ github.event.workflow_run.head_sha }} + + - uses: dorny/paths-filter@b2feaf19c27470162a626bd6fa8438ae5b263721 + if: github.event_name != 'workflow_dispatch' && github.event_name != 'push' + id: filter + with: + working-directory: plugin + filters: | + e2e-test: + - '**/e2e-test/**' + - name: Checkout e2e test repo + uses: actions/checkout@v3 + with: + repository: cdapio/cdap-e2e-tests + path: e2e + + - name: Cache + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ github.workflow }}-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven-${{ github.workflow }} + - name: Get Secrets from GCP Secret Manager + id: secrets + uses: 'google-github-actions/get-secretmanager-secrets@v0' + with: + secrets: |- + ORACLE_HOST:cdapio-github-builds/ORACLE_HOST + ORACLE_USERNAME:cdapio-github-builds/ORACLE_NORMAL_USERNAME + ORACLE_PASSWORD:cdapio-github-builds/ORACLE_NORMAL_PASSWORD + ORACLE_PORT:cdapio-github-builds/ORACLE_PORT + PROJECT_ID:cdapio-github-builds/PROJECT_ID_FOR_REPL + - name: Run required e2e tests + if: github.event_name != 'workflow_dispatch' && github.event_name != 'push' && steps.filter.outputs.e2e-test == 'false' + run: python3 e2e/src/main/scripts/run_e2e_test.py --testRunner TestRunnerRequired.java + env: + ORACLE_HOST: ${{ steps.secrets.outputs.ORACLE_HOST }} + ORACLE_USERNAME: ${{ steps.secrets.outputs.ORACLE_USERNAME }} + ORACLE_PASSWORD: ${{ steps.secrets.outputs.ORACLE_PASSWORD }} + ORACLE_PORT: ${{ steps.secrets.outputs.ORACLE_PORT }} + + + - name: Run all e2e tests + if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || steps.filter.outputs.e2e-test == 'true' + run: python3 e2e/src/main/scripts/run_e2e_test.py --testRunner TestRunner.java + env: + ORACLE_HOST: ${{ steps.secrets.outputs.ORACLE_HOST }} + ORACLE_USERNAME: ${{ steps.secrets.outputs.ORACLE_USERNAME }} + ORACLE_PASSWORD: ${{ steps.secrets.outputs.ORACLE_PASSWORD }} + ORACLE_PORT: ${{ steps.secrets.outputs.ORACLE_PORT }} + PROJECT_ID : ${{ steps.secrets.outputs.PROJECT_ID }} + + - name: Upload report + uses: actions/upload-artifact@v3 + if: always() + with: + name: Cucumber report + path: ./**/target/cucumber-reports + + - name: Upload debug files + uses: actions/upload-artifact@v3 + if: always() + with: + name: Debug files + path: ./**/target/e2e-debug + + - name: Upload files to GCS + uses: google-github-actions/upload-cloud-storage@v0 + if: always() + with: + path: ./plugin + destination: e2e-tests-cucumber-reports/${{ github.event.repository.name }}/${{ github.ref }} + glob: '**/target/cucumber-reports/**' \ No newline at end of file diff --git a/pom.xml b/pom.xml index d81813d..79a519b 100644 --- a/pom.xml +++ b/pom.xml @@ -153,7 +153,7 @@ org.junit.jupiter junit-jupiter-engine - 5.4.0 + 5.9.1 test @@ -385,6 +385,155 @@ + + e2e-tests + + src/e2e-test/java + TestRunner.java + + + + + src/e2e-test/resources + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.0.0 + + + org.apache.maven.surefire + surefire-junit47 + 3.0.0 + + + + + ${TEST_RUNNER} + + + classes + 2 + 2 + true + + + + ${GOOGLE_APPLICATION_CREDENTIALS} + + + ${SERVICE_ACCOUNT_TYPE} + + + ${SERVICE_ACCOUNT_FILE_PATH} + + + ${SERVICE_ACCOUNT_JSON} + + + + + + + integration-test + + + + + + + net.masterthought + maven-cucumber-reporting + 5.5.0 + + + + execution + verify + + generate + + + Cucumber Reports + target/cucumber-reports/advanced-reports + 1 + false + ${project.build.directory}/cucumber-reports + + **/*.json + + ${project.build.directory}/cucumber-reports + true + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-test-source + generate-test-sources + + add-test-source + + + + ${project.basedir}/${testSourceLocation} + + + + + + + + + + + + com.google.guava + guava + 31.1-jre + + + + + + + com.oracle.database.jdbc + ojdbc8 + 21.1.0.0 + test + + + io.cdap.tests.e2e + cdap-e2e-framework + 0.3.0-SNAPSHOT + test + + + ch.qos.logback + logback-classic + 1.2.8 + runtime + + + + + diff --git a/src/e2e-test/features/Pipeline.feature b/src/e2e-test/features/Pipeline.feature new file mode 100644 index 0000000..f2c8547 --- /dev/null +++ b/src/e2e-test/features/Pipeline.feature @@ -0,0 +1,51 @@ +# +# Copyright © 2023 Cask Data, Inc. +# +# 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. +# + +@Oracle +Feature: Oracle - Verify Oracle source data transfer to Big Query + @ENV_VARIABLES + Scenario: To verify replication of snapshot and cdc data from Oracle to Big Query successfully with Sanity test + Given Open DataFusion Project with replication to configure pipeline + When Enter input plugin property: "name" with value: "pipelineName" + And Click on the Next button + And Select Oracle as Source + Then Replace input plugin property: "host" with value: "host" for Credentials and Authorization related fields + Then Replace input plugin property: "port" with value: "port" for Credentials and Authorization related fields + Then Click plugin property: "region" + Then Click plugin property: "regionOption" + Then Replace input plugin property: "user" with value: "username" for Credentials and Authorization related fields + Then Replace input plugin property: "password" with value: "password" for Credentials and Authorization related fields + Then Replace input plugin property: "sid" with value: "dataset" for Credentials and Authorization related fields + Then Click on the Next button + Then Replace input plugin property: "loadInterval" with value: "loadInterval" + Then Click on the Next button + Then Validate Source table is available and select it + And Click on the Next button + And Click on the Next button + And Click on the Next button + Then Deploy the replication pipeline + And Run the replication Pipeline + Then Open the logs + And Wait till pipeline is in running state and check if no errors occurred + Then Verify expected Oracle records in target BigQuery table + And Insert a record in the source table and wait for replication + Then Verify expected Oracle records in target BigQuery table + And Delete a record in the source table and wait for replication + Then Verify expected Oracle records in target BigQuery table + And Update a record in the source table and wait for replication + Then Verify expected Oracle records in target BigQuery table + And Capture raw logs + Then Close the pipeline logs and stop the pipeline \ No newline at end of file diff --git a/src/e2e-test/java/io.cdap.plugin/actions/ReplicationActions.java b/src/e2e-test/java/io.cdap.plugin/actions/ReplicationActions.java new file mode 100644 index 0000000..e51c9be --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/actions/ReplicationActions.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.actions; + +import io.cdap.e2e.pages.actions.CdfPipelineRunAction; +import io.cdap.e2e.pages.locators.CdfPipelineRunLocators; +import io.cdap.e2e.utils.AssertionHelper; +import io.cdap.e2e.utils.ConstantsUtil; +import io.cdap.e2e.utils.ElementHelper; +import io.cdap.e2e.utils.PageHelper; +import io.cdap.e2e.utils.PluginPropertyUtils; +import io.cdap.e2e.utils.SeleniumDriver; +import io.cdap.e2e.utils.SeleniumHelper; +import io.cdap.e2e.utils.WaitHelper; +import io.cdap.plugin.locators.ReplicationLocators; +import io.cdap.plugin.utils.BigQuery; +import io.cdap.plugin.utils.OracleClient; +import io.cdap.plugin.utils.ValidationHelper; +import org.apache.commons.lang.StringUtils; +import org.junit.Assert; +import stepsdesign.BeforeActions; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Replication oracle Actions. + */ +public class ReplicationActions { + private static String parentWindow = StringUtils.EMPTY; + private static final String projectId = PluginPropertyUtils.pluginProp("projectId"); + private static final String database = PluginPropertyUtils.pluginProp("dataset"); + public static String tableName = PluginPropertyUtils.pluginProp("sourceTable"); + public static String schemaName = PluginPropertyUtils.pluginProp("schema"); + public static String datatypeValues = PluginPropertyUtils.pluginProp("datatypeValuesForInsertOperation"); + public static String deleteCondition = PluginPropertyUtils.pluginProp("deleteRowCondition"); + public static String updateCondition = PluginPropertyUtils.pluginProp("updateRowCondition"); + public static String updatedValue = PluginPropertyUtils.pluginProp("updatedRow"); + + static { + SeleniumHelper.getPropertiesLocators(ReplicationLocators.class); + } + public static void clickNextButton() throws InterruptedException { + TimeUnit time = TimeUnit.SECONDS; + time.sleep(1); + ElementHelper.clickOnElement(ReplicationLocators.next); + } + + public static void clickOnOraclePlugin() { + ElementHelper.clickOnElement(ReplicationLocators.oraclePlugin); + } + + public static void selectTable() { + String table = schemaName + "." + tableName; + WaitHelper.waitForElementToBeDisplayed(ReplicationLocators.selectTable(table), 300); + AssertionHelper.verifyElementDisplayed(ReplicationLocators.selectTable(table)); + ElementHelper.clickOnElement(ReplicationLocators.selectTable(table)); + } + + public static void deployPipeline() { + ElementHelper.clickOnElement(ReplicationLocators.deployPipeline); + } + + public static void startPipeline() { + ElementHelper.clickIfDisplayed(ReplicationLocators.start, ConstantsUtil.DEFAULT_TIMEOUT_SECONDS); + } + + public static void runThePipeline() { + startPipeline(); + WaitHelper.waitForElementToBeDisplayed(ReplicationLocators.running); + } + + public static void openAdvanceLogs() { + ReplicationLocators.logs.click(); + parentWindow = SeleniumDriver.getDriver().getWindowHandle(); + ArrayList tabs = new ArrayList(SeleniumDriver.getDriver().getWindowHandles()); + SeleniumDriver.getDriver().switchTo().window(tabs.get(tabs.indexOf(parentWindow) + 1)); + ReplicationLocators.advancedLogs.click(); + } + + public static void captureRawLog() { + //Capturing raw logs. + try { + String rawLogs = getRawLogs(); + String logsSeparatorMessage = ConstantsUtil.LOGS_SEPARATOR_MESSAGE + .replace("MESSAGE", "DEPLOYED PIPELINE RUNTIME LOGS"); + BeforeActions.scenario.write(rawLogs); + CdfPipelineRunAction.writeRawLogsToFile(BeforeActions.file, logsSeparatorMessage, rawLogs); + } catch (Exception e) { + BeforeActions.scenario.write("Exception in capturing logs : " + e); + } + } + + public static String getRawLogs() { + CdfPipelineRunAction.viewRawLogs(); + ArrayList tabs = new ArrayList(SeleniumDriver.getDriver().getWindowHandles()); + PageHelper.switchToWindow(tabs.indexOf(parentWindow) + 2); + String logs = CdfPipelineRunLocators.logsTextbox.getText(); + Assert.assertNotNull(logs); + PageHelper.closeCurrentWindow(); + return logs; + } + + public static void waitTillPipelineIsRunningAndCheckForErrors() throws InterruptedException { + //wait for datastream to startup + int defaultTimeout = Integer.parseInt(PluginPropertyUtils.pluginProp("pipeline-initialization")); + TimeUnit time = TimeUnit.SECONDS; + time.sleep(defaultTimeout); + BigQuery.waitForFlush(); + // Checking if an error message is displayed. + Assert.assertFalse(ElementHelper.isElementDisplayed(ReplicationLocators.error)); + } + + public static void closeTheLogsAndClickOnStopButton() { + SeleniumDriver.getDriver().switchTo().window(parentWindow); + //Stopping the pipeline + ElementHelper.clickOnElement(ReplicationLocators.stop); + SeleniumDriver.getDriver().navigate().refresh(); + WaitHelper.waitForElementToBeDisplayed(ReplicationLocators.stopped); + } + public static void verifyTargetBigQueryRecordMatchesExpectedOracleRecord() + throws IOException, InterruptedException, SQLException, ClassNotFoundException { + // Checking if an error message is displayed. + Assert.assertFalse(ElementHelper.isElementDisplayed(ReplicationLocators.error)); + + List> sourceOracleRecords = OracleClient.getOracleRecordsAsMap(tableName, schemaName); + List> targetBigQueryRecords = + ValidationHelper.getBigQueryRecordsAsMap(projectId, database, tableName); + ValidationHelper.validateRecords(sourceOracleRecords, targetBigQueryRecords); + } + + public static void insertRecordAndWait() + throws InterruptedException, SQLException, ClassNotFoundException { + OracleClient.insertRow(tableName, schemaName, datatypeValues); + OracleClient.forceFlushCDC(); + BigQuery.waitForFlush(); + } + + public static void deleteRecordAndWait() throws SQLException, ClassNotFoundException, InterruptedException { + OracleClient.deleteRow(tableName, schemaName, deleteCondition); + OracleClient.forceFlushCDC(); + BigQuery.waitForFlush(); + } + + public static void updateRecordAndWait() throws SQLException, ClassNotFoundException, InterruptedException { + OracleClient.updateRow(tableName, schemaName, updateCondition, updatedValue); + OracleClient.forceFlushCDC(); + BigQuery.waitForFlush(); + } +} diff --git a/src/e2e-test/java/io.cdap.plugin/actions/package-info.java b/src/e2e-test/java/io.cdap.plugin/actions/package-info.java new file mode 100644 index 0000000..0bf092a --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/actions/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * 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. + */ + +/** + * contains Actions methods. + */ +package io.cdap.plugin.actions; diff --git a/src/e2e-test/java/io.cdap.plugin/hooks/TestSetUpHooks.java b/src/e2e-test/java/io.cdap.plugin/hooks/TestSetUpHooks.java new file mode 100644 index 0000000..926a7dd --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/hooks/TestSetUpHooks.java @@ -0,0 +1,99 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.hooks; + +import io.cdap.e2e.utils.PluginPropertyUtils; +import io.cdap.plugin.utils.BigQuery; +import io.cdap.plugin.utils.OracleClient; +import io.cucumber.java.After; +import io.cucumber.java.Before; +import stepsdesign.BeforeActions; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/** + * Oracle test hooks. + */ +public class TestSetUpHooks { + public static List> sourceOracleRecords = new ArrayList<>(); + public static String tableName = PluginPropertyUtils.pluginProp("sourceTable"); + public static String schemaName = PluginPropertyUtils.pluginProp("schema"); + public static String datatypeColumns = PluginPropertyUtils.pluginProp("datatypeColumns"); + public static String row1 = PluginPropertyUtils.pluginProp("datatypeValuesRow1"); + public static String row2 = PluginPropertyUtils.pluginProp("datatypeValuesRow2"); + + @Before(order = 1, value = "@ENV_VARIABLES") + public static void overridePropertiesFromEnvVarsIfProvided() { + String projectId = System.getenv("PROJECT_ID"); + if (projectId != null && !projectId.isEmpty()) { + PluginPropertyUtils.addPluginProp("projectId", projectId); + } + String username = System.getenv("ORACLE_USERNAME"); + if (username != null && !username.isEmpty()) { + PluginPropertyUtils.addPluginProp("username", username); + } + String password = System.getenv("ORACLE_PASSWORD"); + if (password != null && !password.isEmpty()) { + PluginPropertyUtils.addPluginProp("password", password); + } + String port = System.getenv("ORACLE_PORT"); + if (port != null && !port.isEmpty()) { + PluginPropertyUtils.addPluginProp("port", port); + } + String oracleHost = System.getenv("ORACLE_HOST"); + if (oracleHost != null && !oracleHost.isEmpty()) { + PluginPropertyUtils.addPluginProp("host", oracleHost); + } + String sourceTable = System.getenv("SOURCE_TABLE"); + if (sourceTable != null && !sourceTable.isEmpty()) { + PluginPropertyUtils.addPluginProp("sourceTable", sourceTable); + tableName = PluginPropertyUtils.pluginProp("sourceTable"); + } + } + + @Before(order = 2, value = "@ORACLE_SOURCE") + public static void createTable() throws SQLException, ClassNotFoundException { + OracleClient.createTable(tableName, schemaName, datatypeColumns); + } + + @Before(order = 3, value = "@ORACLE_SOURCE") + public static void insertRow() throws SQLException, ClassNotFoundException { + OracleClient.insertRow(tableName, schemaName, row1); + OracleClient.insertRow(tableName, schemaName, row2); + } + + @Before(order = 4, value = "@ORACLE_SOURCE") + public static void getOracleRecordsAsMap() throws SQLException, ClassNotFoundException { + sourceOracleRecords = OracleClient.getOracleRecordsAsMap(tableName, schemaName); + BeforeActions.scenario.write("Expected Oracle records : " + sourceOracleRecords); + } + + @After(order = 1, value = "@ORACLE_DELETE") + public static void dropTables() throws SQLException, ClassNotFoundException { + OracleClient.deleteTables(schemaName, tableName); + } + + @After(order = 1, value = "@BIGQUERY_DELETE") + public static void deleteTargetBQTable() throws IOException, InterruptedException { + BigQuery.deleteTable(tableName); + } +} diff --git a/src/e2e-test/java/io.cdap.plugin/hooks/package-info.java b/src/e2e-test/java/io.cdap.plugin/hooks/package-info.java new file mode 100644 index 0000000..46e9944 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/hooks/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023. + * + * 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. + */ +/** + * Represent Test Setup/Clean up hooks. + */ +package io.cdap.plugin.hooks; diff --git a/src/e2e-test/java/io.cdap.plugin/locators/ReplicationLocators.java b/src/e2e-test/java/io.cdap.plugin/locators/ReplicationLocators.java new file mode 100644 index 0000000..cce2e17 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/locators/ReplicationLocators.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.locators; + +import io.cdap.e2e.utils.SeleniumDriver; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.How; +/** + * Oracle Plugin Locators. + */ +public class ReplicationLocators { + @FindBy(how = How.XPATH, using = "//*[contains(text(),'Next')]") + public static WebElement next; + @FindBy(how = How.XPATH, using = "//div[contains(text(),'Oracle')]") + public static WebElement oraclePlugin; + public static WebElement selectTable(String tableName) { + return SeleniumDriver.getDriver().findElement(By.xpath("//div[contains(text(),'" + tableName + "')]" + + "/preceding-sibling::div/span")); + } + @FindBy(how = How.XPATH, using = "//span[contains(text(),'Deploy Replication Job')]") + public static WebElement deployPipeline; + @FindBy(how = How.XPATH, using = "//*[contains(text(), 'Running')]") + public static WebElement running; + @FindBy(how = How.XPATH, using = "//*[contains(text(), 'Logs')]") + public static WebElement logs; + @FindBy(how = How.XPATH, using = "(//*[contains(text(), 'View')])[1]") + public static WebElement advancedLogs; + @FindBy(how = How.XPATH, using = "//*[contains(@class, 'icon-stop')]") + public static WebElement stop; + @FindBy(how = How.XPATH, using = "//div[@data-cy='log-viewer-row']//div[contains(text(),'ERROR')]") + public static WebElement error; + @FindBy(how = How.XPATH, using = "//*[contains(text(),'Stopped')]") + public static WebElement stopped; + public static By start = By.xpath("//*[contains(@class, 'icon-play ')]"); +} diff --git a/src/e2e-test/java/io.cdap.plugin/locators/package-info.java b/src/e2e-test/java/io.cdap.plugin/locators/package-info.java new file mode 100644 index 0000000..f4dfbbf --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/locators/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023. + * + * 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. + */ +/** + * contains CDF Oracle Replicator plugin locators. + */ +package io.cdap.plugin.locators; diff --git a/src/e2e-test/java/io.cdap.plugin/stepsdesign/StepDefinition.java b/src/e2e-test/java/io.cdap.plugin/stepsdesign/StepDefinition.java new file mode 100644 index 0000000..ee7fa66 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/stepsdesign/StepDefinition.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.stepsdesign; + +import io.cdap.e2e.utils.CdfHelper; +import io.cdap.e2e.utils.PluginPropertyUtils; +import io.cdap.e2e.utils.SeleniumDriver; +import io.cdap.plugin.actions.ReplicationActions; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; + +import java.io.IOException; +import java.sql.SQLException; +/** + * Contains Oracle replication test scenarios step definitions. + */ +public class StepDefinition implements CdfHelper { + @Given("Open DataFusion Project with replication to configure pipeline") + public void openDataFusionProjectWithReplicationToConfigurePipeline() throws IOException, InterruptedException { + openCdf(); + SeleniumDriver.getDriver().get(SeleniumDriver.getDriver().getCurrentUrl().replace( + PluginPropertyUtils.pluginProp("cdfUrl"), PluginPropertyUtils.pluginProp("replication.url"))); + } + @And("Click on the Next button") + public void clickOnTheNextButton() throws InterruptedException { + ReplicationActions.clickNextButton(); + } + @And("Select Oracle as Source") + public void selectSourceAsOracle() { + ReplicationActions.clickOnOraclePlugin(); + } + + @Then("Validate Source table is available and select it") + public void selectTable() { + ReplicationActions.selectTable(); + } + @Then("Deploy the replication pipeline") + public void deployPipeline() { + ReplicationActions.deployPipeline(); + } + + @Then("Run the replication Pipeline") + public void runPipelineInRuntime() { + ReplicationActions.runThePipeline(); + } + + @Then("Open the logs") + public void openLogsOfSltPipeline() { + ReplicationActions.openAdvanceLogs(); + } + + @Then("Wait till pipeline is in running state and check if no errors occurred") + public void waitTillSltPipelineIsInRunningState() throws InterruptedException { + ReplicationActions.waitTillPipelineIsRunningAndCheckForErrors(); + } + + @Then("Close the pipeline logs and stop the pipeline") + public void closeLogsAndStopThePipeline() { + ReplicationActions.closeTheLogsAndClickOnStopButton(); + } + + @And("Capture raw logs") + public void captureRawLogs() { + ReplicationActions.captureRawLog(); + } + + @And("Insert a record in the source table and wait for replication") + public void triggerInsertCdcEvent() throws IOException, InterruptedException, SQLException, ClassNotFoundException { + ReplicationActions.insertRecordAndWait(); //JCoException, + } + + @And("Delete a record in the source table and wait for replication") + public void triggerDeleteCdcEvent() throws IOException, InterruptedException, SQLException, ClassNotFoundException { + ReplicationActions.deleteRecordAndWait(); //JCoException, + } + + @And("Update a record in the source table and wait for replication") + public void triggerUpdateCdcEvent() throws IOException, InterruptedException, SQLException, ClassNotFoundException { + ReplicationActions.updateRecordAndWait(); //JCoException, + } + @Then("Verify expected Oracle records in target BigQuery table") + public void verifyExpectedOracleRecordsInTargetBigQueryTable() throws + IOException, InterruptedException, SQLException, ClassNotFoundException { + ReplicationActions.verifyTargetBigQueryRecordMatchesExpectedOracleRecord(); + } + +} diff --git a/src/e2e-test/java/io.cdap.plugin/stepsdesign/package-info.java b/src/e2e-test/java/io.cdap.plugin/stepsdesign/package-info.java new file mode 100644 index 0000000..8c1da89 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/stepsdesign/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023. + * + * 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. + */ +/** + * Contains Oracle replication test scenarios step definitions. + */ +package io.cdap.plugin.stepsdesign; diff --git a/src/e2e-test/java/io.cdap.plugin/tests/TestRunner.java b/src/e2e-test/java/io.cdap.plugin/tests/TestRunner.java new file mode 100644 index 0000000..5c063ed --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/tests/TestRunner.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package io.cdap.plugin.tests; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import org.junit.runner.RunWith; + +/** + * Test Runner to execute Oracle plugin test cases. + */ +@RunWith(Cucumber.class) +@CucumberOptions( + features = {"src/e2e-test/features"}, + glue = {"stepsdesign", "io.cdap.plugin.stepsdesign", "io.cdap.plugin.hooks"}, + tags = {"@Oracle"}, monochrome = true, + plugin = {"pretty", "html:target/cucumber-html-report", "json:target/cucumber-reports/cucumber.json", + "junit:target/cucumber-reports/cucumber.xml"} +) +public class TestRunner { +} diff --git a/src/e2e-test/java/io.cdap.plugin/tests/package-info.java b/src/e2e-test/java/io.cdap.plugin/tests/package-info.java new file mode 100644 index 0000000..05da828 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/tests/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023. + * + * 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 contains the runners for Oracle features. + */ +package io.cdap.plugin.tests; diff --git a/src/e2e-test/java/io.cdap.plugin/utils/BigQuery.java b/src/e2e-test/java/io.cdap.plugin/utils/BigQuery.java new file mode 100644 index 0000000..46d5cd0 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/utils/BigQuery.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.utils; + +import com.google.cloud.bigquery.BigQueryException; +import io.cdap.e2e.utils.BigQueryClient; +import io.cdap.e2e.utils.PluginPropertyUtils; +import org.junit.Assert; +import stepsdesign.BeforeActions; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +/** + * Contains bq helper methods used in e2e tests. + */ +public class BigQuery { + public static void waitForFlush() throws InterruptedException { + int flushInterval = Integer.parseInt(PluginPropertyUtils.pluginProp("loadInterval")); + TimeUnit time = TimeUnit.SECONDS; + time.sleep(2 * flushInterval + 60); + } + + public static void deleteTable(String tableName) throws IOException, InterruptedException { + try { + BigQueryClient.dropBqQuery(tableName); + BeforeActions.scenario.write("BQ Target table - " + tableName + " deleted successfully"); + } catch (BigQueryException e) { + if (e.getMessage().contains("Not found: Table")) { + BeforeActions.scenario.write("BQ Target Table does not exist"); + } else { + Assert.fail(e.getMessage()); + } + } + } +} diff --git a/src/e2e-test/java/io.cdap.plugin/utils/OracleClient.java b/src/e2e-test/java/io.cdap.plugin/utils/OracleClient.java new file mode 100644 index 0000000..0bc5828 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/utils/OracleClient.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.utils; + + +import io.cdap.e2e.utils.PluginPropertyUtils; + + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +/** + * Oracle client. + */ +public class OracleClient { + private static Connection getOracleConnection() throws SQLException, ClassNotFoundException { + TimeZone timezone = TimeZone.getTimeZone("UTC"); + TimeZone.setDefault(timezone); + Class.forName("oracle.jdbc.driver.OracleDriver"); + String databaseName = PluginPropertyUtils.pluginProp("dataset"); + String host = PluginPropertyUtils.pluginProp("host"); + String port = PluginPropertyUtils.pluginProp("port"); + String username = PluginPropertyUtils.pluginProp("username"); + String password = PluginPropertyUtils.pluginProp("password"); + + return DriverManager.getConnection("jdbc:oracle:thin:@//" + host + + ":" + port + "/" + databaseName, + username, password); + } + + + public static void createTable(String table, String schema, String datatypeColumns) + throws SQLException, ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + String createTableQuery = "CREATE TABLE " + schema + "." + table + datatypeColumns; + statement.executeUpdate(createTableQuery); + } + } + + public static void forceFlushCDC() throws SQLException, ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + /* + Oracle doesn't immediately flush CDC events, it can automatically happen based + on time/log file size or you can force it + */ + statement.executeUpdate("ALTER SYSTEM SWITCH LOGFILE"); + } + } + + + + public static void insertRow (String table, String schema, String datatypeValues) throws + SQLException, ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + // Insert dummy data. + statement.executeUpdate("INSERT INTO " + schema + "." + table + " " + + " VALUES " + datatypeValues); + + } + } + public static void deleteRow(String table, String schema, String deleteCondition) throws SQLException, + ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + // Insert dummy data. + statement.executeUpdate("DELETE FROM " + schema + "." + table + " WHERE " + deleteCondition); + } + } + public static void updateRow(String table, String schema, String updateCondition, String updatedValue) throws + SQLException, ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + // Insert dummy data. + statement.executeUpdate("UPDATE " + schema + "." + table + " SET " + updatedValue + + " WHERE " + updateCondition); + } + } + + public static List> getOracleRecordsAsMap(String table, String schema) throws SQLException, + ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + // Insert dummy data. + List> oracleRecords = new ArrayList<>(); + String query = "select * from " + schema + "." + table; + ResultSet result = statement.executeQuery(query); + + ResultSetMetaData rsmd = result.getMetaData(); + int numberOfColumns = rsmd.getColumnCount(); + List columns = new ArrayList<>(); + columns.add(""); + for (int colIndex = 1; colIndex <= numberOfColumns; colIndex++) { + columns.add(rsmd.getColumnName(colIndex) + "#" + rsmd.getColumnType(colIndex)); + } + while (result.next()) { + Map record = new HashMap<>(); + for (int colIndex = 1; colIndex <= numberOfColumns; colIndex++) { + String columnName = columns.get(colIndex).split("#")[0]; + int type = Integer.parseInt(columns.get(colIndex).split("#")[1]); + Object value; + switch (type) { + case Types.TIMESTAMP: + Instant instant = result.getTimestamp(colIndex).toInstant(); + //Rounding off as BQ supports till microseconds + value = TimeUnit.SECONDS.toMicros(instant.getEpochSecond()) + + TimeUnit.NANOSECONDS.toMicros(instant.getNano()); + break; + default: + /* + Convert all data types toString as bq converts certain data types to string + to preserve precision and scale + */ + value = result.getString(colIndex); + } + record.put(columnName, value); + } + oracleRecords.add(record); + } + statement.close(); + return oracleRecords; + } + } + + public static void deleteTables(String schema, String table) + throws SQLException, ClassNotFoundException { + try (Connection connect = getOracleConnection(); Statement statement = connect.createStatement()) { + String dropTableQuery = "DROP TABLE " + schema + "." + table; + statement.execute(dropTableQuery); + } + } +} diff --git a/src/e2e-test/java/io.cdap.plugin/utils/ValidationHelper.java b/src/e2e-test/java/io.cdap.plugin/utils/ValidationHelper.java new file mode 100644 index 0000000..c21c8a8 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/utils/ValidationHelper.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package io.cdap.plugin.utils; + +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.FieldValue; +import com.google.cloud.bigquery.FieldValueList; +import com.google.cloud.bigquery.TableResult; +import io.cdap.e2e.utils.BigQueryClient; +import io.cdap.e2e.utils.PluginPropertyUtils; +import org.junit.Assert; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +/** + * Contains validation method from oracle to bq used in e2e tests. + */ +public class ValidationHelper { + + public static List> getBigQueryRecordsAsMap(String projectId, String database, String tableName) + throws IOException, InterruptedException { + String query = "SELECT * EXCEPT ( _row_id, _source_timestamp, _sort) FROM `" + projectId + + "." + database + "." + tableName + "`"; + List> bqRecords = new ArrayList<>(); + TableResult results = BigQueryClient.getQueryResult(query); + List columns = new ArrayList<>(); + for (Field field : results.getSchema().getFields()) { + columns.add(field.getName() + "#" + field.getType()); + } + for (FieldValueList row : results.getValues()) { + Map record = new HashMap<>(); + int index = 0; + for (FieldValue fieldValue : row) { + String columnName = columns.get(index).split("#")[0]; + String dataType = columns.get(index).split("#")[1]; + Object value; + if (dataType.equalsIgnoreCase("TIMESTAMP")) { + value = fieldValue.getTimestampValue(); + } else { + value = fieldValue.getValue(); + value = value != null ? value.toString() : null; + } + record.put(columnName, value); + index++; + } + bqRecords.add(record); + } + return bqRecords; + } + + public static void validateRecords(List> sourceOracleRecords, + List> targetBigQueryRecords) { + + String uniqueField = PluginPropertyUtils.pluginProp("primaryKey"); + // Logic to maintain the order of both lists and validate records based on that order. + Map bqUniqueIdMap = (Map) targetBigQueryRecords.stream() + .filter(t -> t.get("_is_deleted") == null) + .collect(Collectors.toMap( + t -> t.get(uniqueField), + t -> t, + //This logic is to handle duplication scenario in bq target + (x, y) -> { + Long xSeqNum = Long.parseLong(x.get("_sequence_num").toString()); + Long ySeqNum = Long.parseLong(y.get("_sequence_num").toString()); + if (xSeqNum > ySeqNum) { + return x; + } + return y; + })); + + for (int record = 0; record < sourceOracleRecords.size(); record++) { + Map oracleRecord = sourceOracleRecords.get(record); + Object uniqueId = oracleRecord.get(uniqueField); + Map bqRow = (Map) bqUniqueIdMap.get(uniqueId); + if (bqRow != null) { + bqRow.remove("_is_deleted"); + bqRow.remove("_sequence_num"); + } + compareRecords(bqRow, oracleRecord); + } + } + + public static void compareRecords(Map targetBigQueryRecords, + Map sourceOracleRecord) { + Set bigQueryKeySet = targetBigQueryRecords.keySet(); + for (String field : bigQueryKeySet) { + Object bigQueryFieldValue = targetBigQueryRecords.get(field); + Object oracleFieldValue = sourceOracleRecord.get(field); + Assert.assertEquals(String.format("Field %s is not equal: expected %s but got %s", field, + oracleFieldValue, bigQueryFieldValue), + oracleFieldValue, bigQueryFieldValue); + } + } + +} diff --git a/src/e2e-test/java/io.cdap.plugin/utils/package-info.java b/src/e2e-test/java/io.cdap.plugin/utils/package-info.java new file mode 100644 index 0000000..185a9c1 --- /dev/null +++ b/src/e2e-test/java/io.cdap.plugin/utils/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023. + * + * 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. + */ +/** + * Contains helper methods used in e2e tests. + */ +package io.cdap.plugin.utils; diff --git a/src/e2e-test/resources/errorMessage.properties b/src/e2e-test/resources/errorMessage.properties new file mode 100644 index 0000000..0aa0344 --- /dev/null +++ b/src/e2e-test/resources/errorMessage.properties @@ -0,0 +1 @@ +validationSuccessMessage=No errors found diff --git a/src/e2e-test/resources/pluginDataCyAttributes.properties b/src/e2e-test/resources/pluginDataCyAttributes.properties new file mode 100644 index 0000000..7ebda94 --- /dev/null +++ b/src/e2e-test/resources/pluginDataCyAttributes.properties @@ -0,0 +1,6 @@ +host=host +user=user +password=password +region=select-region +regionOption=option-us-west2 + diff --git a/src/e2e-test/resources/pluginParameters.properties b/src/e2e-test/resources/pluginParameters.properties new file mode 100644 index 0000000..371e202 --- /dev/null +++ b/src/e2e-test/resources/pluginParameters.properties @@ -0,0 +1,46 @@ +projectId=PROJECT_ID +dataset=XE +schema=ABC +sourceTable=SANITY +host=ORACLE_HOST +port=ORACLE_PORT +username=ORACLE_USERNAME +password=ORACLE_PASSWORD + +pipelineName=e2e-sanity +pipeline-initialization=200 +loadInterval=30 + +replication.url=http://localhost:11011/cdap/ns/default/replication/create +cdfUrl=http://localhost:11011/pipelines/ns/default/studio + +primaryKey=ID + +deleteRowCondition=ID='USER1' +updateRowCondition=ID='USER2' +updatedRow=COL2='SUCCEEDED' + +datatypeColumns=(ID VARCHAR2(100) PRIMARY KEY, COL1 CHAR, COL2 CHAR(10), COL3 VARCHAR(3), COL4 VARCHAR2(3), \ +COL5 NCHAR, COL6 NCHAR(12), COL7 NVARCHAR2(12), COL11 ROWID, COL12 NUMBER(4), \ +COL13 NUMBER(*), COL15 NUMBER(10,-3), COL16 NUMBER, COL17 DECIMAL(4), COL18 DECIMAL(*),\ + COL20 DECIMAL(10,-3), COL21 DECIMAL, COL22 FLOAT, COL24 INTEGER, \ +COL25 DOUBLE PRECISION, COL26 REAL, COL27 SMALLINT, COL28 TIMESTAMP, COL29 TIMESTAMP(9), \ +COL33 DATE, COL35 BFILE) + +datatypeValuesRow1=('USER1', 'M','ABCDEF','ABC','ABC','ä','ä½ å¥½ï¼?è¿?','ä½ å¥½ï¼?è¿?',\ + 'AAAAaoAATAAABrXAAA',1234,1234.56789,\ + 1234.56789,1234.56789,1234.56789,1234.56789,1234.56789,1234.56789,1234.5679,\ + 1234.56789,1234.5679,1234.5679,1234.56789,TIMESTAMP'2023-01-01 2:00:00',TIMESTAMP'2023-01-01 2:00:00',\ + TIMESTAMP '2023-01-01 00:00:00.000000', NULL) + +datatypeValuesRow2=('USER2', 'M','ABCDEF','ABC','ABC','ä','ä½ å¥½ï¼?è¿?','ä½ å¥½ï¼?è¿?',\ + 'AAAAaoAATAAABrXAAA',1234,1234.56789,\ + 1234.56789,1234.56789,1234.56789,1234.56789,1234.56789,1234.56789,1234.5679,\ + 1234.56789,1234.5679,1234.5679,1234.56789,TIMESTAMP'2023-01-01 2:00:00',TIMESTAMP'2023-01-01 2:00:00',\ + TIMESTAMP '2023-01-01 00:00:00.000000', NULL) + +datatypeValuesForInsertOperation=('USER3', 'M','ABCDEF','ABC','ABC','ä','ä½ å¥½ï¼?è¿?','ä½ å¥½ï¼?è¿?',\ + 'AAAAaoAATAAABrXAAA',1234,1234.56789,\ + 1234.56789,1234.56789,1234.56789,1234.56789,1234.56789,1234.56789,1234.5679,\ + 1234.56789,1234.5679,1234.5679,1234.56789,TIMESTAMP'2023-01-01 2:00:00',TIMESTAMP'2023-01-01 2:00:00',\ + TIMESTAMP '2023-01-01 00:00:00.000000', NULL)