fluent-query-sql 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,559 @@
1
+ # encoding: utf-8
2
+ require "abstract"
3
+ require "hash-utils/module" # >= 0.13.0
4
+ require "hash-utils/string" # >= 0.15.0
5
+ require "hash-utils/array" # >= 0.17.0
6
+
7
+ require "fluent-query/driver"
8
+ require "fluent-query/queries/sql"
9
+ require "fluent-query/drivers/shared/tokens/sql"
10
+
11
+ module FluentQuery
12
+ module Drivers
13
+
14
+ ##
15
+ # Represents abstract SQL driver.
16
+ #
17
+
18
+ class SQL < FluentQuery::Driver
19
+
20
+ ##
21
+ # Contains relevant methods index for this driver.
22
+ #
23
+
24
+ RELEVANT = [:select, :insert, :update, :delete, :truncate, :set, :begin, :commit, :union]
25
+
26
+ ##
27
+ # Contains ordering for typicall queries.
28
+ #
29
+
30
+ ORDERING = {
31
+ :select => [
32
+ :select, :from, :join, :groupBy, :having, :where, :orderBy, :limit, :offset
33
+ ],
34
+ :insert => [
35
+ :insert, :values
36
+ ],
37
+ :update => [
38
+ :update, :set, :where
39
+ ],
40
+ :delete => [
41
+ :delete, :where
42
+ ],
43
+ :truncate => [
44
+ :truncate, :table, :cascade
45
+ ],
46
+ :set => [
47
+ :set
48
+ ],
49
+ :union => [
50
+ :union
51
+ ]
52
+ }
53
+
54
+ ##
55
+ # Contains operators list.
56
+ #
57
+ # Operators are defined as tokens whose multiple parameters in Array
58
+ # are appropriate to join by itself.
59
+ #
60
+
61
+ OPERATORS = {
62
+ :and => "AND",
63
+ :or => "OR"
64
+ }
65
+
66
+ ##
67
+ # Indicates, appropriate token should be present by one real token, but more input tokens.
68
+ #
69
+
70
+ AGREGATE = [:where, :orderBy, :select, :groupBy, :having]
71
+
72
+ ##
73
+ # Indicates token aliases.
74
+ #
75
+
76
+ ALIASES = {
77
+ :leftJoin => :join,
78
+ :rightJoin => :join,
79
+ :fullJoin => :join
80
+ }
81
+
82
+ ##
83
+ # Contains relevant methods index for this driver.
84
+ #
85
+
86
+ @relevant
87
+
88
+ ##
89
+ # Contains ordering for typicall queries.
90
+ #
91
+
92
+ @ordering
93
+
94
+ ##
95
+ # Contains operators list.
96
+ #
97
+ # Operators are defined as tokens whose multiple parameters in Array
98
+ # are appropriate to join by itself.
99
+ #
100
+
101
+ @operators
102
+
103
+ ##
104
+ # Indicates, appropriate token should be present by one real token, but more input tokens.
105
+ #
106
+
107
+ @agregate
108
+
109
+ ##
110
+ # Indicates token aliases.
111
+ #
112
+
113
+ @aliases
114
+
115
+ ##
116
+ # Holds native connection settings.
117
+ #
118
+
119
+ @_nconnection_settings
120
+
121
+ ##
122
+ # Holds native connection.
123
+ # (internal cache)
124
+ #
125
+
126
+ @_nconnection
127
+
128
+ ##
129
+ # Indicates tokens which has been already required.
130
+ #
131
+
132
+ @_tokens_required
133
+
134
+ ##
135
+ # Holds internal token struct.
136
+ #
137
+
138
+ TOKEN = Struct::new(:name, :subtokens)
139
+
140
+ ##
141
+ # Initializes driver.
142
+ #
143
+
144
+ public
145
+ def initialize(connection)
146
+ if self.instance_of? SQL
147
+ not_implemented
148
+ end
149
+
150
+ super(connection)
151
+
152
+ @relevant = SQL::RELEVANT
153
+ @ordering = SQL::ORDERING
154
+ @operators = SQL::OPERATORS
155
+ @aliases = SQL::ALIASES
156
+
157
+ @agregate = { }
158
+ SQL::AGREGATE.each do |i|
159
+ @agregate[i] = true
160
+ end
161
+
162
+ @_tokens_required = { }
163
+ end
164
+
165
+ ####
166
+
167
+ ##
168
+ # Indicates, method is relevant for the driver.
169
+ #
170
+
171
+ public
172
+ def relevant_method?(name)
173
+ @relevant.include? name
174
+ end
175
+
176
+ ##
177
+ # Indicates token is known.
178
+ #
179
+
180
+ public
181
+ def known_token?(group, token_name, cache)
182
+
183
+ # Checks
184
+ if @ordering.has_key? group
185
+ # Creates index
186
+ if not cache.has_key? group
187
+ @ordering[group].each do |item|
188
+ cache[group][item] = true
189
+ end
190
+ end
191
+
192
+ result = cache[group].has_key? token_name.to_sym
193
+ else
194
+ result = false
195
+ end
196
+
197
+ return result
198
+
199
+ end
200
+
201
+ ##
202
+ # Builds given query.
203
+ #
204
+
205
+ public
206
+ def build_query(query, mode = :build)
207
+ self._build_query(query, FluentQuery::Drivers::Shared::Tokens::SQLToken, mode)
208
+ end
209
+
210
+ ##
211
+ # Indicates, token is operator.
212
+ #
213
+
214
+ public
215
+ def operator_token?(token_name)
216
+ @operators.has_key? token_name
217
+ end
218
+
219
+ ##
220
+ # Returns operator string according to operator symbol.
221
+ #
222
+
223
+ public
224
+ def quote_operator(operator)
225
+ @operators[operator]
226
+ end
227
+
228
+ ##
229
+ # Returns correct equality operator for given datatype.
230
+ #
231
+ # Must handle three modes:
232
+ # * "assigning" which classicaly keeps for example the '=' operator,
233
+ # * "comparing" which sets for example 'IS' operator for booleans,
234
+ #
235
+
236
+ public
237
+ def quote_equality(datatype, mode = :comparing)
238
+ if not datatype.kind_of? Class
239
+ datatype = datatype.class
240
+ end
241
+
242
+ if mode == :comparing
243
+ if (datatype == TrueClass) or (datatype == FalseClass) or (datatype == NilClass)
244
+ result = "IS"
245
+ elsif datatype == Array
246
+ result = "IN"
247
+ else
248
+ result = "="
249
+ end
250
+ else
251
+ result = "="
252
+ end
253
+
254
+ return result
255
+ end
256
+
257
+ ##
258
+ # Indicates which query subclass to use.
259
+ #
260
+
261
+ public
262
+ def query_class
263
+ FluentQuery::Queries::SQL
264
+ end
265
+
266
+ ##
267
+ # Builds given query.
268
+ #
269
+
270
+ protected
271
+ def _build_query(query, inittoken_class, mode = :build)
272
+
273
+ stack = query.stack
274
+ token_struct = self.class::TOKEN
275
+ tokens = [ ]
276
+
277
+ last_known = nil
278
+ result = ""
279
+
280
+
281
+ # Gets type of the query (select/update/insert/delete)
282
+
283
+ type = query.type
284
+
285
+ # Sorts according to known token type
286
+ #
287
+ # Unknown tokens are joined to the same subarray as preceded
288
+ # known tokens.
289
+ #
290
+
291
+ stack.each do |item|
292
+ name = item.name
293
+
294
+ # If token is alias, renames it
295
+
296
+ _alias = @aliases[name]
297
+
298
+ if not _alias.nil?
299
+ #_alias = _alias
300
+
301
+ item.alias = _alias
302
+ name = _alias
303
+ end
304
+
305
+ ##
306
+
307
+ if self.known_token? type, name
308
+
309
+ if not last_known.nil?
310
+ last_name = last_known.first.name
311
+ tokens.push(token_struct::new(last_name, last_known))
312
+ end
313
+
314
+ last_known = [item]
315
+
316
+ elsif not last_known.array?
317
+ tokens.push(token_struct::new(:__init__, [item]))
318
+
319
+ else
320
+ last_known << item
321
+
322
+ end
323
+ end
324
+
325
+ if last_known
326
+ last_name = last_known.first.name
327
+ tokens.push(token_struct::new(last_name, last_known))
328
+ end
329
+
330
+ # Generates native tokens
331
+
332
+ native_tokens = [ ]
333
+ init_tokens = nil
334
+ agregates = Hash::new { |hash, key| hash[key] = [ ] }
335
+
336
+ tokens.each do |data|
337
+ name = data.name
338
+ subtokens = data.subtokens
339
+
340
+ if (name == :__init__) and (not subtokens.nil?)
341
+ init_tokens = [inittoken_class::new(self, query, subtokens)]
342
+
343
+ elsif @agregate.has_key? name
344
+ agregates[name] << data
345
+
346
+ elsif self.known_token? type, name
347
+ token_data = self._generate_token(name, query, subtokens)
348
+ native_tokens << token_data
349
+
350
+ end
351
+
352
+ end
353
+
354
+ # Process agregate tokens
355
+
356
+ agregates.each_value do |tokens|
357
+ first = tokens.shift
358
+
359
+ tokens.each do |token|
360
+ first.subtokens += token.subtokens
361
+ end
362
+
363
+ token_data = self._generate_token(first.name, query, first.subtokens)
364
+ native_tokens << token_data
365
+ end
366
+
367
+ # Renders in ordered way
368
+ if not init_tokens.nil?
369
+ init_tokens.each do |token|
370
+ result << token.render!(mode) << " "
371
+ end
372
+ end
373
+
374
+ if @ordering.has_key? type
375
+ @ordering[type].each do |item|
376
+ native_tokens.each do |data|
377
+ if data.name == item
378
+ data.subtokens.each do |token|
379
+ result << token.render!(mode) << " "
380
+ end
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ result.strip!
387
+ return result
388
+ end
389
+
390
+ ##
391
+ # Generates token from token data.
392
+ #
393
+
394
+ protected
395
+ def _generate_token(name, query, subtokens)
396
+ classobject = self._require_token(name)
397
+ tokens = [classobject::new(self, query, subtokens)]
398
+
399
+ ###
400
+
401
+ return self.class::TOKEN::new(name, tokens)
402
+ end
403
+
404
+ ##
405
+ # Requires token class.
406
+ #
407
+
408
+ protected
409
+ def _require_token(name)
410
+
411
+ if not @_tokens_required.has_key? name
412
+ begin
413
+ module_name = name.to_s.ucfirst
414
+ classname = "FluentQuery::Drivers::Shared::Tokens::SQL::" << module_name
415
+ classfile = classname.downcase
416
+ classfile.strtr!("::" => "/", "fluentquery" => "fluent-query")
417
+
418
+ Kernel.require(classfile)
419
+ classobject = FluentQuery::Drivers::Shared::Tokens::SQL.get_module(module_name)
420
+ rescue NameError, LoadError
421
+ classobject = FluentQuery::Drivers::Shared::Tokens::SQLToken
422
+ end
423
+
424
+ @_tokens_required[name] = classobject
425
+ end
426
+
427
+ return @_tokens_required[name]
428
+ end
429
+
430
+
431
+
432
+
433
+ ##### QUOTING
434
+
435
+ ##
436
+ # Quotes string.
437
+ #
438
+
439
+ public
440
+ def quote_string(string)
441
+ string = string.gsub("'", "''")
442
+ string.gsub!("\\", "\\\\\\\\")
443
+
444
+ return "'" << string << "'"
445
+ end
446
+
447
+ ##
448
+ # Quotes integer.
449
+ #
450
+
451
+ public
452
+ def quote_integer(integer)
453
+ integer.to_s
454
+ end
455
+
456
+ ##
457
+ # Quotes float.
458
+ #
459
+
460
+ public
461
+ def quote_float(float)
462
+ float.to_s
463
+ end
464
+
465
+ ##
466
+ # Quotes field by field quoting.
467
+ #
468
+
469
+ public
470
+ def quote_identifier(field)
471
+ '"' << field.to_s.gsub(".", '"."') << '"'
472
+ end
473
+
474
+ ##
475
+ # Creates system-dependent NULL.
476
+ #
477
+
478
+ public
479
+ def null
480
+ "NULL"
481
+ end
482
+
483
+ ##
484
+ # Quotes system-dependent boolean value.
485
+ #
486
+
487
+ public
488
+ def quote_boolean(boolean)
489
+ boolean ? 1 : 0
490
+ end
491
+
492
+ ##
493
+ # Quotes system-dependent date value.
494
+ #
495
+
496
+ public
497
+ def quote_date_time(date)
498
+ self.quote_string(date.to_s)
499
+ end
500
+
501
+ ##
502
+ # Quotes system-dependent subquery.
503
+ #
504
+
505
+ public
506
+ def quote_subquery(subquery)
507
+ "(" << subquery << ")"
508
+ end
509
+
510
+
511
+ ##### EXECUTING
512
+
513
+ ##
514
+ # Opens the connection.
515
+ #
516
+ # It's lazy, so it will open connection before first request through
517
+ # {@link native_connection()} method.
518
+ #
519
+
520
+ public
521
+ def open_connection(settings)
522
+ not_implemented
523
+ end
524
+
525
+ ##
526
+ # Executes query conditionally.
527
+ #
528
+ # If query isn't suitable for executing, returns it. In otherwise
529
+ # returns result or number of changed rows.
530
+ #
531
+
532
+ public
533
+ def execute_conditionally(query, sym, *args, &block)
534
+ case query.type
535
+ when :insert
536
+ if (args.first.symbol?) and (args.second.hash?)
537
+ result = query.do!
538
+ end
539
+ when :begin
540
+ if args.empty?
541
+ result = query.execute!
542
+ end
543
+ when :truncate
544
+ if args.first.symbol?
545
+ result = query.execute!
546
+ end
547
+ when :commit, :rollback
548
+ result = query.execute!
549
+ else
550
+ result = nil
551
+ end
552
+
553
+ return result
554
+ end
555
+
556
+ end
557
+ end
558
+ end
559
+