diff --git a/.github/release-please.yml b/.github/release-please.yml index cbc451149..bd2014774 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -6,4 +6,4 @@ branches: releaseType: java-yoshi bumpMinorPreMajor: true handleGHRelease: true - +extraFiles: ["README.md"] diff --git a/.github/workflows/quickperf.yaml b/.github/workflows/quickperf.yaml new file mode 100644 index 000000000..03c242fef --- /dev/null +++ b/.github/workflows/quickperf.yaml @@ -0,0 +1,30 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Github action job to test core java library features on +# downstream client libraries before they are released. +on: + pull_request: +name: quickperf +jobs: + quickperf: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - name: Run tests + run: mvn test + working-directory: samples/quickperf diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml index 09f954387..8d462abb5 100644 --- a/.github/workflows/unmanaged_dependency_check.yaml +++ b/.github/workflows/unmanaged_dependency_check.yaml @@ -14,6 +14,6 @@ jobs: shell: bash run: .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.33.0 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.34.0 with: bom-path: pom.xml diff --git a/.kokoro/presubmit/graalvm-native-17.cfg b/.kokoro/presubmit/graalvm-native-17.cfg index 7008a7215..53cd15405 100644 --- a/.kokoro/presubmit/graalvm-native-17.cfg +++ b/.kokoro/presubmit/graalvm-native-17.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.33.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.34.0" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg index 931f9bb00..e211e47fc 100644 --- a/.kokoro/presubmit/graalvm-native.cfg +++ b/.kokoro/presubmit/graalvm-native.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.33.0" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.34.0" } env_vars: { diff --git a/CHANGELOG.md b/CHANGELOG.md index 424389937..e48516502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## [2.21.0](https://github.com/googleapis/java-spanner-jdbc/compare/v2.20.2...v2.21.0) (2024-08-23) + + +### Features + +* Add Quickperf for simple performance testing with JDBC ([#1619](https://github.com/googleapis/java-spanner-jdbc/issues/1619)) ([b6bbd8f](https://github.com/googleapis/java-spanner-jdbc/commit/b6bbd8f40c1ce61914e2c7b80be04abbf4e346ab)) + + +### Dependencies + +* Update dependency com.fasterxml.jackson.core:jackson-databind to v2.13.4.2 [security] ([#1710](https://github.com/googleapis/java-spanner-jdbc/issues/1710)) ([eff5df2](https://github.com/googleapis/java-spanner-jdbc/commit/eff5df22785e55a8f0974f028678883ef404b4e6)) +* Update dependency com.fasterxml.jackson.core:jackson-databind to v2.17.2 ([#1715](https://github.com/googleapis/java-spanner-jdbc/issues/1715)) ([21aa199](https://github.com/googleapis/java-spanner-jdbc/commit/21aa19970cee5ee0525c5eaae8bc334cf81d8f25)) +* Update dependency com.google.api.grpc:proto-google-cloud-trace-v1 to v2.48.0 ([#1719](https://github.com/googleapis/java-spanner-jdbc/issues/1719)) ([a40606c](https://github.com/googleapis/java-spanner-jdbc/commit/a40606c2ef75388cfa0733c6955329225f28c71b)) +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.72.0 ([#1702](https://github.com/googleapis/java-spanner-jdbc/issues/1702)) ([31a961d](https://github.com/googleapis/java-spanner-jdbc/commit/31a961d29c7b51e9dcd5aac8a8a66444abbd9088)) +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.73.0 ([#1726](https://github.com/googleapis/java-spanner-jdbc/issues/1726)) ([f5f8051](https://github.com/googleapis/java-spanner-jdbc/commit/f5f80517425969f4c1bab4ec1c72afa1ccbb842c)) +* Update dependency com.google.cloud:google-cloud-trace to v2.48.0 ([#1720](https://github.com/googleapis/java-spanner-jdbc/issues/1720)) ([c9b646d](https://github.com/googleapis/java-spanner-jdbc/commit/c9b646d1b9c0ccef9cf8ba3bc58da686fae34bc1)) +* Update dependency com.google.cloud:sdk-platform-java-config to v3.34.0 ([#1705](https://github.com/googleapis/java-spanner-jdbc/issues/1705)) ([f3f0c10](https://github.com/googleapis/java-spanner-jdbc/commit/f3f0c10394e76389dbc4a62e5702fd5f80c57b1a)) +* Update dependency com.spotify.fmt:fmt-maven-plugin to v2.24 ([#1708](https://github.com/googleapis/java-spanner-jdbc/issues/1708)) ([6881512](https://github.com/googleapis/java-spanner-jdbc/commit/68815128ae2c40c224b4ab155b942e8f5313024f)) +* Update dependency commons-cli:commons-cli to v1.9.0 ([#1716](https://github.com/googleapis/java-spanner-jdbc/issues/1716)) ([6f48065](https://github.com/googleapis/java-spanner-jdbc/commit/6f48065952c2fc2716c911a45959326a6bafaa13)) +* Update dependency io.opentelemetry:opentelemetry-bom to v1.41.0 ([#1703](https://github.com/googleapis/java-spanner-jdbc/issues/1703)) ([af58b7a](https://github.com/googleapis/java-spanner-jdbc/commit/af58b7a882edae9a50fbc0d4084cb74b3727d5a6)) +* Update dependency org.apache.commons:commons-lang3 to v3.16.0 ([#1717](https://github.com/googleapis/java-spanner-jdbc/issues/1717)) ([f5229ce](https://github.com/googleapis/java-spanner-jdbc/commit/f5229ce5099b6d2d2b7c099ff4ac1319f21860df)) +* Update dependency org.postgresql:postgresql to v42.7.4 ([#1722](https://github.com/googleapis/java-spanner-jdbc/issues/1722)) ([1328213](https://github.com/googleapis/java-spanner-jdbc/commit/13282136921d0167c19cac38df0a652cc5477faa)) +* Update dependency org.springframework.boot:spring-boot to v3.3.2 ([#1718](https://github.com/googleapis/java-spanner-jdbc/issues/1718)) ([ede7211](https://github.com/googleapis/java-spanner-jdbc/commit/ede72113801de4a27492cf672a4c5e3edb37bc5e)) +* Update dependency org.springframework.boot:spring-boot to v3.3.3 ([#1723](https://github.com/googleapis/java-spanner-jdbc/issues/1723)) ([55112ac](https://github.com/googleapis/java-spanner-jdbc/commit/55112ac5f00d4a8d7726fa8bc5e9428d08d21227)) +* Update dependency org.springframework.boot:spring-boot-starter-data-jdbc to v3.3.3 ([#1724](https://github.com/googleapis/java-spanner-jdbc/issues/1724)) ([db60f4f](https://github.com/googleapis/java-spanner-jdbc/commit/db60f4f4f8713a30c1fe275266ff455fd03d84a4)) +* Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.3 ([#1725](https://github.com/googleapis/java-spanner-jdbc/issues/1725)) ([47fda8f](https://github.com/googleapis/java-spanner-jdbc/commit/47fda8f9b8cf639fe11fb3241256490f660e0d8b)) +* Update dependency org.springframework.data:spring-data-bom to v2024.0.3 ([#1704](https://github.com/googleapis/java-spanner-jdbc/issues/1704)) ([e82d839](https://github.com/googleapis/java-spanner-jdbc/commit/e82d8398eede11469c966aa11c2188c671a5f02b)) +* Update dependency org.testcontainers:testcontainers to v1.20.1 ([#1684](https://github.com/googleapis/java-spanner-jdbc/issues/1684)) ([0907305](https://github.com/googleapis/java-spanner-jdbc/commit/09073057df2cff41b7a62f56dc0cf57ed62f4801)) + ## [2.20.2](https://github.com/googleapis/java-spanner-jdbc/compare/v2.20.1...v2.20.2) (2024-08-07) diff --git a/README.md b/README.md index e7c6e9e6c..80eb44152 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,32 @@ Java idiomatic client for [Google Cloud Spanner JDBC][product-docs]. If you are using Maven, add this to your pom.xml file: + ```xml com.google.cloud google-cloud-spanner-jdbc - 2.4.1 + 2.21.0 ``` + + If you are using Gradle without BOM, add this to your dependencies + ```Groovy -implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.4.1' +implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.21.0' ``` + If you are using SBT, add this to your dependencies + ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.4.1" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.21.0" ``` + ## Authentication diff --git a/pom.xml b/pom.xml index 9f17e9c78..220e0dcea 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-spanner-jdbc - 2.20.2 + 2.21.0 jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.33.0 + 3.34.0 @@ -61,7 +61,7 @@ com.google.cloud google-cloud-spanner-bom - 6.72.0 + 6.73.0 pom import @@ -166,7 +166,7 @@ org.testcontainers testcontainers - 1.19.8 + 1.20.1 test @@ -232,13 +232,13 @@ com.google.cloud google-cloud-trace - 2.47.0 + 2.48.0 test com.google.api.grpc proto-google-cloud-trace-v1 - 2.47.0 + 2.48.0 test @@ -426,6 +426,32 @@ + + + alt_build_dir + + + alt.build.dir + + + + ${alt.build.dir} + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + + ${alt.build.dir}/single.jar + + + + + + + @@ -433,7 +459,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.6.2 + 3.7.0 diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 87be572f4..522c41a61 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.20.1 + 2.20.2 diff --git a/samples/pom.xml b/samples/pom.xml index 31285be61..39d5d24c8 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -39,7 +39,7 @@ org.apache.maven.plugins maven-deploy-plugin - 3.1.2 + 3.1.3 true diff --git a/samples/quickperf/.gitignore b/samples/quickperf/.gitignore new file mode 100644 index 000000000..1df5b5e0c --- /dev/null +++ b/samples/quickperf/.gitignore @@ -0,0 +1,3 @@ +target +.vscode +.DS_Store \ No newline at end of file diff --git a/samples/quickperf/exampleconfigs/config.json b/samples/quickperf/exampleconfigs/config.json new file mode 100644 index 000000000..b43c32e1c --- /dev/null +++ b/samples/quickperf/exampleconfigs/config.json @@ -0,0 +1,9 @@ +{ + "project": "xxxx", + "instance": "xxx", + "database": "xxx", + "threads": 1, + "iterations": 100, + "query": "SELECT 1", + "writeMetricToFile": false +} \ No newline at end of file diff --git a/samples/quickperf/exampleconfigs/users/groupmgt_config.json b/samples/quickperf/exampleconfigs/users/groupmgt_config.json new file mode 100644 index 000000000..8bc094513 --- /dev/null +++ b/samples/quickperf/exampleconfigs/users/groupmgt_config.json @@ -0,0 +1,13 @@ +{ + "project": "xxx", + "instance": "xxx", + "database": "users", + "threads": 4, + "iterations": 250, + "query": "INSERT INTO GroupMgmt (group_id, grpname) VALUES(?,?)", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#i"}, + {"order": 2, "value": "#s"} + ] +} \ No newline at end of file diff --git a/samples/quickperf/exampleconfigs/users/loadtestusers.json b/samples/quickperf/exampleconfigs/users/loadtestusers.json new file mode 100644 index 000000000..4a4a3aee8 --- /dev/null +++ b/samples/quickperf/exampleconfigs/users/loadtestusers.json @@ -0,0 +1,13 @@ +{ + "project": "xxx", + "instance": "xxx", + "database": "users", + "threads": 1, + "iterations": 10, + "query": "SELECT users.user_id, membership.enrolled, GroupMgmt.grpname FROM users, GroupMgmt, membership WHERE users.user_id = ? AND users.user_id = membership.user_id AND GroupMgmt.group_id = membership.group_id", + "samplingQuery": "SELECT user_id FROM Users TABLESAMPLE RESERVOIR (100000 ROWS)", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#pi"} + ] +} \ No newline at end of file diff --git a/samples/quickperf/exampleconfigs/users/membership_config.json b/samples/quickperf/exampleconfigs/users/membership_config.json new file mode 100644 index 000000000..d3c56d101 --- /dev/null +++ b/samples/quickperf/exampleconfigs/users/membership_config.json @@ -0,0 +1,9 @@ +{ + "project": "xxx", + "instance": "xxx", + "database": "users", + "threads": 1, + "iterations": 100, + "query": "INSERT INTO membership(user_id, group_id, enrolled) VALUES((SELECT user_id FROM Users TABLESAMPLE RESERVOIR (1 ROWS)), (SELECT group_id FROM GroupMgmt TABLESAMPLE RESERVOIR (1 ROWS)), CURRENT_TIMESTAMP())", + "writeMetricToFile": false +} \ No newline at end of file diff --git a/samples/quickperf/exampleconfigs/users/run.sh b/samples/quickperf/exampleconfigs/users/run.sh new file mode 100755 index 000000000..ac82643f8 --- /dev/null +++ b/samples/quickperf/exampleconfigs/users/run.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Generate Data +cd ../.. + +mvn -q compile + +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/users_config.json" + +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/groupmgt_config.json" + +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/membership_config.json" + +# load test random users +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/loadtestusers.json" diff --git a/samples/quickperf/exampleconfigs/users/users.ddl b/samples/quickperf/exampleconfigs/users/users.ddl new file mode 100644 index 000000000..6498bb591 --- /dev/null +++ b/samples/quickperf/exampleconfigs/users/users.ddl @@ -0,0 +1,17 @@ +CREATE TABLE GroupMgmt ( + group_id INT64, + grpname STRING(MAX), +) PRIMARY KEY(group_id); + +CREATE TABLE Users ( + user_id INT64, + name STRING(MAX), +) PRIMARY KEY(user_id); + +CREATE TABLE membership ( + user_id INT64, + group_id INT64, + enrolled TIMESTAMP NOT NULL OPTIONS ( + allow_commit_timestamp = true + ), +) PRIMARY KEY(user_id, group_id); \ No newline at end of file diff --git a/samples/quickperf/exampleconfigs/users/users_config.json b/samples/quickperf/exampleconfigs/users/users_config.json new file mode 100644 index 000000000..6cdbbedc5 --- /dev/null +++ b/samples/quickperf/exampleconfigs/users/users_config.json @@ -0,0 +1,13 @@ +{ + "project": "xxx", + "instance": "xxx", + "database": "users", + "threads": 1, + "iterations": 1000, + "query": "INSERT INTO Users (user_id, name) VALUES(?,?)", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#i"}, + {"order": 2, "value": "#s"} + ] +} \ No newline at end of file diff --git a/samples/quickperf/java.header b/samples/quickperf/java.header new file mode 100644 index 000000000..d0970ba7d --- /dev/null +++ b/samples/quickperf/java.header @@ -0,0 +1,15 @@ +^/\*$ +^ \* Copyright \d\d\d\d,? Google (Inc\.|LLC)$ +^ \*$ +^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\);$ +^ \* you may not use this file except in compliance with the License\.$ +^ \* You may obtain a copy of the License at$ +^ \*$ +^ \*[ ]+https?://www.apache.org/licenses/LICENSE-2\.0$ +^ \*$ +^ \* Unless required by applicable law or agreed to in writing, software$ +^ \* distributed under the License is distributed on an "AS IS" BASIS,$ +^ \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.$ +^ \* See the License for the specific language governing permissions and$ +^ \* limitations under the License\.$ +^ \*/$ diff --git a/samples/quickperf/license-checks.xml b/samples/quickperf/license-checks.xml new file mode 100644 index 000000000..a7a611940 --- /dev/null +++ b/samples/quickperf/license-checks.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml new file mode 100644 index 000000000..181a7639f --- /dev/null +++ b/samples/quickperf/pom.xml @@ -0,0 +1,100 @@ + + + + 4.0.0 + + com.google.cloud.jdbc.quickperf + jdbc-quickperf + 1.0.0 + jdbc-quickperf + + com.google.cloud + sdk-platform-java-config + 3.34.0 + + + + + UTF-8 + 1.8 + 1.8 + + + + + + com.google.cloud + libraries-bom + 26.44.0 + pom + import + + + + + + + net.datafaker + datafaker + 2.3.1 + + + com.google.cloud + google-cloud-spanner + + + commons-cli + commons-cli + 1.9.0 + + + com.google.cloud + google-cloud-spanner-jdbc + + + org.apache.commons + commons-lang3 + 3.16.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.17.2 + + + + org.testcontainers + testcontainers + 1.20.1 + test + + + org.springframework.boot + spring-boot + 3.3.3 + test + + + junit + junit + 4.13.2 + test + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.4.1 + + com.google.cloud.jdbc.quickperf.QuickPerf + + + + + + \ No newline at end of file diff --git a/samples/quickperf/readme.md b/samples/quickperf/readme.md new file mode 100644 index 000000000..675155b94 --- /dev/null +++ b/samples/quickperf/readme.md @@ -0,0 +1,302 @@ +# Introduction + +QuickPerf is a simple utility that uses JDBC to perform load testing on individual statements (such as queries and DML) against Spanner. It provides a rapid assessment of expected end-to-end latencies for specific statements, aiding in the performance tuning of schemas, indexes, and more. The tool includes random data generators to quickly fill a given schema with dummy data, respecting foreign-key relationships and interleaved tables. + +QuickPerf is not designed to replace comprehensive test suites like JMeter. Instead, it serves as a quick alternative for gaining performance insights or populating schemas. + +**Key Features**: +* Multi-threading to simulate concurrency +* Query parameterization with random value generators (String, Integer, Timestamp) +* Sampling of records for seeding foreign-key relationships or testing against a specific subset of data +* Batch mode support +* Automatic statement and transaction tagging + + +# Installation on Ubuntu +``` +sudo apt-get install openjdk-8-jdk +sudo apt install maven +``` + +## Authentification +It is recommended to use a service account, otherwise larger scale tests will run into quota limitations + +Set active auth to service account: +``` +gcloud auth list +gcloud config set account xxx-compute@developer.gserviceaccount.com +``` + +# Configuration + +## Parameters +``` +{ + "project": "Project ID", + "instance": "Spanner Instance", + "database": "Spanner Database", + "threads": Number of concurrent threads, + "iterations": Number of how often a statement should be executed in a thread, + "query": "Statement (e.g. query)", + "samplingQuery": "OPTIONAL - Sampling query", + "writeMetricToFile": Will write latency metrics to a file (true/false), + "batchSize": If testing batching - determines how large a batch size would be, + "queryParams": [ + {"order": 1, "value": "query paramters with value generator"} + ] +} +``` + +## Example Config +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "users", + "threads": 1, + "iterations": 10, + "query": "SELECT users.user_id, membership.enrolled, GroupMgmt.grpname FROM users, GroupMgmt, membership WHERE users.user_id = ? AND users.user_id = membership.user_id AND GroupMgmt.group_id = membership.group_id", + "samplingQuery": "SELECT user_id FROM Users TABLESAMPLE RESERVOIR (100000 ROWS)", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#pi"} + ] +} +``` + +# Hello World Example + +The folder `exampleconfigs/config.json` contains a simple setup that runs SELECT 1 against the database + +Configure the right Spanner `project` and `instance` and run the app. + +**config.json** +``` +{ +"project": "xxxx", +"instance": "xxx", +"database": "xxx", +"threads": 1, +"iterations": 100, +"query": "SELECT 1", +"writeMetricToFile": false, +"batchSize": 0 +} +``` + +**Run:** +``` +mvn -q exec:java -Dexec.args="-c exampleconfigs/config.json" +``` + + + +# End-to-End Example + +Generates three tables with n:m relationships and performs a load test. + +All in one runner generating test data and executing load test: +``` +exampleconfigs/users/run.sh +``` + +What needs to be done to run it: +* Create spanner instance +* Create database named `users` +* Set `project` and `instance` in each of the config JSON files located under `exampleconfigs/users/users_config.json` + * `exampleconfigs/users/users_config.json` + * `exampleconfigs/users/groupmgt_config.json` + * `exampleconfigs/users/membership_config.json` + * `exampleconfigs/users/loadtestusers.json` + + +**Generate users table:** + +``` +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/users_config.json" +``` + +users_config.json: +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "users", + "threads": 4, + "iterations": 1000, + "query": "INSERT INTO Users (user_id, name) VALUES(?,?)", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#i"}, + {"order": 2, "value": "#s"} + ] +} +``` + +**Generate GroupMgmt table:** + +``` +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/groupmgt_config.json" +``` + +groupmgt_config.json +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "users", + "threads": 4, + "iterations": 1000, + "query": "INSERT INTO GroupMgmt (group_id, grpname) VALUES(?,?)", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#i"}, + {"order": 2, "value": "#s"} + ] +} +``` + +**Generate Membership table:** + +Run: +``` +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/membership_config.json" +``` + +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "users", + "threads": 1, + "iterations": 100, + "query": "INSERT INTO membership(user_id, group_id, enrolled) VALUES((SELECT user_id FROM Users TABLESAMPLE RESERVOIR (1 ROWS)), (SELECT group_id FROM GroupMgmt TABLESAMPLE RESERVOIR (1 ROWS)), CURRENT_TIMESTAMP())", + "writeMetricToFile": false +} +``` + + +Load test random users +``` +mvn -q exec:java -Dexec.args="-c exampleconfigs/users/loadtestusers.json" +``` + +# Randomization examples + +## String +Will generate a different random String value for each #s +``` +INSERT INTO transactions (id, name, ts) VALUES (#s, #s, CURRENT_TIMESTAMP()) +``` +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "xxxx", + "threads": 4, + "iterations": 1000, + "query": "INSERT INTO transactions (id, name, ts) VALUES (?, ?, CURRENT_TIMESTAMP())", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#s"}, + {"order": 2, "value": "#s"} + ] +} +``` + +``` +SELECT * FROM transactions WHERE id=#s +``` +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "xxxx", + "threads": 4, + "iterations": 1000, + "query": "SELECT * FROM transactions WHERE id=?", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#s"}, + ] +} +``` + + +## Integer +Will generate a different random value for each #i +``` +UPDATE accounts SET cid=#i WHERE aaId=4 +``` + +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "xxxx", + "threads": 4, + "iterations": 1000, + "query": "UPDATE accounts SET ? WHERE aaId=4", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#i"}, + ] +} +``` + +## Integer min max +Generates #i(1,10) integer values between 1 and 10 +``` +INSERT INTO test (id, groupid, amount) VALUES (#s, #i(1,2)#, #i) +``` + +## Timestamp +Workaround for not randomizing timestamps is to use current_timestsamp() +``` +INSERT INTO transactions (id, name, ts) VALUES (#s, #s, CURRENT_TIMESTAMP())' +``` +``` +{ + "project": "xxxx", + "instance": "xxxx", + "database": "xxxx", + "threads": 4, + "iterations": 1000, + "query": "INSERT INTO transactions (id, name, ts) VALUES (?, ?, CURRENT_TIMESTAMP())'", + "writeMetricToFile": false, + "queryParams": [ + {"order": 1, "value": "#s"}, + {"order": 1, "value": "#s"} + ] +} +``` + +## Sampling IDs +Sometimes it might be required to sample existing IDs that are then used in the query that is executed. +The parameter ```-s``` allows to pull a sampled dataset, but only one column is allowed in the resultset. +This query is executed only once and before the beginning of the run and the dataset is reused across the threads. + +* #ps will add quotes - such as for Strings +* #pi will **not** add quotes such as when integers are used + +In this case the #ps is the placeholder for samples that are pulled from the -s parameter +``` +-q 'SELECT * FROM test WHERE id = #ps'' +-s 'SELECT id FROM test TABLESAMPLE RESERVOIR (100 ROWS)' +``` + +## Many-to-Many Relationship Example +Insert Users +``` +INSERT INTO Users (user_id, name) VALUES(#i,#s) +``` + +Insert Groups +``` +INSERT INTO GroupMgmt (group_id, grpname) VALUES(#i,#s) +``` + +Insert relationships with sampling: +``` +INSERT INTO membership(user_id, group_id, enrolled) VALUES((SELECT user_id FROM Users TABLESAMPLE RESERVOIR (1 ROWS)), (SELECT group_id FROM GroupMgmt TABLESAMPLE RESERVOIR (1 ROWS)), CURRENT_TIMESTAMP()) +``` \ No newline at end of file diff --git a/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/ProgressTracker.java b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/ProgressTracker.java new file mode 100644 index 000000000..90b241b1d --- /dev/null +++ b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/ProgressTracker.java @@ -0,0 +1,79 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf; + +import java.util.List; + +public class ProgressTracker extends Thread { + private static final int SLEEP_TIME_INIT = 2000; + private static final int SLEEP_TIME_POLL = 200; + + private final List threadList; + + private final int maxIt; + private int currentIt = 0; + + public ProgressTracker(List threadList, int maxIt) { + this.threadList = threadList; + this.maxIt = maxIt; + } + + public void run() { + sleep(SLEEP_TIME_INIT); + while (currentIt < maxIt) { + currentIt = 0; + for (QuickPerfRunner thread : threadList) { + currentIt = currentIt + thread.getProgress(); + + int percent = (int) Math.ceil(((double) currentIt / maxIt) * 100.0); + print_progress(percent); + } + + if (sleep(SLEEP_TIME_POLL)) { + break; + } + } + print_progress(100); + } + + public void print_progress(int percent) { + StringBuilder bar = new StringBuilder("Progress: ["); + + for (int i = 0; i < 50; i++) { + if (i < (percent / 2)) { + bar.append("="); + } else if (i == (percent / 2)) { + bar.append(">"); + } else { + bar.append(" "); + } + } + + bar.append("] ").append(percent).append("% "); + System.out.print("\r" + bar); + } + + private boolean sleep(int sleeptime) { + try { + Thread.sleep(sleeptime); + } catch (InterruptedException e) { + System.err.println("Progress tracker thread interrupted"); + return true; + } + return false; + } +} diff --git a/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/QuickPerf.java b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/QuickPerf.java new file mode 100644 index 000000000..9eb0fc45c --- /dev/null +++ b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/QuickPerf.java @@ -0,0 +1,191 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf; + +import com.google.cloud.jdbc.quickperf.config.Config; +import com.google.cloud.jdbc.quickperf.config.ConfigParser; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.ArrayUtils; + +public class QuickPerf extends Thread { + + private static final String BREAK_STR = + "###################################################################################################"; + + // TODO: make measurement file configurable + private static final String MEASURES_FILE_NAME = "measures.txt"; + + public static void main(String[] args) throws Exception { + Options options = new Options(); + + options.addOption(QuickPerf.addOption("c", "config", true, "Config File")); + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + CommandLine cmd = null; + + ZonedDateTime testStartTimestamp = ZonedDateTime.now(); + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + formatter.printHelp("utility-name", options); + + System.exit(1); + } + + Config config = ConfigParser.parseConfigFile(cmd.getOptionValue("config")); + + float[] measures = new float[config.getIterations() * config.getThreads()]; + + // initialize threads (for sampling if present) + List threadList = new ArrayList(); + for (int i = 0; i < config.getThreads(); i++) { + QuickPerfRunner thread = new QuickPerfRunner(config); + if (config.getSamplingQuery() != null) { + thread.runSampling(); + } + threadList.add(thread); + } + + // start threads + for (QuickPerfRunner thread : threadList) { + thread.start(); + } + + // ProgressBar Tracker Thread + ProgressTracker progressTracker = + progressTracker = + new ProgressTracker(threadList, config.getIterations() * config.getThreads()); + + progressTracker.start(); + progressTracker.join(); + + int i = 0; + for (QuickPerfRunner thread : threadList) { + thread.join(); + + if (i == 0) { + measures = thread.getMeasures(); + } else { + measures = ArrayUtils.addAll(measures, thread.getMeasures()); + } + i++; + } + + // write to file before its sorted + if (config.getWriteMetricToFile()) { + try { + writeMeasuresToFile(measures, MEASURES_FILE_NAME); + } catch (IOException e) { + System.err.println("An error occurred while writing the file: " + e.getMessage()); + } + } + + System.out.println("\n" + BREAK_STR); + System.out.println("Query: " + config.getQuery()); + System.out.println("Params: " + config.paramsToString()); + System.out.println("Tag: " + Config.DEFAULT_TAG); + if (config.getBatchSize() > 0) { + System.out.println("Batching Enabled (size): " + config.getBatchSize()); + } + System.out.printf("Start: %s End: %s%n", testStartTimestamp, ZonedDateTime.now()); + System.out.printf( + "Finished with a total of %s runs across %s Threads.\nLatencies (ms): p50 = %s, p95 = %s, p99 = %s, min = %s, max = %s%n", + config.getIterations() * config.getThreads(), + config.getThreads(), + calcPerc(measures, 50), + calcPerc(measures, 95), + calcPerc(measures, 99), + getMin(measures), + getMax(measures)); + System.out.println(BREAK_STR); + } + + public static Option addOption(String option, String longOption, boolean hasArgs, String desc) { + Option opt = new Option(option, longOption, hasArgs, desc); + opt.setRequired(true); + + return opt; + } + + public static Option addOption( + String option, String longOption, boolean hasArgs, String desc, boolean required) { + Option opt = new Option(option, longOption, hasArgs, desc); + opt.setRequired(required); + + return opt; + } + + public static float calcPerc(float[] nums, double percentile) { + int n = nums.length; + Arrays.sort(nums); + + double index = (percentile / 100) * (n - 1); + + if (index == Math.floor(index)) { + return nums[(int) index]; + } else { + int lowerIndex = (int) Math.floor(index); + int upperIndex = (int) Math.ceil(index); + float lowerValue = nums[lowerIndex]; + float upperValue = nums[upperIndex]; + return (float) ((1 - (index - lowerIndex)) * lowerValue + (index - lowerIndex) * upperValue); + } + } + + public static float getMax(float[] measures) { + if (measures == null || measures.length == 0) { + throw new IllegalArgumentException("Array is null or empty"); + } + + Arrays.sort(measures); + return measures[measures.length - 1]; + } + + public static float getMin(float[] measures) { + if (measures == null || measures.length == 0) { + throw new IllegalArgumentException("Array is null or empty"); + } + + Arrays.sort(measures); + return measures[0]; + } + + public static void writeMeasuresToFile(float[] array, String fileName) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { + for (float value : array) { + writer.write(Float.toString(value)); + writer.newLine(); + } + } + } +} diff --git a/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/QuickPerfRunner.java b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/QuickPerfRunner.java new file mode 100644 index 000000000..84e2bc9ac --- /dev/null +++ b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/QuickPerfRunner.java @@ -0,0 +1,320 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.jdbc.quickperf; + +import com.google.cloud.jdbc.quickperf.config.Config; +import com.google.cloud.jdbc.quickperf.config.QueryParam; +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.jdbc.CloudSpannerJdbcConnection; +import java.security.SecureRandom; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.datafaker.Faker; + +public class QuickPerfRunner extends Thread { + private static final Properties DEFAULT_PROPERTIES = new Properties(); + + // perf measurement + private float[] measures; + + private final List sampledValueList = new ArrayList(); + private final Config config; + + private int progress; + + public QuickPerfRunner(Config config) { + this.config = config; + } + + public void runSampling() { + System.out.println("Running Sampling... "); + + try (Connection connection = createConnection(config)) { + try (Statement statement = connection.createStatement()) { + boolean hasResults = statement.execute(config.getSamplingQuery()); + + if (!hasResults) { + System.out.println("Nothing sampled"); + return; + } + + ResultSet rs = statement.getResultSet(); + while (rs.next()) { + String value = rs.getString(1); + sampledValueList.add(value); + } + + System.out.printf("Finished sampling %s records%n", sampledValueList.size()); + } catch (SQLException e) { + //noinspection CallToPrintStackTrace + e.printStackTrace(); + } + + } catch (SQLException e) { + //noinspection CallToPrintStackTrace + e.printStackTrace(); + } + } + + private Connection createConnection(Config config) throws SQLException { + String connectionUrl = createConnectionURL(config); + Properties properties = createConnectionProperties(); + return DriverManager.getConnection(connectionUrl, properties); + } + + private String createConnectionURL(Config config) { + if (config.isIsEmulator()) { + return String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?autoConfigEmulator=true", + config.getProject(), config.getInstance(), config.getDatabase()); + } else { + return String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s", + config.getProject(), config.getInstance(), config.getDatabase()); + } + } + + private Properties createConnectionProperties() { + if (System.getProperty("spanner.host") != null) { + Properties properties = new Properties(); + properties.setProperty("endpoint", System.getProperty("spanner.host")); + return properties; + } + return DEFAULT_PROPERTIES; + } + + public void run() { + if (config.getBatchSize() > 0) { + int val = (int) Math.ceil((double) config.getIterations() / config.getBatchSize()); + measures = new float[val]; + } else { + measures = new float[config.getIterations()]; + } + + try (Connection connection = createConnection(config)) { + + // determine database dialect to set right tagging syntax + boolean isGoogleSQL = + connection + .unwrap(CloudSpannerJdbcConnection.class) + .getDialect() + .equals(Dialect.GOOGLE_STANDARD_SQL); + String tagPrefix = isGoogleSQL ? "" : "SPANNER."; + + connection.setAutoCommit(false); + + // if there is DML switch to r/w transaction mode and apply transaction tagging. + // Otherwise set to read-only mode. + if (config.getQuery().contains("INSERT") + || config.getQuery().contains("UPDATE") + || config.getQuery().contains("DELETE")) { + // read-write + connection.createStatement().execute("SET TRANSACTION READ WRITE"); + connection + .createStatement() + .execute(String.format("SET %sTRANSACTION_TAG = '%s'", tagPrefix, config.DEFAULT_TAG)); + + } else { + // read-only + // connection.createStatement().execute("SET TRANSACTION READ ONLY"); + connection.setAutoCommit(true); + } + + PreparedStatement statement = connection.prepareStatement(config.getQuery()); + int batchCounter = config.getBatchSize(); + int batchRound = 0; + + for (int i = 0; i < config.getIterations(); i++) { + if (config.getBatchSize() == 0) { + // single statements + try { + if (config.getQueryParams() != null) { + parametrizeStatement(statement, config.getQueryParams()); + } + connection + .createStatement() + .execute(String.format("SET %sSTATEMENT_TAG='%s'", tagPrefix, config.DEFAULT_TAG)); + + long start = System.nanoTime(); + boolean hasResults = statement.execute(); + if (!connection.getAutoCommit()) { + connection.commit(); + } + long stop = System.nanoTime() - start; + + if (hasResults) { + statement.getResultSet().close(); + } + + measures[i] = (float) stop / 1000000; + progress++; + } catch (Exception e) { + if (e.getMessage().contains("ALREADY_EXISTS:")) { + System.out.println("duplicate key - retry"); + i--; + } else { + throw e; + } + } + } else if (config.getQuery().contains("INSERT") + || config.getQuery().contains("UPDATE") + || config.getQuery().contains("DELETE")) { + // batching + try { + if (config.getQueryParams() != null) { + parametrizeStatement(statement, config.getQueryParams()); + } + + statement.addBatch(); + + if (batchCounter == 0 || i == config.getIterations() - 1) { + connection + .createStatement() + .execute( + String.format("SET %sSTATEMENT_TAG='%s'", tagPrefix, config.DEFAULT_TAG)); + + long start = System.nanoTime(); + statement.executeBatch(); + if (!connection.getAutoCommit()) { + connection.commit(); + } + long stop = System.nanoTime() - start; + + batchCounter = config.getBatchSize(); + + measures[batchRound] = (float) stop / 1000000; + batchRound++; + } + + progress++; + batchCounter--; + } catch (Exception e) { + if (e.getMessage().contains("ALREADY_EXISTS:")) { + System.out.println("duplicate key - retry"); + i--; + } else { + throw e; + } + } + } else { + System.out.println( + "Batching is only allowed for DML. Set batchSize=0 to disable batching."); + System.exit(1); + } + } + } catch (SQLException e) { + //noinspection CallToPrintStackTrace + e.printStackTrace(); + } + } + + public static float[] appendFloatArray(float[] originalArray, float[] elementsToAppend) { + int originalLength = originalArray.length; + int elementsLength = elementsToAppend.length; + + float[] resultArray = new float[originalLength + elementsLength]; + System.arraycopy(originalArray, 0, resultArray, 0, originalLength); + System.arraycopy(elementsToAppend, 0, resultArray, originalLength, elementsLength); + + return resultArray; + } + + private void parametrizeStatement(PreparedStatement statement, List paramList) + throws SQLException { + for (QueryParam param : paramList) { + if (param.getValue().contains("#i")) { + // integer plus integer with custom range + int value = replaceInt(param.getValue()); + statement.setInt(param.getOrder(), value); + } else if (param.getValue().contains("#d")) { + // double + Double value = replaceDouble(param.getValue()); + statement.setDouble(param.getOrder(), value); + } else if (param.getValue().contains("#s")) { + // String + String value = replaceString(param.getValue()); + statement.setString(param.getOrder(), value); + } else if (param.getValue().contains("#ps")) { + // Sampled Query - String + String value = replaceSampleQueryString(param.getValue()); + statement.setString(param.getOrder(), value); + } else if (param.getValue().contains("#pi")) { + // Sampled Query - Integer + Long value = replaceSampleQueryInt(param.getValue()); + statement.setLong(param.getOrder(), value); + } + } + } + + private int replaceInt(String value) { + Faker f = new Faker(); + // integer with min, max + String pattern = "#i\\((\\d+),(\\d+)\\)#"; + Pattern regexPattern = Pattern.compile(pattern); + Matcher matcher = regexPattern.matcher(value); + + if (matcher.find()) { + int min = Integer.parseInt(matcher.group(1)); + int max = Integer.parseInt(matcher.group(2)); + + return f.number().numberBetween(min, max); + } + + return Integer.parseInt( + value.replaceFirst("#i", String.valueOf(new SecureRandom().nextInt(Integer.MAX_VALUE)))); + } + + private Double replaceDouble(String value) { + Faker f = new Faker(); + + return Double.valueOf( + value.replaceFirst("#d", String.valueOf(f.number().randomDouble(2, 0, 999999999)))); + } + + private String replaceString(String value) { + return value.replaceFirst("#s", UUID.randomUUID().toString()); + } + + private String replaceSampleQueryString(String value) { + int randomIndex = new Random().nextInt(sampledValueList.size()); + return value.replaceFirst("#ps", sampledValueList.get(randomIndex)); + } + + private Long replaceSampleQueryInt(String value) { + int randomIndex = new Random().nextInt(sampledValueList.size()); + + return Long.parseLong(value.replaceFirst("#pi", sampledValueList.get(randomIndex))); + } + + public int getProgress() { + return progress; + } + + public float[] getMeasures() { + return measures; + } +} diff --git a/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/Config.java b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/Config.java new file mode 100644 index 000000000..b869be386 --- /dev/null +++ b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/Config.java @@ -0,0 +1,145 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf.config; + +import java.util.List; +import java.util.Random; + +public class Config { + public static String DEFAULT_TAG = "perftest_" + (new Random()).nextInt(300); + + private String project; + private String instance; + private String database; + private int threads; + private int iterations; + private String query; + private String samplingQuery; + private boolean writeMetricToFile; + private int batchSize; + private boolean isEmulator; + private List queryParams; + + public String paramsToString() { + StringBuilder retVal = new StringBuilder(); + + if (queryParams != null) { + + for (QueryParam param : queryParams) { + retVal.append(String.format("%s:%s ", param.getOrder(), param.getValue())); + } + } + + return retVal.toString(); + } + + public int getBatchSize() { + return this.batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + public String getProject() { + return project; + } + + public void setProject(String project) { + this.project = project; + } + + public String getInstance() { + return instance; + } + + public void setInstance(String instance) { + this.instance = instance; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public int getThreads() { + return threads; + } + + public void setThreads(int threads) { + this.threads = threads; + } + + public int getIterations() { + return iterations; + } + + public void setIterations(int iterations) { + this.iterations = iterations; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public boolean isWriteMetricToFile() { + return writeMetricToFile; + } + + public void setWriteMetricToFile(boolean writeMetricToFile) { + this.writeMetricToFile = writeMetricToFile; + } + + public List getQueryParams() { + return queryParams; + } + + public void setQueryParams(List queryParams) { + this.queryParams = queryParams; + } + + public String getSamplingQuery() { + return this.samplingQuery; + } + + public void setSamplingQuery(String samplingQuery) { + this.samplingQuery = samplingQuery; + } + + public boolean getWriteMetricToFile() { + return this.writeMetricToFile; + } + + public boolean isIsEmulator() { + return this.isEmulator; + } + + public boolean getIsEmulator() { + return this.isEmulator; + } + + public void setIsEmulator(boolean isEmulator) { + this.isEmulator = isEmulator; + } +} diff --git a/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/ConfigParser.java b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/ConfigParser.java new file mode 100644 index 000000000..8f056d114 --- /dev/null +++ b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/ConfigParser.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.IOException; + +public class ConfigParser { + + public static Config parseConfigFile(String configFile) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + + return mapper.readValue(new File(configFile), Config.class); + } +} diff --git a/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/QueryParam.java b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/QueryParam.java new file mode 100644 index 000000000..52807eb49 --- /dev/null +++ b/samples/quickperf/src/main/java/com/google/cloud/jdbc/quickperf/config/QueryParam.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf.config; + +public class QueryParam { + private int order; + private String value; + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/AppTest.java b/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/AppTest.java new file mode 100644 index 000000000..c147ebaa9 --- /dev/null +++ b/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/AppTest.java @@ -0,0 +1,174 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.jdbc.quickperf; + +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.google.cloud.spanner.connection.SpannerPool; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import javax.annotation.Nonnull; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +public class AppTest { + + private static final String TEST_FILE = "src/test/resources/testfile.json"; + + private static GenericContainer emulator; + + private static final String projectId = "test"; + private static final String instanceId = "test"; + private static final String databaseId = "quickperftest"; + + @BeforeClass + public static void setup() throws Exception { + System.out.println("Starting Emulator"); + emulator = + new GenericContainer<>( + DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator:latest")) + .withExposedPorts(9010) + .waitingFor(Wait.forListeningPort()); + + emulator.start(); + System.out.println("Finished starting Emulator"); + + List ddlList = + Arrays.asList( + "CREATE TABLE GroupMgmt (" + + "group_id INT64," + + "grpname STRING(MAX)," + + ") PRIMARY KEY(group_id)", + "CREATE TABLE Users (" + + "user_id INT64," + + "name STRING(MAX)," + + ") PRIMARY KEY(user_id)", + "CREATE TABLE membership (" + + "user_id INT64," + + "group_id INT64," + + "enrolled TIMESTAMP NOT NULL OPTIONS (" + + " allow_commit_timestamp = true" + + ")," + + ") PRIMARY KEY(user_id, group_id)"); + try (Connection connection = createConnection(); + Statement statement = connection.createStatement()) { + for (String ddl : ddlList) { + statement.addBatch(ddl); + } + statement.executeBatch(); + } + // create test file + ProjectConfig projectConfig = createProjectConfig(); + + // Write the JSON to a file + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + File file = new File(TEST_FILE); + mapper.writeValue(file, projectConfig); + } + + @Nonnull + private static ProjectConfig createProjectConfig() { + ProjectConfig projectConfig = new ProjectConfig(); + projectConfig.setProject(projectId); + projectConfig.setInstance(instanceId); + projectConfig.setDatabase(databaseId); + projectConfig.setThreads(1); + projectConfig.setIterations(1000); + projectConfig.setQuery("INSERT INTO Users (user_id, name) VALUES(?,?)"); + projectConfig.setWriteMetricToFile(false); + projectConfig.setIsEmulator(true); + + QueryParam param1 = new QueryParam(1, "#i"); + QueryParam param2 = new QueryParam(2, "#s"); + projectConfig.setQueryParams(Arrays.asList(param1, param2)); + return projectConfig; + } + + @AfterClass + public static void cleanup() throws IOException { + // Close all Spanner connections. + SpannerPool.closeSpannerPool(); + + // Write an empty test file + Path path = Paths.get(TEST_FILE); + Files.newBufferedWriter(path, TRUNCATE_EXISTING).close(); + + // Stop the emulator. + emulator.stop(); + } + + private static Connection createConnection() throws SQLException { + String url = + String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?autoConfigEmulator=true", + projectId, instanceId, databaseId); + Properties properties = new Properties(); + properties.put("endpoint", "localhost:" + emulator.getMappedPort(9010)); + return DriverManager.getConnection(url, properties); + } + + @Test + public void testRunApplication() throws Exception { + + System.setProperty("spanner.emulator", "true"); + System.setProperty("spanner.host", "localhost:" + emulator.getMappedPort(9010)); + SpringApplication.run(AppTest.class).close(); + + String[] userConfig = {"-c" + TEST_FILE}; + QuickPerf.main(userConfig); + + try (Connection connection = createConnection()) { + testQuery(connection, "SELECT count(*) FROM Users", 1000); + } + } + + private void testQuery(Connection connection, String query, int expected) throws SQLException { + try (Statement statement = connection.createStatement()) { + boolean hasResults = statement.execute(query); + assertTrue(hasResults); + + ResultSet rs = statement.getResultSet(); + while (rs.next()) { + int value = rs.getInt(1); + assertEquals(expected, value); + } + } + } +} diff --git a/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/ProjectConfig.java b/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/ProjectConfig.java new file mode 100644 index 000000000..372d100fd --- /dev/null +++ b/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/ProjectConfig.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf; + +import java.util.List; + +public class ProjectConfig { + private String project; + private String instance; + private String database; + private int threads; + private int iterations; + private String query; + private boolean writeMetricToFile; + private boolean isEmulator; + private List queryParams; + + // Getters and setters + + public String getProject() { + return project; + } + + public void setProject(String project) { + this.project = project; + } + + public String getInstance() { + return instance; + } + + public void setInstance(String instance) { + this.instance = instance; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public int getThreads() { + return threads; + } + + public void setThreads(int threads) { + this.threads = threads; + } + + public int getIterations() { + return iterations; + } + + public void setIterations(int iterations) { + this.iterations = iterations; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public boolean isWriteMetricToFile() { + return writeMetricToFile; + } + + public void setWriteMetricToFile(boolean writeMetricToFile) { + this.writeMetricToFile = writeMetricToFile; + } + + public List getQueryParams() { + return queryParams; + } + + public void setQueryParams(List queryParams) { + this.queryParams = queryParams; + } + + public boolean getWriteMetricToFile() { + return this.writeMetricToFile; + } + + public boolean isIsEmulator() { + return this.isEmulator; + } + + public boolean getIsEmulator() { + return this.isEmulator; + } + + public void setIsEmulator(boolean isEmulator) { + this.isEmulator = isEmulator; + } +} diff --git a/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/QueryParam.java b/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/QueryParam.java new file mode 100644 index 000000000..dcc0d85d4 --- /dev/null +++ b/samples/quickperf/src/test/java/com/google/cloud/jdbc/quickperf/QueryParam.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.jdbc.quickperf; + +public class QueryParam { + private int order; + private String value; + + public QueryParam(int order, String value) { + this.order = order; + this.value = value; + } + + // Getters and setters + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/samples/quickperf/src/test/resources/testfile.json b/samples/quickperf/src/test/resources/testfile.json new file mode 100644 index 000000000..e69de29bb diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index ef21adcf1..751a517e9 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.20.2 + 2.21.0 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index ee6deda8b..2d0ed2811 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -9,7 +9,7 @@ com.google.cloud sdk-platform-java-config - 3.33.0 + 3.34.0 @@ -26,7 +26,7 @@ com.google.cloud libraries-bom - 26.43.0 + 26.44.0 pom import @@ -48,7 +48,7 @@ org.testcontainers testcontainers - 1.19.8 + 1.20.1 test diff --git a/samples/spring-data-jdbc/pom.xml b/samples/spring-data-jdbc/pom.xml index f2d20d8ac..cf4c573b7 100644 --- a/samples/spring-data-jdbc/pom.xml +++ b/samples/spring-data-jdbc/pom.xml @@ -23,28 +23,28 @@ org.springframework.data spring-data-bom - 2024.0.2 + 2024.0.3 import pom com.google.cloud google-cloud-spanner-bom - 6.71.0 + 6.73.0 import pom com.google.cloud libraries-bom - 26.43.0 + 26.44.0 import pom io.opentelemetry opentelemetry-bom - 1.40.0 + 1.41.0 pom import @@ -55,19 +55,19 @@ org.springframework.boot spring-boot-starter-data-jdbc - 3.3.2 + 3.3.3 com.google.cloud google-cloud-spanner-jdbc - 2.20.1 + 2.20.2 org.postgresql postgresql - 42.7.3 + 42.7.4 @@ -109,7 +109,7 @@ org.testcontainers testcontainers - 1.19.8 + 1.20.1 test @@ -119,7 +119,7 @@ com.spotify.fmt fmt-maven-plugin - 2.23 + 2.24 diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index a5ad7633d..8a13c6bb8 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.2 + 3.3.3 @@ -28,14 +28,14 @@ org.springframework.data spring-data-bom - 2024.0.2 + 2024.0.3 import pom com.google.cloud libraries-bom - 26.43.0 + 26.44.0 import pom @@ -62,7 +62,7 @@ org.postgresql postgresql - 42.7.3 + 42.7.4 @@ -97,7 +97,7 @@ com.spotify.fmt fmt-maven-plugin - 2.23 + 2.24 diff --git a/src/test/java/com/google/cloud/spanner/jdbc/ConcurrentTransactionOnEmulatorTest.java b/src/test/java/com/google/cloud/spanner/jdbc/ConcurrentTransactionOnEmulatorTest.java index 887412048..74a005038 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/ConcurrentTransactionOnEmulatorTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/ConcurrentTransactionOnEmulatorTest.java @@ -45,7 +45,13 @@ public class ConcurrentTransactionOnEmulatorTest { @BeforeClass public static void startEmulator() { - assumeTrue(DockerClientFactory.instance().isDockerAvailable()); + boolean dockerAvailable = false; + try { + dockerAvailable = DockerClientFactory.instance().isDockerAvailable(); + } catch (Exception ignore) { + // Ignore, and just skip the test. + } + assumeTrue(dockerAvailable); emulator = new GenericContainer<>( diff --git a/src/test/java/com/google/cloud/spanner/jdbc/SingleJarTestApplication.java b/src/test/java/com/google/cloud/spanner/jdbc/SingleJarTestApplication.java new file mode 100644 index 000000000..e9cfba1b2 --- /dev/null +++ b/src/test/java/com/google/cloud/spanner/jdbc/SingleJarTestApplication.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; + +/** + * Simple Java application that is used to verify the working of the single-jar-with-dependencies. + */ +public class SingleJarTestApplication { + + public static void main(String[] args) throws Exception { + if (args.length != 3) { + throw new IllegalArgumentException("expected 3 arguments"); + } + String project = args[0]; + String instance = args[1]; + String database = args[2]; + String extraOptions = ""; + String host = ""; + if (System.getenv("SPANNER_EMULATOR_HOST") != null) { + extraOptions = "?autoConfigEmulator=true"; + host = "//" + System.getenv("SPANNER_EMULATOR_HOST"); + } + + try (Connection connection = + DriverManager.getConnection( + String.format( + "jdbc:cloudspanner:%s/projects/%s/instances/%s/databases/%s%s", + host, project, instance, database, extraOptions))) { + try (ResultSet resultSet = + connection.createStatement().executeQuery("select 'Hello World from Real Spanner!'")) { + while (resultSet.next()) { + System.out.println(resultSet.getString(1)); + } + } + } + } +} diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java index 903df91db..efc3934d3 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcDatabaseMetaDataTest.java @@ -21,9 +21,13 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.testing.EmulatorSpannerHelper; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -31,7 +35,8 @@ import java.sql.Types; import java.util.Arrays; import java.util.List; -import org.junit.Before; +import java.util.stream.Collectors; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -53,11 +58,46 @@ public class ITJdbcDatabaseMetaDataTest extends ITAbstractJdbcTest { @ClassRule public static JdbcIntegrationTestEnv env = new JdbcIntegrationTestEnv(); - private Database database; - - @Before - public void setup() { - database = env.getOrCreateDatabase(getDialect(), getMusicTablesDdl(getDialect())); + private static Database database; + + @BeforeClass + public static void setup() throws Exception { + assumeFalse( + "Named schemas are not yet supported on the emulator", + EmulatorSpannerHelper.isUsingEmulator()); + + database = + env.getOrCreateDatabase( + Dialect.GOOGLE_STANDARD_SQL, getMusicTablesDdl(Dialect.GOOGLE_STANDARD_SQL)); + + // Create the same tables in an additional 'test' schema. + DatabaseAdminClient client = env.getTestHelper().getClient().getDatabaseAdminClient(); + List tables = + getMusicTablesDdl(Dialect.GOOGLE_STANDARD_SQL).stream() + .map(statement -> statement.replace("CREATE TABLE ", "CREATE TABLE test.")) + .map(statement -> statement.replace("CREATE INDEX ", "CREATE INDEX test.")) + .map( + statement -> statement.replace("CREATE UNIQUE INDEX ", "CREATE UNIQUE INDEX test.")) + .map(statement -> statement.replace("CREATE VIEW ", "CREATE VIEW test.")) + .map(statement -> statement.replace("FROM ", "FROM test.")) + .map( + statement -> + statement.replace("INTERLEAVE IN PARENT ", "INTERLEAVE IN PARENT test.")) + .map(statement -> statement.replace("INTERLEAVE IN ", "INTERLEAVE IN test.")) + .map( + statement -> statement.replace("INTERLEAVE IN test.PARENT", "INTERLEAVE IN PARENT")) + .map(statement -> statement.replace(" ON ", " ON test.")) + .map(statement -> statement.replace(" ON test.DELETE", " ON DELETE")) + .map(statement -> statement.replace(" REFERENCES ", " REFERENCES test.")) + .collect(Collectors.toList()); + tables.add(0, "create schema test"); + client + .updateDatabaseDdl( + database.getId().getInstanceId().getInstance(), + database.getId().getDatabase(), + tables, + null) + .get(); } private static final class Column { @@ -165,65 +205,65 @@ private Column( @Test public void testGetColumns() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getColumns(DEFAULT_CATALOG, DEFAULT_SCHEMA, TABLE_WITH_ALL_COLS, null)) { - int pos = 1; - for (Column col : EXPECTED_COLUMNS) { - assertTrue(rs.next()); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(TABLE_WITH_ALL_COLS, rs.getString("TABLE_NAME")); - assertEquals(col.name, rs.getString("COLUMN_NAME")); - assertEquals(col.type, rs.getInt("DATA_TYPE")); - assertEquals(col.typeName, rs.getString("TYPE_NAME")); - if (col.colSize == null) { - assertEquals(0, rs.getInt("COLUMN_SIZE")); - assertTrue(rs.wasNull()); - } else { - assertEquals(col.colSize.intValue(), rs.getInt("COLUMN_SIZE")); - } - rs.getObject("BUFFER_LENGTH"); // just assert that it exists - if (col.decimalDigits == null) { - assertEquals(0, rs.getInt("DECIMAL_DIGITS")); - assertTrue(rs.wasNull()); - } else { - assertEquals(col.decimalDigits.intValue(), rs.getInt("DECIMAL_DIGITS")); - } - if (col.radix == null) { - assertEquals(0, rs.getInt("NUM_PREC_RADIX")); - assertTrue(rs.wasNull()); - } else { - assertEquals(col.radix.intValue(), rs.getInt("NUM_PREC_RADIX")); - } - assertEquals( - col.nullable ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, - rs.getInt("NULLABLE")); - assertNull(rs.getString("REMARKS")); - assertNull(rs.getString("COLUMN_DEF")); - assertEquals(0, rs.getInt("SQL_DATA_TYPE")); - assertEquals(0, rs.getInt("SQL_DATETIME_SUB")); - if (col.charOctetLength == null) { - assertEquals(0, rs.getInt("CHAR_OCTET_LENGTH")); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getColumns(DEFAULT_CATALOG, schema, TABLE_WITH_ALL_COLS, null)) { + int ordinalPosition = 0; + for (Column col : EXPECTED_COLUMNS) { + assertTrue(rs.next()); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(TABLE_WITH_ALL_COLS, rs.getString("TABLE_NAME")); + assertEquals(col.name, rs.getString("COLUMN_NAME")); + assertEquals(col.type, rs.getInt("DATA_TYPE")); + assertEquals(col.typeName, rs.getString("TYPE_NAME")); + if (col.colSize == null) { + assertEquals(0, rs.getInt("COLUMN_SIZE")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.colSize.intValue(), rs.getInt("COLUMN_SIZE")); + } + rs.getObject("BUFFER_LENGTH"); // just assert that it exists + if (col.decimalDigits == null) { + assertEquals(0, rs.getInt("DECIMAL_DIGITS")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.decimalDigits.intValue(), rs.getInt("DECIMAL_DIGITS")); + } + if (col.radix == null) { + assertEquals(0, rs.getInt("NUM_PREC_RADIX")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.radix.intValue(), rs.getInt("NUM_PREC_RADIX")); + } + assertEquals( + col.nullable ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, + rs.getInt("NULLABLE")); + assertNull(rs.getString("REMARKS")); + assertNull(rs.getString("COLUMN_DEF")); + assertEquals(0, rs.getInt("SQL_DATA_TYPE")); + assertEquals(0, rs.getInt("SQL_DATETIME_SUB")); + if (col.charOctetLength == null) { + assertEquals(0, rs.getInt("CHAR_OCTET_LENGTH")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.charOctetLength.intValue(), rs.getInt("CHAR_OCTET_LENGTH")); + } + assertEquals(++ordinalPosition, rs.getInt("ORDINAL_POSITION")); + assertEquals(col.nullable ? "YES" : "NO", rs.getString("IS_NULLABLE")); + assertNull(rs.getString("SCOPE_CATALOG")); + assertNull(rs.getString("SCOPE_SCHEMA")); + assertNull(rs.getString("SCOPE_TABLE")); + assertEquals(0, rs.getShort("SOURCE_DATA_TYPE")); assertTrue(rs.wasNull()); - } else { - assertEquals(col.charOctetLength.intValue(), rs.getInt("CHAR_OCTET_LENGTH")); + assertEquals("NO", rs.getString("IS_AUTOINCREMENT")); + assertEquals(col.computed ? "YES" : "NO", rs.getString("IS_GENERATEDCOLUMN")); + assertEquals(24, rs.getMetaData().getColumnCount()); } - assertEquals(pos, rs.getInt("ORDINAL_POSITION")); - assertEquals(col.nullable ? "YES" : "NO", rs.getString("IS_NULLABLE")); - assertNull(rs.getString("SCOPE_CATALOG")); - assertNull(rs.getString("SCOPE_SCHEMA")); - assertNull(rs.getString("SCOPE_TABLE")); - assertEquals(0, rs.getShort("SOURCE_DATA_TYPE")); - assertTrue(rs.wasNull()); - assertEquals("NO", rs.getString("IS_AUTOINCREMENT")); - assertEquals(col.computed ? "YES" : "NO", rs.getString("IS_GENERATEDCOLUMN")); - assertEquals(24, rs.getMetaData().getColumnCount()); - - pos++; + assertFalse(rs.next()); } - assertFalse(rs.next()); } } } @@ -231,181 +271,173 @@ public void testGetColumns() throws SQLException { @Test public void testGetCrossReferences() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - SINGERS_TABLE, - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - ALBUMS_TABLE)) { - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals("Singers", rs.getString("PKTABLE_NAME")); - assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals("Albums", rs.getString("FKTABLE_NAME")); - assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); - assertEquals(1, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyCascade, rs.getShort("DELETE_RULE")); - assertNull(rs.getString("FK_NAME")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - } - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - ALBUMS_TABLE, - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - SONGS_TABLE)) { - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals("Albums", rs.getString("PKTABLE_NAME")); - assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals("Songs", rs.getString("FKTABLE_NAME")); - assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); - assertEquals(1, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyCascade, rs.getShort("DELETE_RULE")); - assertNull(rs.getString("FK_NAME")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals("Albums", rs.getString("PKTABLE_NAME")); - assertEquals("AlbumId", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals("Songs", rs.getString("FKTABLE_NAME")); - assertEquals("AlbumId", rs.getString("FKCOLUMN_NAME")); - assertEquals(2, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyCascade, rs.getShort("DELETE_RULE")); - assertNull(rs.getString("FK_NAME")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - } + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + DEFAULT_CATALOG, + schema, + SINGERS_TABLE, + DEFAULT_CATALOG, + schema, + ALBUMS_TABLE)) { + assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("Singers", rs.getString("PKTABLE_NAME")); + assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("Albums", rs.getString("FKTABLE_NAME")); + assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); + assertEquals(1, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyCascade, rs.getShort("DELETE_RULE")); + assertNull(rs.getString("FK_NAME")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + } + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + DEFAULT_CATALOG, schema, ALBUMS_TABLE, DEFAULT_CATALOG, schema, SONGS_TABLE)) { + assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("Albums", rs.getString("PKTABLE_NAME")); + assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("Songs", rs.getString("FKTABLE_NAME")); + assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); + assertEquals(1, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyCascade, rs.getShort("DELETE_RULE")); + assertNull(rs.getString("FK_NAME")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - SINGERS_TABLE, - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - CONCERTS_TABLE)) { - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals("Singers", rs.getString("PKTABLE_NAME")); - assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals("Concerts", rs.getString("FKTABLE_NAME")); - assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); - assertEquals(1, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertEquals("Fk_Concerts_Singer", rs.getString("FK_NAME")); - assertEquals("PK_Singers", rs.getString("PK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - assertFalse(rs.next()); - } + assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("Albums", rs.getString("PKTABLE_NAME")); + assertEquals("AlbumId", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("Songs", rs.getString("FKTABLE_NAME")); + assertEquals("AlbumId", rs.getString("FKCOLUMN_NAME")); + assertEquals(2, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyCascade, rs.getShort("DELETE_RULE")); + assertNull(rs.getString("FK_NAME")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + } - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - TABLE_WITH_ALL_COLS, - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - TABLE_WITH_REF)) { + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + DEFAULT_CATALOG, + schema, + SINGERS_TABLE, + DEFAULT_CATALOG, + schema, + CONCERTS_TABLE)) { + assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("Singers", rs.getString("PKTABLE_NAME")); + assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("Concerts", rs.getString("FKTABLE_NAME")); + assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); + assertEquals(1, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertEquals("Fk_Concerts_Singer", rs.getString("FK_NAME")); + assertEquals("PK_Singers", rs.getString("PK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + assertFalse(rs.next()); + } - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); - assertEquals("ColFloat64", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); - assertEquals("RefFloat", rs.getString("FKCOLUMN_NAME")); - assertEquals(1, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertEquals("Fk_TableWithRef_TableWithAllColumnTypes", rs.getString("FK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + DEFAULT_CATALOG, + schema, + TABLE_WITH_ALL_COLS, + DEFAULT_CATALOG, + schema, + TABLE_WITH_REF)) { - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); - assertEquals("ColString", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); - assertEquals("RefString", rs.getString("FKCOLUMN_NAME")); - assertEquals(2, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertEquals("Fk_TableWithRef_TableWithAllColumnTypes", rs.getString("FK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); + assertEquals("ColFloat64", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); + assertEquals("RefFloat", rs.getString("FKCOLUMN_NAME")); + assertEquals(1, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertEquals("Fk_TableWithRef_TableWithAllColumnTypes", rs.getString("FK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - assertTrue(rs.next()); - assertEquals("", rs.getString("PKTABLE_CAT")); - assertEquals("", rs.getString("PKTABLE_SCHEM")); - assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); - assertEquals("ColDate", rs.getString("PKCOLUMN_NAME")); - assertEquals("", rs.getString("FKTABLE_CAT")); - assertEquals("", rs.getString("FKTABLE_SCHEM")); - assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); - assertEquals("RefDate", rs.getString("FKCOLUMN_NAME")); - assertEquals(3, rs.getShort("KEY_SEQ")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertEquals("Fk_TableWithRef_TableWithAllColumnTypes", rs.getString("FK_NAME")); - assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); + assertEquals("ColString", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); + assertEquals("RefString", rs.getString("FKCOLUMN_NAME")); + assertEquals(2, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertEquals("Fk_TableWithRef_TableWithAllColumnTypes", rs.getString("FK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - assertFalse(rs.next()); - } - // try getting self-references - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - ALBUMS_TABLE, - DEFAULT_CATALOG, - DEFAULT_SCHEMA, - ALBUMS_TABLE)) { - assertFalse(rs.next()); - } - // try getting all cross-references in the database - try (ResultSet rs = - connection.getMetaData().getCrossReference(null, null, null, null, null, null)) { - for (int i = 0; i < 7; i++) { assertTrue(rs.next()); + assertEquals("", rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); + assertEquals("ColDate", rs.getString("PKCOLUMN_NAME")); + assertEquals("", rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); + assertEquals("RefDate", rs.getString("FKCOLUMN_NAME")); + assertEquals(3, rs.getShort("KEY_SEQ")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertEquals("Fk_TableWithRef_TableWithAllColumnTypes", rs.getString("FK_NAME")); + assertEquals(DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + + assertFalse(rs.next()); + } + // try getting self-references + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + DEFAULT_CATALOG, schema, ALBUMS_TABLE, DEFAULT_CATALOG, schema, ALBUMS_TABLE)) { + assertFalse(rs.next()); + } + // try getting all cross-references in the database + try (ResultSet rs = + connection.getMetaData().getCrossReference(null, null, null, null, null, null)) { + for (int i = 0; i < 14; i++) { + assertTrue(rs.next()); + } + assertFalse(rs.next()); } - assertFalse(rs.next()); } } } @@ -480,39 +512,39 @@ private IndexInfo( @Test public void testGetIndexInfo() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getIndexInfo(DEFAULT_CATALOG, DEFAULT_SCHEMA, null, false, false)) { - - for (IndexInfo index : EXPECTED_INDICES) { - assertTrue(rs.next()); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(index.tableName, rs.getString("TABLE_NAME")); - assertEquals(index.nonUnique, rs.getBoolean("NON_UNIQUE")); - assertEquals(DEFAULT_CATALOG, rs.getString("INDEX_QUALIFIER")); - // Foreign key index names are automatically generated. - if (!"FOREIGN_KEY".equals(index.indexName) && !"GENERATED".equals(index.indexName)) { - assertEquals(index.indexName, rs.getString("INDEX_NAME")); - } - if (index.indexName.equals("PRIMARY_KEY")) { - assertEquals(DatabaseMetaData.tableIndexClustered, rs.getShort("TYPE")); - } else { - assertEquals(DatabaseMetaData.tableIndexHashed, rs.getShort("TYPE")); - } - assertEquals(index.ordinalPosition, rs.getShort("ORDINAL_POSITION")); - if (index.ordinalPosition == 0) { - assertTrue(rs.wasNull()); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection.getMetaData().getIndexInfo(DEFAULT_CATALOG, schema, null, false, false)) { + + for (IndexInfo index : EXPECTED_INDICES) { + assertTrue(rs.next()); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(index.tableName, rs.getString("TABLE_NAME")); + assertEquals(index.nonUnique, rs.getBoolean("NON_UNIQUE")); + assertEquals(DEFAULT_CATALOG, rs.getString("INDEX_QUALIFIER")); + // Foreign key index names are automatically generated. + if (!"FOREIGN_KEY".equals(index.indexName) && !"GENERATED".equals(index.indexName)) { + assertEquals(index.indexName, rs.getString("INDEX_NAME")); + } + if (index.indexName.equals("PRIMARY_KEY")) { + assertEquals(DatabaseMetaData.tableIndexClustered, rs.getShort("TYPE")); + } else { + assertEquals(DatabaseMetaData.tableIndexHashed, rs.getShort("TYPE")); + } + assertEquals(index.ordinalPosition, rs.getShort("ORDINAL_POSITION")); + if (index.ordinalPosition == 0) { + assertTrue(rs.wasNull()); + } + assertEquals(index.columnName, rs.getString("COLUMN_NAME")); + assertEquals(index.ascDesc, rs.getString("ASC_OR_DESC")); + assertEquals(-1, rs.getInt("CARDINALITY")); + assertEquals(-1, rs.getInt("PAGES")); + assertNull(rs.getString("FILTER_CONDITION")); } - assertEquals(index.columnName, rs.getString("COLUMN_NAME")); - assertEquals(index.ascDesc, rs.getString("ASC_OR_DESC")); - assertEquals(-1, rs.getInt("CARDINALITY")); - assertEquals(-1, rs.getInt("PAGES")); - assertNull(rs.getString("FILTER_CONDITION")); + // all indices found + assertFalse(rs.next()); } - // all indices found - assertFalse(rs.next()); } } } @@ -520,15 +552,15 @@ public void testGetIndexInfo() throws SQLException { @Test public void testGetExportedKeys() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getExportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, SINGERS_TABLE)) { - assertExportedKeysSingers(rs); - } - try (ResultSet rs = - connection.getMetaData().getExportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, ALBUMS_TABLE)) { - assertKeysAlbumsSongs(rs); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection.getMetaData().getExportedKeys(DEFAULT_CATALOG, schema, SINGERS_TABLE)) { + assertExportedKeysSingers(schema, rs); + } + try (ResultSet rs = + connection.getMetaData().getExportedKeys(DEFAULT_CATALOG, schema, ALBUMS_TABLE)) { + assertKeysAlbumsSongs(schema, rs); + } } } } @@ -536,31 +568,27 @@ public void testGetExportedKeys() throws SQLException { @Test public void testGetImportedKeys() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, SINGERS_TABLE)) { - assertImportedKeysSingers(rs); - } - try (ResultSet rs = - connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, ALBUMS_TABLE)) { - assertImportedKeysAlbums(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, CONCERTS_TABLE)) { - assertImportedKeysConcerts(rs); - } - try (ResultSet rs = - connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, SONGS_TABLE)) { - assertKeysAlbumsSongs(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, TABLE_WITH_REF)) { - assertImportedKeysTableWithRef(rs); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, schema, SINGERS_TABLE)) { + assertImportedKeysSingers(rs); + } + try (ResultSet rs = + connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, schema, ALBUMS_TABLE)) { + assertImportedKeysAlbums(schema, rs); + } + try (ResultSet rs = + connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, schema, CONCERTS_TABLE)) { + assertImportedKeysConcerts(schema, rs); + } + try (ResultSet rs = + connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, schema, SONGS_TABLE)) { + assertKeysAlbumsSongs(schema, rs); + } + try (ResultSet rs = + connection.getMetaData().getImportedKeys(DEFAULT_CATALOG, schema, TABLE_WITH_REF)) { + assertImportedKeysTableWithRef(schema, rs); + } } } } @@ -569,14 +597,14 @@ private void assertImportedKeysSingers(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { + private void assertImportedKeysTableWithRef(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); assertEquals("ColFloat64", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); assertEquals("RefFloat", rs.getString("FKCOLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); @@ -588,11 +616,11 @@ private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); assertEquals("ColString", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); assertEquals("RefString", rs.getString("FKCOLUMN_NAME")); assertEquals(2, rs.getShort("KEY_SEQ")); @@ -604,11 +632,11 @@ private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(TABLE_WITH_ALL_COLS, rs.getString("PKTABLE_NAME")); assertEquals("ColDate", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(TABLE_WITH_REF, rs.getString("FKTABLE_NAME")); assertEquals("RefDate", rs.getString("FKCOLUMN_NAME")); assertEquals(3, rs.getShort("KEY_SEQ")); @@ -621,14 +649,14 @@ private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertImportedKeysAlbums(ResultSet rs) throws SQLException { + private void assertImportedKeysAlbums(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(SINGERS_TABLE, rs.getString("PKTABLE_NAME")); assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(ALBUMS_TABLE, rs.getString("FKTABLE_NAME")); assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); @@ -641,14 +669,14 @@ private void assertImportedKeysAlbums(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertImportedKeysConcerts(ResultSet rs) throws SQLException { + private void assertImportedKeysConcerts(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(SINGERS_TABLE, rs.getString("PKTABLE_NAME")); assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(CONCERTS_TABLE, rs.getString("FKTABLE_NAME")); assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); @@ -661,14 +689,14 @@ private void assertImportedKeysConcerts(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertExportedKeysSingers(ResultSet rs) throws SQLException { + private void assertExportedKeysSingers(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(SINGERS_TABLE, rs.getString("PKTABLE_NAME")); assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(ALBUMS_TABLE, rs.getString("FKTABLE_NAME")); assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); @@ -680,11 +708,11 @@ private void assertExportedKeysSingers(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(SINGERS_TABLE, rs.getString("PKTABLE_NAME")); assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(CONCERTS_TABLE, rs.getString("FKTABLE_NAME")); assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); @@ -697,14 +725,14 @@ private void assertExportedKeysSingers(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertKeysAlbumsSongs(ResultSet rs) throws SQLException { + private void assertKeysAlbumsSongs(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(ALBUMS_TABLE, rs.getString("PKTABLE_NAME")); assertEquals("SingerId", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(SONGS_TABLE, rs.getString("FKTABLE_NAME")); assertEquals("SingerId", rs.getString("FKCOLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); @@ -716,11 +744,11 @@ private void assertKeysAlbumsSongs(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(DEFAULT_CATALOG, rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals(ALBUMS_TABLE, rs.getString("PKTABLE_NAME")); assertEquals("AlbumId", rs.getString("PKCOLUMN_NAME")); assertEquals(DEFAULT_CATALOG, rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals(SONGS_TABLE, rs.getString("FKTABLE_NAME")); assertEquals("AlbumId", rs.getString("FKCOLUMN_NAME")); assertEquals(2, rs.getShort("KEY_SEQ")); @@ -735,48 +763,54 @@ private void assertKeysAlbumsSongs(ResultSet rs) throws SQLException { @Test public void testGetPrimaryKeys() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection.getMetaData().getPrimaryKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, SINGERS_TABLE)) { - assertTrue(rs.next()); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(SINGERS_TABLE, rs.getString("TABLE_NAME")); - assertEquals("SingerId", rs.getString("COLUMN_NAME")); - assertEquals(1, rs.getInt("KEY_SEQ")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertFalse(rs.next()); - } - try (ResultSet rs = - connection.getMetaData().getPrimaryKeys(DEFAULT_CATALOG, DEFAULT_SCHEMA, ALBUMS_TABLE)) { - assertTrue(rs.next()); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(ALBUMS_TABLE, rs.getString("TABLE_NAME")); - assertEquals("SingerId", rs.getString("COLUMN_NAME")); - assertEquals(1, rs.getInt("KEY_SEQ")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertTrue(rs.next()); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(ALBUMS_TABLE, rs.getString("TABLE_NAME")); - assertEquals("AlbumId", rs.getString("COLUMN_NAME")); - assertEquals(2, rs.getInt("KEY_SEQ")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertFalse(rs.next()); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection.getMetaData().getPrimaryKeys(DEFAULT_CATALOG, schema, SINGERS_TABLE)) { + assertTrue(rs.next()); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(SINGERS_TABLE, rs.getString("TABLE_NAME")); + assertEquals("SingerId", rs.getString("COLUMN_NAME")); + assertEquals(1, rs.getInt("KEY_SEQ")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertFalse(rs.next()); + } + try (ResultSet rs = + connection.getMetaData().getPrimaryKeys(DEFAULT_CATALOG, schema, ALBUMS_TABLE)) { + assertTrue(rs.next()); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(ALBUMS_TABLE, rs.getString("TABLE_NAME")); + assertEquals("SingerId", rs.getString("COLUMN_NAME")); + assertEquals(1, rs.getInt("KEY_SEQ")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertTrue(rs.next()); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(ALBUMS_TABLE, rs.getString("TABLE_NAME")); + assertEquals("AlbumId", rs.getString("COLUMN_NAME")); + assertEquals(2, rs.getInt("KEY_SEQ")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertFalse(rs.next()); + } } } } @Test public void testGetViews() throws SQLException { - // assumeFalse("Emulator does not yet support views", EmulatorSpannerHelper.isUsingEmulator()); try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = connection.getMetaData().getTables("", "", null, new String[] {"VIEW"})) { - assertTrue(rs.next()); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals("SingersView", rs.getString("TABLE_NAME")); - assertFalse(rs.next()); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getTables(DEFAULT_CATALOG, schema, null, new String[] {"VIEW"})) { + assertTrue(rs.next()); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals("SingersView", rs.getString("TABLE_NAME")); + assertFalse(rs.next()); + } } } } @@ -785,17 +819,24 @@ public void testGetViews() throws SQLException { public void testGetSchemas() throws SQLException { try (Connection connection = createConnection(env, database)) { assertEquals("", connection.getSchema()); - try (ResultSet rs = connection.getMetaData().getSchemas()) { - assertTrue(rs.next()); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CATALOG")); - assertTrue(rs.next()); - assertEquals("INFORMATION_SCHEMA", rs.getString("TABLE_SCHEM")); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CATALOG")); - assertTrue(rs.next()); - assertEquals("SPANNER_SYS", rs.getString("TABLE_SCHEM")); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CATALOG")); - assertFalse(rs.next()); + try (ResultSet schemas = connection.getMetaData().getSchemas()) { + assertTrue(schemas.next()); + assertEquals(DEFAULT_SCHEMA, schemas.getString("TABLE_SCHEM")); + assertEquals(DEFAULT_CATALOG, schemas.getString("TABLE_CATALOG")); + + assertTrue(schemas.next()); + assertEquals("INFORMATION_SCHEMA", schemas.getString("TABLE_SCHEM")); + assertEquals(DEFAULT_CATALOG, schemas.getString("TABLE_CATALOG")); + + assertTrue(schemas.next()); + assertEquals("SPANNER_SYS", schemas.getString("TABLE_SCHEM")); + assertEquals(DEFAULT_CATALOG, schemas.getString("TABLE_CATALOG")); + + assertTrue(schemas.next()); + assertEquals("test", schemas.getString("TABLE_SCHEM")); + assertEquals(DEFAULT_CATALOG, schemas.getString("TABLE_CATALOG")); + + assertFalse(schemas.next()); } } } @@ -841,22 +882,27 @@ private Table(String name, String type) { @Test public void testGetTables() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection.getMetaData().getTables(DEFAULT_CATALOG, DEFAULT_SCHEMA, null, null)) { - for (Table table : EXPECTED_TABLES) { - assertTrue(rs.next()); - assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(table.name, rs.getString("TABLE_NAME")); - assertEquals(table.type, rs.getString("TABLE_TYPE")); - assertNull(rs.getString("REMARKS")); - assertNull(rs.getString("TYPE_CAT")); - assertNull(rs.getString("TYPE_SCHEM")); - assertNull(rs.getString("TYPE_NAME")); - assertNull(rs.getString("SELF_REFERENCING_COL_NAME")); - assertNull(rs.getString("REF_GENERATION")); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection.getMetaData().getTables(DEFAULT_CATALOG, schema, null, null)) { + for (Table table : EXPECTED_TABLES) { + if (EmulatorSpannerHelper.isUsingEmulator() && table.name.equals("SingersView")) { + continue; + } + assertTrue(rs.next()); + assertEquals(DEFAULT_CATALOG, rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(table.name, rs.getString("TABLE_NAME")); + assertEquals(table.type, rs.getString("TABLE_TYPE")); + assertNull(rs.getString("REMARKS")); + assertNull(rs.getString("TYPE_CAT")); + assertNull(rs.getString("TYPE_SCHEM")); + assertNull(rs.getString("TYPE_NAME")); + assertNull(rs.getString("SELF_REFERENCING_COL_NAME")); + assertNull(rs.getString("REF_GENERATION")); + } + assertFalse(rs.next()); } - assertFalse(rs.next()); } } } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgDatabaseMetaDataTest.java index 1b96e9569..033ff27e4 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgDatabaseMetaDataTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPgDatabaseMetaDataTest.java @@ -21,10 +21,13 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.testing.EmulatorSpannerHelper; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -32,9 +35,9 @@ import java.sql.Types; import java.util.Arrays; import java.util.List; -import org.junit.Before; +import java.util.stream.Collectors; +import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -54,11 +57,41 @@ public class ITJdbcPgDatabaseMetaDataTest extends ITAbstractJdbcTest { private static final String TABLE_WITH_ALL_COLS = "TableWithAllColumnTypes"; private static final String TABLE_WITH_REF = "TableWithRef"; - private Database database; - - @Before - public void setup() { - database = env.getOrCreateDatabase(getDialect(), getMusicTablesDdl(getDialect())); + private static Database database; + + @BeforeClass + public static void setup() throws Exception { + assumeFalse( + "PostgreSQL dialect is not yet supported on the emulator", + EmulatorSpannerHelper.isUsingEmulator()); + + database = env.getOrCreateDatabase(Dialect.POSTGRESQL, getMusicTablesDdl(Dialect.POSTGRESQL)); + + // Create the same tables in an additional 'test' schema. + DatabaseAdminClient client = env.getTestHelper().getClient().getDatabaseAdminClient(); + List tables = + getMusicTablesDdl(Dialect.POSTGRESQL).stream() + .map(statement -> statement.replace("CREATE TABLE ", "CREATE TABLE test.")) + .map(statement -> statement.replace("CREATE VIEW ", "CREATE VIEW test.")) + .map(statement -> statement.replace("FROM ", "FROM test.")) + .map( + statement -> + statement.replace("INTERLEAVE IN PARENT ", "INTERLEAVE IN PARENT test.")) + .map(statement -> statement.replace("INTERLEAVE IN ", "INTERLEAVE IN test.")) + .map( + statement -> statement.replace("INTERLEAVE IN test.PARENT", "INTERLEAVE IN PARENT")) + .map(statement -> statement.replace(" ON ", " ON test.")) + .map(statement -> statement.replace(" ON test.DELETE", " ON DELETE")) + .map(statement -> statement.replace(" REFERENCES ", " REFERENCES test.")) + .collect(Collectors.toList()); + tables.add(0, "create schema test"); + client + .updateDatabaseDdl( + database.getId().getInstanceId().getInstance(), + database.getId().getDatabase(), + tables, + null) + .get(); } @Override @@ -138,68 +171,70 @@ private Column( @Test public void testGetColumns() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getColumns(getDefaultCatalog(database), DEFAULT_SCHEMA, TABLE_WITH_ALL_COLS, null)) { - int pos = 1; - for (Column col : EXPECTED_COLUMNS) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals("tablewithallcolumntypes", rs.getString("TABLE_NAME")); - assertEquals(col.name, rs.getString("COLUMN_NAME")); - assertEquals(col.type, rs.getInt("DATA_TYPE")); - assertEquals(col.typeName, rs.getString("TYPE_NAME")); - if (col.colSize == null) { - assertEquals(0, rs.getInt("COLUMN_SIZE")); - assertTrue(rs.wasNull()); - } else { - assertEquals(col.colSize.intValue(), rs.getInt("COLUMN_SIZE")); - } - rs.getObject("BUFFER_LENGTH"); // just assert that it exists - if (col.decimalDigits == null) { - assertEquals(0, rs.getInt("DECIMAL_DIGITS")); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getColumns(getDefaultCatalog(database), schema, TABLE_WITH_ALL_COLS, null)) { + int pos = 1; + for (Column col : EXPECTED_COLUMNS) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals("tablewithallcolumntypes", rs.getString("TABLE_NAME")); + assertEquals(col.name, rs.getString("COLUMN_NAME")); + assertEquals(col.type, rs.getInt("DATA_TYPE")); + assertEquals(col.typeName, rs.getString("TYPE_NAME")); + if (col.colSize == null) { + assertEquals(0, rs.getInt("COLUMN_SIZE")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.colSize.intValue(), rs.getInt("COLUMN_SIZE")); + } + rs.getObject("BUFFER_LENGTH"); // just assert that it exists + if (col.decimalDigits == null) { + assertEquals(0, rs.getInt("DECIMAL_DIGITS")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.decimalDigits.intValue(), rs.getInt("DECIMAL_DIGITS")); + } + if (col.radix == null) { + assertEquals(0, rs.getInt("NUM_PREC_RADIX")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.radix.intValue(), rs.getInt("NUM_PREC_RADIX")); + } + assertEquals( + "Nullable difference for " + col.name, + col.nullable ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, + rs.getInt("NULLABLE")); + assertNull(rs.getString("REMARKS")); + assertNull(rs.getString("COLUMN_DEF")); + assertEquals(0, rs.getInt("SQL_DATA_TYPE")); + assertEquals(0, rs.getInt("SQL_DATETIME_SUB")); + if (col.charOctetLength == null) { + assertEquals(0, rs.getInt("CHAR_OCTET_LENGTH")); + assertTrue(rs.wasNull()); + } else { + assertEquals(col.charOctetLength.intValue(), rs.getInt("CHAR_OCTET_LENGTH")); + } + assertEquals(pos, rs.getInt("ORDINAL_POSITION")); + assertEquals(col.nullable ? "YES" : "NO", rs.getString("IS_NULLABLE")); + assertNull(rs.getString("SCOPE_CATALOG")); + assertNull(rs.getString("SCOPE_SCHEMA")); + assertNull(rs.getString("SCOPE_TABLE")); + + assertEquals((short) 0, rs.getShort("SOURCE_DATA_TYPE")); assertTrue(rs.wasNull()); - } else { - assertEquals(col.decimalDigits.intValue(), rs.getInt("DECIMAL_DIGITS")); - } - if (col.radix == null) { - assertEquals(0, rs.getInt("NUM_PREC_RADIX")); - assertTrue(rs.wasNull()); - } else { - assertEquals(col.radix.intValue(), rs.getInt("NUM_PREC_RADIX")); - } - assertEquals( - "Nullable difference for " + col.name, - col.nullable ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls, - rs.getInt("NULLABLE")); - assertNull(rs.getString("REMARKS")); - assertNull(rs.getString("COLUMN_DEF")); - assertEquals(0, rs.getInt("SQL_DATA_TYPE")); - assertEquals(0, rs.getInt("SQL_DATETIME_SUB")); - if (col.charOctetLength == null) { - assertEquals(0, rs.getInt("CHAR_OCTET_LENGTH")); - assertTrue(rs.wasNull()); - } else { - assertEquals(col.charOctetLength.intValue(), rs.getInt("CHAR_OCTET_LENGTH")); - } - assertEquals(pos, rs.getInt("ORDINAL_POSITION")); - assertEquals(col.nullable ? "YES" : "NO", rs.getString("IS_NULLABLE")); - assertNull(rs.getString("SCOPE_CATALOG")); - assertNull(rs.getString("SCOPE_SCHEMA")); - assertNull(rs.getString("SCOPE_TABLE")); - assertEquals((short) 0, rs.getShort("SOURCE_DATA_TYPE")); - assertTrue(rs.wasNull()); + assertEquals("NO", rs.getString("IS_AUTOINCREMENT")); + assertEquals(col.computed ? "YES" : "NO", rs.getString("IS_GENERATEDCOLUMN")); + assertEquals(24, rs.getMetaData().getColumnCount()); - assertEquals("NO", rs.getString("IS_AUTOINCREMENT")); - assertEquals(col.computed ? "YES" : "NO", rs.getString("IS_GENERATEDCOLUMN")); - assertEquals(24, rs.getMetaData().getColumnCount()); - - pos++; + pos++; + } + assertFalse(rs.next()); } - assertFalse(rs.next()); } } } @@ -207,168 +242,170 @@ public void testGetColumns() throws SQLException { @Test public void testGetCrossReferences() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - getDefaultCatalog(database), - DEFAULT_SCHEMA, - SINGERS_TABLE, - getDefaultCatalog(database), - DEFAULT_SCHEMA, - ALBUMS_TABLE)) { - assertTrue(rs.next()); - - assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); - assertEquals("singers", rs.getString("PKTABLE_NAME")); - assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); - assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); - assertEquals("albums", rs.getString("FKTABLE_NAME")); - assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); - assertEquals((short) 1, rs.getShort("KEY_SEQ")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertNotNull(rs.getString("FK_NAME")); - assertEquals("PK_singers", rs.getString("PK_NAME")); - assertEquals( - (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - } - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - getDefaultCatalog(database), - DEFAULT_SCHEMA, - ALBUMS_TABLE, - getDefaultCatalog(database), - DEFAULT_SCHEMA, - SONGS_TABLE)) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); - assertEquals("albums", rs.getString("PKTABLE_NAME")); - assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); - assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); - assertEquals("songs", rs.getString("FKTABLE_NAME")); - assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); - assertEquals((short) 1, rs.getShort("KEY_SEQ")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertNotNull(rs.getString("FK_NAME")); - assertEquals("PK_albums", rs.getString("PK_NAME")); - assertEquals( - (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + getDefaultCatalog(database), + schema, + SINGERS_TABLE, + getDefaultCatalog(database), + schema, + ALBUMS_TABLE)) { + assertTrue(rs.next()); - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); - assertEquals("albums", rs.getString("PKTABLE_NAME")); - assertEquals("albumid", rs.getString("PKCOLUMN_NAME")); - assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); - assertEquals("songs", rs.getString("FKTABLE_NAME")); - assertEquals("albumid", rs.getString("FKCOLUMN_NAME")); - assertEquals((short) 2, rs.getShort("KEY_SEQ")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertNotNull(rs.getString("FK_NAME")); - assertEquals("PK_albums", rs.getString("PK_NAME")); - assertEquals( - (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("singers", rs.getString("PKTABLE_NAME")); + assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); + assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("albums", rs.getString("FKTABLE_NAME")); + assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); + assertEquals((short) 1, rs.getShort("KEY_SEQ")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertNotNull(rs.getString("FK_NAME")); + assertEquals("PK_singers", rs.getString("PK_NAME")); + assertEquals( + (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + } + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + getDefaultCatalog(database), + schema, + ALBUMS_TABLE, + getDefaultCatalog(database), + schema, + SONGS_TABLE)) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("albums", rs.getString("PKTABLE_NAME")); + assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); + assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("songs", rs.getString("FKTABLE_NAME")); + assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); + assertEquals((short) 1, rs.getShort("KEY_SEQ")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertNotNull(rs.getString("FK_NAME")); + assertEquals("PK_albums", rs.getString("PK_NAME")); + assertEquals( + (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - assertFalse(rs.next()); - } + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("albums", rs.getString("PKTABLE_NAME")); + assertEquals("albumid", rs.getString("PKCOLUMN_NAME")); + assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("songs", rs.getString("FKTABLE_NAME")); + assertEquals("albumid", rs.getString("FKCOLUMN_NAME")); + assertEquals((short) 2, rs.getShort("KEY_SEQ")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertNotNull(rs.getString("FK_NAME")); + assertEquals("PK_albums", rs.getString("PK_NAME")); + assertEquals( + (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - getDefaultCatalog(database), - DEFAULT_SCHEMA, - SINGERS_TABLE, - getDefaultCatalog(database), - DEFAULT_SCHEMA, - CONCERTS_TABLE)) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); - assertEquals("singers", rs.getString("PKTABLE_NAME")); - assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); - assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); - assertEquals("concerts", rs.getString("FKTABLE_NAME")); - assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); - assertEquals((short) 1, rs.getShort("KEY_SEQ")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertNotNull(rs.getString("FK_NAME")); - assertEquals("PK_singers", rs.getString("PK_NAME")); - assertEquals( - (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + assertFalse(rs.next()); + } - assertFalse(rs.next()); - } + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + getDefaultCatalog(database), + schema, + SINGERS_TABLE, + getDefaultCatalog(database), + schema, + CONCERTS_TABLE)) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("singers", rs.getString("PKTABLE_NAME")); + assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); + assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("concerts", rs.getString("FKTABLE_NAME")); + assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); + assertEquals((short) 1, rs.getShort("KEY_SEQ")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertNotNull(rs.getString("FK_NAME")); + assertEquals("PK_singers", rs.getString("PK_NAME")); + assertEquals( + (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - getDefaultCatalog(database), - DEFAULT_SCHEMA, - TABLE_WITH_ALL_COLS, - getDefaultCatalog(database), - DEFAULT_SCHEMA, - TABLE_WITH_REF)) { + assertFalse(rs.next()); + } - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); - assertEquals("tablewithallcolumntypes", rs.getString("PKTABLE_NAME")); - assertEquals("colfloat64", rs.getString("PKCOLUMN_NAME")); - assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); - assertEquals("tablewithref", rs.getString("FKTABLE_NAME")); - assertEquals("reffloat", rs.getString("FKCOLUMN_NAME")); - assertEquals((short) 1, rs.getShort("KEY_SEQ")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertEquals("fk_tablewithref_tablewithallcolumntypes", rs.getString("FK_NAME")); - assertEquals( - (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + getDefaultCatalog(database), + schema, + TABLE_WITH_ALL_COLS, + getDefaultCatalog(database), + schema, + TABLE_WITH_REF)) { + + assertTrue(schema, rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("tablewithallcolumntypes", rs.getString("PKTABLE_NAME")); + assertEquals("colfloat64", rs.getString("PKCOLUMN_NAME")); + assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("tablewithref", rs.getString("FKTABLE_NAME")); + assertEquals("reffloat", rs.getString("FKCOLUMN_NAME")); + assertEquals((short) 1, rs.getShort("KEY_SEQ")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertEquals("fk_tablewithref_tablewithallcolumntypes", rs.getString("FK_NAME")); + assertEquals( + (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); - assertEquals("tablewithallcolumntypes", rs.getString("PKTABLE_NAME")); - assertEquals("colstring", rs.getString("PKCOLUMN_NAME")); - assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); - assertEquals("tablewithref", rs.getString("FKTABLE_NAME")); - assertEquals("refstring", rs.getString("FKCOLUMN_NAME")); - assertEquals((short) 2, rs.getShort("KEY_SEQ")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); - assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); - assertEquals("fk_tablewithref_tablewithallcolumntypes", rs.getString("FK_NAME")); - assertEquals( - (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); + assertEquals("tablewithallcolumntypes", rs.getString("PKTABLE_NAME")); + assertEquals("colstring", rs.getString("PKCOLUMN_NAME")); + assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); + assertEquals("tablewithref", rs.getString("FKTABLE_NAME")); + assertEquals("refstring", rs.getString("FKCOLUMN_NAME")); + assertEquals((short) 2, rs.getShort("KEY_SEQ")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("UPDATE_RULE")); + assertEquals((short) DatabaseMetaData.importedKeyNoAction, rs.getShort("DELETE_RULE")); + assertEquals("fk_tablewithref_tablewithallcolumntypes", rs.getString("FK_NAME")); + assertEquals( + (short) DatabaseMetaData.importedKeyNotDeferrable, rs.getShort("DEFERRABILITY")); - assertFalse(rs.next()); - } - // try getting self-references - try (ResultSet rs = - connection - .getMetaData() - .getCrossReference( - getDefaultCatalog(database), - DEFAULT_SCHEMA, - ALBUMS_TABLE, - getDefaultCatalog(database), - DEFAULT_SCHEMA, - ALBUMS_TABLE)) { - assertFalse(rs.next()); + assertFalse(rs.next()); + } + // try getting self-references + try (ResultSet rs = + connection + .getMetaData() + .getCrossReference( + getDefaultCatalog(database), + schema, + ALBUMS_TABLE, + getDefaultCatalog(database), + schema, + ALBUMS_TABLE)) { + assertFalse(rs.next()); + } } // try getting all cross-references in the database try (ResultSet rs = @@ -377,7 +414,7 @@ public void testGetCrossReferences() throws SQLException { while (rs.next()) { count++; } - assertEquals(6, count); + assertEquals(12, count); } } } @@ -442,36 +479,38 @@ private IndexInfo( @Test public void testGetIndexInfo() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getIndexInfo(getDefaultCatalog(database), DEFAULT_SCHEMA, null, false, false)) { - - for (IndexInfo index : EXPECTED_INDICES) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals(index.tableName, rs.getString("TABLE_NAME")); - assertEquals(index.nonUnique, rs.getBoolean("NON_UNIQUE")); - assertEquals(getDefaultCatalog(database), rs.getString("INDEX_QUALIFIER")); - - // Foreign key index names are automatically generated. - if (!"FOREIGN_KEY".equals(index.indexName) && !"GENERATED".equals(index.indexName)) { - assertEquals(index.indexName, rs.getString("INDEX_NAME")); - } - assertEquals(DatabaseMetaData.tableIndexHashed, rs.getShort("TYPE")); - assertEquals(index.ordinalPosition, rs.getShort("ORDINAL_POSITION")); - if (index.ordinalPosition == 0) { - assertTrue(rs.wasNull()); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getIndexInfo(getDefaultCatalog(database), schema, null, false, false)) { + + for (IndexInfo index : EXPECTED_INDICES) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals(index.tableName, rs.getString("TABLE_NAME")); + assertEquals(index.nonUnique, rs.getBoolean("NON_UNIQUE")); + assertEquals(getDefaultCatalog(database), rs.getString("INDEX_QUALIFIER")); + + // Foreign key index names are automatically generated. + if (!"FOREIGN_KEY".equals(index.indexName) && !"GENERATED".equals(index.indexName)) { + assertEquals(index.indexName, rs.getString("INDEX_NAME")); + } + assertEquals(DatabaseMetaData.tableIndexHashed, rs.getShort("TYPE")); + assertEquals(index.ordinalPosition, rs.getShort("ORDINAL_POSITION")); + if (index.ordinalPosition == 0) { + assertTrue(rs.wasNull()); + } + assertEquals(index.columnName, rs.getString("COLUMN_NAME")); + assertEquals(index.ascDesc, rs.getString("ASC_OR_DESC")); + assertEquals(-1, rs.getInt("CARDINALITY")); + assertEquals(-1, rs.getInt("PAGES")); + assertNull(rs.getString("FILTER_CONDITION")); } - assertEquals(index.columnName, rs.getString("COLUMN_NAME")); - assertEquals(index.ascDesc, rs.getString("ASC_OR_DESC")); - assertEquals(-1, rs.getInt("CARDINALITY")); - assertEquals(-1, rs.getInt("PAGES")); - assertNull(rs.getString("FILTER_CONDITION")); + // all indices found + assertFalse(rs.next()); } - // all indices found - assertFalse(rs.next()); } } } @@ -479,17 +518,19 @@ public void testGetIndexInfo() throws SQLException { @Test public void testGetExportedKeys() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getExportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, SINGERS_TABLE)) { - assertExportedKeysSingers(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getExportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, ALBUMS_TABLE)) { - assertKeysAlbumsSongs(rs); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getExportedKeys(getDefaultCatalog(database), schema, SINGERS_TABLE)) { + assertExportedKeysSingers(schema, rs); + } + try (ResultSet rs = + connection + .getMetaData() + .getExportedKeys(getDefaultCatalog(database), schema, ALBUMS_TABLE)) { + assertKeysAlbumsSongs(schema, rs); + } } } } @@ -497,51 +538,53 @@ public void testGetExportedKeys() throws SQLException { @Test public void testGetImportedKeys() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, SINGERS_TABLE)) { - assertImportedKeysSingers(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, ALBUMS_TABLE)) { - assertImportedKeysAlbums(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, CONCERTS_TABLE)) { - assertImportedKeysConcerts(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, SONGS_TABLE)) { - assertKeysAlbumsSongs(rs); - } - try (ResultSet rs = - connection - .getMetaData() - .getImportedKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, TABLE_WITH_REF)) { - assertImportedKeysTableWithRef(rs); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getImportedKeys(getDefaultCatalog(database), schema, SINGERS_TABLE)) { + assertImportedKeysSingers(schema, rs); + } + try (ResultSet rs = + connection + .getMetaData() + .getImportedKeys(getDefaultCatalog(database), schema, ALBUMS_TABLE)) { + assertImportedKeysAlbums(schema, rs); + } + try (ResultSet rs = + connection + .getMetaData() + .getImportedKeys(getDefaultCatalog(database), schema, CONCERTS_TABLE)) { + assertImportedKeysConcerts(schema, rs); + } + try (ResultSet rs = + connection + .getMetaData() + .getImportedKeys(getDefaultCatalog(database), schema, SONGS_TABLE)) { + assertKeysAlbumsSongs(schema, rs); + } + try (ResultSet rs = + connection + .getMetaData() + .getImportedKeys(getDefaultCatalog(database), schema, TABLE_WITH_REF)) { + assertImportedKeysTableWithRef(schema, rs); + } } } } - private void assertImportedKeysSingers(ResultSet rs) throws SQLException { + private void assertImportedKeysSingers(String schema, ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { + private void assertImportedKeysTableWithRef(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("tablewithallcolumntypes", rs.getString("PKTABLE_NAME")); assertEquals("colfloat64", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("tablewithref", rs.getString("FKTABLE_NAME")); assertEquals("reffloat", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 1, rs.getShort("KEY_SEQ")); @@ -553,11 +596,11 @@ private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("tablewithallcolumntypes", rs.getString("PKTABLE_NAME")); assertEquals("colstring", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("tablewithref", rs.getString("FKTABLE_NAME")); assertEquals("refstring", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 2, rs.getShort("KEY_SEQ")); @@ -570,14 +613,14 @@ private void assertImportedKeysTableWithRef(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertImportedKeysAlbums(ResultSet rs) throws SQLException { + private void assertImportedKeysAlbums(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("singers", rs.getString("PKTABLE_NAME")); assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("albums", rs.getString("FKTABLE_NAME")); assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 1, rs.getShort("KEY_SEQ")); @@ -590,14 +633,14 @@ private void assertImportedKeysAlbums(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertImportedKeysConcerts(ResultSet rs) throws SQLException { + private void assertImportedKeysConcerts(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("singers", rs.getString("PKTABLE_NAME")); assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("concerts", rs.getString("FKTABLE_NAME")); assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 1, rs.getShort("KEY_SEQ")); @@ -609,14 +652,14 @@ private void assertImportedKeysConcerts(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertExportedKeysSingers(ResultSet rs) throws SQLException { + private void assertExportedKeysSingers(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("singers", rs.getString("PKTABLE_NAME")); assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("albums", rs.getString("FKTABLE_NAME")); assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 1, rs.getShort("KEY_SEQ")); @@ -628,11 +671,11 @@ private void assertExportedKeysSingers(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("singers", rs.getString("PKTABLE_NAME")); assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("concerts", rs.getString("FKTABLE_NAME")); assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 1, rs.getShort("KEY_SEQ")); @@ -645,14 +688,14 @@ private void assertExportedKeysSingers(ResultSet rs) throws SQLException { assertFalse(rs.next()); } - private void assertKeysAlbumsSongs(ResultSet rs) throws SQLException { + private void assertKeysAlbumsSongs(String schema, ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("albums", rs.getString("PKTABLE_NAME")); assertEquals("singerid", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("songs", rs.getString("FKTABLE_NAME")); assertEquals("singerid", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 1, rs.getShort("KEY_SEQ")); @@ -664,11 +707,11 @@ private void assertKeysAlbumsSongs(ResultSet rs) throws SQLException { assertTrue(rs.next()); assertEquals(getDefaultCatalog(database), rs.getString("PKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("PKTABLE_SCHEM")); + assertEquals(schema, rs.getString("PKTABLE_SCHEM")); assertEquals("albums", rs.getString("PKTABLE_NAME")); assertEquals("albumid", rs.getString("PKCOLUMN_NAME")); assertEquals(getDefaultCatalog(database), rs.getString("FKTABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("FKTABLE_SCHEM")); + assertEquals(schema, rs.getString("FKTABLE_SCHEM")); assertEquals("songs", rs.getString("FKTABLE_NAME")); assertEquals("albumid", rs.getString("FKCOLUMN_NAME")); assertEquals((short) 2, rs.getShort("KEY_SEQ")); @@ -684,58 +727,60 @@ private void assertKeysAlbumsSongs(ResultSet rs) throws SQLException { @Test public void testGetPrimaryKeys() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getPrimaryKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, SINGERS_TABLE)) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals("singers", rs.getString("TABLE_NAME")); - assertEquals("singerid", rs.getString("COLUMN_NAME")); - assertEquals(1, rs.getInt("KEY_SEQ")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertFalse(rs.next()); - } + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getPrimaryKeys(getDefaultCatalog(database), schema, SINGERS_TABLE)) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals("singers", rs.getString("TABLE_NAME")); + assertEquals("singerid", rs.getString("COLUMN_NAME")); + assertEquals(1, rs.getInt("KEY_SEQ")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertFalse(rs.next()); + } - try (ResultSet rs = - connection - .getMetaData() - .getPrimaryKeys(getDefaultCatalog(database), DEFAULT_SCHEMA, ALBUMS_TABLE)) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals("albums", rs.getString("TABLE_NAME")); - assertEquals("singerid", rs.getString("COLUMN_NAME")); - assertEquals(1, rs.getInt("KEY_SEQ")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals("albums", rs.getString("TABLE_NAME")); - assertEquals("albumid", rs.getString("COLUMN_NAME")); - assertEquals(2, rs.getInt("KEY_SEQ")); - assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + try (ResultSet rs = + connection + .getMetaData() + .getPrimaryKeys(getDefaultCatalog(database), schema, ALBUMS_TABLE)) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals("albums", rs.getString("TABLE_NAME")); + assertEquals("singerid", rs.getString("COLUMN_NAME")); + assertEquals(1, rs.getInt("KEY_SEQ")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals("albums", rs.getString("TABLE_NAME")); + assertEquals("albumid", rs.getString("COLUMN_NAME")); + assertEquals(2, rs.getInt("KEY_SEQ")); + assertEquals("PRIMARY_KEY", rs.getString("PK_NAME")); - assertFalse(rs.next()); + assertFalse(rs.next()); + } } } } - @Ignore("Views are not yet supported for PostgreSQL") @Test public void testGetViews() throws SQLException { try (Connection connection = createConnection(env, database)) { - try (ResultSet rs = - connection - .getMetaData() - .getTables( - getDefaultCatalog(database), DEFAULT_SCHEMA, null, new String[] {"VIEW"})) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); - assertEquals(DEFAULT_SCHEMA, rs.getString("TABLE_SCHEM")); - assertEquals("SingersView", rs.getString("TABLE_NAME")); - assertFalse(rs.next()); + for (String schema : new String[] {DEFAULT_SCHEMA, "test"}) { + try (ResultSet rs = + connection + .getMetaData() + .getTables(getDefaultCatalog(database), schema, null, new String[] {"VIEW"})) { + assertTrue(rs.next()); + assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CAT")); + assertEquals(schema, rs.getString("TABLE_SCHEM")); + assertEquals("singersview", rs.getString("TABLE_NAME")); + assertFalse(rs.next()); + } } } } @@ -744,24 +789,28 @@ public void testGetViews() throws SQLException { public void testGetSchemas() throws SQLException { try (Connection connection = createConnection(env, database)) { assertEquals("public", connection.getSchema()); - try (ResultSet rs = connection.getMetaData().getSchemas()) { - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CATALOG")); - assertEquals("information_schema", rs.getString("TABLE_SCHEM")); + try (ResultSet schemas = connection.getMetaData().getSchemas()) { + assertTrue(schemas.next()); + assertEquals(getDefaultCatalog(database), schemas.getString("TABLE_CATALOG")); + assertEquals("information_schema", schemas.getString("TABLE_SCHEM")); - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CATALOG")); - assertEquals("pg_catalog", rs.getString("TABLE_SCHEM")); + assertTrue(schemas.next()); + assertEquals(getDefaultCatalog(database), schemas.getString("TABLE_CATALOG")); + assertEquals("pg_catalog", schemas.getString("TABLE_SCHEM")); - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CATALOG")); - assertEquals("public", rs.getString("TABLE_SCHEM")); + assertTrue(schemas.next()); + assertEquals(getDefaultCatalog(database), schemas.getString("TABLE_CATALOG")); + assertEquals("public", schemas.getString("TABLE_SCHEM")); - assertTrue(rs.next()); - assertEquals(getDefaultCatalog(database), rs.getString("TABLE_CATALOG")); - assertEquals("spanner_sys", rs.getString("TABLE_SCHEM")); + assertTrue(schemas.next()); + assertEquals(getDefaultCatalog(database), schemas.getString("TABLE_CATALOG")); + assertEquals("spanner_sys", schemas.getString("TABLE_SCHEM")); - assertFalse(rs.next()); + assertTrue(schemas.next()); + assertEquals(getDefaultCatalog(database), schemas.getString("TABLE_CATALOG")); + assertEquals("test", schemas.getString("TABLE_SCHEM")); + + assertFalse(schemas.next()); } } } @@ -799,8 +848,7 @@ private Table(String name, String type) { new Table("all_nullable_types"), new Table("concerts"), new Table("singers"), - // TODO: Enable when views are supported for PostgreSQL dialect databases. - // new Table("singersview", "VIEW"), + new Table("singersview", "VIEW"), new Table("songs"), new Table("tablewithallcolumntypes"), new Table("tablewithref")); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITSingleJarTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITSingleJarTest.java new file mode 100644 index 000000000..9ae8f6003 --- /dev/null +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITSingleJarTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.jdbc.it; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.common.collect.ImmutableList; +import com.google.common.io.CharStreams; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests that the following works: + * + *
    + *
  1. Build a single-jar-with-dependencies + *
  2. Compile a simple Java application consisting of a single file and no dependencies to a + * class file + *
  3. Run the simple Java application with only itself + the single-jar-with-dependencies on the + * class path + *
