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.
- checksums.yaml +15 -0
- data/LICENSE.md +16 -0
- data/README.md +161 -0
- data/Rakefile +11 -0
- data/doc/Object.html +267 -0
- data/doc/Rakefile.html +150 -0
- data/doc/SQLAliasedList.html +717 -0
- data/doc/SQLColumn.html +326 -0
- data/doc/SQLCondList.html +318 -0
- data/doc/SQLConditional.html +1082 -0
- data/doc/SQLConditional/BasicCond.html +325 -0
- data/doc/SQLConstructor.html +763 -0
- data/doc/SQLConstructor/BasicDelete.html +383 -0
- data/doc/SQLConstructor/BasicDelete_mysql.html +368 -0
- data/doc/SQLConstructor/BasicInsert.html +339 -0
- data/doc/SQLConstructor/BasicInsert_mysql.html +325 -0
- data/doc/SQLConstructor/BasicJoin.html +408 -0
- data/doc/SQLConstructor/BasicJoin_mysql.html +439 -0
- data/doc/SQLConstructor/BasicSelect.html +554 -0
- data/doc/SQLConstructor/BasicSelect_example.html +288 -0
- data/doc/SQLConstructor/BasicSelect_mysql.html +466 -0
- data/doc/SQLConstructor/BasicUnion.html +396 -0
- data/doc/SQLConstructor/BasicUpdate.html +409 -0
- data/doc/SQLConstructor/BasicUpdate_mysql.html +310 -0
- data/doc/SQLConstructor/GenericQuery.html +797 -0
- data/doc/SQLConstructor/QAttr.html +398 -0
- data/doc/SQLConstructorTest.html +603 -0
- data/doc/SQLExporter.html +382 -0
- data/doc/SQLExporter/Exporter_generic.html +413 -0
- data/doc/SQLExporter/Exporter_mysql.html +395 -0
- data/doc/SQLObject.html +525 -0
- data/doc/SQLValList.html +322 -0
- data/doc/SQLValue.html +375 -0
- data/doc/created.rid +12 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +356 -0
- data/doc/js/darkfish.js +118 -0
- data/doc/js/jquery.js +32 -0
- data/doc/js/quicksearch.js +114 -0
- data/doc/js/thickbox-compressed.js +10 -0
- data/doc/lib/dialects/example-constructor_rb.html +52 -0
- data/doc/lib/dialects/mysql-constructor_rb.html +52 -0
- data/doc/lib/dialects/mysql-exporter_rb.html +54 -0
- data/doc/lib/sqlconditional_rb.html +64 -0
- data/doc/lib/sqlconstructor_rb.html +52 -0
- data/doc/lib/sqlerrors_rb.html +54 -0
- data/doc/lib/sqlexporter_rb.html +55 -0
- data/doc/lib/sqlobject_rb.html +54 -0
- data/doc/rdoc.css +763 -0
- data/doc/test/queries_rb.html +56 -0
- data/doc/test_rb.html +52 -0
- data/lib/dialects/example-constructor.rb +45 -0
- data/lib/dialects/mysql-constructor.rb +247 -0
- data/lib/dialects/mysql-exporter.rb +108 -0
- data/lib/sqlconditional.rb +196 -0
- data/lib/sqlconstructor.rb +708 -0
- data/lib/sqlerrors.rb +15 -0
- data/lib/sqlexporter.rb +125 -0
- data/lib/sqlobject.rb +284 -0
- data/test/queries.rb +92 -0
- 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
|
+
|