activerecord-cassandra-adapter 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.
- data/README.rdoc +69 -0
- data/lib/active_cassandra/cf.rb +88 -0
- data/lib/active_cassandra/sqlparser.tab.rb +790 -0
- data/lib/active_cassandra/sqlparser.y +298 -0
- data/lib/active_record/connection_adapters/cassandra_adapter.rb +269 -0
- metadata +69 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
class SQLParser
|
|
2
|
+
options no_result_var
|
|
3
|
+
rule
|
|
4
|
+
sql : create_statement
|
|
5
|
+
| read_statemant
|
|
6
|
+
| update_statemant
|
|
7
|
+
| delete_statemant
|
|
8
|
+
|
|
9
|
+
create_statement : INSERT INTO id '(' id_list ')' VALUES '(' value_list ')'
|
|
10
|
+
{
|
|
11
|
+
{:command => :insert, :table => val[2], :column_list => val[4], :value_list => val[8]}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
read_statemant : SELECT select_list FROM id where_clause order_by_clause limit_clause offset_clause
|
|
15
|
+
{
|
|
16
|
+
{:command => :select, :table => val[3], :select_list => val[1], :condition => val[4], :order => val[5], :limit => val[6], :offset => val[7]}
|
|
17
|
+
}
|
|
18
|
+
| SELECT DISTINCT id FROM id where_clause
|
|
19
|
+
{
|
|
20
|
+
{:command => :select, :table => val[4], :select_list => val[2], :distinct => val[2], :condition => val[5]}
|
|
21
|
+
}
|
|
22
|
+
| SELECT count_clause FROM id where_clause order_by_clause limit_clause offset_clause
|
|
23
|
+
{
|
|
24
|
+
{:command => :select, :table => val[3], :count => val[1], :condition => val[4], :order => val[5], :limit => val[6], :offset => val[7]}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
count_clause : COUNT '(' count_arg ')'
|
|
28
|
+
{
|
|
29
|
+
"count_all"
|
|
30
|
+
}
|
|
31
|
+
| COUNT '(' count_arg ')' AS id
|
|
32
|
+
{
|
|
33
|
+
val[5]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
count_arg : '*'
|
|
37
|
+
| id
|
|
38
|
+
|
|
39
|
+
select_list : '*'
|
|
40
|
+
{
|
|
41
|
+
[]
|
|
42
|
+
}
|
|
43
|
+
| id_list
|
|
44
|
+
|
|
45
|
+
where_clause :
|
|
46
|
+
{
|
|
47
|
+
[]
|
|
48
|
+
}
|
|
49
|
+
| WHERE id_search_condition
|
|
50
|
+
{
|
|
51
|
+
val[1]
|
|
52
|
+
}
|
|
53
|
+
| WHERE search_condition
|
|
54
|
+
{
|
|
55
|
+
val[1]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
id_search_condition : id_predicate
|
|
59
|
+
| '(' id_predicate ')'
|
|
60
|
+
{
|
|
61
|
+
val[1]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
id_predicate : ID '=' value
|
|
65
|
+
{
|
|
66
|
+
val[2]
|
|
67
|
+
}
|
|
68
|
+
| ID IN '(' value_list ')'
|
|
69
|
+
{
|
|
70
|
+
val[3]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
search_condition : boolean_primary
|
|
74
|
+
{
|
|
75
|
+
[val[0]].flatten
|
|
76
|
+
}
|
|
77
|
+
| search_condition AND boolean_primary
|
|
78
|
+
{
|
|
79
|
+
(val[0] << val[2]).flatten
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
boolean_primary : predicate
|
|
83
|
+
| '(' search_condition ')'
|
|
84
|
+
{
|
|
85
|
+
val[1]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
predicate : id op value
|
|
89
|
+
{
|
|
90
|
+
{:name => val[0], :op => val[1], :expr => val[2]}
|
|
91
|
+
}
|
|
92
|
+
| NOT id op value
|
|
93
|
+
{
|
|
94
|
+
{:name => val[1], :op => val[2], :expr => val[3], :not => true}
|
|
95
|
+
}
|
|
96
|
+
| id op '(' value_list ')'
|
|
97
|
+
{
|
|
98
|
+
{:name => val[0], :op => val[1], :expr => val[3]}
|
|
99
|
+
}
|
|
100
|
+
| NOT id op '(' value_list ')'
|
|
101
|
+
{
|
|
102
|
+
{:name => val[1], :op => val[2], :expr => val[4], :not => true}
|
|
103
|
+
}
|
|
104
|
+
| between_predicate
|
|
105
|
+
| not_in_predicate
|
|
106
|
+
|
|
107
|
+
between_predicate : id BETWEEN value AND value
|
|
108
|
+
{
|
|
109
|
+
{:name => val[0], :op => '$bt', :expr => [val[2], val[4]]}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
not_in_predicate : id NOT IN '(' value_list ')'
|
|
113
|
+
{
|
|
114
|
+
{:name => val[0], :op => '$in', :expr => val[4], :not => true}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
order_by_clause :
|
|
118
|
+
{
|
|
119
|
+
nil
|
|
120
|
+
}
|
|
121
|
+
| ORDER BY id ordering_spec
|
|
122
|
+
{
|
|
123
|
+
{:name => val[2], :type => val[3]}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ordering_spec :
|
|
127
|
+
{
|
|
128
|
+
:asc
|
|
129
|
+
}
|
|
130
|
+
| order_spec
|
|
131
|
+
|
|
132
|
+
limit_clause :
|
|
133
|
+
{
|
|
134
|
+
nil
|
|
135
|
+
}
|
|
136
|
+
| LIMIT NUMBER
|
|
137
|
+
{
|
|
138
|
+
val[1].to_i
|
|
139
|
+
}
|
|
140
|
+
| LIMIT STRING
|
|
141
|
+
{
|
|
142
|
+
val[1]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
offset_clause :
|
|
146
|
+
{
|
|
147
|
+
nil
|
|
148
|
+
}
|
|
149
|
+
| OFFSET NUMBER
|
|
150
|
+
{
|
|
151
|
+
val[1].to_i
|
|
152
|
+
}
|
|
153
|
+
| OFFSET STRING
|
|
154
|
+
{
|
|
155
|
+
val[1]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
update_statemant : UPDATE id SET set_clause_list where_clause
|
|
159
|
+
{
|
|
160
|
+
{:command => :update, :table => val[1], :set_clause_list => val[3], :condition => val[4]}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
set_clause_list : set_clause
|
|
164
|
+
| set_clause_list ',' set_clause
|
|
165
|
+
{
|
|
166
|
+
val[0].merge val[2]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
set_clause : id '=' value
|
|
170
|
+
{
|
|
171
|
+
{val[0] => val[2]}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
delete_statemant : DELETE FROM id where_clause
|
|
175
|
+
{
|
|
176
|
+
{:command => :delete, :table => val[2], :condition => val[3]}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
id : IDENTIFIER
|
|
180
|
+
|
|
181
|
+
id_list : id
|
|
182
|
+
{
|
|
183
|
+
[val[0]]
|
|
184
|
+
}
|
|
185
|
+
| id_list ',' id
|
|
186
|
+
{
|
|
187
|
+
val[0] << val[2]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
value : STRING
|
|
191
|
+
| NUMBER
|
|
192
|
+
| NULL
|
|
193
|
+
|
|
194
|
+
value_list : value
|
|
195
|
+
{
|
|
196
|
+
[val[0]]
|
|
197
|
+
}
|
|
198
|
+
| value_list ',' value
|
|
199
|
+
{
|
|
200
|
+
val[0] << val[2]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
op : IN { '$in' }
|
|
204
|
+
| REGEXP { '$regexp' }
|
|
205
|
+
| '<>' { :'!=' }
|
|
206
|
+
| '!=' { :'!=' }
|
|
207
|
+
| '>=' { :'>=' }
|
|
208
|
+
| '<=' { :'<=' }
|
|
209
|
+
| '>' { :'>' }
|
|
210
|
+
| '<' { :'<' }
|
|
211
|
+
| '=' { :'==' }
|
|
212
|
+
|
|
213
|
+
order_spec : ASC { :asc }
|
|
214
|
+
| DESC { :desc }
|
|
215
|
+
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
---- header
|
|
219
|
+
|
|
220
|
+
require 'strscan'
|
|
221
|
+
|
|
222
|
+
module ActiveCassandra
|
|
223
|
+
|
|
224
|
+
---- inner
|
|
225
|
+
|
|
226
|
+
KEYWORDS = %w(
|
|
227
|
+
AND
|
|
228
|
+
AS
|
|
229
|
+
ASC
|
|
230
|
+
BETWEEN
|
|
231
|
+
BY
|
|
232
|
+
COUNT
|
|
233
|
+
DELETE
|
|
234
|
+
DESC
|
|
235
|
+
DISTINCT
|
|
236
|
+
FROM
|
|
237
|
+
IN
|
|
238
|
+
INSERT
|
|
239
|
+
INTO
|
|
240
|
+
LIMIT
|
|
241
|
+
NOT
|
|
242
|
+
OFFSET
|
|
243
|
+
ORDER
|
|
244
|
+
REGEXP
|
|
245
|
+
SELECT
|
|
246
|
+
SET
|
|
247
|
+
UPDATE
|
|
248
|
+
VALUES
|
|
249
|
+
WHERE
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
KEYWORD_REGEXP = Regexp.compile("(?:#{KEYWORDS.join '|'})\\b", Regexp::IGNORECASE)
|
|
253
|
+
|
|
254
|
+
def initialize(obj)
|
|
255
|
+
src = obj.is_a?(IO) ? obj.read : obj.to_s
|
|
256
|
+
@ss = StringScanner.new(src)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def scan
|
|
260
|
+
piece = nil
|
|
261
|
+
|
|
262
|
+
until @ss.eos?
|
|
263
|
+
if (tok = @ss.scan /\s+/)
|
|
264
|
+
# nothing to do
|
|
265
|
+
elsif (tok = @ss.scan /(?:<>|!=|>=|<=|>|<|=)/)
|
|
266
|
+
yield tok, tok
|
|
267
|
+
elsif (tok = @ss.scan KEYWORD_REGEXP)
|
|
268
|
+
yield tok.upcase.to_sym, tok
|
|
269
|
+
elsif (tok = @ss.scan /NULL\b/i)
|
|
270
|
+
yield :NULL, nil
|
|
271
|
+
elsif (tok = @ss.scan /'(?:[^']|'')*'/) #'
|
|
272
|
+
yield :STRING, tok.slice(1...-1).gsub(/''/, "'")
|
|
273
|
+
elsif (tok = @ss.scan /-?(?:0|[1-9]\d*)(?:\.\d+)/)
|
|
274
|
+
yield :NUMBER, tok.to_f
|
|
275
|
+
elsif (tok = @ss.scan /-?(?:0|[1-9]\d*)/)
|
|
276
|
+
yield :NUMBER, tok.to_i
|
|
277
|
+
elsif (tok = @ss.scan /[,\(\)\*]/)
|
|
278
|
+
yield tok, tok
|
|
279
|
+
elsif (tok = @ss.scan /(?:[a-z_]\w+\.|[a-z]\.)*ID\b/i)
|
|
280
|
+
yield :ID, tok
|
|
281
|
+
elsif (tok = @ss.scan /(?:[a-z_]\w+\.|[a-z]\.)*(?:[a-z_]\w+|[a-z])/i)
|
|
282
|
+
yield :IDENTIFIER, tok
|
|
283
|
+
else
|
|
284
|
+
raise Racc::ParseError, ('parse error on value "%s"' % @ss.rest.inspect)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
yield false, '$'
|
|
289
|
+
end
|
|
290
|
+
private :scan
|
|
291
|
+
|
|
292
|
+
def parse
|
|
293
|
+
yyparse self, :scan
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
---- footer
|
|
297
|
+
|
|
298
|
+
end # module ActiveCassandra
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
require 'active_record/base'
|
|
2
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
|
3
|
+
require 'cassandra'
|
|
4
|
+
require 'active_cassandra/cf'
|
|
5
|
+
require 'active_cassandra/sqlparser.tab'
|
|
6
|
+
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
class Base
|
|
9
|
+
def self.cassandra_connection(config)
|
|
10
|
+
config.symbolize_keys!
|
|
11
|
+
host = config[:host] || '127.0.0.1'
|
|
12
|
+
port = config[:port] || 9160
|
|
13
|
+
|
|
14
|
+
unless (keyspace = config[:keyspace] || config[:database])
|
|
15
|
+
raise ArgumentError, "No database file specified. Missing argument: keyspace"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
thrift_client_options = config.dup
|
|
19
|
+
[:adapter, :host, :port, :keyspace, :database].each {|i| thrift_client_options.delete(i) }
|
|
20
|
+
|
|
21
|
+
client = Cassandra.new(keyspace, "#{host}:#{port}", thrift_client_options)
|
|
22
|
+
ConnectionAdapters::CassandraAdapter.new(client, logger, config)
|
|
23
|
+
end
|
|
24
|
+
end # class Base
|
|
25
|
+
|
|
26
|
+
module ConnectionAdapters
|
|
27
|
+
class CassandraAdapter < AbstractAdapter
|
|
28
|
+
def initialize(client, logger, config)
|
|
29
|
+
super(client, logger)
|
|
30
|
+
@config = config
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def supports_count_distinct?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def select(sql, name = nil)
|
|
38
|
+
log(sql, name)
|
|
39
|
+
|
|
40
|
+
parsed_sql = ActiveCassandra::SQLParser.new(sql).parse
|
|
41
|
+
|
|
42
|
+
cf = parsed_sql[:table].to_sym
|
|
43
|
+
cond = parsed_sql[:condition]
|
|
44
|
+
count = parsed_sql[:count]
|
|
45
|
+
# not implemented:
|
|
46
|
+
# distinct = parsed_sql[:distinct]
|
|
47
|
+
sqlopts, casopts = rowopts(parsed_sql)
|
|
48
|
+
|
|
49
|
+
if count and cond.empty? and sqlopts.empty?
|
|
50
|
+
[{count => @connection.count_range(cf, casopts)}]
|
|
51
|
+
elsif is_id?(cond)
|
|
52
|
+
ks = [cond].flatten
|
|
53
|
+
@connection.multi_get(cf, ks, casopts).values
|
|
54
|
+
else
|
|
55
|
+
rows = @connection.get_range(cf, casopts).select {|i| i.columns.length > 0 }.map do |key_slice|
|
|
56
|
+
key_slice_to_hash(key_slice)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
unless cond.empty?
|
|
60
|
+
rows = filter(cond).call(rows)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if (offset = sqlopts[:offset])
|
|
64
|
+
rows = rows.slice(offset..-1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if (limit = sqlopts[:limit])
|
|
68
|
+
rows = rows.slice(0, limit)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
count ? [{count => rows.length}] : rows
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
76
|
+
log(sql, name)
|
|
77
|
+
|
|
78
|
+
parsed_sql = ActiveCassandra::SQLParser.new(sql).parse
|
|
79
|
+
table = parsed_sql[:table]
|
|
80
|
+
cf = table.to_sym
|
|
81
|
+
column_list = parsed_sql[:column_list]
|
|
82
|
+
value_list = parsed_sql[:value_list]
|
|
83
|
+
|
|
84
|
+
class_name = ActiveRecord::Base.class_name(table)
|
|
85
|
+
rowid = Module.const_get(class_name).__identify.to_s
|
|
86
|
+
|
|
87
|
+
nvs = {}
|
|
88
|
+
column_list.zip(value_list).each {|n, v| nvs[n] = v.to_s }
|
|
89
|
+
|
|
90
|
+
@connection.insert(cf, rowid, nvs)
|
|
91
|
+
|
|
92
|
+
return rowid
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def update_sql(sql, name = nil)
|
|
96
|
+
log(sql, name)
|
|
97
|
+
parsed_sql = ActiveCassandra::SQLParser.new(sql).parse
|
|
98
|
+
cf = parsed_sql[:table].to_sym
|
|
99
|
+
cond = parsed_sql[:condition]
|
|
100
|
+
|
|
101
|
+
nvs = {}
|
|
102
|
+
parsed_sql[:set_clause_list].each do |n, v|
|
|
103
|
+
n = n.split('.').last
|
|
104
|
+
nvs[n] = v.to_s
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
n = 0
|
|
108
|
+
|
|
109
|
+
if is_id?(cond)
|
|
110
|
+
ks = [cond].flatten
|
|
111
|
+
rs = @connection.multi_get(cf, ks)
|
|
112
|
+
|
|
113
|
+
ks.each do |key|
|
|
114
|
+
row = rs[key]
|
|
115
|
+
@connection.insert(cf, key, row.merge(nvs))
|
|
116
|
+
n += 1
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
rows = @connection.get_range(cf).select {|i| i.columns.length > 0 }.map do |key_slice|
|
|
120
|
+
key_slice_to_hash(key_slice)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
unless cond.empty?
|
|
124
|
+
rows = filter(cond).call(rows)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
rows.each do |row|
|
|
128
|
+
@connection.insert(cf, row['id'], row.merge(nvs))
|
|
129
|
+
n += 1
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
return n
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def delete_sql(sql, name = nil)
|
|
137
|
+
log(sql, name)
|
|
138
|
+
|
|
139
|
+
parsed_sql = ActiveCassandra::SQLParser.new(sql).parse
|
|
140
|
+
cf = parsed_sql[:table].to_sym
|
|
141
|
+
cond = parsed_sql[:condition]
|
|
142
|
+
|
|
143
|
+
n = 0
|
|
144
|
+
|
|
145
|
+
if is_id?(cond)
|
|
146
|
+
[cond].flatten.each do |key|
|
|
147
|
+
@connection.remove(cf, key)
|
|
148
|
+
n += 1
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
rows = @connection.get_range(cf).select {|i| i.columns.length > 0 }
|
|
152
|
+
|
|
153
|
+
unless cond.empty?
|
|
154
|
+
rows = rows.map {|i| key_slice_to_hash(i) }
|
|
155
|
+
rows = filter(cond).call(rows)
|
|
156
|
+
|
|
157
|
+
rows.each do |row|
|
|
158
|
+
@connection.remove(cf, row['id'])
|
|
159
|
+
n += 1
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
rows.each do |key_slice|
|
|
163
|
+
@connection.remove(cf, key_slice.key)
|
|
164
|
+
n += 1
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
return n
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def add_limit_offset!(sql, options)
|
|
173
|
+
if (limit = options[:limit])
|
|
174
|
+
if limit.kind_of?(Numeric)
|
|
175
|
+
sql << " LIMIT #{limit.to_i}"
|
|
176
|
+
else
|
|
177
|
+
sql << " LIMIT #{quote(limit)}"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if (offset = options[:offset])
|
|
182
|
+
if offset.kind_of?(Numeric)
|
|
183
|
+
sql << " OFFSET #{offset.to_i}"
|
|
184
|
+
else
|
|
185
|
+
sql << " OFFSET #{quote(offset)}"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
def key_slice_to_hash(key_slice)
|
|
192
|
+
hash = {'id' => key_slice.key}
|
|
193
|
+
|
|
194
|
+
key_slice.columns.each do |i|
|
|
195
|
+
column = i.column
|
|
196
|
+
hash[column.name] = column.value
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
return hash
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def is_id?(cond)
|
|
203
|
+
not cond.kind_of?(Array) or not cond.all? {|i| i.kind_of?(Hash) }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def filter(cond)
|
|
207
|
+
fs = []
|
|
208
|
+
|
|
209
|
+
cond.each do |c|
|
|
210
|
+
name, op, expr, has_not = c.values_at(:name, :op, :expr, :not)
|
|
211
|
+
name = name.split('.').last
|
|
212
|
+
expr = Regexp.compile(expr) if op == '$regexp'
|
|
213
|
+
|
|
214
|
+
func = case op
|
|
215
|
+
when '$in'
|
|
216
|
+
lambda {|i| expr.include?(i) }
|
|
217
|
+
when '$bt'
|
|
218
|
+
lambda {|i| expr[0] <= i and i <= expr[1] }
|
|
219
|
+
when '$regexp'
|
|
220
|
+
lambda {|i| i =~ Regexp.compile(expr) }
|
|
221
|
+
when :'>=', :'<=', :'>', :'<'
|
|
222
|
+
lambda {|i| i.to_i.send(op, expr.to_i) }
|
|
223
|
+
else
|
|
224
|
+
lambda {|i| i.send(op, expr) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
fs << (has_not ? lambda {|row| not func.call(row[name]) } : lambda {|row| func.call(row[name])})
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
lambda do |rows|
|
|
231
|
+
fs.inject(rows) {|r, f| r.select {|i| f.call(i) } }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def rowopts(parsed_sql)
|
|
236
|
+
order, limit, offset = parsed_sql.values_at(:order, :limit, :offset)
|
|
237
|
+
sqlopts = {}
|
|
238
|
+
casopts = {}
|
|
239
|
+
|
|
240
|
+
# not implemented:
|
|
241
|
+
# if order
|
|
242
|
+
# name, type = order.values_at(:name, :type)
|
|
243
|
+
# ...
|
|
244
|
+
# end
|
|
245
|
+
|
|
246
|
+
if offset
|
|
247
|
+
if offset.kind_of?(Numeric)
|
|
248
|
+
sqlopts[:offset] = offset
|
|
249
|
+
else
|
|
250
|
+
# XXX: offset is not equals to SQL OFFSET
|
|
251
|
+
casopts[:start] = offset
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if limit
|
|
256
|
+
if limit.kind_of?(Numeric)
|
|
257
|
+
sqlopts[:limit] = limit
|
|
258
|
+
else
|
|
259
|
+
# XXX: limit is not equals to SQL LIMIT
|
|
260
|
+
casopts[:finish] = limit
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
return [sqlopts, casopts]
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
end # class CassandraAdapter
|
|
268
|
+
end # module ConnectionAdapters
|
|
269
|
+
end # module ActiveRecord
|