+ */ +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITSingleJarTest extends ITAbstractJdbcTest { + @ClassRule public static JdbcIntegrationTestEnv env = new JdbcIntegrationTestEnv(); + + private Database database; + + @Before + public void setup() { + database = + env.getOrCreateDatabase( + getDialect(), + ImmutableList.of("create table test (id int64, value string(max)) primary key (id)")); + } + + @Test + public void testUseSingleJar() throws Exception { + buildSingleJar(); + buildMainClass(); + runTestApplication(); + } + + @Test + public void testUseShadedJar() throws Exception { + buildShadedJar(); + buildMainClass(); + runTestApplication(); + } + + private void runTestApplication() throws Exception { + DatabaseId id = database.getId(); + ProcessBuilder builder = new ProcessBuilder(); + if (System.getenv("SPANNER_EMULATOR_HOST") != null) { + builder.environment().put("SPANNER_EMULATOR_HOST", System.getenv("SPANNER_EMULATOR_HOST")); + } + // This runs the simple test application with only the shaded jar on the classpath. + builder.command( + "java", + "-cp", + "./target/single/test/:target/single/single.jar", + "com/google/cloud/spanner/jdbc/SingleJarTestApplication", + id.getInstanceId().getProject(), + id.getInstanceId().getInstance(), + id.getDatabase()); + execute(builder); + } + + private void buildSingleJar() throws Exception { + ProcessBuilder builder = new ProcessBuilder(); + builder.command("mvn", "clean", "package", "-DskipTests", "-Dalt.build.dir=./target/single"); + execute(builder); + } + + private void buildShadedJar() throws Exception { + ProcessBuilder builder = new ProcessBuilder(); + builder.command( + "mvn", "clean", "-Pshade", "package", "-DskipTests", "-Dalt.build.dir=./target/single"); + execute(builder); + } + + private void buildMainClass() throws Exception { + Files.createDirectories(Paths.get("target", "single", "test")); + ProcessBuilder builder = new ProcessBuilder(); + builder.command( + "javac", + "src/test/java/com/google/cloud/spanner/jdbc/SingleJarTestApplication.java", + "-d", + "./target/single/test"); + execute(builder); + } + + private void execute(ProcessBuilder builder) throws Exception { + Process process = builder.start(); + String errors; + try (InputStreamReader reader = new InputStreamReader(process.getErrorStream())) { + errors = CharStreams.toString(reader); + } + assertEquals(errors, 0, process.waitFor()); + } +} diff --git a/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables_PG.sql b/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables_PG.sql index abecd9fad..3aafc5722 100644 --- a/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables_PG.sql +++ b/src/test/resources/com/google/cloud/spanner/jdbc/it/CreateMusicTables_PG.sql @@ -26,6 +26,11 @@ CREATE TABLE Singers ( CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName); +CREATE VIEW SingersView SQL SECURITY INVOKER AS +SELECT s.SingerId AS SingerId, s.FirstName AS FirstName, s.LastName AS LastName +FROM Singers s +ORDER BY s.LastName, s.FirstName; + CREATE TABLE Albums ( SingerId BIGINT NOT NULL, AlbumId BIGINT NOT NULL, diff --git a/versions.txt b/versions.txt index 6f8ffa714..05d8b85f0 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.20.2:2.20.2 +google-cloud-spanner-jdbc:2.21.0:2.21.0