SqlStatement 1.0.2 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/ChangeLog +14 -0
- data/lib/sql/statement.rb +228 -218
- data/test/test_combine_statement.rb +52 -0
- data/test/test_primatives.rb +31 -0
- data/test/test_statementparts.rb +127 -0
- data/test/test_statements.rb +68 -0
- metadata +59 -47
- data/doc/EXAMPLE +0 -124
data/doc/ChangeLog
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
=Change Log
|
2
|
+
==2.0
|
3
|
+
* Vastly redesign the DSL. The methods for adding components to an
|
4
|
+
SQL statement have all been renamed, and internal state is no longer
|
5
|
+
directly exposed to the outside. The interface is now much more intutive and
|
6
|
+
consistent. Please read the documentation to find out how this version of
|
7
|
+
the library works.
|
8
|
+
* Left joins have been added
|
9
|
+
* There's no more SelectParts class --
|
10
|
+
the corresponding semantics have been built directly into the Select class.
|
11
|
+
* <tt>Select.new do |s| ... end</tt> syntax has been added
|
12
|
+
* Select statements now remember the order of fields and tables. This makes
|
13
|
+
the next two changes feasible.
|
14
|
+
* Support for Mysql's STRAIGHT_JOIN modifier has been added
|
15
|
+
* Unit tests have been added
|
2
16
|
==1.0.2
|
3
17
|
* Make the methods in SQLHelpers available as module functions too, so
|
4
18
|
not only can you call "include SQLHelpers; sql_func", you can call
|
data/lib/sql/statement.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
# Everything in Ruby is a descendant of the Object class. Practically
|
2
3
|
# speaking, this means that any object that hasn't otherwise defined
|
3
4
|
# the +placeheld+ and <tt>to_sqlpart</tt> methods will be treated as a piece
|
@@ -12,7 +13,7 @@ class Object
|
|
12
13
|
# then the values to be bound to them are returned here, in the proper order.
|
13
14
|
# This allows SQL statements to be called in a manner similar to
|
14
15
|
# s=Select.new
|
15
|
-
# s <<
|
16
|
+
# s << Select.new {...}
|
16
17
|
# dbh.execute(s.to_s, *s.placeheld)
|
17
18
|
# When there are no unbound variables, and empty array is returned.
|
18
19
|
def placeheld; [self]; end
|
@@ -49,6 +50,17 @@ class NilClass
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
53
|
+
module SQLHelpers
|
54
|
+
|
55
|
+
#This helper method takes a string, and encapsulates it as a proper
|
56
|
+
#SQL expression, so that it won't get passed to the database as
|
57
|
+
#though it were a (potentially hostile) string value.
|
58
|
+
def string_func(str,placeheld=[])
|
59
|
+
SQLStatement::Function.new(str,placeheld)
|
60
|
+
end
|
61
|
+
extend self
|
62
|
+
end
|
63
|
+
|
52
64
|
module SQLStatement
|
53
65
|
|
54
66
|
# This class is used to represent complex SQL expressions, so that
|
@@ -56,7 +68,7 @@ module SQLStatement
|
|
56
68
|
# conditions in a WHERE clause. There are no utility classes for
|
57
69
|
# generating these (even though they'll probably include field names
|
58
70
|
# and other kinds of values), but for convenience, they can be generated
|
59
|
-
# with <tt>
|
71
|
+
# with <tt>SQLHelpers#string_func</tt>.
|
60
72
|
class Function < String
|
61
73
|
|
62
74
|
def initialize(val="",placeheld=[])
|
@@ -73,6 +85,32 @@ class Function < String
|
|
73
85
|
end
|
74
86
|
end
|
75
87
|
|
88
|
+
#This class is used to represent table names and field names. It's like a
|
89
|
+
#+Symbol+ in this regard, but it can be garbage collected.
|
90
|
+
class Identifier < String
|
91
|
+
alias_method :_ken_sqlstatement_old_idx, :[]
|
92
|
+
def self.[] param
|
93
|
+
self.new param
|
94
|
+
end
|
95
|
+
def [] *params
|
96
|
+
if params.length==1 and params[0].is_a?(Symbol)
|
97
|
+
SQL_Field.new(self,params[0])
|
98
|
+
elsif params.length==1 and params[0].is_a?(Identifier)
|
99
|
+
SQL_Field.new(self,params[0])
|
100
|
+
else
|
101
|
+
_ken_sqlstatement_old_idx *params
|
102
|
+
end
|
103
|
+
end
|
104
|
+
def to_sqlpart
|
105
|
+
"`#{self}`"
|
106
|
+
end
|
107
|
+
def placeheld
|
108
|
+
[]
|
109
|
+
end
|
110
|
+
def dbid
|
111
|
+
self
|
112
|
+
end
|
113
|
+
end
|
76
114
|
|
77
115
|
# +SQL_Field+ is used for representing field names qualified by table names.
|
78
116
|
# Their text expansion looks like <tt>`tablename`.`fieldname`</tt>.
|
@@ -99,211 +137,148 @@ class SQL_Field
|
|
99
137
|
def placeheld; []; end
|
100
138
|
end
|
101
139
|
|
102
|
-
|
103
|
-
#
|
104
|
-
#
|
140
|
+
# This class is used to represent a SELECT statement, or an incomplete set of
|
141
|
+
# parts to add to a SELECT statement. If we wanted to generate SQL code
|
142
|
+
# similar to
|
105
143
|
# SELECT `value1`,`value2`
|
106
144
|
# FROM `jointable`,`dictionary1`,`dictionary2`
|
107
145
|
# WHERE `jointable`.`id1`=`dictionary1`.`id` AND `jointable`.`id2`=`dictionary2`.`id`
|
108
146
|
# then it may make sense for your application to generate
|
109
147
|
# the +dictionary1+ code and the +dictionary2+ code separately,
|
110
|
-
# in different +
|
148
|
+
# in different +Select+ objects, then combine them into one +Select+.
|
149
|
+
# def foo(x)
|
150
|
+
# Select.new do |s|
|
151
|
+
# s.field :"value#{x}"
|
152
|
+
# s.table :"dictionary#{x}"
|
153
|
+
# s.condition string_func("`jointable`.`id#{x}`=`dictionary#{x}`.`id`")
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
111
157
|
# stmt=Select.new
|
112
|
-
# stmt.
|
158
|
+
# stmt.table :jointable
|
113
159
|
# (1..2).each do |x|
|
114
|
-
#
|
115
|
-
# s.add_fields [:"value#{x}"]
|
116
|
-
# s.add_tables [:"dictionary#{x}"]
|
117
|
-
# s.conditions << SQLFunc("`jointable`.`id#{x}`=`dictionary#{x}`.`id`")
|
118
|
-
# stmt << s
|
160
|
+
# stmt << foo(x)
|
119
161
|
# end
|
120
|
-
# dbh.execute(stmt
|
121
|
-
class
|
122
|
-
|
123
|
-
def expectarray (arg,hash)
|
124
|
-
v=hash[arg]
|
125
|
-
if v==nil
|
126
|
-
[]
|
127
|
-
elsif Array===v
|
128
|
-
v
|
129
|
-
else
|
130
|
-
[v]
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def expecthash (arg,hash,*options)
|
135
|
-
v=hash[arg]
|
136
|
-
if v==nil
|
137
|
-
v={}
|
138
|
-
elsif Array===v and not options.include?(:PreserveArray)
|
139
|
-
v=Hash[*([v,v].transpose.flatten)]
|
140
|
-
elsif Array===v
|
141
|
-
v
|
142
|
-
elsif Hash===v
|
143
|
-
v
|
144
|
-
else
|
145
|
-
[v]
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
protected :expectarray,:expecthash
|
150
|
-
|
162
|
+
# dbh.execute(stmt)
|
163
|
+
class Select
|
151
164
|
#Initializes the various pieces of the SQL statement to values specified in the hash.
|
152
165
|
#Use Symbols corresponding to the attribute names to set an attribute here, otherwise a default
|
153
166
|
#empty array or hash will be used.
|
154
|
-
|
155
|
-
|
156
|
-
@tables=expecthash :tables,hash,:PreserveArray
|
157
|
-
@conditions=expectarray :conditions,hash
|
158
|
-
@groupby=expectarray :groupby,hash
|
159
|
-
@orderby=expectarray :orderby,hash
|
160
|
-
end
|
161
|
-
|
162
|
-
#See the documentation for SelectStatement which describes these.
|
163
|
-
attr_accessor :conditions, :groupby, :orderby, :fields, :tables
|
164
|
-
|
165
|
-
#Adds an array of unaliased fields, or a hash of aliased fields to
|
166
|
-
#this SQL statement. See documentation at SelectStatement
|
167
|
-
def add_fields (newfields)
|
168
|
-
if newfields.is_a?(Hash)
|
169
|
-
@fields.merge! newfields
|
170
|
-
else
|
171
|
-
newfields.each { |x| @fields[x]=x }
|
172
|
-
end
|
173
|
-
nil
|
174
|
-
end
|
175
|
-
|
176
|
-
#Adds an array of unaliased tables or a hash of aliased tables to
|
177
|
-
#this SQL statement. See documentation for SelectStatement
|
178
|
-
def add_tables (newtables)
|
179
|
-
if newtables.is_a?(Hash)
|
180
|
-
@tables.merge! newtables
|
181
|
-
else
|
182
|
-
newtables.each { |x| @tables[x]=x }
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
#Merge several SelectParts objects into one.
|
187
|
-
def +(rhs)
|
188
|
-
b=self.dup
|
189
|
-
b.fields.merge!(rhs.fields)
|
190
|
-
b.tables.merge!(rhs.tables)
|
191
|
-
b.conditions+=rhs.conditions
|
192
|
-
b.groupby+=rhs.groupby
|
193
|
-
b.orderby+=rhs.orderby
|
194
|
-
b
|
195
|
-
end
|
196
|
-
|
197
|
-
class << self
|
198
|
-
#Use this to define additional array attributes in descendant classes
|
199
|
-
#with all of the necessary addition, initialization and merging (<<)
|
200
|
-
#semantics. <b>You can only call this function once per class, as
|
201
|
-
#it redefines methods each time it is called.</b>
|
202
|
-
def newlists *lists
|
203
|
-
attr_accessor *lists
|
204
|
-
|
205
|
-
lines=lists.collect do |listname|
|
206
|
-
["@#{listname}=expectarray :#{listname},hash",
|
207
|
-
"b.#{listname}+=rhs.#{listname}"]
|
208
|
-
end.transpose
|
209
|
-
class_eval <<-"end;"
|
210
|
-
def initialize hash={}
|
211
|
-
super
|
212
|
-
#{lines[0].join("\n")}
|
213
|
-
end
|
214
|
-
def + (rhs)
|
215
|
-
b=super
|
216
|
-
#{lines[1].join("\n")}
|
217
|
-
b
|
218
|
-
end
|
219
|
-
end;
|
220
|
-
end
|
221
|
-
protected :newlists
|
222
|
-
end
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
# Creates a SELECT statement
|
227
|
-
class Select
|
167
|
+
#
|
168
|
+
#The block is a convenient way to scpe the creation of the whole statement into one block.
|
228
169
|
def initialize
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
170
|
+
@field_names=[]
|
171
|
+
@field_aliases=[]
|
172
|
+
@table_names=[]
|
173
|
+
@table_aliases=[]
|
174
|
+
@conditions=[]
|
175
|
+
@groupby_fields=[]
|
176
|
+
@orderby_fields=[]
|
177
|
+
@leftjoin_alias=[]
|
178
|
+
@leftjoin_table=[]
|
179
|
+
@leftjoin_condition=[]
|
180
|
+
yield self if block_given?
|
181
|
+
end
|
182
|
+
|
183
|
+
#Add a condition to the WHERE clause of the SQL statment. Conditions will be
|
184
|
+
#joined by AND. This should either be an array representing an s-expression
|
185
|
+
#for the condition, or it should be a SQLStatement::Function containing a
|
186
|
+
#string for the condition.
|
187
|
+
def condition c
|
188
|
+
@conditions << c
|
189
|
+
self
|
190
|
+
end
|
191
|
+
def groupby c
|
192
|
+
@groupby_fields << c
|
193
|
+
self
|
194
|
+
end
|
195
|
+
def orderby c
|
196
|
+
@orderby_fields << c
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
|
202
|
+
#Add a field or expression to be retrieved in the expression. In a
|
237
203
|
#+SELECT+ statement, this appears immediately after the +SELECT+ keyword.
|
238
204
|
#In an +UPDATE+ statement, this appears after the +SET+ keyword. (And in other
|
239
|
-
#places for other kinds of queries #In an +UPDATE+ statement, this appears after the +SET+ keyword. (And in other
|
240
205
|
#places for other kinds of queries. The SQL inventors were nothing if not consistent.)
|
241
206
|
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
#Adds an array of unaliased fields, or a hash of aliased fields to
|
248
|
-
#this SQL statement.
|
249
|
-
def add_fields (newfields)
|
250
|
-
if newfields.is_a?(Hash)
|
251
|
-
@fields.merge! newfields
|
252
|
-
else
|
253
|
-
newfields.each { |x| @fields[x]=x }
|
254
|
-
end
|
255
|
-
nil
|
207
|
+
# +thefield+ is a field or expression to put in the result of this Select statement
|
208
|
+
# +thealias+ is a name to use for the column, and can be omitted to use the default
|
209
|
+
def field thefield, thealias=nil
|
210
|
+
@field_names << thefield
|
211
|
+
@field_aliases << thealias
|
256
212
|
end
|
257
|
-
|
258
|
-
#This is the tables to include in the query (i.e. the +FROM+ clause).
|
213
|
+
# Add a table to join into the expression, either as the first table, or by using an inner join
|
259
214
|
#
|
260
|
-
|
261
|
-
|
262
|
-
#
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
215
|
+
#+thefield+ is a field or expression to put in the result of this Select statement
|
216
|
+
#+thealias+ is a name to use for the column, and can be omitted to use the default
|
217
|
+
#
|
218
|
+
#If the table/alias pair is already included in the query, it is not included again, but
|
219
|
+
#if a table can be included again by using a new alias
|
220
|
+
def table thetable, thealias=nil
|
221
|
+
return if @table_names.zip(@table_aliases).include? [thetable,thealias]
|
222
|
+
@table_names << thetable
|
223
|
+
@table_aliases << thealias
|
224
|
+
end
|
225
|
+
#Adds a left-join with join conditon
|
226
|
+
def leftjoin table, condition, the_alias=nil
|
227
|
+
return if @leftjoin_table.zip(@leftjoin_alias).include? [table,the_alias]
|
228
|
+
@leftjoin_alias << the_alias
|
229
|
+
@leftjoin_table << table
|
230
|
+
@leftjoin_condition << condition
|
273
231
|
end
|
274
232
|
|
275
|
-
#This is an array of Function objects that specify the contents of
|
276
|
-
#the +WHERE+ clause.
|
277
|
-
attr_accessor :conditions
|
278
233
|
|
279
|
-
#Specifieds fields or expressions to group by, as an array.
|
280
|
-
attr_accessor :groupby
|
281
234
|
|
282
|
-
|
283
|
-
|
235
|
+
|
236
|
+
#Merge several SelectParts objects into one.
|
237
|
+
def +(rhs)
|
238
|
+
b=self.dup
|
239
|
+
b << rhs
|
240
|
+
end
|
284
241
|
|
285
|
-
|
286
|
-
#
|
287
|
-
#
|
242
|
+
|
243
|
+
#Returns two lists: a list of field names (aliases) in the expression, and a
|
244
|
+
#list of field values. If you overriding this class (or any of its
|
245
|
+
#subclasses) to constructing other fields from other lists, override this
|
246
|
+
#function, call +super+, construct the additional fields here, and add them
|
247
|
+
#to the result of +super+. Then return the results.
|
288
248
|
def allfields
|
289
|
-
@
|
249
|
+
[@field_aliases.dup, @field_names.dup]
|
290
250
|
end
|
291
251
|
|
292
252
|
#Select whether this is a SELECT statement or a SELECT DISTINCT
|
293
253
|
#statement. (non-distinct by default)
|
294
|
-
|
254
|
+
attr_accessor :distinct
|
255
|
+
|
256
|
+
#Determine whether to use MySQL's STRAIGHT_JOIN modifier to override the query optimizer
|
257
|
+
attr_accessor :straight_join
|
295
258
|
|
296
|
-
def
|
297
|
-
@
|
259
|
+
def straight_join
|
260
|
+
@straight_join ||= false
|
298
261
|
end
|
299
262
|
|
300
263
|
#Merges the various parts of a SelectParts into the correct places in this SQL statement.
|
301
264
|
def << (parts)
|
302
|
-
|
303
|
-
|
265
|
+
parts.table_names.zip(parts.table_aliases).each do |name,thealias|
|
266
|
+
next if @table_names.zip(@table_aliases).include? [name,thealias]
|
267
|
+
@table_names << name
|
268
|
+
@table_aliases << thealias
|
269
|
+
end
|
270
|
+
parts.leftjoin_alias.zip(parts.leftjoin_table,parts.leftjoin_condition).each \
|
271
|
+
do |thealias,thetable,thecondition|
|
272
|
+
next if @leftjoin_alias.zip(@leftjoin_table).include? [thealias,thetable]
|
273
|
+
@leftjoin_alias << thealias
|
274
|
+
@leftjoin_table << thetable
|
275
|
+
@leftjoin_condition << thecondition
|
276
|
+
end
|
277
|
+
@field_names += parts.field_names
|
278
|
+
@field_aliases += parts.field_aliases
|
304
279
|
@conditions += parts.conditions
|
305
|
-
@
|
306
|
-
@
|
280
|
+
@groupby_fields += parts.groupby_fields
|
281
|
+
@orderby_fields += parts.orderby_fields
|
307
282
|
self
|
308
283
|
end
|
309
284
|
|
@@ -312,15 +287,26 @@ class Select
|
|
312
287
|
#be retrieved with the placeheld method, and used in a manner similar to
|
313
288
|
# dbh.execute(s.to_s,*s.placeheld)
|
314
289
|
def to_s
|
315
|
-
statement="SELECT
|
316
|
-
|
317
|
-
|
318
|
-
|
290
|
+
statement="SELECT"
|
291
|
+
statement << " DISTINCT" if distinct
|
292
|
+
statement << " STRAIGHT_JOIN" if straight_join
|
293
|
+
statement << "\n " << fields_s
|
294
|
+
statement << "\nFROM " << tables_s
|
295
|
+
v=conditions_s; statement << "\nWHERE "<< v if v
|
296
|
+
v=groupby_s; statement << "\nGROUP BY "<< v if v
|
297
|
+
v=orderby_s; statement << "\nORDER BY "<< v if v
|
319
298
|
return statement
|
320
299
|
end
|
321
300
|
|
322
301
|
def placeheld
|
323
|
-
(
|
302
|
+
(
|
303
|
+
allfields[1] +
|
304
|
+
@table_names +
|
305
|
+
@leftjoin_table.zip(@leftjoin_alias,@leftjoin_condition).flatten +
|
306
|
+
conditions +
|
307
|
+
groupby_fields +
|
308
|
+
orderby_fields
|
309
|
+
).collect{|x| x.placeheld}.flatten
|
324
310
|
end
|
325
311
|
|
326
312
|
#This is useful for writing nested queries.
|
@@ -329,42 +315,66 @@ class Select
|
|
329
315
|
end
|
330
316
|
protected
|
331
317
|
|
318
|
+
attr_reader :field_names, :field_aliases, :table_names, :table_aliases,
|
319
|
+
:conditions, :groupby_fields, :orderby_fields, :leftjoin_alias,
|
320
|
+
:leftjoin_table, :leftjoin_condition
|
321
|
+
|
332
322
|
def tables_s
|
333
|
-
@
|
323
|
+
part1=@table_aliases.zip(@table_names).collect do |a,t|
|
324
|
+
if a
|
325
|
+
"#{t.to_sqlpart} as #{a.to_sqlpart}"
|
326
|
+
else
|
327
|
+
t.to_sqlpart
|
328
|
+
end
|
329
|
+
end.join("\n INNER JOIN ")
|
330
|
+
part2=@leftjoin_alias.zip(@leftjoin_table, @leftjoin_condition).collect do |a,t,c|
|
331
|
+
if a==nil
|
332
|
+
"\n LEFT JOIN #{t.to_sqlpart} ON #{c.to_sqlpart}"
|
333
|
+
else
|
334
|
+
"\n LEFT JOIN #{t.to_sqlpart} as #{a.to_sqlpart} ON #{c.to_sqlpart}"
|
335
|
+
end
|
336
|
+
end.join
|
337
|
+
part1+part2
|
334
338
|
end
|
335
339
|
|
336
340
|
def conditions_s
|
337
|
-
@conditions==[] ? nil : @conditions.collect{|x| x.to_sqlpart}.join(" AND ")
|
341
|
+
@conditions==[] ? nil : @conditions.collect{|x| x.to_sqlpart}.join("\n AND ")
|
338
342
|
end
|
339
343
|
|
340
344
|
def groupby_s
|
341
|
-
@
|
345
|
+
@groupby_fields==[] ? nil : @groupby_fields.collect{|x| x.to_sqlpart}.join(", ")
|
342
346
|
end
|
343
347
|
|
344
348
|
def orderby_s
|
345
|
-
@
|
349
|
+
@orderby_fields==[] ? nil : @orderby_fields.collect{|x| x.to_sqlpart}.join(", ")
|
346
350
|
end
|
347
351
|
|
348
352
|
def fields_s
|
349
|
-
allfields.collect
|
353
|
+
allfields.transpose.collect do |a,f|
|
354
|
+
if a
|
355
|
+
"#{f.to_sqlpart} as #{a.to_sqlpart}"
|
356
|
+
else
|
357
|
+
f.to_sqlpart
|
358
|
+
end
|
359
|
+
end.join(", ")
|
350
360
|
end
|
351
361
|
|
352
362
|
class << self
|
353
363
|
#Use this to define additional array attributes in descendant classes
|
354
364
|
#with all of the necessary addition, initialization and merging (<<)
|
355
|
-
#semantics. You
|
356
|
-
#
|
357
|
-
#this method once, as it redefines methods each time it is called</b>
|
365
|
+
#semantics. <b>You can only call this function once per class, as
|
366
|
+
#it redefines methods each time it is called.</b>
|
358
367
|
def newlists *lists
|
359
368
|
attr_accessor *lists
|
360
369
|
|
361
|
-
|
362
|
-
["@#{listname}=[]",
|
370
|
+
lines=lists.collect do |listname|
|
371
|
+
["@#{listname}=[]",
|
372
|
+
"@#{listname} += parts.#{listname}"]
|
363
373
|
end.transpose
|
364
374
|
class_eval <<-"end;"
|
365
|
-
def initialize
|
366
|
-
super
|
375
|
+
def initialize &block
|
367
376
|
#{lines[0].join("\n")}
|
377
|
+
super &block
|
368
378
|
end
|
369
379
|
def << (parts)
|
370
380
|
super
|
@@ -389,14 +399,8 @@ class SelectCreate < Select
|
|
389
399
|
#This may not be supported by all databases.
|
390
400
|
attr_accessor :temporary
|
391
401
|
|
392
|
-
def initialize(targettable,temporary=false)
|
393
|
-
super()
|
394
|
-
@targettable=targettable
|
395
|
-
@temporary=temporary
|
396
|
-
end
|
397
|
-
|
398
402
|
def to_s
|
399
|
-
"CREATE #{ @temporary ? 'TEMPORARY':''}
|
403
|
+
"CREATE #{ @temporary ? 'TEMPORARY ':''}TABLE #{@targettable.to_sqlpart} #{super}"
|
400
404
|
end
|
401
405
|
end
|
402
406
|
|
@@ -407,26 +411,21 @@ class SelectInsert < Select
|
|
407
411
|
#The name of the table to insert into.
|
408
412
|
attr_accessor :targettable
|
409
413
|
|
410
|
-
def initialize(targettable)
|
411
|
-
super()
|
412
|
-
@targettable=targettable
|
413
|
-
end
|
414
|
-
|
415
414
|
def to_s
|
416
|
-
"INSERT INTO #{@targettable.to_sqlpart} #{names_s} #{super}"
|
415
|
+
"INSERT INTO #{@targettable.to_sqlpart} (#{names_s}) #{super}"
|
417
416
|
end
|
418
417
|
|
419
418
|
protected
|
420
419
|
|
421
420
|
def names_s
|
422
|
-
intermediate=allfields.
|
421
|
+
intermediate=allfields[0].collect do |x|
|
423
422
|
if x.is_a? SQL_Field
|
424
423
|
x.field.to_sqlpart
|
425
424
|
else
|
426
425
|
x.to_sqlpart
|
427
426
|
end
|
428
427
|
end
|
429
|
-
|
428
|
+
intermediate.join(", ")
|
430
429
|
end
|
431
430
|
end
|
432
431
|
|
@@ -443,20 +442,28 @@ class Update < Select
|
|
443
442
|
end
|
444
443
|
|
445
444
|
def placeheld
|
446
|
-
(allfields
|
445
|
+
(@table_names+allfields[1]+conditions).collect{|x| x.placeheld}.flatten
|
446
|
+
end
|
447
|
+
|
448
|
+
# Set a field to contain a value. Note the order of the parameters matches
|
449
|
+
# the order used for the Select statement
|
450
|
+
def field expression, targetfield
|
451
|
+
super expression, targetfield
|
447
452
|
end
|
448
453
|
|
449
454
|
protected
|
450
455
|
|
451
456
|
def updatepart_s
|
452
|
-
allfields.collect
|
457
|
+
allfields.transpose.collect do |target,value|
|
458
|
+
target.to_sqlpart+"="+value.to_sqlpart
|
459
|
+
end.join(", ")
|
453
460
|
end
|
454
461
|
end
|
455
462
|
|
456
463
|
#Insert values directly into a table. This class does not support the
|
457
|
-
#
|
458
|
-
#symbols) to values. You can get the "slice" functionality by merging
|
459
|
-
#hashes into the statement using <tt>Hash
|
464
|
+
#ability to merge a Select object, rather it behaves like a hash of column
|
465
|
+
#names (as symbols) to values. You can get the "slice" functionality by merging
|
466
|
+
#hashes into the statement using <tt>Hash#merge!</tt>.
|
460
467
|
class Insert < Hash
|
461
468
|
def initialize table
|
462
469
|
@targettable=table
|
@@ -482,13 +489,16 @@ end
|
|
482
489
|
|
483
490
|
end
|
484
491
|
|
485
|
-
|
492
|
+
class String
|
493
|
+
#Returns a SQLStatement::Identifier for this string
|
494
|
+
def dbid
|
495
|
+
SQLStatement::Identifier.new(self)
|
496
|
+
end
|
497
|
+
end
|
486
498
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
end
|
493
|
-
extend self
|
499
|
+
class Symbol
|
500
|
+
#Returns a SQLStatement::Identifier for this Symbol
|
501
|
+
def dbid
|
502
|
+
SQLStatement::Identifier.new(to_s)
|
503
|
+
end
|
494
504
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require '../lib/sql/statement.rb'
|
4
|
+
|
5
|
+
class TestStatementParts < Test::Unit::TestCase
|
6
|
+
include SQLHelpers
|
7
|
+
|
8
|
+
def test_shiftop
|
9
|
+
part=SQLStatement::Select.new do |s|
|
10
|
+
s.table :foo
|
11
|
+
s.leftjoin :faz, string_func("a=b")
|
12
|
+
s.field :bar
|
13
|
+
s.field :baz
|
14
|
+
end
|
15
|
+
targetstmt=SQLStatement::Select.new do |s|
|
16
|
+
s.table :foo
|
17
|
+
s.leftjoin :faz, string_func("a=b")
|
18
|
+
s.field :bar
|
19
|
+
s.field :baz
|
20
|
+
end
|
21
|
+
|
22
|
+
targetstmt << part
|
23
|
+
|
24
|
+
assert_equal "`foo`\n LEFT JOIN `faz` ON a=b", targetstmt.send(:tables_s)
|
25
|
+
assert_equal "`bar`, `baz`, `bar`, `baz`", targetstmt.send(:fields_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_plus
|
29
|
+
part=SQLStatement::Select.new do |s|
|
30
|
+
s.table :foo
|
31
|
+
s.leftjoin :faz, string_func("a=b")
|
32
|
+
s.field :bar
|
33
|
+
s.field :baz
|
34
|
+
end
|
35
|
+
part2=SQLStatement::Select.new do |s|
|
36
|
+
s.table :foo
|
37
|
+
s.leftjoin :faz, string_func("a=b")
|
38
|
+
s.field :bar
|
39
|
+
s.field :baz
|
40
|
+
end
|
41
|
+
|
42
|
+
targetstmt=part+part2
|
43
|
+
|
44
|
+
#neither of the original parts is modified
|
45
|
+
assert_equal "`bar`, `baz`", part.send(:fields_s)
|
46
|
+
assert_equal "`bar`, `baz`", part2.send(:fields_s)
|
47
|
+
|
48
|
+
#but the new one conforms to the semantics of <<
|
49
|
+
assert_equal "`foo`\n LEFT JOIN `faz` ON a=b", targetstmt.send(:tables_s)
|
50
|
+
assert_equal "`bar`, `baz`, `bar`, `baz`", targetstmt.send(:fields_s)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require '../lib/sql/statement.rb'
|
3
|
+
|
4
|
+
class Primatives < Test::Unit::TestCase
|
5
|
+
def test_construction
|
6
|
+
foo=SQLStatement::Identifier.new("foo")
|
7
|
+
bar=SQLStatement::Identifier["bar"]
|
8
|
+
assert_kind_of SQLStatement::Identifier, foo
|
9
|
+
assert_kind_of SQLStatement::Identifier, bar
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_dbid
|
13
|
+
assert_kind_of SQLStatement::Identifier, :foo.dbid
|
14
|
+
assert_kind_of SQLStatement::Identifier, "foo".dbid
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_indexing
|
18
|
+
foo=SQLStatement::Identifier.new("foo")
|
19
|
+
bar=SQLStatement::Identifier["bar"]
|
20
|
+
assert_kind_of SQLStatement::SQL_Field, foo[bar]
|
21
|
+
assert_kind_of SQLStatement::SQL_Field, foo[:bar]
|
22
|
+
if RUBY_VERSION=~/^1\.8/
|
23
|
+
assert_kind_of Numeric, foo[1]
|
24
|
+
elsif RUBY_VERSION=~/^1\.9/
|
25
|
+
#I don't guarantee that this library can work with Ruby 1.9 to begin
|
26
|
+
#with, but just for good measure, let's get this test right.
|
27
|
+
assert_kind_of String, foo[1]
|
28
|
+
end
|
29
|
+
assert_kind_of String, foo[1,1]
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require '../lib/sql/statement.rb'
|
4
|
+
|
5
|
+
class TestStatementParts < Test::Unit::TestCase
|
6
|
+
include SQLHelpers
|
7
|
+
|
8
|
+
def test_table
|
9
|
+
s=SQLStatement::Select.new
|
10
|
+
s.table :foo
|
11
|
+
assert_equal "`foo`", s.send(:tables_s)
|
12
|
+
s.table :bar
|
13
|
+
assert_equal "`foo`\n INNER JOIN `bar`", s.send(:tables_s)
|
14
|
+
|
15
|
+
#adding an existing table/alias pair doesn't add anything to the statement
|
16
|
+
s.table :foo
|
17
|
+
assert_equal "`foo`\n INNER JOIN `bar`", s.send(:tables_s)
|
18
|
+
|
19
|
+
#but adding the same table with a new alias does
|
20
|
+
s.table :foo, :alias
|
21
|
+
assert_equal "`foo`\n INNER JOIN `bar`\n INNER JOIN `foo` as `alias`", s.send(:tables_s)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_leftjoin
|
25
|
+
s=SQLStatement::Select.new
|
26
|
+
s.table :foo
|
27
|
+
assert_equal "`foo`", s.send(:tables_s)
|
28
|
+
s.leftjoin :bar, string_func("`foo`.`a`=`bar`.`a`")
|
29
|
+
assert_equal "`foo`\n LEFT JOIN `bar` ON `foo`.`a`=`bar`.`a`", s.send(:tables_s)
|
30
|
+
|
31
|
+
#adding the same table/alias pair adds nothing, and doesn't update the join condition
|
32
|
+
s.leftjoin :bar, string_func("`foo`.`b`=`bar`.`b`")
|
33
|
+
assert_equal "`foo`\n LEFT JOIN `bar` ON `foo`.`a`=`bar`.`a`", s.send(:tables_s)
|
34
|
+
|
35
|
+
#but adding the same table with a new alias adds a new join with the new condition
|
36
|
+
s.leftjoin :bar, string_func("`foo`.`b`=`baz`.`b`"), :baz
|
37
|
+
assert_equal "`foo`\n LEFT JOIN `bar` ON `foo`.`a`=`bar`.`a`
|
38
|
+
LEFT JOIN `bar` as `baz` ON `foo`.`b`=`baz`.`b`", s.send(:tables_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_fields
|
42
|
+
s=SQLStatement::Select.new
|
43
|
+
s.field :foo
|
44
|
+
assert_equal "`foo`", s.send(:fields_s)
|
45
|
+
|
46
|
+
#adding a field/alias pair does add it to the statement a second time
|
47
|
+
s.field :foo
|
48
|
+
assert_equal "`foo`, `foo`", s.send(:fields_s)
|
49
|
+
|
50
|
+
s.field :foo, :bar
|
51
|
+
assert_equal "`foo`, `foo`, `foo` as `bar`", s.send(:fields_s)
|
52
|
+
|
53
|
+
s.field string_func("1")
|
54
|
+
assert_equal "`foo`, `foo`, `foo` as `bar`, 1", s.send(:fields_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_conditions
|
58
|
+
s=SQLStatement::Select.new
|
59
|
+
assert_nil s.send(:conditions_s)
|
60
|
+
s.condition string_func("a=b")
|
61
|
+
assert_equal "a=b",s.send(:conditions_s)
|
62
|
+
s.condition string_func("b=c")
|
63
|
+
assert_equal "a=b\n AND b=c",s.send(:conditions_s)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_orderby
|
67
|
+
s=SQLStatement::Select.new
|
68
|
+
assert_nil s.send(:orderby_s)
|
69
|
+
s.orderby :foo
|
70
|
+
assert_equal "`foo`", s.send(:orderby_s)
|
71
|
+
s.orderby :bar
|
72
|
+
assert_equal "`foo`, `bar`", s.send(:orderby_s)
|
73
|
+
s.orderby string_func("`baz` desc")
|
74
|
+
assert_equal "`foo`, `bar`, `baz` desc", s.send(:orderby_s)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_groupby
|
78
|
+
s=SQLStatement::Select.new
|
79
|
+
assert_nil s.send(:groupby_s)
|
80
|
+
s.groupby :foo
|
81
|
+
assert_equal "`foo`", s.send(:groupby_s)
|
82
|
+
s.groupby :bar
|
83
|
+
assert_equal "`foo`, `bar`", s.send(:groupby_s)
|
84
|
+
end
|
85
|
+
|
86
|
+
# the names of the fields we are inserting into
|
87
|
+
def test_selectinsert_names
|
88
|
+
s=SQLStatement::SelectInsert.new
|
89
|
+
s.field :foo, :bar
|
90
|
+
assert_equal "`bar`",s.send(:names_s)
|
91
|
+
s.field :foo2, :baz
|
92
|
+
assert_equal "`bar`, `baz`",s.send(:names_s)
|
93
|
+
end
|
94
|
+
|
95
|
+
#the same tests as for Select, but for SelectInsert we are specifically
|
96
|
+
#specifying that the aliases show up in both as destination fields and as
|
97
|
+
#aliases in the select part
|
98
|
+
def test_selectinsert_fields
|
99
|
+
s=SQLStatement::SelectInsert.new
|
100
|
+
s.field :foo
|
101
|
+
assert_equal "`foo`", s.send(:fields_s)
|
102
|
+
|
103
|
+
#adding a field/alias pair does add it to the statement a second time
|
104
|
+
s.field :foo
|
105
|
+
assert_equal "`foo`, `foo`", s.send(:fields_s)
|
106
|
+
|
107
|
+
s.field :foo, :bar
|
108
|
+
assert_equal "`foo`, `foo`, `foo` as `bar`", s.send(:fields_s)
|
109
|
+
|
110
|
+
s.field string_func("1")
|
111
|
+
assert_equal "`foo`, `foo`, `foo` as `bar`, 1", s.send(:fields_s)
|
112
|
+
end
|
113
|
+
def test_updatepart
|
114
|
+
s=SQLStatement::Update.new
|
115
|
+
assert_raise ArgumentError do
|
116
|
+
#an alias is required, because it's going to be the name of the field to update
|
117
|
+
s.field :foo
|
118
|
+
end
|
119
|
+
|
120
|
+
s.field :foo, :bar
|
121
|
+
assert_equal "`bar`=`foo`", s.send(:updatepart_s)
|
122
|
+
|
123
|
+
s.field string_func("1"), :baz
|
124
|
+
assert_equal "`bar`=`foo`, `baz`=1", s.send(:updatepart_s)
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require '../lib/sql/statement.rb'
|
4
|
+
|
5
|
+
class TestStatements < Test::Unit::TestCase
|
6
|
+
include SQLHelpers
|
7
|
+
|
8
|
+
|
9
|
+
def test_createselect
|
10
|
+
parts=SQLStatement::SelectCreate.new do |s|
|
11
|
+
s.targettable = :targettable
|
12
|
+
s.field :a[:b],:targetcol
|
13
|
+
s.table :extracted_appraisal_expressions_26, :a
|
14
|
+
s.table :extracted_appraisal_26, :d
|
15
|
+
s.leftjoin :dictionary_appraisal_attitude, string_func("a.att_appraisal_attitude_left=b.left_key"), :b
|
16
|
+
s.leftjoin :dictionary_deixis_deixis, string_func("a.att_deixis_deixis_left=c.left_key"), :c
|
17
|
+
s.condition string_func("a>1")
|
18
|
+
end
|
19
|
+
expected="CREATE TABLE `targettable` SELECT
|
20
|
+
`a`.`b` as `targetcol`
|
21
|
+
FROM `extracted_appraisal_expressions_26` as `a`
|
22
|
+
INNER JOIN `extracted_appraisal_26` as `d`
|
23
|
+
LEFT JOIN `dictionary_appraisal_attitude` as `b` ON a.att_appraisal_attitude_left=b.left_key
|
24
|
+
LEFT JOIN `dictionary_deixis_deixis` as `c` ON a.att_deixis_deixis_left=c.left_key
|
25
|
+
WHERE a>1"
|
26
|
+
assert_equal expected, parts.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_insertselect
|
30
|
+
parts=SQLStatement::SelectInsert.new do |s|
|
31
|
+
s.targettable = :targettable
|
32
|
+
s.field :a[:b],:targetcol
|
33
|
+
s.table :extracted_appraisal_expressions_26, :a
|
34
|
+
s.table :extracted_appraisal_26, :d
|
35
|
+
s.leftjoin :dictionary_appraisal_attitude, string_func("a.att_appraisal_attitude_left=b.left_key"), :b
|
36
|
+
s.leftjoin :dictionary_deixis_deixis, string_func("a.att_deixis_deixis_left=c.left_key"), :c
|
37
|
+
s.condition string_func("a>1")
|
38
|
+
end
|
39
|
+
expected="INSERT INTO `targettable` (`targetcol`) SELECT
|
40
|
+
`a`.`b` as `targetcol`
|
41
|
+
FROM `extracted_appraisal_expressions_26` as `a`
|
42
|
+
INNER JOIN `extracted_appraisal_26` as `d`
|
43
|
+
LEFT JOIN `dictionary_appraisal_attitude` as `b` ON a.att_appraisal_attitude_left=b.left_key
|
44
|
+
LEFT JOIN `dictionary_deixis_deixis` as `c` ON a.att_deixis_deixis_left=c.left_key
|
45
|
+
WHERE a>1"
|
46
|
+
assert_equal expected, parts.to_s
|
47
|
+
end
|
48
|
+
def test_select
|
49
|
+
parts=SQLStatement::Select.new do |s|
|
50
|
+
s.straight_join=true
|
51
|
+
s.distinct=true
|
52
|
+
s.field :a[:*]
|
53
|
+
s.table :extracted_appraisal_expressions_26, :a
|
54
|
+
s.table :extracted_appraisal_26, :d
|
55
|
+
s.leftjoin :dictionary_appraisal_attitude, string_func("a.att_appraisal_attitude_left=b.left_key"), :b
|
56
|
+
s.leftjoin :dictionary_deixis_deixis, string_func("a.att_deixis_deixis_left=c.left_key"), :c
|
57
|
+
s.condition string_func("a>1")
|
58
|
+
end
|
59
|
+
expected="SELECT DISTINCT STRAIGHT_JOIN
|
60
|
+
`a`.*
|
61
|
+
FROM `extracted_appraisal_expressions_26` as `a`
|
62
|
+
INNER JOIN `extracted_appraisal_26` as `d`
|
63
|
+
LEFT JOIN `dictionary_appraisal_attitude` as `b` ON a.att_appraisal_attitude_left=b.left_key
|
64
|
+
LEFT JOIN `dictionary_deixis_deixis` as `c` ON a.att_deixis_deixis_left=c.left_key
|
65
|
+
WHERE a>1"
|
66
|
+
assert_equal expected, parts.to_s
|
67
|
+
end
|
68
|
+
end
|
metadata
CHANGED
@@ -1,63 +1,75 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.0
|
3
|
-
specification_version: 1
|
4
2
|
name: SqlStatement
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
7
|
-
date: 2006-12-18 00:00:00 -06:00
|
8
|
-
summary: A library for generating arbitrary SQL statements using convenient Ruby objects.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: kbloom@gmail.com
|
12
|
-
homepage: http://www.rubyforge.org/sqlstatement/
|
13
|
-
rubyforge_project:
|
14
|
-
description:
|
15
|
-
autorequire: sqlstatement
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: "1.8"
|
24
|
-
version:
|
4
|
+
version: "2.0"
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Ken Bloom
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-01-20 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubynode
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
description:
|
25
|
+
email: kbloom@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- doc/ChangeLog
|
31
32
|
files:
|
32
|
-
- lib/sql
|
33
33
|
- lib/sqlstatement.rb
|
34
|
-
- lib/sql
|
34
|
+
- lib/sql
|
35
|
+
- lib/sql/dbi-support.rb
|
35
36
|
- lib/sql/bathon-sxp.rb
|
37
|
+
- lib/sql/expression.rb
|
36
38
|
- lib/sql/statement.rb
|
37
|
-
-
|
38
|
-
-
|
39
|
+
- test/test_combine_statement.rb
|
40
|
+
- test/test_statements.rb
|
41
|
+
- test/test_statementparts.rb
|
42
|
+
- test/test_primatives.rb
|
39
43
|
- doc/ChangeLog
|
40
|
-
|
41
|
-
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://www.rubyforge.org/sqlstatement/
|
46
|
+
post_install_message:
|
42
47
|
rdoc_options:
|
43
48
|
- --main
|
44
49
|
- lib/sqlstatement.rb
|
45
|
-
|
46
|
-
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "1.8"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
52
64
|
requirements: []
|
53
65
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.0.1
|
68
|
+
signing_key:
|
69
|
+
specification_version: 2
|
70
|
+
summary: A library for generating arbitrary SQL statements using convenient Ruby objects.
|
71
|
+
test_files:
|
72
|
+
- test/test_combine_statement.rb
|
73
|
+
- test/test_statements.rb
|
74
|
+
- test/test_statementparts.rb
|
75
|
+
- test/test_primatives.rb
|
data/doc/EXAMPLE
DELETED
@@ -1,124 +0,0 @@
|
|
1
|
-
#=Example
|
2
|
-
# # We subclass the standard classes so that we can construct
|
3
|
-
# # a query with a complex computed field. The newlists method
|
4
|
-
# # makes this easy, giving us a constructor and combination
|
5
|
-
# # operators for free.
|
6
|
-
#
|
7
|
-
# class ProbabilityParts < SQLStatement::SelectParts
|
8
|
-
# newlists :multiply
|
9
|
-
# end
|
10
|
-
# class ProbabilityStatement < SQLStatement::SelectCreate
|
11
|
-
# newlists :multiply
|
12
|
-
#
|
13
|
-
# # Add our own special fields here, computed
|
14
|
-
# # from the list we added to this class.
|
15
|
-
# def allfields
|
16
|
-
# retval=super
|
17
|
-
# retval.merge! :probability =>
|
18
|
-
# SQLFunc(@multiply.collect{|x| x.to_sqlpart}.join('*'))
|
19
|
-
# retval
|
20
|
-
# end
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# #In real life, I use a special library for detecting what attributes
|
25
|
-
# #are available based on the columns in my tables.
|
26
|
-
# #We'll mimic that functionality here.
|
27
|
-
# #
|
28
|
-
# #The first definition of this module mocks the stuff that's in my more
|
29
|
-
# #complicated library
|
30
|
-
# module AttributeDetection
|
31
|
-
# class Attribute
|
32
|
-
# def initialize(domain,name)
|
33
|
-
# @domain=domain
|
34
|
-
# @name=name
|
35
|
-
# end
|
36
|
-
# attr_reader :name, :domain
|
37
|
-
# end
|
38
|
-
# class Hierarchy < Attribute
|
39
|
-
# def initialize(leftcolumn)
|
40
|
-
# ignored1,m_domain,m_name,ignored2=leftcolumn.split(/_/)
|
41
|
-
# super(m_domain,m_name)
|
42
|
-
# end
|
43
|
-
# end
|
44
|
-
# class DomainlessPerGroup < Attribute
|
45
|
-
# def initialize (colname)
|
46
|
-
# name=@colname=colname
|
47
|
-
# domain="!"
|
48
|
-
# super(domain,name)
|
49
|
-
# end
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# #I have a few other attribute types, none of which
|
53
|
-
# #are used in this code yet.
|
54
|
-
#
|
55
|
-
# end
|
56
|
-
#
|
57
|
-
# include SQLHelpers
|
58
|
-
#
|
59
|
-
# #The second definition of this module is the code that I actually define
|
60
|
-
# #in this particular program.
|
61
|
-
# module AttributeDetection
|
62
|
-
# class Attribute
|
63
|
-
# def condname
|
64
|
-
# :"cond_#{name}"
|
65
|
-
# end
|
66
|
-
# def priorname
|
67
|
-
# :"prior_#{name}"
|
68
|
-
# end
|
69
|
-
# #parts that go in to the CREATE TABLE `probabilities` statement
|
70
|
-
# #which relate to the attribute we are disambiguating
|
71
|
-
# def probability_disambig
|
72
|
-
# parts=ProbabilityParts.new
|
73
|
-
# parts.add_fields [priorname[probkey]]
|
74
|
-
# parts.add_tables [priorname]
|
75
|
-
# parts.multiply << priorname[:pt]
|
76
|
-
# parts
|
77
|
-
# end
|
78
|
-
# #parts that go in to the CREATE TABLE `probabilities` statement
|
79
|
-
# #which relate to the attributes we are using as features
|
80
|
-
# def probability_features(conditionalon)
|
81
|
-
# parts=ProbabilityParts.new
|
82
|
-
# parts.tables={priorname=>priorname,condname=>condname}
|
83
|
-
# parts.add_fields [priorname[probkey]]
|
84
|
-
# parts.conditions << sql_func{ condname[probkey]==priorname[probkey]}
|
85
|
-
# parts.conditions << sql_func{
|
86
|
-
# condname[conditionalon.probkey]==
|
87
|
-
# conditionalon.priorname[conditionalon.probkey]
|
88
|
-
# }
|
89
|
-
# parts.multiply << sql_func{condname[:pt]/priorname[:pt]}
|
90
|
-
# parts
|
91
|
-
# end
|
92
|
-
# end
|
93
|
-
# class Hierarchy
|
94
|
-
# def probkey
|
95
|
-
# "att_#{domain}_#{name}_left".to_sym
|
96
|
-
# end
|
97
|
-
# end
|
98
|
-
# class DomainlessPerGroup
|
99
|
-
# def probkey
|
100
|
-
# @colname.to_sym
|
101
|
-
# end
|
102
|
-
# end
|
103
|
-
# end
|
104
|
-
#
|
105
|
-
# include AttributeDetection
|
106
|
-
# include SQLHelpers
|
107
|
-
#
|
108
|
-
# #And this code calls methods to actually build the query.
|
109
|
-
#
|
110
|
-
# #First we define the names of the attributes
|
111
|
-
# att_disambig=Hierarchy.new("att_appraisal_attitude_left")
|
112
|
-
# att_features=[Hierarchy.new("att_products_appraisedtype_left"),
|
113
|
-
# DomainlessPerGroup.new("priority")]
|
114
|
-
#
|
115
|
-
#
|
116
|
-
# #Then we construct the statement
|
117
|
-
# stmt=ProbabilityStatement.new :probabilities,true
|
118
|
-
#
|
119
|
-
# stmt << att_disambig.probability_disambig
|
120
|
-
# att_features.each { |x| stmt<< x.probability_features(att_disambig) }
|
121
|
-
#
|
122
|
-
# #Finally, we execute it against the database. Assume dbh is a
|
123
|
-
# #dbi connection handle.
|
124
|
-
# dbh.execute(stmt.to_s,*stmt.placeheld)
|