diff --git a/README.md b/README.md index 84ac0e9..aa8dd7e 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ [![Maven Central](https://img.shields.io/maven-central/v/com.ing.data/cassandra-jdbc-wrapper)](https://search.maven.org/search?q=g:com.ing.data%20AND%20cassandra-jdbc-wrapper) [![Javadoc](https://javadoc.io/badge2/com.ing.data/cassandra-jdbc-wrapper/javadoc.svg)](https://javadoc.io/doc/com.ing.data/cassandra-jdbc-wrapper) -This is a JDBC wrapper of the DataStax Java Driver for Apache Cassandra (C*), which offers a simple JDBC compliant -API to work with CQL3. +This is a work-in-progress fork of [ing-bank/cassandra-jdbc-wrapper](https://github.com/ing-bank/cassandra-jdbc-wrapper/). + +This is a JDBC wrapper of the YugabyteDB Java Driver for YCQL, which offers a simple JDBC compliant API to work with CQL3. This JDBC wrapper is based on a fork of the project [adejanovski/cassandra-jdbc-wrapper](https://github.com/adejanovski/cassandra-jdbc-wrapper/). We would especially like @@ -42,7 +43,7 @@ This project requires Java 8 JDK (minimum). Clone the repository: ```bash -git clone https://github.com/ing-bank/cassandra-jdbc-wrapper.git +git clone https://github.com/yugabyte/cassandra-jdbc-wrapper.git ``` To compile and run tests, execute the following Maven command: @@ -52,21 +53,14 @@ mvn clean package ### Integration in Maven projects -You can install it in your application using the following Maven dependency: +This project has not been published on Maven yet. -```xml - - com.ing.data - cassandra-jdbc-wrapper - ${cassandra-jdbc-wrapper.version} - -``` ## Usage Connect to a Cassandra cluster using the following arguments: * JDBC driver class: `com.ing.data.cassandra.jdbc.CassandraDriver` -* JDBC URL: `jdbc:cassandra://host1--host2--host3:9042/keyspace?localdatacenter=DC1` (to connect to a DBaaS cluster, +* JDBC URL: `jdbc:cassandra://host1--host2--host3:9042/keyspace?localdatacenter=DC1&loadbalancing=PartitionAwarePolicy` (to connect to a DBaaS cluster, please read the section "[Connecting to DBaaS](#connecting-to-dbaas)"; to use a configuration file, please read the section "[Using a configuration file](#using-a-configuration-file)") @@ -85,7 +79,7 @@ Java example: public class HelloCassandra { public static void main(final String[] args) { // Used driver: com.ing.data.cassandra.cassandra.jdbc.CassandraDriver - final String url = "jdbc:cassandra://host1--host2--host3:9042/keyspace?localdatacenter=DC1"; + final String url = "jdbc:cassandra://host1--host2--host3:9042/keyspace?localdatacenter=DC1&loadbalancing=PartitionAwarePolicy"; final Connection connection = DriverManager.getConnection(url); } } diff --git a/pom.xml b/pom.xml index 59facaa..c3f58d1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,14 +3,14 @@ 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 + com.yugabyte cassandra-jdbc-wrapper - 4.6.0 + 4.6.1-yb-1-alpha1 jar - Cassandra JDBC Wrapper - JDBC wrapper of the DataStax Java Driver for Apache Cassandra. - https://github.com/ing-bank/cassandra-jdbc-wrapper + YugabyteDB YCQL JDBC Wrapper + JDBC wrapper of the YugabyteDB Java Driver for YCQL. + https://github.com/yugabyte/cassandra-jdbc-wrapper 2020 @@ -22,8 +22,8 @@ - ING Bank - https://www.ing.com + Yugabyte Inc + https://www.yugabyte.com @@ -62,9 +62,9 @@ - scm:git:https://github.com:ing-bank/cassandra-jdbc-wrapper.git - scm:git:git@github.com:ing-bank/cassandra-jdbc-wrapper.git - https://github.com/ing-bank/cassandra-jdbc-wrapper + scm:git:https://github.com:yugabyte/cassandra-jdbc-wrapper.git + scm:git:git@github.com:yugabyte/cassandra-jdbc-wrapper.git + https://github.com/yugabyte/cassandra-jdbc-wrapper @@ -108,9 +108,9 @@ - com.datastax.oss + com.yugabyte java-driver-core - ${datastax.java.driver.version} + 4.6.0-yb-11 @@ -195,6 +195,29 @@ + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.1 + + + + jar-with-dependencies + + + + + + make-assembly + package + + single + + + + + maven-clean-plugin 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 378e036..073fc9d 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java @@ -374,10 +374,10 @@ public int getTransactionIsolation() throws SQLException { @Override public void setTransactionIsolation(final int level) throws SQLException { - checkNotClosed(); - if (level != Connection.TRANSACTION_NONE) { - throw new SQLFeatureNotSupportedException(NO_TRANSACTIONS); - } +// checkNotClosed(); +// if (level != Connection.TRANSACTION_NONE) { +// throw new SQLFeatureNotSupportedException(NO_TRANSACTIONS); +// } } @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 f7d39fc..aa1e01f 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java @@ -819,6 +819,7 @@ public Object getObject(final int columnIndex) throws SQLException { switch (dataType) { case VARCHAR: case ASCII: + case JSONB: case TEXT: return this.currentRow.getString(columnIndex - 1); case INT: @@ -950,6 +951,7 @@ public Object getObject(final String columnLabel) throws SQLException { switch (dataType) { case VARCHAR: case ASCII: + case JSONB: case TEXT: return this.currentRow.getString(columnLabel); case INT: @@ -1592,7 +1594,7 @@ public boolean isSearchable(final int column) { @Override public boolean isSigned(final int column) { // TODO: implementation to review - return false; + return true; } @Override diff --git a/src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java b/src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java index 5db2f0e..8cec5ab 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java @@ -66,7 +66,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)), + JSONB(DataType.JSONB, String.class, cqlName(DataTypes.JSONB)); private static final Map CQL_DATATYPE_TO_DATATYPE; final int protocolId; 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 0757788..bf1241c 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java @@ -234,7 +234,9 @@ private Session createSession(final Properties properties) throws SQLException { } // The DefaultLoadBalancingPolicy requires to specify a local data center. - builder.withLocalDatacenter(localDatacenter); + if (!localDatacenter.trim().isEmpty()) { + builder.withLocalDatacenter(localDatacenter); + } if (loadBalancingPolicy.length() > 0) { // if a custom load balancing policy has been given in the JDBC URL, parse it and add it to the cluster // builder. diff --git a/src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java b/src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java index 8cb1f82..09f6133 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java @@ -72,6 +72,7 @@ public final class TypesMap { TYPES_MAP.put("org.apache.cassandra.db.marshal.uuid", JdbcUUID.INSTANCE); TYPES_MAP.put("org.apache.cassandra.db.marshal.varchar", JdbcUTF8.INSTANCE); TYPES_MAP.put("org.apache.cassandra.db.marshal.varint", JdbcInteger.INSTANCE); + TYPES_MAP.put("org.apache.cassandra.db.marshal.jsonb", JdbcUTF8.INSTANCE); } private TypesMap() { diff --git a/src/main/java/com/ing/data/cassandra/jdbc/Utils.java b/src/main/java/com/ing/data/cassandra/jdbc/Utils.java index 795e5d7..67037aa 100644 --- a/src/main/java/com/ing/data/cassandra/jdbc/Utils.java +++ b/src/main/java/com/ing/data/cassandra/jdbc/Utils.java @@ -185,6 +185,7 @@ public final class Utils { protected static final String FORWARD_ONLY = "Can not position cursor with a type of TYPE_FORWARD_ONLY."; protected static final String MALFORMED_URL = "The string '%s' is not a valid URL."; protected static final String SSL_CONFIG_FAILED = "Unable to configure SSL: %s."; + protected static boolean hasPort = false; static final Logger LOG = LoggerFactory.getLogger(Utils.class); @@ -192,6 +193,38 @@ private Utils() { // Private constructor to hide the public one. } + public static boolean isIPv6(String hostsStr) { + Pattern validIpv6Pattern = Pattern.compile("([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}", 2); + Pattern validIpv6WithPortPattern = Pattern.compile("([0-9a-f]{1,4}:){8}([0-9a-f]){1,4}", 2); + String[] hosts = hostsStr.split("--"); + int n = hosts.length; + boolean value = false; + + Matcher m2; + for (int i = 0; i < n - 1; ++i) { + m2 = validIpv6Pattern.matcher(hosts[i]); + value = m2.matches(); + if (!value) { + break; + } + } + + m2 = validIpv6WithPortPattern.matcher(hosts[n - 1]); + Matcher m3; + boolean value2; + if (m2.matches()) { + hasPort = true; + m3 = validIpv6Pattern.matcher(hosts[n - 1].substring(0, hosts[n - 1].lastIndexOf(":"))); + value2 = m3.matches(); + } else { + hasPort = false; + m3 = validIpv6Pattern.matcher(hosts[n - 1]); + value2 = m3.matches(); + } + + return value && value2; + } + /** * Parses a URL for the Cassandra JDBC Driver. *

@@ -212,6 +245,8 @@ public static Properties parseURL(final String url) throws SQLException { final Properties props = new Properties(); if (url != null) { + int port = DEFAULT_PORT; + String host; props.setProperty(TAG_PORT_NUMBER, String.valueOf(DEFAULT_PORT)); boolean isDbaasConnection = false; int uriStartIndex = PROTOCOL.length(); @@ -228,13 +263,30 @@ public static Properties parseURL(final String url) throws SQLException { } if (!isDbaasConnection) { - final String host = uri.getHost(); + host = uri.getHost(); + String[] hostsStr = rawUri.split("/"); if (host == null) { - throw new SQLNonTransientConnectionException(HOST_IN_URL); + if (!hostsStr[2].contains("--")) { + throw new SQLNonTransientConnectionException("Connection url must specify a host, e.g. jdbc:cassandra://localhost:9042/keyspace"); + } + + boolean ipv6 = isIPv6(hostsStr[2]); + if (!ipv6) { + if (hostsStr[2].contains(":")) { + host = hostsStr[2].substring(0, hostsStr[2].indexOf(58)); + port = Integer.parseInt(hostsStr[2].substring(hostsStr[2].indexOf(58) + 1)); + } else { + host = hostsStr[2]; + } + } else if (hasPort) { + host = hostsStr[2].substring(0, hostsStr[2].lastIndexOf(58)); + port = Integer.parseInt(hostsStr[2].substring(hostsStr[2].lastIndexOf(58) + 1)); + } else { + host = hostsStr[2]; + } } props.setProperty(TAG_SERVER_NAME, host); - int port = DEFAULT_PORT; if (uri.getPort() >= 0) { port = uri.getPort(); } @@ -333,7 +385,10 @@ public static String createSubName(final Properties props) throws SQLException { keyspace = StringUtils.prependIfMissing(keyspace, "/"); } - final String host = props.getProperty(TAG_SERVER_NAME); + String host = props.getProperty(TAG_SERVER_NAME); + if (host.contains("--")) { + host = host.split("--")[0]; + } if (host == null) { throw new SQLNonTransientConnectionException(HOST_REQUIRED); } 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..0e80833 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 @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; 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 com.datastax.oss.driver.internal.core.util.collection.QueryPlan; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -43,7 +43,7 @@ public void init(@NonNull final Map nodes, @NonNull final DistanceRe @Override public Queue newQueryPlan(@Nullable final Request request, @Nullable final Session session) { // Do nothing. For testing purpose only. - return new SimpleQueryPlan(mock(Node.class)); + return new QueryPlan(mock(Node.class)); } @Override 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..76fd46f 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 @@ -18,7 +18,7 @@ import com.datastax.oss.driver.api.core.metadata.Node; 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 com.datastax.oss.driver.internal.core.util.collection.QueryPlan; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -43,7 +43,7 @@ public void init(@NonNull final Map nodes, @NonNull final DistanceRe @Override public Queue newQueryPlan(@Nullable final Request request, @Nullable final Session session) { // Do nothing. For testing purpose only. - return new SimpleQueryPlan(mock(Node.class)); + return new QueryPlan(mock(Node.class)); } @Override diff --git a/src/test/java/com/ing/data/cassandra/jdbc/utils/JSONBTest.java b/src/test/java/com/ing/data/cassandra/jdbc/utils/JSONBTest.java new file mode 100644 index 0000000..f1b0cdd --- /dev/null +++ b/src/test/java/com/ing/data/cassandra/jdbc/utils/JSONBTest.java @@ -0,0 +1,46 @@ +package com.ing.data.cassandra.jdbc.utils; + +import java.sql.*; + +public class JSONBTest { + private static final String urlWithoutAuth = "jdbc:cassandra://127.0.0.1/system?localdatacenter=datacenter1"; + public static void main(String args[]) throws ClassNotFoundException, SQLException { +// Class.forName("com.wisecoders.dbschema.cassandra.JdbcDriver"); + /* + create keyspace test; + create table test.jsontable (id int primary key, col1 text, col2 varchar, details jsonb); + insert into test.jsontable (id, col1, col2, details) values (1, 'abc', 'xyz', '{ "name": "Macbeth", "author": { "first_name": "William", "last_name": "Shakespeare" }, "year": 1623, "editors": ["John", "Elizabeth", "Jeff"] }'); + */ + Class.forName("com.ing.data.cassandra.jdbc.CassandraDriver"); + Connection con = DriverManager.getConnection( urlWithoutAuth, "yugabyte", "yugabyte"); + Statement stmt = con.createStatement(); + stmt.execute("SELECT id, col1, col2, details FROM test.jsontable"); + ResultSet rs = stmt.getResultSet(); + while(rs.next()){ + System.out.println("getInt(1): " + rs.getInt(1)); +// System.out.println(rs.getObject(2)); +// System.out.println(rs.getObject(3)); + System.out.println("getObject(4): " + rs.getObject(4)); + System.out.println("getObject('details'): " + rs.getObject("details")); + ResultSetMetaData rmd = rs.getMetaData(); + System.out.println("colName: " + rmd.getColumnName(1)); + System.out.println("colType: " + rmd.getColumnType(1)); + System.out.println("colTypeName: " + rmd.getColumnTypeName(1)); + System.out.println("getLong(1): " + rs.getLong(1)); +// +// System.out.println(rmd.getColumnName(2)); +// System.out.println(rmd.getColumnType(2)); +// System.out.println(rmd.getColumnTypeName(2)); +// +// System.out.println(rmd.getColumnName(3)); +// System.out.println(rmd.getColumnType(3)); +// System.out.println(rmd.getColumnTypeName(3)); + + System.out.println("colName: " + rmd.getColumnName(4)); + System.out.println("colType: " + rmd.getColumnType(4)); + System.out.println("colTypeName: " + rmd.getColumnTypeName(4)); + } + System.out.println("executed query"); + con.close(); + } +}