fluent-query 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ require "fluent-query/exception"
3
+
4
+ module FluentQuery
5
+ module Drivers
6
+ ##
7
+ # Represents general fluent exception.
8
+ #
9
+
10
+ class Exception < FluentQuery::Exception
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+ require "abstract"
3
+
4
+ module FluentQuery
5
+ module Drivers
6
+
7
+ ##
8
+ # Represents general driver result accessor.
9
+ #
10
+
11
+ class Result
12
+
13
+ ##
14
+ # Initializes result.
15
+ #
16
+
17
+ public
18
+ def initialize
19
+ if self.instance_of? FluentQuery::Drivers::Result
20
+ not_implemented
21
+ end
22
+ end
23
+
24
+ ##
25
+ # Returns all selected rows.
26
+ #
27
+
28
+ public
29
+ def all
30
+ not_implemented
31
+ end
32
+
33
+ ##
34
+ # Returns one row.
35
+ #
36
+
37
+ public
38
+ def one
39
+ not_implemented
40
+ end
41
+
42
+ ##
43
+ # Returns first value of first row.
44
+ #
45
+
46
+ public
47
+ def single
48
+ not_implemented
49
+ end
50
+
51
+ ##
52
+ # Returns row as hash.
53
+ #
54
+
55
+ public
56
+ def hash
57
+ not_implemented
58
+ end
59
+
60
+ ##
61
+ # Handles iterating.
62
+ #
63
+
64
+ public
65
+ def each
66
+ not_implemented
67
+ end
68
+
69
+
70
+ ##
71
+ # Repeats the query leaded to the result.
72
+ #
73
+
74
+ public
75
+ def repeat!
76
+ not_implemented
77
+ end
78
+
79
+ ##
80
+ # Returns rows count.
81
+ #
82
+
83
+ public
84
+ def count
85
+ not_implemented
86
+ end
87
+
88
+ ##
89
+ # Frees result resources.
90
+ #
91
+
92
+ public
93
+ def free!
94
+ not_implemented
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ module FluentQuery
4
+
5
+ ##
6
+ # Represents general fluent exception.
7
+ #
8
+
9
+ class Exception < ::Exception
10
+ end
11
+
12
+ end
@@ -0,0 +1,169 @@
1
+ # encoding: utf-8
2
+ require "fluent-query/queries/processor"
3
+ require "abstract"
4
+
5
+ module FluentQuery
6
+ module Queries
7
+
8
+ ##
9
+ # Represents and abstract query.
10
+ #
11
+
12
+ class Abstract
13
+
14
+ ##
15
+ # Holds connection object.
16
+ #
17
+
18
+ @connection
19
+ attr_reader :connection
20
+
21
+ ##
22
+ # Holds appropriate query processor.
23
+ #
24
+
25
+ @processor
26
+
27
+ ##
28
+ # Constructor.
29
+ #
30
+
31
+ public
32
+ def initialize(connection)
33
+ @connection = connection
34
+ end
35
+
36
+ ##
37
+ # Returns processor.
38
+ #
39
+
40
+ public
41
+ def processor
42
+ if not @processor
43
+ driver = @connection.driver
44
+ @processor = FluentQuery::Queries::Processor::new(driver)
45
+ end
46
+
47
+ return @processor
48
+ end
49
+
50
+ ##
51
+ # Builds prepared query string to final form.
52
+ #
53
+
54
+ public
55
+ def build(*args)
56
+ not_implemented
57
+ end
58
+
59
+ alias :"build!" :build
60
+
61
+ ##
62
+ # Executes query and returns result object.
63
+ #
64
+
65
+ public
66
+ def execute(*args)
67
+ @connection.execute(self.build(*args))
68
+ end
69
+
70
+ alias :"execute!" :execute
71
+
72
+ ##
73
+ # Executes query and returns count of changed/inserted rows.
74
+ #
75
+
76
+ public
77
+ def do(*args)
78
+ @connection.do(self.build(*args))
79
+ end
80
+
81
+ alias :"do!" :do
82
+
83
+
84
+ ##
85
+ # Returns all selected rows.
86
+ #
87
+
88
+ public
89
+ def all(*args)
90
+ self.execute(*args).all
91
+ end
92
+
93
+
94
+ ##
95
+ # Returns one row.
96
+ #
97
+
98
+ public
99
+ def one(*args)
100
+ self.execute(*args).one
101
+ end
102
+
103
+ ##
104
+ # Returns all selected rows ordered according to datafield from it.
105
+ #
106
+
107
+ public
108
+ def assoc(*specification, &block)
109
+ self.execute.assoc(*specification, &block)
110
+ end
111
+
112
+ ##
113
+ # Returns first value of first row.
114
+ #
115
+
116
+ public
117
+ def single(*args)
118
+ self.execute(*args).single
119
+ end
120
+
121
+ ##
122
+ # Handles iterating.
123
+ #
124
+
125
+ public
126
+ def each(*args)
127
+ self.execute(*args).each do |item|
128
+ yield item
129
+ end
130
+ end
131
+
132
+ ##
133
+ # Maps callback to array.
134
+ #
135
+
136
+ public
137
+ def map(*args, &block)
138
+ result = [ ]
139
+
140
+ self.each(*args) do |item|
141
+ result << block.call(item)
142
+ end
143
+
144
+ return result
145
+ end
146
+
147
+ ##
148
+ # Returns only rows for which block is true.
149
+ #
150
+
151
+ public
152
+ def find_all(*args, &block)
153
+ result = [ ]
154
+
155
+ self.each(*args) do |item|
156
+ if block.call(item) === true
157
+ result << block.call(item)
158
+ end
159
+ end
160
+
161
+ return result
162
+ end
163
+
164
+ alias :filter :find_all
165
+
166
+ end
167
+ end
168
+ end
169
+
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ require "fluent-query/queries/abstract"
3
+
4
+ module FluentQuery
5
+ module Queries
6
+
7
+ ##
8
+ # Compiled query.
9
+ #
10
+
11
+ class Compiled < FluentQuery::Queries::Abstract
12
+
13
+ ##
14
+ # Holds query in compiled form.
15
+ #
16
+
17
+ @raw
18
+ attr_accessor :raw
19
+
20
+ ##
21
+ # Constructor.
22
+ #
23
+
24
+ public
25
+ def initialize(connection, query)
26
+ super(connection)
27
+ @raw = query.processor.compile(@connection.driver.build_query(query, :compile))
28
+ end
29
+
30
+ ##
31
+ # Builds prepared query string to final form.
32
+ #
33
+
34
+ public
35
+ def build(*args)
36
+ @raw.complete(*args)
37
+ end
38
+
39
+ ##
40
+ # Returns all selected rows ordered according to datafield from it.
41
+ #
42
+
43
+ public
44
+ def assoc(specification, *args)
45
+ self.execute(*args).assoc(specification)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ require "fluent-query/queries/abstract"
3
+
4
+ module FluentQuery
5
+ module Queries
6
+
7
+ ##
8
+ # Compiled query.
9
+ #
10
+
11
+ class Prepared < FluentQuery::Queries::Abstract
12
+
13
+ ##
14
+ # Holds query in prepared form.
15
+ #
16
+
17
+ @query
18
+
19
+ ##
20
+ # Constructor.
21
+ #
22
+
23
+ public
24
+ def initialize(connection, query)
25
+ super(connection)
26
+ @query = @connection.driver.prepare(query)
27
+ end
28
+
29
+ ##
30
+ # Builds prepared query string to final form.
31
+ #
32
+
33
+ public
34
+ def build(*args)
35
+ [@query, args]
36
+ end
37
+
38
+ ##
39
+ # Returns all selected rows ordered according to datafield from it.
40
+ #
41
+
42
+ public
43
+ def assoc(specification, *args)
44
+ self.execute(*args).assoc(specification)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,392 @@
1
+ # encoding: utf-8
2
+ require "date"
3
+ require "hash-utils/string"
4
+
5
+ require "fluent-query/query"
6
+ require "fluent-query/compiler"
7
+
8
+ module FluentQuery
9
+ module Queries
10
+
11
+ ##
12
+ # Query processor.
13
+ #
14
+ # Its primary aim is to dive processing methods from query object as is.
15
+ # In fact, defines something as processing session.
16
+ #
17
+
18
+ class Processor
19
+
20
+ ##
21
+ # Indicates column replacement to perform.
22
+ # (Flag.)
23
+ #
24
+
25
+ COLUMN_REPLACEMENT = 2
26
+
27
+ ##
28
+ # Indicates string replacement to perform.
29
+ # (Flag.)
30
+ #
31
+
32
+ STRING_REPLACEMENT = 1
33
+
34
+ ##
35
+ # Indicates formatting directive replacements to perform.
36
+ # (Flag.)
37
+ #
38
+
39
+ FORMATTING_REPLACEMENT = 4
40
+
41
+ ##
42
+ # Describes formatting directive.
43
+ # @var Regexp
44
+ #
45
+
46
+ FORMATTING_DIRECTIVE = /%%(?:[islbfdt]|sql|and|or)(?:[^\w]|$)/
47
+
48
+ ##
49
+ # Describes column directive.
50
+ # @var Regexp
51
+ #
52
+
53
+ COLUMN_DIRECTIVE = /(?:^|\.|[^\'"\w\\])?\[(?:[\w\.]*[^\\\W])\](?:[^\'"\w]|\.|$)/ # problem is, it interpretes regex
54
+ # from left to right, so if second
55
+ # occurence is immedieately
56
+ # it will not be matched -- that
57
+ # is reason for ? in the begin section
58
+
59
+ ##
60
+ # Describes string directive.
61
+ # @var Regexp
62
+ #
63
+
64
+ STRING_DIRECTIVE = /(?:^|[^\\])"/
65
+
66
+ ##
67
+ # Holds simple column definition.
68
+ # @var Regexp
69
+ #
70
+
71
+ FORMATTING_DIRECTIVE_SIMPLE = /%%\w+/
72
+
73
+ ##
74
+ # Holds simple column definition.
75
+ #
76
+
77
+ COLUMN_DIRECTIVE_SIMPLE = /\[.+\]/
78
+
79
+ ##
80
+ # Final regexp for incremental fluent expanding.
81
+ # (internal cache)
82
+
83
+ @__fluent_expander
84
+
85
+ ##
86
+ # Final regexp for incremental fluent replacing.
87
+ # (internal cache)
88
+
89
+ @__fluent_replacer
90
+
91
+ ##
92
+ # Driver upon which processor should work upon.
93
+ #
94
+
95
+ @driver
96
+ attr_reader :driver
97
+
98
+ ##
99
+ # Compiler associated to this processor.
100
+ #
101
+
102
+ @_compiler
103
+
104
+ ##
105
+ # Constructs processor.
106
+ # @param MP_Fluent_Driver driver Driver upon which processor should work upon.
107
+ #
108
+
109
+ public
110
+ def initialize(driver)
111
+ @driver = driver
112
+ end
113
+
114
+ ##
115
+ # Process array to some reasonable string form.
116
+ #
117
+ # Joins its elements and separates them by commas. Quotes strings
118
+ # by by-driver-defined string quoting and integers by integer quoting.
119
+ #
120
+
121
+ public
122
+ def process_array(array, glue = ",")
123
+ result = [ ]
124
+
125
+ array.each do |item|
126
+ result << self.quote_value(item)
127
+ end
128
+
129
+ return result.join(glue + " ")
130
+ end
131
+
132
+ ##
133
+ # Process hash to some reasonable string form.
134
+ #
135
+ # Joins its elements to name = value form and separates them
136
+ # by commas. Quotes string value by by-driver-defined string quoting
137
+ # and integers by integer quoting. Quotes name by identifiers quoting.
138
+ #
139
+ # Handles two modes:
140
+ # * "assigning" which classicaly keeps for example the '=' operator,
141
+ # * "comparing" which sets for example 'IS' operator for booleans.
142
+ #
143
+
144
+ public
145
+ def process_hash(hash, glue = ",", equality = :comparing)
146
+ result = [ ]
147
+ mode = equality
148
+
149
+ hash.each_pair do |key, value|
150
+ operator = @driver.quote_equality(value, mode)
151
+ key = self.quote_identifier(key)
152
+ value = self.quote_value(value)
153
+
154
+ result << (key + " " + operator + " " + value)
155
+ end
156
+
157
+ return result.join(" " << glue << " ")
158
+ end
159
+
160
+ ##
161
+ # Quotes string by driver string quoting and integers by integer
162
+ # quoting. Other values are converted to string too and
163
+ # leaved unquoted.
164
+ #
165
+
166
+ public
167
+ def quote_value(value)
168
+
169
+ if (value.string?) or (value.symbol?)
170
+ result = @driver.quote_string(value.to_s)
171
+ elsif value.kind_of? Integer
172
+ result = @driver.quote_integer(value)
173
+ elsif value.array?
174
+ result = "(" << self.process_array(value) << ")" # TODO: question is, if we should do it here, if it's enough general just for processor
175
+ elsif value.kind_of? Float
176
+ result = @driver.quote_float(value)
177
+ elsif (value.kind_of? TrueClass) or (value.kind_of? FalseClass)
178
+ result = @driver.quote_boolean(value)
179
+ elsif (value.kind_of? Date) or (value.kind_of? DateTime)
180
+ result = @driver.quote_date_time(value)
181
+ elsif value.nil?
182
+ result = @driver.null
183
+ else
184
+ result = value.to_s
185
+ end
186
+
187
+ return result
188
+
189
+ end
190
+
191
+ ##
192
+ # Quotes subquery by driver subquery quoting.
193
+ #
194
+
195
+ public
196
+ def quote_subquery(subquery)
197
+ if subquery.kind_of? FluentQuery::Query
198
+ subquery = subquery.build!
199
+ end
200
+
201
+ return @driver.quote_subquery(subquery)
202
+ end
203
+
204
+ ##
205
+ # Quotes identifiers by identifier quoting.
206
+ #
207
+
208
+ public
209
+ def quote_identifier(identifier)
210
+ @driver.quote_identifier(identifier.to_s)
211
+ end
212
+
213
+ ##
214
+ # Quotes identifiers list.
215
+ #
216
+
217
+ public
218
+ def quote_identifiers(identifiers)
219
+ identifiers.map { |i| self.quote_identifier(i) }
220
+ end
221
+
222
+ ##
223
+ # Processes identifier list to the string representation.
224
+ # Quotes them and joins them separated by commas.
225
+ #
226
+
227
+ public
228
+ def process_identifiers(identifiers)
229
+ self.quote_identifiers(identifiers).join(", ")
230
+ end
231
+
232
+ ##
233
+ # Processed strings with format definitions and data specifications.
234
+ #
235
+ # Mode can be :compile, :build or :finish. Compiling means building the
236
+ # query without expanding the formatting directives. Finishing means
237
+ # building the prepared query.
238
+ #
239
+
240
+ public
241
+ def process_formatted(sequence, mode = :build)
242
+ count = sequence.length
243
+ i = 0
244
+ output = ""
245
+
246
+ replacer_settings = self.class::COLUMN_REPLACEMENT | self.class::STRING_REPLACEMENT
247
+ expander_settings = self.class::FORMATTING_REPLACEMENT
248
+
249
+ while i < count
250
+ item = sequence[i]
251
+
252
+ ##
253
+
254
+ if item.kind_of? String
255
+ item = item.dup
256
+
257
+ # Calls to each occurence of directive matching to directive
258
+ # finding expression directive processing. In each call increases
259
+ # sequence position counter so moves forward.
260
+
261
+ if mode != :finish
262
+ item.gsub!(self.replacer) do |directive|
263
+ directive.strip!
264
+ self.process_directive(directive, nil, replacer_settings)
265
+ end
266
+ end
267
+
268
+ if mode != :compile
269
+ item.gsub!(self.expander) do |directive|
270
+ self.process_directive(directive, sequence[i += 1], expander_settings)
271
+ end
272
+ end
273
+
274
+ output << item
275
+
276
+ elsif item.kind_of? Symbol
277
+ output << self.quote_identifier(item)
278
+
279
+ else
280
+ output << item.to_s
281
+
282
+ end
283
+
284
+ output << " "
285
+ i += 1
286
+ end
287
+
288
+ return output
289
+
290
+ end
291
+
292
+ ##
293
+ # Processes fluent directive.
294
+ # Replacements is three bytes flag array.
295
+ #
296
+
297
+ public
298
+ def process_directive(directive, argument = nil, replacements = 7)
299
+ if (replacements & self.class::COLUMN_REPLACEMENT > 0) and (directive[0].ord == 91) # "[", Column directive
300
+ result = directive.gsub(self.class::COLUMN_DIRECTIVE_SIMPLE) { |value| self.quote_identifier(value[1..-2]) }
301
+ elsif (replacements & self.class::STRING_REPLACEMENT > 0) and (directive[-1].ord == 34) # "\"", String directive
302
+ result = directive.gsub('"', @driver.quote_string("").last)
303
+ elsif (replacements & self.class::FORMATTING_REPLACEMENT > 0) and (directive[0..1].to_sym == :"%%") # Formatting directive
304
+ result = directive.gsub(self.class::FORMATTING_DIRECTIVE_SIMPLE) { |value| self.process_formatting(value[2..-1], argument) }
305
+ else
306
+ result = directive
307
+ end
308
+
309
+ return result
310
+ end
311
+
312
+ ##
313
+ # Processes formatting directive.
314
+ #
315
+
316
+ public
317
+ def process_formatting(directive, argument)
318
+ proc = self.compiler.compile_formatting(directive)
319
+
320
+ if proc
321
+ output = proc.call(argument)
322
+ else
323
+ output = ""
324
+ end
325
+
326
+ return output
327
+ end
328
+
329
+ ##
330
+ # Returns NULL representation.
331
+ #
332
+
333
+ public
334
+ def null
335
+ @driver.null
336
+ end
337
+
338
+ ##
339
+ # Returns final fluent expander query.
340
+ #
341
+
342
+ public
343
+ def expander
344
+ if @__fluent_expander.nil?
345
+ @__fluent_expander = self.class::FORMATTING_DIRECTIVE
346
+ end
347
+
348
+ @__fluent_expander
349
+ end
350
+
351
+ ##
352
+ # Returns final fluent formatter query.
353
+ #
354
+
355
+ public
356
+ def replacer
357
+ if @__fluent_replacer.nil?
358
+ parts = Array[self.class::COLUMN_DIRECTIVE, self.class::STRING_DIRECTIVE]
359
+
360
+ result = ""
361
+ result << "(?:(?:" << parts.join(")|(?:") << "))"
362
+
363
+ @__fluent_replacer = Regexp::new(result)
364
+ end
365
+
366
+ @__fluent_replacer
367
+ end
368
+
369
+ ##
370
+ # Returns compiler.
371
+ #
372
+
373
+ def compiler
374
+ if @_compiler.nil?
375
+ @_compiler = FluentQuery::Compiler::new(self)
376
+ end
377
+
378
+ @_compiler
379
+ end
380
+
381
+ ##
382
+ # Compiles the string.
383
+ #
384
+
385
+ def compile(string)
386
+ self.compiler.compile(string)
387
+ end
388
+
389
+ end
390
+ end
391
+ end
392
+