fluent-query 0.9.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/.document +5 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +20 -0
- data/README.md +230 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/fluent-query.gemspec +75 -0
- data/lib/fluent-query.rb +2 -0
- data/lib/fluent-query/compiler.rb +182 -0
- data/lib/fluent-query/compilers/result.rb +33 -0
- data/lib/fluent-query/connection.rb +316 -0
- data/lib/fluent-query/data.rb +17 -0
- data/lib/fluent-query/driver.rb +279 -0
- data/lib/fluent-query/drivers/exception.rb +13 -0
- data/lib/fluent-query/drivers/result.rb +98 -0
- data/lib/fluent-query/exception.rb +12 -0
- data/lib/fluent-query/queries/abstract.rb +169 -0
- data/lib/fluent-query/queries/compiled.rb +51 -0
- data/lib/fluent-query/queries/prepared.rb +50 -0
- data/lib/fluent-query/queries/processor.rb +392 -0
- data/lib/fluent-query/query.rb +118 -0
- data/lib/fluent-query/result.rb +153 -0
- data/lib/fluent-query/token.rb +73 -0
- data/lib/fluent-query/tokens/raw.rb +26 -0
- metadata +136 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FluentQuery
|
4
|
+
module Compilers
|
5
|
+
|
6
|
+
##
|
7
|
+
# Query compiler result. Aka compiled string.
|
8
|
+
#
|
9
|
+
|
10
|
+
class Result < ::Array
|
11
|
+
|
12
|
+
##
|
13
|
+
# Completes the compiled string to final one.
|
14
|
+
#
|
15
|
+
|
16
|
+
def complete(*args)
|
17
|
+
result = ""
|
18
|
+
|
19
|
+
self.each do |v|
|
20
|
+
if v.kind_of? Proc
|
21
|
+
result << v.call(args.shift)
|
22
|
+
else
|
23
|
+
result << v.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
return result
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,316 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "fluent-query/result"
|
3
|
+
require "fluent-query/driver"
|
4
|
+
require "fluent-query/exception"
|
5
|
+
|
6
|
+
module FluentQuery
|
7
|
+
|
8
|
+
##
|
9
|
+
# Represents query target connection.
|
10
|
+
#
|
11
|
+
|
12
|
+
class Connection
|
13
|
+
|
14
|
+
##
|
15
|
+
# Driver instance associated to the connection object.
|
16
|
+
#
|
17
|
+
|
18
|
+
@_driver
|
19
|
+
|
20
|
+
##
|
21
|
+
# Holds driver class information.
|
22
|
+
#
|
23
|
+
|
24
|
+
@_driver_class
|
25
|
+
|
26
|
+
##
|
27
|
+
# Connection settings.
|
28
|
+
#
|
29
|
+
|
30
|
+
@_settings
|
31
|
+
|
32
|
+
##
|
33
|
+
# Indicates, connection is open.
|
34
|
+
#
|
35
|
+
|
36
|
+
@_open
|
37
|
+
|
38
|
+
##
|
39
|
+
# Indicates debug mode.
|
40
|
+
#
|
41
|
+
|
42
|
+
@_debug
|
43
|
+
|
44
|
+
##
|
45
|
+
# Initializes the connection.
|
46
|
+
#
|
47
|
+
# Be warn, initializer call opening the connection immediately on
|
48
|
+
# the driver, but if driver is lazy, it will open the connection
|
49
|
+
# at the moment in which it will want to do it.
|
50
|
+
#
|
51
|
+
|
52
|
+
public
|
53
|
+
def initialize(driver_class, settings = nil, open = true)
|
54
|
+
|
55
|
+
# Analyses
|
56
|
+
|
57
|
+
driver = driver_class::new(self)
|
58
|
+
|
59
|
+
if not driver.kind_of? FluentQuery::Driver
|
60
|
+
raise FluentQuery::Exception::new("Driver must be subclass of the 'FluentQuery::Driver' class.")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Assigns
|
64
|
+
|
65
|
+
@_driver_class = driver_class
|
66
|
+
@_settings = settings
|
67
|
+
@_open = false
|
68
|
+
@_debug = false
|
69
|
+
|
70
|
+
# Opens if required
|
71
|
+
|
72
|
+
if open
|
73
|
+
@_driver = driver
|
74
|
+
self.open!
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Catches missing methods calls.
|
81
|
+
#
|
82
|
+
# Asks the driver if method call is relevant for it, of it is,
|
83
|
+
# performs it on it.
|
84
|
+
#
|
85
|
+
|
86
|
+
public
|
87
|
+
def method_missing(sym, *args, &block)
|
88
|
+
|
89
|
+
# Checks if connection is in open state
|
90
|
+
if not @_open
|
91
|
+
raise FluentQuery::Exception::new("Connection is closed.")
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
|
96
|
+
driver = self.driver
|
97
|
+
|
98
|
+
if driver.relevant_method? sym
|
99
|
+
return __query_call(sym, *args, &block)
|
100
|
+
else
|
101
|
+
raise FluentQuery::Exception::new("Method '" << sym.to_s << "' isn't implemented by associated FluentQuery::Driver or FluentQuery::Connection object.")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Sets debug mode.
|
107
|
+
#
|
108
|
+
# Currently two are supported:
|
109
|
+
# * false, which turn debugging of
|
110
|
+
# * :dump_all which puts all queries to the output
|
111
|
+
#
|
112
|
+
|
113
|
+
public
|
114
|
+
def set_debug(mode)
|
115
|
+
@_debug = mode
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Generates query object from free query string.
|
120
|
+
#
|
121
|
+
# But this free query string still will be treated as string only
|
122
|
+
# in the final query.
|
123
|
+
#
|
124
|
+
|
125
|
+
public
|
126
|
+
def query(*args, &block)
|
127
|
+
query = self._new_query_object
|
128
|
+
|
129
|
+
# Calls given block in query context.
|
130
|
+
if block
|
131
|
+
result = query.instance_eval(&block)
|
132
|
+
else
|
133
|
+
result = query
|
134
|
+
end
|
135
|
+
|
136
|
+
return query
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Returns the driver object.
|
141
|
+
#
|
142
|
+
|
143
|
+
public
|
144
|
+
def driver
|
145
|
+
|
146
|
+
# Checks if connection is in open state
|
147
|
+
if @_driver.nil?
|
148
|
+
@_driver = @_driver_class::new(self)
|
149
|
+
end
|
150
|
+
|
151
|
+
return @_driver
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Opens the connection.
|
156
|
+
#
|
157
|
+
|
158
|
+
public
|
159
|
+
def open!(settings = nil)
|
160
|
+
|
161
|
+
if @_open
|
162
|
+
raise FluentQuery::Exception::new("Connection is already open.")
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
|
167
|
+
if (settings == nil) and @_settings
|
168
|
+
settings = @_settings
|
169
|
+
elsif settings == nil
|
170
|
+
raise FluentQuery::Exception::new("Connection settings hasn't been set or given to the #open method.")
|
171
|
+
end
|
172
|
+
|
173
|
+
self.driver.open_connection(settings)
|
174
|
+
|
175
|
+
##
|
176
|
+
|
177
|
+
@_open = true
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Closes the connection.
|
183
|
+
#
|
184
|
+
|
185
|
+
public
|
186
|
+
def close!
|
187
|
+
if @_driver
|
188
|
+
self.driver.close_connection!
|
189
|
+
@_driver = nil
|
190
|
+
end
|
191
|
+
|
192
|
+
@_open = false
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Executes query.
|
197
|
+
#
|
198
|
+
|
199
|
+
public
|
200
|
+
def execute(query)
|
201
|
+
|
202
|
+
# Checks if connection is in open state
|
203
|
+
if not @_open
|
204
|
+
raise FluentQuery::Exception::new("Connection is closed.")
|
205
|
+
end
|
206
|
+
|
207
|
+
# If query is fluent query object, executes it
|
208
|
+
if query.kind_of? FluentQuery::Query
|
209
|
+
result = query.execute!
|
210
|
+
else
|
211
|
+
if @_debug == :dump_all
|
212
|
+
puts query
|
213
|
+
end
|
214
|
+
|
215
|
+
result = self.driver.execute(query)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Wraps driver result to result class
|
219
|
+
result = FluentQuery::Result::new(result)
|
220
|
+
|
221
|
+
return result
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Executes query and returns count of changed/inserted rows.
|
227
|
+
#
|
228
|
+
|
229
|
+
public
|
230
|
+
def do(query)
|
231
|
+
|
232
|
+
# Checks if connection is in open state
|
233
|
+
if not @_open
|
234
|
+
raise FluentQuery::Exception::new("Connection is closed.")
|
235
|
+
end
|
236
|
+
|
237
|
+
# If query is fluent query object, executes it
|
238
|
+
if query.kind_of? FluentQuery::Query
|
239
|
+
result = query.do!
|
240
|
+
else
|
241
|
+
if @_debug == :dump_all
|
242
|
+
puts query
|
243
|
+
end
|
244
|
+
|
245
|
+
result = self.driver.do(query)
|
246
|
+
end
|
247
|
+
|
248
|
+
return result
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
##
|
253
|
+
# Do in transaction context.
|
254
|
+
#
|
255
|
+
|
256
|
+
public
|
257
|
+
def transaction(&block)
|
258
|
+
self.begin
|
259
|
+
block.call
|
260
|
+
self.commit
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
|
265
|
+
#####
|
266
|
+
|
267
|
+
##
|
268
|
+
# Handles built-in shortcut.
|
269
|
+
#
|
270
|
+
|
271
|
+
private
|
272
|
+
def __handle_shortcut(sym, *args, &block)
|
273
|
+
result = __query_call(sym, *args, &block)
|
274
|
+
|
275
|
+
if (not args) or (args.length <= 0)
|
276
|
+
result = result.execute!
|
277
|
+
end
|
278
|
+
|
279
|
+
return result
|
280
|
+
end
|
281
|
+
|
282
|
+
##
|
283
|
+
# Performs query initiating call.
|
284
|
+
#
|
285
|
+
|
286
|
+
private
|
287
|
+
def __query_call(sym, *args, &block)
|
288
|
+
query = self._new_query_object
|
289
|
+
|
290
|
+
# Executes query conditionally. If query isn't suitable for
|
291
|
+
# executing, sends the symbol to it and returns call result.
|
292
|
+
|
293
|
+
query.send(sym, *args)
|
294
|
+
|
295
|
+
# Calls given block in query context.
|
296
|
+
|
297
|
+
if block
|
298
|
+
result = query.instance_eval(&block)
|
299
|
+
else
|
300
|
+
result = query
|
301
|
+
end
|
302
|
+
|
303
|
+
return result
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Returns new query object.
|
308
|
+
#
|
309
|
+
|
310
|
+
protected
|
311
|
+
def _new_query_object
|
312
|
+
self.driver.query_class::new(self)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "hash-utils/string"
|
3
|
+
require "hashie/mash"
|
4
|
+
|
5
|
+
module FluentQuery
|
6
|
+
|
7
|
+
##
|
8
|
+
# Represents data hash.
|
9
|
+
#
|
10
|
+
# In fact, it's common Hash class extended by method which allow
|
11
|
+
# access its fields by "object way".
|
12
|
+
#
|
13
|
+
|
14
|
+
class Data < ::Hashie::Mash
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "abstract"
|
3
|
+
|
4
|
+
module FluentQuery
|
5
|
+
|
6
|
+
##
|
7
|
+
# Represents abstract query driver.
|
8
|
+
# @abstract
|
9
|
+
#
|
10
|
+
|
11
|
+
class Driver
|
12
|
+
|
13
|
+
##
|
14
|
+
# Holds connection associated to this driver instance.
|
15
|
+
#
|
16
|
+
|
17
|
+
@connection
|
18
|
+
attr_reader :connection
|
19
|
+
|
20
|
+
##
|
21
|
+
# Initializes driver.
|
22
|
+
#
|
23
|
+
|
24
|
+
public
|
25
|
+
def initialize(connection)
|
26
|
+
if self.instance_of? FluentQuery::Driver
|
27
|
+
not_implemented
|
28
|
+
end
|
29
|
+
|
30
|
+
@connection = connection
|
31
|
+
end
|
32
|
+
|
33
|
+
#####
|
34
|
+
|
35
|
+
##
|
36
|
+
# Indicates, method is relevant for the driver.
|
37
|
+
# @abstract
|
38
|
+
#
|
39
|
+
|
40
|
+
public
|
41
|
+
def relevant_method?(name)
|
42
|
+
not_implemented
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
##
|
47
|
+
# Indicates token is known.
|
48
|
+
# @abstract
|
49
|
+
#
|
50
|
+
|
51
|
+
public
|
52
|
+
def known_token?(group, token_name)
|
53
|
+
not_implemented
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Indicates, token is operator.
|
58
|
+
# @abstract
|
59
|
+
#
|
60
|
+
|
61
|
+
public
|
62
|
+
def operator_token?(token_name)
|
63
|
+
not_implemented
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Returns operator string according to operator symbol.
|
68
|
+
# @abstract
|
69
|
+
#
|
70
|
+
|
71
|
+
public
|
72
|
+
def quote_operator(operator)
|
73
|
+
not_implemented
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Returns correct equality operator for given datatype.
|
78
|
+
#
|
79
|
+
# Must handle two modes:
|
80
|
+
# * "assigning" which classicaly keeps for example the '=' operator,
|
81
|
+
# * "comparing" which sets for example 'IS' operator for booleans.
|
82
|
+
#
|
83
|
+
# @abstract
|
84
|
+
#
|
85
|
+
|
86
|
+
public
|
87
|
+
def quote_equality(datatype, mode = :comparing)
|
88
|
+
not_implemented
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Indicates which query subclass to use.
|
93
|
+
# @abstract
|
94
|
+
#
|
95
|
+
|
96
|
+
public
|
97
|
+
def query_class
|
98
|
+
not_implemented
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Builds given query.
|
103
|
+
# @abstract
|
104
|
+
#
|
105
|
+
|
106
|
+
public
|
107
|
+
def build_query(query)
|
108
|
+
not_implemented
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Returns preparation placeholder according to given
|
113
|
+
# library placeholder.
|
114
|
+
#
|
115
|
+
# @abstract
|
116
|
+
#
|
117
|
+
|
118
|
+
public
|
119
|
+
def quote_placeholder(placeholder)
|
120
|
+
not_implemented
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
##### QUOTING
|
125
|
+
|
126
|
+
##
|
127
|
+
# Quotes string.
|
128
|
+
# @abstract
|
129
|
+
#
|
130
|
+
|
131
|
+
public
|
132
|
+
def quote_string(string)
|
133
|
+
not_implemented
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Quoting integer.
|
138
|
+
# @abstract
|
139
|
+
#
|
140
|
+
|
141
|
+
public
|
142
|
+
def quote_integer(integer)
|
143
|
+
not_implemented
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Quotes float.
|
148
|
+
# @abstract
|
149
|
+
#
|
150
|
+
|
151
|
+
public
|
152
|
+
def quote_float(float)
|
153
|
+
not_implemented
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Quotes field by field quoting.
|
158
|
+
# @abstract
|
159
|
+
#
|
160
|
+
|
161
|
+
public
|
162
|
+
def quote_identifier(field)
|
163
|
+
not_implemented
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Creates system-dependent NULL.
|
168
|
+
# @abstract
|
169
|
+
#
|
170
|
+
|
171
|
+
public
|
172
|
+
def null
|
173
|
+
not_implemented
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
# Quotes system-dependent boolean value.
|
178
|
+
# @abstract
|
179
|
+
#
|
180
|
+
|
181
|
+
public
|
182
|
+
def quote_boolean(boolean)
|
183
|
+
not_implemented
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Quotes system-dependent date value.
|
188
|
+
# @abstract
|
189
|
+
#
|
190
|
+
|
191
|
+
public
|
192
|
+
def quote_date_time(date)
|
193
|
+
not_implemented
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
# Quotes system-dependent subquery.
|
198
|
+
# @abstract
|
199
|
+
#
|
200
|
+
|
201
|
+
public
|
202
|
+
def quote_subquery(subquery)
|
203
|
+
not_implemented
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
##### EXECUTING
|
209
|
+
|
210
|
+
##
|
211
|
+
# Opens the connection.
|
212
|
+
#
|
213
|
+
# It's lazy, so it will open connection before first request through
|
214
|
+
# {@link native_connection()} method.
|
215
|
+
#
|
216
|
+
# @abstract
|
217
|
+
#
|
218
|
+
|
219
|
+
public
|
220
|
+
def open_connection(settings)
|
221
|
+
not_implemented
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Returns native connection.
|
226
|
+
# @abstract
|
227
|
+
#
|
228
|
+
|
229
|
+
public
|
230
|
+
def native_connection
|
231
|
+
not_implemented
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Closes the connection.
|
236
|
+
# @abstract
|
237
|
+
#
|
238
|
+
|
239
|
+
public
|
240
|
+
def close_connection!
|
241
|
+
not_implemented
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Executes the query.
|
246
|
+
# @abstract
|
247
|
+
#
|
248
|
+
|
249
|
+
public
|
250
|
+
def execute(query)
|
251
|
+
not_implemented
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# Executes the query and returns count of the changed/inserted rows.
|
256
|
+
# @abstract
|
257
|
+
#
|
258
|
+
|
259
|
+
public
|
260
|
+
def do(query)
|
261
|
+
not_implemented
|
262
|
+
end
|
263
|
+
|
264
|
+
##
|
265
|
+
# Generates prepared query. Should be noted, if driver doesn't
|
266
|
+
# support query preparing, it should be +not_implemented+ and
|
267
|
+
# unimplemented.
|
268
|
+
#
|
269
|
+
# @abstract
|
270
|
+
#
|
271
|
+
|
272
|
+
public
|
273
|
+
def prepare(query)
|
274
|
+
not_implemented
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|