Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit edfa8181da6aedcfd4c447b09a499260a30a416c @kreynolds committed Aug 15, 2011
@@ -0,0 +1,7 @@
+pkg/*
+*.gem
+.bundle
+coverage
+.DS_Store
+.rspec-tm
+doc
@@ -0,0 +1,4 @@
+source :gemcutter
+
+# Specify your gem's dependencies in cassandra-cql.gemspec
+gemspec
@@ -0,0 +1,34 @@
+PATH
+ remote: .
+ specs:
+ cassandra-cql (0.0.1)
+ cassandra-thrift (>= 0.0.1)
+ simple_uuid (>= 0.1.1)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ diff-lcs (1.1.2)
+ rcov (0.9.9)
+ rspec (2.6.0)
+ rspec-core (~> 2.6.0)
+ rspec-expectations (~> 2.6.0)
+ rspec-mocks (~> 2.6.0)
+ rspec-core (2.6.4)
+ rspec-expectations (2.6.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.6.0)
+ simple_uuid (0.1.1)
+ thrift (0.6.0)
+ thrift_client (0.6.3)
+ thrift (~> 0.6.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ bundler (>= 1.0.0)
+ cassandra-cql!
+ rcov (>= 0.9.9)
+ rspec (>= 2.6.0)
+ simple_uuid (>= 0.1.1)
@@ -0,0 +1,78 @@
+=Cassandra
+The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.
+
+=CQL
+Cassandra originally went with a Thrift RPC-based API as a way to provide a common denominator that more idiomatic clients could build upon independently.
+However, this worked poorly in practice: raw Thrift is too low-level to use productively, and keeping pace with new API methods to support (for example) indexes in 0.7 or distributed counters in 0.8 is too much for many maintainers to keep pace with.
+
+CQL, the Cassandra Query Language, addresses this by pushing all implementation details to the server; all the client has to know for any operation is how to interpret "resultset" objects.
+So adding a feature like counters just requires teaching the CQL parser to understand "column + N" notation; no client-side changes are necessary.
+
+(source: http://www.datastax.com/dev/blog/what’s-new-in-cassandra-0-8-part-1-cql-the-cassandra-query-language)
+
+=Quick Start
+
+==Establishing a connection
+
+ # Defaults to the system keyspace
+ db = CassandraCQL::Database.new('127.0.0.1:9160')
+
+ # Specifying a keyspace
+ db = CassandraCQL::Database.new('127.0.0.1:9160', {:keyspace => 'keyspace1'})
+
+ # Specifying more than one seed node
+ db = CassandraCQL::Database.new(['127.0.0.1:9160','127.0.0.2:9160'])
+
+==Creating a Keyspace
+
+ # Creating a simple keyspace with replication factor 1
+ db.execute("CREATE KEYSPACE keyspace1 WITH strategy_class='org.apache.cassandra.locator.SimpleStrategy' AND strategy_options:replication_factor=1")
+ db.execute("USE keyspace1")
+
+==Creating a Column Family
+
+ # Creating a column family with a single validated column
+ db.execute("CREATE COLUMNFAMILY users (id varchar PRIMARY KEY, email varchar)")
+
+ # Create an index on the name
+ db.execute("CREATE INDEX users_email_idx ON users (email)")
+
+==Inserting into a Column Family
+
+ # Insert without bound variables
+ db.execute("INSERT INTO users (id, email) VALUES ('kreynolds', 'kelley@insidesystems.net')")
+
+ # Insert with bound variables
+ db.execute("INSERT INTO users (id, email) VALUES (?, ?)", ['kway', 'kevin@insidesystems.net'])
+
+==Updating a Column Family
+
+ # Update
+ db.execute("UPDATE users SET email=? WHERE id=?", ['kreynolds@insidesystems.net', 'kreynolds'])
+
+==Selecting from a Column Family
+
+ # Select all
+ db.execute("SELECT * FROM users").fetch { |row| puts row.to_hash.inspect }
+ {"id"=>"kway", "email"=>"kevin@insidesystems.net"}
+ {"id"=>"kreynolds", "email"=>"kreynolds@insidesystems.net"}
+
+ # Select just one user by id
+ db.execute("SELECT * FROM users WHERE id=?", ['kreynolds']).fetch { |row| puts row.to_hash.inspect }
+ {"id"=>"kreynolds", "email"=>"kreynolds@insidesystems.net"}
+
+ # Select just one user by indexed column
+ db.execute("SELECT * FROM users WHERE email=?", ['kreynolds@insidesystems.net']).fetch { |row| puts row.to_hash.inspect }
+ {"id"=>"kreynolds", "email"=>"kreynolds@insidesystems.net"}
+
+==Deleting from a Column Family
+
+ # Delete the swarthy bastard Kevin
+ db.execute("DELETE FROM users WHERE id=?", ['kway'])
+
+=TODO
+
+ 1. Make bind_var usage friendlier
+ 2. Determine if returning Time objects for TimeUUID is appropriate in all cases and if not, add an option
+ 3. Add yardoc
+ 4. Add spec for database
@@ -0,0 +1,22 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
+
+require 'rake'
+require 'rspec/core'
+require 'rspec/core/rake_task'
+RSpec::Core::RakeTask.new(:spec) do |spec|
+ spec.skip_bundler = true
+ spec.pattern = FileList['spec/**/*_spec.rb']
+end
+
+RSpec::Core::RakeTask.new(:rcov) do |spec|
+ spec.skip_bundler = true
+ spec.pattern = FileList['spec/**/*_spec.rb']
+ spec.rcov = true
+ spec.rcov_opts = "--exclude 'spec/*'"
+end
+
+task :default => :spec
+
+require 'yard'
+YARD::Rake::YardocTask.new
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+require File.expand_path("../lib/cassandra-cql/version", __FILE__)
+
+Gem::Specification.new do |s|
+ s.name = "cassandra-cql"
+ s.version = CassandraCQL::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Kelley Reynolds"]
+ s.email = ["kelley@insidesystems.net"]
+ s.homepage = "http://rubygems.org/gems/cassandra-cql"
+ s.summary = "CQL Interface to Cassandra"
+ s.description = "CQL Interface to Cassandra"
+
+ s.required_rubygems_version = ">= 1.3.6"
+ s.rubyforge_project = "cassandra-cql"
+
+ s.add_development_dependency "bundler", ">= 1.0.0"
+ s.add_development_dependency "rcov", ">= 0.9.9"
+ s.add_development_dependency "rspec", ">= 2.6.0"
+ s.add_dependency "simple_uuid", ">= 0.1.1"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
+ s.require_path = 'lib'
+end
@@ -0,0 +1,11 @@
+here = File.dirname(__FILE__)
+$LOAD_PATH << "#{here}/../vendor/gen-rb"
+require "#{here}/../vendor/gen-rb/cassandra"
+
+require 'cassandra-cql/utility'
+require 'simple_uuid'
+require 'cassandra-cql/database'
+require 'cassandra-cql/schema'
+require 'cassandra-cql/statement'
+require 'cassandra-cql/result'
+require 'cassandra-cql/row'
@@ -0,0 +1,90 @@
+module CassandraCQL
+ module Error
+ class InvalidRequestException < Exception; end
+ end
+
+ class Database
+ attr_reader :connection, :schema, :keyspace
+
+ def initialize(servers, options={}, thrift_client_options={})
+ @options = {
+ :keyspace => 'system'
+ }.merge(options)
+
+ @thrift_client_options = {
+ :exception_classes_without_disconnect => CassandraThrift::InvalidRequestException
+ }.merge(thrift_client_options)
+
+ @keyspace = @options[:keyspace]
+ @servers = servers
+ connect!
+ execute("USE #{@keyspace}")
+ end
+
+ def connect!
+ @connection = ThriftClient.new(CassandraThrift::Cassandra::Client, @servers, @thrift_client_options)
+ obj = self
+ @connection.add_callback(:post_connect) do
+ execute("USE #{@keyspace}")
+ end
+ end
+
+ def disconnect!
+ @connection.disconnect! if active?
+ end
+
+ def active?
+ # TODO: This should be replaced with a CQL call that doesn't exist yet
+ @connection.describe_version
+ true
+ rescue Exception
+ false
+ end
+ alias_method :ping, :active?
+
+ def reset!
+ disconnect!
+ reconnect!
+ end
+ alias_method :reconnect!, :reset!
+
+ def prepare(statement, options={}, &block)
+ stmt = Statement.new(self, statement)
+ if block_given?
+ yield stmt
+ else
+ stmt
+ end
+ end
+
+ def execute(statement, bind_vars=[], &block)
+ result = Statement.new(self, statement).execute(bind_vars)
+ if block_given?
+ yield result
+ else
+ result
+ end
+ rescue CassandraThrift::InvalidRequestException
+ raise Error::InvalidRequestException.new($!.why)
+ end
+
+ def execute_cql_query(cql, compression=CassandraThrift::Compression::NONE)
+ @connection.execute_cql_query(cql, compression)
+ rescue CassandraThrift::InvalidRequestException
+ raise Error::InvalidRequestException.new($!.why)
+ end
+
+ def keyspace=(ks)
+ @keyspace = (ks.nil? ? nil : ks.to_s)
+ end
+
+ def update_schema!
+ if @keyspace.nil?
+ @schema = nil
+ else
+ # TODO: This should be replaced with a CQL call that doesn't exist yet
+ @schema = Schema.new(@connection.describe_keyspace(@keyspace))
+ end
+ end
+ end
+end
@@ -0,0 +1,101 @@
+module CassandraCQL
+ module Error
+ class InvalidResultType < Exception; end
+ class InvalidCursor < Exception; end
+ end
+
+ class Result
+ attr_reader :result, :column_family, :cursor
+
+ def initialize(result, column_family=nil)
+ @result, @column_family = result, column_family
+ @column_family = @column_family.dup unless @column_family.nil?
+ @cursor = 0
+ end
+
+ def void?
+ @result.type == CassandraThrift::CqlResultType::VOID
+ end
+
+ def int?
+ @result.type == CassandraThrift::CqlResultType::INT
+ end
+
+ def rows?
+ @result.type == CassandraThrift::CqlResultType::ROWS
+ end
+
+ def rows
+ @result.rows.size
+ end
+
+ def cursor=(cursor)
+ @cursor = cursor.to_i
+ rescue Exception => e
+ raise Error::InvalidCursor, e.to_s
+ end
+
+ def fetch_row
+ case @result.type
+ when CassandraThrift::CqlResultType::ROWS
+ return nil if @cursor >= rows
+
+ row = Row.new(@result.rows[@cursor], @column_family)
+ @cursor += 1
+ return row
+ when CassandraThrift::CqlResultType::VOID
+ return nil
+ when CassandraThrift::CqlResultType::INT
+ return @result.num
+ else
+ raise Error::InvalidResultType, "Expects one of 0, 1, 2; was #{@result.type} "
+ end
+ end
+
+ def fetch(&block)
+ if block_given?
+ while row = fetch_row
+ yield row
+ end
+ else
+ fetch_row
+ end
+ end
+
+ def fetch_hash(&block)
+ if block_given?
+ while row = fetch_row
+ if row.kind_of?(Fixnum)
+ yield({row => row})
+ else
+ yield row.to_hash
+ end
+ end
+ else
+ if (row = fetch_row).kind_of?(Fixnum)
+ {row => row}
+ else
+ row.to_hash
+ end
+ end
+ end
+
+ def fetch_array(&block)
+ if block_given?
+ while row = fetch_row
+ if row.kind_of?(Fixnum)
+ yield [row]
+ else
+ yield row.to_a
+ end
+ end
+ else
+ if (row = fetch_row).kind_of?(Fixnum)
+ [row]
+ else
+ row.to_a
+ end
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit edfa818

Please sign in to comment.