sql_munger 0.0.7
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/.bnsignore +16 -0
- data/.gitignore +3 -0
- data/History.txt +3 -0
- data/README.txt +57 -0
- data/Rakefile +27 -0
- data/examples/sql_izer.rb +92 -0
- data/lib/sql_munger.rb +49 -0
- data/lib/sql_munger/field_set.rb +355 -0
- data/lib/sql_munger/identifier_quoter.rb +12 -0
- data/lib/sql_munger/nodes.rb +290 -0
- data/lib/sql_munger/quoter.rb +42 -0
- data/lib/sql_munger/sql.tt +214 -0
- data/lib/sql_munger/sql_parser.rb +47 -0
- data/lib/sql_munger/table_name.rb +47 -0
- data/lib/sql_munger/value_quoter.rb +30 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/sql_munger_spec.rb +7 -0
- data/test/mock_field.rb +2 -0
- data/test/test_field_set.rb +462 -0
- data/test/test_sql_munger.rb +9 -0
- data/test/test_table_name.rb +26 -0
- metadata +101 -0
data/.bnsignore
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# PROJ.ignore_file = '.gitignore'
|
8
|
+
#
|
9
|
+
# For a project with a C extension, the following would be a good set of
|
10
|
+
# exclude patterns (uncomment them if you want to use them):
|
11
|
+
# *.[oa]
|
12
|
+
# *~
|
13
|
+
announcement.txt
|
14
|
+
coverage
|
15
|
+
doc
|
16
|
+
pkg
|
data/.gitignore
ADDED
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
by John Anderson http://www.semiosix.com
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
When you're good at SQL, sometimes you want to build SQL statement
|
6
|
+
without an ORM or a db api, or a connection to a database.
|
7
|
+
SqlMunger will simplify some of the fiddly parts of doing that.
|
8
|
+
|
9
|
+
SqlMunger will not give you a SQL equivalent algebra - Sequel and AREL do that very well.
|
10
|
+
|
11
|
+
See SqlIzer for an example.
|
12
|
+
|
13
|
+
== FEATURES/PROBLEMS:
|
14
|
+
|
15
|
+
SqlFieldSet lets you quote, escape, join and generate comparison operators
|
16
|
+
for a set of field names and a related set of values. See SqlMunger::FieldSet
|
17
|
+
|
18
|
+
SqlMunger::SqlParser knows how to parse a create table statement, giving a relatively
|
19
|
+
easy way to create migrations or other representations of a table definition.
|
20
|
+
|
21
|
+
|
22
|
+
== SYNOPSIS:
|
23
|
+
|
24
|
+
require 'sql_munger'
|
25
|
+
|
26
|
+
== REQUIREMENTS:
|
27
|
+
|
28
|
+
treetop
|
29
|
+
|
30
|
+
== INSTALL:
|
31
|
+
|
32
|
+
gem install sql_munger
|
33
|
+
|
34
|
+
== LICENSE:
|
35
|
+
|
36
|
+
(The MIT License)
|
37
|
+
|
38
|
+
Copyright (c) 2009,2011 John Anderson
|
39
|
+
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
41
|
+
a copy of this software and associated documentation files (the
|
42
|
+
'Software'), to deal in the Software without restriction, including
|
43
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
44
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
45
|
+
permit persons to whom the Software is furnished to do so, subject to
|
46
|
+
the following conditions:
|
47
|
+
|
48
|
+
The above copyright notice and this permission notice shall be
|
49
|
+
included in all copies or substantial portions of the Software.
|
50
|
+
|
51
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
52
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
53
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
54
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
55
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
56
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
57
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
begin
|
2
|
+
require 'bones'
|
3
|
+
rescue LoadError
|
4
|
+
abort '### Please install the "bones" gem ###'
|
5
|
+
end
|
6
|
+
|
7
|
+
task :default => 'test:run'
|
8
|
+
task 'gem:release' => 'test:run'
|
9
|
+
|
10
|
+
ensure_in_path 'lib'
|
11
|
+
require 'sql_munger'
|
12
|
+
|
13
|
+
Bones {
|
14
|
+
name 'sql_munger'
|
15
|
+
authors 'John Anderson'
|
16
|
+
email 'john@semiosix.com'
|
17
|
+
url 'http://www.semiosix.com'
|
18
|
+
|
19
|
+
version SqlMunger::VERSION
|
20
|
+
description "SQL manipulation without a db connection"
|
21
|
+
|
22
|
+
rdoc.include %w{README.txt ^lib/sql_munger/.*\.rb$ examples/sql_izer.rb History.txt}
|
23
|
+
|
24
|
+
gem.need_tar false
|
25
|
+
|
26
|
+
depend_on 'treetop', '~>1.4.8'
|
27
|
+
}
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'sql_munger/generation'
|
2
|
+
require 'mysql'
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
Example for how to use SqlMunger
|
6
|
+
|
7
|
+
:include:sql_izer.rb
|
8
|
+
=end
|
9
|
+
|
10
|
+
class SqlIzer
|
11
|
+
|
12
|
+
# If the SQL you want to generate uses something
|
13
|
+
# other than " for identifiers, and ' for string values
|
14
|
+
# you need to define quoting.
|
15
|
+
#
|
16
|
+
# Also, escaping of special characters inside value strings is quite
|
17
|
+
# unpleasant, so for now we depend on external libraries. Feel
|
18
|
+
# free to implement this and send a patch.
|
19
|
+
#
|
20
|
+
# Set this as the default quoter. Note that default_quoter wants an instance, not a class.
|
21
|
+
SqlMunger::FieldSet.default_quoter = Class.new do
|
22
|
+
include SqlMunger::ValueQuoter
|
23
|
+
include SqlMunger::IdentifierQuoter
|
24
|
+
def escape( st )
|
25
|
+
Mysql.escape_string( st )
|
26
|
+
end
|
27
|
+
end.new
|
28
|
+
|
29
|
+
# the name of the table. Note that the namespace is optional
|
30
|
+
def table
|
31
|
+
@table ||= SqlMunger::TableName.new( 'namespace.things' )
|
32
|
+
end
|
33
|
+
|
34
|
+
# this is the set of fields used in the where
|
35
|
+
# clause for update statements
|
36
|
+
def unique_key_fields
|
37
|
+
@unique_key_fields ||= SqlMunger::FieldSet.new( %w{name} )
|
38
|
+
end
|
39
|
+
|
40
|
+
def field_names
|
41
|
+
values.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
# sample values for the example
|
45
|
+
def values
|
46
|
+
@values ||= {
|
47
|
+
'name' => 'Mr Mark',
|
48
|
+
'address' => "'The Pub', Dublin",
|
49
|
+
'phone' => '(31)415328',
|
50
|
+
'flavour' => 'bitter',
|
51
|
+
'colour' => 'dark',
|
52
|
+
'spin' => 'widdershins',
|
53
|
+
'manyness' => 16.5
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def field_set
|
58
|
+
@field_set ||= SqlMunger::FieldSet.new( field_names )
|
59
|
+
end
|
60
|
+
|
61
|
+
# generate an insert statement
|
62
|
+
def insert_sql
|
63
|
+
<<-EOF
|
64
|
+
insert into #{table.quote}
|
65
|
+
( #{field_set.list} )
|
66
|
+
values
|
67
|
+
(
|
68
|
+
\t#{field_set.quoted_values( values ).join(",\n\t")}
|
69
|
+
);
|
70
|
+
EOF
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_sql
|
74
|
+
# We want to update the values, but not the field
|
75
|
+
# used in the where clause
|
76
|
+
fields = field_set - unique_key_fields
|
77
|
+
|
78
|
+
<<-EOF
|
79
|
+
update #{table.quote}
|
80
|
+
set
|
81
|
+
#{fields.update( values ) }
|
82
|
+
where
|
83
|
+
#{unique_key_fields.comparison( values )}
|
84
|
+
;
|
85
|
+
EOF
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
sqlizer = SqlIzer.new
|
91
|
+
puts sqlizer.insert_sql
|
92
|
+
puts sqlizer.update_sql
|
data/lib/sql_munger.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
module SqlMunger
|
3
|
+
|
4
|
+
# :stopdoc:
|
5
|
+
VERSION = '0.0.7'
|
6
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
7
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
8
|
+
# :startdoc:
|
9
|
+
|
10
|
+
# Returns the version string for the library.
|
11
|
+
#
|
12
|
+
def self.version
|
13
|
+
VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the library path for the module. If any arguments are given,
|
17
|
+
# they will be joined to the end of the libray path using
|
18
|
+
# <tt>File.join</tt>.
|
19
|
+
#
|
20
|
+
def self.libpath( *args )
|
21
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the lpath for the module. If any arguments are given,
|
25
|
+
# they will be joined to the end of the path using
|
26
|
+
# <tt>File.join</tt>.
|
27
|
+
#
|
28
|
+
def self.path( *args )
|
29
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Utility method used to require all files ending in .rb that lie in the
|
33
|
+
# directory below this file that has the same name as the filename passed
|
34
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
35
|
+
# the _filename_ does not have to be equivalent to the directory.
|
36
|
+
#
|
37
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
38
|
+
dir ||= ::File.basename(fname, '.*')
|
39
|
+
search_me = ::File.expand_path(
|
40
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
41
|
+
|
42
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
43
|
+
end
|
44
|
+
|
45
|
+
end # module SqlMunger
|
46
|
+
|
47
|
+
SqlMunger.require_all_libs_relative_to SqlMunger.libpath( 'sql_munger' )
|
48
|
+
|
49
|
+
# EOF
|
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'sql_munger/quoter.rb'
|
2
|
+
|
3
|
+
module SqlMunger
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
This is a class encapsulating a set of fields
|
7
|
+
and providing various methods for producing SQL strings
|
8
|
+
from them.
|
9
|
+
|
10
|
+
You can also call operators on fieldset (+ - | & ), or
|
11
|
+
with an array as the right-hand parameter.
|
12
|
+
|
13
|
+
Fields can be either plain strings, or objects responding
|
14
|
+
to name, sql_type, null, default. If they're plain strings, they're
|
15
|
+
converted to FieldSet::Field objects, which have a name attribute.
|
16
|
+
Mixing different classes in the same FieldSet won't work.
|
17
|
+
|
18
|
+
=end
|
19
|
+
|
20
|
+
class FieldSet
|
21
|
+
include Quoter
|
22
|
+
|
23
|
+
class Field
|
24
|
+
def initialize( name )
|
25
|
+
@name = name.to_sym
|
26
|
+
end
|
27
|
+
attr_accessor :name
|
28
|
+
|
29
|
+
def ==( other )
|
30
|
+
name == other.name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# collection_or_enum is a collection of field names.
|
35
|
+
#
|
36
|
+
# If block is supplied, collection_or_enum is a collection
|
37
|
+
# of objects that when map( &block ) is applied returns
|
38
|
+
# a collection of field names.
|
39
|
+
#
|
40
|
+
# options is intended to easily pass a :qualifier and a :quoter.
|
41
|
+
def initialize( collection_or_enum, options = {}, &block )
|
42
|
+
@fields =
|
43
|
+
if collection_or_enum.all?{|x| x.is_a?( String ) || x.is_a?( Symbol ) }
|
44
|
+
collection_or_enum.to_a.map{|x| Field.new(x.to_s)}
|
45
|
+
else
|
46
|
+
first = collection_or_enum.first
|
47
|
+
first.name rescue raise( "Can't use default method :name on #{first}" )
|
48
|
+
collection_or_enum.to_a.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
@field_name_block = block || lambda {|x| x.name}
|
52
|
+
|
53
|
+
# set options
|
54
|
+
options.each do |key,value|
|
55
|
+
send( "#{key}=", value )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# raise an exception with msg appended if any element of ary
|
60
|
+
# is not a String. Otherwise return true
|
61
|
+
def self.check_strings( ary, msg = nil )
|
62
|
+
ary.each do |elt|
|
63
|
+
unless elt.is_a?( String ) || elt.is_a?( Symbol )
|
64
|
+
raise [ "#{elt.inspect} is neither String nor Symbol", msg ].compact.join(' ')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create from either a Array of field names (NOTE *not* field objects)
|
71
|
+
# or from another FieldSet
|
72
|
+
def self.[]( fieldset_or_array )
|
73
|
+
case fieldset_or_array
|
74
|
+
when Array
|
75
|
+
check_strings( fieldset_or_array, "Use #{self.class.name}.new" )
|
76
|
+
FieldSet.new( fieldset_or_array )
|
77
|
+
|
78
|
+
when FieldSet
|
79
|
+
field_set = fieldset_or_array
|
80
|
+
field_set.dup
|
81
|
+
|
82
|
+
when NilClass
|
83
|
+
nil
|
84
|
+
|
85
|
+
else
|
86
|
+
raise "dunno what to do with #{fieldset_or_array.inspect}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# the field objects, which may be objects or Strings. See field_names
|
91
|
+
# if you definitely always want either Strings or Symbols
|
92
|
+
attr_reader :fields
|
93
|
+
|
94
|
+
# Set the fields. Assume that they're the same kind of
|
95
|
+
# fields as the previous set of fields
|
96
|
+
def fields=( other_fields )
|
97
|
+
@field_names = nil
|
98
|
+
@fields = other_fields
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return a new instance of FieldSet. The result
|
102
|
+
# of fields is passed to block, and the result of that
|
103
|
+
# is passed to FieldSet.new. Useful for doing set operations
|
104
|
+
# that aren't covered by + - & |
|
105
|
+
def reconstruct( &block )
|
106
|
+
FieldSet.new( block.call( fields ) )
|
107
|
+
end
|
108
|
+
|
109
|
+
# the names of the fields
|
110
|
+
def field_names
|
111
|
+
@field_names ||= sanity_check_fields
|
112
|
+
end
|
113
|
+
|
114
|
+
# enumerate through field_names
|
115
|
+
def each( &block )
|
116
|
+
field_names.each( &block )
|
117
|
+
end
|
118
|
+
include Enumerable
|
119
|
+
|
120
|
+
# return the field object for name, or nil if it doesn't exist
|
121
|
+
def find_field( name )
|
122
|
+
fields.find{|x| name == @field_name_block.call(x) }
|
123
|
+
end
|
124
|
+
|
125
|
+
# + - & | (append, difference, intersection union) same as array.
|
126
|
+
# other can be another FieldSet, or an Array
|
127
|
+
# Called from method_missing
|
128
|
+
def operate( operation, other )
|
129
|
+
case other
|
130
|
+
when FieldSet
|
131
|
+
# fetch new fields using the operated set of field_names
|
132
|
+
new_field_objects = field_names.send( operation, other.field_names ).map do |name|
|
133
|
+
# first try self's fields, then try other's fields
|
134
|
+
find_field( name ) || other.find_field( name )
|
135
|
+
end
|
136
|
+
|
137
|
+
# create the new FieldSet
|
138
|
+
FieldSet.new( new_field_objects, :quoter => quoter, :qualifier => qualifier )
|
139
|
+
|
140
|
+
when Array
|
141
|
+
case other.first
|
142
|
+
when String
|
143
|
+
operate( operation, FieldSet.new( other ) )
|
144
|
+
|
145
|
+
when Symbol
|
146
|
+
operate( operation, FieldSet.new( other ) )
|
147
|
+
|
148
|
+
else
|
149
|
+
# assume it's a field object
|
150
|
+
FieldSet.new( fields.send( operation, other ), :quoter => quoter, :qualifier => qualifier )
|
151
|
+
end
|
152
|
+
|
153
|
+
else
|
154
|
+
raise "don't know what to do with #{other.inspect}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# passes - + & | to operate, otherwise calls method_missing
|
159
|
+
def method_missing( meth, *args, &block )
|
160
|
+
@operations ||= %w{ - + & | }.map{|x| x.to_sym}
|
161
|
+
if @operations.include?( meth )
|
162
|
+
operate( meth, args.first )
|
163
|
+
else
|
164
|
+
super
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# whatever is in front of the fields, usually a table name
|
169
|
+
# It's stored as an array of parts, to be quoted and joined with
|
170
|
+
# '.' when generating SQL
|
171
|
+
attr_reader :qualifier
|
172
|
+
|
173
|
+
# can take a dotted string, an Array of strings, a TableName instance or nil
|
174
|
+
def qualifier=( other )
|
175
|
+
case other
|
176
|
+
when String
|
177
|
+
@qualifier = other.split( '.' )
|
178
|
+
|
179
|
+
when Array
|
180
|
+
@qualifier = other
|
181
|
+
|
182
|
+
when TableName
|
183
|
+
@qualifier = other.parts
|
184
|
+
|
185
|
+
when NilClass
|
186
|
+
@qualifier = nil
|
187
|
+
|
188
|
+
else
|
189
|
+
raise "Don't understand #{other.inspect}"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# return the set of field names, separated by ', '
|
194
|
+
def list( qualifier = nil )
|
195
|
+
field_names.map do |field|
|
196
|
+
qualify( qualifier, field )
|
197
|
+
end.join(', ')
|
198
|
+
end
|
199
|
+
|
200
|
+
# prepend the qualifier to each field name
|
201
|
+
def qualified_list
|
202
|
+
list( qualifier )
|
203
|
+
end
|
204
|
+
|
205
|
+
alias_method :qlist, :qualified_list
|
206
|
+
|
207
|
+
# one arg is a field, qualified by qualifier
|
208
|
+
# many args is qualifier[s], field
|
209
|
+
def qualify( *args )
|
210
|
+
case args.flatten.size
|
211
|
+
when 1
|
212
|
+
[ qualifier, args.first ]
|
213
|
+
|
214
|
+
else
|
215
|
+
args
|
216
|
+
|
217
|
+
end.flatten.compact.map{|x| identifier_quoter.quote_ident(x)}.join('.')
|
218
|
+
end
|
219
|
+
|
220
|
+
# return a hash of field names to their corresponding transformed values
|
221
|
+
# from hash_values. block will be used to transform each value.
|
222
|
+
def hash_values( hash_values, &block )
|
223
|
+
inject({}) do |hash,field|
|
224
|
+
hash[field] =
|
225
|
+
if block.nil?
|
226
|
+
hash_values[field]
|
227
|
+
else
|
228
|
+
block.call( hash_values[field] )
|
229
|
+
end
|
230
|
+
hash
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# return a comma-separated list of the quoted values for this
|
235
|
+
# fieldset
|
236
|
+
def quoted_values( hash_values )
|
237
|
+
map do |field|
|
238
|
+
value_quoter.quote( hash_values[field] )
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# extract the values for these fields from hash_values
|
243
|
+
# block will be applied to each value
|
244
|
+
def values( hash_values, &block )
|
245
|
+
map do |field|
|
246
|
+
if block.nil?
|
247
|
+
hash_values[field]
|
248
|
+
else
|
249
|
+
block.call( hash_values[field] )
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# return a string of the fields with some operator and value
|
255
|
+
# can be used for where clauses and update statements.
|
256
|
+
# arg is either a String, which is treated passed to TableName.new
|
257
|
+
# or a TableName, which qualifies the rhs
|
258
|
+
# or an Array (actually something with a zip method) which is used as a set of values
|
259
|
+
# or a Hash (or something with to_hash) from which the values corresponding
|
260
|
+
# to the fields in this object are extracted, and used to compare with
|
261
|
+
def express( arg, level = 0, options = { :operator => '=', :joiner => ',' } )
|
262
|
+
expressions =
|
263
|
+
case
|
264
|
+
when arg.is_a?( String )
|
265
|
+
express( TableName.new( arg ), level+1, options )
|
266
|
+
|
267
|
+
when arg.is_a?( TableName )
|
268
|
+
other_table = arg
|
269
|
+
field_names.map do |field|
|
270
|
+
"#{qualify( field )} #{options[:operator]} #{qualify( other_table, field )}"
|
271
|
+
end
|
272
|
+
|
273
|
+
when arg.is_a?( Hash )
|
274
|
+
field_names.map do |field|
|
275
|
+
"#{qualify( field )} #{options[:operator]} #{value_quoter.quote( arg[field] )}"
|
276
|
+
end
|
277
|
+
|
278
|
+
when arg.respond_to?( :to_hash )
|
279
|
+
express( arg.to_hash, level+1, options )
|
280
|
+
|
281
|
+
when arg.respond_to?( :zip )
|
282
|
+
raise "#{arg.inspect} doesn't match number of fields in #{field_names}" unless size == arg.size
|
283
|
+
field_names.zip( [ *arg ] ).map do |field,value|
|
284
|
+
"#{qualify( field )} #{options[:operator]} #{value_quoter.quote( value )}"
|
285
|
+
end
|
286
|
+
|
287
|
+
else
|
288
|
+
raise "Don't know what to do with #{arg.inspect}"
|
289
|
+
end
|
290
|
+
|
291
|
+
if level == 0
|
292
|
+
if options[:joiner].nil?
|
293
|
+
expressions
|
294
|
+
else
|
295
|
+
expressions.join( options[:joiner] )
|
296
|
+
end
|
297
|
+
else
|
298
|
+
expressions
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# return an SQL set of comparison expressions, joined with 'and' suitable for
|
303
|
+
# a where clause
|
304
|
+
def comparison( arg, operator = '=' )
|
305
|
+
if arg.is_a?( String ) && qualifier.nil?
|
306
|
+
raise "no local qualifier"
|
307
|
+
end
|
308
|
+
|
309
|
+
st = express( arg, 0, :operator => operator, :joiner => ' and ' )
|
310
|
+
"( #{st} )"
|
311
|
+
end
|
312
|
+
|
313
|
+
# return something suitable for an update statement
|
314
|
+
def update( arg )
|
315
|
+
save_qualifier = qualifier
|
316
|
+
begin
|
317
|
+
self.qualifier = nil
|
318
|
+
express( arg, 0, :operator => '=', :joiner => ', ' )
|
319
|
+
ensure
|
320
|
+
self.qualifier = save_qualifier
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# probably only works with column definitions from ActiveRecord
|
325
|
+
def definitions( options = {:joiner => ', '} )
|
326
|
+
fields.map do |field|
|
327
|
+
raise "sql_type has no value" if field.sql_type.nil? || field.sql_type == ''
|
328
|
+
"#{field.name} #{field.sql_type}"
|
329
|
+
end.join( options[:joiner] )
|
330
|
+
end
|
331
|
+
|
332
|
+
def size
|
333
|
+
fields.size
|
334
|
+
end
|
335
|
+
|
336
|
+
def empty?
|
337
|
+
fields.empty?
|
338
|
+
end
|
339
|
+
|
340
|
+
def ==( other )
|
341
|
+
return false unless other.is_a?( self.class )
|
342
|
+
qualifier == other.qualifier and
|
343
|
+
fields == other.fields and
|
344
|
+
@field_name_block == other.instance_variable_get( '@field_name_block' )
|
345
|
+
end
|
346
|
+
|
347
|
+
protected
|
348
|
+
|
349
|
+
# generate field names from field objects
|
350
|
+
def sanity_check_fields
|
351
|
+
fields.map( &@field_name_block )
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
end # module SqlMunger
|