CLOVER🍀

That was when it all began.

Cassandra CQLでJDBC

ほんの、触りだけです。

前にCQLを使っていて、すごい低レイヤのコードを書いていたわけですが、なんかJDBCドライバが存在するっぽいです。

cassandra-jdbc
http://code.google.com/a/apache-extras.org/p/cassandra-jdbc/

というかですね、ネットに転がってるサンプルとかを見てると普通にJDBCドライバがorg.apache.cassandra.cql.jdbcパッケージにいたようなのですが、最近のCassandraではいなくなっています。

何かあったのかなぁ…。

んで、前述のcassandra-jdbcを軽く触ってみました。

Maven Central Repositoryにも登録されています。

まずは普通にJDBCで。

@GrabConfig(systemClassLoader = true)
@Grab('org.apache.cassandra:cassandra-all:1.2.4')
@Grab('org.apache-extras.cassandra-jdbc:cassandra-jdbc:1.2.1')
import java.sql.DriverManager

Class.forName('org.apache.cassandra.cql.jdbc.CassandraDriver')
def conn = DriverManager.getConnection('jdbc:cassandra://localhost:9160/cqldemo')

try {
    def ps =
        conn.prepareStatement('SELECT isbn13, price, publish_date, title FROM books WHERE isbn13 = ?')
    ps.setString(1, '978-4798128436')

    def rs = ps.executeQuery()
    while (rs.next()) {
        def isbn13 = rs.getString('isbn13')  // rs.getString(1)とかでもOK
        def price = rs.getInt('price')
        def publishDate = rs.getTimestamp('publish_date')
        def title = rs.getString('title')

        println("isbn13 = $isbn13, price = $price, publishDate = $publishDate, title = $title")
    }
} finally {
    conn.close()
}

対象のテーブルとデータはこんな感じです。cqlshで確認しています。

cqlsh:cqldemo> DESCRIBE TABLE books;

CREATE TABLE books (
  isbn13 text PRIMARY KEY,
  price int,
  publish_date timestamp,
  title text
) WITH
  bloom_filter_fp_chance=0.010000 AND
  caching='KEYS_ONLY' AND
  comment='' AND
  dclocal_read_repair_chance=0.000000 AND
  gc_grace_seconds=864000 AND
  read_repair_chance=0.100000 AND
  replicate_on_write='true' AND
  populate_io_cache_on_flush='false' AND
  compaction={'class': 'SizeTieredCompactionStrategy'} AND
  compression={'sstable_compression': 'SnappyCompressor'};

cqlsh:cqldemo> SELECT * FROM books;

 isbn13         | price | publish_date             | title
----------------+-------+--------------------------+-----------------------------------------
 978-4798128436 |  3360 | 2013-01-16 00:00:00+0900 | Cassandra実用システムインテグレーション
 978-4873115290 |  3570 | 2011-12-24 00:00:00+0900 |                               Cassandra

JDBCのURLは、データベース名にあたる部分にはキースペースの名前を設定します。

def conn = DriverManager.getConnection('jdbc:cassandra://localhost:9160/cqldemo')

ポート番号は、Thriftで接続する際のものでOKなようです。なぜか、JDBCドライバのサイトだと9170になっていましたが…。

いきなりPreparedStatmentを使用してみましたが、とりあえず動作しました。

isbn13 = 978-4798128436, price = 3360, publishDate = 2013-01-16 00:00:00.0, title = Cassandra実用システムインテグレーション

ちなみに、WHERE句が使えるとはいえ、プライマリキー以外の項目をWHERE句に付けると怒られます。

cqlsh:cqldemo> SELECT * FROM books WHERE price > 3000;
Bad Request: No indexed columns present in by-columns clause with Equal operator
Perhaps you meant to use CQL 2? Try using the -2 option when starting cqlsh.

Thrift APIでセカンダリインデックスが必要だったように、こちらもインデックスを貼らなくちゃいけないということなんでしょうね。

で、せっかくなのでGroovyのJDBC拡張を使っても試してみました。

import groovy.sql.Sql

Sql.withInstance('jdbc:cassandra://localhost:9160/cqldemo',
                 'org.apache.cassandra.cql.jdbc.CassandraDriver') { sql ->
    sql.eachRow('SELECT isbn13, price, publish_date, title FROM books',
                { println("row -> $it") })

これ、動くには動いたのですが

row -> [isbn13:978-4798128436, price:3360, publish_date:Wed Jan 16 00:00:00 JST 2013, title:Cassandra実用システムインテグレーション]
row -> [isbn13:978-4873115290, price:3570, publish_date:Sat Dec 24 00:00:00 JST 2011, title:Cassandra]

PreparedStatementにすると、途端にコケます。

Sql.withInstance('jdbc:cassandra://localhost:9160/cqldemo',
                 'org.apache.cassandra.cql.jdbc.CassandraDriver') { sql ->
    try {
        sql.eachRow('SELECT isbn13, price, publish_date, title FROM books WHERE isbn13 = ?',
                    ['978-4873115290'],
                    { println("row -> $it") })
    } catch (e) {
        e.printStackTrace()
        // 5 06, 2013 8:27:27 午後 groovy.sql.Sql eachRow
        // WARNING: Failed to execute: SELECT isbn13, price, publish_date, title FROM books WHERE isbn13 = ? because: the Cassandra implementation does not support this method
        // java.sql.SQLFeatureNotSupportedException: the Cassandra implementation does not support this method
        //     at org.apache.cassandra.cql.jdbc.CassandraConnection.prepareStatement(CassandraConnection.java:365)
        //     at groovy.sql.Sql$CreatePreparedStatementCommand.execute(Sql.java:3996)
        //     at groovy.sql.Sql$CreatePreparedStatementCommand.execute(Sql.java:3978)
        //     at groovy.sql.Sql.getAbstractStatement(Sql.java:3858)
        //     at groovy.sql.Sql.getPreparedStatement(Sql.java:3873)
        //     at groovy.sql.Sql.getPreparedStatement(Sql.java:3921)
        //     at groovy.sql.Sql.eachRow(Sql.java:1241)
        //     at groovy.sql.Sql.eachRow(Sql.java:1329)
        //     at groovy.sql.Sql.eachRow(Sql.java:1383)
    }
}

Connectionの実装で、以下のメソッドが未実装だからなようです…。

PreparedStatement prepareStatement(String sql,
                                   int resultSetType,
                                   int resultSetConcurrency)
                                   throws SQLException

http://code.google.com/a/apache-extras.org/p/cassandra-jdbc/source/browse/src/main/java/org/apache/cassandra/cql/jdbc/CassandraConnection.java

    public PreparedStatement prepareStatement(String arg0, int arg1, int arg2) throws SQLException
    {
        throw new SQLFeatureNotSupportedException(NOT_SUPPORTED);
    }

とりあえず、触りだけでした。

そして、cassandra-jdbcはそもそもプロジェクト的にどういう位置付けなのでしょうか…?