diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..f501c6b23 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Ecosystem engineering are default owners for everything in the repo. +* @firebolt-db/ecosystem-engineering diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 448169567..192172114 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -7,6 +7,13 @@ on: description: 'Database - a new one will be created if not provided' required: false default: '' + engine: + description: 'Engine - a new one will be created if not provided' + required: false + account: + description: 'Account' + required: true + default: 'developer' environment: description: 'Environment to run the tests against' type: choice @@ -21,6 +28,13 @@ jobs: runs-on: ubuntu-latest steps: + - name: Validate database and engine + if: ${{ (github.event.inputs.database == '') != (github.event.inputs.engine == '') }} + uses: actions/github-script@v3 + with: + script: | + core.setFailed("Database and Engine parameters should be provided simultaneously") + - name: "Foresight: Collect Workflow Telemetry" uses: runforesight/foresight-workflow-kit-action@v1 if: ${{ always() }} @@ -35,26 +49,24 @@ jobs: - name: Determine env variables run: | if [ "${{ github.event.inputs.environment }}" == 'staging' ]; then - echo "USERNAME=${{ secrets.FIREBOLT_USERNAME_STAGING }}" >> "$GITHUB_ENV" - echo "PASSWORD=${{ secrets.FIREBOLT_PASSWORD_STAGING }}" >> "$GITHUB_ENV" - echo "SERVICE_ACCOUNT_ID=${{ secrets.SERVICE_ACCOUNT_ID_STAGING }}" >> "$GITHUB_ENV" - echo "SERVICE_ACCOUNT_SECRET=${{ secrets.SERVICE_ACCOUNT_SECRET_STAGING }}" >> "$GITHUB_ENV" + echo "SERVICE_ACCOUNT_ID=${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }}" >> "$GITHUB_ENV" + echo "SERVICE_ACCOUNT_SECRET=${{ secrets.FIREBOLT_CLIENT_SECRET_STG_NEW_IDN }}" >> "$GITHUB_ENV" else - echo "USERNAME=${{ secrets.FIREBOLT_USERNAME_DEV }}" >> "$GITHUB_ENV" - echo "PASSWORD=${{ secrets.FIREBOLT_PASSWORD_DEV }}" >> "$GITHUB_ENV" - echo "SERVICE_ACCOUNT_ID=${{ secrets.SERVICE_ACCOUNT_ID_DEV }}" >> "$GITHUB_ENV" - echo "SERVICE_ACCOUNT_SECRET=${{ secrets.SERVICE_ACCOUNT_SECRET_DEV }}" >> "$GITHUB_ENV" + echo "SERVICE_ACCOUNT_ID=${{ secrets.FIREBOLT_CLIENT_ID_NEW_IDN }}" >> "$GITHUB_ENV" + echo "SERVICE_ACCOUNT_SECRET=${{ secrets.FIREBOLT_CLIENT_SECRET_NEW_IDN }}" >> "$GITHUB_ENV" fi + - name: Setup database and engine id: setup if: ${{ github.event.inputs.database == '' }} - uses: firebolt-db/integration-testing-setup@v1 + uses: firebolt-db/integration-testing-setup@v2 with: - firebolt-username: ${{ env.USERNAME }} - firebolt-password: ${{ env.PASSWORD }} + firebolt-client-id: ${{ env.SERVICE_ACCOUNT_ID }} + firebolt-client-secret: ${{ env.SERVICE_ACCOUNT_SECRET }} + account: ${{ github.event.inputs.account }} api-endpoint: "api.${{ github.event.inputs.environment }}.firebolt.io" - region: "us-east-1" instance-type: "B2" + engine-scale: "1" - name: Determine database name id: find-database-name @@ -65,8 +77,17 @@ jobs: echo "database_name=${{ steps.setup.outputs.database_name }}" >> $GITHUB_OUTPUT fi + - name: Determine engine name + id: find-engine-name + run: | + if ! [[ -z "${{ github.event.inputs.engine }}" ]]; then + echo "engine_name=${{ github.event.inputs.engine }}" >> $GITHUB_OUTPUT + else + echo "engine_name=${{ steps.setup.outputs.engine_name }}" >> $GITHUB_OUTPUT + fi + - name: Run integration tests - run: ./gradlew integrationTest -Ddb=${{ steps.find-database-name.outputs.database_name }} -Dapi=api.${{ github.event.inputs.environment }}.firebolt.io -Dpassword="${{ env.SERVICE_ACCOUNT_SECRET }}" -Duser="${{ env.SERVICE_ACCOUNT_ID }}" + run: ./gradlew integrationTest -Ddb=${{ steps.find-database-name.outputs.database_name }} -Denv=${{ github.event.inputs.environment }} -Dclient_secret="${{ env.SERVICE_ACCOUNT_SECRET }}" -Dclient_id="${{ env.SERVICE_ACCOUNT_ID }}" -Daccount="${{ github.event.inputs.account }}" -Dengine="${{ steps.find-engine-name.outputs.engine_name }}" - name: "Foresight: Analyze Test Results" uses: runforesight/foresight-test-kit-action@v1 diff --git a/gradle.properties b/gradle.properties index 0faed3411..85081c905 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=2.4.6-SNAPSHOT +version=3.0.0 jdbcVersion=4.3 \ No newline at end of file diff --git a/src/integrationTest/java/integration/ConnectionInfo.java b/src/integrationTest/java/integration/ConnectionInfo.java index 9c1e26127..8ce560933 100644 --- a/src/integrationTest/java/integration/ConnectionInfo.java +++ b/src/integrationTest/java/integration/ConnectionInfo.java @@ -1,29 +1,92 @@ package integration; -import lombok.Value; - +import java.util.Objects; import java.util.Optional; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static java.lang.System.getProperty; +import static java.util.stream.Collectors.joining; -@Value public class ConnectionInfo { - private static ConnectionInfo INSTANCE; - String password; - String user; - String api; - String database; + private static final String JDBC_URL_PREFIX = "jdbc:firebolt:"; + private static volatile ConnectionInfo INSTANCE; + // principal and secret are used here instead of client_id and client_secret respectively as more common term also used in java security API. + private final String principal; + private final String secret; + private final String env; + private final String database; + private final String account; + private final String engine; private ConnectionInfo() { - password = Optional.ofNullable(System.getProperty("password")).map(p -> p.replace("\"", "")).orElse(null); - user = Optional.ofNullable(System.getProperty("user")).map(u -> u.replace("\"", "")).orElse(null); - api = System.getProperty("api"); - database = System.getProperty("db"); + this( + getTrimmedProperty("client_id", "user"), + getTrimmedProperty("client_secret", "password"), + getProperty("env"), + getProperty("db"), + getProperty("account"), + getProperty("engine") + ); + } + + public ConnectionInfo(String principal, String secret, String env, String database, String account, String engine) { + this.principal = principal; + this.secret = secret; + this.env = env; + this.database = database; + this.account = account; + this.engine = engine; } public static ConnectionInfo getInstance() { if (INSTANCE == null) { - INSTANCE = new ConnectionInfo(); + synchronized (ConnectionInfo.class) { + if (INSTANCE == null) { + INSTANCE = new ConnectionInfo(); + } + } } return INSTANCE; } + private static String getTrimmedProperty(String name, String alias) { + return Optional.ofNullable(getProperty(name, getProperty(alias))).map(u -> u.replace("\"", "")).orElse(null); + } + + public String getPrincipal() { + return principal; + } + + public String getSecret() { + return secret; + } + + public String getEnv() { + return env; + } + + public String getDatabase() { + return database; + } + + public String getAccount() { + return account; + } + + public String getEngine() { + return engine; + } + + public String toJdbcUrl() { + String params = Stream.of(param("env", env), param("engine", engine), param("account", account)).filter(Objects::nonNull).collect(joining("&")); + if (!params.isEmpty()) { + params = "?" + params; + } + return JDBC_URL_PREFIX + database + params; + } + + private String param(String name, String value) { + return value == null ? null : format("%s=%s", name, value); + } } diff --git a/src/integrationTest/java/integration/IntegrationTest.java b/src/integrationTest/java/integration/IntegrationTest.java index 59fff8f15..441a1b19e 100644 --- a/src/integrationTest/java/integration/IntegrationTest.java +++ b/src/integrationTest/java/integration/IntegrationTest.java @@ -12,7 +12,6 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -20,29 +19,29 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class IntegrationTest { + private static final String JDBC_URL_PREFIX = "jdbc:firebolt:"; + protected Connection createLocalConnection(String queryParams) throws SQLException { return DriverManager.getConnection( - "jdbc:firebolt://localhost" + "/" + integration.ConnectionInfo.getInstance().getDatabase() - + queryParams, - integration.ConnectionInfo.getInstance().getUser(), - integration.ConnectionInfo.getInstance().getPassword()); + JDBC_URL_PREFIX + integration.ConnectionInfo.getInstance().getDatabase() + + queryParams + "&host=localhost" + getAccountParam(), + integration.ConnectionInfo.getInstance().getPrincipal(), + integration.ConnectionInfo.getInstance().getSecret()); } protected Connection createConnection() throws SQLException { - return DriverManager.getConnection( - "jdbc:firebolt://" + integration.ConnectionInfo.getInstance().getApi() + "/" - + integration.ConnectionInfo.getInstance().getDatabase(), - integration.ConnectionInfo.getInstance().getUser(), - integration.ConnectionInfo.getInstance().getPassword()); + return DriverManager.getConnection(integration.ConnectionInfo.getInstance().toJdbcUrl(), + integration.ConnectionInfo.getInstance().getPrincipal(), + integration.ConnectionInfo.getInstance().getSecret()); } protected Connection createConnection(String engine) throws SQLException { - return DriverManager.getConnection( - "jdbc:firebolt://" + integration.ConnectionInfo.getInstance().getApi() + "/" - + integration.ConnectionInfo.getInstance().getDatabase() - + Optional.ofNullable(engine).map(e -> "?engine=" + e).orElse(""), - integration.ConnectionInfo.getInstance().getUser(), - integration.ConnectionInfo.getInstance().getPassword()); + ConnectionInfo current = integration.ConnectionInfo.getInstance(); + ConnectionInfo updated = new ConnectionInfo(current.getPrincipal(), current.getSecret(), + current.getEnv(), current.getDatabase(), current.getAccount(), engine); + return DriverManager.getConnection(updated.toJdbcUrl(), + integration.ConnectionInfo.getInstance().getPrincipal(), + integration.ConnectionInfo.getInstance().getSecret()); } protected void setParam(Connection connection, String name, String value) throws SQLException { @@ -53,7 +52,7 @@ protected void setParam(Connection connection, String name, String value) throws @SneakyThrows protected void executeStatementFromFile(String path) { - executeStatementFromFile(path, null); + executeStatementFromFile(path, integration.ConnectionInfo.getInstance().getEngine()); } @SneakyThrows @@ -71,4 +70,7 @@ protected void removeExistingClient() throws NoSuchFieldException, IllegalAccess field.set(null, null); } + private String getAccountParam() { + return "&account=" + integration.ConnectionInfo.getInstance().getAccount(); + } } diff --git a/src/integrationTest/java/integration/MockWebServerAwareIntegrationTest.java b/src/integrationTest/java/integration/MockWebServerAwareIntegrationTest.java new file mode 100644 index 000000000..9ddd2cf6c --- /dev/null +++ b/src/integrationTest/java/integration/MockWebServerAwareIntegrationTest.java @@ -0,0 +1,33 @@ +package integration; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; + +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class MockWebServerAwareIntegrationTest extends IntegrationTest { + protected MockWebServer mockBackEnd; + private final int INIT_STATEMENTS_COUNT = 1; // The statement that validates that DB exists. + + @BeforeEach + void setUp() throws IOException { + mockBackEnd = new MockWebServer(); + mockBackEnd.start(); + mockBackEnd.enqueue(new MockResponse().setResponseCode(200).setBody(format("database_name%nstring%n%s", System.getProperty("db")))); + } + + @AfterEach + void tearDown() throws IOException { + mockBackEnd.close(); + } + + + protected void assertMockBackendRequestsCount(int expected) { + assertEquals(INIT_STATEMENTS_COUNT + expected, mockBackEnd.getRequestCount()); + } +} diff --git a/src/integrationTest/java/integration/tests/ConnectionTest.java b/src/integrationTest/java/integration/tests/ConnectionTest.java new file mode 100644 index 000000000..2169c76e4 --- /dev/null +++ b/src/integrationTest/java/integration/tests/ConnectionTest.java @@ -0,0 +1,31 @@ +package integration.tests; + +import integration.ConnectionInfo; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ConnectionTest { + /** + * This test connects to specific engine with additional property {@code use_standard_sql} supported by user engine but + * not supported by system engine used here to retrieve the data of user engine. + * The test is needed because there were create connection to system engine by copying all given connection properties + * while additional (custom) parameters should be ignored. + * @throws SQLException if something went wrong + */ + @Test + void connectionWithAdditionalProperties() throws SQLException { + ConnectionInfo params = integration.ConnectionInfo.getInstance(); + String url = format("jdbc:firebolt:%s?env=%s&engine=%s&account=%s&use_standard_sql=1", params.getDatabase(), params.getEnv(), params.getEngine(), params.getAccount()); + try(Connection connection = DriverManager.getConnection(url, params.getPrincipal(), params.getSecret())) { + assertNotNull(connection); + } + } +} diff --git a/src/integrationTest/java/integration/tests/MissingDataTest.java b/src/integrationTest/java/integration/tests/MissingDataTest.java new file mode 100644 index 000000000..497d3b9ae --- /dev/null +++ b/src/integrationTest/java/integration/tests/MissingDataTest.java @@ -0,0 +1,28 @@ +package integration.tests; + +import integration.ConnectionInfo; +import integration.IntegrationTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class MissingDataTest extends IntegrationTest { + @Test + void missingAccount() throws SQLException { + ConnectionInfo current = integration.ConnectionInfo.getInstance(); + try (Connection good = DriverManager.getConnection(current.toJdbcUrl(), current.getPrincipal(), current.getSecret())) { + assertNotNull(good); + } + + ConnectionInfo noAccount = new ConnectionInfo(current.getPrincipal(), current.getSecret(), + current.getEnv(), current.getDatabase(), null, current.getEngine()); + assertThrows(SQLException.class, () -> DriverManager.getConnection(noAccount.toJdbcUrl(), noAccount.getPrincipal(), noAccount.getSecret())); + } +} diff --git a/src/integrationTest/java/integration/tests/NumericTypesTest.java b/src/integrationTest/java/integration/tests/NumericTypesTest.java new file mode 100644 index 000000000..5badb28b6 --- /dev/null +++ b/src/integrationTest/java/integration/tests/NumericTypesTest.java @@ -0,0 +1,24 @@ +package integration.tests; + +import integration.IntegrationTest; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NumericTypesTest extends IntegrationTest { + @Test + void shouldHaveCorrectInfo() throws SQLException { + try (Connection connection = this.createConnection(null); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT 3::decimal")) { + resultSet.next(); + assertEquals(9, resultSet.getMetaData().getScale(1)); + assertEquals(38, resultSet.getMetaData().getPrecision(1)); + } + } +} diff --git a/src/integrationTest/java/integration/tests/PreparedStatementTest.java b/src/integrationTest/java/integration/tests/PreparedStatementTest.java index 2b5e3f39e..4a775cd20 100644 --- a/src/integrationTest/java/integration/tests/PreparedStatementTest.java +++ b/src/integrationTest/java/integration/tests/PreparedStatementTest.java @@ -165,8 +165,9 @@ void shouldFailSQLInjectionAttempt() throws SQLException { private QueryResult createExpectedResult(List> expectedRows) { return QueryResult.builder().databaseName(ConnectionInfo.getInstance().getDatabase()) .tableName("prepared_statement_test") - .columns(Arrays.asList(QueryResult.Column.builder().name("sales").type(FireboltDataType.BIG_INT).build(), - QueryResult.Column.builder().name("make").type(FireboltDataType.TEXT).build())) + .columns( + Arrays.asList(QueryResult.Column.builder().name("sales").type(FireboltDataType.BIG_INT).build(), + QueryResult.Column.builder().name("make").type(FireboltDataType.TEXT).build())) .rows(expectedRows).build(); } diff --git a/src/integrationTest/java/integration/tests/StatementTest.java b/src/integrationTest/java/integration/tests/StatementTest.java index 19eb1f058..f6f3ed16d 100644 --- a/src/integrationTest/java/integration/tests/StatementTest.java +++ b/src/integrationTest/java/integration/tests/StatementTest.java @@ -15,7 +15,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.IntStream; import static java.lang.String.format; @@ -41,6 +40,23 @@ void afterEach() { executeStatementFromFile("/statements/statement/cleanup.sql"); } + @Test + void shouldSelect1() throws SQLException { + try (Connection connection = this.createConnection(); Statement statement = connection.createStatement()) { + statement.executeQuery("SELECT 1;"); + assertNotNull(statement.executeQuery("SELECT 1;")); + } + } + + @Test + void shouldSelect1WithQueryTimeout() throws SQLException { + try (Connection connection = this.createConnection(); Statement statement = connection.createStatement()) { + statement.setQueryTimeout(10); // 10 seconds + statement.executeQuery("SELECT 1;"); + assertNotNull(statement.executeQuery("SELECT 1;")); + } + } + @Test void shouldReuseStatementWhenNotCloseOnCompletion() throws SQLException { try (Connection connection = this.createConnection(); Statement statement = connection.createStatement()) { diff --git a/src/integrationTest/java/integration/tests/SystemEngineTest.java b/src/integrationTest/java/integration/tests/SystemEngineTest.java index 80870eade..eb348e22e 100644 --- a/src/integrationTest/java/integration/tests/SystemEngineTest.java +++ b/src/integrationTest/java/integration/tests/SystemEngineTest.java @@ -18,12 +18,11 @@ public class SystemEngineTest extends IntegrationTest { private static final String DATABASE_NAME = "jdbc_system_engine_integration_test"; private static final String ENGINE_NAME = "jdbc_system_engine_integration_test_engine"; private static final String ENGINE_NEW_NAME = "jdbc_system_engine_integration_test_engine_2"; - private static final String SYSTEM_ENGINE_NAME = "system"; @BeforeAll void beforeAll() { try { - executeStatementFromFile("/statements/system/ddl.sql", SYSTEM_ENGINE_NAME); + executeStatementFromFile("/statements/system/ddl.sql", null); } catch (Exception e) { log.warn("Could not execute statement", e); } @@ -32,16 +31,16 @@ void beforeAll() { @AfterAll void afterAll() { try { - executeStatementFromFile("/statements/system/cleanup.sql", SYSTEM_ENGINE_NAME); + executeStatementFromFile("/statements/system/cleanup.sql", null); } catch (Exception e) { log.warn("Could not execute statement", e); } } @Test - void shouldExecuteQueriesUsingSystemEngine() throws SQLException { - try (Connection connection = this.createConnection(SYSTEM_ENGINE_NAME)) { - List queries = Arrays.asList(String.format("CREATE DATABASE %s", DATABASE_NAME), + void shouldExecuteEngineManagementQueries() throws SQLException { + try (Connection connection = this.createConnection(null)) { + List queries = Arrays.asList(String.format("CREATE DATABASE IF NOT EXISTS %s", DATABASE_NAME), String.format("CREATE ENGINE %s", ENGINE_NAME), String.format("ATTACH ENGINE %s TO %s;", ENGINE_NAME, DATABASE_NAME), String.format("ALTER DATABASE %s WITH DESCRIPTION = 'JDBC Integration test'", DATABASE_NAME), diff --git a/src/integrationTest/java/integration/tests/TimeoutTest.java b/src/integrationTest/java/integration/tests/TimeoutTest.java index 6fcca31c7..81b72be2a 100644 --- a/src/integrationTest/java/integration/tests/TimeoutTest.java +++ b/src/integrationTest/java/integration/tests/TimeoutTest.java @@ -3,8 +3,11 @@ import static org.junit.jupiter.api.Assertions.fail; import java.sql.Connection; +import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -17,12 +20,12 @@ class TimeoutTest extends IntegrationTest { @Test @Timeout(value = 7, unit = TimeUnit.MINUTES) - void shouldExecuteRequestWithoutTimeout() { + void shouldExecuteRequestWithoutTimeout() throws SQLException { long startTime = System.nanoTime(); try (Connection con = this.createConnection(); Statement stmt = con.createStatement()) { - this.setParam(con, "use_standard_sql", "0"); - this.setParam(con, "advanced_mode", "1"); - stmt.executeQuery("SELECT sleepEachRow(1) from numbers(360)"); + String numbers = IntStream.range(1, 10_000).boxed().map(String::valueOf).collect(Collectors.joining(",")); + String query = String.format("WITH arr AS (SELECT [%s] as a)%nSELECT md5(md5(md5(md5(md5(md5(md5(to_string(a)))))))) FROM arr UNNEST(a)", numbers); + stmt.executeQuery(query); } catch (Exception e) { log.error("Error", e); fail(); diff --git a/src/integrationTest/java/integration/tests/client/RetryPolicyTest.java b/src/integrationTest/java/integration/tests/client/RetryPolicyTest.java index 44705dd69..5fbfb298d 100644 --- a/src/integrationTest/java/integration/tests/client/RetryPolicyTest.java +++ b/src/integrationTest/java/integration/tests/client/RetryPolicyTest.java @@ -1,47 +1,29 @@ package integration.tests.client; -import static com.firebolt.jdbc.exception.ExceptionType.INVALID_REQUEST; -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.sql.SQLException; -import java.sql.Statement; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import com.firebolt.jdbc.connection.FireboltConnection; import com.firebolt.jdbc.exception.FireboltException; - -import integration.IntegrationTest; +import integration.MockWebServerAwareIntegrationTest; import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; - -public class RetryPolicyTest extends IntegrationTest { - - private MockWebServer mockBackEnd; +import org.junit.jupiter.api.Test; - @BeforeEach - void setUp() throws IOException { - mockBackEnd = new MockWebServer(); - mockBackEnd.start(); - } +import java.sql.SQLException; +import java.sql.Statement; - @AfterEach - void tearDown() throws IOException { - mockBackEnd.close(); - } +import static com.firebolt.jdbc.exception.ExceptionType.INVALID_REQUEST; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +public class RetryPolicyTest extends MockWebServerAwareIntegrationTest { @Test public void shouldThrowExceptionOn400WithoutRetry() throws SQLException { mockBackEnd.enqueue(new MockResponse().setResponseCode(400)); try (FireboltConnection fireboltConnection = (FireboltConnection) createLocalConnection( - String.format("?ssl=0&port=%d&max_retries=%d", mockBackEnd.getPort(), 3)); + format("?ssl=0&port=%d&max_retries=%d", mockBackEnd.getPort(), 3)); Statement statement = fireboltConnection.createStatement()) { FireboltException ex = assertThrows(FireboltException.class, () -> statement.execute("SELECT 1;")); assertEquals(ex.getType(), INVALID_REQUEST); - assertEquals(1, mockBackEnd.getRequestCount()); + assertMockBackendRequestsCount(1); } } @@ -51,10 +33,10 @@ public void shouldRetryOn502() throws SQLException { mockBackEnd.enqueue(new MockResponse().setResponseCode(502)); mockBackEnd.enqueue(new MockResponse().setResponseCode(200)); try (FireboltConnection fireboltConnection = (FireboltConnection) createLocalConnection( - String.format("?ssl=0&port=%d&max_retries=%d", mockBackEnd.getPort(), 3)); + format("?ssl=0&port=%d&max_retries=%d", mockBackEnd.getPort(), 3)); Statement statement = fireboltConnection.createStatement()) { statement.execute("SELECT 1;"); - assertEquals(3, mockBackEnd.getRequestCount()); + assertMockBackendRequestsCount(3); } } diff --git a/src/integrationTest/java/integration/tests/client/TLSTest.java b/src/integrationTest/java/integration/tests/client/TLSTest.java index 58edfb268..78c9b6ccd 100644 --- a/src/integrationTest/java/integration/tests/client/TLSTest.java +++ b/src/integrationTest/java/integration/tests/client/TLSTest.java @@ -1,6 +1,11 @@ package integration.tests.client; -import static org.junit.jupiter.api.Assertions.assertEquals; +import com.firebolt.jdbc.connection.FireboltConnection; +import integration.MockWebServerAwareIntegrationTest; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.tls.HandshakeCertificates; +import okhttp3.tls.HeldCertificate; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileWriter; @@ -11,32 +16,7 @@ import java.sql.Statement; import java.util.UUID; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import com.firebolt.jdbc.connection.FireboltConnection; - -import integration.IntegrationTest; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.tls.HandshakeCertificates; -import okhttp3.tls.HeldCertificate; - -public class TLSTest extends IntegrationTest { - private MockWebServer mockBackEnd; - - @BeforeEach - void setUp() throws IOException { - mockBackEnd = new MockWebServer(); - mockBackEnd.start(); - } - - @AfterEach - void tearDown() throws IOException { - mockBackEnd.close(); - } - +public class TLSTest extends MockWebServerAwareIntegrationTest { @Test public void shouldUseTLS() throws SQLException, IOException, NoSuchFieldException, IllegalAccessException { mockBackEnd.enqueue(new MockResponse().setResponseCode(200)); @@ -70,7 +50,7 @@ public void shouldUseTLS() throws SQLException, IOException, NoSuchFieldExceptio String.format("?ssl_certificate_path=%s&port=%s", path, mockBackEnd.getPort())); Statement statement = fireboltConnection.createStatement()) { statement.execute("SELECT 1;"); - assertEquals(1, mockBackEnd.getRequestCount()); + assertMockBackendRequestsCount(1); } finally { removeExistingClient(); } diff --git a/src/integrationTest/java/integration/tests/client/UsageTrackingTest.java b/src/integrationTest/java/integration/tests/client/UsageTrackingTest.java index a11e79ba1..850e3c5d7 100644 --- a/src/integrationTest/java/integration/tests/client/UsageTrackingTest.java +++ b/src/integrationTest/java/integration/tests/client/UsageTrackingTest.java @@ -1,39 +1,19 @@ package integration.tests.client; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.sql.SQLException; -import java.sql.Statement; - -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import com.firebolt.jdbc.connection.FireboltConnection; import com.firebolt.jdbc.util.VersionUtil; - -import integration.IntegrationTest; +import integration.MockWebServerAwareIntegrationTest; import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; -public class UsageTrackingTest extends IntegrationTest { - - private MockWebServer mockBackEnd; - - @BeforeEach - void setUp() throws IOException { - mockBackEnd = new MockWebServer(); - mockBackEnd.start(); - } +import java.sql.SQLException; +import java.sql.Statement; - @AfterEach - void tearDown() throws IOException { - mockBackEnd.close(); - } +import static org.junit.jupiter.api.Assertions.assertTrue; +public class UsageTrackingTest extends MockWebServerAwareIntegrationTest { @Test public void shouldSendRequestWithUserAgentHeaderContainingDriverAndClientInfo() throws SQLException, InterruptedException { diff --git a/src/main/java/com/firebolt/FireboltDriver.java b/src/main/java/com/firebolt/FireboltDriver.java index 9800f5bd4..1794d615b 100644 --- a/src/main/java/com/firebolt/FireboltDriver.java +++ b/src/main/java/com/firebolt/FireboltDriver.java @@ -17,7 +17,6 @@ public class FireboltDriver implements Driver { public static final String JDBC_FIREBOLT = "jdbc:firebolt:"; - private static final String JDBC_FIREBOLT_PREFIX = JDBC_FIREBOLT + "//"; static { try { @@ -35,11 +34,11 @@ public Connection connect(String url, Properties connectionSettings) throws SQLE @Override public boolean acceptsURL(String url) { - return StringUtils.isNotEmpty(url) && url.startsWith(JDBC_FIREBOLT_PREFIX); + return StringUtils.isNotEmpty(url) && url.startsWith(JDBC_FIREBOLT); } @Override - public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) { return PropertyUtil.getPropertyInfo(url, info); } diff --git a/src/main/java/com/firebolt/jdbc/Query.java b/src/main/java/com/firebolt/jdbc/Query.java deleted file mode 100644 index aa22b74b5..000000000 --- a/src/main/java/com/firebolt/jdbc/Query.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.firebolt.jdbc; - -import java.util.Iterator; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import lombok.Builder; -import lombok.Value; - -/** - * Represents a SQL query that can be sent to Firebolt - */ -@Builder -@Value -public class Query { - String select; - String from; - String innerJoin; - String orderBy; - List conditions; - - /** - * Parse the object to a SQL query that can be sent to Firebolt - * - * @return SQL query that can be sent to Firebolt - */ - public String toSql() { - StringBuilder query = new StringBuilder(); - if (StringUtils.isBlank(select)) { - throw new IllegalStateException("Cannot create query: SELECT cannot be blank"); - } - if (StringUtils.isBlank(from)) { - throw new IllegalStateException("Cannot create query: FROM cannot be blank"); - } - - query.append("SELECT ").append(select); - query.append(" FROM ").append(from); - if (StringUtils.isNotBlank(innerJoin)) { - query.append(" JOIN ").append(innerJoin); - } - query.append(getConditionsPart()); - if (StringUtils.isNotBlank(orderBy)) { - query.append(" order by ").append(orderBy); - } - return query.toString(); - } - - private String getConditionsPart() { - StringBuilder agg = new StringBuilder(); - Iterator iter = conditions.iterator(); - if (iter.hasNext()) { - agg.append(" WHERE "); - } - if (iter.hasNext()) { - String entry = iter.next(); - agg.append(entry); - } - while (iter.hasNext()) { - String entry = iter.next(); - agg.append(" AND ").append(entry); - } - return agg.toString(); - } -} diff --git a/src/main/java/com/firebolt/jdbc/QueryResult.java b/src/main/java/com/firebolt/jdbc/QueryResult.java index 0e2ccddc4..bcdb01dbb 100644 --- a/src/main/java/com/firebolt/jdbc/QueryResult.java +++ b/src/main/java/com/firebolt/jdbc/QueryResult.java @@ -13,6 +13,7 @@ /** * Class containing a query result that can be used to create a * {@link com.firebolt.jdbc.resultset.FireboltResultSet} + * It is particularly useful for metadata methods as a ResulSet containing metadata info must be returned. */ @Builder @Value @@ -36,8 +37,8 @@ public String toString() { StringBuilder stringBuilder = new StringBuilder(); this.appendWithListValues(stringBuilder, columns.stream().map(Column::getName).collect(Collectors.toList())); stringBuilder.append(NEXT_LINE); - this.appendWithListValues(stringBuilder, columns.stream().map(Column::getType) - .map(FireboltDataType::getAliases).map( aliases -> aliases[0]).collect(Collectors.toList())); + this.appendWithListValues(stringBuilder, columns.stream().map(Column::getType).map(FireboltDataType::getAliases) + .map(aliases -> aliases[0]).collect(Collectors.toList())); stringBuilder.append(NEXT_LINE); for (int i = 0; i < rows.size(); i++) { diff --git a/src/main/java/com/firebolt/jdbc/client/FireboltClient.java b/src/main/java/com/firebolt/jdbc/client/FireboltClient.java index e5b6f1de5..a21c9481b 100644 --- a/src/main/java/com/firebolt/jdbc/client/FireboltClient.java +++ b/src/main/java/com/firebolt/jdbc/client/FireboltClient.java @@ -39,20 +39,24 @@ public abstract class FireboltClient { public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String HEADER_AUTHORIZATION_BEARER_PREFIX_VALUE = "Bearer "; public static final String HEADER_USER_AGENT = "User-Agent"; + private final OkHttpClient httpClient; protected final ObjectMapper objectMapper; private final String headerUserAgentValue; - private final OkHttpClient httpClient; private final FireboltConnection connection; - protected FireboltClient(OkHttpClient httpClient, FireboltConnection connection, String customDrivers, - String customClients, ObjectMapper objectMapper) { + protected FireboltClient(OkHttpClient httpClient, ObjectMapper objectMapper, FireboltConnection connection, String customDrivers, String customClients) { this.httpClient = httpClient; - this.connection = connection; this.objectMapper = objectMapper; + this.connection = connection; this.headerUserAgentValue = UsageTrackerUtil.getUserAgentString(customDrivers != null ? customDrivers : "", customClients != null ? customClients : ""); } + protected T getResource(String uri, String accessToken, Class valueType) + throws IOException, FireboltException { + return getResource(uri, uri, accessToken, valueType); + } + protected T getResource(String uri, String host, String accessToken, Class valueType) throws IOException, FireboltException { Request rq = createGetRequest(uri, accessToken); diff --git a/src/main/java/com/firebolt/jdbc/client/account/FireboltAccount.java b/src/main/java/com/firebolt/jdbc/client/account/FireboltAccount.java new file mode 100644 index 000000000..31a94c90c --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/client/account/FireboltAccount.java @@ -0,0 +1,40 @@ +package com.firebolt.jdbc.client.account; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +public class FireboltAccount { + @JsonProperty + private final String id; + @JsonProperty + private final String region; + + @JsonCreator + public FireboltAccount(@JsonProperty("id") String id, @JsonProperty("region") String region) { + this.id = id; + this.region = region; + } + + public String getId() { + return id; + } + + public String getRegion() { + return region; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FireboltAccount account = (FireboltAccount) o; + return Objects.equals(id, account.id) && Objects.equals(region, account.region); + } + + @Override + public int hashCode() { + return Objects.hash(id, region); + } +} diff --git a/src/main/java/com/firebolt/jdbc/client/account/FireboltAccountClient.java b/src/main/java/com/firebolt/jdbc/client/account/FireboltAccountClient.java deleted file mode 100644 index 0b020a891..000000000 --- a/src/main/java/com/firebolt/jdbc/client/account/FireboltAccountClient.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.firebolt.jdbc.client.account; - -import java.io.IOException; - -import org.apache.commons.lang3.StringUtils; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.firebolt.jdbc.client.FireboltClient; -import com.firebolt.jdbc.client.account.response.FireboltAccountResponse; -import com.firebolt.jdbc.client.account.response.FireboltDefaultDatabaseEngineResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineIdResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineResponse; -import com.firebolt.jdbc.connection.FireboltConnection; -import com.firebolt.jdbc.exception.ExceptionType; -import com.firebolt.jdbc.exception.FireboltException; - -import lombok.CustomLog; -import okhttp3.OkHttpClient; - -@CustomLog -public class FireboltAccountClient extends FireboltClient { - - private static final String GET_ACCOUNT_ID_URI = "%s/iam/v2/accounts:getIdByName?accountName=%s"; - private static final String URI_SUFFIX_ENGINE_AND_ACCOUNT_ID_BY_ENGINE_NAME = "engines:getIdByName?engine_name="; - private static final String URI_SUFFIX_ACCOUNT_ENGINE_INFO_BY_ENGINE_ID = "engines/"; - private static final String URI_SUFFIX_DATABASE_INFO_URL = "engines:getURLByDatabaseName?databaseName="; - private static final String URI_PREFIX_WITH_ACCOUNT_RESOURCE = "%s/core/v1/accounts/%s/%s"; - private static final String URI_PREFIX_WITHOUT_ACCOUNT_RESOURCE = "%s/core/v1/account/%s"; - - public FireboltAccountClient(OkHttpClient httpClient, ObjectMapper objectMapper, - FireboltConnection fireboltConnection, String customDrivers, String customClients) { - super(httpClient, fireboltConnection, customDrivers, customClients, objectMapper); - } - - /** - * Returns the account - * - * @param host the host - * @param account the name of the account - * @param accessToken the access token - * @return the account - */ - public FireboltAccountResponse getAccount(String host, String account, String accessToken) - throws FireboltException, IOException { - String uri = String.format(GET_ACCOUNT_ID_URI, host, account); - return getResource(uri, host, accessToken, FireboltAccountResponse.class); - } - - /** - * Returns an engine - * - * @param host the host - * @param accountId the id of the account - * @param engineName the engine name - * @param engineId the engine id - * @param accessToken the access token - * @return the engine - */ - public FireboltEngineResponse getEngine(String host, String accountId, String engineName, String engineId, - String accessToken) throws FireboltException, IOException { - try { - String uri = createAccountUri(accountId, host, URI_SUFFIX_ACCOUNT_ENGINE_INFO_BY_ENGINE_ID + engineId); - return getResource(uri, host, accessToken, FireboltEngineResponse.class); - } catch (FireboltException exception) { - if (exception.getType() == ExceptionType.RESOURCE_NOT_FOUND) { - throw new FireboltException( - String.format("The address of the engine with name %s and id %s could not be found", engineName, - engineId), - exception, ExceptionType.RESOURCE_NOT_FOUND); - } else { - throw exception; - } - } - } - - /** - * Returns the default engine of the database - * - * @param host the host - * @param accountId the account id - * @param dbName the name of the database - * @param accessToken the access token - * @return the default engine for the database - */ - public FireboltDefaultDatabaseEngineResponse getDefaultEngineByDatabaseName(String host, String accountId, String dbName, - String accessToken) throws FireboltException, IOException { - String uri = createAccountUri(accountId, host, URI_SUFFIX_DATABASE_INFO_URL + dbName); - try { - return getResource(uri, host, accessToken, FireboltDefaultDatabaseEngineResponse.class); - - } catch (FireboltException exception) { - if (exception.getType() == ExceptionType.RESOURCE_NOT_FOUND) { - throw new FireboltException(String.format("The database with the name %s could not be found", dbName), - exception, ExceptionType.RESOURCE_NOT_FOUND); - } else { - throw exception; - } - } - } - - /** - * Returns the engine id - * - * @param host the host - * @param accountId the account id - * @param engineName the name of the engine - * @param accessToken the access token - * @return the engine id - */ - public FireboltEngineIdResponse getEngineId(String host, String accountId, String engineName, String accessToken) - throws FireboltException, IOException { - try { - String uri = createAccountUri(accountId, host, - URI_SUFFIX_ENGINE_AND_ACCOUNT_ID_BY_ENGINE_NAME + engineName); - return getResource(uri, host, accessToken, FireboltEngineIdResponse.class); - } catch (FireboltException exception) { - if (exception.getType() == ExceptionType.RESOURCE_NOT_FOUND) { - throw new FireboltException(String.format("The engine %s could not be found", engineName), exception, - ExceptionType.RESOURCE_NOT_FOUND); - } else { - throw exception; - } - } - } - - private String createAccountUri(String account, String host, String suffix) { - if (StringUtils.isNotEmpty(account)) - return String.format(URI_PREFIX_WITH_ACCOUNT_RESOURCE, host, account, suffix); - else - return String.format(URI_PREFIX_WITHOUT_ACCOUNT_RESOURCE, host, suffix); - } - -} diff --git a/src/main/java/com/firebolt/jdbc/client/account/FireboltAccountRetriever.java b/src/main/java/com/firebolt/jdbc/client/account/FireboltAccountRetriever.java new file mode 100644 index 000000000..c99efc6f7 --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/client/account/FireboltAccountRetriever.java @@ -0,0 +1,33 @@ +package com.firebolt.jdbc.client.account; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.firebolt.jdbc.client.FireboltClient; +import com.firebolt.jdbc.connection.FireboltConnection; +import com.firebolt.jdbc.exception.FireboltException; +import okhttp3.OkHttpClient; + +import java.io.IOException; + +import static java.lang.String.format; + +public class FireboltAccountRetriever extends FireboltClient { + private static final String URL = "https://%s/web/v3/account/%s/%s"; + private final String host; + private final String path; + private final Class type; + + public FireboltAccountRetriever(OkHttpClient httpClient, ObjectMapper objectMapper, FireboltConnection connection, String customDrivers, String customClients, String host, String path, Class type) { + super(httpClient, objectMapper, connection, customDrivers, customClients); + this.host = host; + this.path = path; + this.type = type; + } + + public T retrieve(String accessToken, String accountName) throws FireboltException { + try { + return getResource(format(URL, host, accountName, path), accessToken, type); + } catch (IOException e) { + throw new FireboltException(String.format("Failed to get %s url for account %s", path, accountName), e); + } + } +} diff --git a/src/main/java/com/firebolt/jdbc/client/authentication/AuthenticationRequestFactory.java b/src/main/java/com/firebolt/jdbc/client/authentication/AuthenticationRequestFactory.java deleted file mode 100644 index 0f432c707..000000000 --- a/src/main/java/com/firebolt/jdbc/client/authentication/AuthenticationRequestFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.firebolt.jdbc.client.authentication; - -import org.apache.commons.lang3.StringUtils; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class AuthenticationRequestFactory { - - public static AuthenticationRequest getAuthenticationRequest(String username, String password, String host) { - if (StringUtils.isEmpty(username) || StringUtils.contains(username, "@")) { - return new UsernamePasswordAuthenticationRequest(username, password, host); - } else { - return new ServiceAccountAuthenticationRequest(username, password, host); - } - } - -} diff --git a/src/main/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClient.java b/src/main/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClient.java index 3cf6d6148..f03fa373a 100644 --- a/src/main/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClient.java +++ b/src/main/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClient.java @@ -18,7 +18,7 @@ public class FireboltAuthenticationClient extends FireboltClient { public FireboltAuthenticationClient(OkHttpClient httpClient, ObjectMapper objectMapper, FireboltConnection connection, String customDrivers, String customClients) { - super(httpClient, connection, customDrivers, customClients, objectMapper); + super(httpClient, objectMapper, connection, customDrivers, customClients); } /** @@ -27,12 +27,12 @@ public FireboltAuthenticationClient(OkHttpClient httpClient, ObjectMapper object * @param host the host * @param user the username * @param password the password + * @param environment the environment * @return the connection tokens */ - public FireboltConnectionTokens postConnectionTokens(String host, String user, String password) + public FireboltConnectionTokens postConnectionTokens(String host, String user, String password, String environment) throws IOException, FireboltException { - AuthenticationRequest authenticationRequest = AuthenticationRequestFactory.getAuthenticationRequest(user, - password, host); + AuthenticationRequest authenticationRequest = new ServiceAccountAuthenticationRequest(user, password, environment); String uri = authenticationRequest.getUri(); log.debug("Creating connection with url {}", uri); Request request = this.createPostRequest(uri, authenticationRequest.getRequestBody()); diff --git a/src/main/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequest.java b/src/main/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequest.java index a6bf99182..50ca3e569 100644 --- a/src/main/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequest.java +++ b/src/main/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequest.java @@ -1,27 +1,39 @@ package com.firebolt.jdbc.client.authentication; -import lombok.AllArgsConstructor; import okhttp3.FormBody; import okhttp3.RequestBody; -@AllArgsConstructor public class ServiceAccountAuthenticationRequest implements AuthenticationRequest { - private static final String CLIENT_CREDENTIALS = "client_credentials"; - private static final String GRAND_TYPE_FIELD_NAME = "grant_type"; - private static final String CLIENT_ID_FIELD_NAME = "client_id"; - private static final String CLIENT_SECRET_FIELD_NAME = "client_secret"; - private static final String AUTH_URL = "%s/auth/v1/token"; - private String id; - private String secret; - private String host; - public RequestBody getRequestBody() { - return new FormBody.Builder().add(CLIENT_ID_FIELD_NAME, id).add(CLIENT_SECRET_FIELD_NAME, secret) - .add(GRAND_TYPE_FIELD_NAME, CLIENT_CREDENTIALS).build(); - } + private static final String AUDIENCE_FIELD_NAME = "audience"; + private static final String AUDIENCE_FIELD_VALUE = "https://api.firebolt.io"; + private static final String GRAND_TYPE_FIELD_NAME = "grant_type"; + private static final String GRAND_TYPE_FIELD_VALUE = "client_credentials"; + private static final String CLIENT_ID_FIELD_NAME = "client_id"; + private static final String CLIENT_SECRET_FIELD_NAME = "client_secret"; + private static final String AUTH_URL = "https://id.%s.firebolt.io/oauth/token"; - @Override - public String getUri() { - return String.format(AUTH_URL, host); - } + private final String clientId; + private final String clientSecret; + private final String environment; + + public ServiceAccountAuthenticationRequest(String clientId, String clientSecret, String environment) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.environment = environment; + } + + @Override + public RequestBody getRequestBody() { + return new FormBody.Builder() + .add(AUDIENCE_FIELD_NAME, AUDIENCE_FIELD_VALUE) + .add(GRAND_TYPE_FIELD_NAME, GRAND_TYPE_FIELD_VALUE) + .add(CLIENT_ID_FIELD_NAME, clientId) + .add(CLIENT_SECRET_FIELD_NAME, clientSecret).build(); + } + + @Override + public String getUri() { + return String.format(AUTH_URL, environment); + } } diff --git a/src/main/java/com/firebolt/jdbc/client/authentication/UsernamePasswordAuthenticationRequest.java b/src/main/java/com/firebolt/jdbc/client/authentication/UsernamePasswordAuthenticationRequest.java deleted file mode 100644 index 1ef2b31e9..000000000 --- a/src/main/java/com/firebolt/jdbc/client/authentication/UsernamePasswordAuthenticationRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.firebolt.jdbc.client.authentication; - -import java.util.HashMap; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.firebolt.jdbc.client.FireboltObjectMapper; - -import lombok.AllArgsConstructor; -import okhttp3.MediaType; -import okhttp3.RequestBody; - -@AllArgsConstructor -public class UsernamePasswordAuthenticationRequest implements AuthenticationRequest { - private static final String AUTH_URL = "%s/auth/v1/login"; - private static final String USERNAME_FIELD_NAME = "username"; - private static final String PASSWORD_FIELD_NAME = "password"; - private String username; - private String password; - private String host; - - public RequestBody getRequestBody() throws JsonProcessingException { - Map loginDetailsMap = new HashMap<>(); - loginDetailsMap.put(USERNAME_FIELD_NAME, username); - loginDetailsMap.put(PASSWORD_FIELD_NAME, password); - return RequestBody.create(FireboltObjectMapper.getInstance().writeValueAsString(loginDetailsMap), - MediaType.parse("application/json")); - } - - @Override - public String getUri() { - return String.format(AUTH_URL, host); - } - -} \ No newline at end of file diff --git a/src/main/java/com/firebolt/jdbc/client/gateway/GatewayUrlResponse.java b/src/main/java/com/firebolt/jdbc/client/gateway/GatewayUrlResponse.java new file mode 100644 index 000000000..986e7f614 --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/client/gateway/GatewayUrlResponse.java @@ -0,0 +1,15 @@ +package com.firebolt.jdbc.client.gateway; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; + + +@Value +@AllArgsConstructor +@Builder +public class GatewayUrlResponse { + @JsonProperty("engineUrl") + String engineUrl; +} diff --git a/src/main/java/com/firebolt/jdbc/client/query/StatementClientImpl.java b/src/main/java/com/firebolt/jdbc/client/query/StatementClientImpl.java index 8ef8f1eff..934b12b4b 100644 --- a/src/main/java/com/firebolt/jdbc/client/query/StatementClientImpl.java +++ b/src/main/java/com/firebolt/jdbc/client/query/StatementClientImpl.java @@ -15,6 +15,7 @@ import lombok.CustomLog; import lombok.NonNull; import okhttp3.Call; +import okhttp3.Dispatcher; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -22,7 +23,6 @@ import okhttp3.ResponseBody; import okhttp3.internal.http2.StreamResetException; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import java.io.IOException; @@ -34,10 +34,12 @@ import java.util.Map; import java.util.Optional; import java.util.function.BiPredicate; +import java.util.function.Function; import static com.firebolt.jdbc.connection.settings.FireboltQueryParameterKey.DEFAULT_FORMAT; import static com.firebolt.jdbc.connection.settings.FireboltQueryParameterKey.OUTPUT_FORMAT; import static com.firebolt.jdbc.exception.ExceptionType.UNAUTHORIZED; +import static java.lang.String.format; @CustomLog public class StatementClientImpl extends FireboltClient implements StatementClient { @@ -47,9 +49,8 @@ public class StatementClientImpl extends FireboltClient implements StatementClie private final BiPredicate isCallWithId = (call, id) -> call.request().tag() instanceof String && StringUtils.equals((String) call.request().tag(), id); - public StatementClientImpl(OkHttpClient httpClient, FireboltConnection connection, ObjectMapper objectMapper, - String customDrivers, String customClients) { - super(httpClient, connection, customDrivers, customClients, objectMapper); + public StatementClientImpl(OkHttpClient httpClient, ObjectMapper objectMapper, FireboltConnection connection, String customDrivers, String customClients) { + super(httpClient, objectMapper, connection, customDrivers, customClients); } /** @@ -71,12 +72,11 @@ public InputStream executeSqlStatement(@NonNull StatementInfoWrapper statementIn Map params = getAllParameters(connectionProperties, statementInfoWrapper, systemEngine, queryTimeout); try { String uri = this.buildQueryUri(connectionProperties, params).toString(); - return executeSqlStatementWithRetryOnUnauthorized(statementInfoWrapper, connectionProperties, - formattedStatement, uri); + return executeSqlStatementWithRetryOnUnauthorized(statementInfoWrapper, connectionProperties, formattedStatement, uri); } catch (FireboltException e) { throw e; } catch (Exception e) { - String errorMessage = String.format("Error executing statement with id %s: %s", + String errorMessage = format("Error executing statement with id %s: %s", statementInfoWrapper.getId(), formattedStatement); if (e instanceof StreamResetException) { throw new FireboltException(errorMessage, e, ExceptionType.CANCELED); @@ -148,8 +148,7 @@ public void abortStatement(String id, FireboltProperties fireboltProperties) thr throw e; } } catch (Exception e) { - throw new FireboltException( - String.format("Could not cancel query: %s at %s", id, fireboltProperties.getHost()), e); + throw new FireboltException(format("Could not cancel query: %s at %s", id, fireboltProperties.getHost()), e); } } @@ -164,13 +163,15 @@ public void abortRunningHttpRequest(@NonNull String id) { } private Optional getQueuedCallWithId(String id) { - return getHttpClient().dispatcher().queuedCalls().stream().filter(call -> isCallWithId.test(call, id)) - .findAny(); + return getSelectedCallWithId(id, Dispatcher::queuedCalls); } private Optional getRunningCallWithId(String id) { - return getHttpClient().dispatcher().runningCalls().stream().filter(call -> isCallWithId.test(call, id)) - .findAny(); + return getSelectedCallWithId(id, Dispatcher::runningCalls); + } + + private Optional getSelectedCallWithId(String id, Function> callsGetter) { + return callsGetter.apply(getHttpClient().dispatcher()).stream().filter(call -> isCallWithId.test(call, id)).findAny(); } @Override @@ -190,8 +191,9 @@ private URI buildCancelUri(FireboltProperties fireboltProperties, String id) { private URI buildURI(FireboltProperties fireboltProperties, Map parameters, List pathSegments) { HttpUrl.Builder httpUrlBuilder = new HttpUrl.Builder() - .scheme(Boolean.TRUE.equals(fireboltProperties.isSsl()) ? "https" : "http") - .host(fireboltProperties.getHost()).port(fireboltProperties.getPort()); + .scheme(fireboltProperties.isSsl() ? "https" : "http") + .host(fireboltProperties.getHost()) + .port(fireboltProperties.getPort()); parameters.forEach(httpUrlBuilder::addQueryParameter); pathSegments.forEach(httpUrlBuilder::addPathSegment); @@ -207,14 +209,14 @@ private Map getAllParameters(FireboltProperties fireboltProperti getResponseFormatParameter(statementInfoWrapper.getType() == StatementType.QUERY, isLocalDb) .ifPresent(format -> params.put(format.getLeft(), format.getRight())); - // System engines do not support the following query params - if (!systemEngine) { - params.put(FireboltQueryParameterKey.DATABASE.getKey(), fireboltProperties.getDatabase()); + if (systemEngine) { + params.put(FireboltQueryParameterKey.ACCOUNT_ID.getKey(), fireboltProperties.getAccountId()); + } else { params.put(FireboltQueryParameterKey.QUERY_ID.getKey(), statementInfoWrapper.getId()); - params.put(FireboltQueryParameterKey.COMPRESS.getKey(), - String.format("%d", fireboltProperties.isCompress() ? 1 : 0)); + params.put(FireboltQueryParameterKey.COMPRESS.getKey(), fireboltProperties.isCompress() ? "1" : "0"); + params.put(FireboltQueryParameterKey.DATABASE.getKey(), fireboltProperties.getDatabase()); - if (queryTimeout > -1) { + if (queryTimeout > 0) { params.put("max_execution_time", String.valueOf(queryTimeout)); } } @@ -223,15 +225,10 @@ private Map getAllParameters(FireboltProperties fireboltProperti } private Optional> getResponseFormatParameter(boolean isQuery, boolean isLocalDb) { - if (isQuery) { - FireboltQueryParameterKey key = isLocalDb ? DEFAULT_FORMAT : OUTPUT_FORMAT; - return Optional.of(new ImmutablePair<>(key.getKey(), TAB_SEPARATED_WITH_NAMES_AND_TYPES_FORMAT)); - } - return Optional.empty(); + return isQuery ? Optional.of(Pair.of((isLocalDb ? DEFAULT_FORMAT : OUTPUT_FORMAT).getKey(), TAB_SEPARATED_WITH_NAMES_AND_TYPES_FORMAT)) : Optional.empty(); } private Map getCancelParameters(String statementId) { return Map.of(FireboltQueryParameterKey.QUERY_ID.getKey(), statementId); } - } diff --git a/src/main/java/com/firebolt/jdbc/connection/Engine.java b/src/main/java/com/firebolt/jdbc/connection/Engine.java index d00040f5c..47f355b60 100644 --- a/src/main/java/com/firebolt/jdbc/connection/Engine.java +++ b/src/main/java/com/firebolt/jdbc/connection/Engine.java @@ -1,13 +1,13 @@ package com.firebolt.jdbc.connection; -import lombok.Builder; -import lombok.Data; +import lombok.*; -@Builder +@AllArgsConstructor @Data +@EqualsAndHashCode public class Engine { - private String endpoint; - private String id; - private String status; - private String name; + private final String endpoint; + private final String status; + private final String name; + private final String database; } diff --git a/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java b/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java index 010554dfa..84234cccc 100644 --- a/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java +++ b/src/main/java/com/firebolt/jdbc/connection/FireboltConnection.java @@ -5,8 +5,10 @@ import com.firebolt.jdbc.annotation.NotImplemented; import com.firebolt.jdbc.client.FireboltObjectMapper; import com.firebolt.jdbc.client.HttpClientConfig; -import com.firebolt.jdbc.client.account.FireboltAccountClient; +import com.firebolt.jdbc.client.account.FireboltAccount; +import com.firebolt.jdbc.client.account.FireboltAccountRetriever; import com.firebolt.jdbc.client.authentication.FireboltAuthenticationClient; +import com.firebolt.jdbc.client.gateway.GatewayUrlResponse; import com.firebolt.jdbc.client.query.StatementClientImpl; import com.firebolt.jdbc.connection.settings.FireboltProperties; import com.firebolt.jdbc.exception.ExceptionType; @@ -15,8 +17,10 @@ import com.firebolt.jdbc.exception.FireboltUnsupportedOperationException; import com.firebolt.jdbc.metadata.FireboltDatabaseMetadata; import com.firebolt.jdbc.metadata.FireboltSystemEngineDatabaseMetadata; +import com.firebolt.jdbc.service.FireboltAccountIdService; import com.firebolt.jdbc.service.FireboltAuthenticationService; import com.firebolt.jdbc.service.FireboltEngineService; +import com.firebolt.jdbc.service.FireboltGatewayUrlService; import com.firebolt.jdbc.service.FireboltStatementService; import com.firebolt.jdbc.statement.FireboltStatement; import com.firebolt.jdbc.statement.preparedstatement.FireboltPreparedStatement; @@ -30,10 +34,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; +import java.security.GeneralSecurityException; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; @@ -58,6 +59,7 @@ import java.util.Properties; import java.util.concurrent.Executor; +import static java.lang.String.format; import static java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT; import static java.sql.ResultSet.TYPE_FORWARD_ONLY; @@ -65,8 +67,10 @@ public class FireboltConnection implements Connection { private final FireboltAuthenticationService fireboltAuthenticationService; - private final FireboltEngineService fireboltEngineService; private final FireboltStatementService fireboltStatementService; + private final FireboltEngineService fireboltEngineService; + private final FireboltGatewayUrlService fireboltGatewayUrlService; + private final FireboltAccountIdService fireboltAccountIdService; private final String httpConnectionUrl; private final List statements; private final int connectionTimeout; @@ -79,62 +83,112 @@ public class FireboltConnection implements Connection { private final FireboltProperties loginProperties; public FireboltConnection(@NonNull String url, Properties connectionSettings, - FireboltAuthenticationService fireboltAuthenticationService, FireboltEngineService fireboltEngineService, - FireboltStatementService fireboltStatementService) throws FireboltException { + FireboltAuthenticationService fireboltAuthenticationService, + FireboltGatewayUrlService fireboltGatewayUrlService, + FireboltStatementService fireboltStatementService, + FireboltEngineService fireboltEngineService, + FireboltAccountIdService fireboltAccountIdService) throws SQLException { + this.loginProperties = this.extractFireboltProperties(url, connectionSettings); + this.fireboltAuthenticationService = fireboltAuthenticationService; - this.fireboltEngineService = fireboltEngineService; - loginProperties = this.extractFireboltProperties(url, connectionSettings); + this.fireboltGatewayUrlService = fireboltGatewayUrlService; this.httpConnectionUrl = getHttpConnectionUrl(loginProperties); this.fireboltStatementService = fireboltStatementService; + this.statements = new ArrayList<>(); this.connectionTimeout = loginProperties.getConnectionTimeoutMillis(); this.networkTimeout = loginProperties.getSocketTimeoutMillis(); this.systemEngine = loginProperties.isSystemEngine(); + this.fireboltEngineService = fireboltEngineService; + this.fireboltAccountIdService = fireboltAccountIdService; this.connect(); } + // This code duplication between constructors is done because of back reference: dependent services require reference to current instance of FireboltConnection that prevents using constructor chaining or factory method. @ExcludeFromJacocoGeneratedReport - public FireboltConnection(@NonNull String url, Properties connectionSettings) throws FireboltException { + public FireboltConnection(@NonNull String url, Properties connectionSettings) throws SQLException { + this.loginProperties = extractFireboltProperties(url, connectionSettings); + OkHttpClient httpClient = getHttpClient(loginProperties); ObjectMapper objectMapper = FireboltObjectMapper.getInstance(); - loginProperties = this.extractFireboltProperties(url, connectionSettings); + + this.fireboltAuthenticationService = new FireboltAuthenticationService(new FireboltAuthenticationClient(httpClient, objectMapper, this, loginProperties.getUserDrivers(), loginProperties.getUserClients())); + this.fireboltGatewayUrlService = new FireboltGatewayUrlService(createFireboltAccountRetriever(httpClient, objectMapper, "engineUrl", GatewayUrlResponse.class)); this.httpConnectionUrl = getHttpConnectionUrl(loginProperties); - OkHttpClient httpClient = getHttpClient(loginProperties); - this.systemEngine = loginProperties.isSystemEngine(); - this.fireboltAuthenticationService = new FireboltAuthenticationService( - new FireboltAuthenticationClient(httpClient, objectMapper, this, loginProperties.getUserDrivers(), loginProperties.getUserClients())); - this.fireboltEngineService = new FireboltEngineService( - new FireboltAccountClient(httpClient, objectMapper, this, loginProperties.getUserDrivers(), loginProperties.getUserClients())); - this.fireboltStatementService = new FireboltStatementService( - new StatementClientImpl(httpClient, this, objectMapper, loginProperties.getUserDrivers(), loginProperties.getUserClients()), systemEngine); + this.fireboltStatementService = new FireboltStatementService(new StatementClientImpl(httpClient, objectMapper, this, loginProperties.getUserDrivers(), loginProperties.getUserClients())); + this.statements = new ArrayList<>(); this.connectionTimeout = loginProperties.getConnectionTimeoutMillis(); this.networkTimeout = loginProperties.getSocketTimeoutMillis(); + this.systemEngine = loginProperties.isSystemEngine(); + this.fireboltEngineService = new FireboltEngineService(this); + this.fireboltAccountIdService = new FireboltAccountIdService(createFireboltAccountRetriever(httpClient, objectMapper, "resolve", FireboltAccount.class)); + this.connect(); } private static OkHttpClient getHttpClient(FireboltProperties fireboltProperties) throws FireboltException { try { - return HttpClientConfig.getInstance() == null ? HttpClientConfig.init(fireboltProperties) - : HttpClientConfig.getInstance(); - } catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException - | IOException e) { + return HttpClientConfig.getInstance() == null ? HttpClientConfig.init(fireboltProperties) : HttpClientConfig.getInstance(); + } catch (GeneralSecurityException | IOException e) { throw new FireboltException("Could not instantiate http client", e); } } - private void connect() throws FireboltException { - String accessToken = getAccessToken(loginProperties).orElse(StringUtils.EMPTY); + private FireboltAccountRetriever createFireboltAccountRetriever(OkHttpClient httpClient, ObjectMapper objectMapper, String path, Class type) { + return new FireboltAccountRetriever<>(httpClient, objectMapper, this, loginProperties.getUserDrivers(), loginProperties.getUserClients(), loginProperties.getHost(), path, type); + } + + private void connect() throws SQLException { + String accessToken = this.getAccessToken(loginProperties).orElse(StringUtils.EMPTY); + closed = false; if (!PropertyUtil.isLocalDb(loginProperties)) { - String endpoint = fireboltEngineService.getEngine(httpConnectionUrl, loginProperties, accessToken) - .getEndpoint(); - this.sessionProperties = loginProperties.toBuilder().host(endpoint).build(); + String account = loginProperties.getAccount(); + if (account == null) { + throw new FireboltException("Cannot connect: account is missing"); + } + FireboltProperties internalSystemEngineProperties = createInternalSystemEngineProperties(accessToken, account); + String accountId = fireboltAccountIdService.getValue(accessToken, account); + if (systemEngine) { + //When using system engine, the system engine properties are the same as the session properties + sessionProperties = internalSystemEngineProperties.toBuilder().accountId(accountId).build(); + } else { + sessionProperties = internalSystemEngineProperties.toBuilder() + .engine(loginProperties.getEngine()) + .systemEngine(true) + .accountId(accountId) + .build(); + sessionProperties = getSessionPropertiesForNonSystemEngine(); + } } else { - this.sessionProperties = loginProperties; + //When running packdb locally, the login properties are the session properties + sessionProperties = loginProperties; } - closed = false; + assertDatabaseExisting(sessionProperties.getDatabase()); + log.debug("Connection opened"); } + private FireboltProperties getSessionPropertiesForNonSystemEngine() throws SQLException { + Engine engine = fireboltEngineService.getEngine(loginProperties.getEngine(), loginProperties.getDatabase()); + return loginProperties.toBuilder().host(engine.getEndpoint()).engine(engine.getName()).systemEngine(false).database(engine.getDatabase()).build(); + } + + private void assertDatabaseExisting(String database) throws SQLException { + if (database != null && !fireboltEngineService.doesDatabaseExist(database)) { + throw new FireboltException(format("Database %s does not exist", database)); + } + } + + private FireboltProperties createInternalSystemEngineProperties(String accessToken, String account) throws FireboltException { + String systemEngineEndpoint = fireboltGatewayUrlService.getUrl(accessToken, account); + return this.loginProperties + .toBuilder() + .systemEngine(true) + .additionalProperties(Map.of()) + .compress(false) + .host(UrlUtil.createUrl(systemEngineEndpoint).getHost()).database(null).build(); + } + public void removeExpiredTokens() throws FireboltException { fireboltAuthenticationService.removeConnectionTokens(httpConnectionUrl, loginProperties); } @@ -146,8 +200,8 @@ public Optional getAccessToken() throws FireboltException { private Optional getAccessToken(FireboltProperties fireboltProperties) throws FireboltException { String accessToken = fireboltProperties.getAccessToken(); if (accessToken != null) { - if (fireboltProperties.getUser() != null || fireboltProperties.getPassword() != null) { - throw new FireboltException("Ambiguity: Both access token and user/password are supplied"); + if (fireboltProperties.getPrincipal() != null || fireboltProperties.getSecret() != null) { + throw new FireboltException("Ambiguity: Both access token and client ID/secret are supplied"); } return Optional.of(accessToken); } @@ -223,9 +277,8 @@ public void setCatalog(String catalog) throws SQLException { // no-op as catalogs are not supported } - public String getEngine() throws SQLException { - this.validateConnectionIsNotClose(); - return fireboltEngineService.getEngineNameFromHost(this.getSessionProperties().getHost()); + public String getEngine() { + return this.getSessionProperties().getEngine(); } @Override @@ -296,7 +349,7 @@ public void close() { } private FireboltProperties extractFireboltProperties(String jdbcUri, Properties connectionProperties) { - Properties propertiesFromUrl = FireboltJdbcUrlUtil.extractProperties(jdbcUri); + Properties propertiesFromUrl = UrlUtil.extractProperties(jdbcUri); return FireboltProperties.of(propertiesFromUrl, connectionProperties); } @@ -419,8 +472,7 @@ public synchronized void addProperty(Pair property) throws Fireb } catch (FireboltException e) { throw e; } catch (Exception e) { - throw new FireboltException( - String.format("Could not set property %s=%s", property.getLeft(), property.getRight()), e); + throw new FireboltException(format("Could not set property %s=%s", property.getLeft(), property.getRight()), e); } } diff --git a/src/main/java/com/firebolt/jdbc/connection/FireboltJdbcUrlUtil.java b/src/main/java/com/firebolt/jdbc/connection/FireboltJdbcUrlUtil.java deleted file mode 100644 index b509c8d67..000000000 --- a/src/main/java/com/firebolt/jdbc/connection/FireboltJdbcUrlUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.firebolt.jdbc.connection; - -import java.net.URI; -import java.util.Optional; -import java.util.Properties; - -import org.apache.commons.lang3.StringUtils; - -import com.firebolt.jdbc.connection.settings.FireboltSessionProperty; - -import lombok.CustomLog; -import lombok.experimental.UtilityClass; - -@CustomLog -@UtilityClass -public class FireboltJdbcUrlUtil { - - public static final String JDBC_PREFIX = "jdbc:"; - - public static Properties extractProperties(String jdbcUrl) { - URI uri = extractUriFromJdbcUrl(jdbcUrl); - return parseUriQueryPart(uri); - } - - private URI extractUriFromJdbcUrl(String jdbcConnectionString) { - String cleanURI = StringUtils.replace(jdbcConnectionString, JDBC_PREFIX, ""); - return URI.create(cleanURI); - } - - private static Properties parseUriQueryPart(URI uri) { - Properties uriProperties = new Properties(); - String query = uri.getQuery(); - if (StringUtils.isNotBlank(query)) { - String[] queryKeyValues = query.split("&"); - for (String keyValue : queryKeyValues) { - String[] keyValueTokens = keyValue.split("="); - if (keyValueTokens.length == 2) { - uriProperties.put(keyValueTokens[0], keyValueTokens[1]); - } else { - log.warn("Cannot parse key-pair: {}", keyValue); - } - } - } - Optional.ofNullable(uri.getPath()) - .ifPresent(path -> uriProperties.put(FireboltSessionProperty.PATH.getKey(), path)); - Optional.ofNullable(uri.getHost()) - .ifPresent(host -> uriProperties.put(FireboltSessionProperty.HOST.getKey(), host)); - Optional.of(uri.getPort()).filter(p -> !p.equals(-1)) - .ifPresent(port -> uriProperties.put(FireboltSessionProperty.PORT.getKey(), String.valueOf(port))); - return uriProperties; - } -} diff --git a/src/main/java/com/firebolt/jdbc/connection/UrlUtil.java b/src/main/java/com/firebolt/jdbc/connection/UrlUtil.java new file mode 100644 index 000000000..bc32fa51e --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/connection/UrlUtil.java @@ -0,0 +1,61 @@ +package com.firebolt.jdbc.connection; + +import com.firebolt.jdbc.connection.settings.FireboltSessionProperty; +import lombok.CustomLog; +import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Optional; +import java.util.Properties; + +@CustomLog +@UtilityClass +public class UrlUtil { + + public static final String JDBC_PREFIX = "jdbc:firebolt:"; + + public static Properties extractProperties(String jdbcUrl) { + return parseUriQueryPart(jdbcUrl); + } + + + private static Properties parseUriQueryPart(String jdbcConnectionString) { + String cleanURI = StringUtils.replace(jdbcConnectionString, JDBC_PREFIX, ""); + URI uri = URI.create(cleanURI); + Properties uriProperties = new Properties(); + String query = uri.getQuery(); + if (StringUtils.isNotBlank(query)) { + String[] queryKeyValues = query.split("&"); + for (String keyValue : queryKeyValues) { + String[] keyValueTokens = keyValue.split("="); + if (keyValueTokens.length == 2) { + uriProperties.put(keyValueTokens[0], keyValueTokens[1]); + } else { + log.warn("Cannot parse key-pair: {}", keyValue); + } + } + } + Optional.ofNullable(uri.getPath()).map(p -> !StringUtils.isEmpty(p) ? StringUtils.removeEnd(p, "/") : p).ifPresent(path -> uriProperties.put(FireboltSessionProperty.PATH.getKey(), path)); + Optional.of(uri.getPort()).filter(p -> !p.equals(-1)) + .ifPresent(port -> uriProperties.put(FireboltSessionProperty.PORT.getKey(), String.valueOf(port))); + return uriProperties; + } + + /** + * This factory method is similar to {@link URI#create(String)}. + * The difference is that `URI.host` of {@code http://something} is {@code null} while + * URL spec {@code http://something/} returns URI with host=something. + * @param spec – the String to parse as a URL. + * @return URL instance + */ + public static URL createUrl(String spec) { + try { + return new URL(spec); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } +} diff --git a/src/main/java/com/firebolt/jdbc/connection/settings/FireboltProperties.java b/src/main/java/com/firebolt/jdbc/connection/settings/FireboltProperties.java index 6fe3118ea..a23c0922d 100644 --- a/src/main/java/com/firebolt/jdbc/connection/settings/FireboltProperties.java +++ b/src/main/java/com/firebolt/jdbc/connection/settings/FireboltProperties.java @@ -1,27 +1,29 @@ package com.firebolt.jdbc.connection.settings; +import lombok.Builder; +import lombok.CustomLog; +import lombok.NonNull; +import lombok.Value; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; + import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; - -import lombok.Builder; -import lombok.CustomLog; -import lombok.NonNull; -import lombok.Value; +import static java.lang.String.format; @Value @Builder(toBuilder = true) @CustomLog public class FireboltProperties { - private static final Pattern DB_PATH_PATTERN = Pattern.compile("/([a-zA-Z0-9_*\\-]+)"); + private static final Pattern DB_PATH_PATTERN = Pattern.compile("([a-zA-Z0-9_*\\-]+)"); private static final int FIREBOLT_SSL_PROXY_PORT = 443; private static final int FIREBOLT_NO_SSL_PROXY_PORT = 9090; - private static final String SYSTEM_ENGINE_NAME = "system"; private static final Set sessionPropertyKeys = Arrays.stream(FireboltSessionProperty.values()) .map(property -> { @@ -46,15 +48,17 @@ public class FireboltProperties { String sslCertificatePath; String sslMode; boolean compress; - String user; - String password; + String principal; + String secret; String engine; String account; + String accountId; Integer tcpKeepIdle; Integer tcpKeepCount; Integer tcpKeepInterval; boolean logResultSet; boolean systemEngine; + String environment; String userDrivers; String userClients; String accessToken; @@ -67,10 +71,11 @@ public static FireboltProperties of(Properties... properties) { boolean ssl = getSetting(mergedProperties, FireboltSessionProperty.SSL); String sslRootCertificate = getSetting(mergedProperties, FireboltSessionProperty.SSL_CERTIFICATE_PATH); String sslMode = getSetting(mergedProperties, FireboltSessionProperty.SSL_MODE); - String user = getSetting(mergedProperties, FireboltSessionProperty.USER); - String password = getSetting(mergedProperties, FireboltSessionProperty.PASSWORD); + String principal = getSetting(mergedProperties, FireboltSessionProperty.CLIENT_ID); + String secret = getSetting(mergedProperties, FireboltSessionProperty.CLIENT_SECRET); String path = getSetting(mergedProperties, FireboltSessionProperty.PATH); - String engine = getSetting(mergedProperties, FireboltSessionProperty.ENGINE); + String database = getDatabase(mergedProperties, path); + String engine = getEngine(mergedProperties); boolean isSystemEngine = isSystemEngine(engine); boolean compress = ((Boolean) getSetting(mergedProperties, FireboltSessionProperty.COMPRESS)) && !isSystemEngine; @@ -85,37 +90,76 @@ public static FireboltProperties of(Properties... properties) { int tcpKeepIdle = getSetting(mergedProperties, FireboltSessionProperty.TCP_KEEP_IDLE); int tcpKeepCount = getSetting(mergedProperties, FireboltSessionProperty.TCP_KEEP_COUNT); boolean logResultSet = getSetting(mergedProperties, FireboltSessionProperty.LOG_RESULT_SET); + String configuredEnvironment = getSetting(mergedProperties, FireboltSessionProperty.ENVIRONMENT); String driverVersions = getSetting(mergedProperties, FireboltSessionProperty.USER_DRIVERS); String clientVersions = getSetting(mergedProperties, FireboltSessionProperty.USER_CLIENTS); - String host = getHost(mergedProperties); + String environment = getEnvironment(configuredEnvironment, mergedProperties); + String host = getHost(configuredEnvironment, mergedProperties); Integer port = getPort(mergedProperties, ssl); - String database = getDatabase(mergedProperties, path); String accessToken = getSetting(mergedProperties, FireboltSessionProperty.ACCESS_TOKEN); + Map additionalProperties = getFireboltCustomProperties(mergedProperties); return FireboltProperties.builder().ssl(ssl).sslCertificatePath(sslRootCertificate).sslMode(sslMode).path(path) - .port(port).database(database).compress(compress).user(user).password(password).host(host) + .port(port).database(database).compress(compress).principal(principal).secret(secret).host(host) .additionalProperties(additionalProperties).account(account).engine(engine) .keepAliveTimeoutMillis(keepAliveMillis).maxConnectionsTotal(maxTotal).maxRetries(maxRetries) .bufferSize(bufferSize).socketTimeoutMillis(socketTimeout).connectionTimeoutMillis(connectionTimeout) .tcpKeepInterval(tcpKeepInterval).tcpKeepCount(tcpKeepCount).tcpKeepIdle(tcpKeepIdle) .logResultSet(logResultSet).systemEngine(isSystemEngine) + .environment(environment) .userDrivers(driverVersions) .userClients(clientVersions) .accessToken(accessToken) .build(); } - private static String getHost(Properties properties) { + private static String getEngine(Properties mergedProperties) { + return getSetting(mergedProperties, FireboltSessionProperty.ENGINE); + } + + private static String getHost(String environment, Properties properties ) { String host = getSetting(properties, FireboltSessionProperty.HOST); if (StringUtils.isEmpty(host)) { - throw new IllegalArgumentException("Invalid host: The host is missing or empty"); + return format("api.%s.firebolt.io", environment); } else { return host; } } + /** + * Discovers environment name from host if it matches pattern {@code api.ENV.firebolt.io} + * @param environment - the environment from properties or default value as defined in {@link FireboltSessionProperty#ENVIRONMENT} + * @param properties - configuration properties + * @return the environment value + * @throws IllegalStateException if environment extracted from host is not equal to given one. + */ + private static String getEnvironment(String environment, @NotNull Properties properties) { + Pattern environmentalHost = Pattern.compile("api\\.(.+?)\\.firebolt\\.io"); + String envFromProps = Stream.concat(Stream.of(FireboltSessionProperty.ENVIRONMENT.getKey()), Stream.of(FireboltSessionProperty.ENVIRONMENT.getAliases())) + .map(properties::getProperty) + .filter(Objects::nonNull).findFirst() + .orElse(null); + String envFromHost = null; + String host = getSetting(properties, FireboltSessionProperty.HOST); + if (host != null) { + Matcher m = environmentalHost.matcher(host); + if (m.find() && m.group(1) != null) { + envFromHost = m.group(1); + } + } + if (envFromHost != null) { + if (envFromProps == null) { + return envFromHost; + } + if (!Objects.equals(environment, envFromHost)) { + throw new IllegalStateException(format("Environment %s does not match host %s", environment, host)); + } + } + return environment; + } + @NonNull private static Integer getPort(Properties properties, boolean ssl) { Integer port = getSetting(properties, FireboltSessionProperty.PORT); @@ -128,14 +172,14 @@ private static Integer getPort(Properties properties, boolean ssl) { private static String getDatabase(Properties properties, String path) throws IllegalArgumentException { String database = getSetting(properties, FireboltSessionProperty.DATABASE); if (StringUtils.isEmpty(database)) { - if ("/".equals(path)) { - throw new IllegalArgumentException("A database must be provided"); + if ("/".equals(path) || StringUtils.isEmpty(path)) { + return null; } else { Matcher m = DB_PATH_PATTERN.matcher(path); if (m.matches()) { return m.group(1); } else { - throw new IllegalArgumentException(String.format("The database provided is invalid %s", path)); + throw new IllegalArgumentException(format("The database provided is invalid %s", path)); } } } else { @@ -194,7 +238,7 @@ public static FireboltProperties copy(FireboltProperties properties) { } private static boolean isSystemEngine(String engine) { - return StringUtils.equalsIgnoreCase(SYSTEM_ENGINE_NAME, engine); + return engine == null; } public void addProperty(@NonNull String key, String value) { diff --git a/src/main/java/com/firebolt/jdbc/connection/settings/FireboltQueryParameterKey.java b/src/main/java/com/firebolt/jdbc/connection/settings/FireboltQueryParameterKey.java index ee2a183a3..41d503f98 100644 --- a/src/main/java/com/firebolt/jdbc/connection/settings/FireboltQueryParameterKey.java +++ b/src/main/java/com/firebolt/jdbc/connection/settings/FireboltQueryParameterKey.java @@ -6,8 +6,13 @@ @RequiredArgsConstructor @Getter public enum FireboltQueryParameterKey { - DATABASE("database"), QUERY_ID("query_id"), COMPRESS("compress"), DEFAULT_FORMAT("default_format"), - OUTPUT_FORMAT("output_format"); + DATABASE("database"), + QUERY_ID("query_id"), + COMPRESS("compress"), + DEFAULT_FORMAT("default_format"), + OUTPUT_FORMAT("output_format"), + ACCOUNT_ID("account_id"), + ; private final String key; } \ No newline at end of file diff --git a/src/main/java/com/firebolt/jdbc/connection/settings/FireboltSessionProperty.java b/src/main/java/com/firebolt/jdbc/connection/settings/FireboltSessionProperty.java index 71621e3c6..78ca44038 100644 --- a/src/main/java/com/firebolt/jdbc/connection/settings/FireboltSessionProperty.java +++ b/src/main/java/com/firebolt/jdbc/connection/settings/FireboltSessionProperty.java @@ -10,7 +10,7 @@ @Getter public enum FireboltSessionProperty { - PATH("path", "/", String.class, "Path component of the URI"), + PATH("path", "", String.class, "Path component of the URI"), BUFFER_SIZE("buffer_size", 65536, Integer.class, "The buffer used to create the ResultSet in bytes"), SSL("ssl", true, Boolean.class, "Enable SSL/TLS for the connection"), SSL_CERTIFICATE_PATH("ssl_certificate_path", "", String.class, "SSL/TLS root certificate", "sslrootcert"), @@ -44,18 +44,21 @@ public enum FireboltSessionProperty { */ "compress", true, Boolean.class, "Whether to compress transferred data or not. Compressed by default"), DATABASE("database", null, String.class, "default database name"), - PASSWORD("password", null, String.class, "user password - null by default"), - USER("user", null, String.class, "user name - null by default"), + // Typically client_secret property should be used, but password is the standard JDBC property supported by all tools, so it is silently defined here as alias. Also see CLIENT_ID. + CLIENT_SECRET("client_secret", null, String.class, "client secret - null by default", "password"), + // Typically client_id property should be used, but user is the standard JDBC property supported by all tools, so it is silently defined here as alias. Also see CLIENT_SECRET + CLIENT_ID("client_id", null, String.class, "client ID - null by default", "user"), HOST("host", null, String.class, "Firebolt host - null by default"), PORT("port", null, Integer.class, "Firebolt port - null by default"), ENGINE("engine", null, String.class, "engine - null by default", "engine_name"), ACCOUNT("account", null, String.class, "account - null by default"), + ACCOUNT_ID("account_id", null, String.class, "accountId - null by default"), LOG_RESULT_SET("log_result_set", false, Boolean.class, "When set to true, the result of the queries executed are logged with the log level INFO. This has a negative performance impact and should be enabled only for debugging purposes"), USER_DRIVERS("user_drivers", null, String.class, "user drivers"), USER_CLIENTS("user_clients", null, String.class, "user clients"), ACCESS_TOKEN("access_token", null, String.class, "access token"), - + ENVIRONMENT("environment", "app", String.class, "Firebolt environment", "env"), // We keep all the deprecated properties to ensure backward compatibility - but // they do not have any effect. @Deprecated diff --git a/src/main/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadata.java b/src/main/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadata.java index 1f6eaa2d1..c0d9377f6 100644 --- a/src/main/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadata.java +++ b/src/main/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadata.java @@ -354,7 +354,7 @@ public boolean allTablesAreSelectable() throws SQLException { @Override @ExcludeFromJacocoGeneratedReport public String getUserName() throws SQLException { - return connection.getSessionProperties().getUser(); + return connection.getSessionProperties().getPrincipal(); } @Override diff --git a/src/main/java/com/firebolt/jdbc/metadata/MetadataUtil.java b/src/main/java/com/firebolt/jdbc/metadata/MetadataUtil.java index a56685f7a..31b16cfb6 100644 --- a/src/main/java/com/firebolt/jdbc/metadata/MetadataUtil.java +++ b/src/main/java/com/firebolt/jdbc/metadata/MetadataUtil.java @@ -1,13 +1,12 @@ package com.firebolt.jdbc.metadata; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; -import com.firebolt.jdbc.Query; +import org.apache.commons.lang3.StringUtils; +import lombok.Builder; import lombok.NonNull; +import lombok.Value; import lombok.experimental.UtilityClass; @UtilityClass @@ -102,4 +101,61 @@ public static String getDatabaseVersionQuery(String engine) { .conditions(Collections.singletonList(String.format("engine_name iLIKE '%s%%'", engine))).build() .toSql(); } + + /** + * Represents a SQL query that can be sent to Firebolt to receive metadata info + */ + @Builder + @Value + public static class Query { + String select; + String from; + String innerJoin; + String orderBy; + List conditions; + + /** + * Parse the object to a SQL query that can be sent to Firebolt + * + * @return SQL query that can be sent to Firebolt + */ + public String toSql() { + StringBuilder query = new StringBuilder(); + if (StringUtils.isBlank(select)) { + throw new IllegalStateException("Cannot create query: SELECT cannot be blank"); + } + if (StringUtils.isBlank(from)) { + throw new IllegalStateException("Cannot create query: FROM cannot be blank"); + } + + query.append("SELECT ").append(select); + query.append(" FROM ").append(from); + if (StringUtils.isNotBlank(innerJoin)) { + query.append(" JOIN ").append(innerJoin); + } + query.append(getConditionsPart()); + if (StringUtils.isNotBlank(orderBy)) { + query.append(" order by ").append(orderBy); + } + return query.toString(); + } + + private String getConditionsPart() { + StringBuilder agg = new StringBuilder(); + Iterator iter = conditions.iterator(); + if (iter.hasNext()) { + agg.append(" WHERE "); + } + if (iter.hasNext()) { + String entry = iter.next(); + agg.append(entry); + } + while (iter.hasNext()) { + String entry = iter.next(); + agg.append(" AND ").append(entry); + } + return agg.toString(); + } + } + } diff --git a/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java b/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java index 72f366b3f..b1f814a02 100644 --- a/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java +++ b/src/main/java/com/firebolt/jdbc/resultset/FireboltResultSet.java @@ -11,12 +11,12 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import com.firebolt.jdbc.QueryResult; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.text.StringEscapeUtils; import com.firebolt.jdbc.util.LoggerUtil; -import com.firebolt.jdbc.QueryResult; import com.firebolt.jdbc.annotation.ExcludeFromJacocoGeneratedReport; import com.firebolt.jdbc.annotation.NotImplemented; import com.firebolt.jdbc.exception.FireboltException; diff --git a/src/main/java/com/firebolt/jdbc/service/FireboltAccountIdService.java b/src/main/java/com/firebolt/jdbc/service/FireboltAccountIdService.java new file mode 100644 index 000000000..f49c3d282 --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/service/FireboltAccountIdService.java @@ -0,0 +1,17 @@ +package com.firebolt.jdbc.service; + +import com.firebolt.jdbc.client.account.FireboltAccount; +import com.firebolt.jdbc.client.account.FireboltAccountRetriever; +import com.firebolt.jdbc.exception.FireboltException; + +public class FireboltAccountIdService { + private final FireboltAccountRetriever firebolAccountClient; + + public FireboltAccountIdService(FireboltAccountRetriever firebolAccountClient) { + this.firebolAccountClient = firebolAccountClient; + } + + public String getValue(String accessToken, String account) throws FireboltException { + return firebolAccountClient.retrieve(accessToken, account).getId(); + } +} diff --git a/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java b/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java index 56b17727e..07ed76c8f 100644 --- a/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java +++ b/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java @@ -7,15 +7,16 @@ import lombok.CustomLog; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; -import org.apache.commons.lang3.StringUtils; import javax.xml.bind.DatatypeConverter; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Optional; -import java.util.concurrent.TimeUnit; + +import static java.lang.String.format; +import static java.util.concurrent.TimeUnit.SECONDS; +import static net.jodah.expiringmap.ExpirationPolicy.CREATED; @RequiredArgsConstructor @CustomLog @@ -29,36 +30,29 @@ public class FireboltAuthenticationService { public FireboltConnectionTokens getConnectionTokens(String host, FireboltProperties loginProperties) throws FireboltException { try { - ConnectParams connectionParams = new ConnectParams(host, loginProperties.getUser(), - loginProperties.getPassword()); + ConnectParams connectionParams = new ConnectParams(host, loginProperties.getPrincipal(), loginProperties.getSecret()); synchronized (this) { FireboltConnectionTokens foundToken = tokensMap.get(connectionParams); if (foundToken != null) { log.debug("Using the token of {} from the cache", host); return foundToken; - } else { - FireboltConnectionTokens fireboltConnectionTokens = fireboltAuthenticationClient - .postConnectionTokens(host, loginProperties.getUser(), loginProperties.getPassword()); - long durationInSeconds = getCachingDurationInSeconds( - fireboltConnectionTokens.getExpiresInSeconds()); - tokensMap.put(connectionParams, fireboltConnectionTokens, ExpirationPolicy.CREATED, - durationInSeconds, TimeUnit.SECONDS); - return fireboltConnectionTokens; } + FireboltConnectionTokens fireboltConnectionTokens = fireboltAuthenticationClient + .postConnectionTokens(host, loginProperties.getPrincipal(), loginProperties.getSecret(), loginProperties.getEnvironment()); + long durationInSeconds = getCachingDurationInSeconds(fireboltConnectionTokens.getExpiresInSeconds()); + tokensMap.put(connectionParams, fireboltConnectionTokens, CREATED, durationInSeconds, SECONDS); + return fireboltConnectionTokens; } } catch (Exception e) { log.error("Failed to connect to Firebolt", e); - if (e instanceof FireboltException - && StringUtils.isNotEmpty(((FireboltException) e).getErrorMessageFromServer())) { - throw new FireboltException(String.format( - "Failed to connect to Firebolt with the error from the server: %s, see logs for more info.", - ((FireboltException) e).getErrorMessageFromServer()), e); - } else { - throw new FireboltException( - String.format("Failed to connect to Firebolt with the error: %s, see logs for more info.", - e.getMessage()), - e); + String errorMessageTemplate = "Failed to connect to Firebolt with the error%s: %s, see logs for more info."; + if (e instanceof FireboltException) { + String server = ((FireboltException) e).getErrorMessageFromServer(); + if (server != null) { + throw new FireboltException(format(errorMessageTemplate, " from the server", server), e); + } } + throw new FireboltException(format(errorMessageTemplate, "", e.getMessage()), e); } } @@ -80,8 +74,7 @@ private long getCachingDurationInSeconds(long expireInSeconds) { public void removeConnectionTokens(String host, FireboltProperties loginProperties) throws FireboltException { try { log.debug("Removing connection token for host {}", host); - ConnectParams connectionParams = new ConnectParams(host, loginProperties.getUser(), - loginProperties.getPassword()); + ConnectParams connectionParams = new ConnectParams(host, loginProperties.getPrincipal(), loginProperties.getSecret()); tokensMap.remove(connectionParams); } catch (NoSuchAlgorithmException e) { throw new FireboltException("Could not remove connection tokens", e); @@ -93,11 +86,11 @@ private static class ConnectParams { public final String fireboltHost; public final String credentialsHash; - public ConnectParams(String fireboltHost, String user, String password) throws NoSuchAlgorithmException { + public ConnectParams(String fireboltHost, String principal, String secret) throws NoSuchAlgorithmException { this.fireboltHost = fireboltHost; MessageDigest sha256Instance = MessageDigest.getInstance("SHA-256"); - Optional.ofNullable(user).map(String::getBytes).ifPresent(sha256Instance::update); - Optional.ofNullable(password).map(String::getBytes).ifPresent(sha256Instance::update); + Optional.ofNullable(principal).map(String::getBytes).ifPresent(sha256Instance::update); + Optional.ofNullable(secret).map(String::getBytes).ifPresent(sha256Instance::update); this.credentialsHash = DatatypeConverter.printHexBinary(sha256Instance.digest()); } } diff --git a/src/main/java/com/firebolt/jdbc/service/FireboltEngineService.java b/src/main/java/com/firebolt/jdbc/service/FireboltEngineService.java index 3ebac44b2..7b9e35837 100644 --- a/src/main/java/com/firebolt/jdbc/service/FireboltEngineService.java +++ b/src/main/java/com/firebolt/jdbc/service/FireboltEngineService.java @@ -1,117 +1,83 @@ package com.firebolt.jdbc.service; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; - -import com.firebolt.jdbc.client.account.FireboltAccountClient; -import com.firebolt.jdbc.client.account.response.FireboltAccountResponse; -import com.firebolt.jdbc.client.account.response.FireboltDefaultDatabaseEngineResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineIdResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineResponse; import com.firebolt.jdbc.connection.Engine; -import com.firebolt.jdbc.connection.settings.FireboltProperties; +import com.firebolt.jdbc.connection.FireboltConnection; import com.firebolt.jdbc.exception.FireboltException; - import lombok.CustomLog; import lombok.RequiredArgsConstructor; +import javax.annotation.Nullable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Optional; + +import static java.lang.String.format; + @RequiredArgsConstructor @CustomLog public class FireboltEngineService { - private static final Set ENGINE_NOT_READY_STATUSES = new HashSet<>( - Arrays.asList("ENGINE_STATUS_PROVISIONING_STARTED", "ENGINE_STATUS_PROVISIONING_PENDING", - "ENGINE_STATUS_PROVISIONING_FINISHED", "ENGINE_STATUS_RUNNING_REVISION_STARTING")); - private static final String ERROR_NO_ENGINE_ATTACHED = "There is no Firebolt engine running on %s attached to the database %s. To connect first make sure there is a running engine and then try again."; - private static final String ERROR_NO_ENGINE_WITH_NAME = "There is no Firebolt engine running on %s with the name %s. To connect first make sure there is a running engine and then try again."; - private final FireboltAccountClient fireboltAccountClient; - - /** - * Returns the engine - * - * @param connectionUrl the connection url - * @param loginProperties properties to login - * @param accessToken the access token - * @return the engine - */ - public Engine getEngine(String connectionUrl, FireboltProperties loginProperties, String accessToken) - throws FireboltException { - String accountId = null; - Engine engine; - try { - if (StringUtils.isNotEmpty(loginProperties.getAccount())) { - accountId = getAccountId(connectionUrl, loginProperties.getAccount(), accessToken).orElse(null); - } - if (StringUtils.isEmpty(loginProperties.getEngine())) { - engine = getDefaultEngine(connectionUrl, accountId, loginProperties.getDatabase(), accessToken); - } else { - engine = getEngineWithName(connectionUrl, accountId, loginProperties.getEngine(), accessToken); - } - } catch (FireboltException e) { - throw e; - } catch (Exception e) { - throw new FireboltException("Failed to get engine", e); - } - validateEngineIsNotStarting(engine); - return engine; - } - - private Engine getEngineWithName(String connectionUrl, String accountId, String engineName, String accessToken) - throws FireboltException, IOException { - FireboltEngineIdResponse response = fireboltAccountClient.getEngineId(connectionUrl, accountId, engineName, - accessToken); - String engineID = Optional.ofNullable(response).map(FireboltEngineIdResponse::getEngine) - .map(FireboltEngineIdResponse.Engine::getEngineId).orElseThrow(() -> new FireboltException( - "Failed to extract engine id field from the server response: the response from the server is invalid.")); - FireboltEngineResponse fireboltEngineResponse = fireboltAccountClient.getEngine(connectionUrl, accountId, - engineName, engineID, accessToken); - - return Optional.ofNullable(fireboltEngineResponse).map(FireboltEngineResponse::getEngine) - .filter(e -> StringUtils.isNotEmpty(e.getEndpoint())) - .map(e -> Engine.builder().endpoint(e.getEndpoint()).id(engineID).status(e.getCurrentStatus()) - .name(engineName).build()) - .orElseThrow(() -> new FireboltException( - String.format(ERROR_NO_ENGINE_WITH_NAME, connectionUrl, engineName))); - } + private static final String ENGINE_URL = "url"; + private static final String STATUS_FIELD = "status"; + private static final String ENGINE_NAME_FIELD = "engine_name"; + private static final String RUNNING_STATUS = "running"; + private static final String ENGINE_QUERY = + "SELECT engs.url, engs.attached_to, dbs.database_name, engs.status, engs.engine_name " + + "FROM information_schema.engines as engs " + + "LEFT JOIN information_schema.databases as dbs ON engs.attached_to = dbs.database_name " + + "WHERE engs.engine_name = ?"; + private static final String DATABASE_QUERY = "SELECT database_name FROM information_schema.databases WHERE database_name=?"; - private Engine getDefaultEngine(String connectionUrl, String accountId, String database, String accessToken) - throws FireboltException, IOException { - FireboltDefaultDatabaseEngineResponse defaultEngine = fireboltAccountClient - .getDefaultEngineByDatabaseName(connectionUrl, accountId, database, accessToken); - return Optional.ofNullable(defaultEngine).map(FireboltDefaultDatabaseEngineResponse::getEngineUrl) - .map(url -> Engine.builder().endpoint(url).build()).orElseThrow( - () -> new FireboltException(String.format(ERROR_NO_ENGINE_ATTACHED, connectionUrl, database))); - } + private final FireboltConnection fireboltConnection; - private Optional getAccountId(String connectionUrl, String account, String accessToken) - throws FireboltException, IOException { - FireboltAccountResponse fireboltAccountResponse = fireboltAccountClient.getAccount(connectionUrl, account, - accessToken); - return Optional.ofNullable(fireboltAccountResponse).map(FireboltAccountResponse::getAccountId); - } + /** + * Extracts the engine name from host + * + * @param engineHost engine host + * @return the engine name + */ + public String getEngineNameByHost(String engineHost) throws FireboltException { + return Optional.ofNullable(engineHost).filter(host -> host.contains(".")).map(host -> host.split("\\.")[0]) + .map(host -> host.replace("-", "_")).orElseThrow(() -> new FireboltException( + format("Could not establish the engine from the host: %s", engineHost))); + } - private void validateEngineIsNotStarting(Engine engine) throws FireboltException { - if (StringUtils.isNotEmpty(engine.getId()) && StringUtils.isNotEmpty(engine.getStatus()) - && ENGINE_NOT_READY_STATUSES.contains(engine.getStatus())) { - throw new FireboltException(String.format( - "The engine %s is currently starting. Please wait until the engine is on and then execute the query again.", engine.getName())); - } - } + public boolean doesDatabaseExist(String database) throws SQLException { + try (PreparedStatement ps = fireboltConnection.prepareStatement(DATABASE_QUERY)) { + ps.setString(1, database); + try (ResultSet rs = ps.executeQuery()) { + return rs.next(); + } + } + } - /** - * Extracts the engine name from host - * - * @param engineHost engine host - * @return the engine name - */ - public String getEngineNameFromHost(String engineHost) throws FireboltException { - return Optional.ofNullable(engineHost).filter(host -> host.contains(".")).map(host -> host.split("\\.")[0]) - .map(host -> host.replace("-", "_")).orElseThrow(() -> new FireboltException( - String.format("Could not establish the engine from the host: %s", engineHost))); - } + public Engine getEngine(String engine, @Nullable String database) throws SQLException { + if (engine == null) { + throw new IllegalArgumentException("Cannot retrieve engine parameters because its name is null"); + } + try (PreparedStatement ps = fireboltConnection.prepareStatement(ENGINE_QUERY)) { + ps.setString(1, engine); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + throw new FireboltException(format("The engine with the name %s could not be found", engine)); + } + String status = rs.getString(STATUS_FIELD); + if (!isEngineRunning(status)) { + throw new FireboltException(format("The engine with the name %s is not running. Status: %s", engine, status)); + } + String attachedDatabase = rs.getString("attached_to"); + if (attachedDatabase == null) { + throw new FireboltException(format("The engine with the name %s is not attached to any database", engine)); + } + if (database != null && !database.equals(attachedDatabase)) { + throw new FireboltException(format("The engine with the name %s is not attached to database %s", engine, database)); + } + return new Engine(rs.getString(ENGINE_URL), status, rs.getString(ENGINE_NAME_FIELD), attachedDatabase); + } + } + } + private boolean isEngineRunning(String status) { + return RUNNING_STATUS.equalsIgnoreCase(status); + } } diff --git a/src/main/java/com/firebolt/jdbc/service/FireboltGatewayUrlService.java b/src/main/java/com/firebolt/jdbc/service/FireboltGatewayUrlService.java new file mode 100644 index 000000000..f3df43fe4 --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/service/FireboltGatewayUrlService.java @@ -0,0 +1,16 @@ +package com.firebolt.jdbc.service; + +import com.firebolt.jdbc.client.account.FireboltAccountRetriever; +import com.firebolt.jdbc.client.gateway.GatewayUrlResponse; +import com.firebolt.jdbc.exception.FireboltException; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class FireboltGatewayUrlService { + + private final FireboltAccountRetriever fireboltGatewayUrlClient; + + public String getUrl(String accessToken, String account) throws FireboltException { + return fireboltGatewayUrlClient.retrieve(accessToken, account).getEngineUrl(); + } +} diff --git a/src/main/java/com/firebolt/jdbc/service/FireboltStatementService.java b/src/main/java/com/firebolt/jdbc/service/FireboltStatementService.java index 2b2b87579..be1cd7e09 100644 --- a/src/main/java/com/firebolt/jdbc/service/FireboltStatementService.java +++ b/src/main/java/com/firebolt/jdbc/service/FireboltStatementService.java @@ -28,7 +28,6 @@ public class FireboltStatementService { private static final String UNKNOWN_TABLE_NAME = "unknown"; private final StatementClient statementClient; - private final boolean systemEngine; /** * Executes statement @@ -38,12 +37,12 @@ public class FireboltStatementService { * @param queryTimeout query timeout * @param maxRows max rows * @param standardSql indicates if standard sql should be used + * @param systemEngine indicates if system engine is used * @param statement the statement * @return an InputStream with the result */ public Optional execute(StatementInfoWrapper statementInfoWrapper, - FireboltProperties properties, int queryTimeout, int maxRows, boolean standardSql, - FireboltStatement statement) + FireboltProperties properties, int queryTimeout, int maxRows, boolean standardSql, boolean systemEngine, FireboltStatement statement) throws SQLException { InputStream is = statementClient.executeSqlStatement(statementInfoWrapper, properties, systemEngine, queryTimeout, standardSql); if (statementInfoWrapper.getType() == StatementType.QUERY) { @@ -59,7 +58,7 @@ public Optional execute(StatementInfoWrapper statementInfoWrapper, public void abortStatement(@NonNull String statementId, @NonNull FireboltProperties properties) throws FireboltException { - if (systemEngine) { + if (properties.isSystemEngine()) { throw new FireboltException("Cannot cancel a statement using a system engine", INVALID_REQUEST); } else { statementClient.abortStatement(statementId, properties); diff --git a/src/main/java/com/firebolt/jdbc/statement/FireboltStatement.java b/src/main/java/com/firebolt/jdbc/statement/FireboltStatement.java index 536cb22f7..49c5932d8 100644 --- a/src/main/java/com/firebolt/jdbc/statement/FireboltStatement.java +++ b/src/main/java/com/firebolt/jdbc/statement/FireboltStatement.java @@ -114,7 +114,7 @@ private Optional execute(StatementInfoWrapper statementInfoWrapper, b log.debug("The property from the query {} was stored", runningStatementId); } else { Optional currentRs = statementService.execute(statementInfoWrapper, - this.sessionProperties, this.queryTimeout, this.maxRows, isStandardSql, this); + this.sessionProperties, this.queryTimeout, this.maxRows, isStandardSql, sessionProperties.isSystemEngine(), this); if (currentRs.isPresent()) { resultSet = currentRs.get(); currentUpdateCount = -1; // Always -1 when returning a ResultSet diff --git a/src/main/java/com/firebolt/jdbc/util/PropertyUtil.java b/src/main/java/com/firebolt/jdbc/util/PropertyUtil.java index 7b460cfb0..3be9bc77b 100644 --- a/src/main/java/com/firebolt/jdbc/util/PropertyUtil.java +++ b/src/main/java/com/firebolt/jdbc/util/PropertyUtil.java @@ -7,9 +7,9 @@ import java.util.Properties; import java.util.stream.Collectors; +import com.firebolt.jdbc.connection.UrlUtil; import org.apache.commons.lang3.StringUtils; -import com.firebolt.jdbc.connection.FireboltJdbcUrlUtil; import com.firebolt.jdbc.connection.settings.FireboltProperties; import com.firebolt.jdbc.connection.settings.FireboltSessionProperty; @@ -31,7 +31,7 @@ public class PropertyUtil { */ public DriverPropertyInfo[] getPropertyInfo(String url, Properties properties) { try { - Properties propertiesFromUrl = FireboltJdbcUrlUtil.extractProperties(url); + Properties propertiesFromUrl = UrlUtil.extractProperties(url); for (Object key : propertiesFromUrl.keySet()) { properties.put(key, propertiesFromUrl.get(key.toString())); } diff --git a/src/test/java/com/firebolt/FireboltDriverTest.java b/src/test/java/com/firebolt/FireboltDriverTest.java index b63594f12..e58924ed1 100644 --- a/src/test/java/com/firebolt/FireboltDriverTest.java +++ b/src/test/java/com/firebolt/FireboltDriverTest.java @@ -65,18 +65,18 @@ void jdbcCompliant() { @Test void version() { FireboltDriver fireboltDriver = new FireboltDriver(); - assertEquals(2, fireboltDriver.getMajorVersion()); - assertEquals(4, fireboltDriver.getMinorVersion()); + assertEquals(3, fireboltDriver.getMajorVersion()); + assertEquals(0, fireboltDriver.getMinorVersion()); } @ParameterizedTest @CsvSource(value = { "jdbc:firebolt,,", - "jdbc:firebolt://api.dev.firebolt.io/db_name,,host=api.dev.firebolt.io;path=/db_name", - "jdbc:firebolt://api.dev.firebolt.io/db_name?account=test,,host=api.dev.firebolt.io;path=/db_name;account=test", - "jdbc:firebolt://api.dev.firebolt.io/db_name?account=test,user=usr;password=pwd,host=api.dev.firebolt.io;path=/db_name;account=test;user=usr;password=pwd", // legit:ignore-secrets - "jdbc:firebolt://api.dev.firebolt.io/db_name,user=usr;password=pwd,host=api.dev.firebolt.io;path=/db_name;user=usr;password=pwd" // legit:ignore-secrets + "jdbc:firebolt:db_name,,environment=app;path=db_name", + "jdbc:firebolt:db_name?account=test,,environment=app;path=db_name;account=test", + "jdbc:firebolt:db_name?account=test,client_id=usr;client_secret=pwd,environment=app;path=db_name;account=test;client_id=usr;client_secret=pwd", + "jdbc:firebolt:db_name,client_id=usr;client_secret=pwd,environment=app;path=db_name;client_id=usr;client_secret=pwd" }, delimiter = ',') void getPropertyInfo(String url, String propStr, String expectedInfoStr) throws SQLException { diff --git a/src/test/java/com/firebolt/jdbc/RawStatementTest.java b/src/test/java/com/firebolt/jdbc/RawStatementTest.java index 20e03ade9..197b23ace 100644 --- a/src/test/java/com/firebolt/jdbc/RawStatementTest.java +++ b/src/test/java/com/firebolt/jdbc/RawStatementTest.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.Collections; +import com.firebolt.jdbc.metadata.MetadataUtil.Query; import org.junit.jupiter.api.Test; class RawStatementTest { diff --git a/src/test/java/com/firebolt/jdbc/client/FireboltClientTest.java b/src/test/java/com/firebolt/jdbc/client/FireboltClientTest.java index 443e5134f..8a316e4e9 100644 --- a/src/test/java/com/firebolt/jdbc/client/FireboltClientTest.java +++ b/src/test/java/com/firebolt/jdbc/client/FireboltClientTest.java @@ -128,7 +128,7 @@ void emptyResponseFromServer() throws IOException { Call call = mock(); when(call.execute()).thenReturn(response); when(okHttpClient.newCall(any())).thenReturn(call); - FireboltClient client = new FireboltClient(okHttpClient, mock(), null, null, new ObjectMapper()) {}; + FireboltClient client = new FireboltClient(okHttpClient, new ObjectMapper(), mock(), null, null) {}; assertEquals("Cannot get resource: the response from the server is empty", assertThrows(FireboltException.class, () -> client.getResource("http://foo", "foo", "token", String.class)).getMessage()); } } diff --git a/src/test/java/com/firebolt/jdbc/client/account/FireboltAccountClientTest.java b/src/test/java/com/firebolt/jdbc/client/account/FireboltAccountClientTest.java deleted file mode 100644 index 2ba837462..000000000 --- a/src/test/java/com/firebolt/jdbc/client/account/FireboltAccountClientTest.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.firebolt.jdbc.client.account; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.firebolt.jdbc.client.account.response.FireboltAccountResponse; -import com.firebolt.jdbc.client.account.response.FireboltDefaultDatabaseEngineResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineIdResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineResponse; -import com.firebolt.jdbc.connection.FireboltConnection; -import com.firebolt.jdbc.exception.FireboltException; -import okhttp3.Call; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.junit.function.ThrowingRunnable; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.HashMap; -import java.util.Map; - -import static com.firebolt.jdbc.client.UserAgentFormatter.userAgent; -import static java.lang.String.format; -import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; -import static java.net.HttpURLConnection.HTTP_OK; -import static org.junit.Assert.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class FireboltAccountClientTest { - private static final String ACCESS_TOKEN = "token"; - private static final String HOST = "https://host"; - private static final String ACCOUNT = "account"; - private static final String ACCOUNT_ID = "account_id"; - private static final String DB_NAME = "dbName"; - private static final String ENGINE_NAME = "engineName"; - - @Spy - private final ObjectMapper objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - private final ObjectMapper mapper = new ObjectMapper(); - @Captor - private ArgumentCaptor requestArgumentCaptor; - @Mock - private OkHttpClient httpClient; - @Mock - private Call call; - private FireboltAccountClient fireboltAccountClient; - - @Mock - private FireboltConnection fireboltConnection; - - @BeforeEach - void setUp() { - fireboltAccountClient = new FireboltAccountClient(httpClient, objectMapper, fireboltConnection, "ConnA:1.0.9", - "ConnB:2.0.9"); - when(httpClient.newCall(any())).thenReturn(call); - } - - @Test - void shouldGetAccountId() throws Exception { - Response response = mock(Response.class); - when(response.code()).thenReturn(HTTP_OK); - ResponseBody body = mock(ResponseBody.class); - when(body.string()).thenReturn("{\"account_id\":\"12345\"}"); - when(response.body()).thenReturn(body); - when(call.execute()).thenReturn(response); - - FireboltAccountResponse account = fireboltAccountClient.getAccount(HOST, ACCOUNT, ACCESS_TOKEN); - - Map expectedHeader = Map.of("User-Agent", - userAgent("ConnB/2.0.9 JDBC/%s (Java %s; %s %s; ) ConnA/1.0.9"), "Authorization", - "Bearer " + ACCESS_TOKEN); - - verify(httpClient).newCall(requestArgumentCaptor.capture()); - verify(objectMapper).readValue("{\"account_id\":\"12345\"}", FireboltAccountResponse.class); - assertEquals("https://host/iam/v2/accounts:getIdByName?accountName=" + ACCOUNT, - requestArgumentCaptor.getValue().url().toString()); - assertEquals(expectedHeader, extractHeadersMap(requestArgumentCaptor.getValue())); - assertEquals("12345", account.getAccountId()); - } - - @Test - void shouldGetEngineEndpoint() throws Exception { - Response response = mock(Response.class); - when(response.code()).thenReturn(HTTP_OK); - ResponseBody body = mock(ResponseBody.class); - when(response.body()).thenReturn(body); - when(call.execute()).thenReturn(response); - when(response.body().string()).thenReturn(mapper.writeValueAsString(FireboltEngineResponse.builder() - .engine(FireboltEngineResponse.Engine.builder().endpoint("http://engineEndpoint").build()).build())); - when(httpClient.newCall(any())).thenReturn(call); - - FireboltEngineResponse engine = fireboltAccountClient.getEngine(HOST, ENGINE_NAME, DB_NAME, ACCOUNT_ID, - ACCESS_TOKEN); - Map expectedHeader = Map.of("User-Agent", - userAgent("ConnB/2.0.9 JDBC/%s (Java %s; %s %s; ) ConnA/1.0.9"), "Authorization", - "Bearer " + ACCESS_TOKEN); - - verify(httpClient).newCall(requestArgumentCaptor.capture()); - verify(objectMapper).readValue("{\"engine\":{\"endpoint\":\"http://engineEndpoint\",\"current_status\":null}}", - FireboltEngineResponse.class); - verify(httpClient).newCall(requestArgumentCaptor.capture()); - assertEquals("https://host/core/v1/accounts/engineName/engines/" + ACCOUNT_ID, - requestArgumentCaptor.getValue().url().toString()); - assertEquals(expectedHeader, extractHeadersMap(requestArgumentCaptor.getValue())); - assertEquals("http://engineEndpoint", engine.getEngine().getEndpoint()); - } - - @Test - void shouldGetDbAddress() throws Exception { - Response response = mock(Response.class); - when(response.code()).thenReturn(HTTP_OK); - ResponseBody body = mock(ResponseBody.class); - when(response.body()).thenReturn(body); - when(call.execute()).thenReturn(response); - when(response.body().string()).thenReturn(mapper - .writeValueAsString(FireboltDefaultDatabaseEngineResponse.builder().engineUrl("http://dbAddress").build())); - - FireboltDefaultDatabaseEngineResponse fireboltDefaultDatabaseEngineResponse = fireboltAccountClient - .getDefaultEngineByDatabaseName(HOST, ACCOUNT_ID, DB_NAME, ACCESS_TOKEN); - Map expectedHeader = Map.of("User-Agent", - userAgent("ConnB/2.0.9 JDBC/%s (Java %s; %s %s; ) ConnA/1.0.9"), "Authorization", - "Bearer " + ACCESS_TOKEN); - - verify(httpClient).newCall(requestArgumentCaptor.capture()); - verify(objectMapper).readValue("{\"engine_url\":\"http://dbAddress\"}", FireboltDefaultDatabaseEngineResponse.class); - assertEquals(format("https://host/core/v1/accounts/%s/engines:getURLByDatabaseName?databaseName=%s", - ACCOUNT_ID, DB_NAME), requestArgumentCaptor.getValue().url().toString()); - assertEquals(expectedHeader, extractHeadersMap(requestArgumentCaptor.getValue())); - assertEquals("http://dbAddress", fireboltDefaultDatabaseEngineResponse.getEngineUrl()); - } - - @Test - void shouldGetEngineId() throws Exception { - Response response = mock(Response.class); - when(response.code()).thenReturn(HTTP_OK); - ResponseBody body = mock(ResponseBody.class); - when(response.body()).thenReturn(body); - when(call.execute()).thenReturn(response); - when(response.body().string()).thenReturn(mapper.writeValueAsString(FireboltEngineIdResponse.builder() - .engine(FireboltEngineIdResponse.Engine.builder().engineId("13").build()).build())); - - FireboltEngineIdResponse fireboltEngineIdResponse = fireboltAccountClient.getEngineId(HOST, ACCOUNT_ID, - ENGINE_NAME, ACCESS_TOKEN); - Map expectedHeader = Map.of("User-Agent", - userAgent("ConnB/2.0.9 JDBC/%s (Java %s; %s %s; ) ConnA/1.0.9"), "Authorization", - "Bearer " + ACCESS_TOKEN); - - verify(httpClient).newCall(requestArgumentCaptor.capture()); - verify(objectMapper).readValue("{\"engine_id\":{\"engine_id\":\"13\"}}", FireboltEngineIdResponse.class); - assertEquals(format("https://host/core/v1/accounts/%s/engines:getIdByName?engine_name=%s", ACCOUNT_ID, - ENGINE_NAME), requestArgumentCaptor.getValue().url().toString()); - assertEquals(expectedHeader, extractHeadersMap(requestArgumentCaptor.getValue())); - assertEquals("13", fireboltEngineIdResponse.getEngine().getEngineId()); - } - - @Test - void shouldThrowExceptionWhenStatusCodeIsNotFound() throws Exception { - shouldThrowException(HTTP_NOT_FOUND, () -> fireboltAccountClient.getAccount(HOST, ACCOUNT, ACCESS_TOKEN), null); - } - - @Test - void shouldThrowExceptionWhenStatusCodeIsNotOk() throws Exception { - shouldThrowException(HTTP_BAD_GATEWAY, () -> fireboltAccountClient.getAccount(HOST, ACCOUNT, ACCESS_TOKEN), null); - } - - @Test - void shouldThrowExceptionWithDBNotFoundErrorMessageWhenDBIsNotFound() throws Exception { - shouldThrowException(HTTP_NOT_FOUND, () -> fireboltAccountClient.getDefaultEngineByDatabaseName(HOST, ACCOUNT, DB_NAME, ACCESS_TOKEN), "The database with the name dbName could not be found"); - } - - @Test - void shouldThrowExceptionWithEngineNotFoundErrorMessageWhenEngineAddressIsNotFound() throws Exception { - shouldThrowException(HTTP_NOT_FOUND, () -> fireboltAccountClient.getEngine(HOST, ACCOUNT, ENGINE_NAME, "123", ACCESS_TOKEN), "The address of the engine with name engineName and id 123 could not be found"); - } - - @Test - void shouldThrowExceptionWithEngineNotFoundErrorMessageWhenEngineIdIsNotFound() throws Exception { - shouldThrowException(HTTP_NOT_FOUND, () -> fireboltAccountClient.getEngineId(HOST, ACCOUNT, ENGINE_NAME, ACCESS_TOKEN), "The engine engineName could not be found"); - } - - private void shouldThrowException(int httpStatus, ThrowingRunnable runnable, String expectedMessage) throws Exception { - Response response = mock(Response.class); - when(response.code()).thenReturn(httpStatus); - ResponseBody body = mock(ResponseBody.class); - when(response.body()).thenReturn(body); - when(call.execute()).thenReturn(response); - FireboltException fireboltException = assertThrows(FireboltException.class, runnable); - if (expectedMessage != null) { - assertEquals(expectedMessage, fireboltException.getMessage()); - } - } - - private Map extractHeadersMap(Request request) { - Map headers = new HashMap<>(); - request.headers().forEach(header -> headers.put(header.getFirst(), header.getSecond())); - return headers; - } -} diff --git a/src/test/java/com/firebolt/jdbc/client/authentication/AuthenticationRequestFactoryTest.java b/src/test/java/com/firebolt/jdbc/client/authentication/AuthenticationRequestFactoryTest.java deleted file mode 100644 index 4a0aaebe2..000000000 --- a/src/test/java/com/firebolt/jdbc/client/authentication/AuthenticationRequestFactoryTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.firebolt.jdbc.client.authentication; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class AuthenticationRequestFactoryTest { - - @Test - void shouldGetServiceAccountRequestWhenUsernameDoesNotContainSpecialCharacter() { - String name = "265576ea-2478-4209-860c-f75f55e7c1f7"; - String password = "hello"; - AuthenticationRequest rq = AuthenticationRequestFactory.getAuthenticationRequest(name, password, "localhost"); - assertTrue(rq instanceof ServiceAccountAuthenticationRequest); - } - - @Test - void shouldGetUsernamePasswordRqWhenUsernameIsAnEmailAddress() { - String name = "tester@firebolt.io"; - String password = "hello"; - AuthenticationRequest rq = AuthenticationRequestFactory.getAuthenticationRequest(name, password, "localhost"); - assertTrue(rq instanceof UsernamePasswordAuthenticationRequest); - } - - @Test - void shouldGetUsernamePasswordRqWhenUsernameIsNullOrEmpty() { - assertTrue(AuthenticationRequestFactory.getAuthenticationRequest(null, null, - null) instanceof UsernamePasswordAuthenticationRequest); - assertTrue(AuthenticationRequestFactory.getAuthenticationRequest("", null, - null) instanceof UsernamePasswordAuthenticationRequest); - } -} \ No newline at end of file diff --git a/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java b/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java index 3e6c3e5a1..bc8f349f8 100644 --- a/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java +++ b/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java @@ -30,6 +30,8 @@ class FireboltAuthenticationClientTest { private static final String USER = "usr"; private static final String PASSWORD = "PA§§WORD"; + private static final String ENV = "ENV"; + @Spy private final ObjectMapper objectMapper = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -62,7 +64,7 @@ void shouldPostConnectionTokens() throws IOException, FireboltException { FireboltAuthenticationResponse.builder().accessToken("a").refreshToken("r").expiresIn(1).build()); when(body.string()).thenReturn(tokensResponse); - fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD); + fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD, ENV); verify(httpClient).newCall(requestArgumentCaptor.capture()); Request actualPost = requestArgumentCaptor.getValue(); @@ -82,7 +84,7 @@ void shouldThrowExceptionWhenStatusCodeIsNotFound() throws Exception { when(httpClient.newCall(any())).thenReturn(call); assertThrows(FireboltException.class, - () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD)); + () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD, ENV)); } @Test @@ -91,7 +93,7 @@ void shouldNotRetryWhenFacingANonRetryableException() throws Exception { when(call.execute()).thenThrow(IOException.class); when(httpClient.newCall(any())).thenReturn(call); - assertThrows(IOException.class, () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD)); + assertThrows(IOException.class, () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD, ENV)); verify(call).execute(); verify(call, times(0)).clone(); } @@ -107,6 +109,6 @@ void shouldThrowExceptionWhenStatusCodeIsForbidden() throws Exception { when(httpClient.newCall(any())).thenReturn(call); assertThrows(FireboltException.class, - () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD)); + () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD, ENV)); } } diff --git a/src/test/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequestTest.java b/src/test/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequestTest.java index 0df3cf7e7..5d705858e 100644 --- a/src/test/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequestTest.java +++ b/src/test/java/com/firebolt/jdbc/client/authentication/ServiceAccountAuthenticationRequestTest.java @@ -1,8 +1,11 @@ package com.firebolt.jdbc.client.authentication; +import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; @@ -14,18 +17,12 @@ class ServiceAccountAuthenticationRequestTest { @Test void shouldCreateHttpEntityWithTheProvidedCredentials() throws IOException { ServiceAccountAuthenticationRequest serviceAccountAuthenticationHttpRequest = new ServiceAccountAuthenticationRequest( - "he-ll-o", "secret", "https://api.dev.firebolt.io:443"); + "he-ll-o", "secret", "dev"); RequestBody requestBody = serviceAccountAuthenticationHttpRequest.getRequestBody(); Buffer buffer = new Buffer(); requestBody.writeTo(buffer); - assertEquals("client_id=he-ll-o&client_secret=secret&grant_type=client_credentials", buffer.readUtf8()); + assertEquals(format("audience=%s&grant_type=client_credentials&client_id=he-ll-o&client_secret=secret", URLEncoder.encode("https://api.firebolt.io", StandardCharsets.UTF_8)), buffer.readUtf8()); } - @Test - void shouldGetUri() { - ServiceAccountAuthenticationRequest serviceAccountAuthenticationHttpRequest = new ServiceAccountAuthenticationRequest( - "he-ll-o", "secret", "https://api.dev.firebolt.io:443"); - assertEquals("https://api.dev.firebolt.io:443/auth/v1/token", serviceAccountAuthenticationHttpRequest.getUri()); - } } \ No newline at end of file diff --git a/src/test/java/com/firebolt/jdbc/client/authentication/UsernamePasswordAuthenticationRequestTest.java b/src/test/java/com/firebolt/jdbc/client/authentication/UsernamePasswordAuthenticationRequestTest.java deleted file mode 100644 index f16b0bb54..000000000 --- a/src/test/java/com/firebolt/jdbc/client/authentication/UsernamePasswordAuthenticationRequestTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.firebolt.jdbc.client.authentication; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import okhttp3.RequestBody; -import okio.Buffer; - -class UsernamePasswordAuthenticationRequestTest { - - @Test - void shouldCreateHttpEntityWithTheProvidedCredentials() throws IOException { - UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationHttpRequest = new UsernamePasswordAuthenticationRequest( - "hello", "pa$$word", "https://api.dev.firebolt.io:443"); - RequestBody requestBody = usernamePasswordAuthenticationHttpRequest.getRequestBody(); - Buffer buffer = new Buffer(); - requestBody.writeTo(buffer); - // We transform the requests to map because the order of the fields is not - // guaranteed - Map expectedRequest = new ObjectMapper() - .readValue("{\"username\":\"hello\",\"password\":\"pa$$word\"}", HashMap.class); - Map actualRequest = new ObjectMapper().readValue(buffer.readUtf8(), HashMap.class); - assertEquals(expectedRequest, actualRequest); - } - - @Test - void getUri() { - UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationHttpRequest = new UsernamePasswordAuthenticationRequest( - "hello", "pa$$word", "https://api.dev.firebolt.io:443"); - assertEquals("https://api.dev.firebolt.io:443/auth/v1/login", - usernamePasswordAuthenticationHttpRequest.getUri()); - } -} \ No newline at end of file diff --git a/src/test/java/com/firebolt/jdbc/client/gateway/FireboltGatewayUrlClientTest.java b/src/test/java/com/firebolt/jdbc/client/gateway/FireboltGatewayUrlClientTest.java new file mode 100644 index 000000000..d00021b4b --- /dev/null +++ b/src/test/java/com/firebolt/jdbc/client/gateway/FireboltGatewayUrlClientTest.java @@ -0,0 +1,89 @@ +package com.firebolt.jdbc.client.gateway; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.firebolt.jdbc.client.account.FireboltAccount; +import com.firebolt.jdbc.client.account.FireboltAccountRetriever; +import com.firebolt.jdbc.connection.FireboltConnection; +import com.firebolt.jdbc.exception.FireboltException; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; + +import static java.net.HttpURLConnection.HTTP_OK; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class FireboltAccountRetrieverTest { + + @Spy + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Mock + private OkHttpClient httpClient; + + @Mock + private FireboltConnection fireboltConnection; + + private FireboltAccountRetriever fireboltGatewayUrlClient; + private FireboltAccountRetriever fireboltAccountIdResolver; + + + @BeforeEach + void setUp() { + fireboltGatewayUrlClient = new FireboltAccountRetriever<>(httpClient, objectMapper, fireboltConnection, null, null, "http://test-firebolt.io", "engineUrl", GatewayUrlResponse.class); + fireboltAccountIdResolver = new FireboltAccountRetriever<>(httpClient, objectMapper, fireboltConnection, null, null, "http://test-firebolt.io", "resolve", FireboltAccount.class); + } + + @Test + void shouldGetGatewayUrlWhenResponseIsOk() throws IOException, FireboltException { + GatewayUrlResponse response = GatewayUrlResponse.builder().engineUrl("http://engine").build(); + injectMockedResponse(httpClient, HTTP_OK, response); + assertEquals("http://engine", fireboltGatewayUrlClient.retrieve("access_token", "account").getEngineUrl()); + } + + @Test + void shouldGetAccountId() throws IOException, FireboltException { + FireboltAccount account = new FireboltAccount("12345", "central"); + injectMockedResponse(httpClient, HTTP_OK, account); + assertEquals(account, fireboltAccountIdResolver.retrieve("access_token", "account")); + } + + @Test + void shouldRuntimeExceptionUponRuntimeException() throws FireboltException { + when(httpClient.newCall(any())).thenThrow(new IllegalArgumentException("ex")); + assertEquals("ex", assertThrows(IllegalArgumentException.class, () -> fireboltGatewayUrlClient.retrieve("token", "acc")).getMessage()); + } + + @Test + void shouldThrowFireboltExceptionUponIOException() throws IOException { + Call call = mock(Call.class); + when(httpClient.newCall(any())).thenReturn(call); + when(call.execute()).thenThrow(new IOException("ex")); + assertEquals("Failed to get engineUrl url for account acc", assertThrows(FireboltException.class, () -> fireboltGatewayUrlClient.retrieve("token", "acc")).getMessage()); + } + + private void injectMockedResponse(OkHttpClient httpClient, int code, Object payload) throws IOException { + Response response = mock(Response.class); + Call call = mock(Call.class); + when(httpClient.newCall(any())).thenReturn(call); + when(call.execute()).thenReturn(response); + ResponseBody body = mock(ResponseBody.class); + when(response.body()).thenReturn(body); + when(response.code()).thenReturn(code); + String gatewayResponse = new ObjectMapper().writeValueAsString(payload); + when(body.string()).thenReturn(gatewayResponse); + } +} \ No newline at end of file diff --git a/src/test/java/com/firebolt/jdbc/client/query/StatementClientImplTest.java b/src/test/java/com/firebolt/jdbc/client/query/StatementClientImplTest.java index 31ebdf1ab..f951cd9ba 100644 --- a/src/test/java/com/firebolt/jdbc/client/query/StatementClientImplTest.java +++ b/src/test/java/com/firebolt/jdbc/client/query/StatementClientImplTest.java @@ -8,10 +8,7 @@ import com.firebolt.jdbc.statement.StatementInfoWrapper; import com.firebolt.jdbc.statement.StatementUtil; import lombok.NonNull; -import okhttp3.Call; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; +import okhttp3.*; import okio.Buffer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -21,10 +18,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; -import java.net.URISyntaxException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import static com.firebolt.jdbc.client.UserAgentFormatter.userAgent; @@ -46,12 +43,30 @@ class StatementClientImplTest { @Test void shouldPostSqlQueryWithExpectedUrl() throws FireboltException, IOException { + Entry result = shouldPostSqlQueryForSystemEngine(false); + String requestId = result.getKey(); + String url = result.getValue(); + assertEquals( + format("http://firebolt1:555/?database=db1&output_format=TabSeparatedWithNamesAndTypes&query_id=%s&compress=1&max_execution_time=15", requestId), + url); + } + + @Test + void shouldPostSqlQueryForSystemEngine() throws FireboltException, IOException { + String url = shouldPostSqlQueryForSystemEngine(true).getValue(); + assertEquals( + "http://firebolt1:555/?account_id=12345&output_format=TabSeparatedWithNamesAndTypes", + url); + } + + private Entry shouldPostSqlQueryForSystemEngine(boolean systemEngine) throws FireboltException, IOException { FireboltProperties fireboltProperties = FireboltProperties.builder().database("db1").compress(true) - .host("firebolt1").port(555).build(); + .host("firebolt1").port(555).accountId("12345").systemEngine(systemEngine).build(); when(connection.getAccessToken()) .thenReturn(Optional.of("token")); - StatementClient statementClient = new StatementClientImpl(okHttpClient, connection, mock(ObjectMapper.class), + StatementClient statementClient = new StatementClientImpl(okHttpClient, mock(ObjectMapper.class), connection, "ConnA:1.0.9", "ConnB:2.0.9"); + injectMockedResponse(okHttpClient, 200); Call call = getMockedCallWithResponse(200); when(okHttpClient.newCall(any())).thenReturn(call); StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("show databases").get(0); @@ -66,39 +81,16 @@ void shouldPostSqlQueryWithExpectedUrl() throws FireboltException, IOException { assertEquals(expectedHeaders, extractHeadersMap(actualRequest)); assertEquals("show databases;", actualQuery); - assertEquals(format( - "http://firebolt1:555/?database=db1&output_format=TabSeparatedWithNamesAndTypes&query_id=%s&compress=1&max_execution_time=15", - statementInfoWrapper.getId()), actualRequest.url().toString()); - } - - @Test - void shouldPostSqlQueryForSystemEngine() throws FireboltException, IOException, URISyntaxException { - FireboltProperties fireboltProperties = FireboltProperties.builder().database("db1").compress(true) - .host("firebolt1").port(555).build(); - when(connection.getAccessToken()) - .thenReturn(Optional.of("token")); - StatementClient statementClient = new StatementClientImpl(okHttpClient, connection, mock(ObjectMapper.class), - "ConnA:1.0.9", "ConnB:2.0.9"); - Call call = getMockedCallWithResponse(200); - when(okHttpClient.newCall(any())).thenReturn(call); - StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("show databases").get(0); - statementClient.executeSqlStatement(statementInfoWrapper, fireboltProperties, true, 15, true); - - verify(okHttpClient).newCall(requestArgumentCaptor.capture()); - Request actualRequest = requestArgumentCaptor.getValue(); - String actualQuery = getActualRequestString(actualRequest); - - assertEquals("show databases;", actualQuery); - assertEquals("http://firebolt1:555/?output_format=TabSeparatedWithNamesAndTypes", - actualRequest.url().toString()); + return Map.entry(statementInfoWrapper.getId(), actualRequest.url().toString()); } @Test void shouldCancelSqlQuery() throws FireboltException, IOException { FireboltProperties fireboltProperties = FireboltProperties.builder().database("db1").compress(true) .host("firebolt1").port(555).build(); - StatementClient statementClient = new StatementClientImpl(okHttpClient, connection, - mock(ObjectMapper.class), "", ""); + StatementClient statementClient = new StatementClientImpl(okHttpClient, mock(ObjectMapper.class), connection, + "", ""); + injectMockedResponse(okHttpClient, 200); Call call = getMockedCallWithResponse(200); when(okHttpClient.newCall(any())).thenReturn(call); statementClient.abortStatement("12345", fireboltProperties); @@ -116,7 +108,7 @@ void shouldRetryOnUnauthorized() throws IOException, FireboltException { Call okCall = getMockedCallWithResponse(200); Call unauthorizedCall = getMockedCallWithResponse(401); when(okHttpClient.newCall(any())).thenReturn(unauthorizedCall).thenReturn(okCall); - StatementClient statementClient = new StatementClientImpl(okHttpClient, connection, mock(ObjectMapper.class), + StatementClient statementClient = new StatementClientImpl(okHttpClient, mock(ObjectMapper.class), connection, "ConnA:1.0.9", "ConnB:2.0.9"); StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("show databases").get(0); statementClient.executeSqlStatement(statementInfoWrapper, fireboltProperties, false, 5, true); @@ -133,7 +125,7 @@ void shouldNotRetryNoMoreThanOnceOnUnauthorized() throws IOException, FireboltEx Call okCall = getMockedCallWithResponse(200); Call unauthorizedCall = getMockedCallWithResponse(401); when(okHttpClient.newCall(any())).thenReturn(unauthorizedCall).thenReturn(unauthorizedCall).thenReturn(okCall); - StatementClient statementClient = new StatementClientImpl(okHttpClient, connection, mock(ObjectMapper.class), + StatementClient statementClient = new StatementClientImpl(okHttpClient, mock(ObjectMapper.class), connection, "ConnA:1.0.9", "ConnB:2.0.9"); StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("show databases").get(0); FireboltException ex = assertThrows(FireboltException.class, () -> statementClient.executeSqlStatement(statementInfoWrapper, fireboltProperties, false, 5, true)); @@ -156,6 +148,17 @@ private Map extractHeadersMap(Request request) { return headers; } + + private void injectMockedResponse(OkHttpClient httpClient, int code) throws IOException { + Response response = mock(Response.class); + Call call = mock(Call.class); + lenient().when(httpClient.newCall(any())).thenReturn(call); + lenient().when(call.execute()).thenReturn(response); + ResponseBody body = mock(ResponseBody.class); + lenient().when(response.body()).thenReturn(body); + lenient().when(response.code()).thenReturn(code); + } + @NonNull private String getActualRequestString(Request actualRequest) throws IOException { Buffer buffer = new Buffer(); diff --git a/src/test/java/com/firebolt/jdbc/connection/FireboltConnectionTest.java b/src/test/java/com/firebolt/jdbc/connection/FireboltConnectionTest.java index 6cc6f3407..b19fc328e 100644 --- a/src/test/java/com/firebolt/jdbc/connection/FireboltConnectionTest.java +++ b/src/test/java/com/firebolt/jdbc/connection/FireboltConnectionTest.java @@ -6,8 +6,13 @@ import com.firebolt.jdbc.exception.FireboltException; import com.firebolt.jdbc.service.FireboltAuthenticationService; import com.firebolt.jdbc.service.FireboltEngineService; +import com.firebolt.jdbc.service.FireboltAccountIdService; +import com.firebolt.jdbc.service.FireboltAuthenticationService; +import com.firebolt.jdbc.service.FireboltEngineService; +import com.firebolt.jdbc.service.FireboltGatewayUrlService; import com.firebolt.jdbc.service.FireboltStatementService; import com.firebolt.jdbc.statement.StatementInfoWrapper; +import lombok.NonNull; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; @@ -45,8 +50,15 @@ import static com.firebolt.jdbc.connection.settings.FireboltSessionProperty.ACCESS_TOKEN; import static com.firebolt.jdbc.connection.settings.FireboltSessionProperty.HOST; -import static com.firebolt.jdbc.connection.settings.FireboltSessionProperty.PASSWORD; -import static com.firebolt.jdbc.connection.settings.FireboltSessionProperty.USER; +import static com.firebolt.jdbc.connection.settings.FireboltSessionProperty.CLIENT_SECRET; +import static com.firebolt.jdbc.connection.settings.FireboltSessionProperty.CLIENT_ID; +import java.sql.Statement; +import java.util.Optional; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + import static java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT; import static java.sql.ResultSet.CONCUR_READ_ONLY; import static java.sql.ResultSet.CONCUR_UPDATABLE; @@ -66,6 +78,9 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -73,8 +88,11 @@ @ExtendWith(MockitoExtension.class) class FireboltConnectionTest { - private static final String URL = "jdbc:firebolt://api.dev.firebolt.io/db"; - private static final String LOCAL_URL = "jdbc:firebolt://localhost:8123/local_dev_db?ssl=false&max_query_size=10000000&use_standard_sql=1&mask_internal_errors=0&firebolt_enable_beta_functions=1&firebolt_case_insensitive_identifiers=1&rest_api_pull_timeout_sec=3600&rest_api_pull_interval_millisec=5000&rest_api_retry_times=10"; + private static final String URL = "jdbc:firebolt:db?env=dev&engine=eng&account=dev"; + + private static final String SYSTEM_ENGINE_URL = "jdbc:firebolt:db?env=dev&account=dev"; + + private static final String LOCAL_URL = "jdbc:firebolt:local_dev_db?account=dev&ssl=false&max_query_size=10000000&use_standard_sql=1&mask_internal_errors=0&firebolt_enable_beta_functions=1&firebolt_case_insensitive_identifiers=1&rest_api_pull_timeout_sec=3600&rest_api_pull_interval_millisec=5000&rest_api_retry_times=10&host=localhost"; private final FireboltConnectionTokens fireboltConnectionTokens = FireboltConnectionTokens.builder().build(); @Captor private ArgumentCaptor propertiesArgumentCaptor; @@ -82,10 +100,15 @@ class FireboltConnectionTest { private ArgumentCaptor queryInfoWrapperArgumentCaptor; @Mock private FireboltAuthenticationService fireboltAuthenticationService; + @Mock + private FireboltGatewayUrlService fireboltGatewayUrlService; + @Mock private FireboltEngineService fireboltEngineService; @Mock private FireboltStatementService fireboltStatementService; + @Mock + private FireboltAccountIdService fireboltAccountIdService; private Properties connectionProperties = new Properties(); private static Connection connection; @@ -125,82 +148,85 @@ private static Stream empty() { ); } + private Engine engine; + @BeforeEach - void init() throws FireboltException { + void init() throws SQLException { connectionProperties = new Properties(); - connectionProperties.put("user", "user"); - connectionProperties.put("password", "pa$$word"); + connectionProperties.put("client_id", "somebody"); + connectionProperties.put("client_secret", "pa$$word"); connectionProperties.put("compress", "1"); lenient().when(fireboltAuthenticationService.getConnectionTokens(eq("https://api.dev.firebolt.io:443"), any())) .thenReturn(fireboltConnectionTokens); - lenient().when(fireboltEngineService.getEngine(any(), any(), any())).thenReturn(mock(Engine.class)); + lenient().when(fireboltGatewayUrlService.getUrl(any(), any())).thenReturn("http://foo:8080/bar"); + engine = new Engine("endpoint", "id123", "OK", "noname"); + lenient().when(fireboltEngineService.getEngine(any(), any())).thenReturn(engine); + lenient().when(fireboltEngineService.doesDatabaseExist(any())).thenReturn(true); } @Test void shouldInitConnection() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertFalse(fireboltConnection.isClosed()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertFalse(fireboltConnection.isClosed()); + } } @Test void shouldNotFetchTokenNorEngineHostForLocalFirebolt() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(LOCAL_URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - verifyNoInteractions(fireboltAuthenticationService); - verifyNoInteractions(fireboltEngineService); - assertFalse(fireboltConnection.isClosed()); + try (FireboltConnection fireboltConnection = createConnection(LOCAL_URL, connectionProperties)) { + verifyNoInteractions(fireboltAuthenticationService); + verifyNoInteractions(fireboltGatewayUrlService); + assertFalse(fireboltConnection.isClosed()); + } } @Test void shouldPrepareStatement() throws SQLException { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(),anyBoolean(), any())) .thenReturn(Optional.empty()); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - PreparedStatement statement = fireboltConnection - .prepareStatement("INSERT INTO cars(sales, name) VALUES (?, ?)"); - statement.setObject(1, 500); - statement.setObject(2, "Ford"); - statement.execute(); - assertNotNull(fireboltConnection); - assertNotNull(statement); - verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), any(), anyInt(), anyInt(), - anyBoolean(), any()); - assertEquals("INSERT INTO cars(sales, name) VALUES (500, 'Ford')", - queryInfoWrapperArgumentCaptor.getValue().getSql()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + PreparedStatement statement = fireboltConnection + .prepareStatement("INSERT INTO cars(sales, name) VALUES (?, ?)"); + statement.setObject(1, 500); + statement.setObject(2, "Ford"); + statement.execute(); + assertNotNull(fireboltConnection); + assertNotNull(statement); + verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); + assertEquals("INSERT INTO cars(sales, name) VALUES (500, 'Ford')", + queryInfoWrapperArgumentCaptor.getValue().getSql()); + } } @Test void shouldCloseAllStatementsOnClose() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - Statement statement = fireboltConnection.createStatement(); - Statement preparedStatement = fireboltConnection.prepareStatement("test"); - fireboltConnection.close(); - assertTrue(statement.isClosed()); - assertTrue(preparedStatement.isClosed()); - assertTrue(fireboltConnection.isClosed()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + Statement statement = fireboltConnection.createStatement(); + Statement preparedStatement = fireboltConnection.prepareStatement("test"); + fireboltConnection.close(); + assertTrue(statement.isClosed()); + assertTrue(preparedStatement.isClosed()); + assertTrue(fireboltConnection.isClosed()); + } } @Test void createStatement() throws SQLException { FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + fireboltAuthenticationService, fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService); assertNotNull(fireboltConnection.createStatement()); } @Test void createStatementWithParameters() throws SQLException { FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + fireboltAuthenticationService, fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService); assertNotNull(fireboltConnection.createStatement(TYPE_FORWARD_ONLY, CONCUR_READ_ONLY)); } @Test void unsupportedCreateStatementWithParameters() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + FireboltConnection fireboltConnection = createConnection(URL, connectionProperties); assertThrows(SQLFeatureNotSupportedException.class, () -> fireboltConnection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)); assertThrows(SQLFeatureNotSupportedException.class, () -> fireboltConnection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_READ_ONLY)); assertThrows(SQLFeatureNotSupportedException.class, () -> fireboltConnection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_UPDATABLE)); @@ -229,20 +255,19 @@ void schema() throws SQLException { } private void validateFlag(CheckedFunction getter, T expected) throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + FireboltConnection fireboltConnection = createConnection(URL, connectionProperties); assertEquals(expected, getter.apply(fireboltConnection)); fireboltConnection.close(); assertThrows(FireboltException.class, () -> getter.apply(fireboltConnection)); // cannot invoke this method on closed connection } @Test - void prepareCall() throws FireboltException { + void prepareCall() throws SQLException { notSupported(c -> c.prepareCall("select 1")); } @Test - void unsupportedPrepareStatement() throws FireboltException { + void unsupportedPrepareStatement() throws SQLException { notSupported(c -> c.prepareStatement("select 1", ResultSet.TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)); notSupported(c -> c.prepareStatement("select 1", Statement.RETURN_GENERATED_KEYS)); notSupported(c -> c.prepareStatement("select 1", new int[0])); @@ -251,207 +276,199 @@ void unsupportedPrepareStatement() throws FireboltException { @Test void prepareStatement() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + FireboltConnection fireboltConnection = createConnection(URL, connectionProperties); PreparedStatement ps = fireboltConnection.prepareStatement("select 1", ResultSet.TYPE_FORWARD_ONLY, CONCUR_READ_ONLY); assertNotNull(ps); } - private void notSupported(CheckedFunction getter) throws FireboltException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + private void notSupported(CheckedFunction getter) throws SQLException { + FireboltConnection fireboltConnection = createConnection(URL, connectionProperties); assertThrows(SQLFeatureNotSupportedException.class, () -> getter.apply(fireboltConnection)); // cannot invoke this method on closed connection } @Test void shouldNotSetNewPropertyWhenConnectionIsNotValidWithTheNewProperty() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) - .thenThrow(new FireboltException(ExceptionType.TOO_MANY_REQUESTS)); - assertThrows(FireboltException.class, - () -> fireboltConnection.addProperty(new ImmutablePair<>("custom_1", "1"))); - - verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), - propertiesArgumentCaptor.capture(), anyInt(), anyInt(), anyBoolean(), any()); - assertEquals("1", propertiesArgumentCaptor.getValue().getAdditionalProperties().get("custom_1")); - assertEquals("SELECT 1", queryInfoWrapperArgumentCaptor.getValue().getSql()); - assertNull(fireboltConnection.getSessionProperties().getAdditionalProperties().get("custom_1")); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) + .thenThrow(new FireboltException(ExceptionType.TOO_MANY_REQUESTS)); + assertThrows(FireboltException.class, + () -> fireboltConnection.addProperty(new ImmutablePair<>("custom_1", "1"))); + + verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), + propertiesArgumentCaptor.capture(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); + assertEquals("1", propertiesArgumentCaptor.getValue().getAdditionalProperties().get("custom_1")); + assertEquals("SELECT 1", queryInfoWrapperArgumentCaptor.getValue().getSql()); + assertNull(fireboltConnection.getSessionProperties().getAdditionalProperties().get("custom_1")); + } } @Test void shouldSetNewPropertyWhenConnectionIsValidWithTheNewProperty() throws SQLException { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(),anyBoolean(), any())) .thenReturn(Optional.empty()); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - Pair newProperties = new ImmutablePair<>("custom_1", "1"); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + Pair newProperties = new ImmutablePair<>("custom_1", "1"); - fireboltConnection.addProperty(newProperties); + fireboltConnection.addProperty(newProperties); - verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), - propertiesArgumentCaptor.capture(), anyInt(), anyInt(), anyBoolean(), any()); - assertEquals("1", propertiesArgumentCaptor.getValue().getAdditionalProperties().get("custom_1")); - assertEquals("1", fireboltConnection.getSessionProperties().getAdditionalProperties().get("custom_1")); - assertEquals("SELECT 1", queryInfoWrapperArgumentCaptor.getValue().getSql()); + verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), + propertiesArgumentCaptor.capture(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); + assertEquals("1", propertiesArgumentCaptor.getValue().getAdditionalProperties().get("custom_1")); + assertEquals("1", fireboltConnection.getSessionProperties().getAdditionalProperties().get("custom_1")); + assertEquals("SELECT 1", queryInfoWrapperArgumentCaptor.getValue().getSql()); + } } @Test void shouldValidateConnectionWhenCallingIsValid() throws SQLException { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.empty()); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - fireboltConnection.isValid(500); - - verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), - propertiesArgumentCaptor.capture(), anyInt(), anyInt(), anyBoolean(), any()); - assertEquals("SELECT 1", queryInfoWrapperArgumentCaptor.getValue().getSql()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + fireboltConnection.isValid(500); + verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), + propertiesArgumentCaptor.capture(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); + assertEquals("SELECT 1", queryInfoWrapperArgumentCaptor.getValue().getSql()); + } } @Test void shouldNotValidateConnectionWhenCallingIsValidWhenUsingSystemEngine() throws SQLException { Properties propertiesWithSystemEngine = new Properties(connectionProperties); - propertiesWithSystemEngine.put("engine_name", "system"); - FireboltConnection fireboltConnection = new FireboltConnection(URL, propertiesWithSystemEngine, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - fireboltConnection.isValid(500); - - verifyNoInteractions(fireboltStatementService); + try (FireboltConnection fireboltConnection = createConnection(SYSTEM_ENGINE_URL, propertiesWithSystemEngine)) { + fireboltConnection.isValid(500); + verifyNoInteractions(fireboltStatementService); + } } @Test void shouldIgnore429WhenValidatingConnection() throws SQLException { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenThrow(new FireboltException(ExceptionType.TOO_MANY_REQUESTS)); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertTrue(fireboltConnection.isValid(500)); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertTrue(fireboltConnection.isValid(500)); + } } @Test void shouldReturnFalseWhenValidatingConnectionThrowsAnException() throws SQLException { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenThrow(new FireboltException(ExceptionType.ERROR)); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertFalse(fireboltConnection.isValid(500)); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertFalse(fireboltConnection.isValid(500)); + } } @Test void shouldThrowExceptionWhenValidatingConnectionWithNegativeTimeout() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertThrows(FireboltException.class, () -> fireboltConnection.isValid(-1)); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertThrows(FireboltException.class, () -> fireboltConnection.isValid(-1)); + } } @Test void shouldReturnFalseWhenValidatingClosedConnection() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - fireboltConnection.close(); - assertFalse(fireboltConnection.isValid(50)); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + fireboltConnection.close(); + assertFalse(fireboltConnection.isValid(50)); + } } @Test void shouldExtractConnectorOverrides() throws SQLException { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.empty()); connectionProperties.put("user_clients", "ConnA:1.0.9,ConnB:2.8.0"); connectionProperties.put("user_drivers", "DriverA:2.0.9,DriverB:3.8.0"); - FireboltConnection fireboltConnectionImpl = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + PreparedStatement statement = fireboltConnection.prepareStatement("SELECT 1"); + statement.execute(); - PreparedStatement statement = fireboltConnectionImpl.prepareStatement("SELECT 1"); - statement.execute(); - - verify(fireboltStatementService).execute(any(), propertiesArgumentCaptor.capture(), anyInt(), anyInt(), - anyBoolean(), any()); - assertNull(propertiesArgumentCaptor.getValue().getAdditionalProperties().get("user_clients")); - assertNull(propertiesArgumentCaptor.getValue().getAdditionalProperties().get("user_drivers")); - assertNull(fireboltConnectionImpl.getSessionProperties().getAdditionalProperties().get("user_clients")); - assertNull(fireboltConnectionImpl.getSessionProperties().getAdditionalProperties().get("user_drivers")); + verify(fireboltStatementService).execute(any(), propertiesArgumentCaptor.capture(), anyInt(), anyInt(), + anyBoolean(), anyBoolean(), any()); + assertNull(propertiesArgumentCaptor.getValue().getAdditionalProperties().get("user_clients")); + assertNull(propertiesArgumentCaptor.getValue().getAdditionalProperties().get("user_drivers")); + assertNull(fireboltConnection.getSessionProperties().getAdditionalProperties().get("user_clients")); + assertNull(fireboltConnection.getSessionProperties().getAdditionalProperties().get("user_drivers")); + } } @Test void shouldGetEngineNameFromHost() throws SQLException { - when(fireboltEngineService.getEngineNameFromHost(any())).thenReturn("myHost_345"); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertEquals("myHost_345", fireboltConnection.getEngine()); + connectionProperties.put("engine", "hello"); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertEquals(engine.getName(), fireboltConnection.getEngine()); + } } @Test void shouldInitNetworkTimeoutWithPropertyByDefault() throws SQLException { connectionProperties.put("socket_timeout_millis", "60"); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertEquals(60, fireboltConnection.getNetworkTimeout()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertEquals(60, fireboltConnection.getNetworkTimeout()); + } } @Test void shouldInitConnectionTimeoutWithPropertyByDefault() throws SQLException { connectionProperties.put("connection_timeout_millis", "50"); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertEquals(50, fireboltConnection.getConnectionTimeout()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertEquals(50, fireboltConnection.getConnectionTimeout()); + } } @Test void shouldCloseConnectionWhenAbortingConnection() throws SQLException, InterruptedException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - ExecutorService executorService = Executors.newFixedThreadPool(10); - fireboltConnection.abort(executorService); - executorService.awaitTermination(1, TimeUnit.SECONDS); - assertTrue(fireboltConnection.isClosed()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + ExecutorService executorService = Executors.newFixedThreadPool(10); + fireboltConnection.abort(executorService); + executorService.awaitTermination(1, TimeUnit.SECONDS); + assertTrue(fireboltConnection.isClosed()); + } } @Test void shouldRemoveExpiredToken() throws SQLException { - FireboltProperties fireboltProperties = FireboltProperties.builder().host("host").path("/db").port(8080) - .build(); + FireboltProperties fireboltProperties = FireboltProperties.builder().host("host").path("/db").port(8080).account("dev").build(); try (MockedStatic mockedFireboltProperties = Mockito.mockStatic(FireboltProperties.class)) { when(FireboltProperties.of(any())).thenReturn(fireboltProperties); when(fireboltAuthenticationService.getConnectionTokens("http://host:8080", fireboltProperties)) .thenReturn(FireboltConnectionTokens.builder().build()); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - fireboltConnection.removeExpiredTokens(); - verify(fireboltAuthenticationService).removeConnectionTokens("http://host:8080", fireboltProperties); + lenient().when(fireboltEngineService.getEngine(any(), any())).thenReturn(new Engine("http://hello", null, null, null)); + + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + fireboltConnection.removeExpiredTokens(); + verify(fireboltAuthenticationService).removeConnectionTokens("http://host:8080", fireboltProperties); + } } } @Test void shouldReturnConnectionTokenWhenAvailable() throws SQLException { String accessToken = "hello"; - FireboltProperties fireboltProperties = FireboltProperties.builder().host("host").path("/db").port(8080) - .build(); + FireboltProperties fireboltProperties = FireboltProperties.builder().host("host").path("/db").port(8080).account("dev").build(); try (MockedStatic mockedFireboltProperties = Mockito.mockStatic(FireboltProperties.class)) { when(FireboltProperties.of(any())).thenReturn(fireboltProperties); FireboltConnectionTokens connectionTokens = FireboltConnectionTokens.builder().accessToken(accessToken).build(); - when(fireboltAuthenticationService.getConnectionTokens(eq("http://host:8080"), any())) - .thenReturn(connectionTokens); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - verify(fireboltAuthenticationService).getConnectionTokens("http://host:8080", fireboltProperties); - assertEquals(accessToken, fireboltConnection.getAccessToken().get()); + when(fireboltAuthenticationService.getConnectionTokens(eq("http://host:8080"), any())).thenReturn(connectionTokens); + lenient().when(fireboltEngineService.getEngine(any(), any())).thenReturn(new Engine("http://engineHost", null, null, null)); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + verify(fireboltAuthenticationService).getConnectionTokens("http://host:8080", fireboltProperties); + assertEquals(accessToken, fireboltConnection.getAccessToken().get()); + } } } @Test void shouldNotReturnConnectionTokenWithLocalDb() throws SQLException { - FireboltProperties fireboltProperties = FireboltProperties.builder().host("localhost").path("/db").port(8080) - .build(); + FireboltProperties fireboltProperties = FireboltProperties.builder().host("localhost").path("/db").port(8080).account("dev").build(); try (MockedStatic mockedFireboltProperties = Mockito.mockStatic(FireboltProperties.class)) { when(FireboltProperties.of(any())).thenReturn(fireboltProperties); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertEquals(Optional.empty(), fireboltConnection.getAccessToken()); - verifyNoInteractions(fireboltAuthenticationService); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertEquals(Optional.empty(), fireboltConnection.getAccessToken()); + verifyNoInteractions(fireboltAuthenticationService); + } } } @@ -468,8 +485,9 @@ void shouldGetConnectionTokenFromProperties(String host, String configuredAccess if (configuredAccessToken != null) { propsWithToken.setProperty(ACCESS_TOKEN.getKey(), configuredAccessToken); } + try (FireboltConnection connection = new FireboltConnection(URL, propsWithToken, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)) { + fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService)) { assertEquals(expectedAccessToken, connection.getAccessToken().orElse(null)); Mockito.verifyNoMoreInteractions(fireboltAuthenticationService); } @@ -479,10 +497,10 @@ void shouldGetConnectionTokenFromProperties(String host, String configuredAccess void shouldThrowExceptionIfBothAccessTokenAndUserPasswordAreSupplied() { Properties propsWithToken = new Properties(); propsWithToken.setProperty(ACCESS_TOKEN.getKey(), "my-token"); - propsWithToken.setProperty(USER.getKey(), "my-user"); - propsWithToken.setProperty(PASSWORD.getKey(), "my-password"); + propsWithToken.setProperty(CLIENT_ID.getKey(), "my-client"); + propsWithToken.setProperty(CLIENT_SECRET.getKey(), "my-secret"); assertThrows(SQLException.class, () -> new FireboltConnection(URL, propsWithToken, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)); + fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService)); } @Test @@ -491,46 +509,44 @@ void shouldSetNetworkTimeout() throws SQLException { .socketTimeoutMillis(5).port(8080).build(); try (MockedStatic mockedFireboltProperties = Mockito.mockStatic(FireboltProperties.class)) { when(FireboltProperties.of(any())).thenReturn(fireboltProperties); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertEquals(5, fireboltConnection.getNetworkTimeout()); - fireboltConnection.setNetworkTimeout(null, 1); - assertEquals(1, fireboltConnection.getNetworkTimeout()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertEquals(5, fireboltConnection.getNetworkTimeout()); + fireboltConnection.setNetworkTimeout(null, 1); + assertEquals(1, fireboltConnection.getNetworkTimeout()); + } } } @Test void shouldUseConnectionTimeoutFromProperties() throws SQLException { - FireboltProperties fireboltProperties = FireboltProperties.builder().host("localhost").path("/db") - .connectionTimeoutMillis(20).port(8080).build(); + FireboltProperties fireboltProperties = FireboltProperties.builder().host("localhost").path("/db").connectionTimeoutMillis(20).port(8080).build(); try (MockedStatic mockedFireboltProperties = Mockito.mockStatic(FireboltProperties.class)) { when(FireboltProperties.of(any())).thenReturn(fireboltProperties); - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - assertEquals(20, fireboltConnection.getConnectionTimeout()); + try (FireboltConnection fireboltConnection = createConnection(URL, connectionProperties)) { + assertEquals(20, fireboltConnection.getConnectionTimeout()); + } } } @Test void shouldThrowExceptionWhenTryingToUseClosedConnection() throws SQLException { - FireboltConnection fireboltConnection = new FireboltConnection(URL, connectionProperties, - fireboltAuthenticationService, fireboltEngineService, fireboltStatementService); - fireboltConnection.close(); - assertThrows(FireboltException.class, fireboltConnection::getCatalog); + try (Connection connection = createConnection(URL, connectionProperties)) { + connection.close(); + assertThrows(FireboltException.class, connection::getCatalog); + } } @Test void shouldUnwrapFireboltConnection() throws SQLException { - Connection connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService); - assertTrue(connection.isWrapperFor(FireboltConnection.class)); - assertEquals(connection, connection.unwrap(FireboltConnection.class)); + try (Connection connection = createConnection(URL, connectionProperties)) { + assertTrue(connection.isWrapperFor(FireboltConnection.class)); + assertEquals(connection, connection.unwrap(FireboltConnection.class)); + } } @Test void shouldThrowExceptionWhenCannotUnwrap() throws SQLException { - try (Connection connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)) { + try (Connection connection = createConnection(URL, connectionProperties)) { assertFalse(connection.isWrapperFor(String.class)); assertThrows(SQLException.class, () -> connection.unwrap(String.class)); } @@ -539,17 +555,15 @@ void shouldThrowExceptionWhenCannotUnwrap() throws SQLException { @Test void shouldGetDatabaseWhenGettingCatalog() throws SQLException { connectionProperties.put("database", "db"); - try (Connection connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)) { - assertEquals("db", connection.getCatalog()); + try (Connection connection = createConnection(URL, connectionProperties)) { + assertEquals("noname", connection.getCatalog()); // retrieved engine's DB's name is "noname". Firebolt treats DB as catalog } } @Test void shouldGetNoneTransactionIsolation() throws SQLException { connectionProperties.put("database", "db"); - try (Connection connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)) { + try (Connection connection = createConnection(URL, connectionProperties)) { assertEquals(Connection.TRANSACTION_NONE, connection.getTransactionIsolation()); } } @@ -557,8 +571,7 @@ void shouldGetNoneTransactionIsolation() throws SQLException { @Test void shouldThrowExceptionWhenPreparingStatementWIthInvalidResultSetType() throws SQLException { connectionProperties.put("database", "db"); - try (Connection connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)) { + try (Connection connection = createConnection(URL, connectionProperties)) { assertThrows(SQLFeatureNotSupportedException.class, () -> connection.prepareStatement("any", TYPE_SCROLL_INSENSITIVE, 0)); } @@ -567,7 +580,7 @@ void shouldThrowExceptionWhenPreparingStatementWIthInvalidResultSetType() throws @Test void createArray() throws SQLException { try (Connection connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService)) { + fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService)) { Object[] data = new Object[] {"red", "green", "blue"}; Array array = connection.createArrayOf("text", data); assertEquals(Types.VARCHAR, array.getBaseType()); @@ -577,9 +590,9 @@ void createArray() throws SQLException { @ParameterizedTest(name = "{0}") @MethodSource("unsupported") - void shouldThrowSQLFeatureNotSupportedException(String name, Executable function) throws FireboltException { + void shouldThrowSQLFeatureNotSupportedException(String name, Executable function) throws SQLException { connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService); + fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService); assertThrows(SQLFeatureNotSupportedException.class, function); } @@ -587,7 +600,42 @@ void shouldThrowSQLFeatureNotSupportedException(String name, Executable function @MethodSource("empty") void shouldReturnEmptyResult(String name, Callable function, Object expected) throws Exception { connection = new FireboltConnection(URL, connectionProperties, fireboltAuthenticationService, - fireboltEngineService, fireboltStatementService); + fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService); assertEquals(expected, function.call()); } + + void shouldGetEngineUrlWhenEngineIsProvided() throws SQLException { + connectionProperties.put("engine", "engine"); + when(fireboltEngineService.getEngine(any(), any())).thenReturn(new Engine("http://my_endpoint", null, null, null)); + try (FireboltConnection connection = createConnection(URL, connectionProperties)) { + verify(fireboltEngineService).getEngine("engine", "db"); + assertEquals("http://my_endpoint", connection.getSessionProperties().getHost()); + } + } + + @Test + void shouldNotGetEngineUrlOrDefaultEngineUrlWhenUsingSystemEngine() throws SQLException { + connectionProperties.put("database", "my_db"); + when(fireboltGatewayUrlService.getUrl(any(), any())).thenReturn("http://my_endpoint"); + + try (FireboltConnection connection = createConnection(SYSTEM_ENGINE_URL, connectionProperties)) { + verify(fireboltEngineService, times(0)).getEngine(isNull(), eq("my_db")); + assertEquals("my_endpoint", connection.getSessionProperties().getHost()); + } + } + + @Test + void noEngineAndDb() throws SQLException { + when(fireboltGatewayUrlService.getUrl(any(), any())).thenReturn("http://my_endpoint"); + + try (FireboltConnection connection = createConnection("jdbc:firebolt:?env=dev&account=dev", connectionProperties)) { + assertEquals("my_endpoint", connection.getSessionProperties().getHost()); + assertNull(connection.getSessionProperties().getEngine()); + assertTrue(connection.getSessionProperties().isSystemEngine()); + } + } + + private FireboltConnection createConnection(String url, Properties props) throws SQLException { + return new FireboltConnection(url, props, fireboltAuthenticationService, fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService); + } } diff --git a/src/test/java/com/firebolt/jdbc/connection/FireboltJdbcUrlUtilTest.java b/src/test/java/com/firebolt/jdbc/connection/FireboltJdbcUrlUtilTest.java deleted file mode 100644 index 598a6429c..000000000 --- a/src/test/java/com/firebolt/jdbc/connection/FireboltJdbcUrlUtilTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.firebolt.jdbc.connection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Properties; - -import org.junit.jupiter.api.Test; - -class FireboltJdbcUrlUtilTest { - - @Test - void shouldGetAllPropertiesFromUri() { - String uri = "jdbc:firebolt://api.dev.firebolt.io:123/Tutorial_11_05?use_standard_sql=0&account=firebolt"; - Properties properties = FireboltJdbcUrlUtil.extractProperties(uri); - - Properties expectedProperties = new Properties(); - expectedProperties.put("path", "/Tutorial_11_05"); - expectedProperties.put("host", "api.dev.firebolt.io"); - expectedProperties.put("port", "123"); - expectedProperties.put("use_standard_sql", "0"); - expectedProperties.put("account", "firebolt"); - - assertEquals(expectedProperties, properties); - } -} diff --git a/src/test/java/com/firebolt/jdbc/connection/UrlUtilTest.java b/src/test/java/com/firebolt/jdbc/connection/UrlUtilTest.java new file mode 100644 index 000000000..d0e0ceacf --- /dev/null +++ b/src/test/java/com/firebolt/jdbc/connection/UrlUtilTest.java @@ -0,0 +1,29 @@ +package com.firebolt.jdbc.connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Properties; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class UrlUtilTest { + + + @ParameterizedTest + @CsvSource({ + "jdbc:firebolt:Tutorial_11_05/?use_standard_sql=0&host=api.dev.firebolt.io&account=firebolt, Tutorial_11_05, api.dev.firebolt.io, 0, firebolt", + "jdbc:firebolt:Tutorial_11_05?use_standard_sql=0&host=api.dev.firebolt.io&account=firebolt, Tutorial_11_05, api.dev.firebolt.io, 0, firebolt", + "jdbc:firebolt:/?use_standard_sql=0&host=api.dev.firebolt.io&account=firebolt, '', api.dev.firebolt.io, 0, firebolt" + }) + void shouldGetAllPropertiesFromUri(String uri, String expectedPath, String expectedHost, String expectedUseStandardSql, String expectedAccount) { + Properties properties = UrlUtil.extractProperties(uri); + Properties expectedProperties = new Properties(); + expectedProperties.put("path", expectedPath); + expectedProperties.put("host", expectedHost); + expectedProperties.put("use_standard_sql", expectedUseStandardSql); + expectedProperties.put("account", expectedAccount); + assertEquals(expectedProperties, properties); + } + +} diff --git a/src/test/java/com/firebolt/jdbc/connection/settings/FireboltPropertiesTest.java b/src/test/java/com/firebolt/jdbc/connection/settings/FireboltPropertiesTest.java index 8edc77e92..e2f3fa529 100644 --- a/src/test/java/com/firebolt/jdbc/connection/settings/FireboltPropertiesTest.java +++ b/src/test/java/com/firebolt/jdbc/connection/settings/FireboltPropertiesTest.java @@ -1,26 +1,30 @@ package com.firebolt.jdbc.connection.settings; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import java.util.HashMap; import java.util.Map; import java.util.Properties; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; class FireboltPropertiesTest { @Test void shouldHaveDefaultPropertiesWhenOnlyTheRequiredFieldsAreSpecified() { - FireboltProperties expectedDefaultProperties = FireboltProperties.builder().database("db").bufferSize(65536) - .sslCertificatePath("").sslMode("strict").path("/").port(443) // 443 by default as SSL is enabled by - // default - .compress(true).user(null).password(null).host("host").ssl(true).additionalProperties(new HashMap<>()) - .account(null).engine(null).keepAliveTimeoutMillis(300000).maxConnectionsTotal(300).maxRetries(3) - .socketTimeoutMillis(0).connectionTimeoutMillis(60000).tcpKeepInterval(30).tcpKeepIdle(60) + FireboltProperties expectedDefaultProperties = FireboltProperties.builder().engine("engine").database("db").bufferSize(65536) + .sslCertificatePath("").sslMode("strict").path("").port(443) // 443 by default as SSL is enabled by + .systemEngine(false).compress(true) // default + .principal(null).secret(null).host("host").ssl(true).additionalProperties(new HashMap<>()) + .keepAliveTimeoutMillis(300000).maxConnectionsTotal(300).maxRetries(3) + .socketTimeoutMillis(0).connectionTimeoutMillis(60000).tcpKeepInterval(30).environment("app").tcpKeepIdle(60) .tcpKeepCount(10).build(); Properties properties = new Properties(); + properties.put("engine", "engine"); properties.put("host", "host"); properties.put("database", "db"); assertEquals(expectedDefaultProperties, FireboltProperties.of(properties)); @@ -29,6 +33,7 @@ void shouldHaveDefaultPropertiesWhenOnlyTheRequiredFieldsAreSpecified() { @Test void shouldHaveAllTheSpecifiedCustomProperties() { Properties properties = new Properties(); + properties.put("engine", "my_test"); properties.put("buffer_size", "51"); properties.put("socket_timeout_millis", "20"); properties.put("ssl", "1"); @@ -36,26 +41,26 @@ void shouldHaveAllTheSpecifiedCustomProperties() { properties.put("database", "myDb"); properties.put("ssl_certificate_path", "root_cert"); properties.put("ssl_mode", "none"); - properties.put("path", "/example"); + properties.put("path", "example"); properties.put("someCustomProperties", "custom_value"); properties.put("compress", "1"); Map customProperties = new HashMap<>(); customProperties.put("someCustomProperties", "custom_value"); - FireboltProperties expectedDefaultProperties = FireboltProperties.builder().bufferSize(51) - .sslCertificatePath("root_cert").sslMode("none").path("/example").database("myDb").compress(true) - .port(443).user(null).password(null).host("myDummyHost").ssl(true) - .additionalProperties(customProperties).account(null).engine(null).keepAliveTimeoutMillis(300000) + FireboltProperties expectedDefaultProperties = FireboltProperties.builder().engine("my_test").bufferSize(51) + .sslCertificatePath("root_cert").sslMode("none").path("example").database("myDb").compress(true) + .port(443).principal(null).secret(null).host("myDummyHost").ssl(true).systemEngine(false) + .additionalProperties(customProperties).keepAliveTimeoutMillis(300000) .maxConnectionsTotal(300).maxRetries(3).socketTimeoutMillis(20).connectionTimeoutMillis(60000) - .tcpKeepInterval(30).tcpKeepIdle(60).tcpKeepCount(10).build(); + .tcpKeepInterval(30).tcpKeepIdle(60).tcpKeepCount(10).environment("app").build(); assertEquals(expectedDefaultProperties, FireboltProperties.of(properties)); } @Test void shouldUsePathParamAsDb() { Properties properties = new Properties(); - properties.put("path", "/example"); + properties.put("path", "example"); properties.put("host", "host"); assertEquals("example", FireboltProperties.of(properties).getDatabase()); @@ -64,7 +69,7 @@ void shouldUsePathParamAsDb() { @Test void shouldSupportBooleansForBooleanProperties() { Properties properties = new Properties(); - properties.put("path", "/example"); + properties.put("path", "example"); properties.put("host", "host"); properties.put("ssl", "true"); properties.put("compress", "false"); @@ -76,7 +81,7 @@ void shouldSupportBooleansForBooleanProperties() { @Test void shouldSupportIntForBooleanProperties() { Properties properties = new Properties(); - properties.put("path", "/example"); + properties.put("path", "example"); properties.put("host", "host"); properties.put("ssl", "2"); properties.put("compress", "0"); @@ -88,7 +93,7 @@ void shouldSupportIntForBooleanProperties() { @Test void shouldUseCustomPortWhenProvided() { Properties properties = new Properties(); - properties.put("path", "/example"); + properties.put("path", "example"); properties.put("host", "host"); properties.put("port", "999"); @@ -96,35 +101,13 @@ void shouldUseCustomPortWhenProvided() { } @Test - void shouldThrowExceptionWhenNoDbProvided() { + void shouldUseSystemEngineWhenNoDbOrEngineProvided() { Properties properties = new Properties(); - properties.put("host", "host"); - - assertThrows(IllegalArgumentException.class, () -> FireboltProperties.of(properties)); - } - - @Test - void shouldThrowExceptionWhenHostIsNotProvided() { - Properties properties = new Properties(); - assertThrows(IllegalArgumentException.class, () -> FireboltProperties.of(properties)); - } - - @Test - void shouldThrowExceptionWhenDbPathFormatIsInvalid() { - Properties properties = new Properties(); - properties.put("path", ""); - properties.put("host", "host"); - - assertThrows(IllegalArgumentException.class, () -> FireboltProperties.of(properties)); - } - - @Test - void shouldNotReturnAliasAsCustomProperty() { - Properties properties = new Properties(); - properties.put("path", ""); - properties.put("host", "host"); - - assertThrows(IllegalArgumentException.class, () -> FireboltProperties.of(properties)); + FireboltProperties fireboltProperties = FireboltProperties.of(properties); + assertTrue(fireboltProperties.isSystemEngine()); + assertNull(fireboltProperties.getEngine()); + assertNull(fireboltProperties.getDatabase()); + assertFalse(fireboltProperties.isCompress()); } @Test @@ -140,4 +123,49 @@ void shouldSupportUserClientsAndDrivers() { assertEquals(clients, FireboltProperties.of(properties).getUserClients()); assertEquals(drivers, FireboltProperties.of(properties).getUserDrivers()); } + + @Test + void noEngineNoDbSystemEngine() { + assertNull(FireboltProperties.of(new Properties()).getEngine()); + } + + @ParameterizedTest + @CsvSource(value = { + "env, qa,,api.qa.firebolt.io,qa", + "environment, test,,api.test.firebolt.io,test", + "env, staging,super-host.com,super-host.com,staging", + "env,,my-host.com,my-host.com,app", + "env,,api.dev.firebolt.io,api.dev.firebolt.io,dev", + "env,,something.io,something.io,app", // not standard host, no configured environment -> default environment + ",,,api.app.firebolt.io,app", // no host, no environment -> default environment (app) and default host api.app.firebolt.io + ",,api.app.firebolt.io,api.app.firebolt.io,app", // no configured environment, discover default environment from host + ",,api.dev.firebolt.io,api.dev.firebolt.io,dev", // no configured environment, discover not default environment from host + }, delimiter = ',') + void hostAndEnvironment(String envKey, String envValue, String host, String expectedHost, String expectedEnvironment) { + Properties properties = properties(envKey, envValue, host); + assertEquals(expectedHost, FireboltProperties.of(properties).getHost()); + assertEquals(expectedEnvironment, FireboltProperties.of(properties).getEnvironment()); + } + + @ParameterizedTest + @CsvSource(value = { + "env,app,api.dev.firebolt.io", + "env,qa,api.app.firebolt.io", + }, delimiter = ',') + void environmentDoesNotMatch(String envKey, String envValue, String host) { + Properties properties = properties(envKey, envValue, host); + assertThrows(IllegalStateException.class, () -> FireboltProperties.of(properties)); + } + + private Properties properties(String envKey, String envValue, String host) { + Properties properties = new Properties(); + if (envValue != null) { + properties.put(envKey, envValue); + } + if (host != null) { + properties.put("host", host); + } + return properties; + } + } diff --git a/src/test/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadataTest.java b/src/test/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadataTest.java index eb8ca7d14..5a44d2f5c 100644 --- a/src/test/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadataTest.java +++ b/src/test/java/com/firebolt/jdbc/metadata/FireboltDatabaseMetadataTest.java @@ -300,17 +300,17 @@ void shouldGetTables() throws SQLException { @Test void shouldGetDriverMajorVersion() { - assertEquals(2, fireboltDatabaseMetadata.getDriverMajorVersion()); + assertEquals(3, fireboltDatabaseMetadata.getDriverMajorVersion()); } @Test void shouldGetDriverMinorVersion() { - assertEquals(4, fireboltDatabaseMetadata.getDriverMinorVersion()); + assertEquals(0, fireboltDatabaseMetadata.getDriverMinorVersion()); } @Test void shouldGetDriverVersion() throws SQLException { - assertEquals("2.4.6-SNAPSHOT", fireboltDatabaseMetadata.getDriverVersion()); + assertEquals("3.0.0", fireboltDatabaseMetadata.getDriverVersion()); } @Test diff --git a/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java b/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java index 457ee25b6..f30acc69f 100644 --- a/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java +++ b/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java @@ -26,8 +26,9 @@ class FireboltAuthenticationServiceTest { private static final String USER = "usr"; private static final String PASSWORD = "PA§§WORD"; - private static final FireboltProperties PROPERTIES = FireboltProperties.builder().user(USER).password(PASSWORD) - .compress(true).build(); + private static final String ENV = "ENV"; + + private static final FireboltProperties PROPERTIES = FireboltProperties.builder().principal(USER).secret(PASSWORD).environment(ENV).compress(true).build(); @Mock private FireboltAuthenticationClient fireboltAuthenticationClient; @@ -44,10 +45,10 @@ void shouldGetConnectionToken() throws IOException, FireboltException { String randomHost = UUID.randomUUID().toString(); FireboltConnectionTokens tokens = FireboltConnectionTokens.builder().expiresInSeconds(52) .refreshToken("refresh").accessToken("access").build(); - when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD)).thenReturn(tokens); + when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)).thenReturn(tokens); assertEquals(tokens, fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES)); - verify(fireboltAuthenticationClient).postConnectionTokens(randomHost, USER, PASSWORD); + verify(fireboltAuthenticationClient).postConnectionTokens(randomHost, USER, PASSWORD, ENV); } @Test @@ -55,11 +56,11 @@ void shouldCallClientOnlyOnceWhenServiceCalledTwiceForTheSameHost() throws IOExc String randomHost = UUID.randomUUID().toString(); FireboltConnectionTokens tokens = FireboltConnectionTokens.builder().expiresInSeconds(52) .refreshToken("refresh").accessToken("access").build(); - when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD)).thenReturn(tokens); + when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)).thenReturn(tokens); fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES); assertEquals(tokens, fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES)); - verify(fireboltAuthenticationClient).postConnectionTokens(randomHost, USER, PASSWORD); + verify(fireboltAuthenticationClient).postConnectionTokens(randomHost, USER, PASSWORD, ENV); } @Test @@ -69,7 +70,7 @@ void shouldGetConnectionTokenAfterRemoving() throws IOException, FireboltExcepti .refreshToken("refresh").accessToken("one").build(); FireboltConnectionTokens token2 = FireboltConnectionTokens.builder().expiresInSeconds(52) .refreshToken("refresh").accessToken("two").build(); - when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD)).thenReturn(token1, token2); + when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)).thenReturn(token1, token2); fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES); assertEquals(token1, fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES)); @@ -77,13 +78,13 @@ void shouldGetConnectionTokenAfterRemoving() throws IOException, FireboltExcepti fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES); assertEquals(token2, fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES)); - verify(fireboltAuthenticationClient, Mockito.times(2)).postConnectionTokens(randomHost, USER, PASSWORD); + verify(fireboltAuthenticationClient, Mockito.times(2)).postConnectionTokens(randomHost, USER, PASSWORD, ENV); } @Test void shouldThrowExceptionWithServerResponseWhenAResponseIsAvailable() throws IOException, FireboltException { String randomHost = UUID.randomUUID().toString(); - Mockito.when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD)) + Mockito.when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)) .thenThrow(new FireboltException("An error happened during authentication", 403, "INVALID PASSWORD")); FireboltException ex = assertThrows(FireboltException.class, @@ -96,7 +97,7 @@ void shouldThrowExceptionWithServerResponseWhenAResponseIsAvailable() throws IOE @Test void shouldThrowExceptionWithExceptionMessageWhenAResponseIsNotAvailable() throws IOException, FireboltException { String randomHost = UUID.randomUUID().toString(); - Mockito.when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD)) + Mockito.when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)) .thenThrow(new NullPointerException("NULL!")); FireboltException ex = assertThrows(FireboltException.class, diff --git a/src/test/java/com/firebolt/jdbc/service/FireboltEngineServiceTest.java b/src/test/java/com/firebolt/jdbc/service/FireboltEngineServiceTest.java index ec83e03d8..4bf0cc15f 100644 --- a/src/test/java/com/firebolt/jdbc/service/FireboltEngineServiceTest.java +++ b/src/test/java/com/firebolt/jdbc/service/FireboltEngineServiceTest.java @@ -1,198 +1,102 @@ package com.firebolt.jdbc.service; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; - -import java.sql.SQLException; - +import com.firebolt.jdbc.connection.Engine; +import com.firebolt.jdbc.connection.FireboltConnection; +import com.firebolt.jdbc.exception.FireboltException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import com.firebolt.jdbc.client.account.FireboltAccountClient; -import com.firebolt.jdbc.client.account.response.FireboltAccountResponse; -import com.firebolt.jdbc.client.account.response.FireboltDefaultDatabaseEngineResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineIdResponse; -import com.firebolt.jdbc.client.account.response.FireboltEngineResponse; -import com.firebolt.jdbc.connection.settings.FireboltProperties; -import com.firebolt.jdbc.exception.FireboltException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class FireboltEngineServiceTest { - private static final String HOST = "https://host"; - private static final String ACCOUNT_ID = "account_id"; - private static final String DB_NAME = "dbName"; - private static final String ENGINE_NAME = "engineName"; - private static final String ENGINE_ID = "engineId"; - private static final String ACCESS_TOKEN = "token"; - - @Mock - private FireboltAccountClient fireboltAccountClient; - @InjectMocks private FireboltEngineService fireboltEngineService; - @Test - void shouldGetDefaultDbEngineWhenEngineNameIsNullOrEmpty() throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).account(ACCOUNT_ID).database(DB_NAME) - .compress(false).build(); - - when(fireboltAccountClient.getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN)) - .thenReturn(FireboltAccountResponse.builder().accountId(ACCOUNT_ID).build()); - when(fireboltAccountClient.getDefaultEngineByDatabaseName(HOST, ACCOUNT_ID, DB_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltDefaultDatabaseEngineResponse.builder().engineUrl("URL").build()); - fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN); - - verify(fireboltAccountClient).getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN); - verify(fireboltAccountClient).getDefaultEngineByDatabaseName(HOST, ACCOUNT_ID, DB_NAME, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); - } - - @Test - void shouldGThrowExceptionWhenGettingDefaultEngineAndTheUrlReturnedFromTheServerIsNull() throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).account(ACCOUNT_ID).database(DB_NAME) - .compress(false).build(); - - when(fireboltAccountClient.getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN)) - .thenReturn(FireboltAccountResponse.builder().accountId(ACCOUNT_ID).build()); - when(fireboltAccountClient.getDefaultEngineByDatabaseName(HOST, ACCOUNT_ID, DB_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltDefaultDatabaseEngineResponse.builder().engineUrl(null).build()); - FireboltException exception = assertThrows(FireboltException.class, - () -> fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN)); - assertEquals( - "There is no Firebolt engine running on https://host attached to the database dbName. To connect first make sure there is a running engine and then try again.", - exception.getMessage()); - - verify(fireboltAccountClient).getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN); - verify(fireboltAccountClient).getDefaultEngineByDatabaseName(HOST, ACCOUNT_ID, DB_NAME, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); - } - - @Test - void shouldGetEngineWhenEngineNameIsPresent() throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).account(ACCOUNT_ID).database(DB_NAME) - .engine(ENGINE_NAME).compress(false).build(); - when(fireboltAccountClient.getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN)) - .thenReturn(FireboltAccountResponse.builder().accountId(ACCOUNT_ID).build()); - when(fireboltAccountClient.getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltEngineIdResponse.builder() - .engine(FireboltEngineIdResponse.Engine.builder().engineId(ENGINE_ID).build()).build()); - when(fireboltAccountClient.getEngine(HOST, ACCOUNT_ID, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN)) - .thenReturn(FireboltEngineResponse.builder() - .engine(FireboltEngineResponse.Engine.builder().endpoint("ANY").build()).build()); - fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN); - - verify(fireboltAccountClient).getAccount(properties.getHost(), ACCOUNT_ID, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngine(HOST, ACCOUNT_ID, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); - } - - @Test - void shouldNotGetAccountWhileGettingEngineIfAccountIdIsNotPresent() throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).database(DB_NAME) - .engine(ENGINE_NAME).compress(false).build(); - when(fireboltAccountClient.getEngineId(HOST, null, ENGINE_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltEngineIdResponse.builder() - .engine(FireboltEngineIdResponse.Engine.builder().engineId(ENGINE_ID).build()).build()); - when(fireboltAccountClient.getEngine(HOST, null, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN)) - .thenReturn(FireboltEngineResponse.builder() - .engine(FireboltEngineResponse.Engine.builder().endpoint("ANY").build()).build()); - fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN); - - verify(fireboltAccountClient, times(0)).getAccount(any(), any(), any()); - verify(fireboltAccountClient).getEngineId(HOST, null, ENGINE_NAME, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngine(HOST, null, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); - } + @Mock + private FireboltConnection fireboltConnection; @Test - void shouldThrowExceptionWhenEngineNameIsSpecifiedButUrlIsNotPresentInTheResponse() throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).account(ACCOUNT_ID).database(DB_NAME) - .engine(ENGINE_NAME).compress(false).build(); - when(fireboltAccountClient.getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN)) - .thenReturn(FireboltAccountResponse.builder().accountId(ACCOUNT_ID).build()); - when(fireboltAccountClient.getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltEngineIdResponse.builder() - .engine(FireboltEngineIdResponse.Engine.builder().engineId(ENGINE_ID).build()).build()); - when(fireboltAccountClient.getEngine(HOST, ACCOUNT_ID, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN)) - .thenReturn(FireboltEngineResponse.builder() - .engine(FireboltEngineResponse.Engine.builder().endpoint(null).build()).build()); - FireboltException exception = assertThrows(FireboltException.class, - () -> fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN)); - assertEquals( - "There is no Firebolt engine running on https://host with the name engineName. To connect first make sure there is a running engine and then try again.", - exception.getMessage()); - - verify(fireboltAccountClient).getAccount(properties.getHost(), ACCOUNT_ID, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngine(HOST, ACCOUNT_ID, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); + void shouldGetEngineNameFromEngineHost() throws SQLException { + assertEquals("myHost_345", fireboltEngineService.getEngineNameByHost("myHost-345.firebolt.io")); } @Test - void shouldThrowExceptionWhenEngineNameIsSpecifiedButEngineIdIsNotPresentInTheServerResponse() throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).account(ACCOUNT_ID).database(DB_NAME) - .engine(ENGINE_NAME).compress(false).build(); - when(fireboltAccountClient.getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN)) - .thenReturn(FireboltAccountResponse.builder().accountId(ACCOUNT_ID).build()); - when(fireboltAccountClient.getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltEngineIdResponse.builder() - .engine(FireboltEngineIdResponse.Engine.builder().engineId(null).build()).build()); - FireboltException exception = assertThrows(FireboltException.class, - () -> fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN)); - assertEquals( - "Failed to extract engine id field from the server response: the response from the server is invalid.", - exception.getMessage()); - verify(fireboltAccountClient).getAccount(properties.getHost(), ACCOUNT_ID, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); + void shouldThrowExceptionWhenThEngineCannotBeEstablishedFromTheHost() { + assertThrows(FireboltException.class, () -> fireboltEngineService.getEngineNameByHost("myHost-345")); } @Test - void shouldGetEngineNameFromEngineHost() throws SQLException { - assertEquals("myHost_345", fireboltEngineService.getEngineNameFromHost("myHost-345.firebolt.io")); + void shouldThrowExceptionWhenThEngineCannotBeEstablishedFromNullHost() { + assertThrows(FireboltException.class, () -> fireboltEngineService.getEngineNameByHost(null)); } @Test - void shouldThrowExceptionWhenThEngineCannotBeEstablishedFromTheHost() { - assertThrows(FireboltException.class, () -> fireboltEngineService.getEngineNameFromHost("myHost-345")); + void shouldGetDefaultEngineWhenEngineNameIsNotProvided() throws SQLException { + assertThrows(IllegalArgumentException.class, () -> fireboltEngineService.getEngine(null, "db")); } @Test - void shouldThrowExceptionWhenThEngineCannotBeEstablishedFromNullHost() { - assertThrows(FireboltException.class, () -> fireboltEngineService.getEngineNameFromHost(null)); + void shouldGetEngineWhenEngineNameIsProvided() throws SQLException { + PreparedStatement statement = mock(PreparedStatement.class); + ResultSet resultSet = mockedResultSet(Map.of("status", "running", "url", "https://url", "attached_to", "db", "engine_name", "some-engine")); + when(fireboltConnection.prepareStatement(anyString())).thenReturn(statement); + when(statement.executeQuery()).thenReturn(resultSet); + assertEquals(new Engine("https://url", "running", "some-engine", "db"), fireboltEngineService.getEngine("some-engine", "db")); } @ParameterizedTest - @ValueSource(strings = { "ENGINE_STATUS_PROVISIONING_STARTED", "ENGINE_STATUS_PROVISIONING_FINISHED", - "ENGINE_STATUS_PROVISIONING_PENDING" }) - void shouldThrowExceptionWhenEngineStatusIndicatesEngineIsStarting(String status) throws Exception { - FireboltProperties properties = FireboltProperties.builder().host(HOST).account(ACCOUNT_ID).database(DB_NAME) - .engine(ENGINE_NAME).compress(false).build(); - when(fireboltAccountClient.getAccount(properties.getHost(), properties.getAccount(), ACCESS_TOKEN)) - .thenReturn(FireboltAccountResponse.builder().accountId(ACCOUNT_ID).build()); - when(fireboltAccountClient.getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN)) - .thenReturn(FireboltEngineIdResponse.builder() - .engine(FireboltEngineIdResponse.Engine.builder().engineId(ENGINE_ID).build()).build()); - when(fireboltAccountClient.getEngine(HOST, ACCOUNT_ID, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN)) - .thenReturn(FireboltEngineResponse.builder() - .engine(FireboltEngineResponse.Engine.builder().endpoint("ANY").currentStatus(status).build()) - .build()); - FireboltException exception = assertThrows(FireboltException.class, - () -> fireboltEngineService.getEngine(HOST, properties, ACCESS_TOKEN)); - assertEquals("The engine engineName is currently starting. Please wait until the engine is on and then execute the query again.", - exception.getMessage()); + @CsvSource(value = { + "engine1;db1;http://url1;running;;The engine with the name engine1 is not attached to any database", + "engine1;db1;http://url1;running;db2;The engine with the name engine1 is not attached to database db1", + "engine1;db1;http://url1;starting;;The engine with the name engine1 is not running. Status: starting", + "engine2;;;;;The engine with the name engine2 could not be found", + }, delimiter = ';') + void shouldThrowExceptionWhenSomethingIsWrong(String engineName, String db, String endpoint, String status, String attachedDb, String errorMessage) throws SQLException { + PreparedStatement statement = mock(PreparedStatement.class); + Map rsData = null; + if (endpoint != null || status != null || attachedDb != null) { + rsData = new HashMap<>(); + rsData.put("url", endpoint); + rsData.put("status", status); + rsData.put("attached_to", attachedDb); + rsData.put("engine_name", engineName); + } + ResultSet resultSet = mockedResultSet(rsData); + when(fireboltConnection.prepareStatement(Mockito.matches(Pattern.compile("SELECT.+JOIN", Pattern.MULTILINE | Pattern.DOTALL)))).thenReturn(statement); + when(statement.executeQuery()).thenReturn(resultSet); + assertEquals(errorMessage, assertThrows(FireboltException.class, () -> fireboltEngineService.getEngine(engineName, db)).getMessage()); + Mockito.verify(statement, Mockito.times(1)).setString(1, engineName); + } - verify(fireboltAccountClient).getAccount(properties.getHost(), ACCOUNT_ID, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngineId(HOST, ACCOUNT_ID, ENGINE_NAME, ACCESS_TOKEN); - verify(fireboltAccountClient).getEngine(HOST, ACCOUNT_ID, ENGINE_NAME, ENGINE_ID, ACCESS_TOKEN); - verifyNoMoreInteractions(fireboltAccountClient); + private ResultSet mockedResultSet(Map values) throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + if (values == null) { + when(resultSet.next()).thenReturn(false); + } else { + when(resultSet.next()).thenReturn(true, false); + for (Entry column : values.entrySet()) { + lenient().when(resultSet.getString(column.getKey())).thenReturn(column.getValue()); + } + } + return resultSet; } } diff --git a/src/test/java/com/firebolt/jdbc/service/FireboltStatementServiceTest.java b/src/test/java/com/firebolt/jdbc/service/FireboltStatementServiceTest.java index 01b41efd6..baedb338a 100644 --- a/src/test/java/com/firebolt/jdbc/service/FireboltStatementServiceTest.java +++ b/src/test/java/com/firebolt/jdbc/service/FireboltStatementServiceTest.java @@ -35,9 +35,9 @@ void shouldExecuteQueryAndCreateResultSet() throws SQLException { StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("SELECT 1").get(0); FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("firebolt1") .ssl(true).compress(false).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, false); + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); - fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, 10, -1, true, + fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, 10, -1, true, false, mock(FireboltStatement.class)); verify(statementClient).executeSqlStatement(statementInfoWrapper, fireboltProperties, false, 10, true); Assertions.assertEquals(1, mocked.constructed().size()); @@ -50,8 +50,8 @@ void shouldExecuteQueryWithLocalHostFormatParameters() throws SQLException { StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("SELECT 1").get(0); FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("localhost") .ssl(true).compress(false).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, false); - fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, -1, 10, true, + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); + fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, -1, 10, true, false, mock(FireboltStatement.class)); Assertions.assertEquals(1, mocked.constructed().size()); verify(statementClient).executeSqlStatement(statementInfoWrapper, fireboltProperties, false, -1, true); @@ -63,7 +63,7 @@ void shouldCancelQueryWithAllRequiredParams() throws FireboltException { FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("http://firebolt1") .ssl(true).compress(false).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, false); + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); fireboltStatementService.abortStatement("123", fireboltProperties); verify(statementClient).abortStatement("123", fireboltProperties); } @@ -71,9 +71,9 @@ void shouldCancelQueryWithAllRequiredParams() throws FireboltException { @Test void shouldThrowExceptionWhenTryingToCancelQueryWithASystemEngine() throws FireboltException { FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("http://firebolt1") - .ssl(true).compress(false).build(); + .ssl(true).compress(false).systemEngine(true).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, true); + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); assertThrows(FireboltException.class, () -> fireboltStatementService.abortStatement("123", fireboltProperties)); verifyNoInteractions(statementClient); } @@ -84,8 +84,8 @@ void shouldExecuteQueryWithParametersForSystemEngine() throws SQLException { StatementInfoWrapper statementInfoWrapper = StatementUtil.parseToStatementInfoWrappers("SELECT 1").get(0); FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("firebolt1") .ssl(true).compress(false).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, true); - fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, 10, 10, true, + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); + fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, 10, 10, true, true, mock(FireboltStatement.class)); Assertions.assertEquals(1, mocked.constructed().size()); verify(statementClient).executeSqlStatement(statementInfoWrapper, fireboltProperties, true, 10, true); @@ -100,8 +100,8 @@ void shouldIncludeNonStandardSqlQueryParamForNonStandardSql() throws SQLExceptio FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("localhost") .ssl(true).compress(false).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, true); - fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, -1, 0, false, + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); + fireboltStatementService.execute(statementInfoWrapper, fireboltProperties, -1, 0, false, true, mock(FireboltStatement.class)); Assertions.assertEquals(1, mocked.constructed().size()); verify(statementClient).executeSqlStatement(statementInfoWrapper, fireboltProperties, true, -1, false); @@ -115,9 +115,9 @@ void shouldBeEmptyWithNonQueryStatement() throws SQLException { FireboltProperties fireboltProperties = FireboltProperties.builder().database("db").host("localhost").ssl(true) .compress(false).build(); - FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient, false); + FireboltStatementService fireboltStatementService = new FireboltStatementService(statementClient); Assertions.assertEquals(Optional.empty(), fireboltStatementService.execute(statementInfoWrapper, - fireboltProperties, -1, 10, true, mock(FireboltStatement.class))); + fireboltProperties, -1, 10, true, false, mock(FireboltStatement.class))); verify(statementClient).executeSqlStatement(statementInfoWrapper, fireboltProperties, false, -1, true); } diff --git a/src/test/java/com/firebolt/jdbc/statement/FireboltStatementTest.java b/src/test/java/com/firebolt/jdbc/statement/FireboltStatementTest.java index 00f990725..c1de437e3 100644 --- a/src/test/java/com/firebolt/jdbc/statement/FireboltStatementTest.java +++ b/src/test/java/com/firebolt/jdbc/statement/FireboltStatementTest.java @@ -124,9 +124,9 @@ void shouldCloseInputStreamOnClose() throws SQLException { FireboltStatement fireboltStatement = FireboltStatement.builder().statementService(fireboltStatementService) .sessionProperties(fireboltProperties).connection(connection).build(); - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.empty()); - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.of(rs)); fireboltStatement.executeQuery("show database"); fireboltStatement.close(); @@ -158,11 +158,11 @@ void shouldExecuteIfUpdateStatementWouldNotReturnAResultSet() throws SQLExceptio try (FireboltStatement fireboltStatement = FireboltStatement.builder() .statementService(fireboltStatementService).connection(fireboltConnection) .sessionProperties(fireboltProperties).build()) { - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.empty()); assertEquals(0, fireboltStatement.executeUpdate("INSERT INTO cars(sales, name) VALUES (500, 'Ford')")); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(fireboltProperties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(sales, name) VALUES (500, 'Ford')", queryInfoWrapperArgumentCaptor.getValue().getSql()); assertEquals(0, fireboltStatement.getUpdateCount()); @@ -180,7 +180,7 @@ void shouldCloseCurrentAndGetMoreResultsForMultiStatementQuery() throws SQLExcep FireboltStatement fireboltStatement = FireboltStatement.builder().statementService(fireboltStatementService) .sessionProperties(fireboltProperties).connection(connection).build(); - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.of(rs)).thenReturn(Optional.of(rs2)); fireboltStatement.execute("SELECT 1; SELECT 2;"); assertEquals(rs, fireboltStatement.getResultSet()); @@ -202,7 +202,7 @@ void shouldCloseCurrentAndGetMoreResultWhenCallingGetMoreResultsWithCloseCurrent .build(); FireboltStatement fireboltStatement = FireboltStatement.builder().statementService(fireboltStatementService) .sessionProperties(fireboltProperties).connection(connection).build(); - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.of(mock(FireboltResultSet.class))); fireboltStatement.execute("SELECT 1; SELECT 2;"); ResultSet resultSet = fireboltStatement.getResultSet(); @@ -217,7 +217,7 @@ void shouldKeepCurrentAndGetMoreResultWhenCallingGetMoreResultsWithKeepCurrentRe .build(); FireboltStatement fireboltStatement = FireboltStatement.builder().statementService(fireboltStatementService) .sessionProperties(fireboltProperties).connection(connection).build(); - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.of(mock(ResultSet.class))); fireboltStatement.execute("SELECT 1; SELECT 2;"); @@ -237,7 +237,7 @@ void shouldCloseUnclosedAndGetMoreResultWhenCallingGetMoreResultsWithCloseAllRes FireboltStatement fireboltStatement = FireboltStatement.builder().statementService(fireboltStatementService) .sessionProperties(fireboltProperties).connection(connection).build(); - when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.of(rs)).thenReturn(Optional.of(rs2)).thenReturn(Optional.of(rs3)); fireboltStatement.execute("SELECT 1; SELECT 2; SELECT 3;"); diff --git a/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java b/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java index e6137dd05..9cb0dc819 100644 --- a/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java +++ b/src/test/java/com/firebolt/jdbc/statement/preparedstatement/FireboltPreparedStatementTest.java @@ -114,7 +114,7 @@ private static Stream unsupported() { @BeforeEach void beforeEach() throws SQLException { lenient().when(properties.getBufferSize()).thenReturn(10); - lenient().when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), any())) + lenient().when(fireboltStatementService.execute(any(), any(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any())) .thenReturn(Optional.empty()); } @@ -138,7 +138,7 @@ void shouldExecute() throws SQLException { statement.setArray(7, new FireboltArray(FireboltDataType.TEXT, new String[] {"sedan", "hatchback", "coupe"})); statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars (sales, make, model, minor_model, color, type, types) VALUES (500,'Ford','FOCUS',NULL,NULL,'sedan',['sedan','hatchback','coupe'])", queryInfoWrapperArgumentCaptor.getValue().getSql()); @@ -156,7 +156,7 @@ void shouldExecuteBatch() throws SQLException { statement.addBatch(); statement.executeBatch(); verify(fireboltStatementService, times(2)).execute(queryInfoWrapperArgumentCaptor.capture(), - eq(this.properties), anyInt(), anyInt(), anyBoolean(), any()); + eq(this.properties), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars (sales, make) VALUES (150,'Ford')", queryInfoWrapperArgumentCaptor.getAllValues().get(0).getSql()); @@ -192,7 +192,7 @@ void shouldExecuteWithSpecialCharactersInQuery() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals(expectedSql, queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -226,7 +226,7 @@ void shouldSetNull() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars (sales, make) VALUES (NULL,NULL)", queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -239,7 +239,7 @@ void shouldSetBoolean() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(available) VALUES (1)", queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -265,7 +265,7 @@ void shouldSetInt() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); } @Test @@ -276,7 +276,7 @@ void shouldSetLong() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(price) VALUES (50)", queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -288,7 +288,7 @@ void shouldSetFloat() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(price) VALUES (5.5)", queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -301,7 +301,7 @@ void shouldSetDouble() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); } @Test @@ -312,7 +312,7 @@ void shouldSetBigDecimal() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(price) VALUES (555555555555.55555555)", queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -326,7 +326,7 @@ void shouldSetDate() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(release_date) VALUES ('2019-07-31')", queryInfoWrapperArgumentCaptor.getValue().getSql()); } @@ -340,7 +340,7 @@ void shouldSetTimeStamp() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals("INSERT INTO cars(release_date) VALUES ('2019-07-31 12:15:13')", queryInfoWrapperArgumentCaptor.getValue().getSql()); @@ -365,7 +365,7 @@ void shouldSetAllObjects() throws SQLException { statement.execute(); verify(fireboltStatementService).execute(queryInfoWrapperArgumentCaptor.capture(), eq(this.properties), - anyInt(), anyInt(), anyBoolean(), any()); + anyInt(), anyInt(), anyBoolean(), anyBoolean(), any()); assertEquals( "INSERT INTO cars(timestamp, date, float, long, big_decimal, null, boolean, int) VALUES ('2019-07-31 12:15:13','2019-07-31',5.5,5,555555555555.55555555,NULL,1,5)", diff --git a/src/test/java/com/firebolt/jdbc/util/PropertyUtilTest.java b/src/test/java/com/firebolt/jdbc/util/PropertyUtilTest.java index bc2aadfa5..e64869d4b 100644 --- a/src/test/java/com/firebolt/jdbc/util/PropertyUtilTest.java +++ b/src/test/java/com/firebolt/jdbc/util/PropertyUtilTest.java @@ -1,5 +1,6 @@ package com.firebolt.jdbc.util; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mockStatic; @@ -23,14 +24,14 @@ void shouldGetPropertyInfo() { FireboltSessionProperty.BUFFER_SIZE); mocked.when(FireboltSessionProperty::getNonDeprecatedProperties).thenReturn(existingProperties); DriverPropertyInfo accountDriverInfo = createExpectedDriverInfo(FireboltSessionProperty.ACCOUNT.getKey(), - FireboltSessionProperty.ACCOUNT.getDescription(), null); + FireboltSessionProperty.ACCOUNT.getDescription(), "myAccount"); DriverPropertyInfo bufferDriverInfo = createExpectedDriverInfo(FireboltSessionProperty.BUFFER_SIZE.getKey(), FireboltSessionProperty.BUFFER_SIZE.getDescription(), "1"); DriverPropertyInfo[] expected = new DriverPropertyInfo[] { accountDriverInfo, bufferDriverInfo }; for (int i = 0; i < expected.length; i++) { assertTrue(new ReflectionEquals( - PropertyUtil.getPropertyInfo("jdbc:firebolt://api.dev.firebolt.io/Tutorial_11_04?buffer_size=1", + PropertyUtil.getPropertyInfo("jdbc:firebolt:Tutorial_11_04/?buffer_size=1&account=myAccount", new Properties())[i]).matches(expected[i])); } } diff --git a/src/test/java/com/firebolt/jdbc/util/VersionUtilTest.java b/src/test/java/com/firebolt/jdbc/util/VersionUtilTest.java index 0e4331c6c..bdd5361ca 100644 --- a/src/test/java/com/firebolt/jdbc/util/VersionUtilTest.java +++ b/src/test/java/com/firebolt/jdbc/util/VersionUtilTest.java @@ -8,17 +8,17 @@ class VersionUtilTest { @Test void shouldGetDriverMajorVersion() { - assertEquals(2, VersionUtil.getMajorDriverVersion()); + assertEquals(3, VersionUtil.getMajorDriverVersion()); } @Test void shouldGetDriverMinorVersion() { - assertEquals(4, VersionUtil.getDriverMinorVersion()); + assertEquals(0, VersionUtil.getDriverMinorVersion()); } @Test void shouldGetProjectVersion() { - assertEquals("2.4.6-SNAPSHOT", VersionUtil.getDriverVersion()); + assertEquals("3.0.0", VersionUtil.getDriverVersion()); } @Test