Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions lib/cassava/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ def initialize(session, opts = {})
# @see #insert
def insert_async(table, data)
ttl = data.delete(:ttl)
optional_timestamp = data.delete(:optional_timestamp)
consistency = data.delete(:consistency)
statement = insert_statement(table, data, ttl)

statement = insert_statement(table, data, ttl, optional_timestamp)

if consistency.nil?
executor.execute_async(statement, :arguments => data.values)
else
Expand All @@ -26,8 +29,11 @@ def insert_async(table, data)
# @param data [Hash] A hash of column names to data, which will be inserted into the table
def insert(table, data)
ttl = data.delete(:ttl)
optional_timestamp = data.delete(:optional_timestamp)
consistency = data.delete(:consistency)
statement = insert_statement(table, data, ttl)

statement = insert_statement(table, data, ttl, optional_timestamp)

if consistency.nil?
executor.execute(statement, :arguments => data.values)
else
Expand All @@ -50,11 +56,16 @@ def select_ttl(table, target_attr, where_arguments)
executor.execute(prepared_statement, :arguments => where_arguments.values).rows.first["ttl(#{target_attr})"]
end

def select_writetime(table, target_attr, where_arguments)
prepared_statement = select_writetime_statement(table, target_attr, where_arguments)
executor.execute(prepared_statement, :arguments => where_arguments.values).rows.first["writetime(#{target_attr})"]
end

# @param table [Symbol] the table name
# @param columns [Array<String] A list of columns that will be deleted. If nil, all columns will be deleted.
# @return [StatementBuilder] A statement builder representing the partially completed statement.
def delete(table, columns = nil)
StatementBuilder.new(executor).delete(table, columns)
def delete(table, columns = nil, timestamp = nil)
StatementBuilder.new(executor).delete(table, columns, timestamp)
end

# Pass a raw query to execute asynchronously to the underlying session object.
Expand All @@ -73,10 +84,17 @@ def execute(statement, opts = {})

private

def insert_statement(table, data, ttl = nil)
def insert_statement(table, data, ttl = nil, optional_timestamp = nil)
column_names = data.keys
statement_cql = "INSERT INTO #{table} (#{column_names.join(', ')}) VALUES (#{column_names.map { |x| '?' }.join(',')})"
statement_cql += " USING TTL #{ttl}" if ttl

if ttl && optional_timestamp
statement_cql += " USING TTL #{ttl} AND TIMESTAMP #{optional_timestamp}"
elsif ttl
statement_cql += " USING TTL #{ttl} "
elsif optional_timestamp
statement_cql += " USING TIMESTAMP #{optional_timestamp}"
end

executor.prepare(statement_cql)
end
Expand All @@ -89,6 +107,15 @@ def select_ttl_statement(table, target_attr, where_arguments)
statement_cql += where_arguments.keys.map { |x| "#{x} = ? " }.join(" AND ")
executor.prepare(statement_cql)
end

# @param table [Symbol] the table name
# @param target_attr [Symbol] The attribute to select the write time(timestamp) for
# @param where_arguments [Hash] Pairs of keys and values for the where clause
def select_writetime_statement(table, target_attr, where_arguments)
statement_cql = "SELECT WRITETIME(#{target_attr}) FROM #{table} WHERE "
statement_cql += where_arguments.keys.map { |x| "#{x} = ? " }.join(" AND ")
executor.prepare(statement_cql)
end
end

class StatementBuilder
Expand Down Expand Up @@ -133,8 +160,8 @@ def select(table, columns = nil)
# @param table [Symbol] table to delete data from
# @param columns [Array<Symbol>] Columns to delete -- defaults to all.
# @return [StatementBuilder]
def delete(table, columns = nil)
add_clause(DeleteClause.new(table, columns), :main)
def delete(table, columns = nil, timestamp=nil)
add_clause(DeleteClause.new(table, columns, timestamp), :main)
end

# Condition the query based on a condition
Expand Down Expand Up @@ -215,10 +242,14 @@ def to_s
end
end

