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