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.
@@ -0,0 +1,44 @@
1
+ module Cql::Model::Query
2
+
3
+ class Statement
4
+
5
+ # Initialize instance variables common to all statements
6
+ #
7
+ # @param [Class] klass Model class
8
+ # @param [Cql::Client] client used to connect to Cassandra
9
+ def initialize(klass, client)
10
+ @klass = klass
11
+ @client = client || klass.cql_client
12
+ @consistency = nil
13
+ end
14
+
15
+ # Build a string representation of this CQL statement, suitable for execution by a CQL client.
16
+ # @return [String]
17
+ def to_s
18
+ raise NotImplementedError, "Subclass responsibility"
19
+ end
20
+
21
+ # Execute this CQL statement. Return value and parameters vary for each derived class.
22
+ # @see SelectStatement#execute
23
+ # @see InsertStatement#execute
24
+ # @see UpdateStatement#execute
25
+ def execute
26
+ raise NotImplementedError, "Subclass responsibility"
27
+ end
28
+
29
+ # Specify consistency level to use when executing statemnt
30
+ # See http://www.datastax.com/docs/1.0/dml/data_consistency
31
+ # Defaults to :local_quorum
32
+ #
33
+ # @param [String] consistency One of 'ANY', 'ONE', 'QUORUM', 'LOCAL_QUORUM', 'EACH_QUORUM', 'ALL' as of Cassandra 1.0
34
+ # @return [String] consistency value
35
+ def consistency(consist)
36
+ raise ArgumentError, "Cannot specify USING CONSISTENCY twice" unless @consistency.nil?
37
+ @consistency = consist
38
+ self
39
+ end
40
+
41
+ alias using_consistency consistency
42
+ end
43
+
44
+ end
@@ -0,0 +1,104 @@
1
+ require 'set'
2
+
3
+ module Cql::Model::Query
4
+ # @TODO docs
5
+ class UpdateExpression < Expression
6
+ # Operators allowed in an update lambda
7
+ OPERATORS = {
8
+ :+ => '+',
9
+ :- => '-',
10
+ :[]= => true # special treatment in #__build__
11
+ }.freeze
12
+
13
+ # @TODO docs
14
+ def initialize(&block)
15
+ @left = nil
16
+ @operator = nil
17
+ @right = nil
18
+
19
+ instance_exec(&block) if block
20
+ end
21
+
22
+ # @TODO docs
23
+ def to_s
24
+ __build__
25
+ end
26
+
27
+ # @TODO docs
28
+ def inspect
29
+ __build__
30
+ end
31
+
32
+ # This is where the magic happens. Ensure all of our operators are overloaded so they call
33
+ # #apply and contribute to the CQL expression that will be built.
34
+ OPERATORS.keys.each do |op|
35
+ define_method(op) do |*args|
36
+ __apply__(op, args)
37
+ end
38
+ end
39
+
40
+ # @TODO docs
41
+ def method_missing(token, *args)
42
+ __apply__(token, args)
43
+ end
44
+
45
+ private
46
+
47
+ # @TODO docs
48
+ def __apply__(token, args)
49
+ if @left.nil?
50
+ if args.empty?
51
+ # A well-behaved CQL identifier (column name that is a valid Ruby method name)
52
+ @left = token
53
+ elsif args.length == 1
54
+ # A CQL typecast (column name that is an integer, float, etc and must be wrapped in a decorator)
55
+ @left = args.first
56
+ else
57
+ ::Kernel.raise ::Cql::Model::SyntaxError.new(
58
+ "Unacceptable token '#{token}'; expected a CQL identifier or typecast")
59
+ end
60
+ elsif @operator.nil?
61
+ # Looking for an operator + right operand
62
+ if OPERATORS.keys.include?(token)
63
+ @operator = token
64
+
65
+ if (token == :[]=)
66
+ @right = args # the right-hand argument of []= is a (key, value) pair
67
+ else
68
+ @right = args.first
69
+ end
70
+ else
71
+ ::Kernel.raise ::Cql::Model::SyntaxError.new(
72
+ "Unacceptable token '#{token}'; expected a CQL-compatible operator")
73
+ end
74
+ else
75
+ ::Kernel.raise ::Cql::Model::SyntaxError.new(
76
+ "Unacceptable token '#{token}'; the expression is " +
77
+ "already complete")
78
+ end
79
+
80
+ self
81
+ end
82
+
83
+ # @TODO docs
84
+ def __build__
85
+ if @left.nil? || @operator.nil? || @right.nil?
86
+ ::Kernel.raise ::Cql::Model::SyntaxError.new(
87
+ "Cannot build a CQL expression; the Ruby expression is incomplete " +
88
+ "(#{@left.inspect}, #{@operator.inspect}, #{@right.inspect})")
89
+ else
90
+ left = ::Cql::Model::Query.cql_identifier(@left)
91
+ case @operator
92
+ when :[]=
93
+ key = ::Cql::Model::Query.cql_value(@right[0], context=:update)
94
+ val = ::Cql::Model::Query.cql_value(@right[1], context=:update)
95
+ "#{left}[#{key}] = #{val}"
96
+ else
97
+ op = OPERATORS[@operator]
98
+ right = ::Cql::Model::Query.cql_value(@right, context=:update)
99
+ "#{left} #{op} #{right}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,91 @@
1
+ module Cql::Model::Query
2
+
3
+ # UPDATE statement DSL
4
+ # << An UPDATE writes one or more columns to a record in a Cassandra column family. No results are returned.
5
+ # Row/column records are created if they do not exist, or overwritten if they do exist >>
6
+ # (from http://www.datastax.com/docs/1.1/references/cql/UPDATE)
7
+ #
8
+ # Note: user a hash with a single key :value to update counter columns using the existing counter value:
9
+ # update(:id => 12, :counter => { :value => 'counter + 1' })
10
+ #
11
+ # E.g.:
12
+ # Model.update(:id => '123', :col => 'value', :counter => { :value => 'counter + 2' })
13
+ # Model.update(:id => ['123', '456'], :col => 'value')
14
+ # Model.update(:id => '123', :col => 'value').ttl(3600)
15
+ # Model.update(:id => '123', :col => 'value').timestamp(1366057256324)
16
+ # Model.update(:id => '123', :col => 'value').timestamp('2013-04-15 13:21:48')
17
+ # Model.update(:id => '123', :col => 'value').consistency('ONE')
18
+ # Model.update(:id => ['123', '456'], :col => 'value', :counter => 'counter + 2').ttl(3600).timestamp(1366057256324).consistency('ONE')
19
+ #
20
+ # Can also be used on Model instances, e.g.:
21
+ # @model.update(:col => 'value', :counter => 'counter + 2')
22
+ # @model.update_all_by('name', :col => 'value') # 'name' must be part of the table composite key
23
+ class UpdateStatement < MutationStatement
24
+ def initialize(klass, client=nil)
25
+ super(klass, client)
26
+ @where = []
27
+ end
28
+
29
+ # Create or append to the WHERE clause for this statement. The block that you pass will define the constraint
30
+ # and any where() parameters will be forwarded to the block as yield parameters. This allows late binding of
31
+ # variables in the WHERE clause, e.g. for prepared statements.
32
+ # TODO examples
33
+ # @see Expression
34
+ def where(*params, &block)
35
+ @where << ComparisonExpression.new(*params, &block)
36
+ self
37
+ end
38
+
39
+ alias and where
40
+
41
+ # DSL for setting UPDATE values
42
+ #
43
+ # @param [Hash] values Hash of column values or column update expression indexed by column name
44
+ def update(values)
45
+ raise ArgumentError, "Cannot specify UPDATE values twice" unless @values.nil?
46
+ @values = values
47
+ self
48
+ end
49
+
50
+ # @return [String] a CQL UPDATE statement with suitable constraints and options
51
+ def to_s
52
+ s = "UPDATE #{@klass.table_name}"
53
+
54
+ options = []
55
+ options << "CONSISTENCY #{@consistency || @klass.write_consistency}"
56
+ options << "TIMESTAMP #{@timestamp}" unless @timestamp.nil?
57
+ options << "TTL #{@ttl}" unless @ttl.nil?
58
+ s << " USING #{options.join(' AND ')}"
59
+
60
+ if @values.respond_to?(:map)
61
+ if @values.respond_to?(:each_pair)
62
+ # List of column names and values (or lambdas containing list/set/counter operations)
63
+ pairs = @values.map do |n, v|
64
+ if v.respond_to?(:call)
65
+ "#{n} = #{UpdateExpression.new(&v).to_s}"
66
+ else
67
+ "#{n} = #{::Cql::Model::Query.cql_value(v)}"
68
+ end
69
+ end
70
+ s << " SET #{pairs.join(', ')}"
71
+ elsif @values.all? { |v| v.respond_to?(:call) }
72
+ # Array of hash assignments
73
+ assigns = @values.map { |v| "#{UpdateExpression.new(&v).to_s}" }
74
+ s << " SET #{assigns.join(', ')}"
75
+ end
76
+ elsif @values.respond_to?(:call)
77
+ # Simple hash assignment
78
+ assign = UpdateExpression.new(&@values).to_s
79
+ s << " SET #{assign}"
80
+ end
81
+
82
+ unless @where.empty?
83
+ s << " WHERE " << @where.map { |w| w.to_s }.join(' AND ')
84
+ end
85
+
86
+ s << ';'
87
+
88
+ s
89
+ end
90
+ end
91
+ end
metadata ADDED
@@ -0,0 +1,227 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cql-model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tony Spataro
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cql-rb
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0.pre4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.0.pre4
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.9'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.9'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: cucumber
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.8.3
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.8.3
94
+ - !ruby/object:Gem::Dependency
95
+ name: debugger
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rdoc
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: 2.4.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 2.4.2
126
+ - !ruby/object:Gem::Dependency
127
+ name: flexmock
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: '0.8'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '0.8'
142
+ - !ruby/object:Gem::Dependency
143
+ name: syntax
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: 1.0.0
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 1.0.0
158
+ - !ruby/object:Gem::Dependency
159
+ name: nokogiri
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ~>
164
+ - !ruby/object:Gem::Version
165
+ version: '1.5'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: '1.5'
174
+ description: Lightweight, performant OOP wrapper for Cassandra tables; inspired by
175
+ DataMapper.
176
+ email: gemspec@tracker.xeger.net
177
+ executables: []
178
+ extensions: []
179
+ extra_rdoc_files:
180
+ - README.md
181
+ files:
182
+ - .ruby-version
183
+ - Gemfile
184
+ - Gemfile.lock
185
+ - README.md
186
+ - Rakefile
187
+ - VERSION
188
+ - cql-model.gemspec
189
+ - cql_model.rconf
190
+ - lib/cql/model.rb
191
+ - lib/cql/model/class_methods.rb
192
+ - lib/cql/model/instance_methods.rb
193
+ - lib/cql/model/query.rb
194
+ - lib/cql/model/query/comparison_expression.rb
195
+ - lib/cql/model/query/expression.rb
196
+ - lib/cql/model/query/insert_statement.rb
197
+ - lib/cql/model/query/mutation_statement.rb
198
+ - lib/cql/model/query/select_statement.rb
199
+ - lib/cql/model/query/statement.rb
200
+ - lib/cql/model/query/update_expression.rb
201
+ - lib/cql/model/query/update_statement.rb
202
+ homepage: https://github.com/xeger/cql-model
203
+ licenses:
204
+ - MIT
205
+ post_install_message:
206
+ rdoc_options: []
207
+ require_paths:
208
+ - lib
209
+ required_ruby_version: !ruby/object:Gem::Requirement
210
+ none: false
211
+ requirements:
212
+ - - ! '>='
213
+ - !ruby/object:Gem::Version
214
+ version: 1.9.0
215
+ required_rubygems_version: !ruby/object:Gem::Requirement
216
+ none: false
217
+ requirements:
218
+ - - ! '>='
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ requirements: []
222
+ rubyforge_project:
223
+ rubygems_version: 1.8.23
224
+ signing_key:
225
+ specification_version: 3
226
+ summary: Cassandra CQL model.
227
+ test_files: []