sqlconstructor 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+