sql_query_executor 0.0.1

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,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