From eb3b721f92521363888ca417985edc068514f3aa Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 21 May 2023 15:48:52 +0200 Subject: [PATCH 01/21] Refactor packages Split types, utility classes and db metadata management into dedicated packages. Remove Azure pipelines (deprecated, only Github actions pipelines are still running for this project). Reformat POM file. --- CHANGELOG.md | 4 + azure-build.yaml | 34 ----- azure-release.yaml | 136 ------------------ pom.xml | 82 +++++------ release-settings.xml | 12 -- .../cassandra/jdbc/AbstractConnection.java | 4 +- .../cassandra/jdbc/AbstractResultSet.java | 5 +- .../cassandra/jdbc/AbstractStatement.java | 4 +- .../cassandra/jdbc/CassandraConnection.java | 28 ++-- .../cassandra/jdbc/CassandraDataSource.java | 27 ++-- .../jdbc/CassandraDatabaseMetaData.java | 8 +- .../data/cassandra/jdbc/CassandraDriver.java | 12 +- .../jdbc/CassandraMetadataResultSet.java | 40 ++++-- .../jdbc/CassandraParameterMetaData.java | 5 +- .../jdbc/CassandraPreparedStatement.java | 4 +- .../cassandra/jdbc/CassandraResultSet.java | 30 ++-- .../cassandra/jdbc/CassandraStatement.java | 1 + .../cassandra/jdbc/ManagedConnection.java | 4 +- .../jdbc/ManagedPreparedStatement.java | 2 +- .../cassandra/jdbc/MetadataResultSets.java | 33 +++-- .../jdbc/PooledCassandraDataSource.java | 2 +- .../data/cassandra/jdbc/SessionHolder.java | 11 +- .../cassandra/jdbc/codec/AbstractCodec.java | 2 +- .../jdbc/codec/BigintToBigDecimalCodec.java | 2 +- .../jdbc/codec/DecimalToDoubleCodec.java | 2 +- .../jdbc/codec/FloatToDoubleCodec.java | 2 +- .../cassandra/jdbc/codec/IntToLongCodec.java | 2 +- .../cassandra/jdbc/codec/LongToIntCodec.java | 2 +- .../jdbc/codec/SmallintToIntCodec.java | 2 +- .../jdbc/codec/TimestampToLongCodec.java | 2 +- .../jdbc/codec/TinyintToIntCodec.java | 2 +- .../jdbc/codec/VarintToIntCodec.java | 2 +- .../{ => metadata}/MetadataResultSet.java | 5 +- .../jdbc/{ => metadata}/MetadataRow.java | 6 +- .../cassandra/jdbc/metadata/package-info.java | 19 +++ .../jdbc/{ => types}/AbstractJdbcType.java | 12 +- .../jdbc/{ => types}/AbstractJdbcUUID.java | 2 +- .../jdbc/{ => types}/DataTypeEnum.java | 10 +- .../cassandra/jdbc/{ => types}/JdbcAscii.java | 2 +- .../jdbc/{ => types}/JdbcBoolean.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcByte.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcBytes.java | 2 +- .../jdbc/{ => types}/JdbcCounterColumn.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcDate.java | 2 +- .../jdbc/{ => types}/JdbcDecimal.java | 4 +- .../jdbc/{ => types}/JdbcDouble.java | 2 +- .../jdbc/{ => types}/JdbcDuration.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcFloat.java | 2 +- .../jdbc/{ => types}/JdbcInetAddress.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcInt32.java | 2 +- .../jdbc/{ => types}/JdbcInteger.java | 2 +- .../jdbc/{ => types}/JdbcLexicalUUID.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcLong.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcOther.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcShort.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcTime.java | 2 +- .../jdbc/{ => types}/JdbcTimeUUID.java | 2 +- .../jdbc/{ => types}/JdbcTimestamp.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcTuple.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcUTF8.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcUUID.java | 2 +- .../cassandra/jdbc/{ => types}/JdbcUdt.java | 2 +- .../jdbc/{ => types}/MarshalException.java | 12 +- .../cassandra/jdbc/{ => types}/TypesMap.java | 2 +- .../cassandra/jdbc/types/package-info.java | 19 +++ .../jdbc/{ => utils}/ByteBufferUtil.java | 2 +- .../cassandra/jdbc/{ => utils}/Utils.java | 58 ++++---- .../cassandra/jdbc/ConnectionUnitTest.java | 12 +- .../cassandra/jdbc/DataSourceUnitTest.java | 1 + .../jdbc/MetadataResultSetsUnitTest.java | 1 + .../jdbc/PreparedStatementsUnitTest.java | 4 + .../data/cassandra/jdbc/UtilsUnitTest.java | 21 +-- .../codec/BigintToBigDecimalCodecTest.java | 4 +- .../jdbc/codec/DecimalToDoubleCodecTest.java | 2 +- .../jdbc/codec/FloatToDoubleCodecTest.java | 2 +- .../jdbc/codec/IntToLongCodecTest.java | 2 +- .../jdbc/codec/LongToIntCodecTest.java | 2 +- .../jdbc/codec/SmallintToIntCodecTest.java | 2 +- .../jdbc/codec/TimestampToLongCodecTest.java | 2 +- .../jdbc/codec/TinyintToIntCodecTest.java | 2 +- .../jdbc/codec/VarintToIntCodecTest.java | 2 +- 81 files changed, 336 insertions(+), 428 deletions(-) delete mode 100644 azure-build.yaml delete mode 100644 azure-release.yaml delete mode 100644 release-settings.xml rename src/main/java/com/ing/data/cassandra/jdbc/{ => metadata}/MetadataResultSet.java (92%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => metadata}/MetadataRow.java (99%) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/package-info.java rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/AbstractJdbcType.java (92%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/AbstractJdbcUUID.java (97%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/DataTypeEnum.java (96%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcAscii.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcBoolean.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcByte.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcBytes.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcCounterColumn.java (95%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcDate.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcDecimal.java (96%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcDouble.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcDuration.java (97%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcFloat.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcInetAddress.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcInt32.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcInteger.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcLexicalUUID.java (96%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcLong.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcOther.java (97%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcShort.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcTime.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcTimeUUID.java (96%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcTimestamp.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcTuple.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcUTF8.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcUUID.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/JdbcUdt.java (98%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/MarshalException.java (71%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => types}/TypesMap.java (99%) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/types/package-info.java rename src/main/java/com/ing/data/cassandra/jdbc/{ => utils}/ByteBufferUtil.java (99%) rename src/main/java/com/ing/data/cassandra/jdbc/{ => utils}/Utils.java (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c8825f..e92dbfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Changed +- Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages. + ## [4.9.0] - 2023-04-15 ### Added - Add non-JDBC standard [JSON support](https://cassandra.apache.org/doc/latest/cassandra/cql/json.html) with the diff --git a/azure-build.yaml b/azure-build.yaml deleted file mode 100644 index a739034..0000000 --- a/azure-build.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: 'Build pipeline' -trigger: none -# batch: true -# branches: -# exclude: -# - master - -pool: - vmImage: 'ubuntu-latest' - -variables: - MAVEN_CACHE_FOLDER: $(Pipeline.Workspace)/.m2/repository - MAVEN_OPTS: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' - -steps: - - task: Cache@2 - displayName: 'Cache Maven repository' - inputs: - key: 'mvn | "$(Agent.OS)" | **/pom.xml' - restoreKeys: | - mvn | "$(Agent.OS)" - mvn - path: $(MAVEN_CACHE_FOLDER) - - - task: Maven@3 - displayName: 'Maven build' - inputs: - mavenPomFile: 'pom.xml' - javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.8' - jdkArchitectureOption: 'x64' - publishJUnitResults: false - goals: 'package verify' - mavenOptions: '$(MAVEN_OPTS)' diff --git a/azure-release.yaml b/azure-release.yaml deleted file mode 100644 index a84d61c..0000000 --- a/azure-release.yaml +++ /dev/null @@ -1,136 +0,0 @@ -name: 'Release pipeline' -trigger: none -# batch: true -# branches: -# include: -# - master -# Never trigger a release from a pull request. -pr: none - -pool: - vmImage: 'ubuntu-latest' - -variables: - - group: SonatypeCredentials - - group: GPGKey - - name: MAVEN_CACHE_FOLDER - value: $(Pipeline.Workspace)/.m2/repository - - name: MAVEN_OPTS - value: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' - -steps: - - task: DownloadSecureFile@1 - displayName: 'Get GPG public key' - name: pubring - inputs: - secureFile: pubring.asc - - - task: DownloadSecureFile@1 - displayName: 'Get GPG private key' - name: secring - inputs: - secureFile: secring.asc - - - task: Bash@3 - displayName: 'Prepare GPG key to sign artifacts' - inputs: - targetType: 'inline' - script: | - echo $(tty) - ls -la $(pubring.secureFilePath) - ls -la $(secring.secureFilePath) - mkdir ~/.gnupg - chmod -R 700 ~/.gnupg - echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf - gpg --import $(pubring.secureFilePath) - gpg --batch --import $(secring.secureFilePath) - env: - # Needed for GPG to work on headless Azure agents - GPG_TTY: /dev/pts/0 - - - task: Cache@2 - displayName: 'Cache Maven repository' - inputs: - key: 'mvn | "$(Agent.OS)" | **/pom.xml' - restoreKeys: | - mvn | "$(Agent.OS)" - mvn - path: $(MAVEN_CACHE_FOLDER) - - - task: Maven@3 - displayName: 'Maven package and publish' - inputs: - mavenPomFile: 'pom.xml' - javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.8' - jdkArchitectureOption: 'x64' - publishJUnitResults: false - goals: 'deploy' - mavenOptions: '$(MAVEN_OPTS)' - options: '-s $(System.DefaultWorkingDirectory)/release-settings.xml -Prelease -Dgpg.passphrase=$(GPG_PASSPHRASE)' - env: - # Secrets must be extracted explicitly - OSSRH_USERNAME: $(OSSRH_USERNAME) - OSSRH_PASSWORD: $(OSSRH_PASSWORD) - GPG_PASSPHRASE: $(GPG_PASSPHRASE) - - - task: Bash@3 - displayName: 'Clean environment' - inputs: - targetType: 'inline' - script: | - rm -rf $(pubring.secureFilePath) - rm -rf $(secring.secureFilePath) - rm -rf ~/.gnupg - - - task: Maven@3 - displayName: 'Retrieve artifact version' - inputs: - effectivePomSkip: true - mavenPomFile: 'pom.xml' - javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.8' - jdkArchitectureOption: 'x64' - publishJUnitResults: false - goals: 'help:evaluate' - mavenOptions: '$(MAVEN_OPTS)' - options: '--log-file $(Pipeline.Workspace)/version.txt -Dexpression=project.version -q -DforceStdout' - - - task: Bash@3 - displayName: 'Store artifact version into a variable' - inputs: - targetType: 'inline' - workingDirectory: $(Pipeline.Workspace) - script: | - artifactVersion=$(cat version.txt) - echo "##vso[task.setvariable variable=artifactVersion]${artifactVersion}" - echo Built version: ${artifactVersion} - rm ./version.txt - - - task: Maven@3 - displayName: 'Maven build bundle' - inputs: - mavenPomFile: 'pom.xml' - javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.8' - jdkArchitectureOption: 'x64' - publishJUnitResults: false - goals: 'clean package' - mavenOptions: '$(MAVEN_OPTS)' - options: '-Pbundle' - - - task: GitHubRelease@0 - displayName: 'Create GitHub release' - inputs: - gitHubConnection: 'maximevw' - repositoryName: $(Build.Repository.Name) - action: create - target: $(Build.SourceVersion) - tagSource: manual - tag: 'v$(artifactVersion)' - title: $(artifactVersion) - releaseNotesSource: input - releaseNotes: 'See changes [here](https://github.com/ing-bank/cassandra-jdbc-wrapper/blob/master/CHANGELOG.md#ver---yyyy-MM-dd).' - assets: $(Build.SourcesDirectory)/target/cassandra-jdbc-wrapper-$(artifactVersion)-bundle.jar - isDraft: true - addChangeLog: false diff --git a/pom.xml b/pom.xml index 4c56141..9235298 100644 --- a/pom.xml +++ b/pom.xml @@ -1,50 +1,50 @@ - 4.0.0 + 4.0.0 - com.ing.data - cassandra-jdbc-wrapper + com.ing.data + cassandra-jdbc-wrapper 4.9.0 - jar + jar - Cassandra JDBC Wrapper - JDBC wrapper of the DataStax Java Driver for Apache Cassandra. + Cassandra JDBC Wrapper + JDBC wrapper of the DataStax Java Driver for Apache Cassandra. https://github.com/ing-bank/cassandra-jdbc-wrapper 2020 - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + ING Bank https://www.ing.com - + - maximevw + maximevw https://github.com/maximevw - Maxime Wiewiora - - developer - - - + Maxime Wiewiora + + developer + + + Alexander Dejanovski https://github.com/adejanovski - - developer - + + developer + Madhavan Sridharan @@ -117,9 +117,9 @@ 3.4.1 3.2.1 2.22.2 - + - + com.datastax.oss java-driver-core @@ -128,10 +128,10 @@ - org.apache.commons - commons-lang3 + org.apache.commons + commons-lang3 ${commons-lang3.version} - + @@ -247,18 +247,18 @@ ${lombok.version} test - + - + - - src/main/resources - true - - + + src/main/resources + true + + - + maven-clean-plugin @@ -343,11 +343,11 @@ - - + + - + release @@ -439,5 +439,5 @@ - + diff --git a/release-settings.xml b/release-settings.xml deleted file mode 100644 index d9276bb..0000000 --- a/release-settings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - ossrh - ${env.OSSRH_USERNAME} - ${env.OSSRH_PASSWORD} - - - diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java index da2342c..b8c81a6 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java @@ -30,8 +30,8 @@ import java.util.Map; import java.util.concurrent.Executor; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; /** * Provides a default implementation (returning a {@link SQLFeatureNotSupportedException}) to hold the unimplemented diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java index 2071c94..f58428d 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.type.DataType; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; @@ -37,8 +38,8 @@ import java.sql.Wrapper; import java.util.Map; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; /** * Provides a default implementation (returning a {@link SQLFeatureNotSupportedException}) to hold the unimplemented diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java index cfd146d..f847b4c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java @@ -27,8 +27,8 @@ import java.sql.SQLXML; import java.sql.Wrapper; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; /** * Provides a default implementation (returning a {@link SQLFeatureNotSupportedException}) to hold the unimplemented diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java index 4a373cd..f1f8a93 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java @@ -66,20 +66,20 @@ import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_CONCURRENCY; import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_HOLDABILITY; import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_TYPE; -import static com.ing.data.cassandra.jdbc.Utils.ALWAYS_AUTOCOMMIT; -import static com.ing.data.cassandra.jdbc.Utils.BAD_TIMEOUT; -import static com.ing.data.cassandra.jdbc.Utils.NO_TRANSACTIONS; -import static com.ing.data.cassandra.jdbc.Utils.PROTOCOL; -import static com.ing.data.cassandra.jdbc.Utils.TAG_ACTIVE_CQL_VERSION; -import static com.ing.data.cassandra.jdbc.Utils.TAG_COMPLIANCE_MODE; -import static com.ing.data.cassandra.jdbc.Utils.TAG_CONSISTENCY_LEVEL; -import static com.ing.data.cassandra.jdbc.Utils.TAG_CQL_VERSION; -import static com.ing.data.cassandra.jdbc.Utils.TAG_DATABASE_NAME; -import static com.ing.data.cassandra.jdbc.Utils.TAG_DEBUG; -import static com.ing.data.cassandra.jdbc.Utils.TAG_USER; -import static com.ing.data.cassandra.jdbc.Utils.WAS_CLOSED_CONN; -import static com.ing.data.cassandra.jdbc.Utils.createSubName; -import static com.ing.data.cassandra.jdbc.Utils.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.Utils.ALWAYS_AUTOCOMMIT; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_TRANSACTIONS; +import static com.ing.data.cassandra.jdbc.utils.Utils.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_ACTIVE_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_COMPLIANCE_MODE; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CONSISTENCY_LEVEL; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_DATABASE_NAME; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_DEBUG; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_CONN; +import static com.ing.data.cassandra.jdbc.utils.Utils.createSubName; +import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; /** * Cassandra connection: implementation class for {@link Connection} to create a JDBC connection to a Cassandra diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java index 9197320..3cad7e2 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy; +import com.ing.data.cassandra.jdbc.utils.Utils; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; @@ -28,19 +29,19 @@ import java.util.Properties; import java.util.logging.Logger; -import static com.ing.data.cassandra.jdbc.Utils.HOST_REQUIRED; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.Utils.PROTOCOL; -import static com.ing.data.cassandra.jdbc.Utils.TAG_CONSISTENCY_LEVEL; -import static com.ing.data.cassandra.jdbc.Utils.TAG_CQL_VERSION; -import static com.ing.data.cassandra.jdbc.Utils.TAG_DATABASE_NAME; -import static com.ing.data.cassandra.jdbc.Utils.TAG_LOCAL_DATACENTER; -import static com.ing.data.cassandra.jdbc.Utils.TAG_PASSWORD; -import static com.ing.data.cassandra.jdbc.Utils.TAG_PORT_NUMBER; -import static com.ing.data.cassandra.jdbc.Utils.TAG_SERVER_NAME; -import static com.ing.data.cassandra.jdbc.Utils.TAG_USER; -import static com.ing.data.cassandra.jdbc.Utils.createSubName; +import static com.ing.data.cassandra.jdbc.utils.Utils.HOST_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CONSISTENCY_LEVEL; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_DATABASE_NAME; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_LOCAL_DATACENTER; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PASSWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PORT_NUMBER; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_SERVER_NAME; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.Utils.createSubName; /** * Cassandra data source: implementation class for {@link DataSource} and {@link ConnectionPoolDataSource}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 130a965..9499c29 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -29,10 +29,10 @@ import java.util.Arrays; import java.util.List; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.Utils.getDriverProperty; -import static com.ing.data.cassandra.jdbc.Utils.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.Utils.parseVersion; /** * Cassandra database metadata: implementation class for {@link DatabaseMetaData}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java index 0d49718..9a3319e 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java @@ -32,12 +32,12 @@ import java.util.Map; import java.util.Properties; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.PROTOCOL; -import static com.ing.data.cassandra.jdbc.Utils.TAG_PASSWORD; -import static com.ing.data.cassandra.jdbc.Utils.TAG_USER; -import static com.ing.data.cassandra.jdbc.Utils.getDriverProperty; -import static com.ing.data.cassandra.jdbc.Utils.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PASSWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.Utils.parseVersion; /** * The Cassandra driver implementation. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java index f212e42..c1b8eb2 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java @@ -20,6 +20,12 @@ import com.datastax.oss.driver.api.core.type.ListType; import com.datastax.oss.driver.api.core.type.MapType; import com.datastax.oss.driver.api.core.type.SetType; +import com.ing.data.cassandra.jdbc.metadata.MetadataResultSet; +import com.ing.data.cassandra.jdbc.metadata.MetadataRow; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; +import com.ing.data.cassandra.jdbc.types.TypesMap; +import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayInputStream; @@ -55,16 +61,16 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_PRECISION; -import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_SCALE; -import static com.ing.data.cassandra.jdbc.Utils.BAD_FETCH_DIR; -import static com.ing.data.cassandra.jdbc.Utils.BAD_FETCH_SIZE; -import static com.ing.data.cassandra.jdbc.Utils.FORWARD_ONLY; -import static com.ing.data.cassandra.jdbc.Utils.MUST_BE_POSITIVE; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.Utils.VALID_LABELS; -import static com.ing.data.cassandra.jdbc.Utils.WAS_CLOSED_RS; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_DIR; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_SIZE; +import static com.ing.data.cassandra.jdbc.utils.Utils.FORWARD_ONLY; +import static com.ing.data.cassandra.jdbc.utils.Utils.MUST_BE_POSITIVE; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.VALID_LABELS; +import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_RS; /** * Cassandra metadata result set. This is an implementation of {@link ResultSet} for database metadata. @@ -165,6 +171,20 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas } } + /** + * Builds a new instance of Cassandra metadata result set from a {@link MetadataResultSet}. + * + * @param statement The statement. + * @param metadataResultSet The metadata result set from the Cassandra driver. + * @return A new instance of Cassandra metadata result set. + * @throws SQLException if a database access error occurs or this constructor is called with a closed + * {@link Statement}. + */ + public static CassandraMetadataResultSet buildFrom(final CassandraStatement statement, + final MetadataResultSet metadataResultSet) throws SQLException { + return new CassandraMetadataResultSet(statement, metadataResultSet); + } + private void populateColumns() { this.currentRow = this.rowsIterator.next(); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java index 8bcdf66..518ddf2 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java @@ -16,11 +16,14 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; +import com.ing.data.cassandra.jdbc.types.TypesMap; import java.sql.ParameterMetaData; import java.sql.SQLException; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; /** * Cassandra parameter metadata: implementation class for {@link ParameterMetaData}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java index 67351b7..7ae40de 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java @@ -26,6 +26,8 @@ import com.datastax.oss.driver.internal.core.type.DefaultSetType; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.fasterxml.jackson.core.JsonProcessingException; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; +import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -65,7 +67,7 @@ import java.util.UUID; import java.util.concurrent.CompletionStage; -import static com.ing.data.cassandra.jdbc.Utils.getObjectMapper; +import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; /** * Cassandra prepared statement: implementation class for {@link PreparedStatement}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index a709d7f..9a93a66 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -29,6 +29,10 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; import com.fasterxml.jackson.core.JsonProcessingException; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; +import com.ing.data.cassandra.jdbc.types.TypesMap; +import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -78,19 +82,19 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_PRECISION; -import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_SCALE; -import static com.ing.data.cassandra.jdbc.DataTypeEnum.fromCqlTypeName; -import static com.ing.data.cassandra.jdbc.DataTypeEnum.fromDataType; -import static com.ing.data.cassandra.jdbc.Utils.BAD_FETCH_DIR; -import static com.ing.data.cassandra.jdbc.Utils.BAD_FETCH_SIZE; -import static com.ing.data.cassandra.jdbc.Utils.FORWARD_ONLY; -import static com.ing.data.cassandra.jdbc.Utils.MUST_BE_POSITIVE; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.Utils.VALID_LABELS; -import static com.ing.data.cassandra.jdbc.Utils.WAS_CLOSED_RS; -import static com.ing.data.cassandra.jdbc.Utils.getObjectMapper; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; +import static com.ing.data.cassandra.jdbc.types.DataTypeEnum.fromCqlTypeName; +import static com.ing.data.cassandra.jdbc.types.DataTypeEnum.fromDataType; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_DIR; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_SIZE; +import static com.ing.data.cassandra.jdbc.utils.Utils.FORWARD_ONLY; +import static com.ing.data.cassandra.jdbc.utils.Utils.MUST_BE_POSITIVE; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.VALID_LABELS; +import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_RS; +import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; /** * Cassandra result set: implementation class for {@link java.sql.ResultSet}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java index c9ef629..63a8ead 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.internal.core.cql.MultiPageResultSet; import com.datastax.oss.driver.internal.core.cql.SinglePageResultSet; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.ing.data.cassandra.jdbc.utils.Utils; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java index cfa4dfd..3e7f852 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java @@ -15,6 +15,8 @@ package com.ing.data.cassandra.jdbc; +import com.ing.data.cassandra.jdbc.utils.Utils; + import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; @@ -29,7 +31,7 @@ import java.util.Properties; import java.util.Set; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; /** * Cassandra connection from a pool managed by a {@link PooledCassandraDataSource}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java index 0c3029c..9ae58ee 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java @@ -37,7 +37,7 @@ import java.sql.Timestamp; import java.util.Calendar; -import static com.ing.data.cassandra.jdbc.Utils.WAS_CLOSED_CONN; +import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_CONN; /** * Cassandra prepared statement executed in the context of a pool of connections managed in a diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java index c6c2280..dba9209 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java @@ -24,6 +24,10 @@ import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.ing.data.cassandra.jdbc.metadata.MetadataResultSet; +import com.ing.data.cassandra.jdbc.metadata.MetadataRow; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,9 +41,9 @@ import java.util.List; import java.util.Map; -import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_PRECISION; -import static com.ing.data.cassandra.jdbc.AbstractJdbcType.DEFAULT_SCALE; -import static com.ing.data.cassandra.jdbc.TypesMap.getTypeForComparator; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; +import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; import static java.sql.DatabaseMetaData.functionColumnIn; import static java.sql.DatabaseMetaData.functionReturn; import static java.sql.DatabaseMetaData.typeNullable; @@ -49,6 +53,7 @@ /** * Utility class to manage database metadata result sets ({@link CassandraMetadataResultSet} objects). */ +// TODO: split by families of metadata (table, columns, functions, ...) and move to metadata package. public final class MetadataResultSets { /** * Gets an instance of {@code MetadataResultSets}. @@ -148,7 +153,7 @@ public CassandraMetadataResultSet makeTableTypes(final CassandraStatement statem final ArrayList tableTypes = new ArrayList<>(); final MetadataRow row = new MetadataRow().addEntry(TABLE_TYPE, TABLE); tableTypes.add(row); - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(tableTypes)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(tableTypes)); } /** @@ -169,7 +174,7 @@ public CassandraMetadataResultSet makeCatalogs(final CassandraStatement statemen final ArrayList catalog = new ArrayList<>(); final MetadataRow row = new MetadataRow().addEntry(TABLE_CATALOG_SHORTNAME, statement.connection.getCatalog()); catalog.add(row); - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(catalog)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(catalog)); } /** @@ -210,7 +215,7 @@ public CassandraMetadataResultSet makeSchemas(final CassandraStatement statement } } - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(schemas)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); } /** @@ -281,7 +286,7 @@ public CassandraMetadataResultSet makeTables(final CassandraStatement statement, } } - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(schemas)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); } /** @@ -453,7 +458,7 @@ public CassandraMetadataResultSet makeColumns(final CassandraStatement statement } } - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(schemas)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); } /** @@ -552,7 +557,7 @@ public CassandraMetadataResultSet makeIndexes(final CassandraStatement statement } } - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(schemas)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); } /** @@ -611,7 +616,7 @@ public CassandraMetadataResultSet makePrimaryKeys(final CassandraStatement state } } - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(schemas)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); } /** @@ -701,7 +706,7 @@ public CassandraMetadataResultSet makeUDTs(final CassandraStatement statement, f // Results should all have the same DATA_TYPE and TYPE_CAT so just sort them by TYPE_SCHEM then TYPE_NAME. udtsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TYPE_SCHEMA)) .thenComparing(row -> ((MetadataRow) row).getString(TYPE_NAME))); - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(udtsRows)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(udtsRows)); } /** @@ -796,7 +801,7 @@ public CassandraMetadataResultSet makeTypes(final CassandraStatement statement) } // Sort results by DATA_TYPE. types.sort(Comparator.comparing(row -> Integer.valueOf(row.getString(DATA_TYPE)))); - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(types)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(types)); } /** @@ -877,7 +882,7 @@ public CassandraMetadataResultSet makeFunctions(final CassandraStatement stateme // here SPECIFIC_NAME is equal to FUNCTION_NAME). functionsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA)) .thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME))); - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(functionsRows)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(functionsRows)); } /** @@ -1041,6 +1046,6 @@ public CassandraMetadataResultSet makeFunctionColumns(final CassandraStatement s .thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME)) .thenComparing(row -> ((MetadataRow) row).getString(SPECIFIC_NAME)) .thenComparing(row -> Integer.valueOf(((MetadataRow) row).getString(ORDINAL_POSITION)))); - return new CassandraMetadataResultSet(statement, new MetadataResultSet().setRows(functionParamsRows)); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(functionParamsRows)); } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java b/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java index 4ff91a9..ab8bf2f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java @@ -29,7 +29,7 @@ import java.util.HashSet; import java.util.Set; -import static com.ing.data.cassandra.jdbc.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; /** * Pooled Cassandra data source: implementation class for {@link DataSource} and {@link ConnectionEventListener}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java b/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java index c0e3d8f..9fb04c9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java @@ -40,6 +40,7 @@ import com.ing.data.cassandra.jdbc.codec.TimestampToLongCodec; import com.ing.data.cassandra.jdbc.codec.TinyintToIntCodec; import com.ing.data.cassandra.jdbc.codec.VarintToIntCodec; +import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; @@ -61,11 +62,11 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_KEYSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_KEYSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_TRUSTSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.SSL_CONFIG_FAILED; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.SSL_CONFIG_FAILED; /** * Holds a {@link Session} shared among multiple {@link CassandraConnection} objects. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java index 9ab37ef..229184c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java @@ -19,7 +19,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; /** * Provides a minimal implementation for the methods {@link TypeCodec#parse(String)} and diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java index 26d3a97..423ca93 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.math.BigDecimal; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java index c48edbe..1aa0b46 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java index ff291b8..a8298e1 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java index 32a720d..5b56ae2 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java index bed57c2..51b55a8 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java index 39e1471..7539971 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java index a202ce9..18b13e4 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java index 11e2291..e31ad8d 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java index cfa0bce..c89f6a1 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java @@ -21,7 +21,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; import edu.umd.cs.findbugs.annotations.NonNull; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import java.nio.ByteBuffer; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java similarity index 92% rename from src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSet.java rename to src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java index b8a8a53..b59140c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java @@ -13,7 +13,10 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.metadata; + +import com.ing.data.cassandra.jdbc.ColumnDefinitions; +import com.ing.data.cassandra.jdbc.MetadataResultSets; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataRow.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataRow.java similarity index 99% rename from src/main/java/com/ing/data/cassandra/jdbc/MetadataRow.java rename to src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataRow.java index 1157f67..2b0992b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataRow.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataRow.java @@ -13,10 +13,12 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.metadata; import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.type.DataTypes; +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.ColumnDefinitions; import com.ing.data.cassandra.jdbc.ColumnDefinitions.Definition; import org.apache.commons.lang3.StringUtils; @@ -44,7 +46,7 @@ public class MetadataRow { // The 'names' map contains the metadata names as keys and the position of the corresponding value in the list // 'entries' as values. Each metadata key is a column of the metadata row. private final HashMap names; - private final ArrayList definitions; + private final ArrayList definitions; /** * Constructor. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/package-info.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/package-info.java new file mode 100644 index 0000000..4ad2311 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/package-info.java @@ -0,0 +1,19 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains the classes to manage the database metadata result sets. + */ +package com.ing.data.cassandra.jdbc.metadata; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractJdbcType.java b/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcType.java similarity index 92% rename from src/main/java/com/ing/data/cassandra/jdbc/AbstractJdbcType.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcType.java index 1fd4a80..3215724 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractJdbcType.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcType.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; /** * Abstract class providing description about the JDBC equivalent of a CQL type. @@ -22,8 +22,14 @@ */ public abstract class AbstractJdbcType { - static final int DEFAULT_SCALE = 0; - static final int DEFAULT_PRECISION = -1; + /** + * The default scale value for JDBC types. + */ + public static final int DEFAULT_SCALE = 0; + /** + * The default precision value for JDBC types. + */ + public static final int DEFAULT_PRECISION = -1; /** * Gets whether the values of this type are case-sensitive (if applicable). diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractJdbcUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java similarity index 97% rename from src/main/java/com/ing/data/cassandra/jdbc/AbstractJdbcUUID.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java index 100a544..8261c34 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractJdbcUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java similarity index 96% rename from src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index 65331f0..bfe0300 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.data.TupleValue; @@ -70,9 +70,9 @@ public enum DataTypeEnum { VARINT(DataType.VARINT, BigInteger.class, cqlName(DataTypes.VARINT)); private static final Map CQL_DATATYPE_TO_DATATYPE; + public final Class javaType; + public final String cqlType; final int protocolId; - final Class javaType; - final String cqlType; static { CQL_DATATYPE_TO_DATATYPE = new HashMap<>(); @@ -102,7 +102,7 @@ public enum DataTypeEnum { * @param cqlTypeName The CQL type name. * @return The enumeration item corresponding to the given CQL type name. */ - static DataTypeEnum fromCqlTypeName(final String cqlTypeName) { + public static DataTypeEnum fromCqlTypeName(final String cqlTypeName) { // Manage user-defined types (e.g. "UDT(xxx)") if (cqlTypeName.startsWith(UDT.cqlType)) { return UDT; @@ -122,7 +122,7 @@ static DataTypeEnum fromCqlTypeName(final String cqlTypeName) { * @param dataType The CQL data type. * @return The enumeration item corresponding to the given CQL data type. */ - static DataTypeEnum fromDataType(final com.datastax.oss.driver.api.core.type.DataType dataType) { + public static DataTypeEnum fromDataType(final com.datastax.oss.driver.api.core.type.DataType dataType) { if (dataType instanceof UserDefinedType) { return UDT; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcAscii.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcAscii.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcAscii.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcAscii.java index 0701d23..547599a 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcAscii.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcAscii.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcBoolean.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcBoolean.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcBoolean.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcBoolean.java index 265abdb..937673b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcBoolean.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcBoolean.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.nio.ByteBuffer; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcByte.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcByte.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcByte.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcByte.java index 7947603..51fb3ba 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcByte.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcByte.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcBytes.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcBytes.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcBytes.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcBytes.java index 9e91b77..afca0d9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcBytes.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcBytes.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcCounterColumn.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcCounterColumn.java similarity index 95% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcCounterColumn.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcCounterColumn.java index 70abae1..97c9f75 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcCounterColumn.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcCounterColumn.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; /** * JDBC description of {@code COUNTER} CQL type (corresponding Java type: {@link Long}). diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDate.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDate.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcDate.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDate.java index 13e6c05..bb61641 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDate.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDate.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDecimal.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java similarity index 96% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcDecimal.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java index 5100d83..4290355 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDecimal.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java @@ -13,13 +13,13 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.sql.Types; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; /** * JDBC description of {@code DECIMAL} CQL type (corresponding Java type: {@link BigDecimal}). diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDouble.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDouble.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcDouble.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDouble.java index e116d01..a79e861 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDouble.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDouble.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDuration.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDuration.java similarity index 97% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcDuration.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDuration.java index f614509..9cbb7b5 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcDuration.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDuration.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.CqlDuration; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcFloat.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcFloat.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcFloat.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcFloat.java index 7649118..bd334af 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcFloat.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcFloat.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcInetAddress.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInetAddress.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcInetAddress.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInetAddress.java index 9420568..043dd3b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcInetAddress.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInetAddress.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.net.InetAddress; import java.net.UnknownHostException; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcInt32.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInt32.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcInt32.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInt32.java index f1b8fe5..5bbffee 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcInt32.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInt32.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcInteger.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInteger.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcInteger.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInteger.java index bec6baf..10b6645 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcInteger.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcInteger.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.math.BigInteger; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcLexicalUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java similarity index 96% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcLexicalUUID.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java index 3c379d9..8988d46 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcLexicalUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcLong.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLong.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcLong.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLong.java index 7e5dfe7..43f546f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcLong.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLong.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcOther.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcOther.java similarity index 97% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcOther.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcOther.java index 7025834..699a046 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcOther.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcOther.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcShort.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcShort.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcShort.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcShort.java index 32c9a86..eb73107 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcShort.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcShort.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTime.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTime.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcTime.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTime.java index 8962677..45fff5c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTime.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTime.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Time; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTimeUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java similarity index 96% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcTimeUUID.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java index 96df6e9..0e5dde0 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTimeUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTimestamp.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimestamp.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcTimestamp.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimestamp.java index 7221b77..e1358a8 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTimestamp.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimestamp.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Timestamp; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTuple.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcTuple.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java index ce45038..cfe5ec2 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcTuple.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.TupleValue; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcUTF8.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUTF8.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcUTF8.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUTF8.java index 6b933ae..3cca641 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcUTF8.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUTF8.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.sql.Types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcUUID.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java index 9f7062a..94aa5dc 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/JdbcUdt.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java similarity index 98% rename from src/main/java/com/ing/data/cassandra/jdbc/JdbcUdt.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java index b13842c..3540a17 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/JdbcUdt.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.UdtValue; import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MarshalException.java b/src/main/java/com/ing/data/cassandra/jdbc/types/MarshalException.java similarity index 71% rename from src/main/java/com/ing/data/cassandra/jdbc/MarshalException.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/MarshalException.java index cdd8d5a..00916c6 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MarshalException.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/MarshalException.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; /** * An exception thrown during a marshalling process. @@ -27,14 +27,4 @@ public class MarshalException extends RuntimeException { public MarshalException(final String message) { super(message); } - - /** - * Constructs a {@code MarshalException} with a given message and cause. - * - * @param message A message describing the exception. - * @param cause The underlying cause of the exception. - */ - public MarshalException(final String message, final Throwable cause) { - super(message, cause); - } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java b/src/main/java/com/ing/data/cassandra/jdbc/types/TypesMap.java similarity index 99% rename from src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java rename to src/main/java/com/ing/data/cassandra/jdbc/types/TypesMap.java index 8cb1f82..8512cf3 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/TypesMap.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.types; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/package-info.java b/src/main/java/com/ing/data/cassandra/jdbc/types/package-info.java new file mode 100644 index 0000000..1b5f27f --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/package-info.java @@ -0,0 +1,19 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains the codec classes to handle JDBC types and the correspondence with the CQL types. + */ +package com.ing.data.cassandra.jdbc.types; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/ByteBufferUtil.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/ByteBufferUtil.java similarity index 99% rename from src/main/java/com/ing/data/cassandra/jdbc/ByteBufferUtil.java rename to src/main/java/com/ing/data/cassandra/jdbc/utils/ByteBufferUtil.java index 176d156..ffa13a3 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/ByteBufferUtil.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/ByteBufferUtil.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.utils; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/Utils.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java similarity index 91% rename from src/main/java/com/ing/data/cassandra/jdbc/Utils.java rename to src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java index 131ff8e..f835fb9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/Utils.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.ing.data.cassandra.jdbc; +package com.ing.data.cassandra.jdbc.utils; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverOption; @@ -192,45 +192,45 @@ public final class Utils { */ public static final String NULL_KEYWORD = "NULL"; - static final String WAS_CLOSED_CONN = "Method was called on a closed Connection."; - static final String WAS_CLOSED_STMT = "Method was called on a closed Statement."; - static final String WAS_CLOSED_RS = "Method was called on a closed ResultSet."; - static final String NO_INTERFACE = "No object was found that matched the provided interface: %s"; - static final String NO_TRANSACTIONS = "The Cassandra implementation does not support transactions."; - static final String ALWAYS_AUTOCOMMIT = "The Cassandra implementation is always in auto-commit mode."; - static final String BAD_TIMEOUT = "The timeout value was less than zero."; - static final String NOT_SUPPORTED = "The Cassandra implementation does not support this method."; - static final String NO_GEN_KEYS = + public static final String WAS_CLOSED_CONN = "Method was called on a closed Connection."; + public static final String WAS_CLOSED_STMT = "Method was called on a closed Statement."; + public static final String WAS_CLOSED_RS = "Method was called on a closed ResultSet."; + public static final String NO_INTERFACE = "No object was found that matched the provided interface: %s"; + public static final String NO_TRANSACTIONS = "The Cassandra implementation does not support transactions."; + public static final String ALWAYS_AUTOCOMMIT = "The Cassandra implementation is always in auto-commit mode."; + public static final String BAD_TIMEOUT = "The timeout value was less than zero."; + public static final String NOT_SUPPORTED = "The Cassandra implementation does not support this method."; + public static final String NO_GEN_KEYS = "The Cassandra implementation does not currently support returning generated keys."; - static final String NO_MULTIPLE = + public static final String NO_MULTIPLE = "The Cassandra implementation does not currently support multiple open Result Sets."; - static final String NO_RESULT_SET = + public static final String NO_RESULT_SET = "No ResultSet returned from the CQL statement passed in an 'executeQuery()' method."; - static final String BAD_KEEP_RS = + public static final String BAD_KEEP_RS = "The argument for keeping the current result set: %s is not a valid value."; - static final String BAD_TYPE_RS = "The argument for result set type: %s is not a valid value."; - static final String BAD_CONCURRENCY_RS = + public static final String BAD_TYPE_RS = "The argument for result set type: %s is not a valid value."; + public static final String BAD_CONCURRENCY_RS = "The argument for result set concurrency: %s is not a valid value."; - static final String BAD_HOLD_RS = + public static final String BAD_HOLD_RS = "The argument for result set holdability: %s is not a valid value."; - static final String BAD_FETCH_DIR = "Fetch direction value of: %s is illegal."; - static final String BAD_AUTO_GEN = "Auto key generation value of: %s is illegal."; - static final String BAD_FETCH_SIZE = "Fetch size of: %s rows may not be negative."; - static final String MUST_BE_POSITIVE = + public static final String BAD_FETCH_DIR = "Fetch direction value of: %s is illegal."; + public static final String BAD_AUTO_GEN = "Auto key generation value of: %s is illegal."; + public static final String BAD_FETCH_SIZE = "Fetch size of: %s rows may not be negative."; + public static final String MUST_BE_POSITIVE = "Index must be a positive number less or equal the count of returned columns: %d"; - static final String VALID_LABELS = "Name provided was not in the list of valid column labels: %s"; - static final String HOST_IN_URL = + public static final String VALID_LABELS = "Name provided was not in the list of valid column labels: %s"; + public static final String HOST_IN_URL = "Connection url must specify a host, e.g. jdbc:cassandra://localhost:9042/keyspace"; - static final String HOST_REQUIRED = "A 'host' name is required to build a Connection."; - static final String BAD_KEYSPACE = + public static final String HOST_REQUIRED = "A 'host' name is required to build a Connection."; + public static final String BAD_KEYSPACE = "Keyspace names must be composed of alphanumerics and underscores (parsed: '%s')."; - static final String URI_IS_SIMPLE = + public static final String URI_IS_SIMPLE = "Connection url may only include host, port, and keyspace, consistency and version option, e.g. " + "jdbc:cassandra://localhost:9042/keyspace?version=3.0.0&consistency=ONE"; - static final String SECURECONENCTBUNDLE_REQUIRED = "A 'secureconnectbundle' parameter is required."; - static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; - static final String MALFORMED_URL = "The string '%s' is not a valid URL."; - static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; + public static final String SECURECONENCTBUNDLE_REQUIRED = "A 'secureconnectbundle' parameter is required."; + public static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; + public static final String MALFORMED_URL = "The string '%s' is not a valid URL."; + public static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; static final Logger LOG = LoggerFactory.getLogger(Utils.class); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java index 9a498d9..fe5601c 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java @@ -56,12 +56,12 @@ import java.util.Optional; import static com.ing.data.cassandra.jdbc.SessionHolder.URL_KEY; -import static com.ing.data.cassandra.jdbc.Utils.BAD_TIMEOUT; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_KEYSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_KEYSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.JSSE_TRUSTSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.Utils.SSL_CONFIG_FAILED; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.Utils.SSL_CONFIG_FAILED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java index 7b45a50..f6c5472 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java @@ -13,6 +13,7 @@ */ package com.ing.data.cassandra.jdbc; +import com.ing.data.cassandra.jdbc.utils.Utils; import org.junit.jupiter.api.Test; import javax.sql.DataSource; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index a404f18..842e8f6 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -14,6 +14,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.slf4j.Logger; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/PreparedStatementsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/PreparedStatementsUnitTest.java index 2f5a50a..80997c3 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/PreparedStatementsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/PreparedStatementsUnitTest.java @@ -17,6 +17,10 @@ import com.datastax.oss.driver.api.core.data.CqlDuration; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.type.DataTypes; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; +import com.ing.data.cassandra.jdbc.types.JdbcAscii; +import com.ing.data.cassandra.jdbc.types.JdbcBoolean; +import com.ing.data.cassandra.jdbc.types.JdbcInt32; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.slf4j.Logger; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java index 415a62e..4fe096b 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java @@ -15,6 +15,7 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverOption; +import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -31,16 +32,16 @@ import java.util.Properties; import java.util.stream.Stream; -import static com.ing.data.cassandra.jdbc.Utils.BAD_KEYSPACE; -import static com.ing.data.cassandra.jdbc.Utils.DEFAULT_PORT; -import static com.ing.data.cassandra.jdbc.Utils.HOST_IN_URL; -import static com.ing.data.cassandra.jdbc.Utils.HOST_REQUIRED; -import static com.ing.data.cassandra.jdbc.Utils.SECURECONENCTBUNDLE_REQUIRED; -import static com.ing.data.cassandra.jdbc.Utils.TAG_PORT_NUMBER; -import static com.ing.data.cassandra.jdbc.Utils.TAG_SERVER_NAME; -import static com.ing.data.cassandra.jdbc.Utils.URI_IS_SIMPLE; -import static com.ing.data.cassandra.jdbc.Utils.getDriverProperty; -import static com.ing.data.cassandra.jdbc.Utils.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_KEYSPACE; +import static com.ing.data.cassandra.jdbc.utils.Utils.DEFAULT_PORT; +import static com.ing.data.cassandra.jdbc.utils.Utils.HOST_IN_URL; +import static com.ing.data.cassandra.jdbc.utils.Utils.HOST_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.Utils.SECURECONENCTBUNDLE_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PORT_NUMBER; +import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_SERVER_NAME; +import static com.ing.data.cassandra.jdbc.utils.Utils.URI_IS_SIMPLE; +import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.Utils.parseVersion; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java index 19afb34..a6df575 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java @@ -16,7 +16,7 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import com.ing.data.cassandra.jdbc.ByteBufferUtil; +import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -28,7 +28,7 @@ import java.nio.ByteBuffer; import java.util.stream.Stream; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java index f7a1f5d..bdb2bf7 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java index e5ee998..9eb1a33 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java index af7cb10..1232caf 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java index 8b945f8..223f23d 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java index a0d2bc5..9ad2610 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java index fe968cb..6652589 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; import java.time.Instant; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java index 5a80e6d..6a7b0fc 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java index 2a7067f..5509125 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; From 91a822bb4dc23d75380c8ffb78128a6d2f7d539b Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 21 May 2023 16:25:05 +0200 Subject: [PATCH 02/21] Update dependencies - Apache Commons IO - Jackson dependencies - JUnit 5 & Testcontainers - some Maven plugins --- CHANGELOG.md | 2 ++ pom.xml | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e92dbfc..33b1337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased ### Changed +- Update Apache Commons IO to version 2.12.0. +- Update Jackson dependencies to version 2.15.1. - Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages. ## [4.9.0] - 2023-04-15 diff --git a/pom.xml b/pom.xml index 9235298..c9e3e02 100644 --- a/pom.xml +++ b/pom.xml @@ -94,27 +94,27 @@ 9.3 2.9.3 4.4 - 2.11.0 + 2.12.0 3.12.0 4.15.0 - 2.14.2 + 2.15.1 2.2 - 5.9.2 - 1.9.2 + 5.9.3 + 1.9.3 1.18.26 3.12.4 1.7.36 - 1.18.0 + 1.18.1 - 3.2.0 + 3.2.2 3.2.0 - 3.10.1 - 3.1.0 - 3.0.1 + 3.11.0 + 3.3.0 + 3.1.0 3.4.1 - 3.3.0 - 3.4.1 + 3.3.1 + 3.5.0 3.2.1 2.22.2 From b3801b8d85d7880cb872a30a2b67e3e5fc4af7ff Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 12 Aug 2023 16:17:09 +0200 Subject: [PATCH 03/21] Refactoring of MetadataResultSets for tables --- .../jdbc/CassandraDatabaseMetaData.java | 16 +- .../cassandra/jdbc/CassandraStatement.java | 10 ++ .../cassandra/jdbc/MetadataResultSets.java | 96 ------------ .../AbstractMetadataResultSetBuilder.java | 147 ++++++++++++++++++ .../TableMetadataResultSetBuilder.java | 128 +++++++++++++++ .../jdbc/MetadataResultSetsUnitTest.java | 12 +- ...tractMetadataResultSetBuilderUnitTest.java | 107 +++++++++++++ .../utils/TestMetadataResultSetBuilder.java | 27 ++++ 8 files changed, 433 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/utils/TestMetadataResultSetBuilder.java diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 9499c29..39f309f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; @@ -755,26 +756,23 @@ public ResultSet getTablePrivileges(final String catalog, final String schemaPat @Override public ResultSet getTableTypes() throws SQLException { checkStatementClosed(); - return MetadataResultSets.INSTANCE.makeTableTypes(this.statement); + return new TableMetadataResultSetBuilder(this.statement).buildTableTypes(); } @Override public ResultSet getTables(final String catalog, final String schemaPattern, final String tableNamePattern, final String[] types) throws SQLException { + // Note: only TABLE or null are taken into account for types parameter here since Cassandra only supports + // TABLE type. boolean askingForTable = types == null; if (types != null) { - for (final String typeName : types) { - if (MetadataResultSets.TABLE.equals(typeName)) { - askingForTable = true; - break; - } - } + askingForTable = Arrays.asList(types).contains(MetadataResultSets.TABLE); } + // Only null or the current catalog (i.e. cluster) name are supported. if ((catalog == null || catalog.equals(this.connection.getCatalog())) && askingForTable) { checkStatementClosed(); - return MetadataResultSets.INSTANCE.makeTables(this.statement, schemaPattern, tableNamePattern); + return new TableMetadataResultSetBuilder(this.statement).buildTables(schemaPattern, tableNamePattern); } - return CassandraResultSet.EMPTY_RESULT_SET; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java index 63a8ead..0141c93 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java @@ -452,6 +452,16 @@ public Connection getConnection() throws SQLException { return this.connection; } + /** + * Retrieves the {@link CassandraConnection} object that produced this {@link Statement} object. + * + * @return The Cassandra connection that produced this statement. + * @throws SQLException if a database access error occurs or this method is called on a closed {@link Statement}. + */ + public CassandraConnection getCassandraConnection() throws SQLException { + return (CassandraConnection) getConnection(); + } + @Override public ConsistencyLevel getConsistencyLevel() { return this.consistencyLevel; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java index dba9209..9a72b3d 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java @@ -60,7 +60,6 @@ public final class MetadataResultSets { */ public static final MetadataResultSets INSTANCE = new MetadataResultSets(); - static final String CQL_OPTION_COMMENT = "comment"; static final String ASC_OR_DESC = "ASC_OR_DESC"; static final String AUTO_INCREMENT = "AUTO_INCREMENT"; static final String BASE_TYPE = "BASE_TYPE"; @@ -103,14 +102,12 @@ public final class MetadataResultSets { static final String PRECISION = "PRECISION"; static final String PRIMARY_KEY_NAME = "PK_NAME"; static final String RADIX = "RADIX"; - static final String REF_GENERATION = "REF_GENERATION"; static final String REMARKS = "REMARKS"; static final String SCALE = "SCALE"; static final String SCOPE_CATALOG = "SCOPE_CATALOG"; static final String SCOPE_SCHEMA = "SCOPE_SCHEMA"; static final String SCOPE_TABLE = "SCOPE_TABLE"; static final String SEARCHABLE = "SEARCHABLE"; - static final String SELF_REFERENCING_COL_NAME = "SELF_REFERENCING_COL_NAME"; static final String SOURCE_DATA_TYPE = "SOURCE_DATA_TYPE"; static final String SPECIFIC_NAME = "SPECIFIC_NAME"; static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; @@ -120,7 +117,6 @@ public final class MetadataResultSets { static final String TABLE_CATALOG = "TABLE_CATALOG"; static final String TABLE_NAME = "TABLE_NAME"; static final String TABLE_SCHEMA = "TABLE_SCHEM"; - static final String TABLE_TYPE = "TABLE_TYPE"; static final String TYPE = "TYPE"; static final String TYPE_CATALOG = "TYPE_CAT"; static final String TYPE_NAME = "TYPE_NAME"; @@ -135,27 +131,6 @@ private MetadataResultSets() { // Private constructor to hide the public one. } - /** - * Builds a valid result set of the table types available in Cassandra database. This method is used to implement - * the method {@link DatabaseMetaData#getTableTypes()}. - *

- * The columns of this result set are: - *

    - *
  1. TABLE_TYPE String => table type: always {@value TABLE}.
  2. - *
- *

- * - * @param statement The statement. - * @return A valid result set for implementation of {@link DatabaseMetaData#getTableTypes()}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeTableTypes(final CassandraStatement statement) throws SQLException { - final ArrayList tableTypes = new ArrayList<>(); - final MetadataRow row = new MetadataRow().addEntry(TABLE_TYPE, TABLE); - tableTypes.add(row); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(tableTypes)); - } - /** * Builds a valid result set of the catalog names available in this Cassandra database. This method is used to * implement the method {@link DatabaseMetaData#getCatalogs()}. @@ -218,77 +193,6 @@ public CassandraMetadataResultSet makeSchemas(final CassandraStatement statement return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); } - /** - * Builds a valid result set of the description of the tables available in the given catalog (Cassandra cluster). - * This method is used to implement the method {@link DatabaseMetaData#getTables(String, String, String, String[])}. - *

- * The columns of this result set are: - *

    - *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name - * (if available).
  2. - *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is - * member of.
  4. - *
  5. TABLE_NAME String => table name.
  6. - *
  7. TABLE_TYPE String => table type: always {@value TABLE} here.
  8. - *
  9. REMARKS String => explanatory comment on the table.
  10. - *
  11. TYPE_CAT String => the types catalog: always {@code null} here.
  12. - *
  13. TYPE_SCHEM String => the types schema: always {@code null} here.
  14. - *
  15. TYPE_NAME String => type name: always {@code null} here.
  16. - *
  17. SELF_REFERENCING_COL_NAME String => name of the designated "identifier" column of a typed - * table: always {@code null} here.
  18. - *
  19. REF_GENERATION String => specifies how values in {@code SELF_REFERENCING_COL_NAME} are - * created: always {@code null} here.
  20. - *
- *

- * - * @param statement The statement. - * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the database; - * {@code ""} retrieves those without a schema and {@code null} means that the schema name - * should not be used to narrow down the search. - * @param tableNamePattern A table name pattern. It must match the table name as it is stored in the database. - * @return A valid result set for implementation of - * {@link DatabaseMetaData#getTables(String, String, String, String[])}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeTables(final CassandraStatement statement, final String schemaPattern, - final String tableNamePattern) throws SQLException { - final ArrayList schemas = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - String schemaNamePattern = schemaPattern; - if (WILDCARD_CHAR.equals(schemaPattern)) { - schemaNamePattern = keyspaceMetadata.getName().asInternal(); - } - if (schemaNamePattern == null || schemaNamePattern.equals(keyspaceMetadata.getName().asInternal())) { - final Map tables = keyspaceMetadata.getTables(); - - for (final Map.Entry table : tables.entrySet()) { - final TableMetadata tableMetadata = table.getValue(); - if (WILDCARD_CHAR.equals(tableNamePattern) || tableNamePattern == null - || tableNamePattern.equals(tableMetadata.getName().asInternal())) { - final MetadataRow row = new MetadataRow() - .addEntry(TABLE_CATALOG_SHORTNAME, statement.connection.getCatalog()) - .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) - .addEntry(TABLE_TYPE, TABLE) - .addEntry(REMARKS, tableMetadata.getOptions() - .get(CqlIdentifier.fromCql(CQL_OPTION_COMMENT)).toString()) - .addEntry(TYPE_CATALOG, null) - .addEntry(TYPE_SCHEMA, null) - .addEntry(TYPE_NAME, null) - .addEntry(SELF_REFERENCING_COL_NAME, null) - .addEntry(REF_GENERATION, null); - schemas.add(row); - } - } - } - } - - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); - } - /** * Builds a valid result set of the description of the table columns available in the given catalog (Cassandra * cluster). This method is used to implement the method diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java new file mode 100644 index 0000000..7334efd --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java @@ -0,0 +1,147 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.ing.data.cassandra.jdbc.CassandraConnection; +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; +import org.apache.commons.lang3.StringUtils; + +import java.sql.SQLException; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Abstract class to implement for each utility class building database metadata result sets + * ({@link CassandraMetadataResultSet} objects). + */ +public abstract class AbstractMetadataResultSetBuilder { + + static final String CQL_OPTION_COMMENT = "comment"; + static final String ASC_OR_DESC = "ASC_OR_DESC"; + static final String AUTO_INCREMENT = "AUTO_INCREMENT"; + static final String BASE_TYPE = "BASE_TYPE"; + static final String BUFFER_LENGTH = "BUFFER_LENGTH"; + static final String CARDINALITY = "CARDINALITY"; + static final String CASE_SENSITIVE = "CASE_SENSITIVE"; + static final String CHAR_OCTET_LENGTH = "CHAR_OCTET_LENGTH"; + static final String CLASS_NAME = "CLASS_NAME"; + static final String COLUMN_DEFAULT = "COLUMN_DEF"; + static final String COLUMN_NAME = "COLUMN_NAME"; + static final String COLUMN_SIZE = "COLUMN_SIZE"; + static final String COLUMN_TYPE = "COLUMN_TYPE"; + static final String CREATE_PARAMS = "CREATE_PARAMS"; + static final String DATA_TYPE = "DATA_TYPE"; + static final String DECIMAL_DIGITS = "DECIMAL_DIGITS"; + static final String FILTER_CONDITION = "FILTER_CONDITION"; + static final String FIXED_PRECISION_SCALE = "FIXED_PREC_SCALE"; + static final String FUNCTION_CATALOG = "FUNCTION_CAT"; + static final String FUNCTION_NAME = "FUNCTION_NAME"; + static final String FUNCTION_SCHEMA = "FUNCTION_SCHEM"; + static final String FUNCTION_TYPE = "FUNCTION_TYPE"; + static final String INDEX_NAME = "INDEX_NAME"; + static final String INDEX_QUALIFIER = "INDEX_QUALIFIER"; + static final String IS_AUTOINCREMENT = "IS_AUTOINCREMENT"; + static final String IS_GENERATED_COLUMN = "IS_GENERATEDCOLUMN"; + static final String IS_NULLABLE = "IS_NULLABLE"; + static final String KEY_SEQ = "KEY_SEQ"; + static final String LENGTH = "LENGTH"; + static final String LITERAL_PREFIX = "LITERAL_PREFIX"; + static final String LITERAL_SUFFIX = "LITERAL_SUFFIX"; + static final String LOCALIZED_TYPE_NAME = "LOCAL_TYPE_NAME"; + static final String MAXIMUM_SCALE = "MAXIMUM_SCALE"; + static final String MINIMUM_SCALE = "MINIMUM_SCALE"; + static final String NO_VALUE = "NO"; + static final String NON_UNIQUE = "NON_UNIQUE"; + static final String NULLABLE = "NULLABLE"; + static final String NUM_PRECISION_RADIX = "NUM_PREC_RADIX"; + static final String ORDINAL_POSITION = "ORDINAL_POSITION"; + static final String PAGES = "PAGES"; + static final String PRECISION = "PRECISION"; + static final String PRIMARY_KEY_NAME = "PK_NAME"; + static final String RADIX = "RADIX"; + static final String REF_GENERATION = "REF_GENERATION"; + static final String REMARKS = "REMARKS"; + static final String SCALE = "SCALE"; + static final String SCOPE_CATALOG = "SCOPE_CATALOG"; + static final String SCOPE_SCHEMA = "SCOPE_SCHEMA"; + static final String SCOPE_TABLE = "SCOPE_TABLE"; + static final String SEARCHABLE = "SEARCHABLE"; + static final String SELF_REFERENCING_COL_NAME = "SELF_REFERENCING_COL_NAME"; + static final String SOURCE_DATA_TYPE = "SOURCE_DATA_TYPE"; + static final String SPECIFIC_NAME = "SPECIFIC_NAME"; + static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; + static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB"; + static final String TABLE = "TABLE"; + static final String TABLE_CATALOG_SHORTNAME = "TABLE_CAT"; + static final String TABLE_CATALOG = "TABLE_CATALOG"; + static final String TABLE_NAME = "TABLE_NAME"; + static final String TABLE_SCHEMA = "TABLE_SCHEM"; + static final String TABLE_TYPE = "TABLE_TYPE"; + static final String TYPE = "TYPE"; + static final String TYPE_CATALOG = "TYPE_CAT"; + static final String TYPE_NAME = "TYPE_NAME"; + static final String TYPE_SCHEMA = "TYPE_SCHEM"; + static final String UNSIGNED_ATTRIBUTE = "UNSIGNED_ATTRIBUTE"; + static final String WILDCARD_CHAR = "%"; + static final String YES_VALUE = "YES"; + + CassandraStatement statement; + CassandraConnection connection; + + /** + * Abstract constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + protected AbstractMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + this.statement = statement; + this.connection = statement.getCassandraConnection(); + } + + private boolean matchesPattern(final String pattern, final String against) { + return against.matches(String.format("^%s$", pattern.replaceAll("%", ".*"))); + } + + void filterBySchemaNamePattern(final String schemaNamePattern, final Consumer consumer) { + final Map keyspacesMetadata = + this.connection.getClusterMetadata().getKeyspaces(); + for (final Map.Entry keyspace : keyspacesMetadata.entrySet()) { + final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); + if (schemaNamePattern == null || StringUtils.EMPTY.equals(schemaNamePattern) + || matchesPattern(schemaNamePattern, keyspaceMetadata.getName().asInternal())) { + consumer.accept(keyspaceMetadata); + } + } + } + + void filterByTableNamePattern(final String tableNamePattern, final KeyspaceMetadata keyspaceMetadata, + final Consumer consumer) { + final Map tablesMetadata = keyspaceMetadata.getTables(); + for (final Map.Entry table : tablesMetadata.entrySet()) { + final TableMetadata tableMetadata = table.getValue(); + if (tableNamePattern == null + || matchesPattern(tableNamePattern, tableMetadata.getName().asInternal())) { + consumer.accept(tableMetadata); + } + } + } + +} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java new file mode 100644 index 0000000..781ee32 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java @@ -0,0 +1,128 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; + +/** + * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to tables. + */ +public class TableMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + /** + * Constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + public TableMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + + /** + * Builds a valid result set of the table types available in Cassandra database. This method is used to implement + * the method {@link DatabaseMetaData#getTableTypes()}. + *

+ * The columns of this result set are: + *

    + *
  1. TABLE_TYPE String => table type: always {@value TABLE}.
  2. + *
+ *

+ * + * @return A valid result set for implementation of {@link DatabaseMetaData#getTableTypes()}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildTableTypes() throws SQLException { + final ArrayList tableTypes = new ArrayList<>(); + final MetadataRow row = new MetadataRow().addEntry(TABLE_TYPE, TABLE); + tableTypes.add(row); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(tableTypes)); + } + + /** + * Builds a valid result set of the description of the tables available in the given catalog (Cassandra cluster). + * This method is used to implement the method {@link DatabaseMetaData#getTables(String, String, String, String[])}. + *

+ * Only table descriptions matching the catalog, schema, table name and type criteria are returned. They are + * ordered by {@code TABLE_CAT}, {@code TABLE_SCHEM} and {@code TABLE_NAME}. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name + * (if available).
  2. + *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is + * member of.
  4. + *
  5. TABLE_NAME String => table name.
  6. + *
  7. TABLE_TYPE String => table type: always {@value TABLE} here.
  8. + *
  9. REMARKS String => explanatory comment on the table.
  10. + *
  11. TYPE_CAT String => the types catalog: always {@code null} here.
  12. + *
  13. TYPE_SCHEM String => the types schema: always {@code null} here.
  14. + *
  15. TYPE_NAME String => type name: always {@code null} here.
  16. + *
  17. SELF_REFERENCING_COL_NAME String => name of the designated "identifier" column of a typed + * table: always {@code null} here.
  18. + *
  19. REF_GENERATION String => specifies how values in {@code SELF_REFERENCING_COL_NAME} are + * created: always {@code null} here.
  20. + *
+ *

+ * + * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the database; + * {@code ""} retrieves those without a schema and {@code null} means that the schema name + * should not be used to narrow the search. Using {@code ""} as the same effect as + * {@code null} because here the schema corresponds to the keyspace and Cassandra tables + * cannot be defined outside a keyspace. + * @param tableNamePattern A table name pattern. It must match the table name as it is stored in the database. + * @return A valid result set for implementation of + * {@link DatabaseMetaData#getTables(String, String, String, String[])}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildTables(final String schemaPattern, + final String tableNamePattern) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList tables = new ArrayList<>(); + + filterBySchemaNamePattern(schemaPattern, keyspaceMetadata -> + filterByTableNamePattern(tableNamePattern, keyspaceMetadata, tableMetadata -> { + final MetadataRow row = new MetadataRow() + .addEntry(TABLE_CATALOG_SHORTNAME, catalog) + .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) + .addEntry(TABLE_TYPE, TABLE) + .addEntry(REMARKS, tableMetadata.getOptions() + .get(CqlIdentifier.fromCql(CQL_OPTION_COMMENT)).toString()) + .addEntry(TYPE_CATALOG, null) + .addEntry(TYPE_SCHEMA, null) + .addEntry(TYPE_NAME, null) + .addEntry(SELF_REFERENCING_COL_NAME, null) + .addEntry(REF_GENERATION, null); + tables.add(row); + }) + ); + + // Results should all have the same TABLE_CAT, so just sort them by TABLE_SCHEM then TABLE_NAME. + tables.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TABLE_SCHEMA)) + .thenComparing(row -> ((MetadataRow) row).getString(TABLE_NAME))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(tables)); + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index 842e8f6..155df16 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -14,6 +14,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -51,7 +52,7 @@ static void finalizeSetUpTests() throws Exception { @Test void givenStatement_whenMakeTableTypes_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeTableTypes(statement); + final ResultSet result = new TableMetadataResultSetBuilder(statement).buildTableTypes(); assertNotNull(result); assertEquals(1, result.getMetaData().getColumnCount()); assertEquals("TABLE_TYPE", result.getMetaData().getColumnName(1)); @@ -96,7 +97,7 @@ void givenStatement_whenMakeSchemas_returnExpectedResultSet() throws SQLExceptio @Test void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeTables(statement, null, null); + final ResultSet result = new TableMetadataResultSetBuilder(statement).buildTables(null, null); assertNotNull(result); assertEquals(10, result.getMetaData().getColumnCount()); assertEquals("TABLE_CAT", result.getMetaData().getColumnName(1)); @@ -120,7 +121,7 @@ void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException assertThat(foundTables, hasItem(is(ANOTHER_KEYSPACE.concat(";cf_test2;TABLE;Second table in the keyspace")))); int result2Size = 0; - final ResultSet result2 = MetadataResultSets.INSTANCE.makeTables(statement, ANOTHER_KEYSPACE, null); + final ResultSet result2 = new TableMetadataResultSetBuilder(statement).buildTables(ANOTHER_KEYSPACE, null); assertNotNull(result2); foundTables.clear(); while (result2.next()) { @@ -133,7 +134,7 @@ void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException assertThat(foundTables, hasItem(is(ANOTHER_KEYSPACE.concat(";cf_test2;TABLE;Second table in the keyspace")))); int result3Size = 0; - final ResultSet result3 = MetadataResultSets.INSTANCE.makeTables(statement, null, "cf_test1"); + final ResultSet result3 = new TableMetadataResultSetBuilder(statement).buildTables(null, "cf_test1"); assertNotNull(result3); foundTables.clear(); while (result3.next()) { @@ -146,7 +147,8 @@ void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException assertThat(foundTables, hasItem(is(ANOTHER_KEYSPACE.concat(";cf_test1;TABLE;First table in the keyspace")))); int result4Size = 0; - final ResultSet result4 = MetadataResultSets.INSTANCE.makeTables(statement, ANOTHER_KEYSPACE, "cf_test1"); + final ResultSet result4 = new TableMetadataResultSetBuilder(statement) + .buildTables(ANOTHER_KEYSPACE, "cf_test1"); assertNotNull(result4); foundTables.clear(); while (result4.next()) { diff --git a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java new file mode 100644 index 0000000..3b632b8 --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java @@ -0,0 +1,107 @@ +/* + * 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.ing.data.cassandra.jdbc.metadata; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.ing.data.cassandra.jdbc.CassandraConnection; +import com.ing.data.cassandra.jdbc.CassandraStatement; +import com.ing.data.cassandra.jdbc.utils.TestMetadataResultSetBuilder; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AbstractMetadataResultSetBuilderUnitTest { + + private static final Logger log = LoggerFactory.getLogger(AbstractMetadataResultSetBuilderUnitTest.class); + + static KeyspaceMetadata generateTestKeyspaceMetadata(final String keyspaceName) { + final KeyspaceMetadata mockKeyspaceMetadata = mock(KeyspaceMetadata.class); + when(mockKeyspaceMetadata.getName()).thenReturn(CqlIdentifier.fromCql(keyspaceName)); + return mockKeyspaceMetadata; + } + + @Test + void givenSchemaPattern_whenApplySchemaFiltering_returnExpectedResultSet() throws SQLException { + final CassandraStatement mockStatement = mock(CassandraStatement.class); + final CassandraConnection mockConnection = mock(CassandraConnection.class); + final Metadata mockMetadata = mock(Metadata.class); + when(mockStatement.getCassandraConnection()).thenReturn(mockConnection); + when(mockConnection.getClusterMetadata()).thenReturn(mockMetadata); + final Map testKeyspacesMetadata = new HashMap<>(); + testKeyspacesMetadata.put(CqlIdentifier.fromInternal("ks1"), generateTestKeyspaceMetadata("ks1")); + testKeyspacesMetadata.put(CqlIdentifier.fromInternal("ks2"), generateTestKeyspaceMetadata("ks2")); + testKeyspacesMetadata.put(CqlIdentifier.fromInternal("test_ks"), generateTestKeyspaceMetadata("test_ks")); + testKeyspacesMetadata.put(CqlIdentifier.fromInternal("another"),generateTestKeyspaceMetadata("another")); + when(mockMetadata.getKeyspaces()).thenReturn(testKeyspacesMetadata); + final AbstractMetadataResultSetBuilder sut = new TestMetadataResultSetBuilder(mockStatement); + + final Set filteredSchemas = new HashSet<>(); + sut.filterBySchemaNamePattern(StringUtils.EMPTY, keyspaceMetadata -> { + filteredSchemas.add(keyspaceMetadata.getName().asInternal()); + }); + log.info("Schemas matching '': {}", filteredSchemas); + assertThat(filteredSchemas, hasSize(4)); + assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks", "another")); + + + filteredSchemas.clear(); + sut.filterBySchemaNamePattern(null, keyspaceMetadata -> { + filteredSchemas.add(keyspaceMetadata.getName().asInternal()); + }); + log.info("Schemas matching null: {}", filteredSchemas); + assertThat(filteredSchemas, hasSize(4)); + assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks", "another")); + + filteredSchemas.clear(); + sut.filterBySchemaNamePattern("ks", keyspaceMetadata -> { + filteredSchemas.add(keyspaceMetadata.getName().asInternal()); + }); + log.info("Schemas matching 'ks': {}", filteredSchemas); + assertThat(filteredSchemas, hasSize(0)); + + filteredSchemas.clear(); + sut.filterBySchemaNamePattern("ks%", keyspaceMetadata -> { + filteredSchemas.add(keyspaceMetadata.getName().asInternal()); + }); + log.info("Schemas matching 'ks%': {}", filteredSchemas); + assertThat(filteredSchemas, hasSize(2)); + assertThat(filteredSchemas, hasItems("ks1", "ks2")); + + filteredSchemas.clear(); + sut.filterBySchemaNamePattern("%ks%", keyspaceMetadata -> { + filteredSchemas.add(keyspaceMetadata.getName().asInternal()); + }); + log.info("Schemas matching '%ks%': {}", filteredSchemas); + assertThat(filteredSchemas, hasSize(3)); + assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks")); + + + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/TestMetadataResultSetBuilder.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/TestMetadataResultSetBuilder.java new file mode 100644 index 0000000..398ffe9 --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/TestMetadataResultSetBuilder.java @@ -0,0 +1,27 @@ +/* + * 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.ing.data.cassandra.jdbc.utils; + +import com.ing.data.cassandra.jdbc.CassandraStatement; +import com.ing.data.cassandra.jdbc.metadata.AbstractMetadataResultSetBuilder; + +import java.sql.SQLException; + +public class TestMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + public TestMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + +} From 7661c9f32c7a94c4167d388f0d3badf965350352 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 12 Aug 2023 16:48:26 +0200 Subject: [PATCH 04/21] Update dependencies - DataStax Java Driver to 4.17 - Apache Commons dependencies - Jackson dependencies - some tests dependencies and Maven plugins --- CHANGELOG.md | 6 ++++-- pom.xml | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b1337..1182080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased ### Changed -- Update Apache Commons IO to version 2.12.0. -- Update Jackson dependencies to version 2.15.1. +- Update DataStax Java Driver for Apache Cassandra(R) to version 4.17.0. +- Update Apache Commons IO to version 2.13.0. +- Update Apache Commons Lang to version 3.13.0. +- Update Jackson dependencies to version 2.15.2. - Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages. ## [4.9.0] - 2023-04-15 diff --git a/pom.xml b/pom.xml index c9e3e02..fc82bb8 100644 --- a/pom.xml +++ b/pom.xml @@ -94,25 +94,25 @@ 9.3 2.9.3 4.4 - 2.12.0 - 3.12.0 - 4.15.0 - 2.15.1 + 2.13.0 + 3.13.0 + 4.17.0 + 2.15.2 2.2 - 5.9.3 - 1.9.3 - 1.18.26 + 5.10.0 + 1.10.0 + 1.18.28 3.12.4 1.7.36 - 1.18.1 + 1.18.3 - 3.2.2 - 3.2.0 + 3.3.0 + 3.3.1 3.11.0 3.3.0 3.1.0 - 3.4.1 + 3.5.0 3.3.1 3.5.0 3.2.1 From 41710f60d1ab559c0ae2163927ba72d17371bf80 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 12 Aug 2023 19:14:24 +0200 Subject: [PATCH 05/21] Add support for vector CQL type --- CHANGELOG.md | 3 + .../jdbc/CassandraDatabaseMetaData.java | 12 ++-- .../jdbc/CassandraMetadataResultSet.java | 11 +++ .../jdbc/CassandraPreparedStatement.java | 17 +++++ .../cassandra/jdbc/CassandraResultSet.java | 56 ++++++++++++++- .../jdbc/CassandraResultSetExtras.java | 26 +++++++ .../cassandra/jdbc/types/DataTypeEnum.java | 9 ++- .../ing/data/cassandra/jdbc/utils/Utils.java | 1 + .../jdbc/UsingCassandraContainerTest.java | 2 +- .../data/cassandra/jdbc/VectorsUnitTest.java | 72 +++++++++++++++++++ src/test/resources/initEmbeddedCassandra.cql | 12 ++++ 11 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1182080..412bc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Add support for new [`vector` CQL type](https://datastax-oss.atlassian.net/browse/JAVA-3060) + defined in [CEP-30](https://cwiki.apache.org/confluence/x/OQ40Dw). ### Changed - Update DataStax Java Driver for Apache Cassandra(R) to version 4.17.0. - Update Apache Commons IO to version 2.13.0. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 9499c29..282b817 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -635,13 +635,15 @@ public String getSQLKeywords() throws SQLException { // SQL:2003 standard keywords (see: https://ronsavage.github.io/SQL/sql-2003-2.bnf.html#xref-keywords). // The CQL keywords are listed here: // https://cassandra.apache.org/doc/latest/cassandra/cql/appendices.html#appendix-A - final List cqlKeywords = Arrays.asList("AGGREGATE", "ALLOW", "APPLY", "ASCII", "AUTHORIZE", "BATCH", - "CLUSTERING", "COLUMNFAMILY", "COMPACT", "COUNTER", "CUSTOM", "ENTRIES", "FILTERING", "FINALFUNC", "FROZEN", - "FUNCTIONS", "IF", "INDEX", "INET", "INFINITY", "INITCOND", "JSON", "KEYS", "KEYSPACE", "KEYSPACES", - "LIMIT", "LIST", "LOGIN", "MODIFY", "NAN", "NOLOGIN", "NORECURSIVE", "NOSUPERUSER", "PASSWORD", + // Also add new keywords relative to vector type introduced by CEP-30: + // https://cwiki.apache.org/confluence/x/OQ40Dw + final List cqlKeywords = Arrays.asList("AGGREGATE", "ALLOW", "ANN OF", "APPLY", "ASCII", "AUTHORIZE", + "BATCH", "CLUSTERING", "COLUMNFAMILY", "COMPACT", "COUNTER", "CUSTOM", "ENTRIES", "FILTERING", "FINALFUNC", + "FROZEN", "FUNCTIONS", "IF", "INDEX", "INET", "INFINITY", "INITCOND", "JSON", "KEYS", "KEYSPACE", + "KEYSPACES", "LIMIT", "LIST", "LOGIN", "MODIFY", "NAN", "NOLOGIN", "NORECURSIVE", "NOSUPERUSER", "PASSWORD", "PERMISSION", "PERMISSIONS", "RENAME", "REPLACE", "RETURNS", "ROLES", "SFUNC", "SMALLINT", "STORAGE", "STYPE", "SUPERUSER", "TEXT", "TIMEUUID", "TINYINT", "TOKEN", "TRUNCATE", "TTL", "TUPLE", "UNLOGGED", "USE", - "USERS", "UUID", "VARINT", "WRITETIME"); + "USERS", "UUID", "VARINT", "VECTOR", "WRITETIME"); return String.join(",", cqlKeywords); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java index c1b8eb2..2db13bc 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.ListType; import com.datastax.oss.driver.api.core.type.MapType; @@ -954,6 +955,16 @@ public URL getURL(final String columnLabel) throws SQLException { } } + @Override + public CqlVector getVector(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(NOT_SUPPORTED); + } + + @Override + public CqlVector getVector(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(NOT_SUPPORTED); + } + @Override public SQLWarning getWarnings() throws SQLException { // The rationale is there are no warnings to return in this implementation, but it still throws an exception diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java index 7ae40de..25cc72b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java @@ -20,10 +20,12 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.internal.core.type.DefaultListType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; import com.datastax.oss.driver.internal.core.type.DefaultSetType; +import com.datastax.oss.driver.internal.core.type.DefaultVectorType; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.fasterxml.jackson.core.JsonProcessingException; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; @@ -67,6 +69,7 @@ import java.util.UUID; import java.util.concurrent.CompletionStage; +import static com.ing.data.cassandra.jdbc.utils.Utils.VECTOR_ELEMENTS_NOT_NUMBERS; import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; /** @@ -522,6 +525,8 @@ public void setObject(final int parameterIndex, final Object x) throws SQLExcept targetType = Types.OTHER; } else if (x.getClass().equals(UUID.class)) { targetType = Types.OTHER; + } else if (x.getClass().equals(CqlVector.class)) { + targetType = Types.OTHER; } else { targetType = Types.OTHER; } @@ -609,6 +614,18 @@ public final void setObject(final int parameterIndex, final Object x, final int this.boundStatement = this.boundStatement.setUuid(parameterIndex - 1, (UUID) x); } else if (x instanceof CqlDuration) { this.boundStatement = this.boundStatement.setCqlDuration(parameterIndex - 1, (CqlDuration) x); + } else if (x instanceof CqlVector) { + final CqlVector vector = (CqlVector) x; + final DefaultVectorType vectorType = + (DefaultVectorType) getBoundStatementVariableDefinitions().get(parameterIndex - 1).getType(); + final Class itemsClass = DataTypeEnum.fromCqlTypeName(vectorType.getElementType() + .asCql(false, false)).javaType; + if (Number.class.isAssignableFrom(itemsClass)) { + this.boundStatement = this.boundStatement.setVector(parameterIndex - 1, vector, + itemsClass.asSubclass(Number.class)); + } else { + throw new SQLException(VECTOR_ELEMENTS_NOT_NUMBERS); + } } else if (x instanceof java.net.InetAddress) { this.boundStatement = this.boundStatement.setInetAddress(parameterIndex - 1, (InetAddress) x); } else if (List.class.isAssignableFrom(x.getClass())) { diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index 9a93a66..5a9495c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataType; @@ -27,6 +28,7 @@ import com.datastax.oss.driver.api.core.type.SetType; import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.VectorType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; import com.fasterxml.jackson.core.JsonProcessingException; import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; @@ -93,6 +95,7 @@ import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; import static com.ing.data.cassandra.jdbc.utils.Utils.VALID_LABELS; +import static com.ing.data.cassandra.jdbc.utils.Utils.VECTOR_ELEMENTS_NOT_NUMBERS; import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_RS; import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; @@ -139,6 +142,7 @@ * uuid {@link UUID} A UUID in standard UUID format * varchar {@link String} UTF-8 encoded string * varint {@link BigInteger} Arbitrary-precision integer + * vector {@link CqlVector} A n-dimensional vector * * See: * CQL data types reference and @@ -758,7 +762,7 @@ public Object getObject(final int columnIndex) throws SQLException { return currentRow.getTupleValue(columnIndex - 1); } - // Collections: sets, lists & maps + // Collections: sets, lists, vectors & maps if (dataType.isCollection()) { // Sets if (isCqlType(columnIndex, DataTypeEnum.SET)) { @@ -801,6 +805,11 @@ public Object getObject(final int columnIndex) throws SQLException { return new ArrayList<>(resultList); } + // Vectors + if (isCqlType(columnIndex, DataTypeEnum.VECTOR)) { + return getVector(columnIndex); + } + // Maps if (isCqlType(columnIndex, DataTypeEnum.MAP)) { final MapType mapType = (MapType) cqlDataType; @@ -889,7 +898,7 @@ public Object getObject(final String columnLabel) throws SQLException { return currentRow.getTupleValue(columnLabel); } - // Collections: sets, lists & maps + // Collections: sets, lists, vectors & maps if (dataType.isCollection()) { // Sets if (isCqlType(columnLabel, DataTypeEnum.SET)) { @@ -932,6 +941,11 @@ public Object getObject(final String columnLabel) throws SQLException { return new ArrayList<>(resultList); } + // Vectors + if (isCqlType(columnLabel, DataTypeEnum.VECTOR)) { + return getVector(columnLabel); + } + // Maps if (isCqlType(columnLabel, DataTypeEnum.MAP)) { final MapType mapType = (MapType) cqlDataType; @@ -1089,6 +1103,8 @@ public T getObject(final int columnIndex, final Class type) throws SQLExc returnValue = getDuration(columnIndex); } else if (type == URL.class) { returnValue = getURL(columnIndex); + } else if (type == CqlVector.class) { + returnValue = getVector(columnIndex); } else { throw new SQLException(String.format("Conversion to type %s not supported.", type.getSimpleName())); } @@ -1338,6 +1354,42 @@ public URL getURL(final String columnLabel) throws SQLException { } } + @Override + public CqlVector getVector(final int columnIndex) throws SQLException { + checkIndex(columnIndex); + try { + final VectorType vectorType = (VectorType) getCqlDataType(columnIndex); + final Class elementClass = Class.forName(fromDataType(vectorType.getElementType()).asJavaClass() + .getCanonicalName()); + if (Number.class.isAssignableFrom(elementClass)) { + return this.currentRow.getVector(columnIndex - 1, elementClass.asSubclass(Number.class)); + } else { + throw new SQLException(VECTOR_ELEMENTS_NOT_NUMBERS); + } + } catch (ClassNotFoundException e) { + LOG.warn("Error while executing getSet()", e); + } + return null; + } + + @Override + public CqlVector getVector(final String columnLabel) throws SQLException { + checkName(columnLabel); + try { + final VectorType vectorType = (VectorType) getCqlDataType(columnLabel); + final Class elementClass = Class.forName(fromDataType(vectorType.getElementType()).asJavaClass() + .getCanonicalName()); + if (Number.class.isAssignableFrom(elementClass)) { + return this.currentRow.getVector(columnLabel, elementClass.asSubclass(Number.class)); + } else { + throw new SQLException(VECTOR_ELEMENTS_NOT_NUMBERS); + } + } catch (ClassNotFoundException e) { + LOG.warn("Error while executing getVector()", e); + } + return null; + } + @Override public SQLWarning getWarnings() throws SQLException { // The rationale is there are no warnings to return in this implementation, but it still throws an exception diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java index 265b883..0debc53 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import java.math.BigInteger; import java.sql.ResultSet; @@ -151,4 +152,29 @@ public interface CassandraResultSetExtras extends ResultSet { */ CqlDuration getDuration(String columnLabel) throws SQLException; + /** + * Retrieves the value of the designated column in the current row of this {@code ResultSet} object as a + * {@link CqlVector}. + * + * @param columnIndex The column index (the first column is 1). + * @return The column value. If the value is SQL {@code NULL}, it should return an empty collection or {@code null}, + * depending on the driver implementation. + * @throws SQLException if the columnIndex is not valid; if a database access error occurs or this method is called + * on a closed result set. + */ + CqlVector getVector(int columnIndex) throws SQLException; + + /** + * Retrieves the value of the designated column in the current row of this {@code ResultSet} object as a + * {@link CqlVector}. + * + * @param columnLabel The label for the column specified with the SQL AS clause. If the SQL AS clause was not + * specified, then the label is the name of the column. + * @return The column value. If the value is SQL {@code NULL}, it should return an empty collection or {@code null}, + * depending on the driver implementation. + * @throws SQLException if the columnIndex is not valid; if a database access error occurs or this method is called + * on a closed result set. + */ + CqlVector getVector(String columnLabel) throws SQLException; + } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index bfe0300..c53097b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -67,7 +68,8 @@ public enum DataTypeEnum { UDT(DataType.UDT, UdtValue.class, "UDT"), UUID(DataType.UUID, UUID.class, cqlName(DataTypes.UUID)), VARCHAR(DataType.VARCHAR, String.class, "VARCHAR"), - VARINT(DataType.VARINT, BigInteger.class, cqlName(DataTypes.VARINT)); + VARINT(DataType.VARINT, BigInteger.class, cqlName(DataTypes.VARINT)), + VECTOR(DataType.LIST, CqlVector.class, "vector"); private static final Map CQL_DATATYPE_TO_DATATYPE; public final Class javaType; @@ -130,7 +132,8 @@ public static DataTypeEnum fromDataType(final com.datastax.oss.driver.api.core.t } /** - * Returns whether this data type name represents the name of a collection type (i.e. that is a list, set or map). + * Returns whether this data type name represents the name of a collection type (i.e. that is a list, set, vector + * or map). * * @return {@code true} if this data type name represents the name of a collection type, {@code false} otherwise. */ @@ -139,6 +142,7 @@ public boolean isCollection() { case LIST: case SET: case MAP: + case VECTOR: return true; default: return false; @@ -179,6 +183,7 @@ public boolean isCollection() { * UUID {@link UUID} * VARCHAR {@link String} * VARINT {@link BigInteger} + * VECTOR {@link CqlVector} * *

* (*) See diff --git a/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java index f835fb9..77db68c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java @@ -231,6 +231,7 @@ public final class Utils { public static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; public static final String MALFORMED_URL = "The string '%s' is not a valid URL."; public static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; + public static final String VECTOR_ELEMENTS_NOT_NUMBERS = "Vector elements are not numbers."; static final Logger LOG = LoggerFactory.getLogger(Utils.class); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java index a99c04a..dc3c010 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java @@ -27,7 +27,7 @@ abstract class UsingCassandraContainerTest { // For the official Cassandra image, see here: https://hub.docker.com/_/cassandra - static final DockerImageName CASSANDRA_IMAGE = DockerImageName.parse("cassandra:4.1.1"); + static final DockerImageName CASSANDRA_IMAGE = DockerImageName.parse("cassandra:4.1.3"); static CassandraConnection sqlConnection = null; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java new file mode 100644 index 0000000..2e76e29 --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java @@ -0,0 +1,72 @@ +/* + * 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.ing.data.cassandra.jdbc; + +import com.datastax.oss.driver.api.core.data.CqlVector; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test CQL Vector data type + */ +// FIXME: Implement vector testing when Cassandra 5.0 is available. +@Disabled +class VectorsUnitTest extends UsingCassandraContainerTest { + + private static final String KEYSPACE = "test_keyspace_vect"; + + @BeforeAll + static void finalizeSetUpTests() throws Exception { + initConnection(KEYSPACE, "version=3.0.0", "localdatacenter=datacenter1"); + } + + @Test + void givenVectorInsertStatement_whenExecute_insertExpectedValues() throws Exception { + final Statement statement = sqlConnection.createStatement(); + + final String insert = "INSERT INTO vectors_test (keyValue, intsVector, floatsVector) " + + "VALUES(1, [4, 6, 8], [2.1, 3.7, 9.0, 5.5]);"; + statement.executeUpdate(insert); + + final ResultSet resultSet = statement.executeQuery("SELECT * FROM vectors_test WHERE keyValue = 1;"); + resultSet.next(); + + assertThat(resultSet, is(instanceOf(CassandraResultSet.class))); + assertEquals(1, resultSet.getInt("keyValue")); + + final CqlVector intsVector = ((CassandraResultSet) resultSet).getVector("intsVector"); + assertEquals(3, intsVector.size()); + assertEquals(4, intsVector.get(0)); + assertEquals(6, intsVector.get(1)); + assertEquals(8, intsVector.get(2)); + final CqlVector floatsVector = ((CassandraResultSet) resultSet).getVector(2); + assertEquals(4, floatsVector.size()); + assertEquals(2.1, floatsVector.get(0)); + assertEquals(3.7, floatsVector.get(1)); + assertEquals(9.0, floatsVector.get(2)); + assertEquals(5.5, floatsVector.get(2)); + + statement.close(); + } + +} diff --git a/src/test/resources/initEmbeddedCassandra.cql b/src/test/resources/initEmbeddedCassandra.cql index ed35adc..d95c9ab 100644 --- a/src/test/resources/initEmbeddedCassandra.cql +++ b/src/test/resources/initEmbeddedCassandra.cql @@ -208,3 +208,15 @@ uuidValue: uuid(), varcharValue: 'varchar text', varintValue: 4321 }); + +/* Init keyspace and tables for VectorsUnitTest */ +DROP KEYSPACE IF EXISTS test_keyspace_vect; +CREATE KEYSPACE "test_keyspace_vect" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; + +/* FIXME: Uncomment this script part when Cassandra 5.0 is available. +USE test_keyspace_vect; +CREATE TABLE vectors_test ( +keyValue int PRIMARY KEY, +intsVector vector, +floatsVector vector); +*/ From 1997a98f8be57c910da569c3f74b84a971b57ae4 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 12 Aug 2023 16:48:26 +0200 Subject: [PATCH 06/21] Update dependencies - DataStax Java Driver to 4.17 - Apache Commons dependencies - Jackson dependencies - some tests dependencies and Maven plugins --- CHANGELOG.md | 6 ++++-- pom.xml | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b1337..1182080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased ### Changed -- Update Apache Commons IO to version 2.12.0. -- Update Jackson dependencies to version 2.15.1. +- Update DataStax Java Driver for Apache Cassandra(R) to version 4.17.0. +- Update Apache Commons IO to version 2.13.0. +- Update Apache Commons Lang to version 3.13.0. +- Update Jackson dependencies to version 2.15.2. - Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages. ## [4.9.0] - 2023-04-15 diff --git a/pom.xml b/pom.xml index c9e3e02..fc82bb8 100644 --- a/pom.xml +++ b/pom.xml @@ -94,25 +94,25 @@ 9.3 2.9.3 4.4 - 2.12.0 - 3.12.0 - 4.15.0 - 2.15.1 + 2.13.0 + 3.13.0 + 4.17.0 + 2.15.2 2.2 - 5.9.3 - 1.9.3 - 1.18.26 + 5.10.0 + 1.10.0 + 1.18.28 3.12.4 1.7.36 - 1.18.1 + 1.18.3 - 3.2.2 - 3.2.0 + 3.3.0 + 3.3.1 3.11.0 3.3.0 3.1.0 - 3.4.1 + 3.5.0 3.3.1 3.5.0 3.2.1 From 9adcbc82700948546d49568842cc33baaad81587 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 12 Aug 2023 19:14:24 +0200 Subject: [PATCH 07/21] Add support for vector CQL type --- CHANGELOG.md | 3 + .../jdbc/CassandraDatabaseMetaData.java | 12 ++-- .../jdbc/CassandraMetadataResultSet.java | 11 +++ .../jdbc/CassandraPreparedStatement.java | 17 +++++ .../cassandra/jdbc/CassandraResultSet.java | 56 ++++++++++++++- .../jdbc/CassandraResultSetExtras.java | 26 +++++++ .../cassandra/jdbc/types/DataTypeEnum.java | 9 ++- .../ing/data/cassandra/jdbc/utils/Utils.java | 1 + .../jdbc/UsingCassandraContainerTest.java | 2 +- .../data/cassandra/jdbc/VectorsUnitTest.java | 72 +++++++++++++++++++ src/test/resources/initEmbeddedCassandra.cql | 12 ++++ 11 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1182080..412bc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Add support for new [`vector` CQL type](https://datastax-oss.atlassian.net/browse/JAVA-3060) + defined in [CEP-30](https://cwiki.apache.org/confluence/x/OQ40Dw). ### Changed - Update DataStax Java Driver for Apache Cassandra(R) to version 4.17.0. - Update Apache Commons IO to version 2.13.0. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 39f309f..7c29bc4 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -636,13 +636,15 @@ public String getSQLKeywords() throws SQLException { // SQL:2003 standard keywords (see: https://ronsavage.github.io/SQL/sql-2003-2.bnf.html#xref-keywords). // The CQL keywords are listed here: // https://cassandra.apache.org/doc/latest/cassandra/cql/appendices.html#appendix-A - final List cqlKeywords = Arrays.asList("AGGREGATE", "ALLOW", "APPLY", "ASCII", "AUTHORIZE", "BATCH", - "CLUSTERING", "COLUMNFAMILY", "COMPACT", "COUNTER", "CUSTOM", "ENTRIES", "FILTERING", "FINALFUNC", "FROZEN", - "FUNCTIONS", "IF", "INDEX", "INET", "INFINITY", "INITCOND", "JSON", "KEYS", "KEYSPACE", "KEYSPACES", - "LIMIT", "LIST", "LOGIN", "MODIFY", "NAN", "NOLOGIN", "NORECURSIVE", "NOSUPERUSER", "PASSWORD", + // Also add new keywords relative to vector type introduced by CEP-30: + // https://cwiki.apache.org/confluence/x/OQ40Dw + final List cqlKeywords = Arrays.asList("AGGREGATE", "ALLOW", "ANN OF", "APPLY", "ASCII", "AUTHORIZE", + "BATCH", "CLUSTERING", "COLUMNFAMILY", "COMPACT", "COUNTER", "CUSTOM", "ENTRIES", "FILTERING", "FINALFUNC", + "FROZEN", "FUNCTIONS", "IF", "INDEX", "INET", "INFINITY", "INITCOND", "JSON", "KEYS", "KEYSPACE", + "KEYSPACES", "LIMIT", "LIST", "LOGIN", "MODIFY", "NAN", "NOLOGIN", "NORECURSIVE", "NOSUPERUSER", "PASSWORD", "PERMISSION", "PERMISSIONS", "RENAME", "REPLACE", "RETURNS", "ROLES", "SFUNC", "SMALLINT", "STORAGE", "STYPE", "SUPERUSER", "TEXT", "TIMEUUID", "TINYINT", "TOKEN", "TRUNCATE", "TTL", "TUPLE", "UNLOGGED", "USE", - "USERS", "UUID", "VARINT", "WRITETIME"); + "USERS", "UUID", "VARINT", "VECTOR", "WRITETIME"); return String.join(",", cqlKeywords); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java index c1b8eb2..2db13bc 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.ListType; import com.datastax.oss.driver.api.core.type.MapType; @@ -954,6 +955,16 @@ public URL getURL(final String columnLabel) throws SQLException { } } + @Override + public CqlVector getVector(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(NOT_SUPPORTED); + } + + @Override + public CqlVector getVector(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(NOT_SUPPORTED); + } + @Override public SQLWarning getWarnings() throws SQLException { // The rationale is there are no warnings to return in this implementation, but it still throws an exception diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java index 7ae40de..25cc72b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java @@ -20,10 +20,12 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.internal.core.type.DefaultListType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; import com.datastax.oss.driver.internal.core.type.DefaultSetType; +import com.datastax.oss.driver.internal.core.type.DefaultVectorType; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.fasterxml.jackson.core.JsonProcessingException; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; @@ -67,6 +69,7 @@ import java.util.UUID; import java.util.concurrent.CompletionStage; +import static com.ing.data.cassandra.jdbc.utils.Utils.VECTOR_ELEMENTS_NOT_NUMBERS; import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; /** @@ -522,6 +525,8 @@ public void setObject(final int parameterIndex, final Object x) throws SQLExcept targetType = Types.OTHER; } else if (x.getClass().equals(UUID.class)) { targetType = Types.OTHER; + } else if (x.getClass().equals(CqlVector.class)) { + targetType = Types.OTHER; } else { targetType = Types.OTHER; } @@ -609,6 +614,18 @@ public final void setObject(final int parameterIndex, final Object x, final int this.boundStatement = this.boundStatement.setUuid(parameterIndex - 1, (UUID) x); } else if (x instanceof CqlDuration) { this.boundStatement = this.boundStatement.setCqlDuration(parameterIndex - 1, (CqlDuration) x); + } else if (x instanceof CqlVector) { + final CqlVector vector = (CqlVector) x; + final DefaultVectorType vectorType = + (DefaultVectorType) getBoundStatementVariableDefinitions().get(parameterIndex - 1).getType(); + final Class itemsClass = DataTypeEnum.fromCqlTypeName(vectorType.getElementType() + .asCql(false, false)).javaType; + if (Number.class.isAssignableFrom(itemsClass)) { + this.boundStatement = this.boundStatement.setVector(parameterIndex - 1, vector, + itemsClass.asSubclass(Number.class)); + } else { + throw new SQLException(VECTOR_ELEMENTS_NOT_NUMBERS); + } } else if (x instanceof java.net.InetAddress) { this.boundStatement = this.boundStatement.setInetAddress(parameterIndex - 1, (InetAddress) x); } else if (List.class.isAssignableFrom(x.getClass())) { diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index 9a93a66..5a9495c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataType; @@ -27,6 +28,7 @@ import com.datastax.oss.driver.api.core.type.SetType; import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.VectorType; import com.datastax.oss.driver.internal.core.type.DefaultMapType; import com.fasterxml.jackson.core.JsonProcessingException; import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; @@ -93,6 +95,7 @@ import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; import static com.ing.data.cassandra.jdbc.utils.Utils.VALID_LABELS; +import static com.ing.data.cassandra.jdbc.utils.Utils.VECTOR_ELEMENTS_NOT_NUMBERS; import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_RS; import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; @@ -139,6 +142,7 @@ * uuid {@link UUID} A UUID in standard UUID format * varchar {@link String} UTF-8 encoded string * varint {@link BigInteger} Arbitrary-precision integer + * vector {@link CqlVector} A n-dimensional vector * * See: * CQL data types reference and @@ -758,7 +762,7 @@ public Object getObject(final int columnIndex) throws SQLException { return currentRow.getTupleValue(columnIndex - 1); } - // Collections: sets, lists & maps + // Collections: sets, lists, vectors & maps if (dataType.isCollection()) { // Sets if (isCqlType(columnIndex, DataTypeEnum.SET)) { @@ -801,6 +805,11 @@ public Object getObject(final int columnIndex) throws SQLException { return new ArrayList<>(resultList); } + // Vectors + if (isCqlType(columnIndex, DataTypeEnum.VECTOR)) { + return getVector(columnIndex); + } + // Maps if (isCqlType(columnIndex, DataTypeEnum.MAP)) { final MapType mapType = (MapType) cqlDataType; @@ -889,7 +898,7 @@ public Object getObject(final String columnLabel) throws SQLException { return currentRow.getTupleValue(columnLabel); } - // Collections: sets, lists & maps + // Collections: sets, lists, vectors & maps if (dataType.isCollection()) { // Sets if (isCqlType(columnLabel, DataTypeEnum.SET)) { @@ -932,6 +941,11 @@ public Object getObject(final String columnLabel) throws SQLException { return new ArrayList<>(resultList); } + // Vectors + if (isCqlType(columnLabel, DataTypeEnum.VECTOR)) { + return getVector(columnLabel); + } + // Maps if (isCqlType(columnLabel, DataTypeEnum.MAP)) { final MapType mapType = (MapType) cqlDataType; @@ -1089,6 +1103,8 @@ public T getObject(final int columnIndex, final Class type) throws SQLExc returnValue = getDuration(columnIndex); } else if (type == URL.class) { returnValue = getURL(columnIndex); + } else if (type == CqlVector.class) { + returnValue = getVector(columnIndex); } else { throw new SQLException(String.format("Conversion to type %s not supported.", type.getSimpleName())); } @@ -1338,6 +1354,42 @@ public URL getURL(final String columnLabel) throws SQLException { } } + @Override + public CqlVector getVector(final int columnIndex) throws SQLException { + checkIndex(columnIndex); + try { + final VectorType vectorType = (VectorType) getCqlDataType(columnIndex); + final Class elementClass = Class.forName(fromDataType(vectorType.getElementType()).asJavaClass() + .getCanonicalName()); + if (Number.class.isAssignableFrom(elementClass)) { + return this.currentRow.getVector(columnIndex - 1, elementClass.asSubclass(Number.class)); + } else { + throw new SQLException(VECTOR_ELEMENTS_NOT_NUMBERS); + } + } catch (ClassNotFoundException e) { + LOG.warn("Error while executing getSet()", e); + } + return null; + } + + @Override + public CqlVector getVector(final String columnLabel) throws SQLException { + checkName(columnLabel); + try { + final VectorType vectorType = (VectorType) getCqlDataType(columnLabel); + final Class elementClass = Class.forName(fromDataType(vectorType.getElementType()).asJavaClass() + .getCanonicalName()); + if (Number.class.isAssignableFrom(elementClass)) { + return this.currentRow.getVector(columnLabel, elementClass.asSubclass(Number.class)); + } else { + throw new SQLException(VECTOR_ELEMENTS_NOT_NUMBERS); + } + } catch (ClassNotFoundException e) { + LOG.warn("Error while executing getVector()", e); + } + return null; + } + @Override public SQLWarning getWarnings() throws SQLException { // The rationale is there are no warnings to return in this implementation, but it still throws an exception diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java index 265b883..0debc53 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetExtras.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import java.math.BigInteger; import java.sql.ResultSet; @@ -151,4 +152,29 @@ public interface CassandraResultSetExtras extends ResultSet { */ CqlDuration getDuration(String columnLabel) throws SQLException; + /** + * Retrieves the value of the designated column in the current row of this {@code ResultSet} object as a + * {@link CqlVector}. + * + * @param columnIndex The column index (the first column is 1). + * @return The column value. If the value is SQL {@code NULL}, it should return an empty collection or {@code null}, + * depending on the driver implementation. + * @throws SQLException if the columnIndex is not valid; if a database access error occurs or this method is called + * on a closed result set. + */ + CqlVector getVector(int columnIndex) throws SQLException; + + /** + * Retrieves the value of the designated column in the current row of this {@code ResultSet} object as a + * {@link CqlVector}. + * + * @param columnLabel The label for the column specified with the SQL AS clause. If the SQL AS clause was not + * specified, then the label is the name of the column. + * @return The column value. If the value is SQL {@code NULL}, it should return an empty collection or {@code null}, + * depending on the driver implementation. + * @throws SQLException if the columnIndex is not valid; if a database access error occurs or this method is called + * on a closed result set. + */ + CqlVector getVector(String columnLabel) throws SQLException; + } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index bfe0300..c53097b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataTypes; @@ -67,7 +68,8 @@ public enum DataTypeEnum { UDT(DataType.UDT, UdtValue.class, "UDT"), UUID(DataType.UUID, UUID.class, cqlName(DataTypes.UUID)), VARCHAR(DataType.VARCHAR, String.class, "VARCHAR"), - VARINT(DataType.VARINT, BigInteger.class, cqlName(DataTypes.VARINT)); + VARINT(DataType.VARINT, BigInteger.class, cqlName(DataTypes.VARINT)), + VECTOR(DataType.LIST, CqlVector.class, "vector"); private static final Map CQL_DATATYPE_TO_DATATYPE; public final Class javaType; @@ -130,7 +132,8 @@ public static DataTypeEnum fromDataType(final com.datastax.oss.driver.api.core.t } /** - * Returns whether this data type name represents the name of a collection type (i.e. that is a list, set or map). + * Returns whether this data type name represents the name of a collection type (i.e. that is a list, set, vector + * or map). * * @return {@code true} if this data type name represents the name of a collection type, {@code false} otherwise. */ @@ -139,6 +142,7 @@ public boolean isCollection() { case LIST: case SET: case MAP: + case VECTOR: return true; default: return false; @@ -179,6 +183,7 @@ public boolean isCollection() { * UUID {@link UUID} * VARCHAR {@link String} * VARINT {@link BigInteger} + * VECTOR {@link CqlVector} * *

* (*) See diff --git a/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java index f835fb9..77db68c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java @@ -231,6 +231,7 @@ public final class Utils { public static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; public static final String MALFORMED_URL = "The string '%s' is not a valid URL."; public static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; + public static final String VECTOR_ELEMENTS_NOT_NUMBERS = "Vector elements are not numbers."; static final Logger LOG = LoggerFactory.getLogger(Utils.class); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java index a99c04a..dc3c010 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java @@ -27,7 +27,7 @@ abstract class UsingCassandraContainerTest { // For the official Cassandra image, see here: https://hub.docker.com/_/cassandra - static final DockerImageName CASSANDRA_IMAGE = DockerImageName.parse("cassandra:4.1.1"); + static final DockerImageName CASSANDRA_IMAGE = DockerImageName.parse("cassandra:4.1.3"); static CassandraConnection sqlConnection = null; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java new file mode 100644 index 0000000..2e76e29 --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/VectorsUnitTest.java @@ -0,0 +1,72 @@ +/* + * 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.ing.data.cassandra.jdbc; + +import com.datastax.oss.driver.api.core.data.CqlVector; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test CQL Vector data type + */ +// FIXME: Implement vector testing when Cassandra 5.0 is available. +@Disabled +class VectorsUnitTest extends UsingCassandraContainerTest { + + private static final String KEYSPACE = "test_keyspace_vect"; + + @BeforeAll + static void finalizeSetUpTests() throws Exception { + initConnection(KEYSPACE, "version=3.0.0", "localdatacenter=datacenter1"); + } + + @Test + void givenVectorInsertStatement_whenExecute_insertExpectedValues() throws Exception { + final Statement statement = sqlConnection.createStatement(); + + final String insert = "INSERT INTO vectors_test (keyValue, intsVector, floatsVector) " + + "VALUES(1, [4, 6, 8], [2.1, 3.7, 9.0, 5.5]);"; + statement.executeUpdate(insert); + + final ResultSet resultSet = statement.executeQuery("SELECT * FROM vectors_test WHERE keyValue = 1;"); + resultSet.next(); + + assertThat(resultSet, is(instanceOf(CassandraResultSet.class))); + assertEquals(1, resultSet.getInt("keyValue")); + + final CqlVector intsVector = ((CassandraResultSet) resultSet).getVector("intsVector"); + assertEquals(3, intsVector.size()); + assertEquals(4, intsVector.get(0)); + assertEquals(6, intsVector.get(1)); + assertEquals(8, intsVector.get(2)); + final CqlVector floatsVector = ((CassandraResultSet) resultSet).getVector(2); + assertEquals(4, floatsVector.size()); + assertEquals(2.1, floatsVector.get(0)); + assertEquals(3.7, floatsVector.get(1)); + assertEquals(9.0, floatsVector.get(2)); + assertEquals(5.5, floatsVector.get(2)); + + statement.close(); + } + +} diff --git a/src/test/resources/initEmbeddedCassandra.cql b/src/test/resources/initEmbeddedCassandra.cql index ed35adc..d95c9ab 100644 --- a/src/test/resources/initEmbeddedCassandra.cql +++ b/src/test/resources/initEmbeddedCassandra.cql @@ -208,3 +208,15 @@ uuidValue: uuid(), varcharValue: 'varchar text', varintValue: 4321 }); + +/* Init keyspace and tables for VectorsUnitTest */ +DROP KEYSPACE IF EXISTS test_keyspace_vect; +CREATE KEYSPACE "test_keyspace_vect" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; + +/* FIXME: Uncomment this script part when Cassandra 5.0 is available. +USE test_keyspace_vect; +CREATE TABLE vectors_test ( +keyValue int PRIMARY KEY, +intsVector vector, +floatsVector vector); +*/ From 5f0c831aac9f9cae1953227590544860aec43404 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:37:40 +0200 Subject: [PATCH 08/21] Refactor catalogs and schemas metadata builders --- .gitignore | 16 ++-- .../jdbc/CassandraDatabaseMetaData.java | 9 +- .../cassandra/jdbc/MetadataResultSets.java | 62 ------------- .../CatalogMetadataResultSetBuilder.java | 61 +++++++++++++ .../SchemaMetadataResultSetBuilder.java | 79 +++++++++++++++++ .../jdbc/MetadataResultSetsUnitTest.java | 88 +++++++++++-------- ...tractMetadataResultSetBuilderUnitTest.java | 83 +++++++++++++---- 7 files changed, 272 insertions(+), 126 deletions(-) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java diff --git a/.gitignore b/.gitignore index 3bbe858..780c52d 100644 --- a/.gitignore +++ b/.gitignore @@ -101,14 +101,14 @@ local.properties # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr # CMake cmake-build-*/ diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 7c29bc4..c0b2e0e 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -16,6 +16,8 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.ing.data.cassandra.jdbc.metadata.CatalogMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.SchemaMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; import org.apache.commons.lang3.StringUtils; @@ -144,7 +146,7 @@ public String getCatalogTerm() { @Override public ResultSet getCatalogs() throws SQLException { checkStatementClosed(); - return MetadataResultSets.INSTANCE.makeCatalogs(statement); + return new CatalogMetadataResultSetBuilder(this.statement).buildCatalogs(); } /** @@ -661,7 +663,7 @@ public String getSchemaTerm() { @Override public ResultSet getSchemas() throws SQLException { checkStatementClosed(); - return MetadataResultSets.INSTANCE.makeSchemas(this.statement, null); + return new SchemaMetadataResultSetBuilder(this.statement).buildSchemas(null); } @Override @@ -670,8 +672,7 @@ public ResultSet getSchemas(final String catalog, final String schemaPattern) th if (!(catalog == null || catalog.equals(this.statement.connection.getCatalog()))) { throw new SQLSyntaxErrorException("Catalog name must exactly match or be null."); } - - return MetadataResultSets.INSTANCE.makeSchemas(this.statement, schemaPattern); + return new SchemaMetadataResultSetBuilder(this.statement).buildSchemas(schemaPattern); } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java index 9a72b3d..c19ae23 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java @@ -131,68 +131,6 @@ private MetadataResultSets() { // Private constructor to hide the public one. } - /** - * Builds a valid result set of the catalog names available in this Cassandra database. This method is used to - * implement the method {@link DatabaseMetaData#getCatalogs()}. - *

- * The columns of this result set are: - *

    - *
  1. TABLE_CAT String => catalog name: here is the Cassandra cluster name (if available).
  2. - *
- *

- * - * @param statement The statement. - * @return A valid result set for implementation of {@link DatabaseMetaData#getCatalogs()}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeCatalogs(final CassandraStatement statement) throws SQLException { - final ArrayList catalog = new ArrayList<>(); - final MetadataRow row = new MetadataRow().addEntry(TABLE_CATALOG_SHORTNAME, statement.connection.getCatalog()); - catalog.add(row); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(catalog)); - } - - /** - * Builds a valid result set of the schema names available in this Cassandra database. This method is used to - * implement the methods {@link DatabaseMetaData#getSchemas()} and - * {@link DatabaseMetaData#getSchemas(String, String)}. - *

- * The columns of this result set are: - *

    - *
  1. TABLE_SCHEM String => schema name: here is the keyspace name.
  2. - *
  3. TABLE_CATALOG String => catalog name, may be {@code null}: here is the Cassandra cluster name - * (if available).
  4. - *
- *

- * - * @param statement The statement. - * @param schemaPattern A schema name. It must match the schema name as it is stored in the database; {@code null} - * means schema name should not be used to narrow down the search. - * @return A valid result set for implementation of {@link DatabaseMetaData#getSchemas(String, String)}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeSchemas(final CassandraStatement statement, final String schemaPattern) - throws SQLException { - final ArrayList schemas = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - String schemaNamePattern = schemaPattern; - if (WILDCARD_CHAR.equals(schemaPattern)) { - schemaNamePattern = keyspaceMetadata.getName().asInternal(); - } - if (schemaNamePattern == null || schemaNamePattern.equals(keyspaceMetadata.getName().asInternal())) { - final MetadataRow row = new MetadataRow() - .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(TABLE_CATALOG, statement.connection.getCatalog()); - schemas.add(row); - } - } - - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); - } - /** * Builds a valid result set of the description of the table columns available in the given catalog (Cassandra * cluster). This method is used to implement the method diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java new file mode 100644 index 0000000..50cd36a --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java @@ -0,0 +1,61 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; + +/** + * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to catalogs. + */ +public class CatalogMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + /** + * Constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + public CatalogMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + + /** + * Builds a valid result set of the catalog names available in this Cassandra database. This method is used to + * implement the method {@link DatabaseMetaData#getCatalogs()}. + *

+ * The columns of this result set are: + *

    + *
  1. TABLE_CAT String => catalog name: here is the Cassandra cluster name (if available).
  2. + *
+ *

+ * + * @return A valid result set for implementation of {@link DatabaseMetaData#getCatalogs()}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildCatalogs() throws SQLException { + final ArrayList catalogs = new ArrayList<>(); + final MetadataRow row = new MetadataRow().addEntry(TABLE_CATALOG_SHORTNAME, + this.statement.getConnection().getCatalog()); + catalogs.add(row); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(catalogs)); + } + +} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java new file mode 100644 index 0000000..ece6e96 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java @@ -0,0 +1,79 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; + +/** + * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to schemas. + */ +public class SchemaMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + /** + * Constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + public SchemaMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + + /** + * Builds a valid result set of the schema names available in this Cassandra database. This method is used to + * implement the methods {@link DatabaseMetaData#getSchemas()} and + * {@link DatabaseMetaData#getSchemas(String, String)}. The results are ordered by {@code TABLE_CATALOG}, then + * {@code TABLE_SCHEMA}. + *

+ * The columns of this result set are: + *

    + *
  1. TABLE_SCHEM String => schema name: here is the keyspace name.
  2. + *
  3. TABLE_CATALOG String => catalog name, may be {@code null}: here is the Cassandra cluster name + * (if available).
  4. + *
+ *

+ * + * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the database; + * {@code null} means that the schema name should not be used to narrow the search. Using + * {@code ""} as the same effect as {@code null} because here the schema corresponds to the + * keyspace and Cassandra tables cannot be defined outside a keyspace. + * @return A valid result set for implementation of {@link DatabaseMetaData#getSchemas(String, String)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildSchemas(final String schemaPattern) + throws SQLException { + final ArrayList schemas = new ArrayList<>(); + final String catalog = this.connection.getCatalog(); + + filterBySchemaNamePattern(schemaPattern, keyspaceMetadata -> { + final MetadataRow row = new MetadataRow() + .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TABLE_CATALOG, catalog); + schemas.add(row); + }); + + // Results should all have the same TABLE_CATALOG, so just sort them by TABLE_SCHEM. + schemas.sort(Comparator.comparing(row -> row.getString(TABLE_SCHEMA))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(schemas)); + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index 155df16..a37c77d 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -14,6 +14,8 @@ package com.ing.data.cassandra.jdbc; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.ing.data.cassandra.jdbc.metadata.CatalogMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.SchemaMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.junit.jupiter.api.BeforeAll; @@ -49,6 +51,10 @@ static void finalizeSetUpTests() throws Exception { initConnection(KEYSPACE, "version=3.0.0", "localdatacenter=datacenter1"); } + /* + * Tables metadata + */ + @Test void givenStatement_whenMakeTableTypes_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); @@ -59,41 +65,6 @@ void givenStatement_whenMakeTableTypes_returnExpectedResultSet() throws SQLExcep assertEquals("TABLE", result.getString(1)); } - @Test - void givenStatement_whenMakeCatalogs_returnExpectedResultSet() throws SQLException { - final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeCatalogs(statement); - assertNotNull(result); - assertEquals(1, result.getMetaData().getColumnCount()); - assertEquals("TABLE_CAT", result.getMetaData().getColumnName(1)); - assertEquals("embedded_test_cluster", result.getString(1)); - } - - @Test - void givenStatement_whenMakeSchemas_returnExpectedResultSet() throws SQLException { - final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeSchemas(statement, null); - assertNotNull(result); - assertEquals(2, result.getMetaData().getColumnCount()); - assertEquals("TABLE_SCHEM", result.getMetaData().getColumnName(1)); - final List foundKeyspaces = new ArrayList<>(); - while (result.next()) { - foundKeyspaces.add(result.getString(1)); - } - assertThat(foundKeyspaces, hasItem(is(KEYSPACE))); - assertThat(foundKeyspaces, hasItem(is(ANOTHER_KEYSPACE))); - assertEquals("TABLE_CATALOG", result.getMetaData().getColumnName(2)); - assertEquals("embedded_test_cluster", result.getString(2)); - - final ResultSet result2 = MetadataResultSets.INSTANCE.makeSchemas(statement, ANOTHER_KEYSPACE); - assertNotNull(result2); - assertEquals(2, result2.getMetaData().getColumnCount()); - assertEquals("TABLE_SCHEM", result2.getMetaData().getColumnName(1)); - assertEquals(ANOTHER_KEYSPACE, result2.getString(1)); - assertEquals("TABLE_CATALOG", result.getMetaData().getColumnName(2)); - assertEquals("embedded_test_cluster", result.getString(2)); - } - @Test void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); @@ -160,6 +131,53 @@ void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException assertThat(foundTables, hasItem(is(ANOTHER_KEYSPACE.concat(";cf_test1;TABLE;First table in the keyspace")))); } + /* + * Catalogs metadata + */ + + @Test + void givenStatement_whenMakeCatalogs_returnExpectedResultSet() throws SQLException { + final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); + final ResultSet result = new CatalogMetadataResultSetBuilder(statement).buildCatalogs(); + assertNotNull(result); + assertEquals(1, result.getMetaData().getColumnCount()); + assertEquals("TABLE_CAT", result.getMetaData().getColumnName(1)); + assertEquals("embedded_test_cluster", result.getString(1)); + } + + /* + * Schemas metadata + */ + + @Test + void givenStatement_whenMakeSchemas_returnExpectedResultSet() throws SQLException { + final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); + final ResultSet result = new SchemaMetadataResultSetBuilder(statement).buildSchemas(null); + assertNotNull(result); + assertEquals(2, result.getMetaData().getColumnCount()); + assertEquals("TABLE_SCHEM", result.getMetaData().getColumnName(1)); + final List foundKeyspaces = new ArrayList<>(); + while (result.next()) { + foundKeyspaces.add(result.getString(1)); + } + assertThat(foundKeyspaces, hasItem(is(KEYSPACE))); + assertThat(foundKeyspaces, hasItem(is(ANOTHER_KEYSPACE))); + assertEquals("TABLE_CATALOG", result.getMetaData().getColumnName(2)); + assertEquals("embedded_test_cluster", result.getString(2)); + + final ResultSet result2 = new SchemaMetadataResultSetBuilder(statement).buildSchemas(ANOTHER_KEYSPACE); + assertNotNull(result2); + assertEquals(2, result2.getMetaData().getColumnCount()); + assertEquals("TABLE_SCHEM", result2.getMetaData().getColumnName(1)); + assertEquals(ANOTHER_KEYSPACE, result2.getString(1)); + assertEquals("TABLE_CATALOG", result.getMetaData().getColumnName(2)); + assertEquals("embedded_test_cluster", result.getString(2)); + } + + /* + * Columns metadata + */ + @Test void givenStatement_whenMakeColumns_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java index 3b632b8..9e262b7 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java @@ -16,6 +16,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.ing.data.cassandra.jdbc.CassandraConnection; import com.ing.data.cassandra.jdbc.CassandraStatement; import com.ing.data.cassandra.jdbc.utils.TestMetadataResultSetBuilder; @@ -31,6 +32,7 @@ import java.util.Set; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasSize; import static org.mockito.Mockito.mock; @@ -46,6 +48,12 @@ static KeyspaceMetadata generateTestKeyspaceMetadata(final String keyspaceName) return mockKeyspaceMetadata; } + static TableMetadata generateTestTableMetadata(final String tableName) { + final TableMetadata mockTableMetadata = mock(TableMetadata.class); + when(mockTableMetadata.getName()).thenReturn(CqlIdentifier.fromCql(tableName)); + return mockTableMetadata; + } + @Test void givenSchemaPattern_whenApplySchemaFiltering_returnExpectedResultSet() throws SQLException { final CassandraStatement mockStatement = mock(CassandraStatement.class); @@ -62,46 +70,87 @@ void givenSchemaPattern_whenApplySchemaFiltering_returnExpectedResultSet() throw final AbstractMetadataResultSetBuilder sut = new TestMetadataResultSetBuilder(mockStatement); final Set filteredSchemas = new HashSet<>(); - sut.filterBySchemaNamePattern(StringUtils.EMPTY, keyspaceMetadata -> { - filteredSchemas.add(keyspaceMetadata.getName().asInternal()); - }); + sut.filterBySchemaNamePattern(StringUtils.EMPTY, + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); log.info("Schemas matching '': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(4)); assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks", "another")); - filteredSchemas.clear(); - sut.filterBySchemaNamePattern(null, keyspaceMetadata -> { - filteredSchemas.add(keyspaceMetadata.getName().asInternal()); - }); + sut.filterBySchemaNamePattern(null, + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); log.info("Schemas matching null: {}", filteredSchemas); assertThat(filteredSchemas, hasSize(4)); assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks", "another")); filteredSchemas.clear(); - sut.filterBySchemaNamePattern("ks", keyspaceMetadata -> { - filteredSchemas.add(keyspaceMetadata.getName().asInternal()); - }); + sut.filterBySchemaNamePattern("ks", + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); log.info("Schemas matching 'ks': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(0)); filteredSchemas.clear(); - sut.filterBySchemaNamePattern("ks%", keyspaceMetadata -> { - filteredSchemas.add(keyspaceMetadata.getName().asInternal()); - }); + sut.filterBySchemaNamePattern("ks%", + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); log.info("Schemas matching 'ks%': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(2)); assertThat(filteredSchemas, hasItems("ks1", "ks2")); filteredSchemas.clear(); - sut.filterBySchemaNamePattern("%ks%", keyspaceMetadata -> { - filteredSchemas.add(keyspaceMetadata.getName().asInternal()); - }); + sut.filterBySchemaNamePattern("%ks%", + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); log.info("Schemas matching '%ks%': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(3)); assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks")); + } + @Test + void givenTablePattern_whenApplyTableFiltering_returnExpectedResultSet() throws SQLException { + final CassandraStatement mockStatement = mock(CassandraStatement.class); + final CassandraConnection mockConnection = mock(CassandraConnection.class); + final Metadata mockMetadata = mock(Metadata.class); + when(mockStatement.getCassandraConnection()).thenReturn(mockConnection); + when(mockConnection.getClusterMetadata()).thenReturn(mockMetadata); + final KeyspaceMetadata ksTestMetadata = generateTestKeyspaceMetadata("ks_test"); + final Map testTablesMetadata = new HashMap<>(); + testTablesMetadata.put(CqlIdentifier.fromInternal("cf1"), generateTestTableMetadata("cf1")); + testTablesMetadata.put(CqlIdentifier.fromInternal("cf2"), generateTestTableMetadata("cf2")); + testTablesMetadata.put(CqlIdentifier.fromInternal("another_table"), generateTestTableMetadata("another_table")); + testTablesMetadata.put(CqlIdentifier.fromInternal("test_cf"), generateTestTableMetadata("test_cf")); + when(ksTestMetadata.getTables()).thenReturn(testTablesMetadata); + final AbstractMetadataResultSetBuilder sut = new TestMetadataResultSetBuilder(mockStatement); + final Set filteredTables = new HashSet<>(); + sut.filterByTableNamePattern(StringUtils.EMPTY, ksTestMetadata, + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + log.info("Tables matching '': {}", filteredTables); + assertThat(filteredTables, empty()); + + filteredTables.clear(); + sut.filterByTableNamePattern(null, ksTestMetadata, + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + log.info("Tables matching null: {}", filteredTables); + assertThat(filteredTables, hasSize(4)); + assertThat(filteredTables, hasItems("cf1", "cf2", "another_table", "test_cf")); + + filteredTables.clear(); + sut.filterByTableNamePattern("cf", ksTestMetadata, + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + log.info("Tables matching 'cf': {}", filteredTables); + assertThat(filteredTables, empty()); + + filteredTables.clear(); + sut.filterByTableNamePattern("cf%", ksTestMetadata, + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + log.info("Tables matching 'cf%': {}", filteredTables); + assertThat(filteredTables, hasSize(2)); + assertThat(filteredTables, hasItems("cf1", "cf2")); + + filteredTables.clear(); + sut.filterByTableNamePattern("%cf%", ksTestMetadata, + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + log.info("Tables matching '%cf%': {}", filteredTables); + assertThat(filteredTables, hasSize(3)); + assertThat(filteredTables, hasItems("cf1", "cf2", "test_cf")); } - } From 14f1c685ca5e54d9e8c5c6285060106ea41a4f41 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 3 Sep 2023 11:28:29 +0200 Subject: [PATCH 09/21] Fix issue #25 - fix result sets and statements closing. - introduce a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously (by default, they are executed asynchronously). - return the schema name instead of null when the method CassandraConnection.getCatalog() is called in Liquibase compliance mode. - does not throw SQLFeatureNotSupportedException when CassandraConnection.rollback() is called in Liquibase compliance mode. --- .gitignore | 18 +-- CHANGELOG.md | 13 +++ README.md | 19 ++- pom.xml | 2 +- .../cassandra/jdbc/CassandraConnection.java | 4 +- .../jdbc/CassandraMetadataResultSet.java | 10 +- .../cassandra/jdbc/CassandraResultSet.java | 11 +- .../cassandra/jdbc/CassandraStatement.java | 109 ++++++++++-------- .../cassandra/jdbc/optionset/Default.java | 9 ++ .../cassandra/jdbc/optionset/Liquibase.java | 25 +++- .../cassandra/jdbc/optionset/OptionSet.java | 19 +++ .../cassandra/jdbc/ConnectionUnitTest.java | 6 +- 12 files changed, 169 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index 3bbe858..870e561 100644 --- a/.gitignore +++ b/.gitignore @@ -99,16 +99,16 @@ local.properties # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using +# since they will be recreated, and may cause churn. Uncomment if using # auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr # CMake cmake-build-*/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c8825f..4ce67e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.9.1] - 2023-09-03 +### Fixed +- Fix issue [#25](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/25) causing failure when running with + Liquibase. The fix includes several changes: + - fixes result sets and statements closing. + - introduces a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously + (by default, they are executed asynchronously). + - returns the schema name instead of `null` when the method `CassandraConnection.getCatalog()` is called in Liquibase + compliance mode. + - does not throw `SQLFeatureNotSupportedException` when `CassandraConnection.rollback()` is called in Liquibase + compliance mode. + ## [4.9.0] - 2023-04-15 ### Added - Add non-JDBC standard [JSON support](https://cassandra.apache.org/doc/latest/cassandra/cql/json.html) with the @@ -121,6 +133,7 @@ For this version, the changelog lists the main changes comparatively to the late - Fix logs in `CassandraConnection` constructor. [original project]: https://github.com/adejanovski/cassandra-jdbc-wrapper/ +[4.9.1]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.9.0...v4.9.1 [4.9.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.8.0...v4.9.0 [4.8.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.7.0...v4.8.0 [4.7.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.6.0...v4.7.0 diff --git a/README.md b/README.md index 7a63e8f..b864742 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ For further information about connecting to DBaaS, see [cloud documentation](htt ### Compliance modes For some specific usages, the default behaviour of some JDBC implementations has to be modified. That's why you can -use the argument `compliancemode` in the JDBC URL to cutomize the behaviour of some methods. +use the argument `compliancemode` in the JDBC URL to customize the behaviour of some methods. The values currently allowed for this argument are: * `Default`: mode activated by default if not specified in the JDBC URL. It implements the methods detailed below as @@ -293,10 +293,19 @@ The values currently allowed for this argument are: Here are the behaviours defined by the compliance modes listed above: -| Method | Default mode | Liquibase mode | -|--------------------------------------------|---------------------------------------------------------------------------------------------------|----------------| -| `CassandraConnection.getCatalog()` | returns the result of the query`SELECT cluster_name FROM system.local` or `null` if not available | returns `null` | -| `CassandraStatement.executeUpdate(String)` | returns 0 | returns -1 | +| Method | Default mode | Liquibase mode | +|--------------------------------------------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------| +| `CassandraConnection.getCatalog()` | returns the result of the query`SELECT cluster_name FROM system.local` or `null` if not available | returns the schema name if available, `null` otherwise | +| `CassandraConnection.rollback()` | throws a `SQLFeatureNotSupportedException` | do nothing more after checking connection is open | +| `CassandraStatement.executeUpdate(String)` | returns 0 | returns -1 | + +For the following methods: `CassandraStatement.execute(String)`, `CassandraStatement.executeQuery(String)` and +`CassandraStatement.executeUpdate(String)`, if the CQL statement includes several queries (separated by semicolons), +the behaviour is the following: + +| Default mode | Liquibase mode | +|-------------------------------------|------------------------------------| +| executes the queries asynchronously | executes the queries synchronously | ### Using simple statements diff --git a/pom.xml b/pom.xml index 4c56141..28cbddb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.ing.data cassandra-jdbc-wrapper - 4.9.0 + 4.9.1 jar Cassandra JDBC Wrapper diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java index 4a373cd..1b907cf 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java @@ -547,7 +547,9 @@ public CassandraPreparedStatement prepareStatement(final String cql, final int r @Override public void rollback() throws SQLException { checkNotClosed(); - throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT); + if (this.optionSet.shouldThrowExceptionOnRollback()) { + throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT); + } } /** diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java index f212e42..a69fd1d 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java @@ -128,6 +128,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas private int fetchDirection; private int fetchSize; private boolean wasNull; + private boolean isClosed; // Result set from the Cassandra driver. private MetadataResultSet driverResultSet; @@ -137,6 +138,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas CassandraMetadataResultSet() { this.metadata = new CResultSetMetaData(); this.statement = null; + this.isClosed = false; } /** @@ -156,6 +158,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas this.fetchSize = statement.getFetchSize(); this.driverResultSet = metadataResultSet; this.rowsIterator = metadataResultSet.iterator(); + this.isClosed = false; // Initialize the column values from the first row. // Note that the first call to next() will harmlessly re-write these values for the columns. The row cursor @@ -250,7 +253,7 @@ public void clearWarnings() throws SQLException { @Override public void close() throws SQLException { if (!isClosed()) { - this.statement.close(); + this.isClosed = true; } } @@ -961,10 +964,7 @@ public boolean isBeforeFirst() throws SQLException { @Override public boolean isClosed() { - if (this.statement == null) { - return true; - } - return this.statement.isClosed(); + return this.isClosed; } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index a709d7f..b0e5fdd 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -176,6 +176,7 @@ public class CassandraResultSet extends AbstractResultSet private int fetchDirection; private int fetchSize; private boolean wasNull; + private boolean isClosed; // Result set from the Cassandra driver. private ResultSet driverResultSet; @@ -185,6 +186,7 @@ public class CassandraResultSet extends AbstractResultSet CassandraResultSet() { this.metadata = new CResultSetMetaData(); this.statement = null; + this.isClosed = false; } /** @@ -203,6 +205,7 @@ public class CassandraResultSet extends AbstractResultSet this.fetchSize = statement.getFetchSize(); this.driverResultSet = resultSet; this.rowsIterator = resultSet.iterator(); + this.isClosed = false; // Initialize the column values from the first row. if (hasMoreRows()) { @@ -225,6 +228,7 @@ public class CassandraResultSet extends AbstractResultSet this.resultSetType = statement.getResultSetType(); this.fetchDirection = statement.getFetchDirection(); this.fetchSize = statement.getFetchSize(); + this.isClosed = false; // We have several result sets, but we will use only the first one for metadata needs. this.driverResultSet = resultSets.get(0); @@ -324,7 +328,7 @@ public void clearWarnings() throws SQLException { @Override public void close() throws SQLException { if (!isClosed()) { - this.statement.close(); + this.isClosed = true; } } @@ -1366,10 +1370,7 @@ public boolean isBeforeFirst() throws SQLException { @Override public boolean isClosed() { - if (this.statement == null) { - return true; - } - return this.statement.isClosed(); + return this.isClosed; } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java index c9ef629..d1ac19e 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java @@ -137,7 +137,7 @@ public class CassandraStatement extends AbstractStatement * The consistency level used for the statement. */ protected ConsistencyLevel consistencyLevel; - + private boolean isClosed; private DriverExecutionProfile customTimeoutProfile; /** @@ -210,6 +210,7 @@ public class CassandraStatement extends AbstractStatement this.cql = cql; this.batchQueries = new ArrayList<>(); this.consistencyLevel = connection.getDefaultConsistencyLevel(); + this.isClosed = false; if (!(resultSetType == ResultSet.TYPE_FORWARD_ONLY || resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE @@ -264,7 +265,7 @@ public void clearWarnings() throws SQLException { @Override public void close() { - this.connection = null; + this.isClosed = true; this.cql = null; } @@ -284,67 +285,68 @@ private void doExecute(final String cql) throws SQLException { try { final String[] cqlQueries = cql.split(STATEMENTS_SEPARATOR_REGEX); - if (cqlQueries.length > 1 && !(cql.trim().toLowerCase().startsWith("begin") + if (cqlQueries.length > 1 + && !(cql.trim().toLowerCase().startsWith("begin") && cql.toLowerCase().contains("batch") && cql.toLowerCase().contains("apply"))) { - // Several statements in the query to execute asynchronously... - final ArrayList results = new ArrayList<>(); + + // Several statements in the query to execute asynchronously... if (cqlQueries.length > MAX_ASYNC_QUERIES * 1.1) { // Protect the cluster from receiving too many queries at once and force the dev to split the load throw new SQLNonTransientException("Too many queries at once (" + cqlQueries.length + "). You must split your queries into more batches !"); } - StringBuilder prevCqlQuery = new StringBuilder(); - for (final String cqlQuery : cqlQueries) { - if ((cqlQuery.contains("'") && ((StringUtils.countMatches(cqlQuery, "'") % 2 == 1 - && prevCqlQuery.length() == 0) - || (StringUtils.countMatches(cqlQuery, "'") % 2 == 0 && prevCqlQuery.length() > 0))) - || (prevCqlQuery.toString().length() > 0 && !cqlQuery.contains("'"))) { - prevCqlQuery.append(cqlQuery).append(";"); - } else { - prevCqlQuery.append(cqlQuery); - if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { - LOG.debug("CQL: {}", prevCqlQuery); - } - SimpleStatement stmt = SimpleStatement.newInstance(prevCqlQuery.toString()) - .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) - .setPageSize(this.fetchSize); - if (this.customTimeoutProfile != null) { - stmt = stmt.setExecutionProfile(this.customTimeoutProfile); + // If we should not execute the queries asynchronously, for example if they must be executed in the + // specified order (e.g. in Liquibase scripts with queries such as CREATE TABLE t, then + // INSERT INTO t ...). + if (!this.connection.getOptionSet().executeMultipleQueriesByStatementAsync()) { + for (final String cqlQuery : cqlQueries) { + final com.datastax.oss.driver.api.core.cql.ResultSet rs = executeSingleStatement(cqlQuery); + results.add(rs); + } + } else { + StringBuilder prevCqlQuery = new StringBuilder(); + for (final String cqlQuery : cqlQueries) { + if ((cqlQuery.contains("'") && ((StringUtils.countMatches(cqlQuery, "'") % 2 == 1 + && prevCqlQuery.length() == 0) + || (StringUtils.countMatches(cqlQuery, "'") % 2 == 0 && prevCqlQuery.length() > 0))) + || (!prevCqlQuery.toString().isEmpty() && !cqlQuery.contains("'"))) { + prevCqlQuery.append(cqlQuery).append(";"); + } else { + prevCqlQuery.append(cqlQuery); + if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { + LOG.debug("CQL: {}", prevCqlQuery); + } + SimpleStatement stmt = SimpleStatement.newInstance(prevCqlQuery.toString()) + .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) + .setPageSize(this.fetchSize); + if (this.customTimeoutProfile != null) { + stmt = stmt.setExecutionProfile(this.customTimeoutProfile); + } + final CompletionStage resultSetFuture = + ((CqlSession) this.connection.getSession()).executeAsync(stmt); + futures.add(resultSetFuture); + prevCqlQuery = new StringBuilder(); } - final CompletionStage resultSetFuture = - ((CqlSession) this.connection.getSession()).executeAsync(stmt); - futures.add(resultSetFuture); - prevCqlQuery = new StringBuilder(); } - } - for (final CompletionStage future : futures) { - final AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(future); - final com.datastax.oss.driver.api.core.cql.ResultSet rows; - if (asyncResultSet.hasMorePages()) { - rows = new MultiPageResultSet(asyncResultSet); - } else { - rows = new SinglePageResultSet(asyncResultSet); + for (final CompletionStage future : futures) { + final AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(future); + final com.datastax.oss.driver.api.core.cql.ResultSet rows; + if (asyncResultSet.hasMorePages()) { + rows = new MultiPageResultSet(asyncResultSet); + } else { + rows = new SinglePageResultSet(asyncResultSet); + } + results.add(rows); } - results.add(rows); } this.currentResultSet = new CassandraResultSet(this, results); } else { // Only one statement to execute, so do it synchronously. - if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { - LOG.debug("CQL: " + cql); - } - SimpleStatement stmt = SimpleStatement.newInstance(cql) - .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) - .setPageSize(this.fetchSize); - if (this.customTimeoutProfile != null) { - stmt = stmt.setExecutionProfile(this.customTimeoutProfile); - } - this.currentResultSet = new CassandraResultSet(this, - ((CqlSession) this.connection.getSession()).execute(stmt)); + this.currentResultSet = new CassandraResultSet(this, executeSingleStatement(cql)); } } catch (final Exception e) { for (final CompletionStage future : futures) { @@ -354,6 +356,19 @@ private void doExecute(final String cql) throws SQLException { } } + private com.datastax.oss.driver.api.core.cql.ResultSet executeSingleStatement(final String cql) { + if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { + LOG.debug("CQL: " + cql); + } + SimpleStatement stmt = SimpleStatement.newInstance(cql) + .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) + .setPageSize(this.fetchSize); + if (this.customTimeoutProfile != null) { + stmt = stmt.setExecutionProfile(this.customTimeoutProfile); + } + return ((CqlSession) this.connection.getSession()).execute(stmt); + } + @Override public boolean execute(final String query) throws SQLException { checkNotClosed(); @@ -647,7 +662,7 @@ public SQLWarning getWarnings() throws SQLException { @Override public boolean isClosed() { - return this.connection == null; + return this.isClosed; } /** diff --git a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java index 070f81d..2a96461 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java @@ -50,4 +50,13 @@ public int getSQLUpdateResponse() { return 0; } + @Override + public boolean shouldThrowExceptionOnRollback() { + return true; + } + + @Override + public boolean executeMultipleQueriesByStatementAsync() { + return true; + } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java index d134f15..3e2d1a4 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java @@ -15,14 +15,28 @@ package com.ing.data.cassandra.jdbc.optionset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; + /** * Option set implementation for Liquibase compatibility and flavour of JDBC. */ public class Liquibase extends AbstractOptionSet { + private static final Logger LOG = LoggerFactory.getLogger(AbstractOptionSet.class); @Override public String getCatalog() { - return null; + if (getConnection() == null) { + return null; + } + try { + return getConnection().getSchema(); + } catch (final SQLException e) { + LOG.warn("Unable to retrieve the schema name: {}", e.getMessage()); + return null; + } } @Override @@ -30,4 +44,13 @@ public int getSQLUpdateResponse() { return -1; } + @Override + public boolean shouldThrowExceptionOnRollback() { + return false; + } + + @Override + public boolean executeMultipleQueriesByStatementAsync() { + return false; + } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java b/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java index 4d0ccc9..a6c5189 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java @@ -17,6 +17,8 @@ import com.ing.data.cassandra.jdbc.CassandraConnection; +import java.sql.SQLFeatureNotSupportedException; + /** * Option set for compliance mode. * Different use cases require one or more adjustments to the wrapper, to be compatible. @@ -38,6 +40,23 @@ public interface OptionSet { */ int getSQLUpdateResponse(); + /** + * Whether the rollback method on a Cassandra connection should throw a {@link SQLFeatureNotSupportedException} + * since Cassandra is always in auto-commit mode and does not support rollback. + * + * @return {@code true} if the method {@link CassandraConnection#rollback()} should throw an exception, + * {@code false} otherwise. + */ + boolean shouldThrowExceptionOnRollback(); + + /** + * Whether the statement execution methods must execute queries asynchronously when the statement contains several + * queries separated by semicolons. + * + * @return {@code true} if the queries must be executed asynchronously, {@code false} otherwise. + */ + boolean executeMultipleQueriesByStatementAsync(); + /** * Set referenced connection. See @{@link AbstractOptionSet}. * @param connection Connection to set. diff --git a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java index 9a498d9..60ad8b6 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java @@ -407,12 +407,14 @@ void givenSessionToConnect_andLiquibaseCompliance() throws SQLException { .withLocalDatacenter("datacenter1") .build(); + final Liquibase liquibaseMode = new Liquibase(); final CassandraConnection jdbcConnection = - new CassandraConnection(session, KEYSPACE, ConsistencyLevel.ALL, false, new Liquibase()); + new CassandraConnection(session, KEYSPACE, ConsistencyLevel.ALL, false, liquibaseMode); + liquibaseMode.setConnection(jdbcConnection); final ResultSet resultSet = jdbcConnection.createStatement() .executeQuery("SELECT release_version FROM system.local"); assertNotNull(resultSet.getString("release_version")); - assertNull(jdbcConnection.getCatalog()); + assertEquals(KEYSPACE, jdbcConnection.getCatalog()); } @Test From 7a1b1b8c9e1fdbaa95895ffe5682b9ca67bba1f7 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 3 Sep 2023 11:28:29 +0200 Subject: [PATCH 10/21] Fix issue #25 - fix result sets and statements closing. - introduce a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously (by default, they are executed asynchronously). - return the schema name instead of null when the method CassandraConnection.getCatalog() is called in Liquibase compliance mode. - does not throw SQLFeatureNotSupportedException when CassandraConnection.rollback() is called in Liquibase compliance mode. --- .gitignore | 2 +- CHANGELOG.md | 13 +++ README.md | 19 ++- pom.xml | 8 +- .../cassandra/jdbc/CassandraConnection.java | 4 +- .../jdbc/CassandraMetadataResultSet.java | 10 +- .../cassandra/jdbc/CassandraResultSet.java | 11 +- .../cassandra/jdbc/CassandraStatement.java | 109 ++++++++++-------- .../cassandra/jdbc/optionset/Default.java | 9 ++ .../cassandra/jdbc/optionset/Liquibase.java | 25 +++- .../cassandra/jdbc/optionset/OptionSet.java | 19 +++ .../cassandra/jdbc/ConnectionUnitTest.java | 6 +- 12 files changed, 164 insertions(+), 71 deletions(-) diff --git a/.gitignore b/.gitignore index 780c52d..870e561 100644 --- a/.gitignore +++ b/.gitignore @@ -99,7 +99,7 @@ local.properties # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using +# since they will be recreated, and may cause churn. Uncomment if using # auto-import. .idea/artifacts .idea/compiler.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 412bc1d..cb2b081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Update Jackson dependencies to version 2.15.2. - Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages. +## [4.9.1] - 2023-09-03 +### Fixed +- Fix issue [#25](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/25) causing failure when running with + Liquibase. The fix includes several changes: + - fixes result sets and statements closing. + - introduces a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously + (by default, they are executed asynchronously). + - returns the schema name instead of `null` when the method `CassandraConnection.getCatalog()` is called in Liquibase + compliance mode. + - does not throw `SQLFeatureNotSupportedException` when `CassandraConnection.rollback()` is called in Liquibase + compliance mode. + ## [4.9.0] - 2023-04-15 ### Added - Add non-JDBC standard [JSON support](https://cassandra.apache.org/doc/latest/cassandra/cql/json.html) with the @@ -132,6 +144,7 @@ For this version, the changelog lists the main changes comparatively to the late - Fix logs in `CassandraConnection` constructor. [original project]: https://github.com/adejanovski/cassandra-jdbc-wrapper/ +[4.9.1]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.9.0...v4.9.1 [4.9.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.8.0...v4.9.0 [4.8.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.7.0...v4.8.0 [4.7.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.6.0...v4.7.0 diff --git a/README.md b/README.md index 7a63e8f..b864742 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ For further information about connecting to DBaaS, see [cloud documentation](htt ### Compliance modes For some specific usages, the default behaviour of some JDBC implementations has to be modified. That's why you can -use the argument `compliancemode` in the JDBC URL to cutomize the behaviour of some methods. +use the argument `compliancemode` in the JDBC URL to customize the behaviour of some methods. The values currently allowed for this argument are: * `Default`: mode activated by default if not specified in the JDBC URL. It implements the methods detailed below as @@ -293,10 +293,19 @@ The values currently allowed for this argument are: Here are the behaviours defined by the compliance modes listed above: -| Method | Default mode | Liquibase mode | -|--------------------------------------------|---------------------------------------------------------------------------------------------------|----------------| -| `CassandraConnection.getCatalog()` | returns the result of the query`SELECT cluster_name FROM system.local` or `null` if not available | returns `null` | -| `CassandraStatement.executeUpdate(String)` | returns 0 | returns -1 | +| Method | Default mode | Liquibase mode | +|--------------------------------------------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------| +| `CassandraConnection.getCatalog()` | returns the result of the query`SELECT cluster_name FROM system.local` or `null` if not available | returns the schema name if available, `null` otherwise | +| `CassandraConnection.rollback()` | throws a `SQLFeatureNotSupportedException` | do nothing more after checking connection is open | +| `CassandraStatement.executeUpdate(String)` | returns 0 | returns -1 | + +For the following methods: `CassandraStatement.execute(String)`, `CassandraStatement.executeQuery(String)` and +`CassandraStatement.executeUpdate(String)`, if the CQL statement includes several queries (separated by semicolons), +the behaviour is the following: + +| Default mode | Liquibase mode | +|-------------------------------------|------------------------------------| +| executes the queries asynchronously | executes the queries synchronously | ### Using simple statements diff --git a/pom.xml b/pom.xml index fc82bb8..917f2d0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,10 +3,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.ing.data - cassandra-jdbc-wrapper - 4.9.0 - jar + com.ing.data + cassandra-jdbc-wrapper + 4.9.1 + jar Cassandra JDBC Wrapper JDBC wrapper of the DataStax Java Driver for Apache Cassandra. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java index f1f8a93..a399c1b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java @@ -547,7 +547,9 @@ public CassandraPreparedStatement prepareStatement(final String cql, final int r @Override public void rollback() throws SQLException { checkNotClosed(); - throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT); + if (this.optionSet.shouldThrowExceptionOnRollback()) { + throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT); + } } /** diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java index 2db13bc..6046953 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java @@ -135,6 +135,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas private int fetchDirection; private int fetchSize; private boolean wasNull; + private boolean isClosed; // Result set from the Cassandra driver. private MetadataResultSet driverResultSet; @@ -144,6 +145,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas CassandraMetadataResultSet() { this.metadata = new CResultSetMetaData(); this.statement = null; + this.isClosed = false; } /** @@ -163,6 +165,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas this.fetchSize = statement.getFetchSize(); this.driverResultSet = metadataResultSet; this.rowsIterator = metadataResultSet.iterator(); + this.isClosed = false; // Initialize the column values from the first row. // Note that the first call to next() will harmlessly re-write these values for the columns. The row cursor @@ -271,7 +274,7 @@ public void clearWarnings() throws SQLException { @Override public void close() throws SQLException { if (!isClosed()) { - this.statement.close(); + this.isClosed = true; } } @@ -992,10 +995,7 @@ public boolean isBeforeFirst() throws SQLException { @Override public boolean isClosed() { - if (this.statement == null) { - return true; - } - return this.statement.isClosed(); + return this.isClosed; } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index 5a9495c..4035bc9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -184,6 +184,7 @@ public class CassandraResultSet extends AbstractResultSet private int fetchDirection; private int fetchSize; private boolean wasNull; + private boolean isClosed; // Result set from the Cassandra driver. private ResultSet driverResultSet; @@ -193,6 +194,7 @@ public class CassandraResultSet extends AbstractResultSet CassandraResultSet() { this.metadata = new CResultSetMetaData(); this.statement = null; + this.isClosed = false; } /** @@ -211,6 +213,7 @@ public class CassandraResultSet extends AbstractResultSet this.fetchSize = statement.getFetchSize(); this.driverResultSet = resultSet; this.rowsIterator = resultSet.iterator(); + this.isClosed = false; // Initialize the column values from the first row. if (hasMoreRows()) { @@ -233,6 +236,7 @@ public class CassandraResultSet extends AbstractResultSet this.resultSetType = statement.getResultSetType(); this.fetchDirection = statement.getFetchDirection(); this.fetchSize = statement.getFetchSize(); + this.isClosed = false; // We have several result sets, but we will use only the first one for metadata needs. this.driverResultSet = resultSets.get(0); @@ -332,7 +336,7 @@ public void clearWarnings() throws SQLException { @Override public void close() throws SQLException { if (!isClosed()) { - this.statement.close(); + this.isClosed = true; } } @@ -1422,10 +1426,7 @@ public boolean isBeforeFirst() throws SQLException { @Override public boolean isClosed() { - if (this.statement == null) { - return true; - } - return this.statement.isClosed(); + return this.isClosed; } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java index 0141c93..236097c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java @@ -138,7 +138,7 @@ public class CassandraStatement extends AbstractStatement * The consistency level used for the statement. */ protected ConsistencyLevel consistencyLevel; - + private boolean isClosed; private DriverExecutionProfile customTimeoutProfile; /** @@ -211,6 +211,7 @@ public class CassandraStatement extends AbstractStatement this.cql = cql; this.batchQueries = new ArrayList<>(); this.consistencyLevel = connection.getDefaultConsistencyLevel(); + this.isClosed = false; if (!(resultSetType == ResultSet.TYPE_FORWARD_ONLY || resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE @@ -265,7 +266,7 @@ public void clearWarnings() throws SQLException { @Override public void close() { - this.connection = null; + this.isClosed = true; this.cql = null; } @@ -285,67 +286,68 @@ private void doExecute(final String cql) throws SQLException { try { final String[] cqlQueries = cql.split(STATEMENTS_SEPARATOR_REGEX); - if (cqlQueries.length > 1 && !(cql.trim().toLowerCase().startsWith("begin") + if (cqlQueries.length > 1 + && !(cql.trim().toLowerCase().startsWith("begin") && cql.toLowerCase().contains("batch") && cql.toLowerCase().contains("apply"))) { - // Several statements in the query to execute asynchronously... - final ArrayList results = new ArrayList<>(); + + // Several statements in the query to execute asynchronously... if (cqlQueries.length > MAX_ASYNC_QUERIES * 1.1) { // Protect the cluster from receiving too many queries at once and force the dev to split the load throw new SQLNonTransientException("Too many queries at once (" + cqlQueries.length + "). You must split your queries into more batches !"); } - StringBuilder prevCqlQuery = new StringBuilder(); - for (final String cqlQuery : cqlQueries) { - if ((cqlQuery.contains("'") && ((StringUtils.countMatches(cqlQuery, "'") % 2 == 1 - && prevCqlQuery.length() == 0) - || (StringUtils.countMatches(cqlQuery, "'") % 2 == 0 && prevCqlQuery.length() > 0))) - || (prevCqlQuery.toString().length() > 0 && !cqlQuery.contains("'"))) { - prevCqlQuery.append(cqlQuery).append(";"); - } else { - prevCqlQuery.append(cqlQuery); - if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { - LOG.debug("CQL: {}", prevCqlQuery); - } - SimpleStatement stmt = SimpleStatement.newInstance(prevCqlQuery.toString()) - .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) - .setPageSize(this.fetchSize); - if (this.customTimeoutProfile != null) { - stmt = stmt.setExecutionProfile(this.customTimeoutProfile); + // If we should not execute the queries asynchronously, for example if they must be executed in the + // specified order (e.g. in Liquibase scripts with queries such as CREATE TABLE t, then + // INSERT INTO t ...). + if (!this.connection.getOptionSet().executeMultipleQueriesByStatementAsync()) { + for (final String cqlQuery : cqlQueries) { + final com.datastax.oss.driver.api.core.cql.ResultSet rs = executeSingleStatement(cqlQuery); + results.add(rs); + } + } else { + StringBuilder prevCqlQuery = new StringBuilder(); + for (final String cqlQuery : cqlQueries) { + if ((cqlQuery.contains("'") && ((StringUtils.countMatches(cqlQuery, "'") % 2 == 1 + && prevCqlQuery.length() == 0) + || (StringUtils.countMatches(cqlQuery, "'") % 2 == 0 && prevCqlQuery.length() > 0))) + || (!prevCqlQuery.toString().isEmpty() && !cqlQuery.contains("'"))) { + prevCqlQuery.append(cqlQuery).append(";"); + } else { + prevCqlQuery.append(cqlQuery); + if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { + LOG.debug("CQL: {}", prevCqlQuery); + } + SimpleStatement stmt = SimpleStatement.newInstance(prevCqlQuery.toString()) + .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) + .setPageSize(this.fetchSize); + if (this.customTimeoutProfile != null) { + stmt = stmt.setExecutionProfile(this.customTimeoutProfile); + } + final CompletionStage resultSetFuture = + ((CqlSession) this.connection.getSession()).executeAsync(stmt); + futures.add(resultSetFuture); + prevCqlQuery = new StringBuilder(); } - final CompletionStage resultSetFuture = - ((CqlSession) this.connection.getSession()).executeAsync(stmt); - futures.add(resultSetFuture); - prevCqlQuery = new StringBuilder(); } - } - for (final CompletionStage future : futures) { - final AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(future); - final com.datastax.oss.driver.api.core.cql.ResultSet rows; - if (asyncResultSet.hasMorePages()) { - rows = new MultiPageResultSet(asyncResultSet); - } else { - rows = new SinglePageResultSet(asyncResultSet); + for (final CompletionStage future : futures) { + final AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(future); + final com.datastax.oss.driver.api.core.cql.ResultSet rows; + if (asyncResultSet.hasMorePages()) { + rows = new MultiPageResultSet(asyncResultSet); + } else { + rows = new SinglePageResultSet(asyncResultSet); + } + results.add(rows); } - results.add(rows); } this.currentResultSet = new CassandraResultSet(this, results); } else { // Only one statement to execute, so do it synchronously. - if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { - LOG.debug("CQL: " + cql); - } - SimpleStatement stmt = SimpleStatement.newInstance(cql) - .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) - .setPageSize(this.fetchSize); - if (this.customTimeoutProfile != null) { - stmt = stmt.setExecutionProfile(this.customTimeoutProfile); - } - this.currentResultSet = new CassandraResultSet(this, - ((CqlSession) this.connection.getSession()).execute(stmt)); + this.currentResultSet = new CassandraResultSet(this, executeSingleStatement(cql)); } } catch (final Exception e) { for (final CompletionStage future : futures) { @@ -355,6 +357,19 @@ private void doExecute(final String cql) throws SQLException { } } + private com.datastax.oss.driver.api.core.cql.ResultSet executeSingleStatement(final String cql) { + if (LOG.isTraceEnabled() || this.connection.isDebugMode()) { + LOG.debug("CQL: " + cql); + } + SimpleStatement stmt = SimpleStatement.newInstance(cql) + .setConsistencyLevel(this.connection.getDefaultConsistencyLevel()) + .setPageSize(this.fetchSize); + if (this.customTimeoutProfile != null) { + stmt = stmt.setExecutionProfile(this.customTimeoutProfile); + } + return ((CqlSession) this.connection.getSession()).execute(stmt); + } + @Override public boolean execute(final String query) throws SQLException { checkNotClosed(); @@ -658,7 +673,7 @@ public SQLWarning getWarnings() throws SQLException { @Override public boolean isClosed() { - return this.connection == null; + return this.isClosed; } /** diff --git a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java index 070f81d..2a96461 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java @@ -50,4 +50,13 @@ public int getSQLUpdateResponse() { return 0; } + @Override + public boolean shouldThrowExceptionOnRollback() { + return true; + } + + @Override + public boolean executeMultipleQueriesByStatementAsync() { + return true; + } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java index d134f15..3e2d1a4 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/optionset/Liquibase.java @@ -15,14 +15,28 @@ package com.ing.data.cassandra.jdbc.optionset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; + /** * Option set implementation for Liquibase compatibility and flavour of JDBC. */ public class Liquibase extends AbstractOptionSet { + private static final Logger LOG = LoggerFactory.getLogger(AbstractOptionSet.class); @Override public String getCatalog() { - return null; + if (getConnection() == null) { + return null; + } + try { + return getConnection().getSchema(); + } catch (final SQLException e) { + LOG.warn("Unable to retrieve the schema name: {}", e.getMessage()); + return null; + } } @Override @@ -30,4 +44,13 @@ public int getSQLUpdateResponse() { return -1; } + @Override + public boolean shouldThrowExceptionOnRollback() { + return false; + } + + @Override + public boolean executeMultipleQueriesByStatementAsync() { + return false; + } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java b/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java index 4d0ccc9..a6c5189 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/optionset/OptionSet.java @@ -17,6 +17,8 @@ import com.ing.data.cassandra.jdbc.CassandraConnection; +import java.sql.SQLFeatureNotSupportedException; + /** * Option set for compliance mode. * Different use cases require one or more adjustments to the wrapper, to be compatible. @@ -38,6 +40,23 @@ public interface OptionSet { */ int getSQLUpdateResponse(); + /** + * Whether the rollback method on a Cassandra connection should throw a {@link SQLFeatureNotSupportedException} + * since Cassandra is always in auto-commit mode and does not support rollback. + * + * @return {@code true} if the method {@link CassandraConnection#rollback()} should throw an exception, + * {@code false} otherwise. + */ + boolean shouldThrowExceptionOnRollback(); + + /** + * Whether the statement execution methods must execute queries asynchronously when the statement contains several + * queries separated by semicolons. + * + * @return {@code true} if the queries must be executed asynchronously, {@code false} otherwise. + */ + boolean executeMultipleQueriesByStatementAsync(); + /** * Set referenced connection. See @{@link AbstractOptionSet}. * @param connection Connection to set. diff --git a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java index fe5601c..1e84d0a 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java @@ -407,12 +407,14 @@ void givenSessionToConnect_andLiquibaseCompliance() throws SQLException { .withLocalDatacenter("datacenter1") .build(); + final Liquibase liquibaseMode = new Liquibase(); final CassandraConnection jdbcConnection = - new CassandraConnection(session, KEYSPACE, ConsistencyLevel.ALL, false, new Liquibase()); + new CassandraConnection(session, KEYSPACE, ConsistencyLevel.ALL, false, liquibaseMode); + liquibaseMode.setConnection(jdbcConnection); final ResultSet resultSet = jdbcConnection.createStatement() .executeQuery("SELECT release_version FROM system.local"); assertNotNull(resultSet.getString("release_version")); - assertNull(jdbcConnection.getCatalog()); + assertEquals(KEYSPACE, jdbcConnection.getCatalog()); } @Test From 0bc4a76424cf6f7677ac2c8988066abf6d0d775b Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:35:47 +0200 Subject: [PATCH 11/21] Refactor getColumns() method --- .../jdbc/CassandraDatabaseMetaData.java | 9 +- .../cassandra/jdbc/MetadataResultSets.java | 185 --------------- .../AbstractMetadataResultSetBuilder.java | 111 +++++++-- .../CatalogMetadataResultSetBuilder.java | 2 +- .../ColumnMetadataResultSetBuilder.java | 210 ++++++++++++++++++ .../SchemaMetadataResultSetBuilder.java | 2 +- .../TableMetadataResultSetBuilder.java | 3 +- .../jdbc/MetadataResultSetsUnitTest.java | 3 +- ...tractMetadataResultSetBuilderUnitTest.java | 20 +- 9 files changed, 320 insertions(+), 225 deletions(-) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index c0b2e0e..a5c97e9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -17,6 +17,7 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.ing.data.cassandra.jdbc.metadata.CatalogMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.ColumnMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.SchemaMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; import org.apache.commons.lang3.StringUtils; @@ -192,13 +193,9 @@ public ResultSet getColumnPrivileges(final String catalog, final String schema, public ResultSet getColumns(final String catalog, final String schemaPattern, final String tableNamePattern, final String columnNamePattern) throws SQLException { checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. if (catalog == null || catalog.equals(this.connection.getCatalog())) { - this.statement.connection = connection; - String schemaNamePattern = schemaPattern; - if (schemaPattern == null) { - schemaNamePattern = this.connection.getSchema(); // limit to current schema if defined. - } - return MetadataResultSets.INSTANCE.makeColumns(statement, schemaNamePattern, tableNamePattern, + return new ColumnMetadataResultSetBuilder(this.statement).buildColumns(schemaPattern, tableNamePattern, columnNamePattern); } return CassandraResultSet.EMPTY_RESULT_SET; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java index c19ae23..5450364 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java @@ -63,18 +63,14 @@ public final class MetadataResultSets { static final String ASC_OR_DESC = "ASC_OR_DESC"; static final String AUTO_INCREMENT = "AUTO_INCREMENT"; static final String BASE_TYPE = "BASE_TYPE"; - static final String BUFFER_LENGTH = "BUFFER_LENGTH"; static final String CARDINALITY = "CARDINALITY"; static final String CASE_SENSITIVE = "CASE_SENSITIVE"; static final String CHAR_OCTET_LENGTH = "CHAR_OCTET_LENGTH"; static final String CLASS_NAME = "CLASS_NAME"; - static final String COLUMN_DEFAULT = "COLUMN_DEF"; static final String COLUMN_NAME = "COLUMN_NAME"; - static final String COLUMN_SIZE = "COLUMN_SIZE"; static final String COLUMN_TYPE = "COLUMN_TYPE"; static final String CREATE_PARAMS = "CREATE_PARAMS"; static final String DATA_TYPE = "DATA_TYPE"; - static final String DECIMAL_DIGITS = "DECIMAL_DIGITS"; static final String FILTER_CONDITION = "FILTER_CONDITION"; static final String FIXED_PRECISION_SCALE = "FIXED_PREC_SCALE"; static final String FUNCTION_CATALOG = "FUNCTION_CAT"; @@ -83,8 +79,6 @@ public final class MetadataResultSets { static final String FUNCTION_TYPE = "FUNCTION_TYPE"; static final String INDEX_NAME = "INDEX_NAME"; static final String INDEX_QUALIFIER = "INDEX_QUALIFIER"; - static final String IS_AUTOINCREMENT = "IS_AUTOINCREMENT"; - static final String IS_GENERATED_COLUMN = "IS_GENERATEDCOLUMN"; static final String IS_NULLABLE = "IS_NULLABLE"; static final String KEY_SEQ = "KEY_SEQ"; static final String LENGTH = "LENGTH"; @@ -93,7 +87,6 @@ public final class MetadataResultSets { static final String LOCALIZED_TYPE_NAME = "LOCAL_TYPE_NAME"; static final String MAXIMUM_SCALE = "MAXIMUM_SCALE"; static final String MINIMUM_SCALE = "MINIMUM_SCALE"; - static final String NO_VALUE = "NO"; static final String NON_UNIQUE = "NON_UNIQUE"; static final String NULLABLE = "NULLABLE"; static final String NUM_PRECISION_RADIX = "NUM_PREC_RADIX"; @@ -104,17 +97,12 @@ public final class MetadataResultSets { static final String RADIX = "RADIX"; static final String REMARKS = "REMARKS"; static final String SCALE = "SCALE"; - static final String SCOPE_CATALOG = "SCOPE_CATALOG"; - static final String SCOPE_SCHEMA = "SCOPE_SCHEMA"; - static final String SCOPE_TABLE = "SCOPE_TABLE"; static final String SEARCHABLE = "SEARCHABLE"; - static final String SOURCE_DATA_TYPE = "SOURCE_DATA_TYPE"; static final String SPECIFIC_NAME = "SPECIFIC_NAME"; static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB"; static final String TABLE = "TABLE"; static final String TABLE_CATALOG_SHORTNAME = "TABLE_CAT"; - static final String TABLE_CATALOG = "TABLE_CATALOG"; static final String TABLE_NAME = "TABLE_NAME"; static final String TABLE_SCHEMA = "TABLE_SCHEM"; static final String TYPE = "TYPE"; @@ -125,184 +113,11 @@ public final class MetadataResultSets { static final String WILDCARD_CHAR = "%"; static final String YES_VALUE = "YES"; - private static final Logger LOG = LoggerFactory.getLogger(MetadataResultSets.class); private MetadataResultSets() { // Private constructor to hide the public one. } - /** - * Builds a valid result set of the description of the table columns available in the given catalog (Cassandra - * cluster). This method is used to implement the method - * {@link DatabaseMetaData#getColumns(String, String, String, String)}. - * - *

- * The columns of this result set are: - *

    - *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name - * (if available).
  2. - *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is - * member of.
  4. - *
  5. TABLE_NAME String => table name.
  6. - *
  7. COLUMN_NAME String => column name.
  8. - *
  9. DATA_TYPE int => SQL type from {@link Types}.
  10. - *
  11. TYPE_NAME String => Data source dependent type name, for a UDT the type name is fully - * qualified.
  12. - *
  13. COLUMN_SIZE int => column size.
  14. - *
  15. BUFFER_LENGTH int => not used.
  16. - *
  17. DECIMAL_DIGITS int => the number of fractional digits, {@code null} is returned for data - * types where it is not applicable. Always {@code null} here.
  18. - *
  19. NUM_PREC_RADIX int => Radix (typically either 10 or 2).
  20. - *
  21. NULLABLE int => is {@code NULL} allowed: - *
      - *
    • {@link DatabaseMetaData#columnNoNulls} - might not allow {@code NULL} values
    • - *
    • {@link DatabaseMetaData#columnNullable} - definitely allows {@code NULL} values
    • - *
    • {@link DatabaseMetaData#columnNullableUnknown} - nullability unknown
    • - *
    Always {@link DatabaseMetaData#columnNoNulls} here. - *
  22. - *
  23. REMARKS String => comment describing column, may be {@code null}.
  24. - *
  25. COLUMN_DEF String => default value for the column, which should be interpreted as a string - * when the value is enclosed in single quotes, may be {@code null}. Always {@code null} here.
  26. - *
  27. SQL_DATA_TYPE int => not used.
  28. - *
  29. SQL_DATETIME_SUB int => is not used.
  30. - *
  31. CHAR_OCTET_LENGTH int => for char types the maximum number of bytes in the column.
  32. - *
  33. ORDINAL_POSITION int => index of column in table (starting at 1).
  34. - *
  35. IS_NULLABLE String => ISO rules are used to determine the nullability for a column: - *
      - *
    • YES - if the parameter can include {@code NULL}s
    • - *
    • NO - if the parameter cannot include {@code NULL}s
    • - *
    • empty string - if the nullability for the parameter is unknown
    • - *
    Always empty here. - *
  36. - *
  37. SCOPE_CATALOG String => catalog of table that is the scope of a reference attribute - * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
  38. - *
  39. SCOPE_SCHEMA String => schema of table that is the scope of a reference attribute - * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
  40. - *
  41. SCOPE_TABLE String => table name that is the scope of a reference attribute - * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
  42. - *
  43. SOURCE_DATA_TYPE short => source type of a distinct type or user-generated Ref type, SQL type - * from {@link Types} ({@code null} if {@code DATA_TYPE} isn't {@code DISTINCT} or user-generated - * {@code REF}). Always {@code null} here.
  44. - *
  45. IS_AUTOINCREMENT String => Indicates whether this column is auto-incremented: - *
      - *
    • YES - if the column is auto-incremented
    • - *
    • NO - if the column is not auto-incremented
    • - *
    • empty string - if it cannot be determined whether the column is auto incremented - * parameter is unknown
    • - *
    Always {@code NO} here. - *
  46. - *
  47. IS_GENERATEDCOLUMN String => Indicates whether this is a generated column: - *
      - *
    • YES - if this is a generated column
    • - *
    • NO - if this is not a generated column
    • - *
    • empty string - if it cannot be determined whether this is a generated column
    • - *
    Always {@code NO} here. - *
  48. - *
- *

- * - * @param statement The statement. - * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the database; - * {@code ""} retrieves those without a schema and {@code null} means that the schema name - * should not be used to narrow down the search. - * @param tableNamePattern A table name pattern. It must match the table name as it is stored in the database. - * @param columnNamePattern A column name pattern. It must match the column name as it is stored in the database. - * @return A valid result set for implementation of - * {@link DatabaseMetaData#getColumns(String, String, String, String)}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeColumns(final CassandraStatement statement, final String schemaPattern, - final String tableNamePattern, final String columnNamePattern) - throws SQLException { - String originalSchemaPattern = schemaPattern; - final ArrayList schemas = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - if (WILDCARD_CHAR.equals(schemaPattern)) { - originalSchemaPattern = keyspaceMetadata.getName().asInternal(); - } - - if (originalSchemaPattern == null - || originalSchemaPattern.equals(keyspaceMetadata.getName().asInternal())) { - final Map tables = keyspaceMetadata.getTables(); - - for (final Map.Entry table : tables.entrySet()) { - final TableMetadata tableMetadata = table.getValue(); - if (WILDCARD_CHAR.equals(tableNamePattern) || tableNamePattern == null - || tableNamePattern.equals(tableMetadata.getName().asInternal())) { - final Map columns = tableMetadata.getColumns(); - - int columnIndex = 1; - for (final Map.Entry column : columns.entrySet()) { - final ColumnMetadata columnMetadata = column.getValue(); - if (WILDCARD_CHAR.equals(columnNamePattern) || columnNamePattern == null - || columnNamePattern.equals(columnMetadata.getName().asInternal())) { - final AbstractJdbcType jdbcEquivalentType = - getTypeForComparator(columnMetadata.getType().toString()); - - // Define value of COLUMN_SIZE. - int columnSize = DEFAULT_PRECISION; - if (jdbcEquivalentType != null) { - columnSize = jdbcEquivalentType.getPrecision(null); - } - - // Define value of NUM_PREC_RADIX. - int radix = 2; - if (jdbcEquivalentType != null && (jdbcEquivalentType.getJdbcType() == Types.DECIMAL - || jdbcEquivalentType.getJdbcType() == Types.NUMERIC)) { - radix = 10; - } - - // Define value of DATA_TYPE. - int jdbcType = Types.OTHER; - try { - jdbcType = getTypeForComparator(columnMetadata.getType().toString()) - .getJdbcType(); - } catch (final Exception e) { - LOG.warn("Unable to get JDBC type for comparator [{}]: {}", - columnMetadata.getType(), e.getMessage()); - } - - // Build the metadata row. - final MetadataRow row = new MetadataRow() - .addEntry(TABLE_CATALOG_SHORTNAME, statement.connection.getCatalog()) - .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) - .addEntry(COLUMN_NAME, columnMetadata.getName().asInternal()) - .addEntry(DATA_TYPE, String.valueOf(jdbcType)) - .addEntry(TYPE_NAME, columnMetadata.getType().toString()) - .addEntry(COLUMN_SIZE, String.valueOf(columnSize)) - .addEntry(BUFFER_LENGTH, String.valueOf(0)) - .addEntry(DECIMAL_DIGITS, null) - .addEntry(NUM_PRECISION_RADIX, String.valueOf(radix)) - .addEntry(NULLABLE, String.valueOf(DatabaseMetaData.columnNoNulls)) - .addEntry(REMARKS, column.toString()) - .addEntry(COLUMN_DEFAULT, null) - .addEntry(SQL_DATA_TYPE, null) - .addEntry(SQL_DATETIME_SUB, null) - .addEntry(CHAR_OCTET_LENGTH, String.valueOf(Integer.MAX_VALUE)) - .addEntry(ORDINAL_POSITION, String.valueOf(columnIndex)) - .addEntry(IS_NULLABLE, StringUtils.EMPTY) - .addEntry(SCOPE_CATALOG, null) - .addEntry(SCOPE_SCHEMA, null) - .addEntry(SCOPE_TABLE, null) - .addEntry(SOURCE_DATA_TYPE, null) - .addEntry(IS_AUTOINCREMENT, NO_VALUE) - .addEntry(IS_GENERATED_COLUMN, NO_VALUE); - schemas.add(row); - columnIndex++; - } - } - } - } - } - } - - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); - } - /** * Builds a valid result set of the description of given table's indices and statistics. * This method is used to implement the method diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java index 7334efd..397de69 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java @@ -16,6 +16,7 @@ package com.ing.data.cassandra.jdbc.metadata; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.ing.data.cassandra.jdbc.CassandraConnection; @@ -25,6 +26,8 @@ import java.sql.SQLException; import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Consumer; /** @@ -116,32 +119,102 @@ protected AbstractMetadataResultSetBuilder(final CassandraStatement statement) t this.connection = statement.getCassandraConnection(); } - private boolean matchesPattern(final String pattern, final String against) { - return against.matches(String.format("^%s$", pattern.replaceAll("%", ".*"))); + /** + * Checks whether the specified pattern (potentially using SQL wildcard '%') matches the given string. + *

+ * For example, the pattern {@code a%_table} will be transformed to the regular expression {@code ^a.*_table$} + * and will match {@code any_table} but not {@code main_table}. + *

+ * + * @param pattern The SQL pattern to check. + * @param testedValue The tested string. + * @return {@code true} if the pattern matches the tested string, {@code false} otherwise. + */ + private boolean matchesPattern(final String pattern, final String testedValue) { + return testedValue.matches(String.format("^%s$", pattern.replaceAll("%", ".*"))); } - void filterBySchemaNamePattern(final String schemaNamePattern, final Consumer consumer) { - final Map keyspacesMetadata = - this.connection.getClusterMetadata().getKeyspaces(); - for (final Map.Entry keyspace : keyspacesMetadata.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - if (schemaNamePattern == null || StringUtils.EMPTY.equals(schemaNamePattern) - || matchesPattern(schemaNamePattern, keyspaceMetadata.getName().asInternal())) { - consumer.accept(keyspaceMetadata); - } - } + /** + * Executes a {@link Consumer} function on each {@link KeyspaceMetadata} instance corresponding to a keyspace + * matching the specified schema name pattern. + * + * @implNote The pattern matches a keyspace name if the pattern is {@code null} or empty or if the function + * {@link #matchesPattern(String, String)} returns {@code true}. + * @param schemaNamePattern The schema name pattern. + * @param consumer The applied consumer function when there is a pattern match. + * @param altConsumer The applied consumer function when there is no pattern match.If {@code null}, a no-op + * consumer is used. + */ + @SuppressWarnings("SameParameterValue") + void filterBySchemaNamePattern(final String schemaNamePattern, final Consumer consumer, + final Consumer altConsumer) { + filterByPattern(schemaNamePattern, this.connection.getClusterMetadata().getKeyspaces(), + (pattern, keyspaceMetadata) -> pattern == null || StringUtils.EMPTY.equals(pattern) + || matchesPattern(pattern, keyspaceMetadata.getName().asInternal()), consumer, + Optional.ofNullable(altConsumer).orElse(noOpConsumer())); } + /** + * Executes a {@link Consumer} function on each {@link TableMetadata} instance corresponding to a table + * matching the specified table name pattern in the specified keyspace. + * + * @implNote The pattern matches a table name if the pattern is {@code null} or if the function + * {@link #matchesPattern(String, String)} returns {@code true}. + * @param tableNamePattern The table name pattern. + * @param keyspaceMetadata The keyspace on which the filter is applied: only the tables present in this keyspace + * are filtered. + * @param consumer The applied consumer function when there is a pattern match. + * @param altConsumer The applied consumer function when there is no pattern match. If {@code null}, a no-op + * consumer is used. + */ + @SuppressWarnings("SameParameterValue") void filterByTableNamePattern(final String tableNamePattern, final KeyspaceMetadata keyspaceMetadata, - final Consumer consumer) { - final Map tablesMetadata = keyspaceMetadata.getTables(); - for (final Map.Entry table : tablesMetadata.entrySet()) { - final TableMetadata tableMetadata = table.getValue(); - if (tableNamePattern == null - || matchesPattern(tableNamePattern, tableMetadata.getName().asInternal())) { - consumer.accept(tableMetadata); + final Consumer consumer, final Consumer altConsumer) { + filterByPattern(tableNamePattern, keyspaceMetadata.getTables(), + (pattern, tableMetadata) -> pattern == null + || matchesPattern(pattern, tableMetadata.getName().asInternal()), consumer, + Optional.ofNullable(altConsumer).orElse(noOpConsumer())); + } + + /** + * Executes a {@link Consumer} function on each {@link ColumnMetadata} instance corresponding to a column + * matching the specified column name pattern in the specified table. + * + * @implNote The pattern matches a column name if the pattern is {@code null} or if the function + * {@link #matchesPattern(String, String)} returns {@code true}. + * @param columnNamePattern The column name pattern. + * @param tableMetadata The table on which the filter is applied: only the columns present in this table + * are filtered. + * @param consumer The applied consumer function when there is a pattern match. + * @param altConsumer The applied consumer function when there is no pattern match. If {@code null}, a no-op + * consumer is used. + */ + void filterByColumnNamePattern(final String columnNamePattern, final TableMetadata tableMetadata, + final Consumer consumer, + final Consumer altConsumer) { + filterByPattern(columnNamePattern, tableMetadata.getColumns(), + (pattern, columnMetadata) -> pattern == null + || matchesPattern(pattern, columnMetadata.getName().asInternal()), consumer, + Optional.ofNullable(altConsumer).orElse(noOpConsumer())); + } + + void filterByPattern(final String pattern, final Map metadataMap, + final BiFunction patternMatcher, + final Consumer consumer, final Consumer altConsumer) { + for (final Map.Entry entry : metadataMap.entrySet()) { + final M entityMetadata = entry.getValue(); + if (patternMatcher.apply(pattern, entityMetadata)) { + consumer.accept(entityMetadata); + } else { + altConsumer.accept(entityMetadata); } } } + Consumer noOpConsumer() { + return entityMetadata -> { + // No-op consumer: do nothing + }; + } + } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java index 50cd36a..f7f22d9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java @@ -55,7 +55,7 @@ public CassandraMetadataResultSet buildCatalogs() throws SQLException { final MetadataRow row = new MetadataRow().addEntry(TABLE_CATALOG_SHORTNAME, this.statement.getConnection().getCatalog()); catalogs.add(row); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(catalogs)); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(catalogs)); } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java new file mode 100644 index 0000000..40f3fc5 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java @@ -0,0 +1,210 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; +import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; + +/** + * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to columns. + */ +public class ColumnMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(ColumnMetadataResultSetBuilder.class); + + /** + * Constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + public ColumnMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + + /** + * Builds a valid result set of the description of the table columns available in the given catalog (Cassandra + * cluster). + * This method is used to implement the method {@link DatabaseMetaData#getColumns(String, String, String, String)}. + *

+ * Only table descriptions matching the catalog, schema, table and column name criteria are returned. They are + * ordered by {@code TABLE_CAT}, {@code TABLE_SCHEM}, {@code TABLE_NAME} and {@code ORDINAL_POSITION}. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name + * (if available).
  2. + *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is + * member of.
  4. + *
  5. TABLE_NAME String => table name.
  6. + *
  7. COLUMN_NAME String => column name.
  8. + *
  9. DATA_TYPE int => SQL type from {@link Types}.
  10. + *
  11. TYPE_NAME String => Data source dependent type name, for a UDT the type name is fully + * qualified.
  12. + *
  13. COLUMN_SIZE int => column size.
  14. + *
  15. BUFFER_LENGTH int => not used: always 0 here.
  16. + *
  17. DECIMAL_DIGITS int => the number of fractional digits, {@code null} is returned for data + * types where it is not applicable. Always {@code null} here.
  18. + *
  19. NUM_PREC_RADIX int => Radix (typically either 10 or 2).
  20. + *
  21. NULLABLE int => is {@code NULL} allowed: + *
      + *
    • {@link DatabaseMetaData#columnNoNulls} - might not allow {@code NULL} values
    • + *
    • {@link DatabaseMetaData#columnNullable} - definitely allows {@code NULL} values
    • + *
    • {@link DatabaseMetaData#columnNullableUnknown} - nullability unknown
    • + *
    Always {@link DatabaseMetaData#columnNoNulls} here. + *
  22. + *
  23. REMARKS String => comment describing column, may be {@code null}: + * always {@code null} here since comments on columns does not exist in Cassandra.
  24. + *
  25. COLUMN_DEF String => default value for the column, which should be interpreted as a string + * when the value is enclosed in single quotes, may be {@code null}. Always {@code null} here.
  26. + *
  27. SQL_DATA_TYPE int => not used: always {@code null} here.
  28. + *
  29. SQL_DATETIME_SUB int => is not used: always {@code null} here.
  30. + *
  31. CHAR_OCTET_LENGTH int => for char types the maximum number of bytes in the column.
  32. + *
  33. ORDINAL_POSITION int => index of column in table (starting at 1).
  34. + *
  35. IS_NULLABLE String => ISO rules are used to determine the nullability for a column: + *
      + *
    • YES - if the parameter can include {@code NULL}s
    • + *
    • NO - if the parameter cannot include {@code NULL}s
    • + *
    • empty string - if the nullability for the parameter is unknown
    • + *
    Always empty here. + *
  36. + *
  37. SCOPE_CATALOG String => catalog of table that is the scope of a reference attribute + * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
  38. + *
  39. SCOPE_SCHEMA String => schema of table that is the scope of a reference attribute + * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
  40. + *
  41. SCOPE_TABLE String => table name that is the scope of a reference attribute + * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
  42. + *
  43. SOURCE_DATA_TYPE short => source type of a distinct type or user-generated Ref type, SQL type + * from {@link Types} ({@code null} if {@code DATA_TYPE} isn't {@code DISTINCT} or user-generated + * {@code REF}). Always {@code null} here.
  44. + *
  45. IS_AUTOINCREMENT String => Indicates whether this column is auto-incremented: + *
      + *
    • YES - if the column is auto-incremented
    • + *
    • NO - if the column is not auto-incremented
    • + *
    • empty string - if it cannot be determined whether the column is auto incremented + * parameter is unknown
    • + *
    Always {@code NO} here. + *
  46. + *
  47. IS_GENERATEDCOLUMN String => Indicates whether this is a generated column: + *
      + *
    • YES - if this is a generated column
    • + *
    • NO - if this is not a generated column
    • + *
    • empty string - if it cannot be determined whether this is a generated column
    • + *
    Always {@code NO} here. + *
  48. + *
+ * + * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the database; + * {@code ""} retrieves those without a schema and {@code null} means that the schema name + * should not be used to narrow the search. Using {@code ""} as the same effect as + * {@code null} because here the schema corresponds to the keyspace and Cassandra tables + * cannot be defined outside a keyspace. + * @param tableNamePattern A table name pattern. It must match the table name as it is stored in the database. + * @param columnNamePattern A column name pattern. It must match the column name as it is stored in the database. + * @return A valid result set for implementation of + * {@link DatabaseMetaData#getColumns(String, String, String, String)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildColumns(final String schemaPattern, + final String tableNamePattern, + final String columnNamePattern) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList columns = new ArrayList<>(); + + + filterBySchemaNamePattern(schemaPattern, keyspaceMetadata -> + filterByTableNamePattern(tableNamePattern, keyspaceMetadata, tableMetadata -> { + final AtomicInteger colIndex = new AtomicInteger(1); // The ordinal positions start at 1. + filterByColumnNamePattern(columnNamePattern, tableMetadata, columnMetadata -> { + final AbstractJdbcType jdbcEquivalentType = + getTypeForComparator(columnMetadata.getType().toString()); + + // Define value of COLUMN_SIZE. + int columnSize = DEFAULT_PRECISION; + if (jdbcEquivalentType != null) { + columnSize = jdbcEquivalentType.getPrecision(null); + } + + // Define value of NUM_PREC_RADIX. + int radix = 2; + if (jdbcEquivalentType != null && (jdbcEquivalentType.getJdbcType() == Types.DECIMAL + || jdbcEquivalentType.getJdbcType() == Types.NUMERIC)) { + radix = 10; + } + + // Define value of DATA_TYPE. + int jdbcType = Types.OTHER; + try { + jdbcType = getTypeForComparator(columnMetadata.getType().toString()) + .getJdbcType(); + } catch (final Exception e) { + LOG.warn("Unable to get JDBC type for comparator [{}]: {}", + columnMetadata.getType(), e.getMessage()); + } + + final MetadataRow row = new MetadataRow() + .addEntry(TABLE_CATALOG_SHORTNAME, catalog) + .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) + .addEntry(COLUMN_NAME, columnMetadata.getName().asInternal()) + .addEntry(DATA_TYPE, String.valueOf(jdbcType)) + .addEntry(TYPE_NAME, columnMetadata.getType().toString()) + .addEntry(COLUMN_SIZE, String.valueOf(columnSize)) + .addEntry(BUFFER_LENGTH, String.valueOf(0)) + .addEntry(DECIMAL_DIGITS, null) + .addEntry(NUM_PRECISION_RADIX, String.valueOf(radix)) + .addEntry(NULLABLE, String.valueOf(DatabaseMetaData.columnNoNulls)) + .addEntry(REMARKS, null) + .addEntry(COLUMN_DEFAULT, null) + .addEntry(SQL_DATA_TYPE, null) + .addEntry(SQL_DATETIME_SUB, null) + .addEntry(CHAR_OCTET_LENGTH, String.valueOf(Integer.MAX_VALUE)) + .addEntry(ORDINAL_POSITION, String.valueOf(colIndex.getAndIncrement())) + .addEntry(IS_NULLABLE, StringUtils.EMPTY) + .addEntry(SCOPE_CATALOG, null) + .addEntry(SCOPE_SCHEMA, null) + .addEntry(SCOPE_TABLE, null) + .addEntry(SOURCE_DATA_TYPE, null) + .addEntry(IS_AUTOINCREMENT, NO_VALUE) + .addEntry(IS_GENERATED_COLUMN, NO_VALUE); + columns.add(row); + }, columnMetadata -> colIndex.getAndIncrement()); + }, null), null); + + // Results should all have the same TABLE_CAT, so just sort them by TABLE_SCHEM, TABLE_NAME then + // ORDINAL_POSITION. + columns.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TABLE_SCHEMA)) + .thenComparing(row -> ((MetadataRow) row).getString(TABLE_NAME)) + .thenComparing(row -> ((MetadataRow) row).getString(ORDINAL_POSITION))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(columns)); + } + +} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java index ece6e96..5cad631 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/SchemaMetadataResultSetBuilder.java @@ -69,7 +69,7 @@ public CassandraMetadataResultSet buildSchemas(final String schemaPattern) .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) .addEntry(TABLE_CATALOG, catalog); schemas.add(row); - }); + }, null); // Results should all have the same TABLE_CATALOG, so just sort them by TABLE_SCHEM. schemas.sort(Comparator.comparing(row -> row.getString(TABLE_SCHEMA))); diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java index 781ee32..a34316a 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java @@ -116,8 +116,7 @@ public CassandraMetadataResultSet buildTables(final String schemaPattern, .addEntry(SELF_REFERENCING_COL_NAME, null) .addEntry(REF_GENERATION, null); tables.add(row); - }) - ); + }, null), null); // Results should all have the same TABLE_CAT, so just sort them by TABLE_SCHEM then TABLE_NAME. tables.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TABLE_SCHEMA)) diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index a37c77d..a380cbb 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -15,6 +15,7 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.ing.data.cassandra.jdbc.metadata.CatalogMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.ColumnMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.SchemaMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; @@ -181,7 +182,7 @@ void givenStatement_whenMakeSchemas_returnExpectedResultSet() throws SQLExceptio @Test void givenStatement_whenMakeColumns_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeColumns(statement, KEYSPACE, "cf_test1", null); + final ResultSet result = new ColumnMetadataResultSetBuilder(statement).buildColumns(KEYSPACE, "cf_test1", null); assertNotNull(result); assertEquals(24, result.getMetaData().getColumnCount()); assertEquals("TABLE_CAT", result.getMetaData().getColumnName(1)); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java index 9e262b7..9cebf64 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java @@ -71,34 +71,34 @@ void givenSchemaPattern_whenApplySchemaFiltering_returnExpectedResultSet() throw final Set filteredSchemas = new HashSet<>(); sut.filterBySchemaNamePattern(StringUtils.EMPTY, - keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal()), null); log.info("Schemas matching '': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(4)); assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks", "another")); filteredSchemas.clear(); sut.filterBySchemaNamePattern(null, - keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal()), null); log.info("Schemas matching null: {}", filteredSchemas); assertThat(filteredSchemas, hasSize(4)); assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks", "another")); filteredSchemas.clear(); sut.filterBySchemaNamePattern("ks", - keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal()), null); log.info("Schemas matching 'ks': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(0)); filteredSchemas.clear(); sut.filterBySchemaNamePattern("ks%", - keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal()), null); log.info("Schemas matching 'ks%': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(2)); assertThat(filteredSchemas, hasItems("ks1", "ks2")); filteredSchemas.clear(); sut.filterBySchemaNamePattern("%ks%", - keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal())); + keyspaceMetadata -> filteredSchemas.add(keyspaceMetadata.getName().asInternal()), null); log.info("Schemas matching '%ks%': {}", filteredSchemas); assertThat(filteredSchemas, hasSize(3)); assertThat(filteredSchemas, hasItems("ks1", "ks2", "test_ks")); @@ -122,33 +122,33 @@ void givenTablePattern_whenApplyTableFiltering_returnExpectedResultSet() throws final Set filteredTables = new HashSet<>(); sut.filterByTableNamePattern(StringUtils.EMPTY, ksTestMetadata, - tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal()), null); log.info("Tables matching '': {}", filteredTables); assertThat(filteredTables, empty()); filteredTables.clear(); sut.filterByTableNamePattern(null, ksTestMetadata, - tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal()), null); log.info("Tables matching null: {}", filteredTables); assertThat(filteredTables, hasSize(4)); assertThat(filteredTables, hasItems("cf1", "cf2", "another_table", "test_cf")); filteredTables.clear(); sut.filterByTableNamePattern("cf", ksTestMetadata, - tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal()), null); log.info("Tables matching 'cf': {}", filteredTables); assertThat(filteredTables, empty()); filteredTables.clear(); sut.filterByTableNamePattern("cf%", ksTestMetadata, - tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal()), null); log.info("Tables matching 'cf%': {}", filteredTables); assertThat(filteredTables, hasSize(2)); assertThat(filteredTables, hasItems("cf1", "cf2")); filteredTables.clear(); sut.filterByTableNamePattern("%cf%", ksTestMetadata, - tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal())); + tableMetadata -> filteredTables.add(tableMetadata.getName().asInternal()), null); log.info("Tables matching '%cf%': {}", filteredTables); assertThat(filteredTables, hasSize(3)); assertThat(filteredTables, hasItems("cf1", "cf2", "test_cf")); From 39544b23586c771b0a1e3e8dd5ffbf7bbebf3719 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Thu, 7 Sep 2023 17:28:30 +0200 Subject: [PATCH 12/21] Refactor getIndexInfo() and getPrimaryKeys() methods --- .../jdbc/CassandraDatabaseMetaData.java | 14 +- .../cassandra/jdbc/MetadataResultSets.java | 177 ------------------ .../TableMetadataResultSetBuilder.java | 152 +++++++++++++++ .../jdbc/MetadataResultSetsUnitTest.java | 31 ++- ...tractMetadataResultSetBuilderUnitTest.java | 64 +++++++ 5 files changed, 245 insertions(+), 193 deletions(-) diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index a5c97e9..0131703 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -386,12 +386,9 @@ public ResultSet getImportedKeys(final String catalog, final String schema, fina public ResultSet getIndexInfo(final String catalog, final String schema, final String table, final boolean unique, final boolean approximate) throws SQLException { checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. if (catalog == null || catalog.equals(this.connection.getCatalog())) { - String schemaName = schema; - if (schema == null) { - schemaName = this.connection.getSchema(); // limit to current schema if defined. - } - return MetadataResultSets.INSTANCE.makeIndexes(this.statement, schemaName, table, unique, approximate); + return new TableMetadataResultSetBuilder(this.statement).buildIndexes(schema, table, unique, approximate); } return CassandraResultSet.EMPTY_RESULT_SET; } @@ -517,12 +514,9 @@ public String getNumericFunctions() throws SQLException { @Override public ResultSet getPrimaryKeys(final String catalog, final String schema, final String table) throws SQLException { checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. if (catalog == null || catalog.equals(this.connection.getCatalog())) { - String schemaName = schema; - if (schema == null) { - schemaName = this.connection.getSchema(); // limit to current schema if defined. - } - return MetadataResultSets.INSTANCE.makePrimaryKeys(this.statement, schemaName, table); + return new TableMetadataResultSetBuilder(this.statement).buildPrimaryKeys(schema, table); } return CassandraResultSet.EMPTY_RESULT_SET; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java index 5450364..f5a388b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java @@ -17,20 +17,15 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; -import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.ing.data.cassandra.jdbc.metadata.MetadataResultSet; import com.ing.data.cassandra.jdbc.metadata.MetadataRow; import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.sql.DatabaseMetaData; import java.sql.SQLException; @@ -41,7 +36,6 @@ import java.util.List; import java.util.Map; -import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; import static java.sql.DatabaseMetaData.functionColumnIn; @@ -60,10 +54,8 @@ public final class MetadataResultSets { */ public static final MetadataResultSets INSTANCE = new MetadataResultSets(); - static final String ASC_OR_DESC = "ASC_OR_DESC"; static final String AUTO_INCREMENT = "AUTO_INCREMENT"; static final String BASE_TYPE = "BASE_TYPE"; - static final String CARDINALITY = "CARDINALITY"; static final String CASE_SENSITIVE = "CASE_SENSITIVE"; static final String CHAR_OCTET_LENGTH = "CHAR_OCTET_LENGTH"; static final String CLASS_NAME = "CLASS_NAME"; @@ -71,29 +63,22 @@ public final class MetadataResultSets { static final String COLUMN_TYPE = "COLUMN_TYPE"; static final String CREATE_PARAMS = "CREATE_PARAMS"; static final String DATA_TYPE = "DATA_TYPE"; - static final String FILTER_CONDITION = "FILTER_CONDITION"; static final String FIXED_PRECISION_SCALE = "FIXED_PREC_SCALE"; static final String FUNCTION_CATALOG = "FUNCTION_CAT"; static final String FUNCTION_NAME = "FUNCTION_NAME"; static final String FUNCTION_SCHEMA = "FUNCTION_SCHEM"; static final String FUNCTION_TYPE = "FUNCTION_TYPE"; - static final String INDEX_NAME = "INDEX_NAME"; - static final String INDEX_QUALIFIER = "INDEX_QUALIFIER"; static final String IS_NULLABLE = "IS_NULLABLE"; - static final String KEY_SEQ = "KEY_SEQ"; static final String LENGTH = "LENGTH"; static final String LITERAL_PREFIX = "LITERAL_PREFIX"; static final String LITERAL_SUFFIX = "LITERAL_SUFFIX"; static final String LOCALIZED_TYPE_NAME = "LOCAL_TYPE_NAME"; static final String MAXIMUM_SCALE = "MAXIMUM_SCALE"; static final String MINIMUM_SCALE = "MINIMUM_SCALE"; - static final String NON_UNIQUE = "NON_UNIQUE"; static final String NULLABLE = "NULLABLE"; static final String NUM_PRECISION_RADIX = "NUM_PREC_RADIX"; static final String ORDINAL_POSITION = "ORDINAL_POSITION"; - static final String PAGES = "PAGES"; static final String PRECISION = "PRECISION"; - static final String PRIMARY_KEY_NAME = "PK_NAME"; static final String RADIX = "RADIX"; static final String REMARKS = "REMARKS"; static final String SCALE = "SCALE"; @@ -102,10 +87,6 @@ public final class MetadataResultSets { static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB"; static final String TABLE = "TABLE"; - static final String TABLE_CATALOG_SHORTNAME = "TABLE_CAT"; - static final String TABLE_NAME = "TABLE_NAME"; - static final String TABLE_SCHEMA = "TABLE_SCHEM"; - static final String TYPE = "TYPE"; static final String TYPE_CATALOG = "TYPE_CAT"; static final String TYPE_NAME = "TYPE_NAME"; static final String TYPE_SCHEMA = "TYPE_SCHEM"; @@ -118,164 +99,6 @@ private MetadataResultSets() { // Private constructor to hide the public one. } - /** - * Builds a valid result set of the description of given table's indices and statistics. - * This method is used to implement the method - * {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}. - *

- * The columns of this result set are: - *

    - *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name - * (if available).
  2. - *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is - * member of.
  4. - *
  5. TABLE_NAME String => table name.
  6. - *
  7. NON_UNIQUE boolean => Can index values be non-unique, {@code false} when {@code TYPE} is - * {@link DatabaseMetaData#tableIndexStatistic}. Always {@code true} here.
  8. - *
  9. INDEX_QUALIFIER String => index catalog, {@code null} when {@code TYPE} is - * {@link DatabaseMetaData#tableIndexStatistic}.
  10. - *
  11. INDEX_NAME String => index name, {@code null} when {@code TYPE} is - * {@link DatabaseMetaData#tableIndexStatistic}.
  12. - *
  13. TYPE short => index type: - *
      - *
    • {@link DatabaseMetaData#tableIndexStatistic} - this identifies table statistics that are - * returned in conjunction with a table's index descriptions
    • - *
    • {@link DatabaseMetaData#tableIndexClustered} - this is a clustered index
    • - *
    • {@link DatabaseMetaData#tableIndexHashed} - this is a hashed index
    • - *
    • {@link DatabaseMetaData#tableIndexOther} - this is some other style of index
    • - *
    Always {@link DatabaseMetaData#tableIndexHashed} here. - *
  14. - *
  15. ORDINAL_POSITION short => column sequence number within index; zero when {@code TYPE} is - * {@link DatabaseMetaData#tableIndexStatistic}. Always 1 here.
  16. - *
  17. COLUMN_NAME String => column name, {@code null} when {@code TYPE} is - * {@link DatabaseMetaData#tableIndexStatistic}.
  18. - *
  19. ASC_OR_DESC String => column sort sequence, "A" means ascending, "D" means descending, may be - * {@code null} if sort sequence is not supported or when {@code TYPE} is - * {@link DatabaseMetaData#tableIndexStatistic}. Always {@code null} here.
  20. - *
  21. CARDINALITY int => When {@code TYPE} is {@link DatabaseMetaData#tableIndexStatistic}, then - * this is the number of rows in the table; otherwise, it is the number of unique values in the index. - * Always -1 here.
  22. - *
  23. PAGES int => When {@code TYPE} is {@link DatabaseMetaData#tableIndexStatistic}, then - * this is the number of pages used for the table; otherwise, it is the number of pages used for the - * current index. Always -1 here.
  24. - *
  25. FILTER_CONDITION String => Filter condition, if any: always {@code null} here.
  26. - *
- *

- * - * @param statement The statement. - * @param schema A schema name. It must match the schema name as it is stored in the database; {@code ""} - * retrieves those without a schema and {@code null} means that the schema name should not be - * used to narrow down the search. - * @param tableName A table name. It must match the table name as it is stored in the database. - * @param unique when {@code true}, return only indices for unique values; when {@code false}, return - * indices regardless of whether unique or not. - * @param approximate when {@code true}, result is allowed to reflect approximate or out of data values; when - * {@code false}, results are requested to be accurate. - * @return A valid result set for implementation of - * {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - @SuppressWarnings("unused") - public CassandraMetadataResultSet makeIndexes(final CassandraStatement statement, final String schema, - final String tableName, final boolean unique, - final boolean approximate) throws SQLException { - final ArrayList schemas = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - if (schema.equals(keyspaceMetadata.getName().asInternal())) { - final Map tables = keyspaceMetadata.getTables(); - - for (final Map.Entry table : tables.entrySet()) { - final TableMetadata tableMetadata = table.getValue(); - if (tableName.equals(tableMetadata.getName().asInternal())) { - for (final Map.Entry index - : tableMetadata.getIndexes().entrySet()) { - final IndexMetadata indexMetadata = index.getValue(); - final MetadataRow row = new MetadataRow() - .addEntry(TABLE_CATALOG_SHORTNAME, statement.connection.getCatalog()) - .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) - .addEntry(NON_UNIQUE, Boolean.TRUE.toString()) - .addEntry(INDEX_QUALIFIER, statement.connection.getCatalog()) - .addEntry(INDEX_NAME, indexMetadata.getName().asInternal()) - .addEntry(TYPE, String.valueOf(DatabaseMetaData.tableIndexHashed)) - .addEntry(ORDINAL_POSITION, String.valueOf(1)) - .addEntry(COLUMN_NAME, indexMetadata.getTarget()) - .addEntry(ASC_OR_DESC, null) - .addEntry(CARDINALITY, String.valueOf(-1)) - .addEntry(PAGES, String.valueOf(-1)) - .addEntry(FILTER_CONDITION, null); - schemas.add(row); - } - } - } - } - } - - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); - } - - /** - * Builds a valid result set of the description of given table's primary key columns. - * This method is used to implement the method {@link DatabaseMetaData#getPrimaryKeys(String, String, String)}. - *

- * The columns of this result set are: - *

    - *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name - * (if available).
  2. - *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is - * member of.
  4. - *
  5. TABLE_NAME String => table name.
  6. - *
  7. COLUMN_NAME String => column name.
  8. - *
  9. KEY_SEQ short => sequence number within primary key (a value of 1 represents the first column - * of the primary key, a value of 2 would represent the second column within the primary key).
  10. - *
  11. PK_NAME String => primary key name: always {@code null} here.
  12. - *
- *

- * - * @param statement The statement. - * @param schema A schema name. It must match the schema name as it is stored in the database; {@code ""} - * retrieves those without a schema and {@code null} means that the schema name should not be - * used to narrow down the search. - * @param tableName A table name. It must match the table name as it is stored in the database. - * @return A valid result set for implementation of {@link DatabaseMetaData#getPrimaryKeys(String, String, String)}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makePrimaryKeys(final CassandraStatement statement, final String schema, - final String tableName) throws SQLException { - final ArrayList schemas = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - if (schema.equals(keyspaceMetadata.getName().asInternal())) { - final Map tables = keyspaceMetadata.getTables(); - - for (final Map.Entry table : tables.entrySet()) { - final TableMetadata tableMetadata = table.getValue(); - if (tableName.equals(tableMetadata.getName().asInternal())) { - int seq = 0; - for (final ColumnMetadata col : tableMetadata.getPrimaryKey()) { - final MetadataRow row = new MetadataRow() - .addEntry(TABLE_CATALOG_SHORTNAME, statement.connection.getCatalog()) - .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) - .addEntry(COLUMN_NAME, col.getName().asInternal()) - .addEntry(KEY_SEQ, String.valueOf(seq)) - .addEntry(PRIMARY_KEY_NAME, null); - schemas.add(row); - seq++; - } - } - } - } - } - - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(schemas)); - } - /** * Builds a valid result set of the description of the user-defined types (UDTs) defined in a particular schema. * This method is used to implement the method {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java index a34316a..6a78693 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java @@ -16,6 +16,8 @@ package com.ing.data.cassandra.jdbc.metadata; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; import com.ing.data.cassandra.jdbc.CassandraStatement; @@ -23,6 +25,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Comparator; +import java.util.Map; /** * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to tables. @@ -124,4 +127,153 @@ public CassandraMetadataResultSet buildTables(final String schemaPattern, return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(tables)); } + /** + * Builds a valid result set of the description of the given table's indices and statistics. + * This method is used to implement the method + * {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}. + *

+ * Only indexes of the table exactly matching the catalog, schema and table name are returned. They are + * ordered by {@code NON_UNIQUE}, {@code TYPE}, {@code INDEX_NAME} and {@code ORDINAL_POSITION}. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name + * (if available).
  2. + *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is + * member of.
  4. + *
  5. TABLE_NAME String => table name.
  6. + *
  7. NON_UNIQUE boolean => Can index values be non-unique, {@code false} when {@code TYPE} is + * {@link DatabaseMetaData#tableIndexStatistic}. Always {@code true} here.
  8. + *
  9. INDEX_QUALIFIER String => index catalog, {@code null} when {@code TYPE} is + * {@link DatabaseMetaData#tableIndexStatistic}.
  10. + *
  11. INDEX_NAME String => index name, {@code null} when {@code TYPE} is + * {@link DatabaseMetaData#tableIndexStatistic}.
  12. + *
  13. TYPE short => index type: + *
      + *
    • {@link DatabaseMetaData#tableIndexStatistic} - this identifies table statistics that are + * returned in conjunction with a table's index descriptions
    • + *
    • {@link DatabaseMetaData#tableIndexClustered} - this is a clustered index
    • + *
    • {@link DatabaseMetaData#tableIndexHashed} - this is a hashed index
    • + *
    • {@link DatabaseMetaData#tableIndexOther} - this is some other style of index
    • + *
    Always {@link DatabaseMetaData#tableIndexHashed} here. + *
  14. + *
  15. ORDINAL_POSITION short => column sequence number within index; zero when {@code TYPE} is + * {@link DatabaseMetaData#tableIndexStatistic}. Always 1 here.
  16. + *
  17. COLUMN_NAME String => column name, {@code null} when {@code TYPE} is + * {@link DatabaseMetaData#tableIndexStatistic}.
  18. + *
  19. ASC_OR_DESC String => column sort sequence, "A" means ascending, "D" means descending, may be + * {@code null} if sort sequence is not supported or when {@code TYPE} is + * {@link DatabaseMetaData#tableIndexStatistic}. Always {@code null} here.
  20. + *
  21. CARDINALITY int => When {@code TYPE} is {@link DatabaseMetaData#tableIndexStatistic}, then + * this is the number of rows in the table; otherwise, it is the number of unique values in the index. + * Always -1 here.
  22. + *
  23. PAGES int => When {@code TYPE} is {@link DatabaseMetaData#tableIndexStatistic}, then + * this is the number of pages used for the table; otherwise, it is the number of pages used for the + * current index. Always -1 here.
  24. + *
  25. FILTER_CONDITION String => Filter condition, if any: always {@code null} here.
  26. + *
+ *

+ * + * @param schema A schema name. It must match the schema name as it is stored in the database; {@code ""} + * retrieves those without a schema and {@code null} means that the schema name should not be + * used to narrow down the search. + * @param tableName A table name. It must match the table name as it is stored in the database. + * @param unique when {@code true}, return only indices for unique values; when {@code false}, return + * indices regardless of whether unique or not. This parameter has no effect here. + * @param approximate when {@code true}, result is allowed to reflect approximate or out of data values; when + * {@code false}, results are requested to be accurate. This parameter has no effect here. + * @return A valid result set for implementation of + * {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + @SuppressWarnings("unused") + public CassandraMetadataResultSet buildIndexes(final String schema, + final String tableName, final boolean unique, + final boolean approximate) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList indexes = new ArrayList<>(); + + filterBySchemaNamePattern(schema, keyspaceMetadata -> + filterByTableNamePattern(tableName, keyspaceMetadata, tableMetadata -> { + for (final Map.Entry index : tableMetadata.getIndexes().entrySet()) { + final IndexMetadata indexMetadata = index.getValue(); + final MetadataRow row = new MetadataRow() + .addEntry(TABLE_CATALOG_SHORTNAME, catalog) + .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) + .addEntry(NON_UNIQUE, Boolean.TRUE.toString()) + .addEntry(INDEX_QUALIFIER, catalog) + .addEntry(INDEX_NAME, indexMetadata.getName().asInternal()) + .addEntry(TYPE, String.valueOf(DatabaseMetaData.tableIndexHashed)) + .addEntry(ORDINAL_POSITION, String.valueOf(1)) + .addEntry(COLUMN_NAME, indexMetadata.getTarget()) + .addEntry(ASC_OR_DESC, null) + .addEntry(CARDINALITY, String.valueOf(-1)) + .addEntry(PAGES, String.valueOf(-1)) + .addEntry(FILTER_CONDITION, null); + indexes.add(row); + } + }, null), null); + + // Results should all have the same NON_UNIQUE, TYPE and ORDINAL_POSITION, so just sort them by INDEX_NAME. + indexes.sort(Comparator.comparing(row -> row.getString(INDEX_NAME))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(indexes)); + } + + /** + * Builds a valid result set of the description of the given table's primary key columns. + * This method is used to implement the method {@link DatabaseMetaData#getPrimaryKeys(String, String, String)}. + *

+ * Only primary keys of the table exactly matching the catalog, schema and table name are returned. They are + * ordered by {@code COLUMN_NAME}. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. TABLE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name + * (if available).
  2. + *
  3. TABLE_SCHEM String => table schema, may be {@code null}: here is the keyspace the table is + * member of.
  4. + *
  5. TABLE_NAME String => table name.
  6. + *
  7. COLUMN_NAME String => column name.
  8. + *
  9. KEY_SEQ short => sequence number within primary key (a value of 1 represents the first column + * of the primary key, a value of 2 would represent the second column within the primary key).
  10. + *
  11. PK_NAME String => primary key name: always {@code null} here.
  12. + *
+ *

+ * + * @param schema A schema name. It must match the schema name as it is stored in the database; {@code ""} + * retrieves those without a schema and {@code null} means that the schema name should not be + * used to narrow down the search. + * @param tableName A table name. It must match the table name as it is stored in the database. + * @return A valid result set for implementation of {@link DatabaseMetaData#getPrimaryKeys(String, String, String)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildPrimaryKeys(final String schema, final String tableName) + throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList primaryKeys = new ArrayList<>(); + + filterBySchemaNamePattern(schema, keyspaceMetadata -> + filterByTableNamePattern(tableName, keyspaceMetadata, tableMetadata -> { + int seq = 1; + for (final ColumnMetadata col : tableMetadata.getPrimaryKey()) { + final MetadataRow row = new MetadataRow() + .addEntry(TABLE_CATALOG_SHORTNAME, catalog) + .addEntry(TABLE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TABLE_NAME, tableMetadata.getName().asInternal()) + .addEntry(COLUMN_NAME, col.getName().asInternal()) + .addEntry(KEY_SEQ, String.valueOf(seq)) + .addEntry(PRIMARY_KEY_NAME, null); + primaryKeys.add(row); + seq++; + } + }, null), null); + + // Sort the results by COLUMN_NAME. + primaryKeys.sort(Comparator.comparing(row -> row.getString(COLUMN_NAME))); + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(primaryKeys)); + } + } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index a380cbb..129db49 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -39,6 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; class MetadataResultSetsUnitTest extends UsingCassandraContainerTest { @@ -222,6 +223,10 @@ void givenStatement_whenMakeColumns_returnExpectedResultSet() throws SQLExceptio assertThat(foundColumns, hasItem(is(KEYSPACE.concat(";cf_test1;t1ivalue;INT;11")))); } + /* + * Result sets metadata + */ + @Test void givenStatement_whenGetResultMetadata_returnExpectedValues() throws Exception { final Statement stmtCreateTable = sqlConnection.createStatement(); @@ -307,16 +312,22 @@ void givenStatement_whenGetResultMetadata_returnExpectedValues() throws Exceptio } @Test - void givenCassandraMetadataResultSet_whenUnwrap_returnUnwrappedMetadataResultSet() throws Exception { - final CassandraMetadataResultSet metadataRs = new CassandraMetadataResultSet(); - assertNotNull(metadataRs.unwrap(ResultSet.class)); - assertNotNull(metadataRs.unwrap(CassandraResultSetExtras.class)); + void givenCassandraMetadataResultSet_whenUnwrap_returnUnwrappedMetadataResultSet() { + try (final CassandraMetadataResultSet metadataRs = new CassandraMetadataResultSet()) { + assertNotNull(metadataRs.unwrap(ResultSet.class)); + assertNotNull(metadataRs.unwrap(CassandraResultSetExtras.class)); + } catch (final Exception e) { + fail(e); + } } @Test void givenCassandraMetadataResultSet_whenUnwrapToInvalidInterface_throwException() { - final CassandraMetadataResultSet metadataRs = new CassandraMetadataResultSet(); - assertThrows(SQLException.class, () -> metadataRs.unwrap(this.getClass())); + try (final CassandraMetadataResultSet metadataRs = new CassandraMetadataResultSet()) { + assertThrows(SQLException.class, () -> metadataRs.unwrap(this.getClass())); + } catch (final Exception e) { + fail(e); + } } @Test @@ -354,6 +365,10 @@ void givenStatement_whenGetMetadataIsSearchable_returnExpectedValues() throws Ex stmt.close(); } + /* + * Types metadata + */ + @Test void givenStatement_whenMakeUDTs_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); @@ -505,6 +520,10 @@ void givenStatement_whenMakeTypes_returnExpectedResultSet() throws SQLException foundColumns.get(26)); } + /* + * Functions metadata + */ + @Test void givenStatement_whenMakeFunctions_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java index 9cebf64..38b5a10 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java @@ -15,6 +15,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.ing.data.cassandra.jdbc.CassandraConnection; @@ -54,6 +55,12 @@ static TableMetadata generateTestTableMetadata(final String tableName) { return mockTableMetadata; } + static ColumnMetadata generateTestColumnMetadata(final String columnName) { + final ColumnMetadata mockColumnMetadata = mock(ColumnMetadata.class); + when(mockColumnMetadata.getName()).thenReturn(CqlIdentifier.fromCql(columnName)); + return mockColumnMetadata; + } + @Test void givenSchemaPattern_whenApplySchemaFiltering_returnExpectedResultSet() throws SQLException { final CassandraStatement mockStatement = mock(CassandraStatement.class); @@ -153,4 +160,61 @@ void givenTablePattern_whenApplyTableFiltering_returnExpectedResultSet() throws assertThat(filteredTables, hasSize(3)); assertThat(filteredTables, hasItems("cf1", "cf2", "test_cf")); } + + @Test + void givenColumnPattern_whenApplyColumnFiltering_returnExpectedResultSet() throws SQLException { + final CassandraStatement mockStatement = mock(CassandraStatement.class); + final CassandraConnection mockConnection = mock(CassandraConnection.class); + final Metadata mockMetadata = mock(Metadata.class); + when(mockStatement.getCassandraConnection()).thenReturn(mockConnection); + when(mockConnection.getClusterMetadata()).thenReturn(mockMetadata); + final KeyspaceMetadata ksTestMetadata = generateTestKeyspaceMetadata("ks_test"); + + final Map testTablesMetadata = new HashMap<>(); + final TableMetadata tableTestMetadata = generateTestTableMetadata("tbl_test"); + testTablesMetadata.put(CqlIdentifier.fromInternal("tbl_test"), tableTestMetadata); + when(ksTestMetadata.getTables()).thenReturn(testTablesMetadata); + + final Map testColumnsMetadata = new HashMap<>(); + testColumnsMetadata.put(CqlIdentifier.fromInternal("col1"), generateTestColumnMetadata("col1")); + testColumnsMetadata.put(CqlIdentifier.fromInternal("col2"), generateTestColumnMetadata("col2")); + testColumnsMetadata.put(CqlIdentifier.fromInternal("clmn_test"), generateTestColumnMetadata("clmn_test")); + testColumnsMetadata.put(CqlIdentifier.fromInternal("test_col"), generateTestColumnMetadata("test_col")); + when(tableTestMetadata.getColumns()).thenReturn(testColumnsMetadata); + + final AbstractMetadataResultSetBuilder sut = new TestMetadataResultSetBuilder(mockStatement); + + final Set filteredColumns = new HashSet<>(); + sut.filterByColumnNamePattern(StringUtils.EMPTY, tableTestMetadata, + columnMetadata -> filteredColumns.add(columnMetadata.getName().asInternal()), null); + log.info("Columns matching '': {}", filteredColumns); + assertThat(filteredColumns, empty()); + + filteredColumns.clear(); + sut.filterByColumnNamePattern(null, tableTestMetadata, + tableMetadata -> filteredColumns.add(tableMetadata.getName().asInternal()), null); + log.info("Columns matching null: {}", filteredColumns); + assertThat(filteredColumns, hasSize(4)); + assertThat(filteredColumns, hasItems("col1", "col2", "clmn_test", "test_col")); + + filteredColumns.clear(); + sut.filterByColumnNamePattern("col", tableTestMetadata, + tableMetadata -> filteredColumns.add(tableMetadata.getName().asInternal()), null); + log.info("Columns matching 'col': {}", filteredColumns); + assertThat(filteredColumns, empty()); + + filteredColumns.clear(); + sut.filterByColumnNamePattern("col%", tableTestMetadata, + tableMetadata -> filteredColumns.add(tableMetadata.getName().asInternal()), null); + log.info("Columns matching 'col%': {}", filteredColumns); + assertThat(filteredColumns, hasSize(2)); + assertThat(filteredColumns, hasItems("col1", "col2")); + + filteredColumns.clear(); + sut.filterByColumnNamePattern("%col%", tableTestMetadata, + tableMetadata -> filteredColumns.add(tableMetadata.getName().asInternal()), null); + log.info("Columns matching '%col%': {}", filteredColumns); + assertThat(filteredColumns, hasSize(3)); + assertThat(filteredColumns, hasItems("col1", "col2", "test_col")); + } } From d2f923994b0924a0b14c9879e3c9cdcd169d067f Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 17 Sep 2023 16:20:40 +0200 Subject: [PATCH 13/21] Refactor types and functions metadata builders --- .../jdbc/CassandraDatabaseMetaData.java | 35 +- .../cassandra/jdbc/MetadataResultSets.java | 531 ------------------ .../AbstractMetadataResultSetBuilder.java | 35 +- .../FunctionMetadataResultSetBuilder.java | 270 +++++++++ .../jdbc/metadata/MetadataResultSet.java | 3 +- .../TypeMetadataResultSetBuilder.java | 238 ++++++++ .../jdbc/MetadataResultSetsUnitTest.java | 21 +- 7 files changed, 565 insertions(+), 568 deletions(-) delete mode 100644 src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/FunctionMetadataResultSetBuilder.java create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 0131703..28533ae 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -18,8 +18,10 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.ing.data.cassandra.jdbc.metadata.CatalogMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.ColumnMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.FunctionMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.SchemaMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.TypeMetadataResultSetBuilder; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; @@ -121,7 +123,7 @@ public boolean generatedKeyAlwaysReturned() { @Override public ResultSet getAttributes(final String catalog, final String schemaPattern, final String typeNamePattern, final String attributeNamePattern) throws SQLException { - // TODO: method to implement + // TODO: method to implement into TypeMetadataResultSetBuilder checkStatementClosed(); return CassandraResultSet.EMPTY_RESULT_SET; } @@ -129,7 +131,7 @@ public ResultSet getAttributes(final String catalog, final String schemaPattern, @Override public ResultSet getBestRowIdentifier(final String catalog, final String schema, final String table, final int scope, final boolean nullable) throws SQLException { - // TODO: method to implement + // TODO: method to implement into TableMetadataResultSetBuilder checkStatementClosed(); return CassandraResultSet.EMPTY_RESULT_SET; } @@ -329,13 +331,10 @@ public ResultSet getFunctionColumns(final String catalog, final String schemaPat final String functionNamePattern, final String columnNamePattern) throws SQLException { checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. if (catalog == null || catalog.equals(this.connection.getCatalog())) { - String schemaName = schemaPattern; - if (schemaPattern == null) { - schemaName = this.connection.getSchema(); // limit to current schema if defined. - } - return MetadataResultSets.INSTANCE.makeFunctionColumns(this.statement, schemaName, functionNamePattern, - columnNamePattern); + return new FunctionMetadataResultSetBuilder(this.statement).buildFunctionColumns(schemaPattern, + functionNamePattern, columnNamePattern); } return CassandraResultSet.EMPTY_RESULT_SET; } @@ -344,12 +343,10 @@ public ResultSet getFunctionColumns(final String catalog, final String schemaPat public ResultSet getFunctions(final String catalog, final String schemaPattern, final String functionNamePattern) throws SQLException { checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. if (catalog == null || catalog.equals(this.connection.getCatalog())) { - String schemaName = schemaPattern; - if (schemaPattern == null) { - schemaName = this.connection.getSchema(); // limit to current schema if defined. - } - return MetadataResultSets.INSTANCE.makeFunctions(this.statement, schemaName, functionNamePattern); + return new FunctionMetadataResultSetBuilder(this.statement).buildFunctions(schemaPattern, + functionNamePattern); } return CassandraResultSet.EMPTY_RESULT_SET; } @@ -760,7 +757,7 @@ public ResultSet getTables(final String catalog, final String schemaPattern, fin // TABLE type. boolean askingForTable = types == null; if (types != null) { - askingForTable = Arrays.asList(types).contains(MetadataResultSets.TABLE); + askingForTable = Arrays.asList(types).contains("TABLE"); } // Only null or the current catalog (i.e. cluster) name are supported. if ((catalog == null || catalog.equals(this.connection.getCatalog())) && askingForTable) { @@ -781,7 +778,7 @@ public String getTimeDateFunctions() throws SQLException { @Override public ResultSet getTypeInfo() throws SQLException { checkStatementClosed(); - return MetadataResultSets.INSTANCE.makeTypes(this.statement); + return new TypeMetadataResultSetBuilder(this.statement).buildTypes(); } /** @@ -836,13 +833,9 @@ public ResultSet getTypeInfo() throws SQLException { public ResultSet getUDTs(final String catalog, final String schemaPattern, final String typeNamePattern, final int[] types) throws SQLException { checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. if (catalog == null || catalog.equals(this.connection.getCatalog())) { - this.statement.connection = connection; - String schemaNamePattern = schemaPattern; - if (schemaPattern == null) { - schemaNamePattern = this.connection.getSchema(); // limit to current schema if defined. - } - return MetadataResultSets.INSTANCE.makeUDTs(this.statement, schemaNamePattern, typeNamePattern, types); + return new TypeMetadataResultSetBuilder(this.statement).buildUDTs(schemaPattern, typeNamePattern, types); } return CassandraResultSet.EMPTY_RESULT_SET; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java b/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java deleted file mode 100644 index f5a388b..0000000 --- a/src/main/java/com/ing/data/cassandra/jdbc/MetadataResultSets.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * - * 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.ing.data.cassandra.jdbc; - -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; -import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; -import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; -import com.datastax.oss.driver.api.core.type.UserDefinedType; -import com.ing.data.cassandra.jdbc.metadata.MetadataResultSet; -import com.ing.data.cassandra.jdbc.metadata.MetadataRow; -import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; -import com.ing.data.cassandra.jdbc.types.DataTypeEnum; -import org.apache.commons.lang3.StringUtils; - -import java.sql.DatabaseMetaData; -import java.sql.SQLException; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; -import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; -import static java.sql.DatabaseMetaData.functionColumnIn; -import static java.sql.DatabaseMetaData.functionReturn; -import static java.sql.DatabaseMetaData.typeNullable; -import static java.sql.DatabaseMetaData.typePredBasic; -import static java.sql.Types.JAVA_OBJECT; - -/** - * Utility class to manage database metadata result sets ({@link CassandraMetadataResultSet} objects). - */ -// TODO: split by families of metadata (table, columns, functions, ...) and move to metadata package. -public final class MetadataResultSets { - /** - * Gets an instance of {@code MetadataResultSets}. - */ - public static final MetadataResultSets INSTANCE = new MetadataResultSets(); - - static final String AUTO_INCREMENT = "AUTO_INCREMENT"; - static final String BASE_TYPE = "BASE_TYPE"; - static final String CASE_SENSITIVE = "CASE_SENSITIVE"; - static final String CHAR_OCTET_LENGTH = "CHAR_OCTET_LENGTH"; - static final String CLASS_NAME = "CLASS_NAME"; - static final String COLUMN_NAME = "COLUMN_NAME"; - static final String COLUMN_TYPE = "COLUMN_TYPE"; - static final String CREATE_PARAMS = "CREATE_PARAMS"; - static final String DATA_TYPE = "DATA_TYPE"; - static final String FIXED_PRECISION_SCALE = "FIXED_PREC_SCALE"; - static final String FUNCTION_CATALOG = "FUNCTION_CAT"; - static final String FUNCTION_NAME = "FUNCTION_NAME"; - static final String FUNCTION_SCHEMA = "FUNCTION_SCHEM"; - static final String FUNCTION_TYPE = "FUNCTION_TYPE"; - static final String IS_NULLABLE = "IS_NULLABLE"; - static final String LENGTH = "LENGTH"; - static final String LITERAL_PREFIX = "LITERAL_PREFIX"; - static final String LITERAL_SUFFIX = "LITERAL_SUFFIX"; - static final String LOCALIZED_TYPE_NAME = "LOCAL_TYPE_NAME"; - static final String MAXIMUM_SCALE = "MAXIMUM_SCALE"; - static final String MINIMUM_SCALE = "MINIMUM_SCALE"; - static final String NULLABLE = "NULLABLE"; - static final String NUM_PRECISION_RADIX = "NUM_PREC_RADIX"; - static final String ORDINAL_POSITION = "ORDINAL_POSITION"; - static final String PRECISION = "PRECISION"; - static final String RADIX = "RADIX"; - static final String REMARKS = "REMARKS"; - static final String SCALE = "SCALE"; - static final String SEARCHABLE = "SEARCHABLE"; - static final String SPECIFIC_NAME = "SPECIFIC_NAME"; - static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; - static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB"; - static final String TABLE = "TABLE"; - static final String TYPE_CATALOG = "TYPE_CAT"; - static final String TYPE_NAME = "TYPE_NAME"; - static final String TYPE_SCHEMA = "TYPE_SCHEM"; - static final String UNSIGNED_ATTRIBUTE = "UNSIGNED_ATTRIBUTE"; - static final String WILDCARD_CHAR = "%"; - static final String YES_VALUE = "YES"; - - - private MetadataResultSets() { - // Private constructor to hide the public one. - } - - /** - * Builds a valid result set of the description of the user-defined types (UDTs) defined in a particular schema. - * This method is used to implement the method {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. - *

- * Schema-specific UDTs in a Cassandra database will be considered as having type {@code JAVA_OBJECT}. - *

- *

- * Only types matching the catalog, schema, type name and type criteria are returned. They are ordered by - * {@code DATA_TYPE}, {@code TYPE_CAT}, {@code TYPE_SCHEM} and {@code TYPE_NAME}. The type name parameter may be - * a fully-qualified name (it should respect the format {@code .}). In this case, the - * {@code catalog} and {@code schemaPattern} parameters are ignored. - *

- *

- * The columns of this result set are: - *

    - *
  1. TYPE_CAT String => type's catalog, may be {@code null}: here is the Cassandra cluster name - * (if available).
  2. - *
  3. TYPE_SCHEM String => type's schema, may be {@code null}: here is the keyspace the type is - * member of.
  4. - *
  5. TYPE_NAME String => user-defined type name.
  6. - *
  7. CLASS_NAME String => Java class name, always {@link UdtValue} in the current implementation.
  8. - *
  9. DATA_TYPE int => type value defined in {@link Types}. One of {@link Types#JAVA_OBJECT}, - * {@link Types#STRUCT}, or {@link Types#DISTINCT}. Always {@link Types#JAVA_OBJECT} in the current - * implementation.
  10. - *
  11. REMARKS String => explanatory comment on the type, always empty in the current - * implementation.
  12. - *
  13. BASE_TYPE short => type code of the source type of a {@code DISTINCT} type or the type that - * implements the user-generated reference type of the {@code SELF_REFERENCING_COLUMN} of a structured type - * as defined in {@link Types} ({@code null} if {@code DATA_TYPE} is not {@code DISTINCT} or not - * {@code STRUCT} with {@code REFERENCE_GENERATION = USER_DEFINED}). Always {@code null} in the current - * implementation.
  14. - *
- *

- * - * @param statement The statement. - * @param schemaPattern A schema pattern name; must match the schema name as it is stored in the database; - * {@code ""} retrieves those without a schema (will always return an empty set); - * {@code null} means that the schema name should not be used to narrow the search and in - * this case the search is restricted to the current schema (if available). - * @param typeNamePattern A type name pattern; must match the type name as it is stored in the database (not - * case-sensitive); may be a fully qualified name. - * @param types A list of user-defined types ({@link Types#JAVA_OBJECT}, {@link Types#STRUCT}, or - * {@link Types#DISTINCT}) to include; {@code null} returns all types. All the UDTs defined - * in a Cassandra database are considered as {@link Types#JAVA_OBJECT}, so other values will - * return an empty result set. - * @return A valid result set for implementation of {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeUDTs(final CassandraStatement statement, final String schemaPattern, - final String typeNamePattern, final int[] types) throws SQLException { - final ArrayList udtsRows = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - // Parse the fully-qualified type name, if necessary. - String schemaName = schemaPattern; - String typeName = typeNamePattern; - if (typeNamePattern.contains(".")) { - final String[] fullyQualifiedTypeNameParts = typeNamePattern.split("\\."); - schemaName = fullyQualifiedTypeNameParts[0]; - typeName = fullyQualifiedTypeNameParts[1]; - } - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - if (StringUtils.isEmpty(schemaName) || schemaName.equals(keyspaceMetadata.getName().asInternal())) { - final Map udts = keyspaceMetadata.getUserDefinedTypes(); - - for (final Map.Entry udt : udts.entrySet()) { - final UserDefinedType udtMetadata = udt.getValue(); - if (typeName.equalsIgnoreCase(udtMetadata.getName().asInternal()) - && (types == null || Arrays.stream(types).anyMatch(type -> type == JAVA_OBJECT))) { - final MetadataRow row = new MetadataRow() - .addEntry(TYPE_CATALOG, statement.connection.getCatalog()) - .addEntry(TYPE_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(TYPE_NAME, udtMetadata.getName().asInternal()) - .addEntry(CLASS_NAME, UdtValue.class.getName()) - .addEntry(DATA_TYPE, String.valueOf(JAVA_OBJECT)) - .addEntry(REMARKS, StringUtils.EMPTY) - .addEntry(BASE_TYPE, null); - udtsRows.add(row); - } - } - } - } - // Results should all have the same DATA_TYPE and TYPE_CAT so just sort them by TYPE_SCHEM then TYPE_NAME. - udtsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TYPE_SCHEMA)) - .thenComparing(row -> ((MetadataRow) row).getString(TYPE_NAME))); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(udtsRows)); - } - - /** - * Builds a valid result set of all the data types supported by this database. This method is used to implement - * the method {@link DatabaseMetaData#getTypeInfo()}. - *

- * They are ordered by DATA_TYPE and then by how closely the data type maps to the corresponding JDBC SQL type. - *

- *

- * The Cassandra database does not support SQL distinct types. The information on the individual structured types - * (considered as {@link Types#JAVA_OBJECT}, not {@link Types#STRUCT}) may be obtained from the {@code getUDTs()} - * method. - *

- *

- * The columns of this result set are: - *

    - *
  1. TYPE_NAME String => type name.
  2. - *
  3. DATA_TYPE int => SQL data type from {@link Types}.
  4. - *
  5. PRECISION int => maximum precision.
  6. - *
  7. LITERAL_PREFIX String => prefix used to quote a literal (may be {@code null}).
  8. - *
  9. LITERAL_SUFFIX String => suffix used to quote a literal (may be {@code null}).
  10. - *
  11. CREATE_PARAMS String => parameters used in creating the type (may be {@code null}).
  12. - *
  13. NULLABLE short => can you use {@code NULL} for this type: - *
      - *
    • {@link DatabaseMetaData#typeNoNulls} - does not allow {@code NULL} values
    • - *
    • {@link DatabaseMetaData#typeNullable} - allows {@code NULL} values
    • - *
    • {@link DatabaseMetaData#typeNullableUnknown} - nullability unknown
    • - *
    - *
  14. - *
  15. CASE_SENSITIVE boolean => is it case sensitive.
  16. - *
  17. SEARCHABLE short => can you use "{@code WHERE}" based on this type: - *
      - *
    • {@link DatabaseMetaData#typePredNone} - no support
    • - *
    • {@link DatabaseMetaData#typePredChar} - only supported with {@code WHERE .. LIKE}
    • - *
    • {@link DatabaseMetaData#typePredBasic} - supported except for {@code WHERE .. LIKE}
    • - *
    • {@link DatabaseMetaData#typeSearchable} - supported for all {@code WHERE ..}
    • - *
    - *
  18. - *
  19. UNSIGNED_ATTRIBUTE boolean => is it unsigned.
  20. - *
  21. FIXED_PREC_SCALE boolean => can it be a money value.
  22. - *
  23. AUTO_INCREMENT boolean => can it be used for an auto-increment value. Always {@code false} - * since Cassandra does not support auto-increment.
  24. - *
  25. LOCAL_TYPE_NAME String => localized version of type name (may be {@code null}).
  26. - *
  27. MINIMUM_SCALE short => minimum scale supported.
  28. - *
  29. MAXIMUM_SCALE short => maximum scale supported.
  30. - *
  31. SQL_DATA_TYPE int => not used.
  32. - *
  33. SQL_DATETIME_SUB int => not used.
  34. - *
  35. NUM_PREC_RADIX String => precision radix (typically either 10 or 2).
  36. - *
- *

- *

- * The {@code PRECISION} column represents the maximum column size that the server supports for the given datatype. - * For numeric data, this is the maximum precision. For character data, this is the length in characters. For - * datetime data types, this is the length in characters of the {@code String} representation (assuming the maximum - * allowed precision of the fractional seconds component). For binary data, this is the length in bytes. - * For the {@code ROWID} datatype (not supported by Cassandra), this is the length in bytes. The value {@code null} - * is returned for data types where the column size is not applicable. - *

- * - * @param statement The statement. - * @return A valid result set for implementation of {@link DatabaseMetaData#getTypeInfo()}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeTypes(final CassandraStatement statement) throws SQLException { - final ArrayList types = new ArrayList<>(); - for (final DataTypeEnum dataType : DataTypeEnum.values()) { - final AbstractJdbcType jdbcType = getTypeForComparator(dataType.asLowercaseCql()); - String literalQuotingSymbol = null; - if (jdbcType.needsQuotes()) { - literalQuotingSymbol = "'"; - } - final MetadataRow row = new MetadataRow() - .addEntry(TYPE_NAME, dataType.cqlType) - .addEntry(DATA_TYPE, String.valueOf(jdbcType.getJdbcType())) - .addEntry(PRECISION, String.valueOf(jdbcType.getPrecision(null))) - .addEntry(LITERAL_PREFIX, literalQuotingSymbol) - .addEntry(LITERAL_SUFFIX, literalQuotingSymbol) - .addEntry(CREATE_PARAMS, null) - .addEntry(NULLABLE, String.valueOf(typeNullable)) // absence is the equivalent of null in Cassandra - .addEntry(CASE_SENSITIVE, String.valueOf(jdbcType.isCaseSensitive())) - .addEntry(SEARCHABLE, String.valueOf(typePredBasic)) - .addEntry(UNSIGNED_ATTRIBUTE, String.valueOf(!jdbcType.isSigned())) - .addEntry(FIXED_PRECISION_SCALE, String.valueOf(!jdbcType.isCurrency())) - .addEntry(AUTO_INCREMENT, String.valueOf(false)) - .addEntry(LOCALIZED_TYPE_NAME, null) - .addEntry(MINIMUM_SCALE, String.valueOf(DEFAULT_SCALE)) - .addEntry(MAXIMUM_SCALE, String.valueOf(jdbcType.getScale(null))) - .addEntry(SQL_DATA_TYPE, null) - .addEntry(SQL_DATETIME_SUB, null) - .addEntry(NUM_PRECISION_RADIX, String.valueOf(jdbcType.getPrecision(null))); - types.add(row); - } - // Sort results by DATA_TYPE. - types.sort(Comparator.comparing(row -> Integer.valueOf(row.getString(DATA_TYPE)))); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(types)); - } - - /** - * Builds a valid result set of the system and user functions available in the given catalog (Cassandra cluster). - * This method is used to implement the method {@link DatabaseMetaData#getFunctions(String, String, String)}. - *

- * Only system and user function descriptions matching the schema and function name criteria are returned. They are - * ordered by {@code FUNCTION_CAT}, {@code FUNCTION_SCHEM}, {@code FUNCTION_NAME} and {@code SPECIFIC_NAME}. - *

- *

- * The columns of this result set are: - *

    - *
  1. FUNCTION_CAT String => function catalog, may be {@code null}: here is the Cassandra cluster - * name (if available).
  2. - *
  3. FUNCTION_SCHEM String => function schema, may be {@code null}: here is the keyspace the table - * is member of.
  4. - *
  5. FUNCTION_NAME String => function name. This is the name used to invoke the function.
  6. - *
  7. REMARKS String => explanatory comment on the function (always empty, Cassandra does not - * allow to describe functions with a comment).
  8. - *
  9. FUNCTION_TYPE short => kind of function: - *
      - *
    • {@link DatabaseMetaData#functionResultUnknown} - cannot determine if a return value or table - * will be returned
    • - *
    • {@link DatabaseMetaData#functionNoTable} - does not return a table (Cassandra user-defined - * functions only return CQL types, so never a table)
    • - *
    • {@link DatabaseMetaData#functionReturnsTable} - returns a table
    • - *
    - *
  10. - *
  11. SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. - * This is a user specified, or DBMS generated, name that may be different then the {@code FUNCTION_NAME} - * for example with overload functions.
  12. - *
- *

- *

- * A user may not have permission to execute any of the functions that are returned by {@code getFunctions}. - *

- * - * @param statement The statement. - * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the - * database; {@code ""} retrieves those without a schema and {@code null} means that - * the schema name should not be used to narrow down the search. - * @param functionNamePattern A function name pattern; must match the function name as it is stored in the - * database. - * @return A valid result set for implementation of {@link DatabaseMetaData#getFunctions(String, String, String)}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeFunctions(final CassandraStatement statement, final String schemaPattern, - final String functionNamePattern) throws SQLException { - final ArrayList functionsRows = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - String schemaNamePattern = schemaPattern; - if (WILDCARD_CHAR.equals(schemaPattern)) { - schemaNamePattern = keyspaceMetadata.getName().asInternal(); - } - if (schemaNamePattern == null || schemaNamePattern.equals(keyspaceMetadata.getName().asInternal())) { - final Map functions = keyspaceMetadata.getFunctions(); - - for (final FunctionSignature function : functions.keySet()) { - if (WILDCARD_CHAR.equals(functionNamePattern) || functionNamePattern == null - || functionNamePattern.equals(function.getName().asInternal())) { - final MetadataRow row = new MetadataRow() - .addEntry(FUNCTION_CATALOG, statement.connection.getCatalog()) - .addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(FUNCTION_NAME, function.getName().asInternal()) - .addEntry(REMARKS, StringUtils.EMPTY) - .addEntry(FUNCTION_TYPE, String.valueOf(DatabaseMetaData.functionNoTable)) - .addEntry(SPECIFIC_NAME, function.getName().asInternal()); - functionsRows.add(row); - } - } - } - } - - // Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM then FUNCTION_NAME (since - // here SPECIFIC_NAME is equal to FUNCTION_NAME). - functionsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA)) - .thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME))); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(functionsRows)); - } - - /** - * Builds a valid result set of the given catalog's system or user function parameters and return type. - * This method is used to implement the method - * {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}. - *

- * Only descriptions matching the schema, function and parameter name criteria are returned. They are ordered by - * {@code FUNCTION_CAT}, {@code FUNCTION_SCHEM}, {@code FUNCTION_NAME} and {@code SPECIFIC_NAME}. Within this, the - * return value, if any, is first. Next are the parameter descriptions in call order. The column descriptions - * follow in column number order. - *

- *

- * The columns of this result set are: - *

    - *
  1. FUNCTION_CAT String => function catalog, may be {@code null}: here is the Cassandra cluster - * name (if available).
  2. - *
  3. FUNCTION_SCHEM String => function schema, may be {@code null}: here is the keyspace the table - * is member of.
  4. - *
  5. FUNCTION_NAME String => function name. This is the name used to invoke the function.
  6. - *
  7. COLUMN_NAME String => column/parameter name.
  8. - *
  9. COLUMN_TYPE short => kind of column/parameter: - *
      - *
    • {@link DatabaseMetaData#functionColumnUnknown} - unknown type
    • - *
    • {@link DatabaseMetaData#functionColumnIn} - {@code IN} parameter
    • - *
    • {@link DatabaseMetaData#functionColumnInOut} - {@code INOUT} parameter
    • - *
    • {@link DatabaseMetaData#functionColumnOut} - {@code OUT} parameter
    • - *
    • {@link DatabaseMetaData#functionReturn} - function return value
    • - *
    • {@link DatabaseMetaData#functionColumnResult} - indicates that the parameter or column is a - * column in the {@code ResultSet}
    • - *
    - *
  10. - *
  11. DATA_TYPE int => SQL data type from {@link Types}.
  12. - *
  13. TYPE_NAME String => SQL type name, for a UDT type the type name is fully qualified.
  14. - *
  15. PRECISION int => maximum precision.
  16. - *
  17. LENGTH int => length in bytes of data.
  18. - *
  19. SCALE int => scale, {@code null} is returned for data types where SCALE is not - * applicable.
  20. - *
  21. RADIX short => precision radix.
  22. - *
  23. NULLABLE short => can you use {@code NULL} for this type: - *
      - *
    • {@link DatabaseMetaData#typeNoNulls} - does not allow {@code NULL} values
    • - *
    • {@link DatabaseMetaData#typeNullable} - allows {@code NULL} values
    • - *
    • {@link DatabaseMetaData#typeNullableUnknown} - nullability unknown
    • - *
    - *
  24. - *
  25. REMARKS String => comment describing column/parameter (always empty, Cassandra does not - * allow to describe columns with a comment).
  26. - *
  27. CHAR_OCTET_LENGTH int => the maximum length of binary and character based parameters or - * columns. For any other datatype the returned value is a {@code NULL}.
  28. - *
  29. ORDINAL_POSITION int => the ordinal position, starting from 1, for the input and output - * parameters. A value of 0 is returned if this row describes the function's return value. For result set - * columns, it is the ordinal position of the column in the result set starting from 1.
  30. - *
  31. IS_NULLABLE String => "YES" if a parameter or column accepts {@code NULL} values, "NO" - * if not and empty if the nullability is unknown.
  32. - *
  33. SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. - * This is a user specified, or DBMS generated, name that may be different then the {@code FUNCTION_NAME} - * for example with overload functions.
  34. - *
- *

- *

- * The {@code PRECISION} column represents the maximum column size that the server supports for the given datatype. - * For numeric data, this is the maximum precision. For character data, this is the length in characters. For - * datetime data types, this is the length in characters of the {@code String} representation (assuming the maximum - * allowed precision of the fractional seconds component). For binary data, this is the length in bytes. - * For the {@code ROWID} datatype (not supported by Cassandra), this is the length in bytes. The value {@code null} - * is returned for data types where the column size is not applicable. - *

- * - * @param statement The statement. - * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the - * database; {@code ""} retrieves those without a schema and {@code null} means that - * the schema name should not be used to narrow down the search. - * @param functionNamePattern A function name pattern; must match the function name as it is stored in the - * database. - * @param columnNamePattern A parameter name pattern; must match the parameter or column name as it is stored - * in the database. - * @return A valid result set for implementation of - * {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}. - * @throws SQLException when something went wrong during the creation of the result set. - */ - public CassandraMetadataResultSet makeFunctionColumns(final CassandraStatement statement, - final String schemaPattern, - final String functionNamePattern, - final String columnNamePattern) throws SQLException { - final ArrayList functionParamsRows = new ArrayList<>(); - final Map keyspaces = statement.connection.getClusterMetadata().getKeyspaces(); - - for (final Map.Entry keyspace : keyspaces.entrySet()) { - final KeyspaceMetadata keyspaceMetadata = keyspace.getValue(); - String schemaNamePattern = schemaPattern; - if (WILDCARD_CHAR.equals(schemaPattern)) { - schemaNamePattern = keyspaceMetadata.getName().asInternal(); - } - if (schemaNamePattern == null || schemaNamePattern.equals(keyspaceMetadata.getName().asInternal())) { - final Map functions = keyspaceMetadata.getFunctions(); - - for (final Map.Entry function : functions.entrySet()) { - final FunctionSignature functionSignature = function.getKey(); - final FunctionMetadata functionMetadata = function.getValue(); - if (WILDCARD_CHAR.equals(functionNamePattern) || functionNamePattern == null - || functionNamePattern.equals(functionSignature.getName().asInternal())) { - // Function return type. - final AbstractJdbcType returnJdbcType = - getTypeForComparator(functionMetadata.getReturnType().asCql(false, true)); - final MetadataRow row = new MetadataRow() - .addEntry(FUNCTION_CATALOG, statement.connection.getCatalog()) - .addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(FUNCTION_NAME, functionSignature.getName().asInternal()) - .addEntry(COLUMN_NAME, StringUtils.EMPTY) - .addEntry(COLUMN_TYPE, String.valueOf(functionReturn)) - .addEntry(DATA_TYPE, String.valueOf(returnJdbcType.getJdbcType())) - .addEntry(TYPE_NAME, functionMetadata.getReturnType().toString()) - .addEntry(PRECISION, String.valueOf(returnJdbcType.getPrecision(null))) - .addEntry(LENGTH, String.valueOf(Integer.MAX_VALUE)) - .addEntry(SCALE, String.valueOf(returnJdbcType.getScale(null))) - .addEntry(RADIX, String.valueOf(returnJdbcType.getPrecision(null))) - .addEntry(NULLABLE, String.valueOf(typeNullable)) - .addEntry(REMARKS, StringUtils.EMPTY) - .addEntry(CHAR_OCTET_LENGTH, null) - .addEntry(ORDINAL_POSITION, "0") - .addEntry(IS_NULLABLE, YES_VALUE) - .addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal()); - functionParamsRows.add(row); - // Function input parameters. - final List paramNames = functionMetadata.getParameterNames(); - for (int i = 0; i < paramNames.size(); i++) { - if (WILDCARD_CHAR.equals(columnNamePattern) || columnNamePattern == null - || columnNamePattern.equals(paramNames.get(i).asInternal())) { - final AbstractJdbcType paramJdbcType = getTypeForComparator( - functionSignature.getParameterTypes().get(i).asCql(false, true)); - final MetadataRow paramRow = new MetadataRow() - .addEntry(FUNCTION_CATALOG, statement.connection.getCatalog()) - .addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal()) - .addEntry(FUNCTION_NAME, functionSignature.getName().asInternal()) - .addEntry(COLUMN_NAME, paramNames.get(i).asInternal()) - .addEntry(COLUMN_TYPE, String.valueOf(functionColumnIn)) - .addEntry(DATA_TYPE, String.valueOf(paramJdbcType.getJdbcType())) - .addEntry(TYPE_NAME, functionSignature.getParameterTypes().get(i).toString()) - .addEntry(PRECISION, String.valueOf(paramJdbcType.getPrecision(null))) - .addEntry(LENGTH, String.valueOf(Integer.MAX_VALUE)) - .addEntry(SCALE, String.valueOf(paramJdbcType.getScale(null))) - .addEntry(RADIX, String.valueOf(paramJdbcType.getPrecision(null))) - .addEntry(NULLABLE, String.valueOf(typeNullable)) - .addEntry(REMARKS, StringUtils.EMPTY) - .addEntry(CHAR_OCTET_LENGTH, null) - .addEntry(ORDINAL_POSITION, String.valueOf(i + 1)) - .addEntry(IS_NULLABLE, YES_VALUE) - .addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal()); - functionParamsRows.add(paramRow); - } - } - } - } - } - } - - // Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM then FUNCTION_NAME (since - // here SPECIFIC_NAME is equal to FUNCTION_NAME), and finally by ORDINAL_POSITION. - functionParamsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA)) - .thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME)) - .thenComparing(row -> ((MetadataRow) row).getString(SPECIFIC_NAME)) - .thenComparing(row -> Integer.valueOf(((MetadataRow) row).getString(ORDINAL_POSITION)))); - return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(functionParamsRows)); - } -} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java index 397de69..6b9ab5f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java @@ -17,6 +17,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.ing.data.cassandra.jdbc.CassandraConnection; @@ -27,6 +29,7 @@ import java.sql.SQLException; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -36,6 +39,7 @@ */ public abstract class AbstractMetadataResultSetBuilder { + static final String TABLE = "TABLE"; static final String CQL_OPTION_COMMENT = "comment"; static final String ASC_OR_DESC = "ASC_OR_DESC"; static final String AUTO_INCREMENT = "AUTO_INCREMENT"; @@ -91,7 +95,6 @@ public abstract class AbstractMetadataResultSetBuilder { static final String SPECIFIC_NAME = "SPECIFIC_NAME"; static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB"; - static final String TABLE = "TABLE"; static final String TABLE_CATALOG_SHORTNAME = "TABLE_CAT"; static final String TABLE_CATALOG = "TABLE_CATALOG"; static final String TABLE_NAME = "TABLE_NAME"; @@ -102,7 +105,6 @@ public abstract class AbstractMetadataResultSetBuilder { static final String TYPE_NAME = "TYPE_NAME"; static final String TYPE_SCHEMA = "TYPE_SCHEM"; static final String UNSIGNED_ATTRIBUTE = "UNSIGNED_ATTRIBUTE"; - static final String WILDCARD_CHAR = "%"; static final String YES_VALUE = "YES"; CassandraStatement statement; @@ -130,8 +132,8 @@ protected AbstractMetadataResultSetBuilder(final CassandraStatement statement) t * @param testedValue The tested string. * @return {@code true} if the pattern matches the tested string, {@code false} otherwise. */ - private boolean matchesPattern(final String pattern, final String testedValue) { - return testedValue.matches(String.format("^%s$", pattern.replaceAll("%", ".*"))); + public boolean matchesPattern(final String pattern, final String testedValue) { + return testedValue.matches(String.format("(?i)^%s$", pattern.replaceAll("%", ".*"))); } /** @@ -142,7 +144,7 @@ private boolean matchesPattern(final String pattern, final String testedValue) { * {@link #matchesPattern(String, String)} returns {@code true}. * @param schemaNamePattern The schema name pattern. * @param consumer The applied consumer function when there is a pattern match. - * @param altConsumer The applied consumer function when there is no pattern match.If {@code null}, a no-op + * @param altConsumer The applied consumer function when there is no pattern match. If {@code null}, a no-op * consumer is used. */ @SuppressWarnings("SameParameterValue") @@ -198,6 +200,29 @@ void filterByColumnNamePattern(final String columnNamePattern, final TableMetada Optional.ofNullable(altConsumer).orElse(noOpConsumer())); } + /** + * Executes a {@link BiConsumer} function on each pair of {@link FunctionSignature} and {@link FunctionMetadata} + * instances corresponding to a function matching the specified function name pattern in the specified keyspace. + * + * @implNote The pattern matches a function name if the pattern is {@code null} or if the function + * {@link #matchesPattern(String, String)} returns {@code true}. + * @param functionNamePattern The function name pattern. + * @param keyspaceMetadata The keyspace on which the filter is applied: only the functions present in this + * keyspace are filtered. + * @param consumer The applied bi-consumer function when there is a pattern match. + */ + void filterByFunctionNamePattern(final String functionNamePattern, final KeyspaceMetadata keyspaceMetadata, + final BiConsumer consumer) { + for (final Map.Entry entry : keyspaceMetadata.getFunctions().entrySet()) { + final FunctionSignature functionSignature = entry.getKey(); + final FunctionMetadata functionMetadata = entry.getValue(); + if (functionNamePattern == null + || matchesPattern(functionNamePattern, functionSignature.getName().asInternal())) { + consumer.accept(functionSignature, functionMetadata); + } + } + } + void filterByPattern(final String pattern, final Map metadataMap, final BiFunction patternMatcher, final Consumer consumer, final Consumer altConsumer) { diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/FunctionMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/FunctionMetadataResultSetBuilder.java new file mode 100644 index 0000000..93f1d75 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/FunctionMetadataResultSetBuilder.java @@ -0,0 +1,270 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import org.apache.commons.lang3.StringUtils; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; +import static java.sql.DatabaseMetaData.functionColumnIn; +import static java.sql.DatabaseMetaData.functionReturn; +import static java.sql.DatabaseMetaData.typeNullable; + +/** + * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to functions. + */ +public class FunctionMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + /** + * Constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + public FunctionMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + + /** + * Builds a valid result set of the system and user functions available in the given catalog (Cassandra cluster). + * This method is used to implement the method {@link DatabaseMetaData#getFunctions(String, String, String)}. + *

+ * Only system and user function descriptions matching the schema and function name criteria are returned. They are + * ordered by {@code FUNCTION_CAT}, {@code FUNCTION_SCHEM}, {@code FUNCTION_NAME} and {@code SPECIFIC_NAME}. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. FUNCTION_CAT String => function catalog, may be {@code null}: here is the Cassandra cluster + * name (if available).
  2. + *
  3. FUNCTION_SCHEM String => function schema, may be {@code null}: here is the keyspace the table + * is member of.
  4. + *
  5. FUNCTION_NAME String => function name. This is the name used to invoke the function.
  6. + *
  7. REMARKS String => explanatory comment on the function (always empty, Cassandra does not + * allow to describe functions with a comment).
  8. + *
  9. FUNCTION_TYPE short => kind of function: + *
      + *
    • {@link DatabaseMetaData#functionResultUnknown} - cannot determine if a return value or table + * will be returned
    • + *
    • {@link DatabaseMetaData#functionNoTable} - does not return a table (Cassandra user-defined + * functions only return CQL types, so never a table)
    • + *
    • {@link DatabaseMetaData#functionReturnsTable} - returns a table
    • + *
    + *
  10. + *
  11. SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. + * This is a user specified, or DBMS generated, name that may be different then the {@code FUNCTION_NAME} + * for example with overload functions.
  12. + *
+ *

+ *

+ * A user may not have permission to execute any of the functions that are returned by {@code getFunctions}. + *

+ * + * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the + * database; {@code ""} retrieves those without a schema and {@code null} means that + * the schema name should not be used to narrow down the search. + * @param functionNamePattern A function name pattern; must match the function name as it is stored in the + * database. + * @return A valid result set for implementation of {@link DatabaseMetaData#getFunctions(String, String, String)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + + public CassandraMetadataResultSet buildFunctions(final String schemaPattern, + final String functionNamePattern) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList functionsRows = new ArrayList<>(); + + filterBySchemaNamePattern(schemaPattern, keyspaceMetadata -> + filterByFunctionNamePattern(functionNamePattern, keyspaceMetadata, + (functionSignature, functionMetadata) -> { + final MetadataRow row = new MetadataRow() + .addEntry(FUNCTION_CATALOG, catalog) + .addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(FUNCTION_NAME, functionSignature.getName().asInternal()) + .addEntry(REMARKS, StringUtils.EMPTY) + .addEntry(FUNCTION_TYPE, String.valueOf(DatabaseMetaData.functionNoTable)) + .addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal()); + functionsRows.add(row); + }), null); + + // Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM then FUNCTION_NAME (since + // here SPECIFIC_NAME is equal to FUNCTION_NAME). + functionsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA)) + .thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(functionsRows)); + } + + /** + * Builds a valid result set of the given catalog's system or user function parameters and return type. + * This method is used to implement the method + * {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}. + *

+ * Only descriptions matching the schema, function and parameter name criteria are returned. They are ordered by + * {@code FUNCTION_CAT}, {@code FUNCTION_SCHEM}, {@code FUNCTION_NAME} and {@code SPECIFIC_NAME}. Within this, the + * return value, if any, is first. Next are the parameter descriptions in call order. The column descriptions + * follow in column number order. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. FUNCTION_CAT String => function catalog, may be {@code null}: here is the Cassandra cluster + * name (if available).
  2. + *
  3. FUNCTION_SCHEM String => function schema, may be {@code null}: here is the keyspace the table + * is member of.
  4. + *
  5. FUNCTION_NAME String => function name. This is the name used to invoke the function.
  6. + *
  7. COLUMN_NAME String => column/parameter name.
  8. + *
  9. COLUMN_TYPE short => kind of column/parameter: + *
      + *
    • {@link DatabaseMetaData#functionColumnUnknown} - unknown type
    • + *
    • {@link DatabaseMetaData#functionColumnIn} - {@code IN} parameter
    • + *
    • {@link DatabaseMetaData#functionColumnInOut} - {@code INOUT} parameter
    • + *
    • {@link DatabaseMetaData#functionColumnOut} - {@code OUT} parameter
    • + *
    • {@link DatabaseMetaData#functionReturn} - function return value
    • + *
    • {@link DatabaseMetaData#functionColumnResult} - indicates that the parameter or column is a + * column in the {@code ResultSet}
    • + *
    + *
  10. + *
  11. DATA_TYPE int => SQL data type from {@link Types}.
  12. + *
  13. TYPE_NAME String => SQL type name, for a UDT type the type name is fully qualified.
  14. + *
  15. PRECISION int => maximum precision.
  16. + *
  17. LENGTH int => length in bytes of data.
  18. + *
  19. SCALE int => scale, {@code null} is returned for data types where SCALE is not + * applicable.
  20. + *
  21. RADIX short => precision radix.
  22. + *
  23. NULLABLE short => can you use {@code NULL} for this type: + *
      + *
    • {@link DatabaseMetaData#typeNoNulls} - does not allow {@code NULL} values
    • + *
    • {@link DatabaseMetaData#typeNullable} - allows {@code NULL} values
    • + *
    • {@link DatabaseMetaData#typeNullableUnknown} - nullability unknown
    • + *
    + *
  24. + *
  25. REMARKS String => comment describing column/parameter (always empty, Cassandra does not + * allow to describe columns with a comment).
  26. + *
  27. CHAR_OCTET_LENGTH int => the maximum length of binary and character based parameters or + * columns. For any other datatype the returned value is a {@code NULL}.
  28. + *
  29. ORDINAL_POSITION int => the ordinal position, starting from 1, for the input and output + * parameters. A value of 0 is returned if this row describes the function's return value. For result set + * columns, it is the ordinal position of the column in the result set starting from 1.
  30. + *
  31. IS_NULLABLE String => "YES" if a parameter or column accepts {@code NULL} values, "NO" + * if not and empty if the nullability is unknown.
  32. + *
  33. SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. + * This is a user specified, or DBMS generated, name that may be different then the {@code FUNCTION_NAME} + * for example with overload functions.
  34. + *
+ *

+ *

+ * The {@code PRECISION} column represents the maximum column size that the server supports for the given datatype. + * For numeric data, this is the maximum precision. For character data, this is the length in characters. For + * datetime data types, this is the length in characters of the {@code String} representation (assuming the maximum + * allowed precision of the fractional seconds component). For binary data, this is the length in bytes. + * For the {@code ROWID} datatype (not supported by Cassandra), this is the length in bytes. The value {@code null} + * is returned for data types where the column size is not applicable. + *

+ * + * @param schemaPattern A schema name pattern. It must match the schema name as it is stored in the + * database; {@code ""} retrieves those without a schema and {@code null} means that + * the schema name should not be used to narrow down the search. + * @param functionNamePattern A function name pattern; must match the function name as it is stored in the + * database. + * @param columnNamePattern A parameter name pattern; must match the parameter or column name as it is stored + * in the database. + * @return A valid result set for implementation of + * {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildFunctionColumns(final String schemaPattern, + final String functionNamePattern, + final String columnNamePattern) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList functionParamsRows = new ArrayList<>(); + + filterBySchemaNamePattern(schemaPattern, keyspaceMetadata -> + filterByFunctionNamePattern(functionNamePattern, keyspaceMetadata, + (functionSignature, functionMetadata) -> { + // Function return type. + final AbstractJdbcType returnJdbcType = + getTypeForComparator(functionMetadata.getReturnType().asCql(false, true)); + final MetadataRow row = new MetadataRow() + .addEntry(FUNCTION_CATALOG, catalog) + .addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(FUNCTION_NAME, functionSignature.getName().asInternal()) + .addEntry(COLUMN_NAME, StringUtils.EMPTY) + .addEntry(COLUMN_TYPE, String.valueOf(functionReturn)) + .addEntry(DATA_TYPE, String.valueOf(returnJdbcType.getJdbcType())) + .addEntry(TYPE_NAME, functionMetadata.getReturnType().toString()) + .addEntry(PRECISION, String.valueOf(returnJdbcType.getPrecision(null))) + .addEntry(LENGTH, String.valueOf(Integer.MAX_VALUE)) + .addEntry(SCALE, String.valueOf(returnJdbcType.getScale(null))) + .addEntry(RADIX, String.valueOf(returnJdbcType.getPrecision(null))) + .addEntry(NULLABLE, String.valueOf(typeNullable)) + .addEntry(REMARKS, StringUtils.EMPTY) + .addEntry(CHAR_OCTET_LENGTH, null) + .addEntry(ORDINAL_POSITION, "0") + .addEntry(IS_NULLABLE, YES_VALUE) + .addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal()); + functionParamsRows.add(row); + + // Function input parameters. + final List paramNames = functionMetadata.getParameterNames(); + for (int i = 0; i < paramNames.size(); i++) { + if (columnNamePattern == null + || matchesPattern(columnNamePattern, paramNames.get(i).asInternal())) { + final AbstractJdbcType paramJdbcType = getTypeForComparator( + functionSignature.getParameterTypes().get(i).asCql(false, true)); + final MetadataRow paramRow = new MetadataRow() + .addEntry(FUNCTION_CATALOG, catalog) + .addEntry(FUNCTION_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(FUNCTION_NAME, functionSignature.getName().asInternal()) + .addEntry(COLUMN_NAME, paramNames.get(i).asInternal()) + .addEntry(COLUMN_TYPE, String.valueOf(functionColumnIn)) + .addEntry(DATA_TYPE, String.valueOf(paramJdbcType.getJdbcType())) + .addEntry(TYPE_NAME, functionSignature.getParameterTypes().get(i).toString()) + .addEntry(PRECISION, String.valueOf(paramJdbcType.getPrecision(null))) + .addEntry(LENGTH, String.valueOf(Integer.MAX_VALUE)) + .addEntry(SCALE, String.valueOf(paramJdbcType.getScale(null))) + .addEntry(RADIX, String.valueOf(paramJdbcType.getPrecision(null))) + .addEntry(NULLABLE, String.valueOf(typeNullable)) + .addEntry(REMARKS, StringUtils.EMPTY) + .addEntry(CHAR_OCTET_LENGTH, null) + .addEntry(ORDINAL_POSITION, String.valueOf(i + 1)) + .addEntry(IS_NULLABLE, YES_VALUE) + .addEntry(SPECIFIC_NAME, functionSignature.getName().asInternal()); + functionParamsRows.add(paramRow); + } + } + }), null); + + // Results should all have the same FUNCTION_CAT, so just sort them by FUNCTION_SCHEM then FUNCTION_NAME (since + // here SPECIFIC_NAME is equal to FUNCTION_NAME), and finally by ORDINAL_POSITION. + functionParamsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(FUNCTION_SCHEMA)) + .thenComparing(row -> ((MetadataRow) row).getString(FUNCTION_NAME)) + .thenComparing(row -> ((MetadataRow) row).getString(SPECIFIC_NAME)) + .thenComparing(row -> Integer.valueOf(((MetadataRow) row).getString(ORDINAL_POSITION)))); + return CassandraMetadataResultSet.buildFrom(this.statement, + new MetadataResultSet().setRows(functionParamsRows)); + } + +} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java index b59140c..ca47dad 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/MetadataResultSet.java @@ -16,7 +16,6 @@ package com.ing.data.cassandra.jdbc.metadata; import com.ing.data.cassandra.jdbc.ColumnDefinitions; -import com.ing.data.cassandra.jdbc.MetadataResultSets; import java.util.ArrayList; import java.util.Iterator; @@ -24,7 +23,7 @@ /** * A simple metadata result set made of {@link MetadataRow} objects. * - * @see MetadataResultSets + * @see AbstractMetadataResultSetBuilder */ public class MetadataResultSet { diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java new file mode 100644 index 0000000..0510046 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java @@ -0,0 +1,238 @@ +/* + * + * 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.ing.data.cassandra.jdbc.metadata; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; +import com.ing.data.cassandra.jdbc.CassandraStatement; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import com.ing.data.cassandra.jdbc.types.DataTypeEnum; +import org.apache.commons.lang3.StringUtils; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; +import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; +import static java.sql.DatabaseMetaData.typeNullable; +import static java.sql.DatabaseMetaData.typePredBasic; +import static java.sql.Types.JAVA_OBJECT; + +/** + * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to types. + */ +public class TypeMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + + /** + * Constructor. + * + * @param statement The statement. + * @throws SQLException if a database access error occurs or this statement is closed. + */ + public TypeMetadataResultSetBuilder(final CassandraStatement statement) throws SQLException { + super(statement); + } + + /** + * Builds a valid result set of the description of the user-defined types (UDTs) defined in a particular schema. + * This method is used to implement the method {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. + *

+ * Schema-specific UDTs in a Cassandra database will be considered as having type {@code JAVA_OBJECT}. + *

+ *

+ * Only types matching the catalog, schema, type name and type criteria are returned. They are ordered by + * {@code DATA_TYPE}, {@code TYPE_CAT}, {@code TYPE_SCHEM} and {@code TYPE_NAME}. The type name parameter may be + * a fully-qualified name (it should respect the format {@code .}). In this case, the + * {@code catalog} and {@code schemaPattern} parameters are ignored. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. TYPE_CAT String => type's catalog, may be {@code null}: here is the Cassandra cluster name + * (if available).
  2. + *
  3. TYPE_SCHEM String => type's schema, may be {@code null}: here is the keyspace the type is + * member of.
  4. + *
  5. TYPE_NAME String => user-defined type name.
  6. + *
  7. CLASS_NAME String => Java class name, always {@link UdtValue} in the current implementation.
  8. + *
  9. DATA_TYPE int => type value defined in {@link Types}. One of {@link Types#JAVA_OBJECT}, + * {@link Types#STRUCT}, or {@link Types#DISTINCT}. Always {@link Types#JAVA_OBJECT} in the current + * implementation.
  10. + *
  11. REMARKS String => explanatory comment on the type, always empty in the current + * implementation.
  12. + *
  13. BASE_TYPE short => type code of the source type of a {@code DISTINCT} type or the type that + * implements the user-generated reference type of the {@code SELF_REFERENCING_COLUMN} of a structured type + * as defined in {@link Types} ({@code null} if {@code DATA_TYPE} is not {@code DISTINCT} or not + * {@code STRUCT} with {@code REFERENCE_GENERATION = USER_DEFINED}). Always {@code null} in the current + * implementation.
  14. + *
+ *

+ * + * @param schemaPattern A schema pattern name; must match the schema name as it is stored in the database; + * {@code ""} retrieves those without a schema (will always return an empty set); + * {@code null} means that the schema name should not be used to narrow the search and in + * this case the search is restricted to the current schema (if available). + * @param typeNamePattern A type name pattern; must match the type name as it is stored in the database (not + * case-sensitive); may be a fully qualified name. + * @param types A list of user-defined types ({@link Types#JAVA_OBJECT}, {@link Types#STRUCT}, or + * {@link Types#DISTINCT}) to include; {@code null} returns all types. All the UDTs defined + * in a Cassandra database are considered as {@link Types#JAVA_OBJECT}, so other values will + * return an empty result set. + * @return A valid result set for implementation of {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildUDTs(final String schemaPattern, final String typeNamePattern, + final int[] types) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList udtsRows = new ArrayList<>(); + + // Parse the fully-qualified type name, if necessary. + String schemaName = schemaPattern; + final AtomicReference typeName = new AtomicReference<>(typeNamePattern); + if (typeNamePattern.contains(".")) { + final String[] fullyQualifiedTypeNameParts = typeNamePattern.split("\\."); + schemaName = fullyQualifiedTypeNameParts[0]; + typeName.set(fullyQualifiedTypeNameParts[1]); + } + + filterBySchemaNamePattern(schemaName, keyspaceMetadata -> { + final Map udts = keyspaceMetadata.getUserDefinedTypes(); + for (final Map.Entry udt : udts.entrySet()) { + final UserDefinedType udtMetadata = udt.getValue(); + if (matchesPattern(typeName.get(), udtMetadata.getName().asInternal()) + && (types == null || Arrays.stream(types).anyMatch(type -> type == JAVA_OBJECT))) { + final MetadataRow row = new MetadataRow() + .addEntry(TYPE_CATALOG, catalog) + .addEntry(TYPE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TYPE_NAME, udtMetadata.getName().asInternal()) + .addEntry(CLASS_NAME, UdtValue.class.getName()) + .addEntry(DATA_TYPE, String.valueOf(JAVA_OBJECT)) + .addEntry(REMARKS, StringUtils.EMPTY) + .addEntry(BASE_TYPE, null); + udtsRows.add(row); + } + } + }, null); + + // Results should all have the same DATA_TYPE and TYPE_CAT so just sort them by TYPE_SCHEM then TYPE_NAME. + udtsRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TYPE_SCHEMA)) + .thenComparing(row -> ((MetadataRow) row).getString(TYPE_NAME))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(udtsRows)); + } + + /** + * Builds a valid result set of all the data types supported by this database. This method is used to implement + * the method {@link DatabaseMetaData#getTypeInfo()}. + *

+ * They are ordered by DATA_TYPE and then by how closely the data type maps to the corresponding JDBC SQL type. + *

+ *

+ * The Cassandra database does not support SQL distinct types. The information on the individual structured types + * (considered as {@link Types#JAVA_OBJECT}, not {@link Types#STRUCT}) may be obtained from the {@code getUDTs()} + * method. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. TYPE_NAME String => type name.
  2. + *
  3. DATA_TYPE int => SQL data type from {@link Types}.
  4. + *
  5. PRECISION int => maximum precision.
  6. + *
  7. LITERAL_PREFIX String => prefix used to quote a literal (may be {@code null}).
  8. + *
  9. LITERAL_SUFFIX String => suffix used to quote a literal (may be {@code null}).
  10. + *
  11. CREATE_PARAMS String => parameters used in creating the type (may be {@code null}).
  12. + *
  13. NULLABLE short => can you use {@code NULL} for this type: + *
      + *
    • {@link DatabaseMetaData#typeNoNulls} - does not allow {@code NULL} values
    • + *
    • {@link DatabaseMetaData#typeNullable} - allows {@code NULL} values
    • + *
    • {@link DatabaseMetaData#typeNullableUnknown} - nullability unknown
    • + *
    + *
  14. + *
  15. CASE_SENSITIVE boolean => is it case sensitive.
  16. + *
  17. SEARCHABLE short => can you use "{@code WHERE}" based on this type: + *
      + *
    • {@link DatabaseMetaData#typePredNone} - no support
    • + *
    • {@link DatabaseMetaData#typePredChar} - only supported with {@code WHERE .. LIKE}
    • + *
    • {@link DatabaseMetaData#typePredBasic} - supported except for {@code WHERE .. LIKE}
    • + *
    • {@link DatabaseMetaData#typeSearchable} - supported for all {@code WHERE ..}
    • + *
    + *
  18. + *
  19. UNSIGNED_ATTRIBUTE boolean => is it unsigned.
  20. + *
  21. FIXED_PREC_SCALE boolean => can it be a money value.
  22. + *
  23. AUTO_INCREMENT boolean => can it be used for an auto-increment value. Always {@code false} + * since Cassandra does not support auto-increment.
  24. + *
  25. LOCAL_TYPE_NAME String => localized version of type name (may be {@code null}).
  26. + *
  27. MINIMUM_SCALE short => minimum scale supported.
  28. + *
  29. MAXIMUM_SCALE short => maximum scale supported.
  30. + *
  31. SQL_DATA_TYPE int => not used.
  32. + *
  33. SQL_DATETIME_SUB int => not used.
  34. + *
  35. NUM_PREC_RADIX String => precision radix (typically either 10 or 2).
  36. + *
+ *

+ *

+ * The {@code PRECISION} column represents the maximum column size that the server supports for the given datatype. + * For numeric data, this is the maximum precision. For character data, this is the length in characters. For + * datetime data types, this is the length in characters of the {@code String} representation (assuming the maximum + * allowed precision of the fractional seconds component). For binary data, this is the length in bytes. + * For the {@code ROWID} datatype (not supported by Cassandra), this is the length in bytes. The value {@code null} + * is returned for data types where the column size is not applicable. + *

+ * + * @return A valid result set for implementation of {@link DatabaseMetaData#getTypeInfo()}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildTypes() throws SQLException { + final ArrayList types = new ArrayList<>(); + for (final DataTypeEnum dataType : DataTypeEnum.values()) { + final AbstractJdbcType jdbcType = getTypeForComparator(dataType.asLowercaseCql()); + String literalQuotingSymbol = null; + if (jdbcType.needsQuotes()) { + literalQuotingSymbol = "'"; + } + final MetadataRow row = new MetadataRow() + .addEntry(TYPE_NAME, dataType.cqlType) + .addEntry(DATA_TYPE, String.valueOf(jdbcType.getJdbcType())) + .addEntry(PRECISION, String.valueOf(jdbcType.getPrecision(null))) + .addEntry(LITERAL_PREFIX, literalQuotingSymbol) + .addEntry(LITERAL_SUFFIX, literalQuotingSymbol) + .addEntry(CREATE_PARAMS, null) + .addEntry(NULLABLE, String.valueOf(typeNullable)) // absence is the equivalent of null in Cassandra + .addEntry(CASE_SENSITIVE, String.valueOf(jdbcType.isCaseSensitive())) + .addEntry(SEARCHABLE, String.valueOf(typePredBasic)) + .addEntry(UNSIGNED_ATTRIBUTE, String.valueOf(!jdbcType.isSigned())) + .addEntry(FIXED_PRECISION_SCALE, String.valueOf(!jdbcType.isCurrency())) + .addEntry(AUTO_INCREMENT, String.valueOf(false)) + .addEntry(LOCALIZED_TYPE_NAME, null) + .addEntry(MINIMUM_SCALE, String.valueOf(DEFAULT_SCALE)) + .addEntry(MAXIMUM_SCALE, String.valueOf(jdbcType.getScale(null))) + .addEntry(SQL_DATA_TYPE, null) + .addEntry(SQL_DATETIME_SUB, null) + .addEntry(NUM_PRECISION_RADIX, String.valueOf(jdbcType.getPrecision(null))); + types.add(row); + } + + // Sort results by DATA_TYPE. + types.sort(Comparator.comparing(row -> Integer.valueOf(row.getString(DATA_TYPE)))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(types)); + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index 129db49..188353d 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -16,8 +16,10 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.ing.data.cassandra.jdbc.metadata.CatalogMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.ColumnMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.FunctionMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.SchemaMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.metadata.TableMetadataResultSetBuilder; +import com.ing.data.cassandra.jdbc.metadata.TypeMetadataResultSetBuilder; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -372,7 +374,7 @@ void givenStatement_whenGetMetadataIsSearchable_returnExpectedValues() throws Ex @Test void givenStatement_whenMakeUDTs_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeUDTs(statement, KEYSPACE, "CustomType1", + final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildUDTs(KEYSPACE, "CustomType1", new int[]{Types.JAVA_OBJECT}); assertNotNull(result); assertEquals(7, result.getMetaData().getColumnCount()); @@ -395,8 +397,8 @@ void givenStatement_whenMakeUDTs_returnExpectedResultSet() throws SQLException { .concat(";2000;;null")))); // Using a fully-qualified type name. - final ResultSet resultFullyQualifiedName = MetadataResultSets.INSTANCE.makeUDTs(statement, - KEYSPACE, KEYSPACE + ".customtype2", new int[]{Types.JAVA_OBJECT}); + final ResultSet resultFullyQualifiedName = new TypeMetadataResultSetBuilder(statement) + .buildUDTs(KEYSPACE, KEYSPACE + ".customtype2", new int[]{Types.JAVA_OBJECT}); assertNotNull(resultFullyQualifiedName); assertEquals(7, resultFullyQualifiedName.getMetaData().getColumnCount()); assertTrue(resultFullyQualifiedName.next()); @@ -405,7 +407,7 @@ void givenStatement_whenMakeUDTs_returnExpectedResultSet() throws SQLException { @Test void givenStatement_whenMakeUDTsWithNonJavaObjectTypes_returnEmptyResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeUDTs(statement, KEYSPACE, "CustomType1", + final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildUDTs(KEYSPACE, "CustomType1", new int[]{Types.STRUCT, Types.DISTINCT}); assertNotNull(result); assertFalse(result.next()); @@ -414,7 +416,7 @@ void givenStatement_whenMakeUDTsWithNonJavaObjectTypes_returnEmptyResultSet() th @Test void givenStatement_whenMakeUDTsNotSpecifyingSchemaPattern_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeUDTs(statement, null, "type_in_different_ks", + final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildUDTs(null, "type_in_different_ks", new int[]{Types.JAVA_OBJECT}); assertNotNull(result); assertEquals(7, result.getMetaData().getColumnCount()); @@ -432,7 +434,7 @@ void givenStatement_whenMakeUDTsNotSpecifyingSchemaPattern_returnExpectedResultS @Test void givenStatement_whenMakeTypes_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeTypes(statement); + final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildTypes(); assertNotNull(result); assertEquals(18, result.getMetaData().getColumnCount()); assertEquals("TYPE_NAME", result.getMetaData().getColumnName(1)); @@ -527,7 +529,8 @@ void givenStatement_whenMakeTypes_returnExpectedResultSet() throws SQLException @Test void givenStatement_whenMakeFunctions_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeFunctions(statement, KEYSPACE, "function_test1"); + final ResultSet result = new FunctionMetadataResultSetBuilder(statement) + .buildFunctions(KEYSPACE, "function_test1"); assertNotNull(result); assertEquals(6, result.getMetaData().getColumnCount()); assertEquals("FUNCTION_CAT", result.getMetaData().getColumnName(1)); @@ -550,8 +553,8 @@ void givenStatement_whenMakeFunctions_returnExpectedResultSet() throws SQLExcept @Test void givenStatement_whenMakeFunctionColumns_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); - final ResultSet result = MetadataResultSets.INSTANCE.makeFunctionColumns(statement, KEYSPACE, "function_test1", - "%"); + final ResultSet result = new FunctionMetadataResultSetBuilder(statement) + .buildFunctionColumns(KEYSPACE, "function_test1", "%"); assertNotNull(result); assertEquals(17, result.getMetaData().getColumnCount()); assertEquals("FUNCTION_CAT", result.getMetaData().getColumnName(1)); From 8e97d8c5d24b6c98a722b53242ad5f1308d121b3 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 17 Sep 2023 18:00:37 +0200 Subject: [PATCH 14/21] Implement ResultSet.getWarnings() method --- CHANGELOG.md | 4 +- .../cassandra/jdbc/CassandraResultSet.java | 22 ++++++- .../cassandra/jdbc/ResultSetUnitTest.java | 60 ++++++++++++++++++ ...tractMetadataResultSetBuilderUnitTest.java | 62 +++++++++++++++++++ 4 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/ResultSetUnitTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2b081..57ca8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [4.10.0] - Unreleased ### Added - Add support for new [`vector` CQL type](https://datastax-oss.atlassian.net/browse/JAVA-3060) defined in [CEP-30](https://cwiki.apache.org/confluence/x/OQ40Dw). +- Implement the method `getWarnings()` in `CassandraResultSet`. ### Changed - Update DataStax Java Driver for Apache Cassandra(R) to version 4.17.0. - Update Apache Commons IO to version 2.13.0. @@ -144,6 +145,7 @@ For this version, the changelog lists the main changes comparatively to the late - Fix logs in `CassandraConnection` constructor. [original project]: https://github.com/adejanovski/cassandra-jdbc-wrapper/ +[4.10.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.9.1...v4.10.0 [4.9.1]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.9.0...v4.9.1 [4.9.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.8.0...v4.9.0 [4.8.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.7.0...v4.8.0 diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index 4035bc9..1da6e33 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -1396,10 +1396,26 @@ public CqlVector getVector(final String columnLabel) throws SQLException { @Override public SQLWarning getWarnings() throws SQLException { - // The rationale is there are no warnings to return in this implementation, but it still throws an exception - // when called on a closed result set. checkNotClosed(); - return null; + final List driverWarnings = this.driverResultSet.getExecutionInfo().getWarnings(); + if (!driverWarnings.isEmpty()) { + SQLWarning firstWarning = null; + SQLWarning previousWarning = null; + + for (final String warningMessage : driverWarnings) { + final SQLWarning warning = new SQLWarning(warningMessage); + if (previousWarning == null) { + firstWarning = warning; + } else { + previousWarning.setNextWarning(warning); + } + previousWarning = warning; + } + + return firstWarning; + } else { + return null; + } } /** diff --git a/src/test/java/com/ing/data/cassandra/jdbc/ResultSetUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/ResultSetUnitTest.java new file mode 100644 index 0000000..2f816ad --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/ResultSetUnitTest.java @@ -0,0 +1,60 @@ +/* + * 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.ing.data.cassandra.jdbc; + +import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.SQLWarning; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test Cassandra Result Sets + */ +class ResultSetUnitTest extends UsingCassandraContainerTest { + + private static final String KEYSPACE = "test_keyspace"; + + @BeforeAll + static void finalizeSetUpTests() throws Exception { + initConnection(KEYSPACE, "version=3.0.0", "localdatacenter=datacenter1"); + } + + @Test + void givenSelectStatementGeneratingWarning_whenGetWarnings_returnExpectedWarning() throws Exception { + final CassandraStatement mockStmt = mock(CassandraStatement.class); + final com.datastax.oss.driver.api.core.cql.ResultSet mockDriverRs = + mock(com.datastax.oss.driver.api.core.cql.ResultSet.class); + when(mockDriverRs.getExecutionInfo()).thenReturn(mock(ExecutionInfo.class)); + when(mockDriverRs.getExecutionInfo().getWarnings()) + .thenReturn(Arrays.asList("First warning message", "Second warning message")); + final ResultSet fakeRs = new CassandraResultSet(mockStmt, mockDriverRs); + when(mockStmt.executeQuery(anyString())).thenReturn(fakeRs); + + final ResultSet resultSet = mockStmt.executeQuery("SELECT * FROM test_table"); + assertEquals("First warning message", resultSet.getWarnings().getMessage()); + final SQLWarning nextWarning = resultSet.getWarnings().getNextWarning(); + assertNotNull(nextWarning); + assertEquals("Second warning message", nextWarning.getMessage()); + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java index 38b5a10..37620bd 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilderUnitTest.java @@ -16,6 +16,8 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.FunctionSignature; import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; import com.ing.data.cassandra.jdbc.CassandraConnection; @@ -61,6 +63,12 @@ static ColumnMetadata generateTestColumnMetadata(final String columnName) { return mockColumnMetadata; } + static FunctionMetadata generateTestFunctionMetadata(final FunctionSignature signature) { + final FunctionMetadata mockFunctionMetadata = mock(FunctionMetadata.class); + when(mockFunctionMetadata.getSignature()).thenReturn(signature); + return mockFunctionMetadata; + } + @Test void givenSchemaPattern_whenApplySchemaFiltering_returnExpectedResultSet() throws SQLException { final CassandraStatement mockStatement = mock(CassandraStatement.class); @@ -217,4 +225,58 @@ void givenColumnPattern_whenApplyColumnFiltering_returnExpectedResultSet() throw assertThat(filteredColumns, hasSize(3)); assertThat(filteredColumns, hasItems("col1", "col2", "test_col")); } + + @Test + void givenFunctionNamePattern_whenApplyFunctionFiltering_returnExpectedResultSet() throws SQLException { + final CassandraStatement mockStatement = mock(CassandraStatement.class); + final CassandraConnection mockConnection = mock(CassandraConnection.class); + final Metadata mockMetadata = mock(Metadata.class); + when(mockStatement.getCassandraConnection()).thenReturn(mockConnection); + when(mockConnection.getClusterMetadata()).thenReturn(mockMetadata); + final KeyspaceMetadata ksTestMetadata = generateTestKeyspaceMetadata("ks_test"); + final Map testFunctionsMetadata = new HashMap<>(); + final FunctionSignature signatureFunc1 = new FunctionSignature("func1"); + testFunctionsMetadata.put(signatureFunc1, generateTestFunctionMetadata(signatureFunc1)); + final FunctionSignature signatureFunc2 = new FunctionSignature("func2"); + testFunctionsMetadata.put(signatureFunc2, generateTestFunctionMetadata(signatureFunc2)); + final FunctionSignature signatureAnotherFunc = new FunctionSignature("another_function"); + testFunctionsMetadata.put(signatureAnotherFunc, generateTestFunctionMetadata(signatureAnotherFunc)); + final FunctionSignature signatureAnotherTestFunc = new FunctionSignature("another_test"); + testFunctionsMetadata.put(signatureAnotherTestFunc, generateTestFunctionMetadata(signatureAnotherTestFunc)); + when(ksTestMetadata.getFunctions()).thenReturn(testFunctionsMetadata); + final AbstractMetadataResultSetBuilder sut = new TestMetadataResultSetBuilder(mockStatement); + + final Set filteredFunctions = new HashSet<>(); + sut.filterByFunctionNamePattern(StringUtils.EMPTY, ksTestMetadata, + (signature, functionMetadata) -> filteredFunctions.add(signature.getName().asInternal())); + log.info("Functions matching '': {}", filteredFunctions); + assertThat(filteredFunctions, empty()); + + filteredFunctions.clear(); + sut.filterByFunctionNamePattern(null, ksTestMetadata, + (signature, functionMetadata) -> filteredFunctions.add(signature.getName().asInternal())); + log.info("Functions matching null: {}", filteredFunctions); + assertThat(filteredFunctions, hasSize(4)); + assertThat(filteredFunctions, hasItems("func1", "func2", "another_function", "another_test")); + + filteredFunctions.clear(); + sut.filterByFunctionNamePattern("func", ksTestMetadata, + (signature, functionMetadata) -> filteredFunctions.add(signature.getName().asInternal())); + log.info("Functions matching 'func': {}", filteredFunctions); + assertThat(filteredFunctions, empty()); + + filteredFunctions.clear(); + sut.filterByFunctionNamePattern("func%", ksTestMetadata, + (signature, functionMetadata) -> filteredFunctions.add(signature.getName().asInternal())); + log.info("Functions matching 'func%': {}", filteredFunctions); + assertThat(filteredFunctions, hasSize(2)); + assertThat(filteredFunctions, hasItems("func1", "func2")); + + filteredFunctions.clear(); + sut.filterByFunctionNamePattern("%func%", ksTestMetadata, + (signature, functionMetadata) -> filteredFunctions.add(signature.getName().asInternal())); + log.info("Functions matching '%func%': {}", filteredFunctions); + assertThat(filteredFunctions, hasSize(3)); + assertThat(filteredFunctions, hasItems("func1", "func2", "another_function")); + } } From 59d3e7b2dbdcd4467e5c12e482e7adebbac1f6af Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sun, 17 Sep 2023 20:23:39 +0200 Subject: [PATCH 15/21] Refactor utility classes Split Utils class into several specific classes and add Javadoc on public constants. --- .../cassandra/jdbc/AbstractConnection.java | 4 +- .../cassandra/jdbc/AbstractResultSet.java | 4 +- .../cassandra/jdbc/AbstractStatement.java | 4 +- .../cassandra/jdbc/CassandraConnection.java | 28 +- .../cassandra/jdbc/CassandraDataSource.java | 33 +- .../jdbc/CassandraDatabaseMetaData.java | 8 +- .../data/cassandra/jdbc/CassandraDriver.java | 12 +- .../jdbc/CassandraMetadataResultSet.java | 22 +- .../jdbc/CassandraParameterMetaData.java | 2 +- .../jdbc/CassandraPreparedStatement.java | 8 +- .../cassandra/jdbc/CassandraResultSet.java | 26 +- .../cassandra/jdbc/CassandraStatement.java | 39 +- .../cassandra/jdbc/ManagedConnection.java | 7 +- .../jdbc/ManagedPreparedStatement.java | 2 +- .../jdbc/PooledCassandraDataSource.java | 2 +- .../data/cassandra/jdbc/SessionHolder.java | 97 +++-- .../cassandra/jdbc/codec/AbstractCodec.java | 2 +- .../cassandra/jdbc/types/JdbcDecimal.java | 2 +- .../data/cassandra/jdbc/utils/DriverUtil.java | 114 ++++++ .../cassandra/jdbc/utils/ErrorConstants.java | 230 ++++++++++++ .../utils/{Utils.java => JdbcUrlUtil.java} | 347 +++++++++--------- .../data/cassandra/jdbc/utils/JsonUtil.java | 70 ++++ .../cassandra/jdbc/ConnectionUnitTest.java | 13 +- .../cassandra/jdbc/DataSourceUnitTest.java | 4 +- .../data/cassandra/jdbc/UtilsUnitTest.java | 164 +++++---- .../codec/BigintToBigDecimalCodecTest.java | 2 +- .../jdbc/codec/DecimalToDoubleCodecTest.java | 2 +- .../jdbc/codec/FloatToDoubleCodecTest.java | 2 +- .../jdbc/codec/IntToLongCodecTest.java | 2 +- .../jdbc/codec/LongToIntCodecTest.java | 2 +- .../jdbc/codec/SmallintToIntCodecTest.java | 2 +- .../jdbc/codec/TimestampToLongCodecTest.java | 2 +- .../jdbc/codec/TinyintToIntCodecTest.java | 2 +- .../jdbc/codec/VarintToIntCodecTest.java | 2 +- 34 files changed, 865 insertions(+), 397 deletions(-) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/utils/DriverUtil.java create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/utils/ErrorConstants.java rename src/main/java/com/ing/data/cassandra/jdbc/utils/{Utils.java => JdbcUrlUtil.java} (70%) create mode 100644 src/main/java/com/ing/data/cassandra/jdbc/utils/JsonUtil.java diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java index b8c81a6..2222b26 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractConnection.java @@ -30,8 +30,8 @@ import java.util.Map; import java.util.concurrent.Executor; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; /** * Provides a default implementation (returning a {@link SQLFeatureNotSupportedException}) to hold the unimplemented diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java index f58428d..6ec1be9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java @@ -38,8 +38,8 @@ import java.sql.Wrapper; import java.util.Map; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; /** * Provides a default implementation (returning a {@link SQLFeatureNotSupportedException}) to hold the unimplemented diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java index f847b4c..eeed400 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractStatement.java @@ -27,8 +27,8 @@ import java.sql.SQLXML; import java.sql.Wrapper; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; /** * Provides a default implementation (returning a {@link SQLFeatureNotSupportedException}) to hold the unimplemented diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java index a399c1b..de077a5 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java @@ -66,20 +66,20 @@ import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_CONCURRENCY; import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_HOLDABILITY; import static com.ing.data.cassandra.jdbc.CassandraResultSet.DEFAULT_TYPE; -import static com.ing.data.cassandra.jdbc.utils.Utils.ALWAYS_AUTOCOMMIT; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_TIMEOUT; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_TRANSACTIONS; -import static com.ing.data.cassandra.jdbc.utils.Utils.PROTOCOL; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_ACTIVE_CQL_VERSION; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_COMPLIANCE_MODE; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CONSISTENCY_LEVEL; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CQL_VERSION; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_DATABASE_NAME; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_DEBUG; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_USER; -import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_CONN; -import static com.ing.data.cassandra.jdbc.utils.Utils.createSubName; -import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.ALWAYS_AUTOCOMMIT; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_TRANSACTIONS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_CONN; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_ACTIVE_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_COMPLIANCE_MODE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONSISTENCY_LEVEL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DATABASE_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DEBUG; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.createSubName; /** * Cassandra connection: implementation class for {@link Connection} to create a JDBC connection to a Cassandra diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java index 3cad7e2..f665cf1 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDataSource.java @@ -17,7 +17,7 @@ import com.datastax.oss.driver.api.core.ConsistencyLevel; import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy; -import com.ing.data.cassandra.jdbc.utils.Utils; +import com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; @@ -29,19 +29,20 @@ import java.util.Properties; import java.util.logging.Logger; -import static com.ing.data.cassandra.jdbc.utils.Utils.HOST_REQUIRED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.utils.Utils.PROTOCOL; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CONSISTENCY_LEVEL; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_CQL_VERSION; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_DATABASE_NAME; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_LOCAL_DATACENTER; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PASSWORD; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PORT_NUMBER; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_SERVER_NAME; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_USER; -import static com.ing.data.cassandra.jdbc.utils.Utils.createSubName; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.HOST_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.DEFAULT_PORT; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONSISTENCY_LEVEL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DATABASE_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOCAL_DATACENTER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PASSWORD; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PORT_NUMBER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SERVER_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.createSubName; /** * Cassandra data source: implementation class for {@link DataSource} and {@link ConnectionPoolDataSource}. @@ -66,9 +67,9 @@ public class CassandraDataSource implements ConnectionPoolDataSource, DataSource */ protected String serverName; /** - * The port number of the data source, by default {@value Utils#DEFAULT_PORT}. + * The port number of the data source, by default {@value JdbcUrlUtil#DEFAULT_PORT}. */ - protected int portNumber = Utils.DEFAULT_PORT; + protected int portNumber = DEFAULT_PORT; /** * The database name. In case of Cassandra, i.e. the keyspace used as data source. */ diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 28533ae..ca33de8 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -35,10 +35,10 @@ import java.util.Arrays; import java.util.List; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; -import static com.ing.data.cassandra.jdbc.utils.Utils.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; /** * Cassandra database metadata: implementation class for {@link DatabaseMetaData}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java index 9a3319e..68ac9ae 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java @@ -32,12 +32,12 @@ import java.util.Map; import java.util.Properties; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.PROTOCOL; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PASSWORD; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_USER; -import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; -import static com.ing.data.cassandra.jdbc.utils.Utils.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PASSWORD; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER; /** * The Cassandra driver implementation. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java index 6046953..564288c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java @@ -26,7 +26,6 @@ import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import com.ing.data.cassandra.jdbc.types.TypesMap; -import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayInputStream; @@ -64,14 +63,15 @@ import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_DIR; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_SIZE; -import static com.ing.data.cassandra.jdbc.utils.Utils.FORWARD_ONLY; -import static com.ing.data.cassandra.jdbc.utils.Utils.MUST_BE_POSITIVE; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.utils.Utils.VALID_LABELS; -import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_RS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_DIR; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_SIZE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.FORWARD_ONLY; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.MALFORMED_URL; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.MUST_BE_POSITIVE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.VALID_LABELS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_RS; /** * Cassandra metadata result set. This is an implementation of {@link ResultSet} for database metadata. @@ -937,7 +937,7 @@ public URL getURL(final int columnIndex) throws SQLException { try { return new URL(storedUrl); } catch (final MalformedURLException e) { - throw new SQLException(String.format(Utils.MALFORMED_URL, storedUrl), e); + throw new SQLException(String.format(MALFORMED_URL, storedUrl), e); } } } @@ -953,7 +953,7 @@ public URL getURL(final String columnLabel) throws SQLException { try { return new URL(storedUrl); } catch (final MalformedURLException e) { - throw new SQLException(String.format(Utils.MALFORMED_URL, storedUrl), e); + throw new SQLException(String.format(MALFORMED_URL, storedUrl), e); } } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java index 518ddf2..6748c56 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraParameterMetaData.java @@ -23,7 +23,7 @@ import java.sql.ParameterMetaData; import java.sql.SQLException; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; /** * Cassandra parameter metadata: implementation class for {@link ParameterMetaData}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java index 25cc72b..3f189f7 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraPreparedStatement.java @@ -29,7 +29,6 @@ import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import com.fasterxml.jackson.core.JsonProcessingException; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; -import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -69,8 +68,9 @@ import java.util.UUID; import java.util.concurrent.CompletionStage; -import static com.ing.data.cassandra.jdbc.utils.Utils.VECTOR_ELEMENTS_NOT_NUMBERS; -import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_RESULT_SET; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.VECTOR_ELEMENTS_NOT_NUMBERS; +import static com.ing.data.cassandra.jdbc.utils.JsonUtil.getObjectMapper; /** * Cassandra prepared statement: implementation class for {@link PreparedStatement}. @@ -289,7 +289,7 @@ public ResultSet executeQuery() throws SQLException { checkNotClosed(); doExecute(); if (this.currentResultSet == null) { - throw new SQLNonTransientException(Utils.NO_RESULT_SET); + throw new SQLNonTransientException(NO_RESULT_SET); } return this.currentResultSet; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index 1da6e33..cff14e6 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -34,7 +34,6 @@ import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import com.ing.data.cassandra.jdbc.types.TypesMap; -import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -88,16 +87,17 @@ import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; import static com.ing.data.cassandra.jdbc.types.DataTypeEnum.fromCqlTypeName; import static com.ing.data.cassandra.jdbc.types.DataTypeEnum.fromDataType; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_DIR; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_FETCH_SIZE; -import static com.ing.data.cassandra.jdbc.utils.Utils.FORWARD_ONLY; -import static com.ing.data.cassandra.jdbc.utils.Utils.MUST_BE_POSITIVE; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; -import static com.ing.data.cassandra.jdbc.utils.Utils.NO_INTERFACE; -import static com.ing.data.cassandra.jdbc.utils.Utils.VALID_LABELS; -import static com.ing.data.cassandra.jdbc.utils.Utils.VECTOR_ELEMENTS_NOT_NUMBERS; -import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_RS; -import static com.ing.data.cassandra.jdbc.utils.Utils.getObjectMapper; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_DIR; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_SIZE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.FORWARD_ONLY; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.MALFORMED_URL; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.MUST_BE_POSITIVE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.VALID_LABELS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.VECTOR_ELEMENTS_NOT_NUMBERS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_RS; +import static com.ing.data.cassandra.jdbc.utils.JsonUtil.getObjectMapper; /** * Cassandra result set: implementation class for {@link java.sql.ResultSet}. @@ -1337,7 +1337,7 @@ public URL getURL(final int columnIndex) throws SQLException { try { return new URL(storedUrl); } catch (final MalformedURLException e) { - throw new SQLException(String.format(Utils.MALFORMED_URL, storedUrl), e); + throw new SQLException(String.format(MALFORMED_URL, storedUrl), e); } } } @@ -1353,7 +1353,7 @@ public URL getURL(final String columnLabel) throws SQLException { try { return new URL(storedUrl); } catch (final MalformedURLException e) { - throw new SQLException(String.format(Utils.MALFORMED_URL, storedUrl), e); + throw new SQLException(String.format(MALFORMED_URL, storedUrl), e); } } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java index 236097c..3c8276a 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java @@ -24,7 +24,6 @@ import com.datastax.oss.driver.internal.core.cql.MultiPageResultSet; import com.datastax.oss.driver.internal.core.cql.SinglePageResultSet; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import com.ing.data.cassandra.jdbc.utils.Utils; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -49,6 +48,18 @@ import java.util.Objects; import java.util.concurrent.CompletionStage; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_AUTO_GEN; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_CONCURRENCY_RS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_DIR; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_SIZE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_HOLD_RS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_KEEP_RS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_TYPE_RS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_GEN_KEYS; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_MULTIPLE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_RESULT_SET; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_STMT; + /** * Cassandra statement: implementation class for {@link Statement}. *

@@ -216,19 +227,19 @@ public class CassandraStatement extends AbstractStatement if (!(resultSetType == ResultSet.TYPE_FORWARD_ONLY || resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE || resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE)) { - throw new SQLSyntaxErrorException(String.format(Utils.BAD_TYPE_RS, resultSetType)); + throw new SQLSyntaxErrorException(String.format(BAD_TYPE_RS, resultSetType)); } this.resultSetType = resultSetType; if (!(resultSetConcurrency == ResultSet.CONCUR_READ_ONLY || resultSetConcurrency == ResultSet.CONCUR_UPDATABLE)) { - throw new SQLSyntaxErrorException(String.format(Utils.BAD_CONCURRENCY_RS, resultSetConcurrency)); + throw new SQLSyntaxErrorException(String.format(BAD_CONCURRENCY_RS, resultSetConcurrency)); } this.resultSetConcurrency = resultSetConcurrency; if (!(resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT || resultSetHoldability == ResultSet.CLOSE_CURSORS_AT_COMMIT)) { - throw new SQLSyntaxErrorException(String.format(Utils.BAD_HOLD_RS, resultSetHoldability)); + throw new SQLSyntaxErrorException(String.format(BAD_HOLD_RS, resultSetHoldability)); } this.resultSetHoldability = resultSetHoldability; } @@ -247,7 +258,7 @@ public void addBatch(final String query) throws SQLException { */ protected final void checkNotClosed() throws SQLException { if (isClosed()) { - throw new SQLRecoverableException(Utils.WAS_CLOSED_STMT); + throw new SQLRecoverableException(WAS_CLOSED_STMT); } } @@ -383,10 +394,10 @@ public boolean execute(final String query) throws SQLException { public boolean execute(final String cql, final int autoGeneratedKeys) throws SQLException { checkNotClosed(); if (!(autoGeneratedKeys == RETURN_GENERATED_KEYS || autoGeneratedKeys == NO_GENERATED_KEYS)) { - throw new SQLSyntaxErrorException(Utils.BAD_AUTO_GEN); + throw new SQLSyntaxErrorException(String.format(BAD_AUTO_GEN, autoGeneratedKeys)); } if (autoGeneratedKeys == RETURN_GENERATED_KEYS) { - throw new SQLFeatureNotSupportedException(Utils.NO_GEN_KEYS); + throw new SQLFeatureNotSupportedException(NO_GEN_KEYS); } return execute(cql); } @@ -428,7 +439,7 @@ public ResultSet executeQuery(final String cql) throws SQLException { checkNotClosed(); doExecute(cql); if (this.currentResultSet == null) { - throw new SQLNonTransientException(Utils.NO_RESULT_SET); + throw new SQLNonTransientException(NO_RESULT_SET); } return currentResultSet; } @@ -456,7 +467,7 @@ public int executeUpdate(final String cql) throws SQLException { public int executeUpdate(final String cql, final int autoGeneratedKeys) throws SQLException { checkNotClosed(); if (!(autoGeneratedKeys == RETURN_GENERATED_KEYS || autoGeneratedKeys == NO_GENERATED_KEYS)) { - throw new SQLFeatureNotSupportedException(Utils.BAD_AUTO_GEN); + throw new SQLFeatureNotSupportedException(String.format(BAD_AUTO_GEN, autoGeneratedKeys)); } return executeUpdate(cql); } @@ -500,11 +511,11 @@ public void setFetchDirection(final int direction) throws SQLException { if (direction == ResultSet.FETCH_FORWARD || direction == ResultSet.FETCH_REVERSE || direction == ResultSet.FETCH_UNKNOWN) { if (getResultSetType() == ResultSet.TYPE_FORWARD_ONLY && direction != ResultSet.FETCH_FORWARD) { - throw new SQLSyntaxErrorException(String.format(Utils.BAD_FETCH_DIR, direction)); + throw new SQLSyntaxErrorException(String.format(BAD_FETCH_DIR, direction)); } this.fetchDirection = direction; } else { - throw new SQLSyntaxErrorException(String.format(Utils.BAD_FETCH_DIR, direction)); + throw new SQLSyntaxErrorException(String.format(BAD_FETCH_DIR, direction)); } } @@ -518,7 +529,7 @@ public int getFetchSize() throws SQLException { public void setFetchSize(final int rows) throws SQLException { checkNotClosed(); if (rows < 0) { - throw new SQLSyntaxErrorException(String.format(Utils.BAD_FETCH_SIZE, rows)); + throw new SQLSyntaxErrorException(String.format(BAD_FETCH_SIZE, rows)); } this.fetchSize = rows; } @@ -584,9 +595,9 @@ public boolean getMoreResults(final int current) throws SQLException { break; case CLOSE_ALL_RESULTS: case KEEP_CURRENT_RESULT: - throw new SQLFeatureNotSupportedException(Utils.NO_MULTIPLE); + throw new SQLFeatureNotSupportedException(NO_MULTIPLE); default: - throw new SQLSyntaxErrorException(String.format(Utils.BAD_KEEP_RS, current)); + throw new SQLSyntaxErrorException(String.format(BAD_KEEP_RS, current)); } // In the current Cassandra implementation there are never more results. return false; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java index 3e7f852..2cb9f74 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/ManagedConnection.java @@ -15,8 +15,6 @@ package com.ing.data.cassandra.jdbc; -import com.ing.data.cassandra.jdbc.utils.Utils; - import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; @@ -31,7 +29,8 @@ import java.util.Properties; import java.util.Set; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_CONN; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; /** * Cassandra connection from a pool managed by a {@link PooledCassandraDataSource}. @@ -54,7 +53,7 @@ class ManagedConnection extends AbstractConnection implements Connection { private void checkNotClosed() throws SQLNonTransientConnectionException { if (isClosed()) { - throw new SQLNonTransientConnectionException(Utils.WAS_CLOSED_CONN); + throw new SQLNonTransientConnectionException(WAS_CLOSED_CONN); } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java index 9ae58ee..1f429e8 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/ManagedPreparedStatement.java @@ -37,7 +37,7 @@ import java.sql.Timestamp; import java.util.Calendar; -import static com.ing.data.cassandra.jdbc.utils.Utils.WAS_CLOSED_CONN; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_CONN; /** * Cassandra prepared statement executed in the context of a pool of connections managed in a diff --git a/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java b/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java index ab8bf2f..575fb52 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/PooledCassandraDataSource.java @@ -29,7 +29,7 @@ import java.util.HashSet; import java.util.Set; -import static com.ing.data.cassandra.jdbc.utils.Utils.NOT_SUPPORTED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED; /** * Pooled Cassandra data source: implementation class for {@link DataSource} and {@link ConnectionEventListener}. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java b/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java index 9fb04c9..dbe2e49 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java @@ -40,7 +40,6 @@ import com.ing.data.cassandra.jdbc.codec.TimestampToLongCodec; import com.ing.data.cassandra.jdbc.codec.TinyintToIntCodec; import com.ing.data.cassandra.jdbc.codec.VarintToIntCodec; -import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; @@ -62,11 +61,33 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.SSL_CONFIG_FAILED; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_KEYSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_KEYSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_TRUSTSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.SSL_CONFIG_FAILED; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CLOUD_SECURE_CONNECT_BUNDLE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONFIG_FILE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONNECT_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONSISTENCY_LEVEL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DATABASE_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DEBUG; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_ENABLE_SSL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_KEEP_ALIVE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOAD_BALANCING_POLICY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOCAL_DATACENTER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PASSWORD; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PORT_NUMBER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_RECONNECT_POLICY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_REQUEST_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_RETRY_POLICY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SERVER_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SSL_ENGINE_FACTORY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SSL_HOSTNAME_VERIFICATION; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_TCP_NO_DELAY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.parseReconnectionPolicy; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.parseURL; /** * Holds a {@link Session} shared among multiple {@link CassandraConnection} objects. @@ -102,7 +123,7 @@ class SessionHolder { // Parse the URL into a set of Properties and replace double quote marks (") by simple quotes (') to handle the // fact that double quotes (") are not valid characters in URIs. - this.properties = Utils.parseURL(url.replace("\"", "'")); + this.properties = parseURL(url.replace("\"", "'")); // Other properties in parameters come from the initial call to connect(), they take priority. params.keySet().stream() @@ -162,23 +183,23 @@ boolean acquire() { private Session createSession(final Properties properties) throws SQLException { File configurationFile = null; - final String configurationFilePath = properties.getProperty(Utils.TAG_CONFIG_FILE, StringUtils.EMPTY); + final String configurationFilePath = properties.getProperty(TAG_CONFIG_FILE, StringUtils.EMPTY); if (StringUtils.isNotBlank(configurationFilePath)) { configurationFile = new File(configurationFilePath); if (configurationFile.exists()) { // We remove some parameters to use the values defined into the specified configuration file // instead. - this.properties.remove(Utils.TAG_CONSISTENCY_LEVEL); - this.properties.remove(Utils.TAG_LOCAL_DATACENTER); - this.properties.remove(Utils.TAG_USER); - this.properties.remove(Utils.TAG_PASSWORD); - this.properties.remove(Utils.TAG_ENABLE_SSL); - this.properties.remove(Utils.TAG_SSL_ENGINE_FACTORY); - this.properties.remove(Utils.TAG_SSL_HOSTNAME_VERIFICATION); - this.properties.remove(Utils.TAG_REQUEST_TIMEOUT); - this.properties.remove(Utils.TAG_CONNECT_TIMEOUT); - this.properties.remove(Utils.TAG_KEEP_ALIVE); - this.properties.remove(Utils.TAG_TCP_NO_DELAY); + this.properties.remove(TAG_CONSISTENCY_LEVEL); + this.properties.remove(TAG_LOCAL_DATACENTER); + this.properties.remove(TAG_USER); + this.properties.remove(TAG_PASSWORD); + this.properties.remove(TAG_ENABLE_SSL); + this.properties.remove(TAG_SSL_ENGINE_FACTORY); + this.properties.remove(TAG_SSL_HOSTNAME_VERIFICATION); + this.properties.remove(TAG_REQUEST_TIMEOUT); + this.properties.remove(TAG_CONNECT_TIMEOUT); + this.properties.remove(TAG_KEEP_ALIVE); + this.properties.remove(TAG_TCP_NO_DELAY); LOG.info("The configuration file {} will be used and will override the parameters defined into the " + "JDBC URL except contact points and keyspace.", configurationFilePath); } else { @@ -186,27 +207,27 @@ private Session createSession(final Properties properties) throws SQLException { } } - final String hosts = properties.getProperty(Utils.TAG_SERVER_NAME); - final int port = Integer.parseInt(properties.getProperty(Utils.TAG_PORT_NUMBER)); - final String cloudSecureConnectBundle = properties.getProperty(Utils.TAG_CLOUD_SECURE_CONNECT_BUNDLE); - final String keyspace = properties.getProperty(Utils.TAG_DATABASE_NAME); - final String username = properties.getProperty(Utils.TAG_USER, StringUtils.EMPTY); - final String password = properties.getProperty(Utils.TAG_PASSWORD, StringUtils.EMPTY); - final String loadBalancingPolicy = properties.getProperty(Utils.TAG_LOAD_BALANCING_POLICY, StringUtils.EMPTY); - final String localDatacenter = properties.getProperty(Utils.TAG_LOCAL_DATACENTER, StringUtils.EMPTY); - final String retryPolicy = properties.getProperty(Utils.TAG_RETRY_POLICY, StringUtils.EMPTY); - final String reconnectPolicy = properties.getProperty(Utils.TAG_RECONNECT_POLICY, StringUtils.EMPTY); - final boolean debugMode = Boolean.TRUE.toString().equals(properties.getProperty(Utils.TAG_DEBUG, + final String hosts = properties.getProperty(TAG_SERVER_NAME); + final int port = Integer.parseInt(properties.getProperty(TAG_PORT_NUMBER)); + final String cloudSecureConnectBundle = properties.getProperty(TAG_CLOUD_SECURE_CONNECT_BUNDLE); + final String keyspace = properties.getProperty(TAG_DATABASE_NAME); + final String username = properties.getProperty(TAG_USER, StringUtils.EMPTY); + final String password = properties.getProperty(TAG_PASSWORD, StringUtils.EMPTY); + final String loadBalancingPolicy = properties.getProperty(TAG_LOAD_BALANCING_POLICY, StringUtils.EMPTY); + final String localDatacenter = properties.getProperty(TAG_LOCAL_DATACENTER, StringUtils.EMPTY); + final String retryPolicy = properties.getProperty(TAG_RETRY_POLICY, StringUtils.EMPTY); + final String reconnectPolicy = properties.getProperty(TAG_RECONNECT_POLICY, StringUtils.EMPTY); + final boolean debugMode = Boolean.TRUE.toString().equals(properties.getProperty(TAG_DEBUG, StringUtils.EMPTY)); - final String enableSslValue = properties.getProperty(Utils.TAG_ENABLE_SSL); - final String sslEngineFactoryClassName = properties.getProperty(Utils.TAG_SSL_ENGINE_FACTORY, + final String enableSslValue = properties.getProperty(TAG_ENABLE_SSL); + final String sslEngineFactoryClassName = properties.getProperty(TAG_SSL_ENGINE_FACTORY, StringUtils.EMPTY); final boolean sslEnabled = Boolean.TRUE.toString().equals(enableSslValue) || (enableSslValue == null && StringUtils.isNotEmpty(sslEngineFactoryClassName)); - final String enableSslHostnameVerification = properties.getProperty(Utils.TAG_SSL_HOSTNAME_VERIFICATION); + final String enableSslHostnameVerification = properties.getProperty(TAG_SSL_HOSTNAME_VERIFICATION); final boolean sslHostnameVerificationEnabled = Boolean.TRUE.toString().equals(enableSslHostnameVerification) || enableSslHostnameVerification == null; - final String requestTimeoutRawValue = properties.getProperty(Utils.TAG_REQUEST_TIMEOUT); + final String requestTimeoutRawValue = properties.getProperty(TAG_REQUEST_TIMEOUT); Integer requestTimeout = null; if (NumberUtils.isParsable(requestTimeoutRawValue)) { requestTimeout = Integer.parseInt(requestTimeoutRawValue); @@ -277,7 +298,7 @@ private Session createSession(final Properties properties) throws SQLException { // if reconnection policy has been given in the JDBC URL, parse it and add it to the cluster builder. try { final Map parsedPolicy = Optional.ofNullable( - Utils.parseReconnectionPolicy(reconnectPolicy)).orElse(new HashMap<>()); + parseReconnectionPolicy(reconnectPolicy)).orElse(new HashMap<>()); driverConfigLoaderBuilder.withString(DefaultDriverOption.RECONNECTION_POLICY_CLASS, (String) parsedPolicy.get(DefaultDriverOption.RECONNECTION_POLICY_CLASS)); @@ -347,14 +368,14 @@ private Session createSession(final Properties properties) throws SQLException { void configureSocketOptions(final ProgrammaticDriverConfigLoaderBuilder driverConfigLoaderBuilder, final Properties properties) { // Parse options received from JDBC URL - final String connectTimeoutRawValue = properties.getProperty(Utils.TAG_CONNECT_TIMEOUT); + final String connectTimeoutRawValue = properties.getProperty(TAG_CONNECT_TIMEOUT); Integer connectTimeout = null; if (NumberUtils.isParsable(connectTimeoutRawValue)) { connectTimeout = Integer.parseInt(connectTimeoutRawValue); } - final String enableTcpNoDelay = properties.getProperty(Utils.TAG_TCP_NO_DELAY); + final String enableTcpNoDelay = properties.getProperty(TAG_TCP_NO_DELAY); final boolean tcpNoDelayEnabled = Boolean.TRUE.toString().equals(enableTcpNoDelay) || enableTcpNoDelay == null; - final String enableTcpKeepAlive = properties.getProperty(Utils.TAG_KEEP_ALIVE); + final String enableTcpKeepAlive = properties.getProperty(TAG_KEEP_ALIVE); final boolean tcpKeepAliveEnabled = Boolean.TRUE.toString().equals(enableTcpKeepAlive); // Apply configuration diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java index 229184c..dbab784 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java @@ -19,7 +19,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; /** * Provides a minimal implementation for the methods {@link TypeCodec#parse(String)} and diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java index 4290355..22599fa 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcDecimal.java @@ -19,7 +19,7 @@ import java.nio.ByteBuffer; import java.sql.Types; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; /** * JDBC description of {@code DECIMAL} CQL type (corresponding Java type: {@link BigDecimal}). diff --git a/src/main/java/com/ing/data/cassandra/jdbc/utils/DriverUtil.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/DriverUtil.java new file mode 100644 index 0000000..1d5cfbb --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/DriverUtil.java @@ -0,0 +1,114 @@ +/* + * + * 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.ing.data.cassandra.jdbc.utils; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * A set of static utility methods and constants used by the JDBC driver. + */ +public final class DriverUtil { + + /** + * Properties file name containing some properties relative to this JDBC wrapper (such as JDBC driver version, + * name, etc.). + */ + public static final String JDBC_DRIVER_PROPERTIES_FILE = "jdbc-driver.properties"; + + /** + * The JSSE property used to retrieve the trust store when SSL is enabled on the database connection using + * JSSE properties. + */ + public static final String JSSE_TRUSTSTORE_PROPERTY = "javax.net.ssl.trustStore"; + + /** + * The JSSE property used to retrieve the trust store password when SSL is enabled on the database connection using + * JSSE properties. + */ + public static final String JSSE_TRUSTSTORE_PASSWORD_PROPERTY = "javax.net.ssl.trustStorePassword"; + + /** + * The JSSE property used to retrieve the key store when SSL is enabled on the database connection using + * JSSE properties. + */ + public static final String JSSE_KEYSTORE_PROPERTY = "javax.net.ssl.keyStore"; + + /** + * The JSSE property used to retrieve the key store password when SSL is enabled on the database connection using + * JSSE properties. + */ + public static final String JSSE_KEYSTORE_PASSWORD_PROPERTY = "javax.net.ssl.keyStorePassword"; + + /** + * {@code NULL} CQL keyword. + */ + public static final String NULL_KEYWORD = "NULL"; + + static final Logger LOG = LoggerFactory.getLogger(DriverUtil.class); + + private DriverUtil() { + // Private constructor to hide the public one. + } + + /** + * Gets a property value from the Cassandra JDBC driver properties file. + * + * @param name The name of the property. + * @return The property value or an empty string the value cannot be retrieved. + */ + public static String getDriverProperty(final String name) { + try (final InputStream propertiesFile = + DriverUtil.class.getClassLoader().getResourceAsStream(JDBC_DRIVER_PROPERTIES_FILE)) { + final Properties driverProperties = new Properties(); + driverProperties.load(propertiesFile); + return driverProperties.getProperty(name, StringUtils.EMPTY); + } catch (IOException ex) { + LOG.error("Unable to get JDBC driver property: {}.", name, ex); + return StringUtils.EMPTY; + } + } + + /** + * Gets a part of a version string. + *

+ * It uses the dot character as separator to parse the different parts of a version (major, minor, patch). + *

+ * + * @param version The version string (for example X.Y.Z). + * @param part The part of the version to extract (for the semantic versioning, use 0 for the major version, 1 for + * the minor and 2 for the patch). + * @return The requested part of the version, or 0 if the requested part cannot be parsed correctly. + */ + public static int parseVersion(final String version, final int part) { + if (StringUtils.isBlank(version) || StringUtils.countMatches(version, ".") < part || part < 0) { + return 0; + } else { + try { + return Integer.parseInt(version.split("\\.")[part]); + } catch (final NumberFormatException ex) { + LOG.error("Unable to parse version: {}", version); + return 0; + } + } + } + +} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/utils/ErrorConstants.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/ErrorConstants.java new file mode 100644 index 0000000..084dab3 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/ErrorConstants.java @@ -0,0 +1,230 @@ +/* + * + * 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.ing.data.cassandra.jdbc.utils; + +import com.ing.data.cassandra.jdbc.CassandraResultSet; + +import java.net.URI; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +/** + * Error messages strings shared across the JDBC wrapper classes. + */ +public final class ErrorConstants { + + /** + * Error message used in any SQL exception thrown when a method is called on a closed {@link Connection}. + */ + public static final String WAS_CLOSED_CONN = "Method was called on a closed Connection."; + + /** + * Error message used in any SQL exception thrown when a method is called on a closed {@link Statement}. + */ + public static final String WAS_CLOSED_STMT = "Method was called on a closed Statement."; + + /** + * Error message used in any SQL exception thrown when a method is called on a closed {@link ResultSet}. + */ + public static final String WAS_CLOSED_RS = "Method was called on a closed ResultSet."; + + /** + * Error message used in any SQL exception thrown when the method {@code unwrap(Class)} is called with a class + * not matching the expected interface. This message is a template expecting the name of the class parameter as + * placeholder (example: {@code String.format(NO_INTERFACE, iface.getSimpleName())}). + */ + public static final String NO_INTERFACE = "No object was found that matched the provided interface: %s"; + + /** + * Error message used in any SQL exception thrown because the called method requires transactions (currently not + * implemented in Cassandra). + */ + public static final String NO_TRANSACTIONS = "The Cassandra implementation does not support transactions."; + + /** + * Error message used in any SQL exception thrown because the called method requires a non-committed transaction + * (but transactions are not currently implemented in Cassandra, so we consider we are always in auto-commit mode). + */ + public static final String ALWAYS_AUTOCOMMIT = "The Cassandra implementation is always in auto-commit mode."; + + /** + * Error message used in any SQL exception thrown because the provided timeout is invalid (less than 0). + */ + public static final String BAD_TIMEOUT = "The timeout value was less than zero."; + + /** + * Error message used in any SQL exception thrown because the called method requires a feature not currently + * supported by Cassandra. + */ + public static final String NOT_SUPPORTED = "The Cassandra implementation does not support this method."; + + /** + * Error message used in any SQL exception thrown because the called method requires auto-generated keys (currently + * not implemented in Cassandra). + */ + public static final String NO_GEN_KEYS = + "The Cassandra implementation does not currently support returning generated keys."; + + /** + * Error message used in any SQL exception thrown because the called method requires keeping multiple open result + * sets (currently not implemented in Cassandra). + */ + public static final String NO_MULTIPLE = + "The Cassandra implementation does not currently support multiple open result sets."; + + /** + * Error message used in any SQL exception thrown because a {@code null} result set has been returned by the + * DataStax driver when a query is executed. + */ + public static final String NO_RESULT_SET = + "No ResultSet returned from the CQL statement passed in an 'executeQuery()' method."; + + /** + * Error message used in any SQL exception thrown when the parameter passed to the method + * {@code Statement.getMoreResults(int)} is invalid. This message is a template expecting the value of the + * invalid parameter as placeholder (example: {@code String.format(BAD_KEEP_RS, 9)}). + */ + public static final String BAD_KEEP_RS = + "The argument for keeping the current result set: %d is not a valid value."; + + /** + * Error message used in any SQL exception thrown when the expected type of result set for a + * {@link Statement} is invalid. This message is a template expecting the value of the + * invalid type of result set as placeholder (example: {@code String.format(BAD_TYPE_RS, 1099)}). + */ + public static final String BAD_TYPE_RS = "The argument for result set type: %d is not a valid value."; + + /** + * Error message used in any SQL exception thrown when the expected result set concurrency for a + * {@link Statement} is invalid. This message is a template expecting the value of the + * invalid result set concurrency value as placeholder (example: {@code String.format(BAD_CONCURRENCY_RS, 1099)}). + */ + public static final String BAD_CONCURRENCY_RS = + "The argument for result set concurrency: %d is not a valid value."; + + /** + * Error message used in any SQL exception thrown when the expected result set holdability for a + * {@link Statement} is invalid. This message is a template expecting the value of the + * invalid result set holdability value as placeholder (example: {@code String.format(BAD_HOLD_RS, 9)}). + */ + public static final String BAD_HOLD_RS = + "The argument for result set holdability: %d is not a valid value."; + + /** + * Error message used in any SQL exception thrown when the expected fetching direction for a + * {@link Statement} or a {@link ResultSet} is invalid. This message is a template expecting the + * value of the invalid fetching direction value as placeholder (example: + * {@code String.format(BAD_FETCH_DIR, 1099)}). + */ + public static final String BAD_FETCH_DIR = "Fetch direction value of: %d is illegal."; + + /** + * Error message used in any SQL exception thrown when the expected key auto-generation parameter used in a + * {@link Statement} is invalid. Note that auto-generated keys are currently not implemented in Cassandra. + * This message is a template expecting the value of the invalid parameter as placeholder (example: + * {@code String.format(BAD_AUTO_GEN, 9)}). + */ + public static final String BAD_AUTO_GEN = "Auto key generation value of: %d is illegal."; + + /** + * Error message used in any SQL exception thrown when the specified fetch size for a + * {@link Statement} or a {@link ResultSet} is negative. This message is a template expecting the + * value of the invalid fetch size value as placeholder (example: {@code String.format(BAD_FETCH_SIZE, -10)}). + */ + public static final String BAD_FETCH_SIZE = "Fetch size of: %d rows may not be negative."; + + /** + * Error message used in any SQL exception thrown when the specified column index in a {@link ResultSet} + * is not strictly positive or greater than the number of columns in the result set. This message is a template + * expecting the value of the invalid index value as placeholder (example: + * {@code String.format(MUST_BE_POSITIVE, 0)}). + */ + public static final String MUST_BE_POSITIVE = + "Index must be a positive number less or equal the count of returned columns: %d"; + + /** + * Error message used in any SQL exception thrown when the specified column name in a {@link ResultSet} + * is invalid. This message is a template expecting the value of the invalid column name as placeholder (example: + * {@code String.format(VALID_LABELS, "invalid_column")}). + */ + public static final String VALID_LABELS = "Name provided was not in the list of valid column labels: %s"; + + /** + * Error message used in any SQL exception thrown when the JDBC URL does not specify any host name. + */ + public static final String HOST_IN_URL = + "Connection url must specify a host, e.g. jdbc:cassandra://localhost:9042/keyspace"; + + /** + * Error message used in any SQL exception thrown when a connection cannot be established due to a missing host + * name. + */ + public static final String HOST_REQUIRED = "A host name is required to build a connection."; + + /** + * Error message used in any SQL exception thrown when the specified keyspace name is invalid. This message is a + * template expecting the value of the invalid keyspace name as placeholder (example: + * {@code String.format(BAD_KEYSPACE, "invalid_key$pace")}). + */ + public static final String BAD_KEYSPACE = + "Keyspace names must be composed of alphanumerics and underscores (parsed: '%s')."; + + /** + * Error message used in any SQL exception thrown when the provided JDBC URL contains not allowed user information + * (see {@link URI#getUserInfo()}). + */ + public static final String URI_IS_SIMPLE = + "Connection URL may only include host, port, keyspace, and allowed options, e.g. " + + "jdbc:cassandra://localhost:9042/keyspace?consistency=ONE"; + + /** + * Error message used in any SQL exception thrown when the required parameter {@code secureconnectbundle} is + * missing in the JDBC URL. + */ + public static final String SECURECONENCTBUNDLE_REQUIRED = "A 'secureconnectbundle' parameter is required."; + + /** + * Error message used in any SQL exception thrown because the {@link ResultSet} type is set to + * {@link ResultSet#TYPE_FORWARD_ONLY} (but cursors are currently not implemented in Cassandra). + */ + public static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; + + /** + * Error message used in any SQL exception thrown when the method {@link ResultSet#getURL(int)} or + * {@link ResultSet#getURL(String)} is invoked on a column containing an invalid URL. This message is a template + * expecting the invalid value as placeholder (example: {@code String.format(MALFORMED_URL, "not_a_valid_url")}). + */ + public static final String MALFORMED_URL = "The string '%s' is not a valid URL."; + + /** + * Error message used in any SQL exception thrown when the SSL configuration for the connection fails. This message + * is a template expecting the message of the error cause as placeholder (example: + * {@code String.format(SSL_CONFIG_FAILED, "Invalid certificate")}). + */ + public static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; + + /** + * Error message used in any SQL exception thrown when the method {@link CassandraResultSet#getVector(int)} or + * {@link CassandraResultSet#getVector(String)} is invoked on a column containing an invalid CQL vector. + */ + public static final String VECTOR_ELEMENTS_NOT_NUMBERS = "Vector elements are not numbers."; + + private ErrorConstants() { + // Private constructor to hide the public one. + } + +} diff --git a/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/JdbcUrlUtil.java similarity index 70% rename from src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java rename to src/main/java/com/ing/data/cassandra/jdbc/utils/JdbcUrlUtil.java index 77db68c..c67577b 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/utils/Utils.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/JdbcUrlUtil.java @@ -18,269 +18,301 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.ing.data.cassandra.jdbc.json.CassandraBlobDeserializer; -import com.ing.data.cassandra.jdbc.json.CassandraBlobSerializer; -import com.ing.data.cassandra.jdbc.json.CassandraDateDeserializer; -import com.ing.data.cassandra.jdbc.json.CassandraDateTimeDeserializer; -import com.ing.data.cassandra.jdbc.json.CassandraTimeDeserializer; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.sql.SQLNonTransientConnectionException; import java.sql.SQLSyntaxErrorException; import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.OffsetDateTime; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_KEYSPACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.HOST_IN_URL; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.HOST_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.SECURECONENCTBUNDLE_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.URI_IS_SIMPLE; + /** - * A set of static utility methods and constants used by the JDBC wrapper, various default values and error message - * strings that can be shared across classes. + * A set of static utility methods and constants used to parse the JDBC URL used to establish a connection to a + * Cassandra database. */ -public final class Utils { +public final class JdbcUrlUtil { + + /** + * Default Cassandra cluster port. + */ + public static final int DEFAULT_PORT = 9042; + /** * JDBC protocol for Cassandra connection. */ public static final String PROTOCOL = "jdbc:cassandra:"; + /** * JDBC protocol for Cassandra DBaaS connection. */ public static final String PROTOCOL_DBAAS = "jdbc:cassandra:dbaas:"; + /** - * Default Cassandra cluster port. + * JDBC URL parameter key for the CQL version. */ - public static final int DEFAULT_PORT = 9042; + public static final String KEY_VERSION = "version"; + /** - * Properties file name containing some properties relative to this JDBC wrapper (such as JDBC driver version, - * name, etc.). + * Property name used to retrieve the active CQL version when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code version} or from the default value defined in the + * property {@code database.defaultCqlVersion} of the resource file 'jdbc-driver.properties'. */ - public static final String JDBC_DRIVER_PROPERTIES_FILE = "jdbc-driver.properties"; + public static final String TAG_ACTIVE_CQL_VERSION = "activeCqlVersion"; /** - * JDBC URL parameter key for the database version. + * Property name used to retrieve the active CQL version when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code version}. */ - public static final String KEY_VERSION = "version"; + public static final String TAG_CQL_VERSION = "cqlVersion"; + /** * JDBC URL parameter key for the consistency. */ public static final String KEY_CONSISTENCY = "consistency"; + + /** + * Property name used to retrieve the consistency when the connection to Cassandra is established. This property + * is mapped from the JDBC URL parameter {@code consistency}. + */ + public static final String TAG_CONSISTENCY_LEVEL = "consistencyLevel"; + /** * JDBC URL parameter key for the connection number of retries. */ public static final String KEY_CONNECTION_RETRIES = "retries"; + + /** + * Property name used to retrieve the number of retries when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code retries}. + */ + public static final String TAG_CONNECTION_RETRIES = "retries"; + /** * JDBC URL parameter key for the load balancing policy. */ public static final String KEY_LOAD_BALANCING_POLICY = "loadbalancing"; + + /** + * Property name used to retrieve the load balancing policy when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code loadbalancing}. + */ + public static final String TAG_LOAD_BALANCING_POLICY = "loadBalancing"; + /** * JDBC URL parameter key for the local data center. */ public static final String KEY_LOCAL_DATACENTER = "localdatacenter"; + + /** + * Property name used to retrieve the local data center when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code localdatacenter}. + */ + public static final String TAG_LOCAL_DATACENTER = "localDatacenter"; + /** * JDBC URL parameter key for the retry policy. */ public static final String KEY_RETRY_POLICY = "retry"; + + /** + * Property name used to retrieve the retry policy when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code retry}. + */ + public static final String TAG_RETRY_POLICY = "retry"; + /** * JDBC URL parameter key for the reconnection policy. */ public static final String KEY_RECONNECT_POLICY = "reconnection"; + + /** + * Property name used to retrieve the reconnection policy when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code reconnection}. + */ + public static final String TAG_RECONNECT_POLICY = "reconnection"; + /** * JDBC URL parameter key for the debug mode. */ public static final String KEY_DEBUG = "debug"; + + /** + * Property name used to retrieve the debug mode value when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code debug}. + */ + public static final String TAG_DEBUG = "debug"; + /** * JDBC URL parameter key for SSL enabling. */ public static final String KEY_ENABLE_SSL = "enablessl"; + + /** + * Property name used to retrieve the SSL enabling value when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code enablessl}. + */ + public static final String TAG_ENABLE_SSL = "enableSsl"; + /** * JDBC URL parameter key for the custom SSL engine factory ({@link SslEngineFactory}). */ public static final String KEY_SSL_ENGINE_FACTORY = "sslenginefactory"; + + /** + * Property name used to retrieve the custom SSL engine factory when the connection to Cassandra is established. + * This property is mapped from the JDBC URL parameter {@code sslenginefactory}. + */ + public static final String TAG_SSL_ENGINE_FACTORY = "sslEngineFactory"; + /** * JDBC URL parameter key for SSL hostname verification disabling. */ public static final String KEY_SSL_HOSTNAME_VERIFICATION = "hostnameverification"; + + /** + * Property name used to retrieve the SSL hostname verification enabling when the connection to Cassandra is + * established. This property is mapped from the JDBC URL parameter {@code hostnameverification}. + */ + public static final String TAG_SSL_HOSTNAME_VERIFICATION = "hostnameVerification"; + /** * JDBC URL parameter key for the cloud secure connect bundle. */ public static final String KEY_CLOUD_SECURE_CONNECT_BUNDLE = "secureconnectbundle"; + + /** + * Property name used to retrieve the secure connect Bundle when the connection to Cassandra DBaaS is established. + * This property is mapped from the JDBC URL parameter {@code secureconnectbundle}. + */ + public static final String TAG_CLOUD_SECURE_CONNECT_BUNDLE = "secureConnectBundle"; + /** * JDBC URL parameter key for the username. */ public static final String KEY_USER = "user"; + + /** + * Property name used to retrieve the username when the connection to Cassandra is established. This property + * is mapped from the JDBC URL parameter {@code user}. + */ + public static final String TAG_USER = "user"; + /** * JDBC URL parameter key for the user password. */ public static final String KEY_PASSWORD = "password"; + + /** + * Property name used to retrieve the user password when the connection to Cassandra is established. This property + * is mapped from the JDBC URL parameter {@code password}. + */ + public static final String TAG_PASSWORD = "password"; + /** * JDBC URL parameter key for the request timeout. */ public static final String KEY_REQUEST_TIMEOUT = "requesttimeout"; + + /** + * Property name used to retrieve the request timeout when the connection to Cassandra is established. This property + * is mapped from the JDBC URL parameter {@code requesttimeout}. + */ + public static final String TAG_REQUEST_TIMEOUT = "requestTimeout"; + /** * JDBC URL parameter key for the connection timeout. */ public static final String KEY_CONNECT_TIMEOUT = "connecttimeout"; + + /** + * Property name used to retrieve the connection timeout when the connection to Cassandra is established. This + * property is mapped from the JDBC URL parameter {@code connecttimeout}. + */ + public static final String TAG_CONNECT_TIMEOUT = "connectTimeout"; + /** * JDBC URL parameter key for the Nagle's algorithm enabling. */ public static final String KEY_TCP_NO_DELAY = "tcpnodelay"; + + /** + * Property name used to retrieve the Nagle's algorithm enabling when the connection to Cassandra is established. + * This property is mapped from the JDBC URL parameter {@code tcpnodelay}. + */ + public static final String TAG_TCP_NO_DELAY = "tcpNoDelay"; + /** * JDBC URL parameter key for the TCP keep-alive enabling. */ public static final String KEY_KEEP_ALIVE = "keepalive"; + + /** + * Property name used to retrieve the TCP keep-alive enabling when the connection to Cassandra is established. + * This property is mapped from the JDBC URL parameter {@code keepalive}. + */ + public static final String TAG_KEEP_ALIVE = "keepAlive"; + /** * JDBC URL parameter key for the configuration file. */ public static final String KEY_CONFIG_FILE = "configfile"; + + /** + * Property name used to retrieve the configuration file when the connection to Cassandra is established. + * This property is mapped from the JDBC URL parameter {@code configfile}. + */ + public static final String TAG_CONFIG_FILE = "configFile"; + /** * JDBC URL parameter key for the compliance mode. */ public static final String KEY_COMPLIANCE_MODE = "compliancemode"; - public static final String TAG_USER = "user"; - public static final String TAG_PASSWORD = "password"; - public static final String TAG_DATABASE_NAME = "databaseName"; - public static final String TAG_SERVER_NAME = "serverName"; - public static final String TAG_PORT_NUMBER = "portNumber"; - public static final String TAG_ACTIVE_CQL_VERSION = "activeCqlVersion"; - public static final String TAG_CQL_VERSION = "cqlVersion"; - public static final String TAG_CONSISTENCY_LEVEL = "consistencyLevel"; - public static final String TAG_LOAD_BALANCING_POLICY = "loadBalancing"; - public static final String TAG_LOCAL_DATACENTER = "localDatacenter"; - public static final String TAG_RETRY_POLICY = "retry"; - public static final String TAG_RECONNECT_POLICY = "reconnection"; - public static final String TAG_DEBUG = "debug"; - public static final String TAG_CONNECTION_RETRIES = "retries"; - public static final String TAG_ENABLE_SSL = "enableSsl"; - public static final String TAG_SSL_ENGINE_FACTORY = "sslEngineFactory"; - public static final String TAG_SSL_HOSTNAME_VERIFICATION = "hostnameVerification"; - public static final String TAG_CLOUD_SECURE_CONNECT_BUNDLE = "secureConnectBundle"; - public static final String TAG_CONFIG_FILE = "configFile"; - public static final String TAG_REQUEST_TIMEOUT = "requestTimeout"; - public static final String TAG_CONNECT_TIMEOUT = "connectTimeout"; - public static final String TAG_TCP_NO_DELAY = "tcpNoDelay"; - public static final String TAG_KEEP_ALIVE = "keepAlive"; + /** + * Property name used to retrieve the compliance mode to use when the connection to Cassandra is established. + * This property is mapped from the JDBC URL parameter {@code compliancemode}. + */ public static final String TAG_COMPLIANCE_MODE = "complianceMode"; - public static final String JSSE_TRUSTSTORE_PROPERTY = "javax.net.ssl.trustStore"; - public static final String JSSE_TRUSTSTORE_PASSWORD_PROPERTY = "javax.net.ssl.trustStorePassword"; - public static final String JSSE_KEYSTORE_PROPERTY = "javax.net.ssl.keyStore"; - public static final String JSSE_KEYSTORE_PASSWORD_PROPERTY = "javax.net.ssl.keyStorePassword"; - - /** - * {@code NULL} CQL keyword. - */ - public static final String NULL_KEYWORD = "NULL"; - - public static final String WAS_CLOSED_CONN = "Method was called on a closed Connection."; - public static final String WAS_CLOSED_STMT = "Method was called on a closed Statement."; - public static final String WAS_CLOSED_RS = "Method was called on a closed ResultSet."; - public static final String NO_INTERFACE = "No object was found that matched the provided interface: %s"; - public static final String NO_TRANSACTIONS = "The Cassandra implementation does not support transactions."; - public static final String ALWAYS_AUTOCOMMIT = "The Cassandra implementation is always in auto-commit mode."; - public static final String BAD_TIMEOUT = "The timeout value was less than zero."; - public static final String NOT_SUPPORTED = "The Cassandra implementation does not support this method."; - public static final String NO_GEN_KEYS = - "The Cassandra implementation does not currently support returning generated keys."; - public static final String NO_MULTIPLE = - "The Cassandra implementation does not currently support multiple open Result Sets."; - public static final String NO_RESULT_SET = - "No ResultSet returned from the CQL statement passed in an 'executeQuery()' method."; - public static final String BAD_KEEP_RS = - "The argument for keeping the current result set: %s is not a valid value."; - public static final String BAD_TYPE_RS = "The argument for result set type: %s is not a valid value."; - public static final String BAD_CONCURRENCY_RS = - "The argument for result set concurrency: %s is not a valid value."; - public static final String BAD_HOLD_RS = - "The argument for result set holdability: %s is not a valid value."; - public static final String BAD_FETCH_DIR = "Fetch direction value of: %s is illegal."; - public static final String BAD_AUTO_GEN = "Auto key generation value of: %s is illegal."; - public static final String BAD_FETCH_SIZE = "Fetch size of: %s rows may not be negative."; - public static final String MUST_BE_POSITIVE = - "Index must be a positive number less or equal the count of returned columns: %d"; - public static final String VALID_LABELS = "Name provided was not in the list of valid column labels: %s"; - public static final String HOST_IN_URL = - "Connection url must specify a host, e.g. jdbc:cassandra://localhost:9042/keyspace"; - public static final String HOST_REQUIRED = "A 'host' name is required to build a Connection."; - public static final String BAD_KEYSPACE = - "Keyspace names must be composed of alphanumerics and underscores (parsed: '%s')."; - public static final String URI_IS_SIMPLE = - "Connection url may only include host, port, and keyspace, consistency and version option, e.g. " - + "jdbc:cassandra://localhost:9042/keyspace?version=3.0.0&consistency=ONE"; - public static final String SECURECONENCTBUNDLE_REQUIRED = "A 'secureconnectbundle' parameter is required."; - public static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; - public static final String MALFORMED_URL = "The string '%s' is not a valid URL."; - public static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; - public static final String VECTOR_ELEMENTS_NOT_NUMBERS = "Vector elements are not numbers."; - - static final Logger LOG = LoggerFactory.getLogger(Utils.class); - - static ObjectMapper objectMapperInstance = null; - - private Utils() { - // Private constructor to hide the public one. - } + /** + * Property name used to retrieve the keyspace name when the connection to Cassandra is established. This property + * is mapped from the JDBC URL keyspace path parameter. + */ + public static final String TAG_DATABASE_NAME = "databaseName"; /** - * Gets a property value from the Cassandra JDBC driver properties file. - * - * @param name The name of the property. - * @return The property value or an empty string the value cannot be retrieved. - */ - public static String getDriverProperty(final String name) { - try (final InputStream propertiesFile = - Utils.class.getClassLoader().getResourceAsStream(JDBC_DRIVER_PROPERTIES_FILE)) { - final Properties driverProperties = new Properties(); - driverProperties.load(propertiesFile); - return driverProperties.getProperty(name, StringUtils.EMPTY); - } catch (IOException ex) { - LOG.error("Unable to get JDBC driver property: {}.", name, ex); - return StringUtils.EMPTY; - } - } + * Property name used to retrieve the contact points when the connection to Cassandra is established. This property + * is mapped from the JDBC URL host. + */ + public static final String TAG_SERVER_NAME = "serverName"; /** - * Gets a part of a version string. - *

- * It uses the dot character as separator to parse the different parts of a version (major, minor, patch). - *

- * - * @param version The version string (for example X.Y.Z). - * @param part The part of the version to extract (for the semantic versioning, use 0 for the major version, 1 for - * the minor and 2 for the patch). - * @return The requested part of the version, or 0 if the requested part cannot be parsed correctly. - */ - public static int parseVersion(final String version, final int part) { - if (StringUtils.isBlank(version) || StringUtils.countMatches(version, ".") < part || part < 0) { - return 0; - } else { - try { - return Integer.parseInt(version.split("\\.")[part]); - } catch (final NumberFormatException ex) { - LOG.error("Unable to parse version: {}", version); - return 0; - } - } + * Property name used to retrieve the port used when the connection to Cassandra is established. This property + * is mapped from the JDBC URL port. + */ + public static final String TAG_PORT_NUMBER = "portNumber"; + + static final Logger LOG = LoggerFactory.getLogger(JdbcUrlUtil.class); + + private JdbcUrlUtil() { + // Private constructor to hide the public one. } /** @@ -551,7 +583,7 @@ private static Map getReconnectionPolicy(final String prim policyParametersMap.put(DefaultDriverOption.RECONNECTION_POLICY_CLASS, primaryReconnectionPolicyClass); // Parameters have been specified - if (parameters.length() > 0) { + if (!parameters.isEmpty()) { final String paramsRegex = "([^,]+\\(.+?\\))|([^,]+)"; final Pattern paramsPattern = Pattern.compile(paramsRegex); final Matcher paramsMatcher = paramsPattern.matcher(parameters); @@ -584,29 +616,4 @@ private static Map getReconnectionPolicy(final String prim return policyParametersMap; } - /** - * Gets a pre-configured {@link ObjectMapper} for JSON support. - * - * @return A pre-configured {@link ObjectMapper} for JSON support. - */ - public static ObjectMapper getObjectMapper() { - if (objectMapperInstance != null) { - return objectMapperInstance; - } else { - final ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - objectMapper.registerModule(new JavaTimeModule()); - final SimpleModule cassandraExtensionsModule = new SimpleModule(); - cassandraExtensionsModule.addDeserializer(ByteBuffer.class, new CassandraBlobDeserializer()); - cassandraExtensionsModule.addDeserializer(LocalDate.class, new CassandraDateDeserializer()); - cassandraExtensionsModule.addDeserializer(LocalTime.class, new CassandraTimeDeserializer()); - cassandraExtensionsModule.addDeserializer(OffsetDateTime.class, new CassandraDateTimeDeserializer()); - cassandraExtensionsModule.addSerializer(ByteBuffer.class, new CassandraBlobSerializer()); - objectMapper.registerModule(cassandraExtensionsModule); - objectMapperInstance = objectMapper; - return objectMapper; - } - } - } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/utils/JsonUtil.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/JsonUtil.java new file mode 100644 index 0000000..869d8d0 --- /dev/null +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/JsonUtil.java @@ -0,0 +1,70 @@ +/* + * + * 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.ing.data.cassandra.jdbc.utils; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.ing.data.cassandra.jdbc.json.CassandraBlobDeserializer; +import com.ing.data.cassandra.jdbc.json.CassandraBlobSerializer; +import com.ing.data.cassandra.jdbc.json.CassandraDateDeserializer; +import com.ing.data.cassandra.jdbc.json.CassandraDateTimeDeserializer; +import com.ing.data.cassandra.jdbc.json.CassandraTimeDeserializer; + +import java.nio.ByteBuffer; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; + +/** + * Utility methods used for JSON-type handling. + */ +public final class JsonUtil { + + static ObjectMapper objectMapperInstance = null; + + private JsonUtil() { + // Private constructor to hide the public one. + } + + /** + * Gets a pre-configured {@link ObjectMapper} for JSON support. + * + * @return A pre-configured {@link ObjectMapper} for JSON support. + */ + public static ObjectMapper getObjectMapper() { + if (objectMapperInstance != null) { + return objectMapperInstance; + } else { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + objectMapper.registerModule(new JavaTimeModule()); + final SimpleModule cassandraExtensionsModule = new SimpleModule(); + cassandraExtensionsModule.addDeserializer(ByteBuffer.class, new CassandraBlobDeserializer()); + cassandraExtensionsModule.addDeserializer(LocalDate.class, new CassandraDateDeserializer()); + cassandraExtensionsModule.addDeserializer(LocalTime.class, new CassandraTimeDeserializer()); + cassandraExtensionsModule.addDeserializer(OffsetDateTime.class, new CassandraDateTimeDeserializer()); + cassandraExtensionsModule.addSerializer(ByteBuffer.class, new CassandraBlobSerializer()); + objectMapper.registerModule(cassandraExtensionsModule); + objectMapperInstance = objectMapper; + return objectMapper; + } + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java index 1e84d0a..e11a959 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java @@ -56,19 +56,18 @@ import java.util.Optional; import static com.ing.data.cassandra.jdbc.SessionHolder.URL_KEY; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_TIMEOUT; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_KEYSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.JSSE_TRUSTSTORE_PROPERTY; -import static com.ing.data.cassandra.jdbc.utils.Utils.SSL_CONFIG_FAILED; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_KEYSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_KEYSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_TRUSTSTORE_PASSWORD_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.JSSE_TRUSTSTORE_PROPERTY; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.SSL_CONFIG_FAILED; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java index f6c5472..aeabe3c 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/DataSourceUnitTest.java @@ -13,12 +13,12 @@ */ package com.ing.data.cassandra.jdbc; -import com.ing.data.cassandra.jdbc.utils.Utils; import org.junit.jupiter.api.Test; import javax.sql.DataSource; import java.sql.SQLException; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CQL_VERSION; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -58,7 +58,7 @@ void givenParameters_whenConstructDataSource_returnCassandraDataSource() throws cnx = ds.getConnection(); assertFalse(cnx.isClosed()); ds.setLoginTimeout(5); - assertEquals(VERSION, ((CassandraConnection) cnx).getConnectionProperties().get(Utils.TAG_CQL_VERSION)); + assertEquals(VERSION, ((CassandraConnection) cnx).getConnectionProperties().get(TAG_CQL_VERSION)); assertEquals(5, ds.getLoginTimeout()); } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java index 4fe096b..aec67aa 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java @@ -15,7 +15,6 @@ import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverOption; -import com.ing.data.cassandra.jdbc.utils.Utils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -32,16 +31,33 @@ import java.util.Properties; import java.util.stream.Stream; -import static com.ing.data.cassandra.jdbc.utils.Utils.BAD_KEYSPACE; -import static com.ing.data.cassandra.jdbc.utils.Utils.DEFAULT_PORT; -import static com.ing.data.cassandra.jdbc.utils.Utils.HOST_IN_URL; -import static com.ing.data.cassandra.jdbc.utils.Utils.HOST_REQUIRED; -import static com.ing.data.cassandra.jdbc.utils.Utils.SECURECONENCTBUNDLE_REQUIRED; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_PORT_NUMBER; -import static com.ing.data.cassandra.jdbc.utils.Utils.TAG_SERVER_NAME; -import static com.ing.data.cassandra.jdbc.utils.Utils.URI_IS_SIMPLE; -import static com.ing.data.cassandra.jdbc.utils.Utils.getDriverProperty; -import static com.ing.data.cassandra.jdbc.utils.Utils.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.parseVersion; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_KEYSPACE; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.HOST_IN_URL; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.HOST_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.SECURECONENCTBUNDLE_REQUIRED; +import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.URI_IS_SIMPLE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.DEFAULT_PORT; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.PROTOCOL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CLOUD_SECURE_CONNECT_BUNDLE; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONNECTION_RETRIES; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONSISTENCY_LEVEL; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CQL_VERSION; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DATABASE_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DEBUG; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOAD_BALANCING_POLICY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOCAL_DATACENTER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PASSWORD; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PORT_NUMBER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_RECONNECT_POLICY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_REQUEST_TIMEOUT; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_RETRY_POLICY; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SERVER_NAME; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.createSubName; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.parseReconnectionPolicy; +import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.parseURL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -52,75 +68,75 @@ static Stream buildUrlParsingTestCases() { return Stream.of( Arguments.of("jdbc:cassandra://localhost:9042/astra?secureconnectbundle=/path/to/location/filename.extn&user=user1&password=password1", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, "astra"); - put(Utils.TAG_CLOUD_SECURE_CONNECT_BUNDLE, "/path/to/location/filename.extn"); - put(Utils.TAG_USER, "user1"); - put(Utils.TAG_PASSWORD, "password1"); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, "astra"); + put(TAG_CLOUD_SECURE_CONNECT_BUNDLE, "/path/to/location/filename.extn"); + put(TAG_USER, "user1"); + put(TAG_PASSWORD, "password1"); }}), Arguments.of("jdbc:cassandra:dbaas:///astra?secureconnectbundle=/path/to/location/filename.extn&user=user1&password=password1", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, null); - put(Utils.TAG_PORT_NUMBER, String.valueOf(DEFAULT_PORT)); - put(Utils.TAG_DATABASE_NAME, "astra"); - put(Utils.TAG_CLOUD_SECURE_CONNECT_BUNDLE, "/path/to/location/filename.extn"); - put(Utils.TAG_USER, "user1"); - put(Utils.TAG_PASSWORD, "password1"); + put(TAG_SERVER_NAME, null); + put(TAG_PORT_NUMBER, String.valueOf(DEFAULT_PORT)); + put(TAG_DATABASE_NAME, "astra"); + put(TAG_CLOUD_SECURE_CONNECT_BUNDLE, "/path/to/location/filename.extn"); + put(TAG_USER, "user1"); + put(TAG_PASSWORD, "password1"); }}), Arguments.of("jdbc:cassandra://localhost:9042/Keyspace1?version=3.0.0&consistency=QUORUM", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, "Keyspace1"); - put(Utils.TAG_CQL_VERSION, "3.0.0"); - put(Utils.TAG_CONSISTENCY_LEVEL, "QUORUM"); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, "Keyspace1"); + put(TAG_CQL_VERSION, "3.0.0"); + put(TAG_CONSISTENCY_LEVEL, "QUORUM"); }}), Arguments.of("jdbc:cassandra://localhost/Keyspace1?consistency=QUORUM", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, "Keyspace1"); - put(Utils.TAG_CQL_VERSION, null); - put(Utils.TAG_CONSISTENCY_LEVEL, "QUORUM"); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, "Keyspace1"); + put(TAG_CQL_VERSION, null); + put(TAG_CONSISTENCY_LEVEL, "QUORUM"); }}), Arguments.of("jdbc:cassandra://localhost/Keyspace1?version=2.0.0", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, "Keyspace1"); - put(Utils.TAG_CQL_VERSION, "2.0.0"); - put(Utils.TAG_CONSISTENCY_LEVEL, null); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, "Keyspace1"); + put(TAG_CQL_VERSION, "2.0.0"); + put(TAG_CONSISTENCY_LEVEL, null); }}), Arguments.of("jdbc:cassandra://localhost", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, null); - put(Utils.TAG_CQL_VERSION, null); - put(Utils.TAG_CONSISTENCY_LEVEL, null); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, null); + put(TAG_CQL_VERSION, null); + put(TAG_CONSISTENCY_LEVEL, null); }}), Arguments.of("jdbc:cassandra://localhost/Keyspace1?localdatacenter=DC1", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, "Keyspace1"); - put(Utils.TAG_LOCAL_DATACENTER, "DC1"); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, "Keyspace1"); + put(TAG_LOCAL_DATACENTER, "DC1"); }}), Arguments.of("jdbc:cassandra://localhost/Keyspace1?localdatacenter=DC1&debug=true" + "&retries=5&requesttimeout=3000&loadbalancing=com.company.package.CustomLBPolicy" + "&retry=com.company.package.CustomRetryPolicy&reconnection=ConstantReconnectionPolicy()", new HashMap() {{ - put(Utils.TAG_SERVER_NAME, "localhost"); - put(Utils.TAG_PORT_NUMBER, "9042"); - put(Utils.TAG_DATABASE_NAME, "Keyspace1"); - put(Utils.TAG_LOCAL_DATACENTER, "DC1"); - put(Utils.TAG_DEBUG, "true"); - put(Utils.TAG_CONNECTION_RETRIES, "5"); - put(Utils.TAG_LOAD_BALANCING_POLICY, "com.company.package.CustomLBPolicy"); - put(Utils.TAG_RETRY_POLICY, "com.company.package.CustomRetryPolicy"); - put(Utils.TAG_RECONNECT_POLICY, "ConstantReconnectionPolicy()"); - put(Utils.TAG_REQUEST_TIMEOUT, "3000"); + put(TAG_SERVER_NAME, "localhost"); + put(TAG_PORT_NUMBER, "9042"); + put(TAG_DATABASE_NAME, "Keyspace1"); + put(TAG_LOCAL_DATACENTER, "DC1"); + put(TAG_DEBUG, "true"); + put(TAG_CONNECTION_RETRIES, "5"); + put(TAG_LOAD_BALANCING_POLICY, "com.company.package.CustomLBPolicy"); + put(TAG_RETRY_POLICY, "com.company.package.CustomRetryPolicy"); + put(TAG_RECONNECT_POLICY, "ConstantReconnectionPolicy()"); + put(TAG_REQUEST_TIMEOUT, "3000"); }}) ); } @@ -130,7 +146,7 @@ static Stream buildUrlParsingTestCases() { void givenJdbcUrl_whenParseUrl_returnExpectedProperties(final String jdbcUrl, final Map expectedProperties) throws SQLException { - final Properties result = Utils.parseURL(jdbcUrl); + final Properties result = parseURL(jdbcUrl); expectedProperties.forEach((key, value) -> assertEquals(value, result.getProperty(key))); } @@ -171,7 +187,7 @@ static Stream buildReconnectionPolicyParsingTestCases() { @MethodSource("buildReconnectionPolicyParsingTestCases") void givenReconnectionPolicyString_whenParsePolicy_returnExpectedOptions( final String policyString, final Map expectedPolicy) { - final Map policyOptions = Utils.parseReconnectionPolicy(policyString); + final Map policyOptions = parseReconnectionPolicy(policyString); assertNotNull(policyOptions); expectedPolicy.forEach((key, value) -> assertEquals(value, policyOptions.get(key))); } @@ -179,28 +195,28 @@ void givenReconnectionPolicyString_whenParsePolicy_returnExpectedOptions( @Test void testCreateSubName() throws Exception { final String jdbcUrl = "jdbc:cassandra://localhost:9042/Keyspace1?consistency=QUORUM&version=3.0.0"; - final Properties props = Utils.parseURL(jdbcUrl); - final String result = Utils.createSubName(props); - assertEquals(jdbcUrl, Utils.PROTOCOL + result); + final Properties props = parseURL(jdbcUrl); + final String result = createSubName(props); + assertEquals(jdbcUrl, PROTOCOL + result); } @Test void testCreateSubNameWithoutParams() throws Exception { final String jdbcUrl = "jdbc:cassandra://localhost:9042/Keyspace1"; - final Properties props = Utils.parseURL(jdbcUrl); - final String result = Utils.createSubName(props); - assertEquals(jdbcUrl, Utils.PROTOCOL + result); + final Properties props = parseURL(jdbcUrl); + final String result = createSubName(props); + assertEquals(jdbcUrl, PROTOCOL + result); } @Test void testInvalidJdbcUrl() { - assertThrows(SQLSyntaxErrorException.class, () -> Utils.parseURL("jdbc:cassandra/bad%uri")); + assertThrows(SQLSyntaxErrorException.class, () -> parseURL("jdbc:cassandra/bad%uri")); } @Test void testNullHost() { final SQLNonTransientConnectionException exception = assertThrows(SQLNonTransientConnectionException.class, - () -> Utils.parseURL("jdbc:cassandra:")); + () -> parseURL("jdbc:cassandra:")); assertEquals(HOST_IN_URL, exception.getMessage()); } @@ -208,40 +224,40 @@ void testNullHost() { void testInvalidKeyspaceName() { final String invalidKeyspaceName = "bad-keyspace"; final SQLNonTransientConnectionException exception = assertThrows(SQLNonTransientConnectionException.class, - () -> Utils.parseURL("jdbc:cassandra://hostname:9042/" + invalidKeyspaceName)); + () -> parseURL("jdbc:cassandra://hostname:9042/" + invalidKeyspaceName)); assertEquals(String.format(BAD_KEYSPACE, invalidKeyspaceName), exception.getMessage()); } @Test void testNotNullUserInfo() { final SQLNonTransientConnectionException exception = assertThrows(SQLNonTransientConnectionException.class, - () -> Utils.parseURL("jdbc:cassandra://john_doe@hostname:9042/validKeyspace")); + () -> parseURL("jdbc:cassandra://john_doe@hostname:9042/validKeyspace")); assertEquals(URI_IS_SIMPLE, exception.getMessage()); } @Test void testCreateSubNameWithoutHost() throws Exception { final String jdbcUrl = "jdbc:cassandra://localhost:9042/Keyspace1"; - final Properties props = Utils.parseURL(jdbcUrl); + final Properties props = parseURL(jdbcUrl); props.remove(TAG_SERVER_NAME); final SQLNonTransientConnectionException exception = assertThrows(SQLNonTransientConnectionException.class, - () -> Utils.createSubName(props)); + () -> createSubName(props)); assertEquals(HOST_REQUIRED, exception.getMessage()); } @Test void testCreateSubNameWithInvalidPortNumber() throws Exception { final String jdbcUrl = "jdbc:cassandra://localhost/Keyspace1"; - final Properties props = Utils.parseURL(jdbcUrl); + final Properties props = parseURL(jdbcUrl); props.put(TAG_PORT_NUMBER, "-9042"); - assertThrows(SQLNonTransientConnectionException.class, () -> Utils.createSubName(props)); + assertThrows(SQLNonTransientConnectionException.class, () -> createSubName(props)); } @ParameterizedTest @ValueSource(strings = {"jdbc:cassandra:dbaas:///astra", "jdbc:cassandra:dbaas:///astra?user=User1"}) void testMissingSecureConnectBundleOnDbaasConenctionString(final String jdbcUrl) { final SQLNonTransientConnectionException exception = assertThrows(SQLNonTransientConnectionException.class, - () -> Utils.parseURL(jdbcUrl)); + () -> parseURL(jdbcUrl)); assertEquals(SECURECONENCTBUNDLE_REQUIRED, exception.getMessage()); } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java index a6df575..be326c5 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodecTest.java @@ -28,7 +28,7 @@ import java.nio.ByteBuffer; import java.util.stream.Stream; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java index bdb2bf7..2306a48 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java index 9eb1a33..dd5b02e 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java index 1232caf..0e28c2a 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java index 223f23d..ade6eb6 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java index 9ad2610..f3b524e 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java index 6652589..34b3daf 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodecTest.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; import java.time.Instant; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java index 6a7b0fc..7004117 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java b/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java index 5509125..fb1d530 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodecTest.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; -import static com.ing.data.cassandra.jdbc.utils.Utils.NULL_KEYWORD; +import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; From c2b8e1c5ab7089bdda8e81fa5d917fee26c05fac Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:56:23 +0200 Subject: [PATCH 16/21] Refactor connection test with SSL using container (and add some missing Javadoc on DataTypeEnum values) --- .../cassandra/jdbc/types/DataTypeEnum.java | 108 ++++++++++++++++++ .../cassandra/jdbc/ConnectionUnitTest.java | 26 +++-- .../jdbc/UsingCassandraContainerTest.java | 7 +- src/test/resources/cassandra.keystore | Bin 0 -> 2734 bytes src/test/resources/cassandra.truststore | Bin 0 -> 1254 bytes .../resources/config_override/cassandra.yaml | 18 +-- src/test/resources/test_keystore.jks | Bin 5071 -> 0 bytes src/test/resources/test_truststore.jks | Bin 1330 -> 0 bytes 8 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 src/test/resources/cassandra.keystore create mode 100644 src/test/resources/cassandra.truststore delete mode 100644 src/test/resources/test_keystore.jks delete mode 100644 src/test/resources/test_truststore.jks diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index c53097b..d1fe6e3 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -42,38 +42,146 @@ */ public enum DataTypeEnum { + /** + * {@code ascii} CQL type (type {@value DataType#ASCII} in CQL native protocol) mapped to {@link String} Java type. + */ ASCII(DataType.ASCII, String.class, cqlName(DataTypes.ASCII)), + /** + * {@code bigint} CQL type (type {@value DataType#BIGINT} in CQL native protocol) mapped to {@link Long} Java type. + */ BIGINT(DataType.BIGINT, Long.class, cqlName(DataTypes.BIGINT)), + /** + * {@code blob} CQL type (type {@value DataType#BLOB} in CQL native protocol) mapped to {@link ByteBuffer} Java + * type. + */ BLOB(DataType.BLOB, ByteBuffer.class, cqlName(DataTypes.BLOB)), + /** + * {@code boolean} CQL type (type {@value DataType#BOOLEAN} in CQL native protocol) mapped to {@link Boolean} Java + * type. + */ BOOLEAN(DataType.BOOLEAN, Boolean.class, cqlName(DataTypes.BOOLEAN)), + /** + * {@code counter} CQL type (type {@value DataType#COUNTER} in CQL native protocol) mapped to {@link Long} Java + * type. + */ COUNTER(DataType.COUNTER, Long.class, cqlName(DataTypes.COUNTER)), + /** + * {@code custom} CQL type (type {@value DataType#CUSTOM} in CQL native protocol) mapped to {@link ByteBuffer} Java + * type. + */ CUSTOM(DataType.CUSTOM, ByteBuffer.class, "CUSTOM"), + /** + * {@code date} CQL type (type {@value DataType#DATE} in CQL native protocol) mapped to {@link Date} Java type. + */ DATE(DataType.DATE, Date.class, cqlName(DataTypes.DATE)), + /** + * {@code decimal} CQL type (type {@value DataType#DECIMAL} in CQL native protocol) mapped to {@link BigDecimal} + * Java type. + */ DECIMAL(DataType.DECIMAL, BigDecimal.class, cqlName(DataTypes.DECIMAL)), + /** + * {@code double} CQL type (type {@value DataType#DOUBLE} in CQL native protocol) mapped to {@link Double} Java + * type. + */ DOUBLE(DataType.DOUBLE, Double.class, cqlName(DataTypes.DOUBLE)), + /** + * {@code duration} CQL type (type {@value DataType#DURATION} in CQL native protocol) mapped to {@link CqlDuration} + * Java type. + */ DURATION(DataType.DURATION, CqlDuration.class, cqlName(DataTypes.DURATION)), + /** + * {@code float} CQL type (type {@value DataType#FLOAT} in CQL native protocol) mapped to {@link Float} Java type. + */ FLOAT(DataType.FLOAT, Float.class, cqlName(DataTypes.FLOAT)), + /** + * {@code inet} CQL type (type {@value DataType#INET} in CQL native protocol) mapped to {@link InetAddress} Java + * type. + */ INET(DataType.INET, InetAddress.class, cqlName(DataTypes.INET)), + /** + * {@code int} CQL type (type {@value DataType#INT} in CQL native protocol) mapped to {@link Integer} Java type. + */ INT(DataType.INT, Integer.class, cqlName(DataTypes.INT)), + /** + * {@code list} CQL type (type {@value DataType#LIST} in CQL native protocol) mapped to {@link List} Java type. + */ LIST(DataType.LIST, List.class, "list"), + /** + * {@code map} CQL type (type {@value DataType#MAP} in CQL native protocol) mapped to {@link Map} Java type. + */ MAP(DataType.MAP, Map.class, "map"), + /** + * {@code set} CQL type (type {@value DataType#SET} in CQL native protocol) mapped to {@link Set} Java type. + */ SET(DataType.SET, Set.class, "set"), + /** + * {@code smallint} CQL type (type {@value DataType#SMALLINT} in CQL native protocol) mapped to {@link Short} Java + * type. + */ SMALLINT(DataType.SMALLINT, Short.class, cqlName(DataTypes.SMALLINT)), + /** + * {@code text} CQL type (type {@value DataType#VARCHAR} in CQL native protocol) mapped to {@link String} Java type. + */ TEXT(DataType.VARCHAR, String.class, cqlName(DataTypes.TEXT)), + /** + * {@code time} CQL type (type {@value DataType#TIME} in CQL native protocol) mapped to {@link Time} Java type. + */ TIME(DataType.TIME, Time.class, cqlName(DataTypes.TIME)), + /** + * {@code timestamp} CQL type (type {@value DataType#TIMESTAMP} in CQL native protocol) mapped to {@link Timestamp} + * Java type. + */ TIMESTAMP(DataType.TIMESTAMP, Timestamp.class, cqlName(DataTypes.TIMESTAMP)), + /** + * {@code timeuuid} CQL type (type {@value DataType#TIMEUUID} in CQL native protocol) mapped to {@link UUID} Java + * type. + */ TIMEUUID(DataType.TIMEUUID, UUID.class, cqlName(DataTypes.TIMEUUID)), + /** + * {@code tinyint} CQL type (type {@value DataType#TINYINT} in CQL native protocol) mapped to {@link Byte} Java + * type. + */ TINYINT(DataType.TINYINT, Byte.class, cqlName(DataTypes.TINYINT)), + /** + * {@code tuple} CQL type (type {@value DataType#TUPLE} in CQL native protocol) mapped to {@link TupleValue} Java + * type. + */ TUPLE(DataType.TUPLE, TupleValue.class, "tuple"), + /** + * {@code udt} CQL type (type {@value DataType#UDT} in CQL native protocol) mapped to {@link UdtValue} Java type. + */ UDT(DataType.UDT, UdtValue.class, "UDT"), + /** + * {@code uuid} CQL type (type {@value DataType#UUID} in CQL native protocol) mapped to {@link UUID} Java type. + */ UUID(DataType.UUID, UUID.class, cqlName(DataTypes.UUID)), + /** + * {@code varchar} CQL type (type {@value DataType#VARCHAR} in CQL native protocol) mapped to {@link String} Java + * type. + */ VARCHAR(DataType.VARCHAR, String.class, "VARCHAR"), + /** + * {@code varint} CQL type (type {@value DataType#VARINT} in CQL native protocol) mapped to {@link BigInteger} Java + * type. + */ VARINT(DataType.VARINT, BigInteger.class, cqlName(DataTypes.VARINT)), + /** + * {@code vector} CQL type (type {@value DataType#LIST} in CQL native protocol) mapped to {@link CqlVector} Java + * type. + */ VECTOR(DataType.LIST, CqlVector.class, "vector"); private static final Map CQL_DATATYPE_TO_DATATYPE; + + /** + * Gets the Java type corresponding to the enum value. + */ public final Class javaType; + /** + * Gets the CQL type corresponding to the enum value. + */ public final String cqlType; + final int protocolId; static { diff --git a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java index e11a959..0d11b2a 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/ConnectionUnitTest.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.CqlSessionBuilder; import com.datastax.oss.driver.api.core.auth.AuthProvider; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy; @@ -28,7 +27,6 @@ import com.datastax.oss.driver.internal.core.connection.ExponentialReconnectionPolicy; import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy; import com.datastax.oss.driver.internal.core.retry.DefaultRetryPolicy; -import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; import com.ing.data.cassandra.jdbc.optionset.Liquibase; import com.ing.data.cassandra.jdbc.utils.AnotherFakeLoadBalancingPolicy; import com.ing.data.cassandra.jdbc.utils.AnotherFakeRetryPolicy; @@ -351,7 +349,7 @@ void givenInvalidSslEngineFactory_whenGetConnection_throwsException() { /* * IMPORTANT NOTE: - * The resources 'test_keystore.jks' and 'test_truststore.jks' are provided for testing purpose only. They contain + * The resources 'cassandra.keystore' and 'cassandra.truststore' are provided for testing purpose only. They contain * self-signed certificate to not use in a production context. */ @@ -359,18 +357,24 @@ void givenInvalidSslEngineFactory_whenGetConnection_throwsException() { void givenEnabledSslWithJsse_whenConfigureSsl_addDefaultSslEngineFactoryToSessionBuilder() throws Exception { final ClassLoader classLoader = this.getClass().getClassLoader(); System.setProperty(JSSE_TRUSTSTORE_PROPERTY, - Objects.requireNonNull(classLoader.getResource("test_truststore.jks")).getPath()); + Objects.requireNonNull(classLoader.getResource("cassandra.truststore")).getPath()); System.setProperty(JSSE_TRUSTSTORE_PASSWORD_PROPERTY, "changeit"); System.setProperty(JSSE_KEYSTORE_PROPERTY, - Objects.requireNonNull(classLoader.getResource("test_keystore.jks")).getPath()); + Objects.requireNonNull(classLoader.getResource("cassandra.keystore")).getPath()); System.setProperty(JSSE_KEYSTORE_PASSWORD_PROPERTY, "changeit"); - final SessionHolder sessionHolder = new SessionHolder(Collections.singletonMap(URL_KEY, - buildJdbcUrl(cassandraContainer.getContactPoint().getHostName(), - cassandraContainer.getContactPoint().getPort(), KEYSPACE)), null); - final CqlSessionBuilder cqlSessionBuilder = spy(new CqlSessionBuilder()); - sessionHolder.configureDefaultSslEngineFactory(cqlSessionBuilder, DriverConfigLoader.programmaticBuilder()); - verify(cqlSessionBuilder).withSslEngineFactory(any(DefaultSslEngineFactory.class)); + initConnection(KEYSPACE, "enablessl=true", "localdatacenter=datacenter1"); + assertNotNull(sqlConnection); + assertNotNull(sqlConnection.getSession()); + assertNotNull(sqlConnection.getSession().getContext()); + assertTrue(sqlConnection.getSession().getContext().getSslEngineFactory().isPresent()); + + final Statement statement = sqlConnection.createStatement(); + final ResultSet resultSet = statement.executeQuery("SELECT * FROM system.local"); + assertNotNull(resultSet); + resultSet.close(); + statement.close(); + sqlConnection.close(); } @Test diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java index dc3c010..b3cad8b 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/UsingCassandraContainerTest.java @@ -19,6 +19,7 @@ import org.testcontainers.containers.CassandraContainer; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; import java.net.InetSocketAddress; import java.sql.DriverManager; @@ -37,6 +38,10 @@ abstract class UsingCassandraContainerTest { static final CassandraContainer cassandraContainer = new CassandraContainer<>(CASSANDRA_IMAGE) .withEnv("CASSANDRA_DC", "datacenter1") .withEnv("CASSANDRA_CLUSTER_NAME", "embedded_test_cluster") + .withCopyFileToContainer(MountableFile.forClasspathResource("cassandra.keystore"), + "/security/cassandra.keystore") + .withCopyFileToContainer(MountableFile.forClasspathResource("cassandra.truststore"), + "/security/cassandra.truststore") .withConfigurationOverride("config_override") .withInitScript("initEmbeddedCassandra.cql"); @@ -59,7 +64,7 @@ static void initConnection(final String keyspace, final String... parameters) th static CassandraConnection newConnection(final String keyspace, final String... parameters) throws Exception { final InetSocketAddress contactPoint = cassandraContainer.getContactPoint(); - return (CassandraConnection) DriverManager.getConnection(buildJdbcUrl(contactPoint.getHostName(), + return (CassandraConnection) DriverManager.getConnection(buildJdbcUrl(contactPoint.getHostName(), contactPoint.getPort(), keyspace, parameters)); } diff --git a/src/test/resources/cassandra.keystore b/src/test/resources/cassandra.keystore new file mode 100644 index 0000000000000000000000000000000000000000..46c4628673d4c1cc03f2e47fee8a71fadf1c7379 GIT binary patch literal 2734 zcma)8S5y-S7EK_903sbUOOt`2C?rIxK|v(a2P8ygAP}UgG!qDnfD%d=X#%0k0BYz4 z>0QP!lpsQoA{}W$s7eM!KpFPz9%uJ!_v4-W-hJ=h&wCz%!e7P%@NbnMKai(@0ya7#(Fo7)3h*(+ zb%fsEqaZ>ZBtGkryM_XMn^^6Bu1e?qsQFoh?8?LQqc9Ip9w7+f`K5Ks*yYy$;~J~Usn&Ezf&2bQ^0K@<>sw~BPC40Pi7z8=<0Cz%UyRC-j zMdSL}_Sc7dsf6*wUQCFy#HrfP(2*EB&eenFcX1J>WmUI#bswH=yxrLsj54rBE9-p< z&E=FlC!DKIF9>w1*g?Czx~yU84~9eMOO4}*x(}X?7y!w+YxHH_s<6$n-}~N%eQ_AK zbt?8!c7Vtk~#JHWNo zpQ_athpU0YtCYFvBJVNkc9KF9jb?X?Ki}mQd#70MkS*OZCO8t#lK&=mh&_;UnmD{) zL7iij2HIAU?nJaz*q7G_b^n?!5VZi~0RJ2@{lPhU+s-pF)$!gRC5x#|X~9A?X45PZ zPlNy`WgE9C^pef^i})T@d6jR>*@2yShz{ zZO4<`=&u0^Xxse&uXnaG#)rEhD%`oJ8uV1R1^9al2)-b~XTHyYs$U`ZHkp#D^@-%qO3ybm) zI@zAw)YfJpIi1tDm`g8byu%jgu7HOTd$8Lpf!g|BOBHR ztxF!=ERbu11@CMtHwAh^i(2~FcTd-d5ROQA-zjgUQ1=|_#FPAFy`ghR#Ct!W5LVNyGj9_7u8!im2YG@OIrsQRef-{ zb;;u5n1XACNDM;ZdtAcAk>awVfZG6nzzqQ52)&OwB7pRtClW|;kSNy2*FzquqpyuX z>7Ug?ptO((3Mlfs4-`^B0o^&G{CI$XBk=lr1N>iMZ3%afgEj@!3;Eek7|s3Ba&y&o z{r?H;vSbaz`GM_(K$^Qop~;y2eG2b~E8hH5wWZ+(QZU?zrTe_wJ5G&?LWPLiIdUT- zBUgQMXGPE}DU)*3wf`CZ`kUiAt6l0&?Z%@F9reBN#hwvVl=Xuqy)NKWs5vYPW1B>nZPi974gib9i;K~OTaGS<^i z>)yAa;9{cI*JYLsedQBN_rtLl*#U`P6q_soT`r5kWeFZ_R;{P&=J=DJ^Pn18kJeMr zjN_Z;%~bi(OeVPnEa&GF1T5{&Re$QhS6}0VOWH}G?s4_1II#O!>W%`|FQ$fa${&YxeiZI3##;i67k4ag$dwxAg26+=$iT1bs5_f|t0IY5u zma`PcwDCLa?`f;4_!Dod1Om4`1=D)oorzdBMUq_7Mk*Gqz-4`ThDxs6-IJ&ER2IhU zEF=ulbKV;v;hXkAws61by~}aU7e#ty3y<9^p7Ak}XUC1pvBxV$)&CK9Nbu!97|qaW zgKaHt6g#Gb(6r+t>BSMsFk#&%<)M#@LobI|k@9WMP}T%A7~2e)K3)v_=^6ani7B`Dp;LPCO?guGLHbGlbREz{Ylq(@usq zt9V~F?c*+|4vn__-rh(Z?hFzI!w7B*4H9NG7Bal)_uC}F)7VsMf9-Sx324PwswcW&AI9l6Di}?ynurE^_B?!RJlXj$>7I z6U6tT2666Zs_e2=n!Z(bD9zs3f-6Vk^7~#MTHRVNwN9`5%B|uTODCt~zxtbCIVYxIlWw*Gm7G}M$=*Tf? z`BHn|!s_Q}xqQo_ch-9<>$OfYIbT|(AH&%BGqA$a#@{c!CFW0B;z6{`P;&2_L9|BE zG=QKpWUvrVzxuO-RE;UG^H&D0BEJqd&}(b&K9yObC#Ll1VM7&LNQU+thDZTr0|Wso1Q6?+pKZ2afLx1J3ONVuy@Y^*1K`Gzie@|>F^6IuE7xDcO_j0s z{76d7MnUL6QYpFaMxq7)_EeLCznS$b1wZ+}h<5F!gsJ(^?zAc=P~=(B_oI&Gv$-d~s~tM93GmSj z&>`pIT4ir(R9=L{I5T$Q$~Sim;uKFhTu`QJZ6E z=oo1`#7^qCPwU6B)62|YbS&fcmJqD&9s&D$sPy2u9K*}?fylbKomE}2K^^Dc0!Ieq z=poG2@rNC1`@9}_{LYv=T4I%C6xetCexa4&#VwW|BQEp6C(C2d7s);GIl{|WE!^9~ zwK_=hc4VB63abuK_?JIy9Wm-c<}`%ha9ZZ3^!?rebK#*!Z9vz$E&WC;)!+ zoSZUW%&;D)p-;wvsyARvh?o73cIMTl{fxuxLO`y%njsBxt$!(#(iKthox632f)%y+ z!%iTkquAH(v;H3xQMyy!sdd7i%^v@iXDKpBh9Uh z!kNttuO!qiiQ4Rh=X2UvmRD0RT3AY%BcldYyitsN1wLWLhtHw5;v+O?Phl9aOy6mo z+J4B+6Z|OF9AcRDG80s7ond0?m+gMwFdz9IxeD?bG3Jo%gfw?{elil|XQc5$>Vx2s z6xrJH7g_rEsd)|6pF>D!O*lJzc89g^bJR+C+37#RZI5+(ntiD@#I(cXi2jgdzIoMc zwv<7=FN%#;^A3%O>b#vd_Mil}K@nk3Mk8DnMFKTx5}|z8wc00Sw=hm0+s$6LlgW&E zSD;8Ns^*!K%;;E83IS)IUaA(9?TI)4XN9V8#m&1#!Nu921!>4#9tgG8A2v%JC=Ti= zeD&n^oW*@BeH{+xF@pOS|AB$%-Zb^(h$SB~Njk{j97h4C1nPGQYH3s-jn7fODq5ez z=rguG8?fr)`G%!~p8GY{P&=>(IwH2!+zt$W7Pf9|cl#A&2R(m1#0(6{ezo6~hy=V_Y)8<990uUz?`z@rY_N6t$8R3< zK8jL4pLvpPd*gNU;eZMtuCjCEcF49xqw7QD@+l?euLnLAP?lhj!iFzzEll-qofXjk zl@Bh{8Bd}FXG}NBjY`RYqv6$7l+q|{Bioy?x8wMWw_Er3K>E*aZ}f;<7TEfNGU8ux z=Rh|Y1I04%Ru$yiKJ?7vGbwmUa138F6XAc{V5zHZpGB{_Mu{*@FflL<1_@w>NC9O7 z1OfpC00ban{TgksqKjUPRK{9vLeCE})>LG9$#j?QHib@`5=~No&kXkyWJ0zA4mjT6TS7E!v;r8p4j^@fzsr**{A$Ojif56sqaH3N0no|0w z!#~J|Z(b$$+txZ{vsBh1y47Ak4j&bvGX0*S#{HivBBr3TbO}~-8>Qf5m-w`a^qs-Q z0}S0=9OrIIm`xJ{ZE?;W$w3JER!&eSn$2aMt?!#WCy(OlsuAT6(}$YSAGv2J;lNstt<8VF5&l*t67+sE$3!g|?X z7tOR0>bU1?j}abYRj&X)8QC zBQhk7{;ZZpqvB_-Li44DlJIGP{^<9IQ|(TkJGP0wdFCN83x0N;X=MT1;i6$Xf4zSs z73vK`f2u03?o>di$=z)K(bFH$J?}DO&W3Z?m?9`!5g{pIudcsdKOupUfa#)w%el(2~h8z;5oSZAD)#ToAB5pOx+l}7<%qpP5v;e=OqgG>CiNvoCl z%XhA(CtklKRL2rX%ZtRR8V3pSN}!U+xhh<-VOyd=hEFP0M(cib%omMj3s4_(Wav z0$=jkF$>MB_?Qq!5y_x(oq`wWJ>>lvxwnTp!OeV^pHJT{zIs*w5?_``=B$bQ6)yB%By?=cLWTusHBPKro6WHGnv2nU`NT|x^+-{7 zqH0s1zDTG(B|y`wv!ZNaDK5~=UgPcO)Vkec+g*^-2TRkR33;z9XQ3H*i<{+y*K%@F z@uf_W~yodSd1-+

BL0TrSL|-lV6sCBvP1lekIZ+K)gZ*pLoUWW_G(KjM?BAApzyP=OF`JbY~Ho)O63H4cwsAZovi3W9H+>HPPb(!AYu#6kV*I5t9S z@tCn27ivp2!fn5Xp50#+3v=qYgt5(4H|nV}2?q206RnB;WDpdhzlwD#XMydPX9S=A z;u{|V8o!b)E-9fhIbKbNAV(UJi$}7*8|pj~DqT?92^Wi7T`SI&PA3OlPJH-TwYn;* zi8M-*Nfzu$LqB+*+8uUCC=Pfr3RKYRUyk{=|LnC(R2ES%>T+&3tYMcsX&Q)M$eho( zddEHyX)5hLn3c8vt2RacPIQa0=ikwtC{|urSb2i1^dB(n>60WyiDxy3Ws(89Vcii#RYsO`RTJ#LTtBR*(nTF6hG5xT6iJYeMj&5W)v#$ zblYaEfj`sRk}%*X1QPcZ%)*n-lldcirB}f_yDHAwJD=NX!A&fZBav!jo6JUKn)D#I zliP!JuEeY?SFB!T)!LF7F{IdJ9)xN)l6LvS)rel?sE~|$)6n{eTkYt#@~ScY1%8#EUpP zQTQS~C10r$G}u8|g(0YI(@Qt0{z3kz)|JOt`^q|LQ~UOLJ<1NX)5yxg z@Py98-umJ)r9Nb6V-2)^U|yopCmObeim>eqMaAKQJ9Q59&iSoXt9i(|`A)&tE=D9cg{7wY{EbrS3hn2b^Q--La8}gG{lC9qoydj0s3V0hpYKw54SX9|L@n$j z+x7Mmo&JI(y%anSD-OSHYL8dSTe$H_GofVp?$Z2=;R*G&C{x=Z;Oo@v-t6TAf`~tN zsRjEv8ubG$Utj2tk|0xIlY=mH{^Fj`!CiWvKRIQQ6NVgi#EAr?J8q27xfhrOAk3XL zLS<{+HIC|%0bU1yKfof67Ve&jkSl$3*2b(@!eSZ;aX&c0A|RJ7VO6c12Ulsb^$$fO zM~u%CHC@e%bBv(Vq6Zl~92m8rIlFO#Yous^C(Jj;~J7=9r*eLx)nSG0ag%kIY7G5~#)h4hBY0PNOGO+$IXt0jhO{TTOTA%}t}u$3 zJA&Fb9LPOGizlN&z3gDuFF76~?=$J7R6JN^dDK)qO7z|$_Uz_KdPaPX_SV20bq+xZ zQH|>oYDGiPVcoA~j(rFZYIV1a{SULeZsOW2>RJ*VhZ6HYhoE8F&a0wgWm}vep3=v| za+WXvaofo3K6qpAfhsTf5Ve*ZLGMMk3$SPxRvdNqxV8iZ9#`rz*K<61jo>Io2uaaY zUtDd7bqfGX#BBI;B+IskUtVS zfIc@#{H^$;LLC}q0)d?@98aJ?!t%NkHhYXGKF?-~`6lq9;ES4ZotQ@FFj3|;Y3MxUdg#I!Ty zxNG9X%@ffy-QRP)z)z45JMi0Jb?)%;;_=doE6&`P>ZyI^d2|PHBxt17wHLpmDZLvd zsUzRp1OI&#I{5~z%#dhxUD=!p^4IQB!MLP_D!nw%T?GFzM}dn)qsZu-1^AUq%7;l=i(?Co3?6G zme<{j6>K|%f8wIFmn!$oTHDjKfgW}i4EG(1ow#G8c9&~1RuCe^g&TwKNo0~`#^)1_ zzIbe|%gNJ9j{InA2mUQG{>&6W_=9oM$Rg^n?j9(gy@tA()4^hFT%7e zm{ilH3cIPDrYJZ1R#!KS@cZ0oK=zd)t}*3;0?yC;A?SK517_TnxMJo&^q$?h%OMR0 zAf&QE0*h8?D}SeF<&njTE-gjVnabH_o=rNYR-9p-ERGTG3j=(f_dWH+KE$Srt$C{y zu*7bl`KIPhGmmFL4f!lO(>GQkc{+2&tsjB|T)4{ugNy%p5BSIynAkLYEB)!w`aSg3 zIKjb``0uR#OB%Ow!oxuyC)`oKp~2bVSC)QS((N4Zl?1hf=tCCl6c_x2Ci8x^clI0U z^J*KT63Xto9XSM!5eY)SBX7y89acBfURnLcr`2~mMNUo}K^l}iveg;09wB)%EOQlw z6qilWP+{>jC-odeEHy63*OxlHA>uj+^VNz{eturP79AUC+V(<%j6w)wBPu{jLkQ9m zcV@Wu9x`_$BFb`*d({$}!1YDm8+Od<#>m8~WW14L1W7}Dxgv_BR$D9=+AAeu6I$Zg z<^@vAAzpRA@%;*X6v|4r=5W@@$+pX-@zm-!Wv389Lq~l*+_}y4PgtIr{5+@TG18bp zD*;L^7Y)}{ge+7RP-x_eBrrRzXX3RSiu!Fby2!;H=;ZX-q#!bIr}w>lZji*`-M@PE zG|BlNe4T!sZNat?zBe)UmBs8gL#^b5Z5$8&E|kmtnGa{w^HYWDH(dyMuJecX^Ll~v zcS(789bky;1h{ss>|sT*H#`6+sf~ zO0)oCyu|pby(+7N3c}b6M|GPYNbu zuz)B>#B%rxlDQ!uG7ewRavUVpMKE)v85~Vt#`gmDla9BB-33T4S zl%|y|T7ew*_#2CLP_=3obv)8j%NhkW5^#R%m%-=E3%4ni?5Gr?dgM!-4UMG?<8d^1 z^(OC1piq60G15-b_Xv&G%khOPQVX8q2KIo95yjsoZE4p+hsj#s>oGyaB!=tc{55>E z=B7=0lADH9r*$hHP-ClZ2GB6qh0SIMj_3U;X0pIUpX!r5KzJm?BBt9egfr$#IPmpK zaNCZeh_$S5v8dI+jj-mqnf6_g&PEX{?d2dwR4`2PCOTG9E%nKA#QLYlg1fVtlh`9| z8N2-I+cvcl+Jq%0on71~WnpRee&(tlCbM~=KE9VQ$C4q>+Is&&A>q^Uw$10=p zCI!U-(f!dE!VyQ7J@dDgQSB{q0BCkZcj4EoA_I}gY z@dmV{@6jF+RI00aqc8BCJY;2N$eS}A&WX@apaKRbO8Bm9XqmTmC8)j9S+(#mY1OsJ zk}H+cBi3cyyTZh--?{s$2{2Z1Djn4s-=2+p^djZy7R=W7D32vLZSe7})Mt(PsGxnV zpCgW)iR~{FG-0C#>rq?|C0%wuq*Xp@GVU~^snRw4YVMQCE|iP{hC8)vm0^*AntrM@ zqz&CABMH+IHHd9D{lOkm<<~6QN)}OuxmnWPJa- zZ2sT}OM^jRTr40L5e7Of5P(S`$B?q&s#wa{Ty_q$UW%y}ebez0lj66Kt#J_*bnh1M T_1?K_(cIPCs~a5yExO=eG*hui diff --git a/src/test/resources/test_truststore.jks b/src/test/resources/test_truststore.jks deleted file mode 100644 index faacea8da4d9ab604a12d1ee630b3dce3bf778a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1330 zcmV-21}f(0%D0Ru3C1m^|`Duzgg_YDCD0ic2e*aU(E)G&es&@h4o%mxW6hDe6@ z4FLxRpn?RwFoFcR0s#Opf&{V#2`Yw2hW8Bt2LUiC1_~;MNQU^InZ-Rr_fRRr zdjQbE)9Mo3D^tcO{^(-Onp5GVdzTi4!3rz6J)NiI29R-TSm%JEf8gZ8Ry@b?WXw5g zH|_#h7uOFMY@8@(q)>ST0UvtTYa}KiLH#Jz#fZThqshE>QAMy(9fe$^?O1;+-S7qo zqSi)hrLVO({N(?SJeX-up}E`jCt6|AJrT9^kfb7C{~bTG9YZc}WO-0|3s^R>F78Hq z?*s!q==wX-@HCYhji9XJ5TS*e7ym^1)jN!mJ~?_%Z`Y1(2lHDu@f3;Z(i@_H>qW zVw~Q_;djdk zv5Xu%V&~$9cSKJpLcu*OruTc01@Z#-QaA2PiB8?ODNM|VkTroL%nh3rDV{1g}NHKfv>LTi!YsKtz z2p&vpJXgJJa8^Z)m*9VP6>y?*;^P;o6q3Y7Nv$7YYBJL3>@9?u=%_?PDw0a_VL;6H zl`HNktoRV+MAYWk{}ZjYR>yPFC-P;h^y5Vnku#3h#eY9rQr^eB}EF`g3KjX|{GOtvu{T=fEi z`}S7|ODqBri26@yDJUY?m1osY^^JwE#T0pLwd|%t6$O~Z!`~at0tnVS$}9i@8i`pr zza8@si>SzA*uhCB0B9bb)jubXw6ojNTATZLD-tAgW|2fWiW(xAeA5BDJIZDsi@tfD z8C-{3+N$J!omQqX$$js;x%jxn1S_N@128@?AutIB1uG5%0vZJX1QZcyCXeRXcVfVu oEP Date: Mon, 18 Sep 2023 15:09:39 +0200 Subject: [PATCH 17/21] Remove org.apache.cassandra2.cql.jdbc package --- CHANGELOG.md | 3 +++ .../ing/data/cassandra/jdbc/utils/package-info.java} | 12 ++---------- 2 files changed, 5 insertions(+), 10 deletions(-) rename src/main/java/{org/apache/cassandra2/cql/jdbc/CassandraDriver.java => com/ing/data/cassandra/jdbc/utils/package-info.java} (65%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ca8f0..d986a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Update Apache Commons Lang to version 3.13.0. - Update Jackson dependencies to version 2.15.2. - Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages. +### Removed +- Remove the legacy package `org.apache.cassandra2.cql.jdbc`: only `com.ing.data.cassandra.jdbc.CassandraDriver` should + be used now as `java.sql.Driver` implementation. ## [4.9.1] - 2023-09-03 ### Fixed diff --git a/src/main/java/org/apache/cassandra2/cql/jdbc/CassandraDriver.java b/src/main/java/com/ing/data/cassandra/jdbc/utils/package-info.java similarity index 65% rename from src/main/java/org/apache/cassandra2/cql/jdbc/CassandraDriver.java rename to src/main/java/com/ing/data/cassandra/jdbc/utils/package-info.java index c7eaee0..0f858b2 100644 --- a/src/main/java/org/apache/cassandra2/cql/jdbc/CassandraDriver.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/utils/package-info.java @@ -1,5 +1,4 @@ /* - * Copyright (C) 2012-2015 DataStax Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +13,7 @@ * limitations under the License. */ -package org.apache.cassandra2.cql.jdbc; - -import java.sql.Driver; - /** - * The {@code CassandraDriver} class. - * - * @see com.ing.data.cassandra.jdbc.CassandraDriver + * This package contains utility classes used by the JDBC wrapper. */ -public class CassandraDriver extends com.ing.data.cassandra.jdbc.CassandraDriver implements Driver { -} +package com.ing.data.cassandra.jdbc.utils; From 236928ecb478e228ee65804c3a1587a71be7a7d4 Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 23 Sep 2023 15:08:31 +0200 Subject: [PATCH 18/21] Implement CassandraDatabaseMetaData.getBestRowIdentifier() --- .github/workflows/ci-workflow.yml | 2 +- .github/workflows/release-workflow.yml | 4 +- CHANGELOG.md | 2 + .../jdbc/CassandraDatabaseMetaData.java | 5 +- .../AbstractMetadataResultSetBuilder.java | 2 + .../TableMetadataResultSetBuilder.java | 105 ++++++++++++++++++ .../jdbc/MetadataResultSetsUnitTest.java | 51 +++++++-- .../utils/AnotherFakeLoadBalancingPolicy.java | 6 +- .../jdbc/utils/FakeLoadBalancingPolicy.java | 4 +- src/test/resources/initEmbeddedCassandra.cql | 7 ++ 10 files changed, 168 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 61ab7c1..83b53ee 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -13,7 +13,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 8 uses: actions/setup-java@v3 diff --git a/.github/workflows/release-workflow.yml b/.github/workflows/release-workflow.yml index 18d9f11..a796de6 100644 --- a/.github/workflows/release-workflow.yml +++ b/.github/workflows/release-workflow.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 8 uses: actions/setup-java@v3 @@ -39,7 +39,7 @@ jobs: run: echo "RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV - name: Create GitHub release - uses: ncipollo/release-action@v1.12.0 + uses: ncipollo/release-action@v1 with: artifacts: "target/cassandra-jdbc-wrapper-${{ env.RELEASE_VERSION }}-bundle.jar" name: "${{ env.RELEASE_VERSION }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index d986a50..fadcdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Add support for new [`vector` CQL type](https://datastax-oss.atlassian.net/browse/JAVA-3060) defined in [CEP-30](https://cwiki.apache.org/confluence/x/OQ40Dw). - Implement the method `getWarnings()` in `CassandraResultSet`. +- Implement the following methods of `CassandraDatabaseMetaData`: + `getBestRowIdentifier(String, String, String, int, boolean)` and `getAttributes(String, String, String, String)`. ### Changed - Update DataStax Java Driver for Apache Cassandra(R) to version 4.17.0. - Update Apache Commons IO to version 2.13.0. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index ca33de8..f31acd0 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -131,8 +131,11 @@ public ResultSet getAttributes(final String catalog, final String schemaPattern, @Override public ResultSet getBestRowIdentifier(final String catalog, final String schema, final String table, final int scope, final boolean nullable) throws SQLException { - // TODO: method to implement into TableMetadataResultSetBuilder checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. + if (catalog == null || catalog.equals(this.connection.getCatalog())) { + return new TableMetadataResultSetBuilder(this.statement).buildBestRowIdentifier(schema, table, scope); + } return CassandraResultSet.EMPTY_RESULT_SET; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java index 6b9ab5f..bfb0c12 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java @@ -82,10 +82,12 @@ public abstract class AbstractMetadataResultSetBuilder { static final String PAGES = "PAGES"; static final String PRECISION = "PRECISION"; static final String PRIMARY_KEY_NAME = "PK_NAME"; + static final String PSEUDO_COLUMN = "PSEUDO_COLUMN"; static final String RADIX = "RADIX"; static final String REF_GENERATION = "REF_GENERATION"; static final String REMARKS = "REMARKS"; static final String SCALE = "SCALE"; + static final String SCOPE = "SCOPE"; static final String SCOPE_CATALOG = "SCOPE_CATALOG"; static final String SCOPE_SCHEMA = "SCOPE_SCHEMA"; static final String SCOPE_TABLE = "SCOPE_TABLE"; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java index 6a78693..b7346f4 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java @@ -20,18 +20,28 @@ import com.datastax.oss.driver.api.core.metadata.schema.IndexMetadata; import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; import com.ing.data.cassandra.jdbc.CassandraStatement; +import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.sql.DatabaseMetaData; import java.sql.SQLException; +import java.sql.Types; import java.util.ArrayList; import java.util.Comparator; import java.util.Map; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; +import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; +import static java.sql.DatabaseMetaData.bestRowNotPseudo; + /** * Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to tables. */ public class TableMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + private static final Logger LOG = LoggerFactory.getLogger(TableMetadataResultSetBuilder.class); + /** * Constructor. * @@ -276,4 +286,99 @@ public CassandraMetadataResultSet buildPrimaryKeys(final String schema, final St return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(primaryKeys)); } + /** + * Builds a valid result set of the description of a table's optimal set of columns that uniquely identifies a row. + * This method is used to implement the method + * {@link DatabaseMetaData#getBestRowIdentifier(String, String, String, int, boolean)}. + *

+ * In Cassandra, all the tables must define a single primary key and the columns defining this primary key + * ensure the uniqueness of each row. So, we consider in this implementation that the best row identifier for + * a table is always its primary key regardless of the specified scope. Also, the parameter {@code nullable} has + * no effect here since Cassandra does not allow null values in primary keys. + *

+ *

+ * Only identifiers for tables matching the catalog, schema and table name criteria are returned. They are + * ordered by {@code SCOPE}. + *

+ *

+ * The columns of this result set are: + *

    + *
  1. SCOPE short => actual scope of result: + *
      + *
    • {@link DatabaseMetaData#bestRowTemporary} - very temporary, while using row
    • + *
    • {@link DatabaseMetaData#bestRowTransaction} - valid for remainder of current transaction
    • + *
    • {@link DatabaseMetaData#bestRowSession} - valid for remainder of current session
    • + *
    Always the input scope value. + *
  2. + *
  3. COLUMN_NAME String => column name.
  4. + *
  5. DATA_TYPE int => SQL type from {@link Types}.
  6. + *
  7. TYPE_NAME String => Data source dependent type name, for a UDT the type name is fully + * qualified.
  8. + *
  9. COLUMN_SIZE int => column size.
  10. + *
  11. BUFFER_LENGTH int => not used: always 0 here.
  12. + *
  13. DECIMAL_DIGITS int => the number of fractional digits, {@code null} is returned for data + * types where it is not applicable. Always {@code null} here.
  14. + *
  15. PSEUDO_COLUMN short => is this a pseudo column like an Oracle ROWID: + *
      + *
    • {@link DatabaseMetaData#bestRowUnknown} - may or may not be pseudo column
    • + *
    • {@link DatabaseMetaData#bestRowNotPseudo} - is not a pseudo column
    • + *
    • {@link DatabaseMetaData#bestRowPseudo} - is a pseudo column
    • + *
    Always {@link DatabaseMetaData#bestRowNotPseudo} here since there is no concept of pseudo + * column in Cassandra. + *
  16. + *
+ *

+ * + * @param schema A schema name pattern. It must match the schema name as it is stored in the database; {@code ""} + * retrieves those without a schema and {@code null} means that the schema name should not be used to + * narrow the search. Using {@code ""} as the same effect as {@code null} because here the schema + * corresponds to the keyspace and Cassandra tables cannot be defined outside a keyspace. + * @param table A table name. It must match the table name as it is stored in the database. + * @param scope The scope of interest, using the same values as {@code SCOPE} in the result set. + * @return A valid result set for implementation of + * {@link DatabaseMetaData#getBestRowIdentifier(String, String, String, int, boolean)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildBestRowIdentifier(final String schema, final String table, final int scope) + throws SQLException { + final ArrayList bestRowIdentifiers = new ArrayList<>(); + + filterBySchemaNamePattern(schema, keyspaceMetadata -> + filterByTableNamePattern(table, keyspaceMetadata, tableMetadata -> { + for (final ColumnMetadata columnMetadata : tableMetadata.getPrimaryKey()) { + + final AbstractJdbcType jdbcEquivalentType = + getTypeForComparator(columnMetadata.getType().toString()); + + // Define value of COLUMN_SIZE. + int columnSize = DEFAULT_PRECISION; + if (jdbcEquivalentType != null) { + columnSize = jdbcEquivalentType.getPrecision(null); + } + + // Define value of DATA_TYPE. + int jdbcType = Types.OTHER; + try { + jdbcType = getTypeForComparator(columnMetadata.getType().toString()).getJdbcType(); + } catch (final Exception e) { + LOG.warn("Unable to get JDBC type for comparator [{}]: {}", + columnMetadata.getType(), e.getMessage()); + } + + final MetadataRow row = new MetadataRow() + .addEntry(SCOPE, String.valueOf(scope)) + .addEntry(COLUMN_NAME, columnMetadata.getName().asInternal()) + .addEntry(DATA_TYPE, String.valueOf(jdbcType)) + .addEntry(TYPE_NAME, columnMetadata.getType().toString()) + .addEntry(COLUMN_SIZE, String.valueOf(columnSize)) + .addEntry(BUFFER_LENGTH, String.valueOf(0)) + .addEntry(DECIMAL_DIGITS, null) + .addEntry(PSEUDO_COLUMN, String.valueOf(bestRowNotPseudo)); + bestRowIdentifiers.add(row); + } + }, null), null); + + // All the rows of the result set have the same scope, so there is no need to perform an additional sort. + return CassandraMetadataResultSet.buildFrom(statement, new MetadataResultSet().setRows(bestRowIdentifiers)); + } } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index 188353d..22ca4cd 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -60,7 +61,7 @@ static void finalizeSetUpTests() throws Exception { */ @Test - void givenStatement_whenMakeTableTypes_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildTableTypes_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new TableMetadataResultSetBuilder(statement).buildTableTypes(); assertNotNull(result); @@ -70,7 +71,7 @@ void givenStatement_whenMakeTableTypes_returnExpectedResultSet() throws SQLExcep } @Test - void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildTables_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new TableMetadataResultSetBuilder(statement).buildTables(null, null); assertNotNull(result); @@ -135,12 +136,40 @@ void givenStatement_whenMakeTables_returnExpectedResultSet() throws SQLException assertThat(foundTables, hasItem(is(ANOTHER_KEYSPACE.concat(";cf_test1;TABLE;First table in the keyspace")))); } + @Test + void givenStatement_whenBuildBestRowIdentifier_returnExpectedResultSet() throws SQLException { + final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); + final ResultSet result = new TableMetadataResultSetBuilder(statement) + .buildBestRowIdentifier(KEYSPACE, "cf_test3", DatabaseMetaData.bestRowTemporary); + assertNotNull(result); + assertEquals(8, result.getMetaData().getColumnCount()); + assertEquals("SCOPE", result.getMetaData().getColumnName(1)); + assertEquals("COLUMN_NAME", result.getMetaData().getColumnName(2)); + assertEquals("DATA_TYPE", result.getMetaData().getColumnName(3)); + assertEquals("TYPE_NAME", result.getMetaData().getColumnName(4)); + assertEquals("COLUMN_SIZE", result.getMetaData().getColumnName(5)); + assertEquals("BUFFER_LENGTH", result.getMetaData().getColumnName(6)); + assertEquals("DECIMAL_DIGITS", result.getMetaData().getColumnName(7)); + assertEquals("PSEUDO_COLUMN", result.getMetaData().getColumnName(8)); + final List foundColumns = new ArrayList<>(); + int resultSize = 0; + while (result.next()) { + ++resultSize; + foundColumns.add(String.join(";", result.getString(1), result.getString(2), result.getString(3), + result.getString(4), result.getString(5), result.getString(6), result.getString(7), + result.getString(8))); + } + assertEquals(2, resultSize); + assertThat(foundColumns, hasItem(is("0;keyname;12;TEXT;2147483647;0;null;1"))); + assertThat(foundColumns, hasItem(is("0;t3ivalue;4;INT;11;0;null;1"))); + } + /* * Catalogs metadata */ @Test - void givenStatement_whenMakeCatalogs_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildCatalogs_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new CatalogMetadataResultSetBuilder(statement).buildCatalogs(); assertNotNull(result); @@ -154,7 +183,7 @@ void givenStatement_whenMakeCatalogs_returnExpectedResultSet() throws SQLExcepti */ @Test - void givenStatement_whenMakeSchemas_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildSchemas_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new SchemaMetadataResultSetBuilder(statement).buildSchemas(null); assertNotNull(result); @@ -183,7 +212,7 @@ void givenStatement_whenMakeSchemas_returnExpectedResultSet() throws SQLExceptio */ @Test - void givenStatement_whenMakeColumns_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildColumns_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new ColumnMetadataResultSetBuilder(statement).buildColumns(KEYSPACE, "cf_test1", null); assertNotNull(result); @@ -372,7 +401,7 @@ void givenStatement_whenGetMetadataIsSearchable_returnExpectedValues() throws Ex */ @Test - void givenStatement_whenMakeUDTs_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildUDTs_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildUDTs(KEYSPACE, "CustomType1", new int[]{Types.JAVA_OBJECT}); @@ -405,7 +434,7 @@ void givenStatement_whenMakeUDTs_returnExpectedResultSet() throws SQLException { } @Test - void givenStatement_whenMakeUDTsWithNonJavaObjectTypes_returnEmptyResultSet() throws SQLException { + void givenStatement_whenBuildUDTsWithNonJavaObjectTypes_returnEmptyResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildUDTs(KEYSPACE, "CustomType1", new int[]{Types.STRUCT, Types.DISTINCT}); @@ -414,7 +443,7 @@ void givenStatement_whenMakeUDTsWithNonJavaObjectTypes_returnEmptyResultSet() th } @Test - void givenStatement_whenMakeUDTsNotSpecifyingSchemaPattern_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildUDTsNotSpecifyingSchemaPattern_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildUDTs(null, "type_in_different_ks", new int[]{Types.JAVA_OBJECT}); @@ -432,7 +461,7 @@ void givenStatement_whenMakeUDTsNotSpecifyingSchemaPattern_returnExpectedResultS } @Test - void givenStatement_whenMakeTypes_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildTypes_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new TypeMetadataResultSetBuilder(statement).buildTypes(); assertNotNull(result); @@ -527,7 +556,7 @@ void givenStatement_whenMakeTypes_returnExpectedResultSet() throws SQLException */ @Test - void givenStatement_whenMakeFunctions_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildFunctions_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new FunctionMetadataResultSetBuilder(statement) .buildFunctions(KEYSPACE, "function_test1"); @@ -551,7 +580,7 @@ void givenStatement_whenMakeFunctions_returnExpectedResultSet() throws SQLExcept } @Test - void givenStatement_whenMakeFunctionColumns_returnExpectedResultSet() throws SQLException { + void givenStatement_whenBuildFunctionColumns_returnExpectedResultSet() throws SQLException { final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); final ResultSet result = new FunctionMetadataResultSetBuilder(statement) .buildFunctionColumns(KEYSPACE, "function_test1", "%"); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java index 08735b6..dc5e514 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java @@ -19,9 +19,9 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.NonNull; +import javax.annotation.Nullable; import java.util.Map; import java.util.Queue; import java.util.UUID; @@ -30,7 +30,7 @@ public class AnotherFakeLoadBalancingPolicy implements LoadBalancingPolicy { - public AnotherFakeLoadBalancingPolicy(@NonNull final DriverContext context, @NonNull final String profileName) { + public AnotherFakeLoadBalancingPolicy(final DriverContext context, final String profileName) { // Do nothing. For testing purpose only. } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java index 8e61a3a..f23b137 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java @@ -19,9 +19,9 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.NonNull; +import javax.annotation.Nullable; import java.util.Map; import java.util.Queue; import java.util.UUID; diff --git a/src/test/resources/initEmbeddedCassandra.cql b/src/test/resources/initEmbeddedCassandra.cql index d95c9ab..044bc24 100644 --- a/src/test/resources/initEmbeddedCassandra.cql +++ b/src/test/resources/initEmbeddedCassandra.cql @@ -18,6 +18,13 @@ t2bValue boolean, t2iValue int) WITH comment = 'Second table in the keyspace'; +CREATE COLUMNFAMILY cf_test3 ( +keyname text, +t3bValue boolean, +t3iValue int, +PRIMARY KEY(keyname, t3iValue)) +WITH comment = 'Third table in the keyspace'; + CREATE TYPE CustomType1 ( key1 int, value1 text, From 99ade34fb98fb139089277cf774f47309f4029bc Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 23 Sep 2023 17:04:35 +0200 Subject: [PATCH 19/21] Implement CassandraDatabaseMetaData.getAttributes() Also replace all Findbugs NonNull/Nullable annotations with standard Java equivalent, and preform some code cleanup. --- .../cassandra/jdbc/AbstractResultSet.java | 6 +- .../cassandra/jdbc/CassandraConnection.java | 8 +- .../jdbc/CassandraDatabaseMetaData.java | 6 +- .../jdbc/CassandraResultSetJsonSupport.java | 1 + .../cassandra/jdbc/CassandraStatement.java | 4 +- .../cassandra/jdbc/codec/AbstractCodec.java | 9 +- .../jdbc/codec/BigintToBigDecimalCodec.java | 14 +- .../jdbc/codec/DecimalToDoubleCodec.java | 14 +- .../jdbc/codec/FloatToDoubleCodec.java | 14 +- .../cassandra/jdbc/codec/IntToLongCodec.java | 14 +- .../cassandra/jdbc/codec/LongToIntCodec.java | 14 +- .../jdbc/codec/SmallintToIntCodec.java | 14 +- .../jdbc/codec/TimestampToLongCodec.java | 14 +- .../jdbc/codec/TinyintToIntCodec.java | 14 +- .../jdbc/codec/VarintToIntCodec.java | 14 +- .../AbstractMetadataResultSetBuilder.java | 4 + .../ColumnMetadataResultSetBuilder.java | 8 +- .../TypeMetadataResultSetBuilder.java | 171 ++++++++++++++++++ .../jdbc/types/AbstractJdbcUUID.java | 5 +- .../cassandra/jdbc/types/DataTypeEnum.java | 4 +- .../cassandra/jdbc/types/JdbcLexicalUUID.java | 7 +- .../cassandra/jdbc/types/JdbcTimeUUID.java | 6 +- .../data/cassandra/jdbc/types/JdbcTuple.java | 4 +- .../data/cassandra/jdbc/types/JdbcUUID.java | 5 +- .../data/cassandra/jdbc/types/JdbcUdt.java | 4 +- .../jdbc/MetadataResultSetsUnitTest.java | 43 +++++ .../utils/AnotherFakeLoadBalancingPolicy.java | 14 +- .../jdbc/utils/AnotherFakeRetryPolicy.java | 17 +- .../jdbc/utils/FakeLoadBalancingPolicy.java | 16 +- .../jdbc/utils/FakeReconnectionPolicy.java | 9 +- .../cassandra/jdbc/utils/FakeRetryPolicy.java | 17 +- .../jdbc/utils/FakeSslEngineFactory.java | 6 +- 32 files changed, 360 insertions(+), 140 deletions(-) diff --git a/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java index 6ec1be9..13f81ab 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/AbstractResultSet.java @@ -17,9 +17,9 @@ import com.datastax.oss.driver.api.core.type.DataType; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; -import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; +import javax.annotation.Nonnull; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; @@ -56,7 +56,7 @@ abstract class AbstractResultSet implements Wrapper { * @param type The data type to check. * @return {@code true} if the column CQL data type is the given one, {@code false} otherwise. */ - boolean isCqlType(final int columnIndex, @NonNull final DataTypeEnum type) { + boolean isCqlType(final int columnIndex, @Nonnull final DataTypeEnum type) { final String columnType = StringUtils.substringBefore(DataTypeEnum.cqlName(getCqlDataType(columnIndex)), "<"); return type.cqlType.equalsIgnoreCase(columnType); } @@ -68,7 +68,7 @@ boolean isCqlType(final int columnIndex, @NonNull final DataTypeEnum type) { * @param type The data type to check. * @return {@code true} if the column CQL data type is the given one, {@code false} otherwise. */ - boolean isCqlType(final String columnLabel, @NonNull final DataTypeEnum type) { + boolean isCqlType(final String columnLabel, @Nonnull final DataTypeEnum type) { final String columnType = StringUtils.substringBefore(DataTypeEnum.cqlName(getCqlDataType(columnLabel)), "<"); return type.cqlType.equalsIgnoreCase(columnType); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java index de077a5..49cc2bf 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; @@ -160,18 +161,13 @@ public CassandraConnection(final SessionHolder sessionHolder) throws SQLExceptio this.cSession = sessionHolder.session; this.metadata = this.cSession.getMetadata(); - // TODO check if this code should be definitely removed. - // final List l = new ArrayList<>(); - // l.stream().map(s -> s.session).collect(Collectors.toList()); - LOG.info("Connected to cluster: {}, with session: {}", - StringUtils.defaultString(getCatalog(), ""), this.cSession.getName()); + Objects.toString(getCatalog(), ""), this.cSession.getName()); this.metadata.getNodes().forEach( (uuid, node) -> LOG.info("Datacenter: {}; Host: {}; Rack: {}", node.getDatacenter(), node.getEndPoint().resolve(), node.getRack()) ); - // TODO this is shared among all Connections, what if they belong to different clusters? this.metadata.getNodes().entrySet().stream().findFirst().ifPresent(entry -> { final Version cassandraVersion = entry.getValue().getCassandraVersion(); if (cassandraVersion != null) { diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index f31acd0..3ca43ca 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -123,8 +123,12 @@ public boolean generatedKeyAlwaysReturned() { @Override public ResultSet getAttributes(final String catalog, final String schemaPattern, final String typeNamePattern, final String attributeNamePattern) throws SQLException { - // TODO: method to implement into TypeMetadataResultSetBuilder checkStatementClosed(); + // Only null or the current catalog (i.e. cluster) name are supported. + if (catalog == null || catalog.equals(this.connection.getCatalog())) { + return new TypeMetadataResultSetBuilder(this.statement).buildAttributes(schemaPattern, typeNamePattern, + attributeNamePattern); + } return CassandraResultSet.EMPTY_RESULT_SET; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetJsonSupport.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetJsonSupport.java index da5d193..1046353 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetJsonSupport.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSetJsonSupport.java @@ -69,6 +69,7 @@ * uuid string {@link UUID} * varchar string {@link String} * varint integer {@link Number} + * vector list {@link List} * * See:
* CQL reference for JSON support. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java index 3c8276a..1bae7d1 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java @@ -24,11 +24,11 @@ import com.datastax.oss.driver.internal.core.cql.MultiPageResultSet; import com.datastax.oss.driver.internal.core.cql.SinglePageResultSet; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; -import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; @@ -282,7 +282,7 @@ public void close() { } @Override - public int compareTo(@NonNull final Object target) { + public int compareTo(@Nonnull final Object target) { if (this.equals(target)) { return 0; } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java index dbab784..89a492e 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/AbstractCodec.java @@ -16,9 +16,10 @@ package com.ing.data.cassandra.jdbc.codec; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; -import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang3.StringUtils; +import javax.annotation.Nonnull; + import static com.ing.data.cassandra.jdbc.utils.DriverUtil.NULL_KEYWORD; /** @@ -51,7 +52,7 @@ public JavaTypeT parse(final String value) { * @param value The value to parse. * @return The parsed value. */ - abstract JavaTypeT parseNonNull(@NonNull String value); + abstract JavaTypeT parseNonNull(@Nonnull String value); /** * Formats the given value as a valid CQL literal according to the CQL type handled by this codec. @@ -59,7 +60,7 @@ public JavaTypeT parse(final String value) { * @param value The value to format. * @return The formatted value or {@code NULL} CQL keyword if the value to format is {@code null}. */ - @NonNull + @Nonnull public String format(final JavaTypeT value) { if (value == null) { return NULL_KEYWORD; @@ -73,5 +74,5 @@ public String format(final JavaTypeT value) { * @param value The value to format. * @return The formatted value. */ - abstract String formatNonNull(@NonNull JavaTypeT value); + abstract String formatNonNull(@Nonnull JavaTypeT value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java index 423ca93..93b607f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/BigintToBigDecimalCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.math.BigDecimal; import java.nio.ByteBuffer; @@ -37,20 +37,20 @@ public class BigintToBigDecimalCodec extends AbstractCodec implement public BigintToBigDecimalCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.BIG_DECIMAL; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.BIGINT; } @Override - public ByteBuffer encode(final BigDecimal value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final BigDecimal value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -58,7 +58,7 @@ public ByteBuffer encode(final BigDecimal value, @NonNull final ProtocolVersion } @Override - public BigDecimal decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public BigDecimal decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -68,12 +68,12 @@ public BigDecimal decode(final ByteBuffer bytes, @NonNull final ProtocolVersion } @Override - BigDecimal parseNonNull(@NonNull final String value) { + BigDecimal parseNonNull(@Nonnull final String value) { return BigDecimal.valueOf(Long.parseLong(value)); } @Override - String formatNonNull(@NonNull final BigDecimal value) { + String formatNonNull(@Nonnull final BigDecimal value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java index 1aa0b46..0cbd766 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/DecimalToDoubleCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class DecimalToDoubleCodec extends AbstractCodec implements TypeC public DecimalToDoubleCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.DOUBLE; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.DECIMAL; } @Override - public ByteBuffer encode(final Double value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Double value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Double value, @NonNull final ProtocolVersion prot } @Override - public Double decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Double decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -66,12 +66,12 @@ public Double decode(final ByteBuffer bytes, @NonNull final ProtocolVersion prot } @Override - Double parseNonNull(@NonNull final String value) { + Double parseNonNull(@Nonnull final String value) { return Double.valueOf(value); } @Override - String formatNonNull(@NonNull final Double value) { + String formatNonNull(@Nonnull final Double value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java index a8298e1..7262414 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/FloatToDoubleCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class FloatToDoubleCodec extends AbstractCodec implements TypeCod public FloatToDoubleCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.DOUBLE; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.FLOAT; } @Override - public ByteBuffer encode(final Double value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Double value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Double value, @NonNull final ProtocolVersion prot } @Override - public Double decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Double decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -67,12 +67,12 @@ public Double decode(final ByteBuffer bytes, @NonNull final ProtocolVersion prot } @Override - Double parseNonNull(@NonNull final String value) { + Double parseNonNull(@Nonnull final String value) { return Double.valueOf(value); } @Override - String formatNonNull(@NonNull final Double value) { + String formatNonNull(@Nonnull final Double value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java index 5b56ae2..5d0a33c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/IntToLongCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class IntToLongCodec extends AbstractCodec implements TypeCodec getJavaType() { return GenericType.LONG; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.INT; } @Override - public ByteBuffer encode(final Long value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Long value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Long value, @NonNull final ProtocolVersion protoc } @Override - public Long decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Long decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -66,12 +66,12 @@ public Long decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protoc } @Override - Long parseNonNull(@NonNull final String value) { + Long parseNonNull(@Nonnull final String value) { return Long.valueOf(value); } @Override - String formatNonNull(@NonNull final Long value) { + String formatNonNull(@Nonnull final Long value) { return String.valueOf(value); } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java index 51b55a8..d835166 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/LongToIntCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class LongToIntCodec extends AbstractCodec implements TypeCodec< public LongToIntCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.INTEGER; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.BIGINT; } @Override - public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Integer value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion pro } @Override - public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Integer decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -67,12 +67,12 @@ public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion pro } @Override - Integer parseNonNull(@NonNull final String value) { + Integer parseNonNull(@Nonnull final String value) { return Integer.valueOf(value); } @Override - String formatNonNull(@NonNull final Integer value) { + String formatNonNull(@Nonnull final Integer value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java index 7539971..d15e7f0 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/SmallintToIntCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class SmallintToIntCodec extends AbstractCodec implements TypeCo public SmallintToIntCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.INTEGER; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.SMALLINT; } @Override - public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Integer value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion pro } @Override - public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Integer decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -67,12 +67,12 @@ public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion pro } @Override - Integer parseNonNull(@NonNull final String value) { + Integer parseNonNull(@Nonnull final String value) { return Integer.valueOf(value); } @Override - String formatNonNull(@NonNull final Integer value) { + String formatNonNull(@Nonnull final Integer value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java index 18b13e4..06fdeae 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/TimestampToLongCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class TimestampToLongCodec extends AbstractCodec implements TypeCod public TimestampToLongCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.LONG; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.TIMESTAMP; } @Override - public ByteBuffer encode(final Long value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Long value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Long value, @NonNull final ProtocolVersion protoc } @Override - public Long decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Long decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -66,12 +66,12 @@ public Long decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protoc } @Override - Long parseNonNull(@NonNull final String value) { + Long parseNonNull(@Nonnull final String value) { return Long.valueOf(value); } @Override - String formatNonNull(@NonNull final Long value) { + String formatNonNull(@Nonnull final Long value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java index e31ad8d..3a09f9d 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/TinyintToIntCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class TinyintToIntCodec extends AbstractCodec implements TypeCod public TinyintToIntCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.INTEGER; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.TINYINT; } @Override - public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Integer value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion pro } @Override - public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Integer decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -67,12 +67,12 @@ public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion pro } @Override - Integer parseNonNull(@NonNull final String value) { + Integer parseNonNull(@Nonnull final String value) { return Integer.valueOf(value); } @Override - String formatNonNull(@NonNull final Integer value) { + String formatNonNull(@Nonnull final Integer value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java b/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java index c89f6a1..79988ad 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/codec/VarintToIntCodec.java @@ -20,9 +20,9 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.reflect.GenericType; -import edu.umd.cs.findbugs.annotations.NonNull; import com.ing.data.cassandra.jdbc.utils.ByteBufferUtil; +import javax.annotation.Nonnull; import java.nio.ByteBuffer; /** @@ -36,20 +36,20 @@ public class VarintToIntCodec extends AbstractCodec implements TypeCode public VarintToIntCodec() { } - @NonNull + @Nonnull @Override public GenericType getJavaType() { return GenericType.INTEGER; } - @NonNull + @Nonnull @Override public DataType getCqlType() { return DataTypes.VARINT; } @Override - public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion protocolVersion) { + public ByteBuffer encode(final Integer value, @Nonnull final ProtocolVersion protocolVersion) { if (value == null) { return null; } @@ -57,7 +57,7 @@ public ByteBuffer encode(final Integer value, @NonNull final ProtocolVersion pro } @Override - public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion protocolVersion) { + public Integer decode(final ByteBuffer bytes, @Nonnull final ProtocolVersion protocolVersion) { if (bytes == null) { return null; } @@ -66,12 +66,12 @@ public Integer decode(final ByteBuffer bytes, @NonNull final ProtocolVersion pro } @Override - Integer parseNonNull(@NonNull final String value) { + Integer parseNonNull(@Nonnull final String value) { return Integer.valueOf(value); } @Override - String formatNonNull(@NonNull final Integer value) { + String formatNonNull(@Nonnull final Integer value) { return String.valueOf(value); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java index bfb0c12..2bf8bb3 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/AbstractMetadataResultSetBuilder.java @@ -42,6 +42,10 @@ public abstract class AbstractMetadataResultSetBuilder { static final String TABLE = "TABLE"; static final String CQL_OPTION_COMMENT = "comment"; static final String ASC_OR_DESC = "ASC_OR_DESC"; + static final String ATTRIBUTE_DEFAULT = "ATTR_DEF"; + static final String ATTRIBUTE_NAME = "ATTR_NAME"; + static final String ATTRIBUTE_SIZE = "ATTR_SIZE"; + static final String ATTRIBUTE_TYPE_NAME = "ATTR_TYPE_NAME"; static final String AUTO_INCREMENT = "AUTO_INCREMENT"; static final String BASE_TYPE = "BASE_TYPE"; static final String BUFFER_LENGTH = "BUFFER_LENGTH"; diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java index 40f3fc5..3b69f53 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/ColumnMetadataResultSetBuilder.java @@ -85,15 +85,15 @@ public ColumnMetadataResultSetBuilder(final CassandraStatement statement) throws * always {@code null} here since comments on columns does not exist in Cassandra. *
  • COLUMN_DEF String => default value for the column, which should be interpreted as a string * when the value is enclosed in single quotes, may be {@code null}. Always {@code null} here.
  • - *
  • SQL_DATA_TYPE int => not used: always {@code null} here.
  • + *
  • SQL_DATA_TYPE int => is not used: always {@code null} here.
  • *
  • SQL_DATETIME_SUB int => is not used: always {@code null} here.
  • *
  • CHAR_OCTET_LENGTH int => for char types the maximum number of bytes in the column.
  • *
  • ORDINAL_POSITION int => index of column in table (starting at 1).
  • *
  • IS_NULLABLE String => ISO rules are used to determine the nullability for a column: *
      - *
    • YES - if the parameter can include {@code NULL}s
    • - *
    • NO - if the parameter cannot include {@code NULL}s
    • - *
    • empty string - if the nullability for the parameter is unknown
    • + *
    • YES - if the column can include {@code NULL}s
    • + *
    • NO - if the column cannot include {@code NULL}s
    • + *
    • empty string - if the nullability for the column is unknown
    • *
    Always empty here. *
  • *
  • SCOPE_CATALOG String => catalog of table that is the scope of a reference attribute diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java index 0510046..cb8cf6e 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TypeMetadataResultSetBuilder.java @@ -17,12 +17,15 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet; import com.ing.data.cassandra.jdbc.CassandraStatement; import com.ing.data.cassandra.jdbc.types.AbstractJdbcType; import com.ing.data.cassandra.jdbc.types.DataTypeEnum; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.sql.DatabaseMetaData; import java.sql.SQLException; @@ -30,9 +33,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION; import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE; import static com.ing.data.cassandra.jdbc.types.TypesMap.getTypeForComparator; import static java.sql.DatabaseMetaData.typeNullable; @@ -44,6 +49,8 @@ */ public class TypeMetadataResultSetBuilder extends AbstractMetadataResultSetBuilder { + private static final Logger LOG = LoggerFactory.getLogger(TypeMetadataResultSetBuilder.class); + /** * Constructor. * @@ -208,6 +215,8 @@ public CassandraMetadataResultSet buildTypes() throws SQLException { if (jdbcType.needsQuotes()) { literalQuotingSymbol = "'"; } + /* FIXME: some values should be adapted for list, set, map, vector, tuple and UDTs (JDBC type OTHER). + Special JDBC types similar to JdbcCounterColumn should be used for that. */ final MetadataRow row = new MetadataRow() .addEntry(TYPE_NAME, dataType.cqlType) .addEntry(DATA_TYPE, String.valueOf(jdbcType.getJdbcType())) @@ -235,4 +244,166 @@ public CassandraMetadataResultSet buildTypes() throws SQLException { return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(types)); } + /** + * Builds a valid result set of the description of the given attribute of the given type for a user-defined type + * (UDT) that is available in the given schema. + * This method is used to implement the method + * {@link DatabaseMetaData#getAttributes(String, String, String, String)}. + *

    + * Only descriptions for attributes of UDTs matching the catalog, schema, type and attribute criteria are returned. + * They are ordered by {@code TYPE_CAT}, {@code TYPE_SCHEM} and {@code TYPE_NAME} and {@code ORDINAL_POSITION}. + * This description does not contain inherited attributes. + * The type name parameter may be a fully-qualified name (it should respect the format + * {@code .}). In this case, the {@code catalog} and {@code schemaPattern} parameters are + * ignored. + *

    + *

    + * The columns of this result set are: + *

      + *
    1. TYPE_CAT String => table catalog, may be {@code null}: here is the Cassandra cluster name + * (if available).
    2. + *
    3. TYPE_SCHEM String => table schema, may be {@code null}: here is the keyspace the type is + * member of.
    4. + *
    5. TYPE_NAME String => type name.
    6. + *
    7. ATTR_NAME String => attribute name.
    8. + *
    9. DATA_TYPE int => attribute SQL type from {@link Types}.
    10. + *
    11. ATTR_TYPE_NAME String => the data source dependent type name. For a UDT, the type name is + * fully qualified. There is no {@code REF} in Cassandra.
    12. + *
    13. ATTR_SIZE int => column size.
    14. + *
    15. DECIMAL_DIGITS int => the number of fractional digits, {@code null} is returned for data + * types where it is not applicable. Always {@code null} here.
    16. + *
    17. NUM_PREC_RADIX int => Radix (typically either 10 or 2).
    18. + *
    19. NULLABLE int => is {@code NULL} allowed: + *
        + *
      • {@link DatabaseMetaData#attributeNoNulls} - might not allow {@code NULL} values
      • + *
      • {@link DatabaseMetaData#attributeNullable} - definitely allows {@code NULL} values
      • + *
      • {@link DatabaseMetaData#attributeNullableUnknown} - nullability unknown
      • + *
      Always {@link DatabaseMetaData#attributeNoNulls} here. + *
    20. + *
    21. REMARKS String => comment describing column, may be {@code null}: + * always {@code null} here since comments on columns does not exist in Cassandra.
    22. + *
    23. ATTR_DEF String => attribute default value, always {@code null} since Cassandra does not + * support default values.
    24. + *
    25. SQL_DATA_TYPE int => is not used: always {@code null} here.
    26. + *
    27. SQL_DATETIME_SUB int => is not used: always {@code null} here.
    28. + *
    29. CHAR_OCTET_LENGTH int => for char types the maximum number of bytes in the column.
    30. + *
    31. ORDINAL_POSITION int => index of attribute in UDT (starting at 1).
    32. + *
    33. IS_NULLABLE String => ISO rules are used to determine the nullability for an attribute: + *
        + *
      • YES - if the attribute can include {@code NULL}s
      • + *
      • NO - if the attribute cannot include {@code NULL}s
      • + *
      • empty string - if the nullability for the attribute is unknown
      • + *
      Always empty here. + *
    34. + *
    35. SCOPE_CATALOG String => catalog of table that is the scope of a reference attribute + * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
    36. + *
    37. SCOPE_SCHEMA String => schema of table that is the scope of a reference attribute + * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
    38. + *
    39. SCOPE_TABLE String => table name that is the scope of a reference attribute + * ({@code null} if {@code DATA_TYPE} isn't REF). Always {@code null} here.
    40. + *
    41. SOURCE_DATA_TYPE short => source type of a distinct type or user-generated Ref type, SQL type + * from {@link Types} ({@code null} if {@code DATA_TYPE} isn't {@code DISTINCT} or user-generated + * {@code REF}). Always {@code null} here.
    42. + *
    + *

    + * + * @param schemaPattern A schema pattern name; must match the schema name as it is stored in the database; + * {@code ""} retrieves those without a schema (will always return an empty set); + * {@code null} means that the schema name should not be used to narrow the search and + * in this case the search is restricted to the current schema (if available). + * @param typeNamePattern A type name pattern; must match the type name as it is stored in the database (not + * case-sensitive); may be a fully qualified name. + * @param attributesNamePattern An attribute name pattern; must match the attribute name as it is declared in the + * database (not case-sensitive). + * @return A valid result set for implementation of + * {@link DatabaseMetaData#getAttributes(String, String, String, String)}. + * @throws SQLException when something went wrong during the creation of the result set. + */ + public CassandraMetadataResultSet buildAttributes(final String schemaPattern, final String typeNamePattern, + final String attributesNamePattern) throws SQLException { + final String catalog = this.connection.getCatalog(); + final ArrayList attributesRows = new ArrayList<>(); + + // Parse the fully-qualified type name, if necessary. + String schemaName = schemaPattern; + final AtomicReference typeName = new AtomicReference<>(typeNamePattern); + if (typeNamePattern.contains(".")) { + final String[] fullyQualifiedTypeNameParts = typeNamePattern.split("\\."); + schemaName = fullyQualifiedTypeNameParts[0]; + typeName.set(fullyQualifiedTypeNameParts[1]); + } + + filterBySchemaNamePattern(schemaName, keyspaceMetadata -> { + final Map udts = keyspaceMetadata.getUserDefinedTypes(); + udts.entrySet() + .stream().filter(udt -> matchesPattern(typeName.get(), udt.getValue().getName().asInternal())) + .forEach(udt -> { + final UserDefinedType udtMetadata = udt.getValue(); + final List attrNames = udtMetadata.getFieldNames(); + final List attrTypes = udtMetadata.getFieldTypes(); + for (int i = 0; i < attrNames.size(); i++) { + final String attrName = attrNames.get(i).asInternal(); + if (!matchesPattern(attributesNamePattern, attrName)) { + continue; + } + + final DataType attrType = attrTypes.get(i); + final AbstractJdbcType jdbcEquivalentType = getTypeForComparator(attrType.toString()); + + // Define value of ATTR_SIZE. + int columnSize = DEFAULT_PRECISION; + if (jdbcEquivalentType != null) { + columnSize = jdbcEquivalentType.getPrecision(null); + } + + // Define value of NUM_PREC_RADIX. + int radix = 2; + if (jdbcEquivalentType != null && (jdbcEquivalentType.getJdbcType() == Types.DECIMAL + || jdbcEquivalentType.getJdbcType() == Types.NUMERIC)) { + radix = 10; + } + + // Define value of DATA_TYPE. + int jdbcType = Types.OTHER; + try { + jdbcType = getTypeForComparator(attrType.toString()).getJdbcType(); + } catch (final Exception e) { + LOG.warn("Unable to get JDBC type for comparator [{}]: {}", attrType, e.getMessage()); + } + + final MetadataRow row = new MetadataRow() + .addEntry(TYPE_CATALOG, catalog) + .addEntry(TYPE_SCHEMA, keyspaceMetadata.getName().asInternal()) + .addEntry(TYPE_NAME, udtMetadata.getName().asInternal()) + .addEntry(ATTRIBUTE_NAME, attrName) + .addEntry(DATA_TYPE, String.valueOf(jdbcType)) + .addEntry(ATTRIBUTE_TYPE_NAME, attrType.toString()) + .addEntry(ATTRIBUTE_SIZE, String.valueOf(columnSize)) + .addEntry(DECIMAL_DIGITS, null) + .addEntry(NUM_PRECISION_RADIX, String.valueOf(radix)) + .addEntry(NULLABLE, String.valueOf(DatabaseMetaData.attributeNoNulls)) + .addEntry(REMARKS, null) + .addEntry(ATTRIBUTE_DEFAULT, null) + .addEntry(SQL_DATA_TYPE, null) + .addEntry(SQL_DATETIME_SUB, null) + .addEntry(CHAR_OCTET_LENGTH, String.valueOf(Integer.MAX_VALUE)) + .addEntry(ORDINAL_POSITION, String.valueOf(i + 1)) + .addEntry(IS_NULLABLE, StringUtils.EMPTY) + .addEntry(SCOPE_CATALOG, null) + .addEntry(SCOPE_SCHEMA, null) + .addEntry(SCOPE_TABLE, null) + .addEntry(SOURCE_DATA_TYPE, null); + attributesRows.add(row); + } + }); + + }, null); + + // Results should all have the same TYPE_CAT so just sort them by TYPE_SCHEM, TYPE_NAME then ORDINAL_POSITION. + attributesRows.sort(Comparator.comparing(row -> ((MetadataRow) row).getString(TYPE_SCHEMA)) + .thenComparing(row -> ((MetadataRow) row).getString(TYPE_NAME)) + .thenComparing(row -> ((MetadataRow) row).getString(ORDINAL_POSITION))); + return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(attributesRows)); + } + } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java index 8261c34..261eb11 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/AbstractJdbcUUID.java @@ -15,8 +15,7 @@ package com.ing.data.cassandra.jdbc.types; -import edu.umd.cs.findbugs.annotations.NonNull; - +import javax.annotation.Nonnull; import java.sql.Types; import java.util.UUID; @@ -29,7 +28,7 @@ public abstract class AbstractJdbcUUID extends AbstractJdbcType { private static final int DEFAULT_UUID_PRECISION = 36; @Override - public String toString(@NonNull final UUID obj) { + public String toString(@Nonnull final UUID obj) { return obj.toString(); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index d1fe6e3..1b03f5c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -22,8 +22,8 @@ import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.protocol.internal.ProtocolConstants.DataType; -import edu.umd.cs.findbugs.annotations.NonNull; +import javax.annotation.Nonnull; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; @@ -324,7 +324,7 @@ public String toString() { * @param dataType The data type. * @return The CQL name of the type. */ - public static String cqlName(@NonNull final com.datastax.oss.driver.api.core.type.DataType dataType) { + public static String cqlName(@Nonnull final com.datastax.oss.driver.api.core.type.DataType dataType) { return dataType.asCql(false, false); } } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java index 8988d46..a165f7e 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcLexicalUUID.java @@ -15,8 +15,7 @@ package com.ing.data.cassandra.jdbc.types; -import edu.umd.cs.findbugs.annotations.NonNull; - +import javax.annotation.Nonnull; import java.util.UUID; /** @@ -34,12 +33,12 @@ public class JdbcLexicalUUID extends AbstractJdbcUUID { } @Override - public UUID compose(@NonNull final Object obj) { + public UUID compose(@Nonnull final Object obj) { return UUID.fromString(obj.toString()); } @Override - public Object decompose(@NonNull final UUID value) { + public Object decompose(@Nonnull final UUID value) { return value.toString(); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java index 0e5dde0..965b0f4 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTimeUUID.java @@ -15,8 +15,8 @@ package com.ing.data.cassandra.jdbc.types; -import edu.umd.cs.findbugs.annotations.NonNull; +import javax.annotation.Nonnull; import java.util.UUID; /** @@ -34,12 +34,12 @@ public class JdbcTimeUUID extends AbstractJdbcUUID { } @Override - public UUID compose(@NonNull final Object obj) { + public UUID compose(@Nonnull final Object obj) { return UUID.fromString(obj.toString()); } @Override - public Object decompose(@NonNull final UUID value) { + public Object decompose(@Nonnull final UUID value) { return value.toString(); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java index cfe5ec2..0b2cde9 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcTuple.java @@ -16,8 +16,8 @@ package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.TupleValue; -import edu.umd.cs.findbugs.annotations.NonNull; +import javax.annotation.Nonnull; import java.sql.Types; /** @@ -50,7 +50,7 @@ public boolean isSigned() { } @Override - public String toString(@NonNull final TupleValue obj) { + public String toString(@Nonnull final TupleValue obj) { return getString(obj); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java index 94aa5dc..1485739 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUUID.java @@ -15,8 +15,7 @@ package com.ing.data.cassandra.jdbc.types; -import edu.umd.cs.findbugs.annotations.NonNull; - +import javax.annotation.Nonnull; import java.nio.ByteBuffer; import java.util.UUID; @@ -52,7 +51,7 @@ public UUID compose(final ByteBuffer bytes) { } @Override - public UUID compose(@NonNull final Object obj) { + public UUID compose(@Nonnull final Object obj) { return UUID.fromString(obj.toString()); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java index 3540a17..f19a29f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/JdbcUdt.java @@ -16,8 +16,8 @@ package com.ing.data.cassandra.jdbc.types; import com.datastax.oss.driver.api.core.data.UdtValue; -import edu.umd.cs.findbugs.annotations.NonNull; +import javax.annotation.Nonnull; import java.sql.Types; /** @@ -49,7 +49,7 @@ public boolean isSigned() { } @Override - public String toString(@NonNull final UdtValue obj) { + public String toString(@Nonnull final UdtValue obj) { return getString(obj); } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java index 22ca4cd..a2812ab 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/MetadataResultSetsUnitTest.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.List; +import static com.ing.data.cassandra.jdbc.types.DataTypeEnum.VECTOR; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsIterableContaining.hasItem; @@ -549,6 +550,48 @@ void givenStatement_whenBuildTypes_returnExpectedResultSet() throws SQLException foundColumns.get(25)); assertEquals("uuid;1111;36;null;null;null;1;false;2;true;true;false;null;0;0;null;null;36", foundColumns.get(26)); + assertEquals(VECTOR.cqlType.concat(";1111;-1;';';null;1;true;2;true;true;false;null;0;0;null;null;-1"), + foundColumns.get(27)); + } + + @Test + void givenStatement_whenBuildAttributes_returnExpectedResultSet() throws SQLException { + final CassandraStatement statement = (CassandraStatement) sqlConnection.createStatement(); + final ResultSet result = new TypeMetadataResultSetBuilder(statement) + .buildAttributes(KEYSPACE, "type_in_different_ks", "t_%"); + assertNotNull(result); + assertEquals(21, result.getMetaData().getColumnCount()); + assertEquals("TYPE_CAT", result.getMetaData().getColumnName(1)); + assertEquals("TYPE_SCHEM", result.getMetaData().getColumnName(2)); + assertEquals("TYPE_NAME", result.getMetaData().getColumnName(3)); + assertEquals("ATTR_NAME", result.getMetaData().getColumnName(4)); + assertEquals("DATA_TYPE", result.getMetaData().getColumnName(5)); + assertEquals("ATTR_TYPE_NAME", result.getMetaData().getColumnName(6)); + assertEquals("ATTR_SIZE", result.getMetaData().getColumnName(7)); + assertEquals("DECIMAL_DIGITS", result.getMetaData().getColumnName(8)); + assertEquals("NUM_PREC_RADIX", result.getMetaData().getColumnName(9)); + assertEquals("NULLABLE", result.getMetaData().getColumnName(10)); + assertEquals("REMARKS", result.getMetaData().getColumnName(11)); + assertEquals("ATTR_DEF", result.getMetaData().getColumnName(12)); + assertEquals("SQL_DATA_TYPE", result.getMetaData().getColumnName(13)); + assertEquals("SQL_DATETIME_SUB", result.getMetaData().getColumnName(14)); + assertEquals("CHAR_OCTET_LENGTH", result.getMetaData().getColumnName(15)); + assertEquals("ORDINAL_POSITION", result.getMetaData().getColumnName(16)); + assertEquals("IS_NULLABLE", result.getMetaData().getColumnName(17)); + assertEquals("SCOPE_CATALOG", result.getMetaData().getColumnName(18)); + assertEquals("SCOPE_SCHEMA", result.getMetaData().getColumnName(19)); + assertEquals("SCOPE_TABLE", result.getMetaData().getColumnName(20)); + assertEquals("SOURCE_DATA_TYPE", result.getMetaData().getColumnName(21)); + final List foundAttrs = new ArrayList<>(); + int resultSize = 0; + while (result.next()) { + ++resultSize; + foundAttrs.add(String.join(";", result.getString(2), result.getString(3), result.getString(4), + result.getString(6), result.getString(16))); + } + assertEquals(2, resultSize); + assertThat(foundAttrs, hasItem(is(KEYSPACE.concat(";type_in_different_ks;t_key;INT;1")))); + assertThat(foundAttrs, hasItem(is(KEYSPACE.concat(";type_in_different_ks;t_value;TEXT;2")))); } /* diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java index dc5e514..bebdd8a 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeLoadBalancingPolicy.java @@ -19,8 +19,8 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan; -import lombok.NonNull; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Map; import java.util.Queue; @@ -35,11 +35,11 @@ public AnotherFakeLoadBalancingPolicy(final DriverContext context, final String } @Override - public void init(@NonNull final Map nodes, @NonNull final DistanceReporter distanceReporter) { + public void init(@Nonnull final Map nodes, @Nonnull final DistanceReporter distanceReporter) { // Do nothing. For testing purpose only. } - @NonNull + @Nonnull @Override public Queue newQueryPlan(@Nullable final Request request, @Nullable final Session session) { // Do nothing. For testing purpose only. @@ -47,22 +47,22 @@ public Queue newQueryPlan(@Nullable final Request request, @Nullable final } @Override - public void onAdd(@NonNull final Node node) { + public void onAdd(@Nonnull final Node node) { // Do nothing. For testing purpose only. } @Override - public void onUp(@NonNull final Node node) { + public void onUp(@Nonnull final Node node) { // Do nothing. For testing purpose only. } @Override - public void onDown(@NonNull final Node node) { + public void onDown(@Nonnull final Node node) { // Do nothing. For testing purpose only. } @Override - public void onRemove(@NonNull final Node node) { + public void onRemove(@Nonnull final Node node) { // Do nothing. For testing purpose only. } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeRetryPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeRetryPolicy.java index 1a18d49..ef5eb46 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeRetryPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/AnotherFakeRetryPolicy.java @@ -20,16 +20,17 @@ import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.datastax.oss.driver.api.core.session.Request; -import edu.umd.cs.findbugs.annotations.NonNull; + +import javax.annotation.Nonnull; public class AnotherFakeRetryPolicy implements RetryPolicy { - public AnotherFakeRetryPolicy(@NonNull final DriverContext context, @NonNull final String profileName) { + public AnotherFakeRetryPolicy(final DriverContext context, final String profileName) { // Do nothing. For testing purpose only. } @Override - public RetryDecision onReadTimeout(@NonNull final Request request, @NonNull final ConsistencyLevel cl, + public RetryDecision onReadTimeout(@Nonnull final Request request, @Nonnull final ConsistencyLevel cl, final int blockFor, final int received, final boolean dataPresent, final int retryCount) { // Do nothing. For testing purpose only. @@ -37,29 +38,29 @@ public RetryDecision onReadTimeout(@NonNull final Request request, @NonNull fina } @Override - public RetryDecision onWriteTimeout(@NonNull final Request request, @NonNull final ConsistencyLevel cl, - @NonNull final WriteType writeType, final int blockFor, final int received, + public RetryDecision onWriteTimeout(@Nonnull final Request request, @Nonnull final ConsistencyLevel cl, + @Nonnull final WriteType writeType, final int blockFor, final int received, final int retryCount) { // Do nothing. For testing purpose only. return null; } @Override - public RetryDecision onUnavailable(@NonNull final Request request, @NonNull final ConsistencyLevel cl, + public RetryDecision onUnavailable(@Nonnull final Request request, @Nonnull final ConsistencyLevel cl, final int required, final int alive, final int retryCount) { // Do nothing. For testing purpose only. return null; } @Override - public RetryDecision onRequestAborted(@NonNull final Request request, @NonNull final Throwable error, + public RetryDecision onRequestAborted(@Nonnull final Request request, @Nonnull final Throwable error, final int retryCount) { // Do nothing. For testing purpose only. return null; } @Override - public RetryDecision onErrorResponse(@NonNull final Request request, @NonNull final CoordinatorException error, + public RetryDecision onErrorResponse(@Nonnull final Request request, @Nonnull final CoordinatorException error, final int retryCount) { // Do nothing. For testing purpose only. return null; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java index f23b137..544badf 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeLoadBalancingPolicy.java @@ -19,8 +19,8 @@ import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan; -import lombok.NonNull; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Map; import java.util.Queue; @@ -30,16 +30,16 @@ public class FakeLoadBalancingPolicy implements LoadBalancingPolicy { - public FakeLoadBalancingPolicy(@NonNull final DriverContext context, @NonNull final String profileName) { + public FakeLoadBalancingPolicy(@Nonnull final DriverContext context, @Nonnull final String profileName) { // Do nothing. For testing purpose only. } @Override - public void init(@NonNull final Map nodes, @NonNull final DistanceReporter distanceReporter) { + public void init(@Nonnull final Map nodes, @Nonnull final DistanceReporter distanceReporter) { // Do nothing. For testing purpose only. } - @NonNull + @Nonnull @Override public Queue newQueryPlan(@Nullable final Request request, @Nullable final Session session) { // Do nothing. For testing purpose only. @@ -47,22 +47,22 @@ public Queue newQueryPlan(@Nullable final Request request, @Nullable final } @Override - public void onAdd(@NonNull final Node node) { + public void onAdd(@Nonnull final Node node) { // Do nothing. For testing purpose only. } @Override - public void onUp(@NonNull final Node node) { + public void onUp(@Nonnull final Node node) { // Do nothing. For testing purpose only. } @Override - public void onDown(@NonNull final Node node) { + public void onDown(@Nonnull final Node node) { // Do nothing. For testing purpose only. } @Override - public void onRemove(@NonNull final Node node) { + public void onRemove(@Nonnull final Node node) { // Do nothing. For testing purpose only. } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeReconnectionPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeReconnectionPolicy.java index 5589413..7457d44 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeReconnectionPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeReconnectionPolicy.java @@ -16,7 +16,8 @@ import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.Node; -import edu.umd.cs.findbugs.annotations.NonNull; + +import javax.annotation.Nonnull; import static org.mockito.Mockito.mock; @@ -26,14 +27,14 @@ public FakeReconnectionPolicy(final DriverContext context) { // Do nothing. For testing purpose only. } - @NonNull + @Nonnull @Override - public ReconnectionSchedule newNodeSchedule(@NonNull final Node node) { + public ReconnectionSchedule newNodeSchedule(@Nonnull final Node node) { // Do nothing. For testing purpose only. return mock(ReconnectionSchedule.class); } - @NonNull + @Nonnull @Override public ReconnectionSchedule newControlConnectionSchedule(final boolean isInitialConnection) { // Do nothing. For testing purpose only. diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeRetryPolicy.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeRetryPolicy.java index 762ae8c..cf3b659 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeRetryPolicy.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeRetryPolicy.java @@ -20,16 +20,17 @@ import com.datastax.oss.driver.api.core.servererrors.CoordinatorException; import com.datastax.oss.driver.api.core.servererrors.WriteType; import com.datastax.oss.driver.api.core.session.Request; -import edu.umd.cs.findbugs.annotations.NonNull; + +import javax.annotation.Nonnull; public class FakeRetryPolicy implements RetryPolicy { - public FakeRetryPolicy(@NonNull final DriverContext context, @NonNull final String profileName) { + public FakeRetryPolicy(final DriverContext context, final String profileName) { // Do nothing. For testing purpose only. } @Override - public RetryDecision onReadTimeout(@NonNull final Request request, @NonNull final ConsistencyLevel cl, + public RetryDecision onReadTimeout(@Nonnull final Request request, @Nonnull final ConsistencyLevel cl, final int blockFor, final int received, final boolean dataPresent, final int retryCount) { // Do nothing. For testing purpose only. @@ -37,29 +38,29 @@ public RetryDecision onReadTimeout(@NonNull final Request request, @NonNull fina } @Override - public RetryDecision onWriteTimeout(@NonNull final Request request, @NonNull final ConsistencyLevel cl, - @NonNull final WriteType writeType, final int blockFor, final int received, + public RetryDecision onWriteTimeout(@Nonnull final Request request, @Nonnull final ConsistencyLevel cl, + @Nonnull final WriteType writeType, final int blockFor, final int received, final int retryCount) { // Do nothing. For testing purpose only. return null; } @Override - public RetryDecision onUnavailable(@NonNull final Request request, @NonNull final ConsistencyLevel cl, + public RetryDecision onUnavailable(@Nonnull final Request request, @Nonnull final ConsistencyLevel cl, final int required, final int alive, final int retryCount) { // Do nothing. For testing purpose only. return null; } @Override - public RetryDecision onRequestAborted(@NonNull final Request request, @NonNull final Throwable error, + public RetryDecision onRequestAborted(@Nonnull final Request request, @Nonnull final Throwable error, final int retryCount) { // Do nothing. For testing purpose only. return null; } @Override - public RetryDecision onErrorResponse(@NonNull final Request request, @NonNull final CoordinatorException error, + public RetryDecision onErrorResponse(@Nonnull final Request request, @Nonnull final CoordinatorException error, final int retryCount) { // Do nothing. For testing purpose only. return null; diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeSslEngineFactory.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeSslEngineFactory.java index ba52e19..aafa229 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeSslEngineFactory.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/FakeSslEngineFactory.java @@ -15,8 +15,8 @@ import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; -import edu.umd.cs.findbugs.annotations.NonNull; +import javax.annotation.Nonnull; import javax.net.ssl.SSLEngine; public class FakeSslEngineFactory implements SslEngineFactory { @@ -25,9 +25,9 @@ public FakeSslEngineFactory() { // Do nothing. For testing purpose only. } - @NonNull + @Nonnull @Override - public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) { + public SSLEngine newSslEngine(@Nonnull EndPoint remoteEndpoint) { // Do nothing. For testing purpose only. return null; } From 7b4c5b8c110c391a0bd2eb7dcf5ce7faabee0799 Mon Sep 17 00:00:00 2001 From: Cedrick Lunven Date: Tue, 26 Sep 2023 08:48:53 +0200 Subject: [PATCH 20/21] Fix Vector Support and add Samples for DBaas and Dse (#27) --- pom.xml | 22 ++ .../cassandra/jdbc/CassandraResultSet.java | 3 + .../cassandra/jdbc/types/DataTypeEnum.java | 16 +- .../jdbc/DbaasAstraIntegrationTest.java | 218 ++++++++++++++++++ .../cassandra/jdbc/UsingDseContainerTest.java | 74 ++++++ .../jdbc/VectorsDseContainerTest.java | 56 +++++ src/test/resources/initEmbeddedDse.cql | 33 +++ 7 files changed, 420 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java create mode 100644 src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java create mode 100644 src/test/resources/initEmbeddedDse.cql diff --git a/pom.xml b/pom.xml index 917f2d0..36ee2e8 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,13 @@ developer + + Cedrick Lunven + https://github.com/clun + + developer + + @@ -106,6 +113,7 @@ 3.12.4 1.7.36 1.18.3 + 0.6.11 3.3.0 3.3.1 @@ -233,6 +241,20 @@ ${testcontainers.version} test + + + com.datastax.astra + astra-sdk-devops + ${astra-sdk.version} + test + + + + com.datastax.oss + java-driver-query-builder + ${datastax.java.driver.version} + test + org.slf4j diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java index cff14e6..07edc07 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -1593,6 +1593,9 @@ public String getColumnTypeName(final int column) { } else { dataType = driverResultSet.getColumnDefinitions().get(column - 1).getType(); } + if (dataType.toString().contains(DataTypeEnum.VECTOR.cqlType)) { + return DataTypeEnum.VECTOR.cqlType; + } return dataType.toString(); } diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index 1b03f5c..a1fe3db 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -21,6 +21,7 @@ import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.VectorType; import com.datastax.oss.protocol.internal.ProtocolConstants.DataType; import javax.annotation.Nonnull; @@ -169,7 +170,7 @@ public enum DataTypeEnum { * {@code vector} CQL type (type {@value DataType#LIST} in CQL native protocol) mapped to {@link CqlVector} Java * type. */ - VECTOR(DataType.LIST, CqlVector.class, "vector"); + VECTOR(DataType.LIST, CqlVector.class, "Vector"); private static final Map CQL_DATATYPE_TO_DATATYPE; @@ -184,6 +185,8 @@ public enum DataTypeEnum { final int protocolId; + static final String VECTOR_CLASSNAME = "org.apache.cassandra.db.marshal.VectorType"; + static { CQL_DATATYPE_TO_DATATYPE = new HashMap<>(); for (final DataTypeEnum dataType : DataTypeEnum.values()) { @@ -217,6 +220,9 @@ public static DataTypeEnum fromCqlTypeName(final String cqlTypeName) { if (cqlTypeName.startsWith(UDT.cqlType)) { return UDT; } + if (cqlTypeName.contains(VECTOR_CLASSNAME)) { + return VECTOR; + } // Manage collection types (e.g. "list") final int collectionTypeCharPos = cqlTypeName.indexOf("<"); String cqlDataType = cqlTypeName; @@ -236,6 +242,9 @@ public static DataTypeEnum fromDataType(final com.datastax.oss.driver.api.core.t if (dataType instanceof UserDefinedType) { return UDT; } + if (dataType instanceof VectorType) { + return VECTOR; + } return fromCqlTypeName(dataType.asCql(false, false)); } @@ -320,12 +329,15 @@ public String toString() { /** * Gets the CQL name from a given {@link com.datastax.oss.driver.api.core.type.DataType} instance. + * For vectors, dataType.asCql returns looks like 'org.apache.cassandra.db.marshal.VectorType(n)' where n is + * the dimension of the vector. In this specific case, return a common name not including the dimension. * * @param dataType The data type. * @return The CQL name of the type. */ public static String cqlName(@Nonnull final com.datastax.oss.driver.api.core.type.DataType dataType) { - return dataType.asCql(false, false); + final String rawCql = dataType.asCql(false, false); + return rawCql.contains(VECTOR_CLASSNAME) ? VECTOR.cqlType : rawCql; } } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java b/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java new file mode 100644 index 0000000..c674a9c --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java @@ -0,0 +1,218 @@ +/* + * 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.ing.data.cassandra.jdbc; + +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; +import com.dtsx.astra.sdk.db.AstraDbClient; +import com.dtsx.astra.sdk.db.domain.DatabaseStatusType; +import com.dtsx.astra.sdk.utils.TestUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Test JDBC Driver against DbAAS Astra. + * To run this test define environment variable ASTRA_DB_APPLICATION_TOKEN + * but not having any token does not block the build. + */ +@TestMethodOrder(org.junit.jupiter.api.MethodOrderer.OrderAnnotation.class) +class DbaasAstraIntegrationTest { + + private static final Logger log = LoggerFactory.getLogger(DbaasAstraIntegrationTest.class); + private static final String DATABASE_NAME = "test_cassandra_jdbc"; + private static final String KEYSPACE_NAME = "test"; + static CassandraConnection sqlConnection = null; + + @BeforeAll + static void setupAstra() throws Exception { + if (System.getenv("ASTRA_DB_APPLICATION_TOKEN") != null) { + log.debug("ASTRA_DB_APPLICATION_TOKEN is provided, Astra Test is executed"); + + + /* + * Devops API Client (create database, resume, delete) + */ + AstraDbClient astraDbClient = new AstraDbClient(TestUtils.getAstraToken()); + log.debug("Connected the dbaas API"); + + /* + * Set up a Database in Astra : create if not exist, resume if needed + * Vector Database is Cassandra DB with vector support enabled. + * It can take up to 1 min to create the database if not exists + */ + String dbId = TestUtils.setupVectorDatabase(DATABASE_NAME, KEYSPACE_NAME); + Assertions.assertTrue(astraDbClient.findById(dbId).isPresent()); + Assertions.assertEquals(DatabaseStatusType.ACTIVE, astraDbClient.findById(dbId).get().getStatus()); + log.debug("Database ready"); + + /* + * Download cloud secure bundle to connect to the database. + * - Saved in /tmp + * - Single region = we can use default region + */ + astraDbClient + .database(dbId) + .downloadDefaultSecureConnectBundle("/tmp/" + DATABASE_NAME + "_scb.zip"); + log.debug("Connection bundle downloaded."); + + /* + * Building jdbcUrl and sqlConnection. + * Note: Astra can be access with only a token (username='token') + */ + sqlConnection = (CassandraConnection) DriverManager.getConnection( + "jdbc:cassandra://dbaas/" + KEYSPACE_NAME + + "?user=" + "token" + + "&password=" + TestUtils.getAstraToken() + // env var ASTRA_DB_APPLICATION_TOKEN + "&consistency=" + "LOCAL_QUORUM" + + "&secureconnectbundle=/tmp/" + DATABASE_NAME + "_scb.zip"); + } else { + log.debug("ASTRA_DB_APPLICATION_TOKEN is not defined, skipping ASTRA test"); + } + } + + @Test + @Order(1) + @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + void givenConnection_whenCreateTable_shouldTableExist() throws SQLException { + // Given + Assertions.assertNotNull(sqlConnection); + // When + sqlConnection.createStatement().execute(SchemaBuilder + .createTable("simple_table") + .ifNotExists() + .withPartitionKey("email", DataTypes.TEXT) + .withColumn("firstname", DataTypes.TEXT) + .withColumn("lastname", DataTypes.TEXT) + .build().getQuery()); + // Then + Assertions.assertTrue(tableExist("simple_table")); + } + + @Test + @Order(2) + @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + void givenTable_whenInsert_shouldRetrieveData() throws Exception { + // Given + Assertions.assertTrue(tableExist("simple_table")); + // When + String insertSimpleCQL = "INSERT INTO simple_table (email, firstname, lastname) VALUES(?,?,?)"; + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement(insertSimpleCQL); + prepStatement.setString(1, "pierre.feuille@foo.com"); + prepStatement.setString(2, "pierre"); + prepStatement.setString(2, "feuille"); + prepStatement.execute(); + // Then (warning on Cassandra expected) + Assertions.assertEquals(1, countRecords("simple_table")); + } + + @Test + @Order(3) + @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + void givenConnection_whenCreateTableVector_shouldTableExist() throws Exception { + // When + sqlConnection.createStatement().execute("" + + "CREATE TABLE IF NOT EXISTS pet_supply_vectors (" + + " product_id TEXT PRIMARY KEY," + + " product_name TEXT," + + " product_vector vector)"); + // Then + Assertions.assertTrue(tableExist("pet_supply_vectors")); + sqlConnection.createStatement().execute("" + + "CREATE CUSTOM INDEX IF NOT EXISTS idx_vector " + + "ON pet_supply_vectors(product_vector) " + + "USING 'StorageAttachedIndex'"); + // When + sqlConnection.createStatement().execute("" + + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf1843','HealthyFresh - Chicken raw dog food',[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0])"); + sqlConnection.createStatement().execute("" + + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf1844','HealthyFresh - Beef raw dog food',[1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0])"); + sqlConnection.createStatement().execute("" + + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pt0021','Dog Tennis Ball Toy',[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0])"); + sqlConnection.createStatement().execute("" + + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pt0041','Dog Ring Chew Toy',[0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0])"); + sqlConnection.createStatement().execute("" + + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf7043','PupperSausage Bacon dog Treats',[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1])"); + sqlConnection.createStatement().execute("" + + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf7044','PupperSausage Beef dog Treats',[0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0])"); + // Then (warning on Cassandra expected) + Assertions.assertEquals(6, countRecords("pet_supply_vectors")); + } + + @Test + @Order(4) + @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + void givenVectorTable_whenSimilaritySearch_shouldReturnResults() throws Exception { + // Given + Assertions.assertTrue(tableExist("pet_supply_vectors")); + Assertions.assertEquals(6, countRecords("pet_supply_vectors")); + // When + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement("" + + "SELECT\n" + + " product_id, product_vector,\n" + + " similarity_dot_product(product_vector,[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) as similarity\n" + + "FROM pet_supply_vectors\n" + + "ORDER BY product_vector\n" + + "ANN OF [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + + "LIMIT 2;"); + java.sql.ResultSet rs = prepStatement.executeQuery(); + // A result has been found + Assertions.assertTrue(rs.next()); + // Parsing Results + Assertions.assertNotNull(rs.getObject("product_vector")); + Assertions.assertEquals(3.0d, rs.getDouble("similarity")); + } + + private boolean tableExist(String tableName) throws SQLException { + String existTableCql = "select table_name,keyspace_name from system_schema.tables where keyspace_name=? and table_name=?"; + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement(existTableCql); + prepStatement.setString(1, KEYSPACE_NAME); + prepStatement.setString(2, tableName); + return prepStatement.executeQuery().next(); + } + + private int countRecords(String tablename) throws SQLException { + String countRecordsCql = "select count(*) from " + tablename; + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement(countRecordsCql); + final ResultSet resultSet = prepStatement.executeQuery(); + resultSet.next(); + return resultSet.getInt(1); + } + + @AfterAll + static void closeSql() throws SQLException { + if (sqlConnection != null) { + sqlConnection.close(); + } + } + +} + + diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java new file mode 100644 index 0000000..1a34631 --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java @@ -0,0 +1,74 @@ +/* + * 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.ing.data.cassandra.jdbc; + +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; +import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.InetSocketAddress; +import java.sql.DriverManager; + +@Testcontainers +abstract class UsingDseContainerTest { + + static CassandraConnection sqlConnection = null; + + // Using @Container annotation restarts a new container for each test of the class, so as it takes ~20/30 sec. to + // start a Cassandra container, we just want to have one container instance for all the tests of the class. See: + // https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers + static CassandraContainer cassandraContainer; + + protected static void initializeContainer(String version) { + DockerImageName dockerImageName = DockerImageName + .parse("datastax/dse-server:"+ version) + .asCompatibleSubstituteFor("cassandra"); + cassandraContainer = new CassandraContainer<>(dockerImageName) + .withEnv("DS_LICENSE", "accept") + .withEnv("CLUSTER_NAME", "embedded_test_cluster") + .withEnv("DC", "datacenter1") + .withInitScript("initEmbeddedDse.cql"); + cassandraContainer.start(); + } + + @AfterAll + static void afterTests() throws Exception { + if (sqlConnection != null) { + sqlConnection.close(); + } + cassandraContainer.stop(); + } + + static void initConnection(final String keyspace, final String... parameters) throws Exception { + sqlConnection = newConnection(keyspace, parameters); + } + + static CassandraConnection newConnection(final String keyspace, final String... parameters) throws Exception { + final InetSocketAddress contactPoint = cassandraContainer.getContactPoint(); + return (CassandraConnection) DriverManager.getConnection(buildJdbcUrl(contactPoint.getHostName(), + contactPoint.getPort(), keyspace, parameters)); + } + + static String buildJdbcUrl(final String host, final int port, final String keyspace, final String... parameters) { + String joinedParameters = String.join("&", parameters); + if (StringUtils.isNotBlank(joinedParameters)) { + joinedParameters = StringUtils.prependIfMissing(joinedParameters, "?"); + } + + return String.format("jdbc:cassandra://%s:%d/%s%s", host, port, keyspace, joinedParameters); + } + +} diff --git a/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java new file mode 100644 index 0000000..a123924 --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java @@ -0,0 +1,56 @@ +/* + * 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.ing.data.cassandra.jdbc; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test CQL Vector data type + */ +class VectorsDseContainerTest extends UsingDseContainerTest { + + private static final String KEYSPACE = "test_keyspace_vect"; + + @BeforeAll + static void setup() throws Exception { + initializeContainer("7.0.0-a"); + initConnection(KEYSPACE, "version=3.0.0", "localdatacenter=datacenter1"); + } + + @Test + void givenVectorTable_whenSimilaritySearch_shouldReturnResults() throws Exception { + // When + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement("" + + "SELECT\n" + + " product_id, product_vector,\n" + + " similarity_dot_product(product_vector,[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) as similarity\n" + + "FROM pet_supply_vectors\n" + + "ORDER BY product_vector\n" + + "ANN OF [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n" + + "LIMIT 2;"); + java.sql.ResultSet rs = prepStatement.executeQuery(); + // A result has been found + Assertions.assertTrue(rs.next()); + // Parsing Results + Assertions.assertNotNull(rs.getObject("product_vector")); + Assertions.assertEquals(3.0d, rs.getDouble("similarity")); + } + +} diff --git a/src/test/resources/initEmbeddedDse.cql b/src/test/resources/initEmbeddedDse.cql new file mode 100644 index 0000000..c5b41cd --- /dev/null +++ b/src/test/resources/initEmbeddedDse.cql @@ -0,0 +1,33 @@ +/* Init keyspace and tables for VectorsUnitTest */ +DROP KEYSPACE IF EXISTS test_keyspace_vect; +CREATE KEYSPACE "test_keyspace_vect" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; + +USE test_keyspace_vect; + +CREATE TABLE IF NOT EXISTS pet_supply_vectors ( + product_id TEXT PRIMARY KEY, + product_name TEXT, + product_vector vector +); + +/* Ni similarity search without the SAI INDEX. */ +CREATE CUSTOM INDEX IF NOT EXISTS idx_vector +ON pet_supply_vectors(product_vector) +USING 'StorageAttachedIndex'; + +INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) +VALUES ('pf1843','HealthyFresh - Chicken raw dog food',[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + +INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) +VALUES ('pf1844','HealthyFresh - Beef raw dog food',[1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]); +INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) +VALUES ('pt0021','Dog Tennis Ball Toy',[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0]); + +INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) +VALUES ('pt0041','Dog Ring Chew Toy',[0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0]); + +INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) +VALUES ('pf7043','PupperSausage Bacon dog Treats',[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1]); + +INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) +VALUES ('pf7044','PupperSausage Beef dog Treats',[0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0]); From 20a22642ee02e2b1ef7036e0eb84e48b2a7880ac Mon Sep 17 00:00:00 2001 From: Maxime Wiewiora <48218208+maximevw@users.noreply.github.com> Date: Sat, 30 Sep 2023 13:36:37 +0200 Subject: [PATCH 21/21] Prepare version 4.10.0 - Add vectors similarity functions from CEP-30 into the numeric functions listed into the DatabaseMetadata. - Allow disabling DSE tests using Maven profile. --- CHANGELOG.md | 5 +- README.md | 33 +++++-- pom.xml | 29 +++++- .../jdbc/CassandraDatabaseMetaData.java | 7 +- .../TableMetadataResultSetBuilder.java | 4 +- .../cassandra/jdbc/types/DataTypeEnum.java | 10 +- .../jdbc/DbaasAstraIntegrationTest.java | 93 ++++++++++--------- .../cassandra/jdbc/UsingDseContainerTest.java | 8 +- .../jdbc/VectorsDseContainerTest.java | 12 +-- src/test/resources/initEmbeddedDse.cql | 1 + 10 files changed, 123 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fadcdaf..28de2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [4.10.0] - Unreleased +## [4.10.0] - 2023-09-30 ### Added - Add support for new [`vector` CQL type](https://datastax-oss.atlassian.net/browse/JAVA-3060) - defined in [CEP-30](https://cwiki.apache.org/confluence/x/OQ40Dw). + defined in [CEP-30](https://cwiki.apache.org/confluence/x/OQ40Dw) + Also see PR [#27](https://github.com/ing-bank/cassandra-jdbc-wrapper/pull/27). - Implement the method `getWarnings()` in `CassandraResultSet`. - Implement the following methods of `CassandraDatabaseMetaData`: `getBestRowIdentifier(String, String, String, int, boolean)` and `getAttributes(String, String, String, String)`. diff --git a/README.md b/README.md index b864742..3924a0a 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,17 @@ To compile and run tests, execute the following Maven command: ```bash mvn clean package ``` + +#### Some considerations about running tests + +If for some reason the tests using DataStax Enterprise server (`*DseContainerTest`) fail in your local environment, you +might disable them using the Maven profile `disableDseTests`: +```bash +mvn clean package -PdisableDseTests +``` + +The test suite also includes integration tests with AstraDB (`DbaasAstraIntegrationTest`). These tests require an +AstraDB token configured in the environment variable `ASTRA_DB_APPLICATION_TOKEN`, otherwise they are skipped. ### Integration in Maven projects @@ -265,8 +276,11 @@ For further information about custom implementations of `SslEngineFactory`, see ### Connecting to DBaaS -In order to connect to the cloud [Cassandra-based DBaaS AstraDB](https://www.datastax.com/astra) cluster, one would -need to specify: +An alternative JDBC driver based on this one exists to ease the connection to the cloud +[Cassandra-based DBaaS AstraDB](https://www.datastax.com/astra) cluster: +[Astra JDBC driver](https://github.com/DataStax-Examples/astra-jdbc-connector/tree/main). Do not hesitate to use it if you are in this specific situation. + +It's still possible to connect to AstraDB using this JDBC wrapper, so one would need to specify: * `secureconnectbundle`: the fully qualified path of the cloud secure connect bundle file * `keyspace`: the keyspace to connect to * `user`: the username @@ -352,7 +366,8 @@ CREATE TABLE example_table ( varint_col varint, string_set_col set, string_list_col list, - string_map_col map + string_map_col map, + vector_col vector ); ``` @@ -360,6 +375,7 @@ To insert a record into `example_table` using a prepared statement: ```java import com.datastax.oss.driver.api.core.data.CqlDuration; +import com.datastax.oss.driver.api.core.data.CqlVector; import java.io.ByteArrayInputStream; import java.sql.Date; @@ -370,8 +386,8 @@ public class HelloCassandra { final String insertCql = "INSERT INTO example_table (bigint_col, ascii_col, blob_col, boolean_col, decimal_col, " + "double_col, float_col, inet_col, int_col, smallint_col, text_col, timestamp_col, time_col, date_col, " + "tinyint_col, duration_col, uuid_col, timeuuid_col, varchar_col, varint_col, string_set_col, " - + "string_list_col, string_map_col) " - + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now(), ?, ?, ?, ?, ?);"; + + "string_list_col, string_map_col, vector_col) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now(), ?, ?, ?, ?, ?, ?);"; final PreparedStatement preparedStatement = connection.prepareStatement(insertCql); preparedStatement.setObject(1, 1L); // bigint preparedStatement.setObject(2, "test"); // ascii @@ -401,14 +417,16 @@ public class HelloCassandra { sampleSet.add("test1"); sampleSet.add("test2"); preparedStatement.setObject(20, sampleSet); // set - ArrayList sampleList = new ArrayList(); + final ArrayList sampleList = new ArrayList(); sampleList.add("test1"); sampleList.add("test2"); preparedStatement.setObject(21, sampleList); // list - HashMap sampleMap = new HashMap(); + final HashMap sampleMap = new HashMap(); sampleMap.put("1", "test1"); sampleMap.put("2", "test2"); preparedStatement.setObject(22, sampleMap); // map + final CqlVector sampleVector = CqlVector.newInstance(1.0f, 0.0f, 1.0f, 0.5f, 0.2f); + preparedStatement.setObject(23, sampleVector); // vector // Execute the prepare statement. preparedStatement.execute(); } @@ -696,6 +714,7 @@ We use [SemVer](http://semver.org/) for versioning. * Madhavan Sridharan - **[@msmygit](https://github.com/msmygit)** * Marius Jokubauskas - **[@mjok](https://github.com/mjok)** * Sualeh Fatehi - **[@sualeh](https://github.com/sualeh)** +* Cedrick Lunven - **[@clun](https://github.com/clun)** And special thanks to the developer of the original project on which is based this one: * Alexander Dejanovski - **[@adejanovski](https://github.com/adejanovski)** diff --git a/pom.xml b/pom.xml index 36ee2e8..f79a208 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.ing.data cassandra-jdbc-wrapper - 4.9.1 + 4.10.0 jar Cassandra JDBC Wrapper @@ -109,10 +109,10 @@ 2.2 5.10.0 1.10.0 - 1.18.28 + 1.18.30 3.12.4 1.7.36 - 1.18.3 + 1.19.0 0.6.11 3.3.0 @@ -241,14 +241,14 @@ ${testcontainers.version} test - + com.datastax.astra astra-sdk-devops ${astra-sdk.version} test - + com.datastax.oss java-driver-query-builder @@ -461,5 +461,24 @@ + + + + disableDseTests + + + + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + *DseContainerTest.java + + + + + + diff --git a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java index 3ca43ca..8c7bce8 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraDatabaseMetaData.java @@ -511,8 +511,9 @@ public int getMaxUserNameLength() { @Override public String getNumericFunctions() throws SQLException { checkStatementClosed(); - // Cassandra does not implement natively numeric functions. - return StringUtils.EMPTY; + // We consider here the vectors similarity functions introduced by CEP-30 as numeric functions (see + // https://issues.apache.org/jira/browse/CASSANDRA-18640). + return "similarity_cosine,similarity_euclidean,similarity_dot_product"; } @Override @@ -779,7 +780,7 @@ public String getTimeDateFunctions() throws SQLException { checkStatementClosed(); // See: https://cassandra.apache.org/doc/latest/cassandra/cql/functions.html return "dateOf,now,minTimeuuid,maxTimeuuid,unixTimestampOf,toDate,toTimestamp,toUnixTimestamp,currentTimestamp," - + "currentDate,currentTime,currentTimeUUID,"; + + "currentDate,currentTime,currentTimeUUID"; } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java index b7346f4..b458deb 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/metadata/TableMetadataResultSetBuilder.java @@ -330,8 +330,8 @@ public CassandraMetadataResultSet buildPrimaryKeys(final String schema, final St *

    * * @param schema A schema name pattern. It must match the schema name as it is stored in the database; {@code ""} - * retrieves those without a schema and {@code null} means that the schema name should not be used to - * narrow the search. Using {@code ""} as the same effect as {@code null} because here the schema + * retrieves those without a schema and {@code null} means that the schema name should not be used + * to narrow the search. Using {@code ""} as the same effect as {@code null} because here the schema * corresponds to the keyspace and Cassandra tables cannot be defined outside a keyspace. * @param table A table name. It must match the table name as it is stored in the database. * @param scope The scope of interest, using the same values as {@code SCOPE} in the result set. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java index a1fe3db..ff34159 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/types/DataTypeEnum.java @@ -172,6 +172,8 @@ public enum DataTypeEnum { */ VECTOR(DataType.LIST, CqlVector.class, "Vector"); + static final String VECTOR_CLASSNAME = "org.apache.cassandra.db.marshal.VectorType"; + private static final Map CQL_DATATYPE_TO_DATATYPE; /** @@ -185,8 +187,6 @@ public enum DataTypeEnum { final int protocolId; - static final String VECTOR_CLASSNAME = "org.apache.cassandra.db.marshal.VectorType"; - static { CQL_DATATYPE_TO_DATATYPE = new HashMap<>(); for (final DataTypeEnum dataType : DataTypeEnum.values()) { @@ -220,6 +220,7 @@ public static DataTypeEnum fromCqlTypeName(final String cqlTypeName) { if (cqlTypeName.startsWith(UDT.cqlType)) { return UDT; } + // Manage vector type if (cqlTypeName.contains(VECTOR_CLASSNAME)) { return VECTOR; } @@ -337,7 +338,10 @@ public String toString() { */ public static String cqlName(@Nonnull final com.datastax.oss.driver.api.core.type.DataType dataType) { final String rawCql = dataType.asCql(false, false); - return rawCql.contains(VECTOR_CLASSNAME) ? VECTOR.cqlType : rawCql; + if (rawCql.contains(VECTOR_CLASSNAME)) { + return VECTOR.cqlType; + } + return rawCql; } } diff --git a/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java b/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java index c674a9c..27252f0 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/DbaasAstraIntegrationTest.java @@ -33,39 +33,41 @@ import java.sql.SQLException; /** - * Test JDBC Driver against DbAAS Astra. - * To run this test define environment variable ASTRA_DB_APPLICATION_TOKEN + * Test JDBC Driver against DBaaS Astra. + * To run this test class, define an environment variable ASTRA_DB_APPLICATION_TOKEN containing the AstraDB token, * but not having any token does not block the build. */ @TestMethodOrder(org.junit.jupiter.api.MethodOrderer.OrderAnnotation.class) class DbaasAstraIntegrationTest { private static final Logger log = LoggerFactory.getLogger(DbaasAstraIntegrationTest.class); + private static final String ASTRA_DB_TOKEN_ENV_VARIABLE = "ASTRA_DB_APPLICATION_TOKEN"; + private static final String ASTRA_DB_TOKEN_PATTERN = "Astra.*"; private static final String DATABASE_NAME = "test_cassandra_jdbc"; private static final String KEYSPACE_NAME = "test"; + static CassandraConnection sqlConnection = null; @BeforeAll static void setupAstra() throws Exception { - if (System.getenv("ASTRA_DB_APPLICATION_TOKEN") != null) { - log.debug("ASTRA_DB_APPLICATION_TOKEN is provided, Astra Test is executed"); - + if (System.getenv(ASTRA_DB_TOKEN_ENV_VARIABLE) != null) { + log.debug("ASTRA_DB_APPLICATION_TOKEN is provided, AstraDB tests are executed."); /* * Devops API Client (create database, resume, delete) */ - AstraDbClient astraDbClient = new AstraDbClient(TestUtils.getAstraToken()); - log.debug("Connected the dbaas API"); + final AstraDbClient astraDbClient = new AstraDbClient(TestUtils.getAstraToken()); + log.debug("Connected the DBaaS API."); /* - * Set up a Database in Astra : create if not exist, resume if needed + * Set up a Database in Astra: create if not exist, resume if needed. * Vector Database is Cassandra DB with vector support enabled. - * It can take up to 1 min to create the database if not exists + * It can take up to 1 min to create the database if not exists. */ String dbId = TestUtils.setupVectorDatabase(DATABASE_NAME, KEYSPACE_NAME); Assertions.assertTrue(astraDbClient.findById(dbId).isPresent()); Assertions.assertEquals(DatabaseStatusType.ACTIVE, astraDbClient.findById(dbId).get().getStatus()); - log.debug("Database ready"); + log.debug("Database ready."); /* * Download cloud secure bundle to connect to the database. @@ -79,7 +81,7 @@ static void setupAstra() throws Exception { /* * Building jdbcUrl and sqlConnection. - * Note: Astra can be access with only a token (username='token') + * Note: Astra can be accessed with only a token (username='token'). */ sqlConnection = (CassandraConnection) DriverManager.getConnection( "jdbc:cassandra://dbaas/" + KEYSPACE_NAME + @@ -88,13 +90,13 @@ static void setupAstra() throws Exception { "&consistency=" + "LOCAL_QUORUM" + "&secureconnectbundle=/tmp/" + DATABASE_NAME + "_scb.zip"); } else { - log.debug("ASTRA_DB_APPLICATION_TOKEN is not defined, skipping ASTRA test"); + log.debug("ASTRA_DB_APPLICATION_TOKEN is not defined, skipping AstraDB tests."); } } @Test @Order(1) - @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + @EnabledIfEnvironmentVariable(named = ASTRA_DB_TOKEN_ENV_VARIABLE, matches = ASTRA_DB_TOKEN_PATTERN) void givenConnection_whenCreateTable_shouldTableExist() throws SQLException { // Given Assertions.assertNotNull(sqlConnection); @@ -107,15 +109,15 @@ void givenConnection_whenCreateTable_shouldTableExist() throws SQLException { .withColumn("lastname", DataTypes.TEXT) .build().getQuery()); // Then - Assertions.assertTrue(tableExist("simple_table")); + Assertions.assertTrue(tableExists("simple_table")); } @Test @Order(2) - @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + @EnabledIfEnvironmentVariable(named = ASTRA_DB_TOKEN_ENV_VARIABLE, matches = ASTRA_DB_TOKEN_PATTERN) void givenTable_whenInsert_shouldRetrieveData() throws Exception { // Given - Assertions.assertTrue(tableExist("simple_table")); + Assertions.assertTrue(tableExists("simple_table")); // When String insertSimpleCQL = "INSERT INTO simple_table (email, firstname, lastname) VALUES(?,?,?)"; final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement(insertSimpleCQL); @@ -129,52 +131,52 @@ void givenTable_whenInsert_shouldRetrieveData() throws Exception { @Test @Order(3) - @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + @EnabledIfEnvironmentVariable(named = ASTRA_DB_TOKEN_ENV_VARIABLE, matches = ASTRA_DB_TOKEN_PATTERN) void givenConnection_whenCreateTableVector_shouldTableExist() throws Exception { // When - sqlConnection.createStatement().execute("" + + sqlConnection.createStatement().execute( "CREATE TABLE IF NOT EXISTS pet_supply_vectors (" + " product_id TEXT PRIMARY KEY," + " product_name TEXT," + " product_vector vector)"); // Then - Assertions.assertTrue(tableExist("pet_supply_vectors")); - sqlConnection.createStatement().execute("" + + Assertions.assertTrue(tableExists("pet_supply_vectors")); + sqlConnection.createStatement().execute( "CREATE CUSTOM INDEX IF NOT EXISTS idx_vector " + "ON pet_supply_vectors(product_vector) " + "USING 'StorageAttachedIndex'"); // When - sqlConnection.createStatement().execute("" + - "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + - "VALUES ('pf1843','HealthyFresh - Chicken raw dog food',[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0])"); - sqlConnection.createStatement().execute("" + - "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + - "VALUES ('pf1844','HealthyFresh - Beef raw dog food',[1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0])"); - sqlConnection.createStatement().execute("" + - "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + - "VALUES ('pt0021','Dog Tennis Ball Toy',[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0])"); - sqlConnection.createStatement().execute("" + - "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + - "VALUES ('pt0041','Dog Ring Chew Toy',[0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0])"); - sqlConnection.createStatement().execute("" + - "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + - "VALUES ('pf7043','PupperSausage Bacon dog Treats',[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1])"); - sqlConnection.createStatement().execute("" + - "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + - "VALUES ('pf7044','PupperSausage Beef dog Treats',[0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0])"); + sqlConnection.createStatement().execute( + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf1843','HealthyFresh - Chicken raw dog food',[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0])"); + sqlConnection.createStatement().execute( + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf1844','HealthyFresh - Beef raw dog food',[1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0])"); + sqlConnection.createStatement().execute( + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pt0021','Dog Tennis Ball Toy',[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0])"); + sqlConnection.createStatement().execute( + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pt0041','Dog Ring Chew Toy',[0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0])"); + sqlConnection.createStatement().execute( + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf7043','PupperSausage Bacon dog Treats',[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1])"); + sqlConnection.createStatement().execute( + "INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) " + + "VALUES ('pf7044','PupperSausage Beef dog Treats',[0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0])"); // Then (warning on Cassandra expected) Assertions.assertEquals(6, countRecords("pet_supply_vectors")); } @Test @Order(4) - @EnabledIfEnvironmentVariable(named = "ASTRA_DB_APPLICATION_TOKEN", matches = "Astra.*") + @EnabledIfEnvironmentVariable(named = ASTRA_DB_TOKEN_ENV_VARIABLE, matches = ASTRA_DB_TOKEN_PATTERN) void givenVectorTable_whenSimilaritySearch_shouldReturnResults() throws Exception { // Given - Assertions.assertTrue(tableExist("pet_supply_vectors")); + Assertions.assertTrue(tableExists("pet_supply_vectors")); Assertions.assertEquals(6, countRecords("pet_supply_vectors")); // When - final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement("" + + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement( "SELECT\n" + " product_id, product_vector,\n" + " similarity_dot_product(product_vector,[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) as similarity\n" + @@ -190,16 +192,17 @@ void givenVectorTable_whenSimilaritySearch_shouldReturnResults() throws Exceptio Assertions.assertEquals(3.0d, rs.getDouble("similarity")); } - private boolean tableExist(String tableName) throws SQLException { - String existTableCql = "select table_name,keyspace_name from system_schema.tables where keyspace_name=? and table_name=?"; + private boolean tableExists(final String tableName) throws SQLException { + final String existTableCql = + "select table_name,keyspace_name from system_schema.tables where keyspace_name=? and table_name=?"; final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement(existTableCql); prepStatement.setString(1, KEYSPACE_NAME); prepStatement.setString(2, tableName); return prepStatement.executeQuery().next(); } - private int countRecords(String tablename) throws SQLException { - String countRecordsCql = "select count(*) from " + tablename; + private int countRecords(final String tableName) throws SQLException { + String countRecordsCql = "select count(*) from " + tableName; final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement(countRecordsCql); final ResultSet resultSet = prepStatement.executeQuery(); resultSet.next(); diff --git a/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java index 1a34631..b1705ab 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/UsingDseContainerTest.java @@ -32,11 +32,11 @@ abstract class UsingDseContainerTest { // https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers static CassandraContainer cassandraContainer; - protected static void initializeContainer(String version) { - DockerImageName dockerImageName = DockerImageName - .parse("datastax/dse-server:"+ version) + protected static void initializeContainer() { + // For the official DataStax Enterprise server image, see here: https://hub.docker.com/r/datastax/dse-server/ + final DockerImageName dseServerImage = DockerImageName.parse("datastax/dse-server:7.0.0-a") .asCompatibleSubstituteFor("cassandra"); - cassandraContainer = new CassandraContainer<>(dockerImageName) + cassandraContainer = new CassandraContainer<>(dseServerImage) .withEnv("DS_LICENSE", "accept") .withEnv("CLUSTER_NAME", "embedded_test_cluster") .withEnv("DC", "datacenter1") diff --git a/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java b/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java index a123924..6bc816c 100644 --- a/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java +++ b/src/test/java/com/ing/data/cassandra/jdbc/VectorsDseContainerTest.java @@ -17,27 +17,23 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** - * Test CQL Vector data type + * Test CQL Vector data type using DataStax Enterprise. */ class VectorsDseContainerTest extends UsingDseContainerTest { private static final String KEYSPACE = "test_keyspace_vect"; @BeforeAll - static void setup() throws Exception { - initializeContainer("7.0.0-a"); + static void finalizeSetUpTests() throws Exception { + initializeContainer(); initConnection(KEYSPACE, "version=3.0.0", "localdatacenter=datacenter1"); } @Test void givenVectorTable_whenSimilaritySearch_shouldReturnResults() throws Exception { // When - final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement("" + + final CassandraPreparedStatement prepStatement = sqlConnection.prepareStatement( "SELECT\n" + " product_id, product_vector,\n" + " similarity_dot_product(product_vector,[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]) as similarity\n" + diff --git a/src/test/resources/initEmbeddedDse.cql b/src/test/resources/initEmbeddedDse.cql index c5b41cd..8568e6e 100644 --- a/src/test/resources/initEmbeddedDse.cql +++ b/src/test/resources/initEmbeddedDse.cql @@ -20,6 +20,7 @@ VALUES ('pf1843','HealthyFresh - Chicken raw dog food',[1, 1, 1, 1, 1, 0, 0, 0, INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) VALUES ('pf1844','HealthyFresh - Beef raw dog food',[1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]); + INSERT INTO pet_supply_vectors (product_id, product_name, product_vector) VALUES ('pt0021','Dog Tennis Ball Toy',[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0]);