SqlStatement 1.0.2 → 2.0
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.
- 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)
|