cassava_rb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 608975429084fed43ea970713fc2ba91497d061b
4
+ data.tar.gz: 8c782e434e9172dd8d9a23ac286186de9ba155b4
5
+ SHA512:
6
+ metadata.gz: 2116e9edaff4ac4ef6d91f105a41028b1d217b57f689f26ee96594792dff87c68ec201f28f4fe062d2819fa719d60726a19011c0e1a3de2b06e2a761ae18c8ba
7
+ data.tar.gz: b027b99912c044a853d0d47bc76308df2714dfba43da863ff866f63d7d603f47c16b14a6251993da834c41e8cb49d8142166e0e359bde53eac84fd4d4924b85c
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cassava.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem "pry"
8
+ gem "awesome_print"
9
+ gem 'm', :git => 'git@github.com:ANorwell/m.git', :branch => 'minitest_5'
10
+ end
11
+
12
+ group :test do
13
+ gem 'minitest_should', :git => 'git@github.com:citrus/minitest_should.git'
14
+ gem "mocha"
15
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Datto
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Cassava
2
+
3
+ [![Code Climate](https://codeclimate.com/github/backupify/cassava/badges/gpa.svg)](https://codeclimate.com/github/backupify/cassava)
4
+
5
+ An unopinionated Cassandra client built on top of the Datastax Cassandra Driver. Cassava provides a higher-level statement execution interface while still supporting asynchronous queries and the ability to connect to multiple clusters.
6
+
7
+ _If prepared incorrectly, the cassava plant can produce cyanide, a deadly compound when consumed._
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'cassava_rb', github: 'backupify/cassava'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install cassava_rb
24
+
25
+ ## Usage
26
+
27
+ Creating a client requires a `Cassandra::Session` object:
28
+
29
+ ```ruby
30
+ require 'cassava'
31
+ cluster = Cassandra.cluster
32
+ session = cluster.connect('a_keyspace')
33
+ client = Cassava::Client.new(session)
34
+ ```
35
+
36
+ ### Insert
37
+
38
+ ```ruby
39
+ client.insert(:table, :id => 'id', :a => 1, :b => 'b')
40
+
41
+ ```
42
+
43
+ ### Select
44
+
45
+ A select statement is built and then executed. To create a statement that will
46
+ select all columns:
47
+
48
+ ```ruby
49
+ statement = client.select(:table)
50
+ ```
51
+
52
+ This statement can then be further refined:
53
+
54
+ ```ruby
55
+ new_statement = statement.where(:id => 'id').limit(2)
56
+ ```
57
+
58
+ and then executed:
59
+
60
+ ```ruby
61
+ result = new_statement.execute
62
+ ```
63
+
64
+ or executed asynchronously:
65
+
66
+ ```ruby
67
+ promise = new_statement.execute_async
68
+ ```
69
+
70
+ To select only certain rows, provide those rows to the select method:
71
+
72
+ ```ruby
73
+ client.select(:table, [:id, :a, :b]).execute
74
+ ```
75
+
76
+ Ordering can be specified using the order method:
77
+
78
+ ```ruby
79
+ client.select(:table).where('id = ? AND a > ?', 1, 'b').order(:a, :desc).execute
80
+ ```
81
+
82
+ Filtering is permitting with the `allow_filtering` method.
83
+
84
+ Multiple records can be specified by passing an array of values, but this will generate an CQL IN query and should be used with caution:
85
+
86
+ ```ruby
87
+ client.select(:table).where(:id => 1, :a => [1, 2])
88
+ ```
89
+
90
+ ### Delete
91
+
92
+ To delete an entire record:
93
+
94
+ ```ruby
95
+ client.delete(table).where(:id => 1, :a => 1).execute
96
+ ```
97
+
98
+ To delete only certain columns:
99
+
100
+ ```ruby
101
+ client.delete(table, [:c, :d]).where(:id => 1, :a => 1).execute
102
+ ```
103
+
104
+ Note here that :c and :d must not be part of the primary key.
105
+
106
+ ## Contributing
107
+
108
+ 1. Fork it ( https://github.com/backupify/cassava/fork )
109
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
110
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
111
+ 4. Push to the branch (`git push origin my-new-feature`)
112
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ t.libs.push 'test'
8
+ end
9
+
10
+ task :setup do
11
+ require 'bundler/setup'
12
+ Bundler.require(:default, :development)
13
+ end
14
+
15
+
16
+ task :console => [:setup] do
17
+ Pry.start
18
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cassava/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cassava_rb"
8
+ spec.version = Cassava::VERSION
9
+ spec.authors = ["Arron Norwell"]
10
+ spec.email = ["anorwell@datto.com"]
11
+ spec.summary = %q{An unopinionated wrapper for the datastax cassandra gem.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "minitest-reporters"
24
+
25
+ spec.add_dependency 'cassandra-driver'
26
+ end
data/lib/cassava.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'cassandra'
2
+ require "cassava/version"
3
+ require 'cassava/client'
4
+
5
+ module Cassava; end
@@ -0,0 +1,264 @@
1
+ module Cassava
2
+ class Client
3
+ attr_reader :session, :executor
4
+
5
+ # @param session [Cassandra::Session] The session object
6
+ # @option opts [Object] :logger responding to :debug, :info, :warn, :error, :fatal
7
+ def initialize(session, opts = {})
8
+ @session = session
9
+ logger = opts[:logger] || NullLogger.new
10
+ @executor = Executor.new(session, logger)
11
+ end
12
+
13
+ # @see #insert
14
+ def insert_async(table, data)
15
+ executor.execute_async(insert_statement(table, data), :arguments => data.values)
16
+ end
17
+
18
+ # @param table [Symbol] the table name
19
+ # @param data [Hash] A hash of column names to data, which will be inserted into the table
20
+ def insert(table, data)
21
+ statement = insert_statement(table, data)
22
+ executor.execute(statement, :arguments => data.values)
23
+ end
24
+
25
+ # @param table [Symbol] the table name
26
+ # @param columns [Array<Symbol>] An optional list of column names (as symbols), to only select those columns
27
+ # @return [StatementBuilder] A statement builder representing the partially completed statement.
28
+ def select(table, columns = nil)
29
+ StatementBuilder.new(executor).select(table, columns)
30
+ end
31
+
32
+ # @param table [Symbol] the table name
33
+ # @param columns [Array<String] A list of columns that will be deleted. If nil, all columns will be deleted.
34
+ # @return [StatementBuilder] A statement builder representing the partially completed statement.
35
+ def delete(table, columns = nil)
36
+ StatementBuilder.new(executor).delete(table, columns)
37
+ end
38
+
39
+ # Pass a raw query to execute synchronously to the underlying session object.
40
+ # @param statement[String] The statment to execute
41
+ # @param opts [Hash] options accepted by Cassandra::Session
42
+ def execute_async(statement, opts = {})
43
+ executor.execute_async(statement, opts)
44
+ end
45
+
46
+ # Pass a raw query to execute asynchronously to the underlying session object.
47
+ # @param statement [String] The statment to execute
48
+ # @param opts [Hash] options accepted by Cassandra::Session
49
+ def execute(statement, opts = {})
50
+ executor.execute(statement, opts)
51
+ end
52
+
53
+ private
54
+
55
+ def insert_statement(table, data)
56
+ column_names = data.keys
57
+ statement_cql = "INSERT INTO #{table} (#{column_names.join(', ')}) VALUES (#{column_names.map { |x| '?' }.join(',')})"
58
+ executor.prepare(statement_cql)
59
+ end
60
+ end
61
+
62
+ class StatementBuilder
63
+ attr_reader :executor, :table, :clauses
64
+
65
+ CLAUSE_ORDERING = {
66
+ :main => 0,
67
+ :from => 1,
68
+ :where => 2,
69
+ :order => 3,
70
+ :limit => 4,
71
+ :allow_filtering => 5
72
+ }
73
+
74
+ def initialize(executor, clauses = {})
75
+ @executor = executor
76
+ @table = table
77
+ @clauses = clauses
78
+ end
79
+
80
+ # Execute the statement synchronously
81
+ # @param opts [Hash] options accepted by Cassandra::Session
82
+ def execute(opts = {})
83
+ options = opts.dup.merge(:arguments => prepared_arguments)
84
+ executor.execute(prepared_statement, options)
85
+ end
86
+
87
+ # Execute the statement asynchronously
88
+ # @param opts [Hash] options accepted by Cassandra::Session
89
+ def execute_async(opts = {})
90
+ options = opts.dup.merge(:arguments => prepared_arguments)
91
+ executor.execute_async(prepared_statement, options)
92
+ end
93
+
94
+ # @param table [Symbol] table to select data from
95
+ # @param columns [Array<Symbol>] Columns to select -- defaults to all.
96
+ # @return [StatementBuilder]
97
+ def select(table, columns = nil)
98
+ add_clause(SelectClause.new(table, columns), :main)
99
+ end
100
+
101
+ # @param table [Symbol] table to delete data from
102
+ # @param columns [Array<Symbol>] Columns to delete -- defaults to all.
103
+ # @return [StatementBuilder]
104
+ def delete(table, columns = nil)
105
+ add_clause(DeleteClause.new(table, columns), :main)
106
+ end
107
+
108
+ # Condition the query based on a condition
109
+ # Provide either a String and a list of arguments, or a hash.
110
+ # @example
111
+ # statement.where('id = ? and field > ?', 1, 'a')
112
+ # @example
113
+ # statement.where(:id => 1, :field => 'x')
114
+ # @param args [Array] arguments representing the where condition
115
+ # @return [StatementBuilder]
116
+ def where(*args)
117
+ clause = clauses[:where] || WhereClause.new([], [])
118
+ add_clause(clause.where(*args), :where)
119
+ end
120
+
121
+ # Allow filtering for this query
122
+ # @return [StatementBuilder]
123
+ def allow_filtering
124
+ add_clause('ALLOW FILTERING', :allow_filtering)
125
+ end
126
+
127
+ # @param clustering_column [Symbol] clustering_column to order by
128
+ # @param direction [:asc|:desc] the direction to order by, defaults to :asc
129
+ # @return [StatementBuilder]
130
+ def order(clustering_column, direction = :asc)
131
+ add_clause("ORDER BY #{clustering_column.to_s} #{direction.to_s}", :order)
132
+ end
133
+
134
+ # @param n [Integer] maximum number of results to return
135
+ # @return [StatementBuilder]
136
+ def limit(n)
137
+ add_clause("LIMIT #{n.to_i}", :limit)
138
+ end
139
+
140
+ # Return the count of objects rather than the objects themselves
141
+ # @return [StatementBuilder]
142
+ def count
143
+ add_clause(clauses[:main].count, :main)
144
+ end
145
+
146
+ # @return [String] the CQL statement that this StatementBuilder represents
147
+ def statement
148
+ clauses.sort_by { |s| CLAUSE_ORDERING[s[0]] }.map { |s| s[1] }.join(' ')
149
+ end
150
+
151
+ private
152
+
153
+ def prepared_statement
154
+ executor.prepare(statement)
155
+ end
156
+
157
+ def prepared_arguments
158
+ clauses[:where] ? clauses[:where].arguments : []
159
+ end
160
+
161
+ # Adds a clause of a given type.
162
+ # @return [StatementBuilder] A new StatementBuilder with the added clause
163
+ def add_clause(clause, type)
164
+ clauses_copy = clauses.dup
165
+ clauses_copy[type] = clause
166
+ self.class.new(executor, clauses_copy)
167
+ end
168
+ end
169
+
170
+ SelectClause = Struct.new(:table, :columns, :count_boolean) do
171
+ def count
172
+ self.class.new(table, _columns = nil, _count_boolean = true)
173
+ end
174
+
175
+ def to_s
176
+ if count_boolean
177
+ "SELECT COUNT(*) FROM #{table}"
178
+ else
179
+ self.columns ||= ['*']
180
+ "SELECT #{columns.join(', ')} from #{table}"
181
+ end
182
+ end
183
+ end
184
+
185
+ DeleteClause = Struct.new(:table, :columns) do
186
+ def to_s
187
+ if columns
188
+ "DELETE #{columns.join(', ')} from #{table}"
189
+ else
190
+ "DELETE from #{table}"
191
+ end
192
+ end
193
+ end
194
+
195
+ WhereClause = Struct.new(:parts, :arguments) do
196
+ def where(*args)
197
+ new_parts = self.parts.dup || []
198
+ new_arguments = self.arguments.dup || []
199
+
200
+ case args[0]
201
+ when String
202
+ new_parts << args[0]
203
+ new_arguments += args[1..-1]
204
+ when Hash
205
+ new_parts += args[0].map { |key, value| "#{key} #{where_string(value)}" }
206
+ new_arguments += args[0].values.flatten
207
+ end
208
+ self.class.new(new_parts, new_arguments)
209
+ end
210
+
211
+ def to_s
212
+ "WHERE #{parts.join(' AND ')}"
213
+ end
214
+
215
+ private
216
+
217
+ def where_string(value)
218
+ case value
219
+ when Array
220
+ quoted_values = value.map { |v| type_quote(v) }
221
+ "IN(#{quoted_values.map { |_| '?' }.join(', ')})"
222
+ else "= ?"
223
+ end
224
+ end
225
+
226
+ def type_quote(value)
227
+ case value
228
+ when Numeric then value.to_s
229
+ when String then "'#{value}'"
230
+ end
231
+ end
232
+ end
233
+
234
+ Executor = Struct.new(:session, :logger) do
235
+ def execute(statement, opts = {})
236
+ log_statement(statement, opts)
237
+ session.execute(statement, opts)
238
+ rescue => e
239
+ log_error(e, statement, opts)
240
+ raise e
241
+ end
242
+
243
+ def execute_async(statement, opts = {})
244
+ log_statement(statement, opts)
245
+ session.execute_async(statement, opts)
246
+ end
247
+
248
+ def prepare(*args)
249
+ session.prepare(*args)
250
+ end
251
+
252
+ def log_statement(statement, opts)
253
+ logger.debug("Executing Cassandra request #{statement.to_s} with options #{opts}")
254
+ end
255
+
256
+ def log_error(e, statement, opts)
257
+ logger.debug("Error #{e} executing Cassandra request #{statement.to_s} with options #{opts}")
258
+ end
259
+ end
260
+
261
+ class NullLogger
262
+ def method_missing(*); end
263
+ end
264
+ end
@@ -0,0 +1,3 @@
1
+ module Cassava
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,195 @@
1
+ require_relative '../test_helper'
2
+
3
+ module Cassava
4
+ class ClientTest < Minitest::Should::TestCase
5
+ setup do
6
+ initialize_test_table
7
+ @session = session_for_keyspace
8
+ @client = Cassava::Client.new(@session)
9
+ end
10
+
11
+ def string_keys(hash)
12
+ Hash[hash.map { |k, v| [k.to_s, v] }]
13
+ end
14
+
15
+ context 'insert' do
16
+ should 'be able to insert an item' do
17
+ item = { :id => 'i', :a => 1, :b => 'b', :c => 'c', :d => 1}
18
+ @client.insert(:test, item)
19
+ assert_equal string_keys(item), @client.select(:test).execute.first
20
+ end
21
+
22
+ should 'insert an item with some fields missing' do
23
+ item = { :id => 'i', :a => 1, :b => 'b', :c => 'c'}
24
+ expected = item.merge(:d => nil)
25
+ @client.insert(:test, item)
26
+ assert_equal string_keys(expected), @client.select(:test).execute.first
27
+ end
28
+
29
+ should 'raise an error if the row key is missing' do
30
+ item = { :a => 1, :b => 'b', :c => 'c'}
31
+ assert_raises(Cassandra::Errors::InvalidError) { @client.insert(:test, item) }
32
+ end
33
+
34
+ should 'raise an error if a primary key part is missing' do
35
+ item = { :id => 'i', :a => 1, :c => 'c'}
36
+ assert_raises(Cassandra::Errors::InvalidError) { @client.insert(:test, item) }
37
+ end
38
+
39
+ should 'allow the insertion of columns with mismatched quotes' do
40
+ item = { :id => 'i', :a => 1, :b => 'b', :c => "'\"item(", :d => 1}
41
+ @client.insert(:test, item)
42
+ assert_equal string_keys(item), @client.select(:test).execute.first
43
+ end
44
+ end
45
+
46
+ context 'select' do
47
+
48
+ setup do
49
+ @client.insert(:test, :id => 'i', :a => 1, :b => 'b', :c => '1')
50
+ @client.insert(:test, :id => 'i', :a => 2, :b => 'a', :c => '1')
51
+ @client.insert(:test, :id => 'i', :a => 3, :b => 'c', :c => '1')
52
+ @client.insert(:test, :id => 'i2', :a => 4, :b => 'b', :c => '1')
53
+ end
54
+
55
+ should 'select all columns for all items' do
56
+ items = @client.select(:test).execute
57
+ assert_equal 4, items.count
58
+ assert_equal [1,2,3,4].to_set, items.map { |x| x['a'] }.to_set
59
+ assert_equal %w(id a b c d).to_set, items.first.keys.to_set
60
+ end
61
+
62
+ should 'select certain columns for all items' do
63
+ items = @client.select(:test, %w(id a c)).execute
64
+ assert_equal 4, items.count
65
+ assert_equal [1,2,3,4].to_set, items.map { |x| x['a'] }.to_set
66
+ assert_equal %w(id a c).to_set, items.first.keys.to_set
67
+ end
68
+
69
+ context 'where' do
70
+ should 'allow where clause' do
71
+ items = @client.select(:test).where(:id => 'i').execute
72
+ assert_equal [1,2,3].to_set, items.map { |x| x['a'] }.to_set
73
+ end
74
+
75
+ should 'allow string-based where clauses' do
76
+ items = @client.select(:test).where("id = 'i' and a > 1").execute
77
+ assert_equal [2,3].to_set, items.map { |x| x['a'] }.to_set
78
+ end
79
+
80
+ should 'allow string-based where clauses with arguments' do
81
+ items = @client.select(:test).where("id = ? and a > ?", 'i', 1).execute
82
+ assert_equal [2,3].to_set, items.map { |x| x['a'] }.to_set
83
+ end
84
+
85
+ should 'allow multiple where clauses to be chained' do
86
+ items = @client.select(:test).where(:id => 'i').where('a > 1').execute
87
+ assert_equal [2,3].to_set, items.map { |x| x['a'] }.to_set
88
+ end
89
+
90
+ should 'create an IN clause when a list of values is passed' do
91
+ items = @client.select(:test).where(:id => 'i', :a => 1, :b => %w(a b)).execute
92
+ assert_equal [1], items.map { |x| x['a'] }
93
+ end
94
+
95
+ context 'hash arguments' do
96
+ should 'allow single and double quotes in the value' do
97
+ items = @client.select(:test).where(:id => "'\"abc").execute
98
+ assert_equal [], items.to_a
99
+ end
100
+ end
101
+
102
+ context 'string arguments' do
103
+ should 'allow single and double quotes in the value' do
104
+ items = @client.select(:test).where('id = ?', "'\"abc").execute
105
+ assert_equal [], items.to_a
106
+ end
107
+ end
108
+ end
109
+
110
+ should 'order by ascending primary key by default' do
111
+ items = @client.select(:test).where(:id => 'i').execute
112
+ assert_equal [1,2,3], items.map { |x| x['a'] }
113
+ end
114
+
115
+ should 'allow order to specify ordering' do
116
+ items = @client.select(:test).where(:id => 'i').order(:a, :desc).execute
117
+ assert_equal [3, 2, 1], items.map { |x| x['a'] }
118
+ end
119
+
120
+ should 'allow limiting the result count' do
121
+ items = @client.select(:test).where(:id => 'i').limit(2).execute
122
+ assert_equal [1, 2], items.map { |x| x['a'] }
123
+ end
124
+
125
+ should 'not allow queries across multiple rows if allow_filtering is not set' do
126
+ assert_raises(Cassandra::Errors::InvalidError) { @client.select(:test).where(:a => 1).execute }
127
+ end
128
+
129
+ should 'allow queries across multiple rows if allow_filtering is set' do
130
+ items = @client.select(:test).where('a >= 3').allow_filtering.execute
131
+ assert_equal [3, 4].to_set, items.map { |x| x['a'] }.to_set
132
+ end
133
+
134
+ should 'allow clauses to be chained in any order' do
135
+ items = @client.select(:test).limit(2).allow_filtering.where('a >= 2').execute
136
+ assert_equal [2, 4].to_set, items.map { |x| x['a'] }.to_set
137
+ end
138
+
139
+ should 'allow select statements to be modified without affecting the original statement' do
140
+ partial_query = @client.select(:test).allow_filtering.where(:a => 1)
141
+
142
+ items = partial_query.where(:b => 'missing').execute
143
+ assert_equal 0, items.count
144
+
145
+ original_items = partial_query.execute
146
+ assert_equal 1, original_items.count
147
+ end
148
+
149
+ should 'support count queries' do
150
+ count = @client.select(:test).where("id = ? and a > ?", 'i', 1).count.execute
151
+ assert_equal 2, count.first["count"]
152
+ end
153
+ end
154
+
155
+ context 'delete' do
156
+ setup do
157
+ @client.insert(:test, :id => 'i', :a => 2, :b => 'a', :c => '1', :d => 1)
158
+ @client.insert(:test, :id => 'i', :a => 3, :b => 'c', :c => '1', :d => 1)
159
+ end
160
+
161
+ should 'delete entire rows' do
162
+ @client.delete(:test).where(:id => 'i', :a => 2).execute
163
+ items = @client.select(:test).where(:id => 'i', :a => 2).execute
164
+ assert_equal 0, items.count
165
+ end
166
+
167
+ should 'delete multiple elements in a partition' do
168
+ @client.delete(:test).where(:id => 'i').execute
169
+ items = @client.select(:test).where(:id => 'i').execute
170
+ assert_equal 0, items.count
171
+ end
172
+
173
+ should 'delete individual columns' do
174
+ @client.delete(:test, [:c, :d]).where(:id => 'i', :a => 2, :b => 'a').execute
175
+ items = @client.select(:test).where(:id => 'i', :a => 2).execute
176
+ assert_equal nil, items.first['c']
177
+ assert_equal nil, items.first['d']
178
+ end
179
+
180
+ context 'hash arguments' do
181
+ should 'allow single and double quotes in the value' do
182
+ # no error raised
183
+ @client.delete(:test).where(:id => "'\"abc").execute
184
+ end
185
+ end
186
+
187
+ context 'string arguments' do
188
+ should 'allow single and double quotes in the value' do
189
+ # no error raised
190
+ @client.delete(:test).where('id = ?', "'\"abc").execute
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'pry'
3
+ require 'minitest/autorun'
4
+ require 'minitest/should'
5
+
6
+
7
+ require 'cassava'
8
+
9
+ class Minitest::Should::TestCase
10
+ def self.xshould(*args)
11
+ puts "Disabled test: #{args}"
12
+ end
13
+ end
14
+
15
+ def session_for_keyspace(keyspace = 'test_cassava')
16
+ c = Cassandra.cluster(port: 9242)
17
+ c.connect(keyspace)
18
+ end
19
+
20
+ def initialize_test_table
21
+ sess = session_for_keyspace(nil)
22
+ sess.execute('DROP KEYSPACE test_cassava') rescue nil
23
+ sess.execute("CREATE KEYSPACE test_cassava with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }")
24
+ session_for_keyspace.execute('CREATE TABLE test(id text, a int, b text, c text, d int, primary key ((id), a, b))')
25
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassava_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Arron Norwell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cassandra-driver
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - anorwell@datto.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - cassava_rb.gemspec
96
+ - lib/cassava.rb
97
+ - lib/cassava/client.rb
98
+ - lib/cassava/version.rb
99
+ - test/cassava/client_test.rb
100
+ - test/test_helper.rb
101
+ homepage: ''
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.2.2
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: An unopinionated wrapper for the datastax cassandra gem.
125
+ test_files:
126
+ - test/cassava/client_test.rb
127
+ - test/test_helper.rb
128
+ has_rdoc: