sqlconstructor 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.
Files changed (80) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE.md +16 -0
  3. data/README.md +161 -0
  4. data/Rakefile +11 -0
  5. data/doc/Object.html +267 -0
  6. data/doc/Rakefile.html +150 -0
  7. data/doc/SQLAliasedList.html +717 -0
  8. data/doc/SQLColumn.html +326 -0
  9. data/doc/SQLCondList.html +318 -0
  10. data/doc/SQLConditional.html +1082 -0
  11. data/doc/SQLConditional/BasicCond.html +325 -0
  12. data/doc/SQLConstructor.html +763 -0
  13. data/doc/SQLConstructor/BasicDelete.html +383 -0
  14. data/doc/SQLConstructor/BasicDelete_mysql.html +368 -0
  15. data/doc/SQLConstructor/BasicInsert.html +339 -0
  16. data/doc/SQLConstructor/BasicInsert_mysql.html +325 -0
  17. data/doc/SQLConstructor/BasicJoin.html +408 -0
  18. data/doc/SQLConstructor/BasicJoin_mysql.html +439 -0
  19. data/doc/SQLConstructor/BasicSelect.html +554 -0
  20. data/doc/SQLConstructor/BasicSelect_example.html +288 -0
  21. data/doc/SQLConstructor/BasicSelect_mysql.html +466 -0
  22. data/doc/SQLConstructor/BasicUnion.html +396 -0
  23. data/doc/SQLConstructor/BasicUpdate.html +409 -0
  24. data/doc/SQLConstructor/BasicUpdate_mysql.html +310 -0
  25. data/doc/SQLConstructor/GenericQuery.html +797 -0
  26. data/doc/SQLConstructor/QAttr.html +398 -0
  27. data/doc/SQLConstructorTest.html +603 -0
  28. data/doc/SQLExporter.html +382 -0
  29. data/doc/SQLExporter/Exporter_generic.html +413 -0
  30. data/doc/SQLExporter/Exporter_mysql.html +395 -0
  31. data/doc/SQLObject.html +525 -0
  32. data/doc/SQLValList.html +322 -0
  33. data/doc/SQLValue.html +375 -0
  34. data/doc/created.rid +12 -0
  35. data/doc/images/brick.png +0 -0
  36. data/doc/images/brick_link.png +0 -0
  37. data/doc/images/bug.png +0 -0
  38. data/doc/images/bullet_black.png +0 -0
  39. data/doc/images/bullet_toggle_minus.png +0 -0
  40. data/doc/images/bullet_toggle_plus.png +0 -0
  41. data/doc/images/date.png +0 -0
  42. data/doc/images/find.png +0 -0
  43. data/doc/images/loadingAnimation.gif +0 -0
  44. data/doc/images/macFFBgHack.png +0 -0
  45. data/doc/images/package.png +0 -0
  46. data/doc/images/page_green.png +0 -0
  47. data/doc/images/page_white_text.png +0 -0
  48. data/doc/images/page_white_width.png +0 -0
  49. data/doc/images/plugin.png +0 -0
  50. data/doc/images/ruby.png +0 -0
  51. data/doc/images/tag_green.png +0 -0
  52. data/doc/images/wrench.png +0 -0
  53. data/doc/images/wrench_orange.png +0 -0
  54. data/doc/images/zoom.png +0 -0
  55. data/doc/index.html +356 -0
  56. data/doc/js/darkfish.js +118 -0
  57. data/doc/js/jquery.js +32 -0
  58. data/doc/js/quicksearch.js +114 -0
  59. data/doc/js/thickbox-compressed.js +10 -0
  60. data/doc/lib/dialects/example-constructor_rb.html +52 -0
  61. data/doc/lib/dialects/mysql-constructor_rb.html +52 -0
  62. data/doc/lib/dialects/mysql-exporter_rb.html +54 -0
  63. data/doc/lib/sqlconditional_rb.html +64 -0
  64. data/doc/lib/sqlconstructor_rb.html +52 -0
  65. data/doc/lib/sqlerrors_rb.html +54 -0
  66. data/doc/lib/sqlexporter_rb.html +55 -0
  67. data/doc/lib/sqlobject_rb.html +54 -0
  68. data/doc/rdoc.css +763 -0
  69. data/doc/test/queries_rb.html +56 -0
  70. data/doc/test_rb.html +52 -0
  71. data/lib/dialects/example-constructor.rb +45 -0
  72. data/lib/dialects/mysql-constructor.rb +247 -0
  73. data/lib/dialects/mysql-exporter.rb +108 -0
  74. data/lib/sqlconditional.rb +196 -0
  75. data/lib/sqlconstructor.rb +708 -0
  76. data/lib/sqlerrors.rb +15 -0
  77. data/lib/sqlexporter.rb +125 -0
  78. data/lib/sqlobject.rb +284 -0
  79. data/test/queries.rb +92 -0
  80. metadata +121 -0
@@ -0,0 +1,196 @@
1
+
2
+ ##################################################################################################
3
+ # This class represents an SQL conditional statement.
4
+ #
5
+ # Author:: Vasiliy Korol (mailto:vakorol@mail.ru)
6
+ # Copyright:: Vasiliy Korol (c) 2014
7
+ # License:: Distributes under terms of GPLv2
8
+ ##################################################################################################
9
+ class SQLConditional < SQLObject
10
+
11
+ attr_accessor :caller
12
+
13
+ # Dirty hack to make .join work on an array of SQLObjects
14
+ alias :to_str :to_s
15
+
16
+ ##########################################################################
17
+ # Class constructor. Accepts an optional parameter to set the @caller
18
+ # attribute, which is used in method_missing magic method to return
19
+ # control to the calling SQLConstructor object (see method_missing for
20
+ # more info).
21
+ ##########################################################################
22
+ def initialize ( params = nil )
23
+ @dialect, @tidy, @separator = nil, false, " "
24
+ if params.is_a? Hash
25
+ @caller = params[ :caller ]
26
+ if @caller
27
+ @dialect = params[ :dialect ] || @caller.dialect
28
+ @tidy = params[ :tidy ] || @caller.tidy
29
+ @separator = @caller.exporter.separator if @caller.exporter
30
+ end
31
+ end
32
+ @list = [ ]
33
+ @objects = [ ]
34
+ @string = nil
35
+ end
36
+
37
+ ##########################################################################
38
+ # Adds another SQLConditional object to the conditions list of the
39
+ # current object. Example:
40
+ # <tt>cond1 = SQLConditional.new.eq(':c1',3)</tt>
41
+ # <tt>cond2 = SQLConditional.new.lt(':c2',5).and.is(cond2)</tt>
42
+ ##########################################################################
43
+ def is ( cond )
44
+ raise SQLException, ERR_INVALID_CONDITIONAL if ! cond.is_a? SQLObject
45
+ cond.separator = @separator
46
+ @list << cond
47
+ @string = nil
48
+ return self
49
+ end
50
+
51
+ ##########################################################################
52
+ # Same as .is(), but negated
53
+ ##########################################################################
54
+ def not_is ( cond )
55
+ self.not( cond )
56
+ end
57
+
58
+ ##########################################################################
59
+ # Negates the following conditional statement.
60
+ ##########################################################################
61
+ def not ( *expr )
62
+ _addBasicCond( :NOT, BasicCond::LHS, *expr )
63
+ end
64
+
65
+ def and
66
+ _addBasicCond( :AND, BasicCond::MIDDLE )
67
+ end
68
+
69
+ def or
70
+ _addBasicCond( :OR, BasicCond::MIDDLE )
71
+ end
72
+
73
+ def eq ( expr1, expr2 )
74
+ _addBasicCond( :'=', BasicCond::MIDDLE, expr1, expr2 )
75
+ end
76
+
77
+ def ne ( expr1, expr2 )
78
+ _addBasicCond( :'!=', BasicCond::MIDDLE, expr1, expr2 )
79
+ end
80
+
81
+ def gt ( expr1, expr2 )
82
+ _addBasicCond( :'>', BasicCond::MIDDLE, expr1, expr2 )
83
+ end
84
+
85
+ def lt ( expr1, expr2 )
86
+ _addBasicCond( :'<', BasicCond::MIDDLE, expr1, expr2 )
87
+ end
88
+
89
+ def gte ( expr1, expr2 )
90
+ _addBasicCond( :'>=', BasicCond::MIDDLE, expr1, expr2 )
91
+ end
92
+
93
+ def lte ( expr1, expr2 )
94
+ _addBasicCond( :'<=', BasicCond::MIDDLE, expr1, expr2 )
95
+ end
96
+
97
+ def is_null ( expr )
98
+ _addBasicCond( :'IS NULL', BasicCond::RHS, SQLObject.get( expr ) )
99
+ end
100
+
101
+ def is_not_null ( expr )
102
+ _addBasicCond( :'IS NOT NULL', BasicCond::RHS, SQLObject.get( expr ) )
103
+ end
104
+
105
+ def in ( expr1, expr2 )
106
+ _addBasicCond( :'IN', BasicCond::MIDDLE, expr1, expr2 )
107
+ end
108
+
109
+ def not_in ( expr1, expr2 )
110
+ _addBasicCond( :'NOT IN', BasicCond::MIDDLE, expr1, expr2 )
111
+ end
112
+
113
+ def like ( expr1, expr2 )
114
+ _addBasicCond( :'LIKE', BasicCond::MIDDLE, expr1, expr2 )
115
+ end
116
+
117
+ def not_like ( expr1, expr2 )
118
+ _addBasicCond( :'NOT LIKE', BasicCond::MIDDLE, expr1, expr2 )
119
+ end
120
+
121
+
122
+ def to_s
123
+ return @string if @string
124
+ @string = @separator
125
+ @string += "("
126
+
127
+ @list.each do |item|
128
+ @string += item.to_s
129
+ end
130
+
131
+ @string += ")"
132
+
133
+ return @string
134
+ end
135
+
136
+ #############################################################################
137
+ # Return control to the object stored in @caller.
138
+ # This allows mixing the methods different classes, i.e.:
139
+ # <tt>SQLConstructor.new.select(':a').from('tab').where.eq(':b',3).limit(5)</tt>
140
+ # Here .where.eq() returns an SQLConditional object, but further usage
141
+ # of the foreign method .limit() returns back to the SQLConstructor object.
142
+ #############################################################################
143
+ def method_missing ( method, *args )
144
+ return @caller.send( method.to_sym, *args ) if @caller
145
+ raise NoMethodError, ERR_UNKNOWN_METHOD + ": " + method.to_s
146
+ end
147
+
148
+
149
+ private
150
+
151
+ def _addBasicCond ( operator, type, *expressions )
152
+ objects = SQLObject.get( *expressions )
153
+ @list << BasicCond.new( operator, type, *objects )
154
+ @string = nil
155
+ return self
156
+ end
157
+
158
+
159
+ ###############################################################################################
160
+ # Internal class which represents a basic logical operation.
161
+ ###############################################################################################
162
+ class BasicCond
163
+
164
+ LHS = -1
165
+ MIDDLE = 0
166
+ RHS = 1
167
+
168
+ def initialize ( operator, type, *objects )
169
+ @operator, @type, @objects = operator.to_s, type, objects
170
+ @string = nil
171
+ end
172
+
173
+ def to_s
174
+ return @string if @string
175
+ @string = " "
176
+ if @objects.empty?
177
+ @string += " " + @operator + " "
178
+ else
179
+ case @type
180
+ when LHS
181
+ @string = @operator + " " + @objects[0].to_s
182
+ when RHS
183
+ @string = @objects[0].to_s + " " + @operator
184
+ when MIDDLE
185
+ @string = @objects.empty? ? " " + @operator + " "
186
+ : @objects.join( " " + @operator + " " )
187
+ else
188
+ raise NameError, ERR_UNKNOWN_OPERATOR_TYPE
189
+ end
190
+ end
191
+ end
192
+
193
+ end
194
+
195
+ end
196
+
@@ -0,0 +1,708 @@
1
+
2
+ DIALECTS_PATH = File.expand_path( "../dialects", __FILE__ )
3
+
4
+ require File.expand_path( "../sqlobject", __FILE__ )
5
+ require File.expand_path( "../sqlconditional", __FILE__ )
6
+ require File.expand_path( "../sqlexporter", __FILE__ )
7
+ require File.expand_path( "../sqlerrors", __FILE__ )
8
+
9
+ ##################################################################################################
10
+ # Author:: Vasiliy Korol (mailto:vakorol@mail.ru)
11
+ # Copyright:: Vasiliy Korol (c) 2014
12
+ # License:: Distributes under terms of GPLv2
13
+ #
14
+ # This class implements methods to construct a valid SQL query.
15
+ # SQL SELECT, DELETE, UPDATE and INSERT clauses are supported.
16
+ #
17
+ # There's also an experimental implementation of MySQL index hints.
18
+ #
19
+ # Column values and other data that should be escaped is passed to the methods as strings.
20
+ # Column and table names, aliases and everything that goes unescaped is passed as symbols.
21
+ # === Typical usage:
22
+ # sql = SQLConstructor.new
23
+ # sql.select( :col1, :col2 ).from( :table ).where.eq( :col3, 16 ).and.lt( :col4, 5 )
24
+ # p sql
25
+ #
26
+ # will result in:
27
+ #
28
+ # SELECT col1,col2 FROM table WHERE (col3 = 16 AND col4 < 5)
29
+ #
30
+ # One can also construct complex queries like:
31
+ #
32
+ # sql = SQLConstructor.new( :tidy => true, :dialect => 'mysql' )
33
+ # inner_select1 = SQLConstructor.new( :tidy => true )
34
+ # inner_select1.select( :"MAX(h.item_id)" ).from( :item_data => :d ).
35
+ # inner_join( :call_data => :h ).on.eq( :"d.item_nm", :call_ref ).where.
36
+ # eq( :"d.item_num", :"g.item_num" ).group_by( :"h.venue_nm" ).having.eq( :"COUNT(*)", 1 )
37
+ # inner_select2 = SQLConstructor.new( :dialect => 'mysql', :tidy => true )
38
+ # inner_select2.select( :"d.item_num" ).from( :item_data => :d ).
39
+ # inner_join( :call_data => :h ).on.eq( :"d.item_nm", :call_ref ).
40
+ # group_by( :"h.venue_nm" ).having.eq( :"COUNT(*)", 1 )
41
+ # sql.update( :guest => :g ).set( :link_id => inner_select1).
42
+ # where.in( :"g.item_num", inner_select2 )
43
+ # p sql
44
+ #
45
+ # It will produce:
46
+ #
47
+ # UPDATE
48
+ # guest g
49
+ # SET link_id=
50
+ # (SELECT
51
+ # MAX(h.item_id)
52
+ # FROM item_data d
53
+ # INNER JOIN call_data h
54
+ # ON
55
+ # (d.item_nm = call_ref)
56
+ # WHERE
57
+ # (d.item_num = g.item_num)
58
+ # GROUP BY h.venue_nm
59
+ # HAVING
60
+ # (COUNT(*) = 1)
61
+ # )
62
+ # WHERE
63
+ # (g.item_num IN
64
+ # (SELECT
65
+ # d.item_num
66
+ # FROM item_data d
67
+ # INNER JOIN call_data h
68
+ # ON
69
+ # (d.item_nm = call_ref)
70
+ # GROUP BY h.venue_nm
71
+ # HAVING
72
+ # (COUNT(*) = 1)
73
+ # ))
74
+ #
75
+ # Queries can be modified "on the fly", which can be useful for dynamic construction:
76
+ #
77
+ # sql.delete.from( :datas ).where.ne( :x, "SOME TEXT" ).order_by( :y )
78
+ # p sql
79
+ #
80
+ # DELETE
81
+ # FROM datas
82
+ # WHERE
83
+ # (x != 'SOME TEXT')
84
+ # ORDER BY y
85
+ #
86
+ # sql._remove( :order_by )
87
+ # sql._get( :from ).push( :dataf )
88
+ # p sql
89
+ #
90
+ # DELETE
91
+ # FROM datas,dataf
92
+ # WHERE
93
+ # (x != 'SOME TEXT')
94
+ #################################################################################################
95
+ class SQLConstructor < SQLObject
96
+
97
+ attr_accessor :exporter, :tidy
98
+ attr_reader :obj, :dialect
99
+
100
+ # Dirty hack to make .join work on an array of SQLConstructors
101
+ alias :to_str :to_s
102
+
103
+ ##########################################################################
104
+ # Class constructor. Accepts an optional argument with a hash of
105
+ # parameters :dialect and :tidy to set the SQLExporter object in @exporter,
106
+ # or :exporter to receive a predefined SQLExporter object.
107
+ ##########################################################################
108
+ def initialize ( params = nil )
109
+ @dialect, @string, @obj, @tidy = nil, nil, nil, false
110
+ if params.is_a? Hash
111
+ @dialect = params[ :dialect ]
112
+ @tidy = params[ :tidy ]
113
+ @exporter = params[ :exporter ]
114
+ end
115
+ @exporter ||= SQLExporter.new @dialect, @tidy
116
+ @dialect = @exporter.dialect
117
+ end
118
+
119
+ ##########################################################################
120
+ # Add a SELECT statement with columns specified by *cols.
121
+ # Returns an instance of BasicSelect_[%dialect%] class.
122
+ ##########################################################################
123
+ def select ( *cols )
124
+ _getGenericQuery 'select', *cols
125
+ end
126
+
127
+ ##########################################################################
128
+ # Add a DELETE statement.
129
+ # Returns an instance of BasicDelete_[%dialect%] class.
130
+ ##########################################################################
131
+ def delete
132
+ _getGenericQuery 'delete'
133
+ end
134
+
135
+ ##########################################################################
136
+ # Add a INSERT statement
137
+ # Returns an instance of BasicInsert_[%dialect%] class.
138
+ ##########################################################################
139
+ def insert
140
+ _getGenericQuery 'insert'
141
+ end
142
+
143
+ ##########################################################################
144
+ # Add a UPDATE statement
145
+ # Returns an instance of BasicUpdate_[%dialect%] class.
146
+ ##########################################################################
147
+ def update ( *tabs )
148
+ _getGenericQuery 'update', *tabs
149
+ end
150
+
151
+ ##########################################################################
152
+ # Convert object to string by calling the .export() method of
153
+ # the @exporter object.
154
+ ##########################################################################
155
+ def to_s
156
+ # return @string if @string
157
+ @obj.inline = self.inline
158
+ @string = @exporter.export @obj
159
+ end
160
+
161
+ ##########################################################################
162
+ # Pass all unknown methods to @obj or throw an exception if the call
163
+ # already originated from @obj.
164
+ ##########################################################################
165
+ def method_missing ( method, *args )
166
+ return @obj.send( method, *args ) if @obj && @obj.child_caller != @obj
167
+ # raise an exception if the call is "bouncing" between self and @obj
168
+ raise NoMethodError, ERR_UNKNOWN_METHOD +
169
+ ": '#{method.to_s}' from #{@obj.class.name}"
170
+ end
171
+
172
+
173
+ #########
174
+ private
175
+ #########
176
+
177
+ ##########################################################################
178
+ # Returns an instance of Basic* child dialect-specific class
179
+ ##########################################################################
180
+ def _getGenericQuery ( type, *args )
181
+ class_basic = 'Basic' + type.capitalize
182
+ class_child = class_basic + '_' + @dialect
183
+ begin
184
+ @obj = self.class.const_get( class_child ).new self, *args
185
+ rescue NameError
186
+ @obj = self.class.const_get( class_basic ).new self, *args
187
+ end
188
+ end
189
+
190
+
191
+ ###############################################################################################
192
+ ###############################################################################################
193
+ class QAttr
194
+
195
+ attr_reader :name, :text, :val_type, :type, :no_commas
196
+ attr_accessor :val
197
+
198
+ def initialize ( init_hash = nil )
199
+ if init_hash.is_a? Hash
200
+ @name = init_hash[:name]
201
+ @text = init_hash[:text]
202
+ @val = init_hash[:val]
203
+ @val_type = init_hash[:val_type]
204
+ @type = init_hash[:type]
205
+ @no_commas = init_hash[:no_commas]
206
+ end
207
+ end
208
+
209
+
210
+ def to_s
211
+ if [ SQLValList, SQLAliasedList ].include? @val
212
+ result = @val.to_s
213
+ else
214
+ result = @text
215
+ if @val
216
+ val_arr = @val.is_a?( Array ) ? @val : [ @val ]
217
+ result += " " + val_arr.join( "," )
218
+ end
219
+ end
220
+ return result
221
+ end
222
+
223
+ end
224
+
225
+
226
+ ###############################################################################################
227
+ # Internal class - generic query attributes and methods. Should be parent to all Basic*
228
+ # classes.
229
+ ###############################################################################################
230
+ class GenericQuery < SQLObject
231
+
232
+ attr_accessor :caller, :string
233
+ attr_reader :type, :dialect, :exporter, :child_caller, :tidy, :attr_index_hints
234
+
235
+ # Dirty hack to make .join work on an array of GenericQueries
236
+ alias :to_str :to_s
237
+
238
+ ##########################################################################
239
+ # Class constructor.
240
+ # _caller - the caller object
241
+ ##########################################################################
242
+ def initialize ( _caller )
243
+ @caller = _caller
244
+ @dialect = @caller.dialect
245
+ @tidy = @caller.tidy
246
+ @exporter = _caller.exporter
247
+ @inline = @caller.inline
248
+ self._setMethods
249
+ end
250
+
251
+ ##########################################################################
252
+ # Returns an object by clause (keys of child class' METHODS attribute)
253
+ # or by SQLObject.name
254
+ ##########################################################################
255
+ def _get ( clause, *args )
256
+ result = nil
257
+ if @methods.has_key? clause
258
+ name = args ? args[0] : nil
259
+ result = self.send @methods[clause].name
260
+ result = result.val if result.is_a? QAttr
261
+ if name && [ Array, SQLValList, SQLAliasedList, SQLCondList ].include?( result.class )
262
+ # return the first object if multiple objects have the same name
263
+ result = result.find { |obj| obj.name == name }
264
+ end
265
+ end
266
+ return result
267
+ end
268
+
269
+ ##########################################################################
270
+ # NILs attribute by clause name (specified in the child class' METHODS
271
+ # attribure), or removes an named item from a list attribute.
272
+ # This method must be overriden in child classes if any methods were
273
+ # defined explicitly (not in METHODS).
274
+ ##########################################################################
275
+ def _remove ( clause, *args )
276
+ if @methods.has_key? clause
277
+ _attr = self.send @methods[clause].name
278
+ name = args ? args[0] : nil
279
+ if name && [ Array, SQLValList, SQLAliasedList, SQLCondList ].include?( _attr.class )
280
+ _attr.delete_if { |obj| obj.name == name }
281
+ else
282
+ self.send "#{@methods[clause].name}=", nil
283
+ end
284
+ @string = nil
285
+ end
286
+ return self
287
+ end
288
+
289
+ ##########################################################################
290
+ # Convert object to string by calling the .export() method of
291
+ # the @exporter object.
292
+ ##########################################################################
293
+ def to_s
294
+ return @string if @string
295
+ @string = @exporter.export self
296
+ end
297
+
298
+ ##########################################################################
299
+ # Process method calls described in the child's METHODS attribute.
300
+ # If no corresponding entries are found in all object's parent classes,
301
+ # then send missing methods calls to the @caller object.
302
+ ##########################################################################
303
+ def method_missing ( method, *args )
304
+ # If the method is described in the class' METHODS constant, then
305
+ # create an attribute with the proper name, an attr_accessor
306
+ # for it, and set it's value to the one in METHODS.
307
+ if @methods.has_key? method
308
+ _attr = @methods[method].dup
309
+ attr_name = _attr.name
310
+ val_obj = nil
311
+
312
+ # get the current value of the attribute {_attr.name}
313
+ self.class.send :attr_accessor, attr_name.to_sym
314
+ cur_attr = self.send attr_name.to_sym
315
+ cur_attr_val = cur_attr.is_a?( QAttr ) ? cur_attr.val : cur_attr
316
+
317
+ # Create an instance of the corresponding class if _attr.val is
318
+ # on of SQLObject container classes:
319
+ if [ SQLValList, SQLAliasedList, SQLCondList ].include? _attr.val
320
+ _attr.val = _attr.val.new *args
321
+
322
+ # Create an array of SQLObjects if _attr.val is SQLObject class:
323
+ elsif _attr.val == SQLObject
324
+ _attr.val = SQLObject.get *args #args.map{ |arg| SQLObject.get arg }
325
+
326
+ # Create an instance of the corresponding class if _attr.val is
327
+ # SQLConstructor or SQLConditional class:
328
+ elsif [ SQLConstructor, SQLConditional ].include? _attr.val
329
+ val_obj = cur_attr_val || _attr.val.new(
330
+ :dialect => @dialect,
331
+ :tidy => @tidy,
332
+ :exporter => @exporter,
333
+ :caller => self
334
+ )
335
+ _attr.val = val_obj
336
+
337
+ # create a BasicSelect dialect-specific child class:
338
+ elsif _attr.val == BasicSelect
339
+ val_obj = SQLConstructor.new(
340
+ :dialect => @dialect,
341
+ :tidy => @tidy,
342
+ :exporter => @exporter
343
+ ).select( *args )
344
+ _attr.val = val_obj
345
+
346
+ # If the :val parameter is some different class, then we should
347
+ # create an instance of it or return the existing value:
348
+ elsif _attr.val && _attr.val.ancestors.include?( GenericQuery )
349
+ val_obj = _getBasicClass( _attr.val, _attr.text )
350
+ _attr.val = val_obj
351
+ end
352
+
353
+ # If the object already has attribute {_attr.name} defined and it's
354
+ # an array or one of the SQL* class containers,then we should rather
355
+ # append to it than reassign the value.
356
+ # If :attr_val=list, then create a new SQLAliasedList container.
357
+ if [ Array, SQLValList, SQLAliasedList, SQLCondList ].include?( cur_attr_val.class ) ||
358
+ _attr.val_type == 'list'
359
+ cur_attr_val ||= SQLAliasedList.new
360
+ cur_attr_val.no_commas = true if _attr.no_commas
361
+ cur_attr_val.push _attr.val
362
+ _attr = cur_attr_val
363
+ end
364
+
365
+ # self.class.send :attr_accessor, attr_name.to_sym if ! cur_attr_val
366
+ self.send "#{attr_name}=", _attr
367
+
368
+ @string = nil
369
+ return ( val_obj || self )
370
+ end
371
+
372
+ # Otherwise send the call to @caller object
373
+ @child_caller = self
374
+ return @caller.send( method.to_sym, *args ) if @caller
375
+ raise NoMethodError, ERR_UNKNOWN_METHOD + ": " + method.to_s
376
+ end
377
+
378
+ ###########
379
+ protected
380
+ ###########
381
+
382
+ ##########################################################################
383
+ # Creates a new BasicJoin object for the JOIN statement.
384
+ ##########################################################################
385
+ def _addJoin ( type, *tables )
386
+ @string = nil
387
+ join = _getBasicClass BasicJoin, type, *tables
388
+ @attr_joins ||= [ ]
389
+ @attr_joins.push join
390
+ return join
391
+ end
392
+
393
+ ##########################################################################
394
+ # Returns an instance of Basic* child dialect-specific class
395
+ ##########################################################################
396
+ def _getBasicClass ( class_basic, *args )
397
+ class_basic_name = class_basic.name.sub /^(?:\w+::)*/, ''
398
+ class_child = class_basic_name + '_' + @dialect
399
+ if SQLConstructor.const_defined? class_child.to_sym
400
+ SQLConstructor.const_get( class_child.to_sym ).new self, *args
401
+ else
402
+ SQLConstructor.const_get( class_basic_name.to_sym ).new self, *args
403
+ end
404
+ end
405
+
406
+ ##########################################################################
407
+ # Returns the METHODS hash of child dialect-specific class merged with
408
+ # parent's METHODS hash.
409
+ ##########################################################################
410
+ def _setMethods
411
+ if ! @methods
412
+ methods_self = { }
413
+ self.class.ancestors.each do |_class|
414
+ next if ! _class.ancestors.include? SQLConstructor::GenericQuery
415
+ begin
416
+ class_methods = _class.const_get :METHODS || { }
417
+ rescue
418
+ class_methods = { }
419
+ end
420
+ methods_self.merge! class_methods
421
+ end
422
+ @methods = methods_self
423
+ end
424
+ return @methods
425
+ end
426
+
427
+ end
428
+
429
+
430
+ ###############################################################################################
431
+ # Internal class which represents a basic JOIN statement.
432
+ ###############################################################################################
433
+ class BasicJoin < GenericQuery
434
+
435
+ attr_accessor :join_on, :join_sources, :join_using
436
+
437
+ METHODS = {
438
+ :on => QAttr.new( :name => 'join_on', :text => 'ON', :val => SQLConditional ),
439
+ :using => QAttr.new( :name => 'join_using', :text => 'USING', :val => SQLObject ),
440
+ }
441
+
442
+ ##########################################################################
443
+ # Class contructor. Takes a caller object as the first argument, JOIN
444
+ # type as the second argument, and a list of sources for the JOIN clause
445
+ ##########################################################################
446
+ def initialize ( _caller, type, *sources )
447
+ type = type.to_s
448
+ type.upcase!.gsub! /_/, ' '
449
+ super _caller
450
+ @type = type
451
+ @join_sources = SQLAliasedList.new *sources
452
+ end
453
+
454
+ ##########################################################################
455
+ # Adds more sources to @join_sources list
456
+ ##########################################################################
457
+ def join_more ( *sources )
458
+ @join_sources.push *sources
459
+ end
460
+
461
+ ##########################################################################
462
+ # Export to string with sources aliases
463
+ ##########################################################################
464
+ def to_s
465
+ return @string if @string
466
+ result = @type + " "
467
+ arr = [ ]
468
+ @join_sources.each do |src|
469
+ _alias = src.alias ? " " + src.alias.to_s : ""
470
+ str = src.to_s + _alias
471
+ arr << str
472
+ end
473
+ result += arr.join ','
474
+ result += @exporter.separator
475
+ result += "ON " + @join_on.val.to_s if @join_on
476
+ @string = result
477
+ end
478
+
479
+ end
480
+
481
+
482
+ ###############################################################################################
483
+ # Internal class which represents a basic UNION statement.
484
+ ###############################################################################################
485
+ class BasicUnion < GenericQuery
486
+
487
+ ##########################################################################
488
+ # Class contructor. Takes a caller object as the first argument and UNION
489
+ # type as the second argument. Inits @obj to new SQLConstructor instance
490
+ ##########################################################################
491
+ def initialize ( _caller, type )
492
+ @type = type
493
+ super _caller
494
+ @obj = SQLConstructor.new( :dialect => @dialect, :tidy => @tidy )
495
+ end
496
+
497
+ ##########################################################################
498
+ # Export to string
499
+ ##########################################################################
500
+ def to_s
501
+ @type + @caller.exporter.separator + @obj.to_s
502
+ end
503
+
504
+ ##########################################################################
505
+ # Override GenericQuery method and send call to @obj
506
+ ##########################################################################
507
+ def _get ( *args )
508
+ @obj._get *args
509
+ end
510
+
511
+ ##########################################################################
512
+ # Override GenericQuery method and send call to @obj
513
+ ##########################################################################
514
+ def _remove ( *args )
515
+ @obj._remove *args
516
+ end
517
+
518
+ ##########################################################################
519
+ # Send call to @obj
520
+ ##########################################################################
521
+ def method_missing ( method, *args )
522
+ @obj.send method, *args
523
+ end
524
+
525
+ end
526
+
527
+
528
+ ###############################################################################################
529
+ # Internal class which represents a basic SELECT statement.
530
+ ###############################################################################################
531
+ class BasicSelect < GenericQuery
532
+
533
+ attr_accessor :attr_expression, :attr_group_by, :attr_unions, :attr_index_hints,
534
+ :attr_distinction, :attr_having, :attr_group_by_order, :attr_where, :attr_from,
535
+ :attr_first, :attr_skip, :attr_order_by, :attr_order_by_order, :attr_joins
536
+
537
+ # Hash - list of available class meta-methods, which would be processed by .method_missing()
538
+ # to set the appropriate object's attributes (as defined in the METHODS hash itself).
539
+ # The keys of the hash are the methods names (symbols), the values are instances of
540
+ # the QAttr class.
541
+ METHODS = {
542
+ :where => QAttr.new( :name => 'attr_where', :text => 'WHERE', :val => SQLConditional ),
543
+ :from => QAttr.new( :name => 'attr_from', :text => 'FROM', :val => SQLAliasedList ),
544
+ :all => QAttr.new( :name => 'attr_distinction', :text => 'ALL' ),
545
+ :distinct => QAttr.new( :name => 'attr_distinction', :text => 'DISTINCT' ),
546
+ :distinctrow => QAttr.new( :name => 'attr_distinction', :text => 'DISTINCTROW' ),
547
+ :having => QAttr.new( :name => 'attr_having', :text => 'HAVING', :val => SQLConditional ),
548
+ :group_by => QAttr.new( :name => 'attr_group_by', :text => 'GROUP BY', :val => SQLObject),
549
+ :group_by_asc => QAttr.new( :name => 'attr_group_by_order', :text => 'ASC' ),
550
+ :group_by_desc => QAttr.new( :name => 'attr_group_by_order', :text => 'DESC' ),
551
+ :union => QAttr.new(
552
+ :name => 'attr_unions',
553
+ :text => 'UNION',
554
+ :val_type => 'list',
555
+ :no_commas => true,
556
+ :val => SQLConstructor::BasicUnion
557
+ ),
558
+ :union_all => QAttr.new(
559
+ :name => 'attr_unions',
560
+ :text => 'UNION_ALL',
561
+ :val_type => 'list',
562
+ :no_commas => true,
563
+ :val => SQLConstructor::BasicUnion
564
+ ),
565
+ :union_distinct => QAttr.new(
566
+ :name => 'attr_unions',
567
+ :text => 'UNION_DISTINCT',
568
+ :val_type => 'list',
569
+ :no_commas => true,
570
+ :val => SQLConstructor::BasicUnion
571
+ ),
572
+ :join => SQLConstructor::QAttr.new( :name => "attr_joins", :text => "JOIN",
573
+ :val => SQLConstructor::BasicJoin,
574
+ :val_type => 'list' ),
575
+ :first => QAttr.new( :name => 'attr_first', :text => 'FIRST', :val => SQLObject ),
576
+ :skip => QAttr.new( :name => 'attr_skip', :text => 'SKIP', :val => SQLObject ),
577
+ :order_by => QAttr.new( :name => 'attr_order_by', :text => 'ORDER BY', :val => SQLObject ),
578
+ :order_by_asc => QAttr.new( :name => 'attr_order_by_order', :text => 'ASC' ),
579
+ :order_by_desc => QAttr.new( :name => 'attr_order_by_order', :text => 'DESC' )
580
+ }
581
+
582
+ ##########################################################################
583
+ # Class constructor.
584
+ # _caller - the caller object
585
+ # *list - list of sources for the FROM clause
586
+ ##########################################################################
587
+ def initialize ( _caller, *list )
588
+ super _caller
589
+ @attr_expression = QAttr.new(
590
+ :name => 'attr_expression',
591
+ :text => '',
592
+ :val => SQLAliasedList.new( *list )
593
+ )
594
+ end
595
+
596
+ ##########################################################################
597
+ # Add more objects to SELECT expression list ( @attr_expression[:val] )
598
+ ##########################################################################
599
+ def select_more ( *list )
600
+ @attr_expression.val.push *list
601
+ end
602
+
603
+ end
604
+
605
+
606
+ ###############################################################################################
607
+ # Internal class which represents a basic DELETE statement.
608
+ ###############################################################################################
609
+ class BasicDelete < GenericQuery
610
+
611
+ attr_accessor :del_using, :attr_where, :attr_from, :attr_skip, :attr_first, :attr_order_by,
612
+ :attr_order_by_order
613
+
614
+ METHODS = {
615
+ :using => QAttr.new( :name => 'del_using', :text => 'USING', :val => SQLObject ),
616
+ :where => QAttr.new( :name => 'attr_where', :text => 'WHERE', :val => SQLConditional),
617
+ :from => QAttr.new( :name => 'attr_from', :text => 'FROM', :val => SQLAliasedList),
618
+ :first => QAttr.new( :name => 'attr_first', :text => 'FIRST', :val => SQLObject ),
619
+ :skip => QAttr.new( :name => 'attr_skip', :text => 'SKIP', :val => SQLObject ),
620
+ :order_by => QAttr.new( :name => 'attr_order_by', :text => 'ORDER BY',
621
+ :val => SQLObject ),
622
+ :order_by_asc => QAttr.new( :name => 'attr_order_by_order', :text => 'ASC' ),
623
+ :order_by_desc => QAttr.new( :name => 'attr_order_by_order', :text => 'DESC' )
624
+ }
625
+
626
+ ##########################################################################
627
+ # Class constructor.
628
+ # _caller - the caller object
629
+ ##########################################################################
630
+ def initialize ( _caller )
631
+ super
632
+ end
633
+
634
+ end
635
+
636
+
637
+ ###############################################################################################
638
+ # Internal class which represents a basic INSERT statement.
639
+ ###############################################################################################
640
+ class BasicInsert < GenericQuery
641
+
642
+ attr_reader :ins_into, :ins_values, :ins_set, :ins_columns, :ins_select
643
+
644
+ METHODS = {
645
+ :into => QAttr.new( :name => 'ins_into', :text => 'INTO', :val => SQLObject ),
646
+ :values => QAttr.new( :name => 'ins_values', :text => 'VALUES', :val => SQLValList),
647
+ :set => QAttr.new( :name => 'ins_set', :text => 'SET', :val => SQLCondList ),
648
+ :columns => QAttr.new( :name => 'ins_columns', :text => 'COLUMNS', :val => SQLObject),
649
+ :select => QAttr.new( :name => 'ins_select', :text => '', :val => BasicSelect )
650
+ }
651
+
652
+ ##########################################################################
653
+ # Class constructor.
654
+ # _caller - the caller object
655
+ ##########################################################################
656
+ def initialize ( _caller )
657
+ super
658
+ end
659
+
660
+ end
661
+
662
+
663
+ ###############################################################################################
664
+ # Internal class which represents a basic INSERT statement.
665
+ ###############################################################################################
666
+ class BasicUpdate < GenericQuery
667
+
668
+ attr_accessor :upd_tables, :upd_set, :attr_where, :attr_order_by, :attr_first, :attr_skip
669
+
670
+ METHODS = {
671
+ :tables => QAttr.new( :name => 'upd_tables', :text => '', :val => SQLObject ),
672
+ :set => QAttr.new( :name => 'upd_set', :text => 'SET', :val => SQLCondList ),
673
+ :where => QAttr.new( :name => 'attr_where', :text => 'WHERE', :val => SQLConditional),
674
+ :first => QAttr.new( :name => 'attr_first', :text => 'FIRST', :val => SQLObject ),
675
+ :skip => QAttr.new( :name => 'attr_skip', :text => 'SKIP', :val => SQLObject ),
676
+ }
677
+
678
+ ##########################################################################
679
+ # Class constructor.
680
+ # _caller - the caller object
681
+ ##########################################################################
682
+ def initialize ( _caller, *list )
683
+ super _caller
684
+ @upd_tables = QAttr.new( :name => 'upd_tables', :text => '',
685
+ :val => SQLAliasedList.new( *list ) )
686
+ end
687
+
688
+ ##########################################################################
689
+ # Add tables to UPDATE tables list ( @upd_tables[:val] )
690
+ ##########################################################################
691
+ def update_more ( *list )
692
+ @upd_tables.val.push *list
693
+ end
694
+
695
+ end
696
+
697
+ end
698
+
699
+
700
+ ##################################################################################################
701
+ ##################################################################################################
702
+ # Include dialect-specific classes from ./dialects/constructor/ :
703
+ # This should be done after SQLConstructor is defined.
704
+ ##################################################################################################
705
+ ##################################################################################################
706
+
707
+ Dir[ DIALECTS_PATH + "/*-constructor.rb"].each { |file| require file }
708
+