sql_query_executor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ require 'sql_query_executor/operators/base'
2
+
3
+ module SqlQueryExecutor
4
+ module Operators
5
+ class Default < SqlQueryExecutor::Operators::Base
6
+ def initialize(query, collection)
7
+ super
8
+ convert_operator
9
+ end
10
+
11
+ def execute!
12
+ @collection.select do |record|
13
+ value = record.send(@field.to_s)
14
+
15
+ value.send(@operator, @value) rescue false
16
+ end
17
+ end
18
+
19
+ private
20
+ def convert_operator
21
+ @operator = @operator == "=" ? "==" : @operator
22
+ @operator = @operator == "<>" ? "!=" : @operator
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require 'sql_query_executor/operators/base'
2
+
3
+ module SqlQueryExecutor
4
+ module Operators
5
+ class In < SqlQueryExecutor::Operators::Base
6
+ def execute!
7
+ @collection.select do |record|
8
+ value = record.send(@field)
9
+
10
+ @value.send('include?', value)
11
+ end
12
+ end
13
+
14
+ private
15
+ def get_value
16
+ value = super
17
+
18
+ value.gsub('$S$', '').split(',').map &:strip
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ require 'sql_query_executor/operators/base'
2
+
3
+ module SqlQueryExecutor
4
+ module Operators
5
+ class Is < SqlQueryExecutor::Operators::Base
6
+ def initialize(query, collection)
7
+ super
8
+ convert_operator
9
+ end
10
+
11
+ def execute!
12
+ @collection.select do |record|
13
+ value = record.send(@field)
14
+
15
+ value.send(@operator, nil)
16
+ end
17
+ end
18
+
19
+ private
20
+ def get_value
21
+ @array.include?('null') ? 'nil' : @array.last
22
+ end
23
+
24
+ def convert_operator
25
+ @operator = @array.include?('not') ? '!=' : '=='
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,106 @@
1
+ require 'sql_query_executor/query/executor'
2
+
3
+ module SqlQueryExecutor
4
+ module Query
5
+ class Base
6
+ attr_reader :query
7
+
8
+ CONVERT_METHODS = {"String" => ["get_query", ""], "Array" => ["interpolate_query", "query.flatten"], "Hash" => ["concatenate_hash", "query"]}
9
+
10
+ def initialize(query, collection)
11
+ query = clean_query_attribute(query)
12
+ array = CONVERT_METHODS[query.class.name]
13
+
14
+ @query = send(array.first, query)
15
+ @collection = collection
16
+ sanitize
17
+ end
18
+
19
+ def execute!
20
+ SqlQueryExecutor::Query::Executor.execute!(@query, @collection)
21
+ end
22
+
23
+ private
24
+ def clean_query_attribute(query)
25
+ return query unless query.is_a?(Array)
26
+
27
+ query = query.flatten
28
+
29
+ query.size == 1 ? query.first : query
30
+ end
31
+
32
+ def get_query(query)
33
+ query
34
+ end
35
+
36
+ # Prepares query by replacing all ? by it's real values in #args
37
+ def interpolate_query(args)
38
+ args.flatten!
39
+ return args.first if args.size == 1 && args.first.is_a?(String)
40
+
41
+ query = args.first
42
+ param = args.delete_at(1)
43
+
44
+ param = convert_param(param)
45
+
46
+ args[0] = query.sub("?", param)
47
+
48
+ interpolate_query(args)
49
+ end
50
+
51
+ # Removes all accents and other non default characters
52
+ def sanitize
53
+ cruft = /\'|\"|\(|\)/
54
+
55
+ new_query = @query ? @query.dup : @query
56
+ params = new_query.scan(/(["|'].*?["|'])|(\(.*?\))/).flatten.compact
57
+
58
+ params.each do |param|
59
+ new_param = param.dup
60
+
61
+ new_param = new_param.gsub(cruft,"")
62
+ new_param = new_param.gsub(" ", "$S$")
63
+
64
+ new_query = new_query.gsub(param, new_param)
65
+ end
66
+
67
+ @query = new_query
68
+
69
+ remove_spaces
70
+ end
71
+
72
+ def remove_spaces
73
+ @query.gsub!(/\[.*?\]/) { |substr| substr.gsub(' ', '') }
74
+ end
75
+
76
+ # Returns converted #param based on its Class, so it can be used on the query
77
+ def convert_param(param)
78
+ case param.class.name
79
+ when "String"
80
+ param = "'#{param}'"
81
+ when "Date"
82
+ param = "'#{param.strftime("%Y-%m-%d")}'"
83
+ when "Time"
84
+ param = "'#{param.strftime("%Y-%m-%d %H:%M:%S %z")}'"
85
+ else
86
+ param = param.to_s
87
+ end
88
+ end
89
+
90
+ def concatenate_hash(query)
91
+ return "" unless query.is_a?(Hash)
92
+ query_array = []
93
+
94
+ query.each do |key, value|
95
+ if value.is_a?(Array)
96
+ query_array << "#{key} in (#{value.join(',')})"
97
+ else
98
+ query_array << "#{key} = #{value}"
99
+ end
100
+ end
101
+
102
+ query_array.join(" and ")
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,56 @@
1
+ require 'sql_query_executor/query/sub_query'
2
+
3
+ module SqlQueryExecutor
4
+ module Query
5
+ class Divider
6
+ BINDING_OPERATORS = ['and', 'or']
7
+
8
+ class << self
9
+ def divide(query, collection)
10
+ @query = query
11
+ divide_query(collection)
12
+ end
13
+
14
+ private
15
+ # Splits the first sub query from the rest of the query and returns it.
16
+ def divide_query(collection)
17
+ array = @query.split(" ")
18
+ operator = array[1].downcase
19
+
20
+ sub_query = get_sub_array(operator, array)
21
+ remaining_query = @query.gsub(sub_query, '').strip
22
+
23
+ sub_query = SqlQueryExecutor::Query::SubQuery.new(sub_query, collection)
24
+
25
+ return sub_query, remaining_query == '' ? nil : remaining_query
26
+ end
27
+
28
+ def get_sub_array(operator, array)
29
+ result = case operator
30
+ when "between"
31
+ array[0..4]
32
+ when "is"
33
+ size = array[2] == "not" ? 3 : 2
34
+ array[0..size]
35
+ when "not"
36
+ array[0..3]
37
+ else
38
+ array[0..2]
39
+ end
40
+
41
+ result = fix_array(array, result)
42
+
43
+ result.join(' ')
44
+ end
45
+
46
+ def fix_array(array, result)
47
+ if BINDING_OPERATORS.include?(result.first)
48
+ array[0..result.size]
49
+ else
50
+ result
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,17 @@
1
+ require 'sql_query_executor/query/divider'
2
+
3
+ module SqlQueryExecutor
4
+ module Query
5
+ class Executor
6
+ class << self
7
+ def execute!(query, collection, data=[])
8
+ sub_query, remaining_query = SqlQueryExecutor::Query::Divider.divide(query, collection)
9
+
10
+ result = sub_query.execute!(data)
11
+
12
+ remaining_query.nil? ? result.sort_by{ |r| r.id } : execute!(remaining_query, collection, result)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,68 @@
1
+ require 'sql_query_executor/operators/default'
2
+ require 'sql_query_executor/operators/between'
3
+ require 'sql_query_executor/operators/is'
4
+ require 'sql_query_executor/operators/in'
5
+
6
+ module SqlQueryExecutor
7
+ module Query
8
+ class SubQuery
9
+ OPERATORS = {
10
+ "between" => SqlQueryExecutor::Operators::Between,
11
+ "is" => SqlQueryExecutor::Operators::Is,
12
+ "in" => SqlQueryExecutor::Operators::In,
13
+ "=" => SqlQueryExecutor::Operators::Default,
14
+ ">" => SqlQueryExecutor::Operators::Default,
15
+ "<" => SqlQueryExecutor::Operators::Default,
16
+ ">=" => SqlQueryExecutor::Operators::Default,
17
+ "<=" => SqlQueryExecutor::Operators::Default,
18
+ "<>" => SqlQueryExecutor::Operators::Default,
19
+ "!=" => SqlQueryExecutor::Operators::Default,
20
+ "not" => SqlQueryExecutor::Operators::Default,
21
+ "exists" => SqlQueryExecutor::Operators::Default,#Exists
22
+ }
23
+
24
+ BINDING_OPERATORS = {
25
+ "and" => "&",
26
+ "or" => "+"
27
+ }
28
+
29
+ def initialize(query, collection)
30
+ @query = query
31
+ @collection = collection
32
+ @array = query.split(' ')
33
+
34
+ set_binding_operator
35
+ set_operator
36
+ end
37
+
38
+ def execute!(data)
39
+ return [] unless @operator
40
+
41
+ result = @operator.execute!
42
+
43
+ result = data.send(@binding_operator, result) if @binding_operator && (data && !data.empty?)
44
+
45
+ result
46
+ end
47
+
48
+ private
49
+ def set_operator
50
+ operator = OPERATORS[@query.split(' ')[1]]
51
+
52
+ @operator = operator ? operator.new(@query, @collection) : nil
53
+ end
54
+
55
+ def set_binding_operator
56
+ @binding_operator = BINDING_OPERATORS[@array.first]
57
+
58
+ fix_query if @binding_operator
59
+ end
60
+
61
+ def fix_query
62
+ @array.delete_at(0)
63
+
64
+ @query = @array.join(' ')
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ module SqlQueryExecutor
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,446 @@
1
+ require 'spec_helper'
2
+
3
+ require 'sql_query_executor'
4
+
5
+ describe SqlQueryExecutor, "Base" do
6
+ before do
7
+ @data = []
8
+
9
+ @data << {:id => 1, :name => "US", :language => 'English'}
10
+ @data << {:id => 2, :name => "Canada", :language => 'English', :monarch => "The Crown of England"}
11
+ @data << {:id => 3, :name => "Mexico", :language => 'Spanish'}
12
+ @data << {:id => 4, :name => "UK", :language => 'English', :monarch => "The Crown of England"}
13
+ @data << {:id => 5, :name => "Brazil", :founded_at => Time.parse('1500-04-22 13:34:25')}
14
+ end
15
+
16
+ subject { SqlQueryExecutor::Base.new(@data) }
17
+
18
+ describe ".where" do
19
+ describe "=" do
20
+ context "when attribute is string" do
21
+ it "matches a record" do
22
+ record = subject.where("name = 'US'")
23
+ record.size.should == 1
24
+ record.first.id.should == 1
25
+ record.first.name.should == 'US'
26
+ end
27
+
28
+ it "doesn't match any record" do
29
+ record = subject.where("name = 'Argentina'")
30
+ record.count.should == 0
31
+ record.should == []
32
+ end
33
+ end
34
+
35
+ context "when attribute is integer" do
36
+ it "matches a record" do
37
+ record = subject.where("id = 1")
38
+ record.count.should == 1
39
+ record.first.id.should == 1
40
+ record.first.name.should == 'US'
41
+ end
42
+
43
+ it "doesn't match any record" do
44
+ record = subject.where("id = 43")
45
+ record.count.should == 0
46
+ record.should == []
47
+ end
48
+ end
49
+
50
+ context "when attribute is datetime" do
51
+ it "matches a record" do
52
+ record = subject.where("founded_at = ?", Time.parse('1500-04-22 13:34:25'))
53
+ record.count.should == 1
54
+ record.first.id.should == 5
55
+ record.first.name.should == 'Brazil'
56
+ end
57
+
58
+ it "doesn't match any record" do
59
+ record = subject.where("id = ?", Time.parse('1500-09-07 13:34:25'))
60
+ record.count.should == 0
61
+ record.should == []
62
+ end
63
+ end
64
+ end
65
+
66
+ describe ">" do
67
+ context "when attribute is a string" do
68
+ it "matches a record" do
69
+ records = subject.where("name > 'T'")
70
+ records.count.should == 2
71
+ records.first.id.should == 1
72
+
73
+ records.map! do |record|
74
+ record.to_h
75
+ end
76
+
77
+ records.should == [@data[0], @data[3]]
78
+ end
79
+
80
+ it "doesn't match any record" do
81
+ record = subject.where("name > 'Z'")
82
+ record.count.should == 0
83
+ record.should == []
84
+ end
85
+ end
86
+
87
+ context "when attribute is integer" do
88
+ it "matches a record" do
89
+ records = subject.where("id > 3")
90
+ records.count.should == 2
91
+ records.first.id.should == 4
92
+
93
+ records.map! do |record|
94
+ record.to_h
95
+ end
96
+
97
+ records.should == [@data[3], @data[4]]
98
+ end
99
+
100
+ it "doesn't match any record" do
101
+ record = subject.where("id > 5")
102
+ record.count.should == 0
103
+ record.should == []
104
+ end
105
+ end
106
+
107
+ context "when attribute is datetime" do
108
+ it "matches a record" do
109
+ record = subject.where("founded_at > ?", Time.parse('1500-04-20 13:34:25'))
110
+ record.count.should == 1
111
+ record.first.id.should == 5
112
+ record.first.name.should == 'Brazil'
113
+ end
114
+
115
+ it "doesn't match any record" do
116
+ record = subject.where("founded_at > ?", Time.parse('1500-04-23 13:34:25'))
117
+ record.count.should == 0
118
+ record.should == []
119
+ end
120
+ end
121
+ end
122
+
123
+ describe ">=" do
124
+ context "when attribute is a string" do
125
+ it "matches a record" do
126
+ records = subject.where("name >= 'U'")
127
+ records.count.should == 2
128
+ records.first.id.should == 1
129
+
130
+ records.map! do |record|
131
+ record.to_h
132
+ end
133
+
134
+ records.should == [@data[0], @data[3]]
135
+ end
136
+
137
+ it "doesn't match any record" do
138
+ record = subject.where("name >= 'Z'")
139
+ record.count.should == 0
140
+ record.should == []
141
+ end
142
+ end
143
+
144
+ context "when attribute is integer" do
145
+ it "matches a record" do
146
+ records = subject.where("id >= 4")
147
+ records.count.should == 2
148
+ records.first.id.should == 4
149
+
150
+ records.map! do |record|
151
+ record.to_h
152
+ end
153
+
154
+ records.should == [@data[3], @data[4]]
155
+ end
156
+
157
+ it "doesn't match any record" do
158
+ record = subject.where("id >= 6")
159
+ record.count.should == 0
160
+ record.should == []
161
+ end
162
+ end
163
+
164
+ context "when attribute is datetime" do
165
+ it "matches a record" do
166
+ record = subject.where("founded_at >= ?", Time.parse('1500-04-22 13:34:25'))
167
+ record.count.should == 1
168
+ record.first.id.should == 5
169
+ record.first.name.should == 'Brazil'
170
+ end
171
+
172
+ it "doesn't match any record" do
173
+ record = subject.where("founded_at >= ?", Time.parse('1500-04-23 13:34:25'))
174
+ record.count.should == 0
175
+ record.should == []
176
+ end
177
+ end
178
+ end
179
+
180
+ describe "<" do
181
+ context "when attribute is a string" do
182
+ it "matches a record" do
183
+ records = subject.where("name < 'C'")
184
+ records.count.should == 1
185
+ records.first.id.should == 5
186
+
187
+ records.map! do |record|
188
+ record.to_h
189
+ end
190
+
191
+ records.should == [@data[4]]
192
+ end
193
+
194
+ it "doesn't match any record" do
195
+ record = subject.where("name < 'B'")
196
+ record.count.should == 0
197
+ record.should == []
198
+ end
199
+ end
200
+
201
+ context "when attribute is integer" do
202
+ it "matches a record" do
203
+ records = subject.where("id < 3")
204
+ records.count.should == 2
205
+ records.first.id.should == 1
206
+
207
+ records.map! do |record|
208
+ record.to_h
209
+ end
210
+
211
+ records.should == [@data[0], @data[1]]
212
+ end
213
+
214
+ it "doesn't match any record" do
215
+ record = subject.where("id < 1")
216
+ record.count.should == 0
217
+ record.should == []
218
+ end
219
+ end
220
+
221
+ context "when attribute is datetime" do
222
+ it "matches a record" do
223
+ record = subject.where("founded_at < ?", Time.parse('1500-04-22 13:34:26'))
224
+ record.count.should == 1
225
+ record.first.id.should == 5
226
+ record.first.name.should == 'Brazil'
227
+ end
228
+
229
+ it "doesn't match any record" do
230
+ record = subject.where("founded_at < ?", Time.parse('1500-04-22 13:34:25'))
231
+ record.count.should == 0
232
+ record.should == []
233
+ end
234
+ end
235
+ end
236
+
237
+ describe "<=" do
238
+ context "when attribute is a string" do
239
+ it "matches a record" do
240
+ records = subject.where("name <= 'Brb'")
241
+ records.count.should == 1
242
+ records.first.id.should == 5
243
+
244
+ records.map! do |record|
245
+ record.to_h
246
+ end
247
+
248
+ records.should == [@data[4]]
249
+ end
250
+
251
+ it "doesn't match any record" do
252
+ record = subject.where("name <= 'A'")
253
+ record.count.should == 0
254
+ record.should == []
255
+ end
256
+ end
257
+
258
+ context "when attribute is integer" do
259
+ it "matches a record" do
260
+ records = subject.where("id <= 2")
261
+ records.count.should == 2
262
+ records.first.id.should == 1
263
+
264
+ records.map! do |record|
265
+ record.to_h
266
+ end
267
+
268
+ records.should == [@data[0], @data[1]]
269
+ end
270
+
271
+ it "doesn't match any record" do
272
+ record = subject.where("id <= 0")
273
+ record.count.should == 0
274
+ record.should == []
275
+ end
276
+ end
277
+
278
+ context "when attribute is datetime" do
279
+ it "matches a record" do
280
+ record = subject.where("founded_at <= ?", Time.parse('1500-04-22 13:34:25'))
281
+ record.count.should == 1
282
+ record.first.id.should == 5
283
+ record.first.name.should == 'Brazil'
284
+ end
285
+
286
+ it "doesn't match any record" do
287
+ record = subject.where("founded_at <= ?", Time.parse('1500-04-22 13:34:24'))
288
+ record.count.should == 0
289
+ record.should == []
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "between" do
295
+ context "when attribute is a string" do
296
+ it "matches a record" do
297
+ records = subject.where("name between 'A' and 'C'")
298
+ records.count.should == 1
299
+ records.first.id.should == 5
300
+
301
+ records.map! do |record|
302
+ record.to_h
303
+ end
304
+
305
+ records.should == [@data[4]]
306
+ end
307
+
308
+ it "doesn't match any record" do
309
+ record = subject.where("name between 'K' and 'M'")
310
+ record.count.should == 0
311
+ record.should == []
312
+ end
313
+ end
314
+
315
+ context "when attribute is integer" do
316
+ it "matches a record" do
317
+ records = subject.where("id between 1 and 2")
318
+ records.count.should == 2
319
+ records.first.id.should == 1
320
+
321
+ records.map! do |record|
322
+ record.to_h
323
+ end
324
+
325
+ records.should == [@data[0], @data[1]]
326
+ end
327
+
328
+ it "doesn't match any record" do
329
+ record = subject.where("id between 6 and 10")
330
+ record.count.should == 0
331
+ record.should == []
332
+ end
333
+ end
334
+
335
+ context "when attribute is datetime" do
336
+ it "matches a record" do
337
+ record = subject.where("founded_at between ? and ?", Time.parse('1500-04-22 13:34:24'), Time.parse('1500-04-22 13:34:26'))
338
+ record.count.should == 1
339
+ record.first.id.should == 5
340
+ record.first.name.should == 'Brazil'
341
+ end
342
+
343
+ it "doesn't match any record" do
344
+ record = subject.where("founded_at between ? and ?", Time.parse('1500-04-22 13:34:26'), Time.parse('1500-09-22 13:34:25'))
345
+ record.count.should == 0
346
+ record.should == []
347
+ end
348
+ end
349
+ end
350
+
351
+ describe "is" do
352
+ it "attribute is condition" do
353
+ records = subject.where("founded_at is null")
354
+ records.count.should == 4
355
+ records.first.id.should == 1
356
+
357
+ records.map! do |record|
358
+ record.to_h
359
+ end
360
+
361
+ records.should == [@data[0], @data[1], @data[2], @data[3]]
362
+ end
363
+
364
+ it "attribute is not condition" do
365
+ id = @data.last[:id]
366
+ records = subject.where("founded_at is not null")
367
+ records.count.should == 1
368
+ records.first.id.should == id
369
+
370
+ records.map! do |record|
371
+ record.to_h
372
+ end
373
+
374
+ records.should == [@data[id-1]]
375
+ end
376
+ end
377
+
378
+ describe "and" do
379
+ it "attribute and condition" do
380
+ records = subject.where("language = 'English' and monarch = 'The Crown of England'")
381
+ records.count.should == 2
382
+ records.first.id.should == 2
383
+
384
+ records.map! do |record|
385
+ record.to_h
386
+ end
387
+
388
+ records.should == [@data[1], @data[3]]
389
+ end
390
+
391
+ it "integer attribute and condition" do
392
+ records = subject.where("id = 2 and language = 'English'")
393
+ records.count.should == 1
394
+ records.first.id.should == 2
395
+
396
+ records.map! do |record|
397
+ record.to_h
398
+ end
399
+
400
+ records.should == [@data[1]]
401
+ end
402
+ end
403
+
404
+ describe "or" do
405
+ it "attribute or condition" do
406
+ records = subject.where("language = 'English' or language = 'Spanish'")
407
+ records.count.should == 4
408
+ records.first.id.should == 1
409
+
410
+ records.map! do |record|
411
+ record.to_h
412
+ end
413
+
414
+ records.should == [@data[0], @data[1], @data[2], @data[3]]
415
+ end
416
+ end
417
+
418
+ describe "in" do
419
+ it "attribute in condition" do
420
+ records = subject.where("language in ('English', 'Spanish')")
421
+ records.count.should == 4
422
+ records.first.id.should == 1
423
+
424
+ records.map! do |record|
425
+ record.to_h
426
+ end
427
+
428
+ records.should == [@data[0], @data[1], @data[2], @data[3]]
429
+ end
430
+ end
431
+
432
+ describe "not" do
433
+ xit "attribute not condition" do
434
+ records = subject.where("not language = 'English'")
435
+ records.count.should == 1
436
+ records.first.id.should == 5
437
+
438
+ records.map! do |record|
439
+ record.to_h
440
+ end
441
+
442
+ records.should == [@data[4]]
443
+ end
444
+ end
445
+ end
446
+ end