DeleteClause = Struct.new(:table, :columns) do
DeleteClause = Struct.new(:table, :columns, :timestamp) do
def to_s
if columns
if columns && timestamp
"DELETE #{columns.join(', ')} from #{table} USING TIMESTAMP #{timestamp}"
elsif columns
"DELETE #{columns.join(', ')} from #{table}"
elsif timestamp
"DELETE from #{table} USING TIMESTAMP #{timestamp}"
else
"DELETE from #{table}"
end
Expand Down
2 changes: 1 addition & 1 deletion lib/cassava/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Cassava
VERSION = "0.1.4"
VERSION = "0.1.5"
end
48 changes: 47 additions & 1 deletion test/cassava/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,31 @@ def string_keys(hash)
item = { :id => 'i', :a => 1, :b => 'b', :c => "'\"item(", :d => 1, :ttl => ttl }
@client.insert(:test, item)

assert @client.send(:insert_statement, :test, item, ttl).cql =~ /\sUSING\sTTL\s#{ttl}$/
assert @client.send(:insert_statement, :test, item, ttl).cql =~ /\sUSING\sTTL\s#{ttl}/
assert_equal string_keys(item), @client.select(:test).execute.first
end

should 'allow the insertion with a timestamp' do
timestamp = Time.now.to_i * 1000000 + Time.now.usec
item = { :id => 'i', :a => 1, :b => 'b', :c => "'\"item(", :d => 1, :optional_timestamp => timestamp }
@client.insert(:test, item)

assert @client.send(:insert_statement, :test, item, nil, timestamp).cql =~ /\sUSING\sTIMESTAMP\s#{timestamp}/
saved_timestamp = @client.select_writetime(:test, :d, { :id => 'i' })
assert_equal timestamp, saved_timestamp
end

should 'allow the insertion of a ttl and a timestamp' do
ttl = 12345
timestamp = Time.now.to_i * 1000000 + Time.now.usec
item = { :id => 'i', :a => 1, :b => 'b', :c => "'\"item(", :d => 1, :ttl => ttl, :optional_timestamp => timestamp }
@client.insert(:test, item)

assert @client.send(:insert_statement, :test, item, ttl, timestamp).cql =~ /\sUSING\sTTL\s#{ttl}\sAND\sTIMESTAMP\s#{timestamp}/
assert_equal string_keys(item), @client.select(:test).execute.first
saved_timestamp = @client.select_writetime(:test, :d, { :id => 'i' })
assert_equal timestamp, saved_timestamp
end
end

context 'select' do
Expand Down Expand Up @@ -180,6 +202,21 @@ def string_keys(hash)
end
end

context 'select_writetime' do
should 'build the correct writetime select statement' do
statement = @client.send(:select_writetime_statement, :test, :d, { :id => 'i' })
assert_match /SELECT WRITETIME/, statement.cql
end

should 'correctly fetch the timestamp of a given column' do
item = { :id => 'i', :a => 1, :b => 'b', :c => "item", :d => 1 }
@client.insert(:test, item)

timestamp = @client.select_writetime(:test, :d, { :id => 'i' })
assert timestamp
end
end

context 'delete' do
setup do
@client.insert(:test, :id => 'i', :a => 2, :b => 'a', :c => '1', :d => 1)
Expand All @@ -205,6 +242,15 @@ def string_keys(hash)
assert_nil items.first['d']
end

should 'delete with a timestamp' do
timestamp = Time.now.to_i * 1000000 + Time.now.usec
assert @client.delete(:test, [:c, :d], timestamp).where(:id => 'i', :a => 2, :b => 'a').statement =~ /\sUSING\sTIMESTAMP\s#{timestamp}/
@client.delete(:test, [:c, :d]).where(:id => 'i', :a => 2, :b => 'a').execute
items = @client.select(:test).where(:id => 'i', :a => 2).execute
assert_nil items.first['c']
assert_nil items.first['d']
end

context 'hash arguments' do
should 'allow single and double quotes in the value' do
# no error raised
Expand Down