fluent-query-sql 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.
@@ -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
+