Skip to content

Commit

Permalink
YugabyteDB specific fixes including JSONB support
Browse files Browse the repository at this point in the history
  • Loading branch information
ashetkar committed Sep 5, 2022
2 parents 497d543 + 36d9c20 commit 5f45494
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 40 deletions.
20 changes: 7 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
<dependency>
<groupId>com.ing.data</groupId>
<artifactId>cassandra-jdbc-wrapper</artifactId>
<version>${cassandra-jdbc-wrapper.version}</version>
</dependency>
```

## 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)")

Expand All @@ -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);
}
}
Expand Down
47 changes: 35 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.ing.data</groupId>
<groupId>com.yugabyte</groupId>
<artifactId>cassandra-jdbc-wrapper</artifactId>
<version>4.6.0</version>
<version>4.6.1-yb-1-alpha1</version>
<packaging>jar</packaging>

<name>Cassandra JDBC Wrapper</name>
<description>JDBC wrapper of the DataStax Java Driver for Apache Cassandra.</description>
<url>https://github.com/ing-bank/cassandra-jdbc-wrapper</url>
<name>YugabyteDB YCQL JDBC Wrapper</name>
<description>JDBC wrapper of the YugabyteDB Java Driver for YCQL.</description>
<url>https://github.com/yugabyte/cassandra-jdbc-wrapper</url>
<inceptionYear>2020</inceptionYear>

<licenses>
Expand All @@ -22,8 +22,8 @@
</licenses>

<organization>
<name>ING Bank</name>
<url>https://www.ing.com</url>
<name>Yugabyte Inc</name>
<url>https://www.yugabyte.com</url>
</organization>

<developers>
Expand Down Expand Up @@ -62,9 +62,9 @@
</contributors>

<scm>
<connection>scm:git:https://github.com:ing-bank/cassandra-jdbc-wrapper.git</connection>
<developerConnection>scm:git:git@github.com:ing-bank/cassandra-jdbc-wrapper.git</developerConnection>
<url>https://github.com/ing-bank/cassandra-jdbc-wrapper</url>
<connection>scm:git:https://github.com:yugabyte/cassandra-jdbc-wrapper.git</connection>
<developerConnection>scm:git:git@github.com:yugabyte/cassandra-jdbc-wrapper.git</developerConnection>
<url>https://github.com/yugabyte/cassandra-jdbc-wrapper</url>
</scm>

<distributionManagement>
Expand Down Expand Up @@ -108,9 +108,9 @@

<dependencies>
<dependency>
<groupId>com.datastax.oss</groupId>
<groupId>com.yugabyte</groupId>
<artifactId>java-driver-core</artifactId>
<version>${datastax.java.driver.version}</version>
<version>4.6.0-yb-11</version>
</dependency>

<!-- Include Guava following the removal of cassandra-all dependency -->
Expand Down Expand Up @@ -195,6 +195,29 @@
</resources>

<plugins>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>

<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>

<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>

</plugin>
<!-- Cleaning -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/ing/data/cassandra/jdbc/DataTypeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, DataTypeEnum> CQL_DATATYPE_TO_DATATYPE;
final int protocolId;
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/ing/data/cassandra/jdbc/SessionHolder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/ing/data/cassandra/jdbc/TypesMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
63 changes: 59 additions & 4 deletions src/main/java/com/ing/data/cassandra/jdbc/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,46 @@ 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);

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.
* <p>
Expand All @@ -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();
Expand All @@ -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();
}
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -43,7 +43,7 @@ public void init(@NonNull final Map<UUID, Node> nodes, @NonNull final DistanceRe
@Override
public Queue<Node> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -43,7 +43,7 @@ public void init(@NonNull final Map<UUID, Node> nodes, @NonNull final DistanceRe
@Override
public Queue<Node> 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
Expand Down
46 changes: 46 additions & 0 deletions src/test/java/com/ing/data/cassandra/jdbc/utils/JSONBTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 5f45494

Please sign in to comment.