cql-model 0.3.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.
- data/.ruby-version +1 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +56 -0
- data/README.md +24 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/cql-model.gemspec +85 -0
- data/cql_model.rconf +11 -0
- data/lib/cql/model.rb +49 -0
- data/lib/cql/model/class_methods.rb +178 -0
- data/lib/cql/model/instance_methods.rb +50 -0
- data/lib/cql/model/query.rb +107 -0
- data/lib/cql/model/query/comparison_expression.rb +126 -0
- data/lib/cql/model/query/expression.rb +5 -0
- data/lib/cql/model/query/insert_statement.rb +51 -0
- data/lib/cql/model/query/mutation_statement.rb +48 -0
- data/lib/cql/model/query/select_statement.rb +143 -0
- data/lib/cql/model/query/statement.rb +44 -0
- data/lib/cql/model/query/update_expression.rb +104 -0
- data/lib/cql/model/query/update_statement.rb +91 -0
- metadata +227 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Cql::Model::InstanceMethods
|
2
|
+
# Instantiate a new instance of this model. Do not validate the contents of
|
3
|
+
# cql_properties; it may contain properties that aren't declared by this model, that have
|
4
|
+
# a missing CQL column, or an improper name/value for their column type.
|
5
|
+
#
|
6
|
+
# @param [Hash] cql_properties typed hash of all properties associated with this model
|
7
|
+
def initialize(cql_properties=nil)
|
8
|
+
@cql_properties = cql_properties || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Read a property. Property names are column names, and can therefore take any data type
|
12
|
+
# that a column name can take (integer, UUID, etc).
|
13
|
+
#
|
14
|
+
# @param [Object] name
|
15
|
+
# @return [Object] the value of the specified column, or nil
|
16
|
+
def [](name)
|
17
|
+
@cql_properties[name]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Start an INSERT CQL statement to update model
|
21
|
+
# @see Cql::Model::Query::InsertStatement
|
22
|
+
#
|
23
|
+
# @param [Hash] values Hash of column values indexed by column name, optional
|
24
|
+
# @return [Cql::Model::Query::InsertStatement] a query object to customize (ttl, timestamp etc) or execute
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# joe.update(:age => 35).execute
|
28
|
+
# joe.update.ttl(3600).execute
|
29
|
+
# joe.update(:age => 36).ttl(7200).consistency('ONE').execute
|
30
|
+
def update(values={})
|
31
|
+
key_vals = self.class.primary_key.inject({}) { |h, k| h[k] = @cql_properties[k]; h }
|
32
|
+
Cql::Model::Query::UpdateStatement.new(self.class).update(values.merge(key_vals))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Start an UPDATE CQL statement to update all models with given key
|
36
|
+
# This can update multiple models if the key is part of a composite key
|
37
|
+
# Updating all models with given (different) key values can be done using the '.update' class method
|
38
|
+
# @see Cql::Model::Query::UpdateStatement
|
39
|
+
#
|
40
|
+
# @param [Symbol|String] key Name of key used to select models to be updated
|
41
|
+
# @param [Hash] values Hash of column values indexed by column names, optional
|
42
|
+
# @return [Cql::Model::Query::UpdateStatement] a query object to customize (ttl, timestamp etc) or execute
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# joe.update_all_by(:name, :age => 25).execute # Set all joe's age to 25
|
46
|
+
# joe.update_all_by(:name).ttl(3600).execute # Set all joe's TTL to one hour
|
47
|
+
def update_all_by(key, values={})
|
48
|
+
Cql::Model::Query::UpdateStatement.new(self.class).update(values.merge({ key => @cql_properties[key.to_s] }))
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Cql::Model::Query
|
2
|
+
# CQL single quote character.
|
3
|
+
SQ = "'"
|
4
|
+
|
5
|
+
# CQL single-quote escape sequence.
|
6
|
+
SQSQ = "''"
|
7
|
+
|
8
|
+
# CQL double-quote character.
|
9
|
+
DQ = '"'
|
10
|
+
|
11
|
+
# CQL double-quote escape.
|
12
|
+
DQDQ = '""'
|
13
|
+
|
14
|
+
# Valid CQL identifier (can be used as a column name without double-quoting)
|
15
|
+
IDENTIFIER = /[a-z][a-z0-9_]*/i
|
16
|
+
|
17
|
+
module_function
|
18
|
+
|
19
|
+
# Transform a list of symbols or strings into CQL column names. Performs no safety checks!!
|
20
|
+
def cql_column_names(list)
|
21
|
+
if list.empty?
|
22
|
+
'*'
|
23
|
+
else
|
24
|
+
list.join(', ')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Transform a Ruby object into its CQL identifier representation.
|
29
|
+
#
|
30
|
+
# @TODO more docs
|
31
|
+
#
|
32
|
+
def cql_identifier(value)
|
33
|
+
# TODO UUID, Time, ...
|
34
|
+
case value
|
35
|
+
when Symbol, String
|
36
|
+
if value =~ IDENTIFIER
|
37
|
+
value.to_s
|
38
|
+
else
|
39
|
+
"#{DQ}#{value.gsub(DQ, DQDQ)}#{DQ}"
|
40
|
+
end
|
41
|
+
when Numeric, TrueClass, FalseClass
|
42
|
+
"#{DQ}#{cql_value(value)}#{DQ}"
|
43
|
+
else
|
44
|
+
raise Cql::Model::SyntaxError, "Cannot convert #{value.class} to a CQL identifier"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Transform a Ruby object into its CQL literal value representation. A literal value is anything
|
49
|
+
# that can appear in a CQL statement as a key or column value (but not column NAME; see
|
50
|
+
# #cql_identifier to convert values to column names).
|
51
|
+
#
|
52
|
+
# @param value [String,Numeric,Boolean,Array,Set,Array,#map]
|
53
|
+
# @return [String] the CQL equivalent of value
|
54
|
+
#
|
55
|
+
# When used as a key or column value, CQL supports the following kinds of literal value:
|
56
|
+
# * unquoted identifier (treated as a string value)
|
57
|
+
# * string literal
|
58
|
+
# * integer number
|
59
|
+
# * UUID
|
60
|
+
# * floating-point number
|
61
|
+
# * boolean true/false
|
62
|
+
#
|
63
|
+
# When used as a column name, any value that is not a valid identifier MUST BE ENCLOSED IN
|
64
|
+
# DOUBLE QUOTES. This method does not handle the double-quote escaping; see #cql_identifier
|
65
|
+
# for that.
|
66
|
+
#
|
67
|
+
# @see #cql_identifier
|
68
|
+
# @see http://www.datastax.com/docs/1.1/references/cql/cql_lexicon#keywords-and-identifiers
|
69
|
+
def cql_value(value, context=nil)
|
70
|
+
# TODO UUID, Time, ...
|
71
|
+
case value
|
72
|
+
when String
|
73
|
+
"#{SQ}#{value.gsub(SQ, SQSQ)}#{SQ}"
|
74
|
+
when Numeric, TrueClass, FalseClass
|
75
|
+
value.to_s
|
76
|
+
when Set
|
77
|
+
raise SyntaxError, "Set notation is not allowed outside UPDATE statements" unless (context == :update)
|
78
|
+
'{' + value.map { |v| cql_value(v) }.join(', ') + '}'
|
79
|
+
else
|
80
|
+
if value.respond_to?(:map)
|
81
|
+
if value.respond_to?(:each_pair)
|
82
|
+
# Pairwise map -- CQL map literal
|
83
|
+
'{' + value.map { |k, v| "#{cql_value(k)}: #{cql_value(v)}" }.join(', ') + '}'
|
84
|
+
else
|
85
|
+
# Single map -- CQL list (for UPDATE) or set (for WHERE...IN) literal
|
86
|
+
case context
|
87
|
+
when :update
|
88
|
+
'[' + value.map { |v| cql_value(v) }.join(', ') + ']'
|
89
|
+
else
|
90
|
+
'(' + value.map { |v| cql_value(v) }.join(', ') + ')'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
else
|
94
|
+
raise Cql::Model::SyntaxError, "Cannot convert #{value.class} to a CQL value"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
require 'cql/model/query/expression'
|
101
|
+
require 'cql/model/query/comparison_expression'
|
102
|
+
require 'cql/model/query/update_expression'
|
103
|
+
require 'cql/model/query/statement'
|
104
|
+
require 'cql/model/query/mutation_statement'
|
105
|
+
require 'cql/model/query/select_statement'
|
106
|
+
require 'cql/model/query/insert_statement'
|
107
|
+
require 'cql/model/query/update_statement'
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Cql::Model::Query
|
4
|
+
# @TODO docs
|
5
|
+
class ComparisonExpression < Expression
|
6
|
+
# Operators allowed in a where-clause lambda
|
7
|
+
OPERATORS = {
|
8
|
+
:== => '=',
|
9
|
+
:'!=' => '!=',
|
10
|
+
:'>' => '>',
|
11
|
+
:'<' => '<',
|
12
|
+
:'>=' => '>=',
|
13
|
+
:'<=' => '<=',
|
14
|
+
:'in' => 'IN',
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Methods used to escape CQL column names that aren't valid CQL identifiers
|
18
|
+
TYPECASTS = [
|
19
|
+
:ascii,
|
20
|
+
:bigint,
|
21
|
+
:blob,
|
22
|
+
:boolean,
|
23
|
+
:counter,
|
24
|
+
:decimal,
|
25
|
+
:double,
|
26
|
+
:float,
|
27
|
+
:int,
|
28
|
+
:text,
|
29
|
+
:timestamp,
|
30
|
+
:uuid,
|
31
|
+
:timeuuid,
|
32
|
+
:varchar,
|
33
|
+
:varint
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
# @TODO docs
|
37
|
+
def initialize(*params, &block)
|
38
|
+
@left = nil
|
39
|
+
@operator = nil
|
40
|
+
@right = nil
|
41
|
+
|
42
|
+
instance_exec(*params, &block) if block
|
43
|
+
end
|
44
|
+
|
45
|
+
# @TODO docs
|
46
|
+
def to_s
|
47
|
+
__build__
|
48
|
+
end
|
49
|
+
|
50
|
+
# @TODO docs
|
51
|
+
def inspect
|
52
|
+
__build__
|
53
|
+
end
|
54
|
+
|
55
|
+
# This is where the magic happens. Ensure all of our operators are overloaded so they call
|
56
|
+
# #apply and contribute to the CQL expression that will be built.
|
57
|
+
OPERATORS.keys.each do |op|
|
58
|
+
define_method(op) do |*args|
|
59
|
+
__apply__(op, args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
TYPECASTS.each do |func|
|
64
|
+
define_method(func) do |*args|
|
65
|
+
__apply__(func, args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @TODO docs
|
70
|
+
def method_missing(token, *args)
|
71
|
+
__apply__(token, args)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @TODO docs
|
77
|
+
def __apply__(token, args)
|
78
|
+
if @left.nil?
|
79
|
+
if args.empty?
|
80
|
+
# A well-behaved CQL identifier (column name that is a valid Ruby method name)
|
81
|
+
@left = token
|
82
|
+
elsif args.length == 1
|
83
|
+
# A CQL typecast (column name that is an integer, float, etc and must be wrapped in a decorator)
|
84
|
+
@left = args.first
|
85
|
+
else
|
86
|
+
::Kernel.raise ::Cql::Model::SyntaxError.new(
|
87
|
+
"Unacceptable token '#{token}'; expected a CQL identifier or typecast")
|
88
|
+
end
|
89
|
+
elsif @operator.nil?
|
90
|
+
# Looking for an operator + right operand
|
91
|
+
if OPERATORS.keys.include?(token)
|
92
|
+
@operator = token
|
93
|
+
|
94
|
+
if (args.size > 1) || (token == :in)
|
95
|
+
@right = args
|
96
|
+
else
|
97
|
+
@right = args.first
|
98
|
+
end
|
99
|
+
else
|
100
|
+
::Kernel.raise ::Cql::Model::SyntaxError.new(
|
101
|
+
"Unacceptable token '#{token}'; expected a CQL-compatible operator")
|
102
|
+
end
|
103
|
+
else
|
104
|
+
::Kernel.raise ::Cql::Model::SyntaxError.new(
|
105
|
+
"Unacceptable token '#{token}'; the expression is " +
|
106
|
+
"already complete")
|
107
|
+
end
|
108
|
+
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# @TODO docs
|
113
|
+
def __build__
|
114
|
+
if @left.nil? || @operator.nil? || @right.nil?
|
115
|
+
::Kernel.raise ::Cql::Model::SyntaxError.new(
|
116
|
+
"Cannot build a CQL expression; the Ruby expression is incomplete " +
|
117
|
+
"(#{@left.inspect}, #{@operator.inspect}, #{@right.inspect})")
|
118
|
+
else
|
119
|
+
left = ::Cql::Model::Query.cql_identifier(@left)
|
120
|
+
op = OPERATORS[@operator]
|
121
|
+
right = ::Cql::Model::Query.cql_value(@right)
|
122
|
+
"#{left} #{op} #{right}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Cql::Model::Query
|
2
|
+
|
3
|
+
# INSERT statement DSL
|
4
|
+
# << An INSERT writes one or more columns to a record in a Cassandra column family. No results are returned.
|
5
|
+
# The first column name in the INSERT list must be the name of the column family key >>
|
6
|
+
# (from: http://www.datastax.com/docs/1.1/references/cql/INSERT)
|
7
|
+
#
|
8
|
+
# Ex:
|
9
|
+
# Model.create(:key => 'val', :col1 => 'value', :col2 => 42) # Simple insert
|
10
|
+
# Model.create(:key => 'val', :key2 => 64, :col1 => 'value', :col2 => 42) # Composite keys
|
11
|
+
# Model.create(:key => 'val', :col => 'value').ttl(3600) # TTL in seconds
|
12
|
+
# Model.create(:key => 'val', :col => 'value').timestamp(1366057256324) # Milliseconds since epoch timestamp
|
13
|
+
# Model.create(:key => 'val', :col => 'value').timestamp('2013-04-15 13:21:48') # ISO 8601 timestamp
|
14
|
+
# Model.create(:key => 'val', :col => 'value').consistency('ONE') # Custom consistency (default is 'LOCAL_QUORUM')
|
15
|
+
# Model.create(:key => 'val', :col => 'value').ttl(3600).timestamp(1366057256324).consistency('ONE') # Multiple options
|
16
|
+
class InsertStatement < MutationStatement
|
17
|
+
|
18
|
+
# Specify names and values to insert.
|
19
|
+
#
|
20
|
+
# @param [Hash] values Hash of column values indexed by column name
|
21
|
+
def insert(values)
|
22
|
+
raise ArgumentError, "Cannot specify INSERT values twice" unless @values.nil?
|
23
|
+
@values = values
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
alias create insert
|
28
|
+
|
29
|
+
# Build a string representation of this CQL statement, suitable for execution by a CQL client.
|
30
|
+
# Do not validate the statement for completeness; Cassnadra will raise an error if a key
|
31
|
+
# component is missing.
|
32
|
+
#
|
33
|
+
# @return [String] a CQL INSERT statement with suitable constraints and options
|
34
|
+
def to_s
|
35
|
+
keys = @klass.primary_key.inject([]) { |h, k| h << [k, @values.delete(k)]; h }
|
36
|
+
if keys.any? { |k| k[1].nil? }
|
37
|
+
raise Cql::Model::MissingKey.new("Missing primary key(s) in INSERT statement: #{keys.select { |k| k[1].nil? }.map(&:first).map(&:inspect).join(', ')}")
|
38
|
+
end
|
39
|
+
s = "INSERT INTO #{@klass.table_name} (#{keys.map { |k| k[0] }.join(', ')}, #{@values.keys.join(', ')})"
|
40
|
+
s << " VALUES (#{keys.map { |k| ::Cql::Model::Query.cql_value(k[1]) }.join(', ')}, #{@values.values.map { |v| ::Cql::Model::Query.cql_value(v) }.join(', ')})"
|
41
|
+
options = []
|
42
|
+
options << "CONSISTENCY #{@consistency || @klass.write_consistency}"
|
43
|
+
options << "TIMESTAMP #{@timestamp}" unless @timestamp.nil?
|
44
|
+
options << "TTL #{@ttl}" unless @ttl.nil?
|
45
|
+
s << " USING #{options.join(' AND ')}"
|
46
|
+
s << ';'
|
47
|
+
|
48
|
+
s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cql::Model::Query
|
2
|
+
|
3
|
+
# Common parent to InsertStatement and UpdateStatment
|
4
|
+
# provide helpers for managing common DSL settings
|
5
|
+
class MutationStatement < Statement
|
6
|
+
|
7
|
+
# Instantiate statement
|
8
|
+
#
|
9
|
+
# @param [Class] klass
|
10
|
+
# @param [Cql::Client] CQL client used to execute statement
|
11
|
+
def initialize(klass, client=nil)
|
12
|
+
super(klass, client)
|
13
|
+
@values = nil
|
14
|
+
@ttl = nil
|
15
|
+
@timestamp = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# DSL for setting TTL value
|
19
|
+
#
|
20
|
+
# @param [Fixnum] ttl_value TTL value in seconds
|
21
|
+
def ttl(ttl_value)
|
22
|
+
raise ArgumentError, "Cannot specify TTL twice" unless @ttl.nil?
|
23
|
+
@ttl = ttl_value
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# DSL for setting timestamp value
|
28
|
+
#
|
29
|
+
# @param [Fixnum|String] timestamp_value (number of milliseconds since epoch or ISO 8601 date time value)
|
30
|
+
def timestamp(timestamp_value)
|
31
|
+
raise ArgumentError, "Cannot specify timestamp twice" unless @timestamp.nil?
|
32
|
+
@timestamp = timestamp_value
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Execute this statement on the CQL client connection
|
37
|
+
# INSERT statements do not return a result
|
38
|
+
#
|
39
|
+
# @return [true] always returns true
|
40
|
+
def execute
|
41
|
+
@client.execute(to_s)
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Cql::Model::Query
|
2
|
+
|
3
|
+
# SELECT statement DSL
|
4
|
+
# << A SELECT expression reads one or more records from a Cassandra column family and returns a result-set of rows.
|
5
|
+
# Each row consists of a row key and a collection of columns corresponding to the query. >>
|
6
|
+
# (from: http://www.datastax.com/docs/1.1/references/cql/SELECT)
|
7
|
+
#
|
8
|
+
# Ex:
|
9
|
+
# Model.select(:col1, :col2)
|
10
|
+
# Model.select(:col1, :col2).where { name == 'Joe' }
|
11
|
+
# Model.select(:col1, :col2).where { name == 'Joe' }.and { age.in(33,34,35) }
|
12
|
+
# Model.select(:col1, :col2).where { name == 'Joe' }.and { age.in(33,34,35) }.order_by(:age).desc
|
13
|
+
class SelectStatement < Statement
|
14
|
+
|
15
|
+
# Instantiate statement
|
16
|
+
#
|
17
|
+
# @param [Class] klass Model class
|
18
|
+
# @param [Cql::Client] CQL client used to execute statement
|
19
|
+
def initialize(klass, client=nil)
|
20
|
+
super(klass, client)
|
21
|
+
@columns = nil
|
22
|
+
@where = []
|
23
|
+
@order = ''
|
24
|
+
@limit = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create or append to the WHERE clause for this statement. The block that you pass will define the constraint
|
28
|
+
# and any where() parameters will be forwarded to the block as yield parameters. This allows late binding of
|
29
|
+
# variables in the WHERE clause, e.g. for prepared statements.
|
30
|
+
#
|
31
|
+
# @param [Object] *params parameters to be forwarded to the block
|
32
|
+
# @yield the block will be evaluated in the context of a ComparisonExpression to capture a CQL expression that will be appended to the WHERE clause
|
33
|
+
#
|
34
|
+
# @return [SelectStatement] always returns self
|
35
|
+
#
|
36
|
+
# @example find people named Joe
|
37
|
+
# where { name == "Joe" }
|
38
|
+
#
|
39
|
+
# @example find people older than 33 who are named Joe or Fred
|
40
|
+
# where { age > 33 }.and { name.in("Joe", "Fred") }
|
41
|
+
#
|
42
|
+
# @example find people older than 33
|
43
|
+
# where { age > 33 }
|
44
|
+
#
|
45
|
+
# @example find by a late-bound search term (e.g. for a named scope)
|
46
|
+
# where(age_chosen_by_user) { |chosen| age > chosen }
|
47
|
+
#
|
48
|
+
# @example find by a column name that is not a valid Ruby identifier
|
49
|
+
# where { timestamp(12345) == "logged in" }
|
50
|
+
#
|
51
|
+
# @see ComparisonExpression
|
52
|
+
# @see Cql::Model::ClassMethods#scope
|
53
|
+
def where(*params, &block)
|
54
|
+
@where << ComparisonExpression.new(*params, &block)
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
alias and where
|
59
|
+
|
60
|
+
# Specify the order in which result rows should be returned.
|
61
|
+
#
|
62
|
+
# @param [Array] *params glob of column names (symbols, strings, or Ruby values that correspond to valid column names)
|
63
|
+
# @return [SelectStatement] always returns self
|
64
|
+
def order(*columns)
|
65
|
+
raise ArgumentError, "Cannot specify ORDER BY twice" unless @order.empty?
|
66
|
+
@order = ::Cql::Model::Query.cql_column_names(columns)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
alias order_by order
|
71
|
+
|
72
|
+
# @TODO docs
|
73
|
+
def asc
|
74
|
+
raise ArgumentError, "Cannot specify ASC / DESC twice" if @order =~ /ASC|DESC$/
|
75
|
+
raise ArgumentError, "Must specify ORDER BY before ASC" if @order.empty?
|
76
|
+
@order << " ASC"
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# @TODO docs
|
81
|
+
def desc
|
82
|
+
raise ArgumentError, "Cannot specify ASC / DESC twice" if @order =~ /ASC|DESC$/
|
83
|
+
raise ArgumentError, "Must specify ORDER BY before DESC" if @order.empty?
|
84
|
+
@order << " DESC"
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
# @TODO docs
|
89
|
+
def limit(lim)
|
90
|
+
raise ArgumentError, "Cannot specify LIMIT twice" unless @limit.nil?
|
91
|
+
@limit = lim
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# @TODO docs
|
96
|
+
def select(*columns)
|
97
|
+
raise ArgumentError, "Cannot specify SELECT column names twice" unless @columns.nil?
|
98
|
+
@columns = ::Cql::Model::Query.cql_column_names(columns)
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [String] a CQL SELECT statement with suitable constraints and options
|
103
|
+
def to_s
|
104
|
+
s = "SELECT #{@columns || '*'} FROM #{@klass.table_name}"
|
105
|
+
|
106
|
+
s << " USING CONSISTENCY " << (@consistency || @klass.write_consistency)
|
107
|
+
|
108
|
+
unless @where.empty?
|
109
|
+
s << " WHERE " << @where.map { |w| w.to_s }.join(' AND ')
|
110
|
+
end
|
111
|
+
|
112
|
+
s << " ORDER BY " << @order unless @order.empty?
|
113
|
+
s << " LIMIT #{@limit}" unless @limit.nil?
|
114
|
+
s << ';'
|
115
|
+
end
|
116
|
+
|
117
|
+
# Execute this SELECT statement on the CQL client connection and yield each row of the
|
118
|
+
# result set as a raw-data Hash.
|
119
|
+
#
|
120
|
+
# @yield each row of the result set
|
121
|
+
# @yieldparam [Hash] row a Ruby Hash representing the column names and values for a given row
|
122
|
+
# @return [true] always returns true
|
123
|
+
def execute(&block)
|
124
|
+
@client.execute(to_s).each_row(&block).size
|
125
|
+
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
alias each_row execute
|
130
|
+
|
131
|
+
# Execute this SELECT statement on the CQL client connection and yield each row of the
|
132
|
+
# as an instance of CQL::Model.
|
133
|
+
#
|
134
|
+
# @yield each row of the result set
|
135
|
+
# @yieldparam [Hash] row a Ruby Hash representing the column names and values for a given row
|
136
|
+
# @return [true] always returns true
|
137
|
+
def each(&block)
|
138
|
+
each_row do |row|
|
139
|
+
block.call(@klass.new(row))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|