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
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require 'sql_munger/nodes.rb'
|
3
|
+
|
4
|
+
Treetop.load "#{File.dirname(__FILE__)}/sql.tt"
|
5
|
+
|
6
|
+
class SqlParser
|
7
|
+
unless instance_methods.include? 'case_sensitive_parse'
|
8
|
+
alias_method :case_sensitive_parse, :parse
|
9
|
+
end
|
10
|
+
|
11
|
+
# Allow for case-insensitive parsing. Convert
|
12
|
+
# to downcase, then parse, then replace the string
|
13
|
+
# with the original version. The numeric indices
|
14
|
+
# will still point to the right places.
|
15
|
+
def parse( st, *args )
|
16
|
+
ds = st.downcase
|
17
|
+
begin
|
18
|
+
root = case_sensitive_parse( ds, *args )
|
19
|
+
ensure
|
20
|
+
ds.replace st
|
21
|
+
end
|
22
|
+
|
23
|
+
# raise exception on failure
|
24
|
+
# or return the root of the parse tree on success
|
25
|
+
if root.nil?
|
26
|
+
raise "SQL error at #{failure_line}:#{failure_column}. #{failure_reason}"
|
27
|
+
else
|
28
|
+
root
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def dump( sql )
|
33
|
+
root = parse( sql )
|
34
|
+
root.statements.map do |table|
|
35
|
+
if table.respond_to? :name
|
36
|
+
puts table.name
|
37
|
+
columns = %w{name sql_type length precision scale}
|
38
|
+
table.fields.each do |field|
|
39
|
+
puts " " + columns.map{|x| field.send(x) }.join(', ')
|
40
|
+
end
|
41
|
+
else
|
42
|
+
puts table.text_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
puts
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'sql_munger/sql_parser.rb'
|
2
|
+
require 'sql_munger/quoter.rb'
|
3
|
+
|
4
|
+
module SqlMunger
|
5
|
+
|
6
|
+
# Essentially a qualified table name that's parsed
|
7
|
+
# and makes the parts accessible.
|
8
|
+
# TODO reduce dependency on Treetop and parser.
|
9
|
+
class TableName
|
10
|
+
include Quoter
|
11
|
+
|
12
|
+
def initialize( qualified_identifier, quoter = nil )
|
13
|
+
@quoter = quoter
|
14
|
+
@qualified_identifier = qualified_identifier
|
15
|
+
@tree = self.class.sql_parser.parse( @qualified_identifier )
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :tree
|
19
|
+
|
20
|
+
def self.sql_parser
|
21
|
+
if @sql_parser.nil?
|
22
|
+
@sql_parser = SqlParser.new
|
23
|
+
@sql_parser.root = :qualified_identifier
|
24
|
+
end
|
25
|
+
@sql_parser
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
@tree.parts.last
|
30
|
+
end
|
31
|
+
|
32
|
+
def parts
|
33
|
+
@tree.parts
|
34
|
+
end
|
35
|
+
|
36
|
+
def quote
|
37
|
+
parts.map do |part|
|
38
|
+
quoter.quote_ident( part )
|
39
|
+
end.join('.')
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@tree.parts.join( '.' )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module SqlMunger
|
4
|
+
|
5
|
+
# Quote a value in default SQL standard style. Probably
|
6
|
+
# won't work for many backends, so the quote
|
7
|
+
# method from, for example, DBI should be used.
|
8
|
+
module ValueQuoter
|
9
|
+
def quote( value )
|
10
|
+
case value
|
11
|
+
when NilClass
|
12
|
+
"null"
|
13
|
+
|
14
|
+
when Numeric
|
15
|
+
value
|
16
|
+
|
17
|
+
when DateTime
|
18
|
+
# 2004-10-19 10:23:54
|
19
|
+
"'#{value.strftime "%Y-%m-%d %H:%M:%S"}'"
|
20
|
+
|
21
|
+
when Date, Time
|
22
|
+
"'#{value}'"
|
23
|
+
|
24
|
+
else
|
25
|
+
"'#{escape( value )}'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(
|
3
|
+
File.join(File.dirname(__FILE__), %w[.. lib sql_munger]))
|
4
|
+
|
5
|
+
Spec::Runner.configure do |config|
|
6
|
+
# == Mock Framework
|
7
|
+
#
|
8
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
9
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
10
|
+
#
|
11
|
+
# config.mock_with :mocha
|
12
|
+
# config.mock_with :flexmock
|
13
|
+
# config.mock_with :rr
|
14
|
+
end
|
15
|
+
|
16
|
+
# EOF
|
data/test/mock_field.rb
ADDED
@@ -0,0 +1,462 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_sql_munger.rb'
|
2
|
+
require File.dirname(__FILE__) + '/mock_field.rb'
|
3
|
+
|
4
|
+
class TestFieldSet < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@fields = [
|
7
|
+
MockField.new( :id, 'int', false, 'nextval'),
|
8
|
+
MockField.new( :name, 'varchar(20)' ),
|
9
|
+
MockField.new( :key, 'varchar(50)' ),
|
10
|
+
MockField.new( :value, 'varchar(200)' ),
|
11
|
+
]
|
12
|
+
|
13
|
+
@hash_values = {
|
14
|
+
:key => 'cut',
|
15
|
+
:value => 15,
|
16
|
+
:name => 'Egoyan',
|
17
|
+
:id => '700822',
|
18
|
+
:extra => 'movie',
|
19
|
+
}
|
20
|
+
|
21
|
+
@name_block = lambda{|x| x.name}
|
22
|
+
@field_name_objects = @fields.map{|x| FieldSet::Field.new( @name_block.call(x) ) }
|
23
|
+
@field_names = @fields.map( &@name_block )
|
24
|
+
|
25
|
+
@values = @field_names.map{|x| @hash_values[x] }
|
26
|
+
|
27
|
+
@quoted_values = @values.dup.map do |value|
|
28
|
+
case value
|
29
|
+
when Numeric
|
30
|
+
value
|
31
|
+
else
|
32
|
+
%Q{'#{value}'}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@fs = FieldSet.new( @fields, &@name_block )
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.should_have_empty_names
|
40
|
+
should "have no field_names" do
|
41
|
+
assert_nil @fs.instance_variable_get( '@field_names' )
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.should_build_field_names
|
46
|
+
should_have_empty_names
|
47
|
+
|
48
|
+
should "have a field transform" do
|
49
|
+
block = @fs.instance_variable_get( '@field_name_block' )
|
50
|
+
assert_kind_of Proc, block
|
51
|
+
|
52
|
+
# check that both blocks return the name from a field object
|
53
|
+
the_name = Struct.new( :name ).new( 'my name' )
|
54
|
+
assert_equal @name_block.call( the_name ), block.call( the_name )
|
55
|
+
end
|
56
|
+
|
57
|
+
should "create field names" do
|
58
|
+
names = @fs.send :sanity_check_fields
|
59
|
+
assert_equal @field_names, names
|
60
|
+
end
|
61
|
+
|
62
|
+
should "return field names" do
|
63
|
+
assert_equal @field_names, @fs.field_names
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.should_fields_as_names
|
68
|
+
should "have the same fields" do
|
69
|
+
assert_equal @field_name_objects, @fs.fields
|
70
|
+
assert_equal @field_names, @fs.field_names
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.should_have_field_objects
|
75
|
+
should "have field objects" do
|
76
|
+
@fs.fields.each { |e| assert_instance_of MockField, e }
|
77
|
+
assert_equal @fields, @fs.fields
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
should "have symbols for field names" do
|
82
|
+
assert @field_names.all?{|x| x.is_a? Symbol}, "#{@field_names} are not all symbols"
|
83
|
+
end
|
84
|
+
|
85
|
+
context "Field" do
|
86
|
+
setup do
|
87
|
+
@name = 'some_name'
|
88
|
+
@field = FieldSet::Field.new( @name )
|
89
|
+
@other_field = FieldSet::Field.new( @name )
|
90
|
+
end
|
91
|
+
|
92
|
+
should "be equal" do
|
93
|
+
assert_equal @name.to_sym, @field.name
|
94
|
+
assert_equal @name.to_sym, @other_field.name
|
95
|
+
assert_equal @field, @other_field
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "Construct" do
|
100
|
+
context "FieldSet.new( Array<Field> ) {|x| x.name}" do
|
101
|
+
setup do
|
102
|
+
@fs = FieldSet.new( @fields, &@name_block )
|
103
|
+
end
|
104
|
+
|
105
|
+
should_build_field_names
|
106
|
+
should_have_field_objects
|
107
|
+
end
|
108
|
+
|
109
|
+
context "FieldSet.new( Array<Field> ) with no block" do
|
110
|
+
setup do
|
111
|
+
@fs = FieldSet.new( @fields )
|
112
|
+
end
|
113
|
+
|
114
|
+
should_build_field_names
|
115
|
+
should_have_field_objects
|
116
|
+
end
|
117
|
+
|
118
|
+
context "FieldSet.new( Array<String> )" do
|
119
|
+
setup do
|
120
|
+
@fs = FieldSet.new @field_names
|
121
|
+
end
|
122
|
+
|
123
|
+
should_fields_as_names
|
124
|
+
end
|
125
|
+
|
126
|
+
context "FieldSet.new( Array<Symbol> )" do
|
127
|
+
setup do
|
128
|
+
@fs = FieldSet.new @field_names.map( &:to_sym )
|
129
|
+
end
|
130
|
+
|
131
|
+
should_fields_as_names
|
132
|
+
end
|
133
|
+
|
134
|
+
context "name block" do
|
135
|
+
setup do
|
136
|
+
@blah_fs = FieldSet.new( @field_names ) {|x| x.blah_name}
|
137
|
+
@fs = FieldSet.new( @field_names ) {|x| x.name}
|
138
|
+
end
|
139
|
+
|
140
|
+
should "fail" do
|
141
|
+
assert_raise( NoMethodError ) { @blah_fs.field_names }
|
142
|
+
end
|
143
|
+
|
144
|
+
should "succeed" do
|
145
|
+
assert_nothing_raised( Exception ) { @fs.field_names }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "FieldSet[Array<FieldObject>]" do
|
150
|
+
setup do
|
151
|
+
assert_raise RuntimeError { @fs = FieldSet[@fields] }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "FieldSet[Array<String>]" do
|
156
|
+
setup do
|
157
|
+
@fs = FieldSet[@field_names]
|
158
|
+
end
|
159
|
+
|
160
|
+
should_fields_as_names
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "reconstruct" do
|
165
|
+
setup do
|
166
|
+
@recon = @fs.reconstruct do |fields|
|
167
|
+
fields.select{|x| [:key, :value].include?( x.name )}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
should "have the correct fields" do
|
172
|
+
assert_equal [:key, :value], @recon.field_names
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "assign fields" do
|
177
|
+
setup do
|
178
|
+
# create new field objects
|
179
|
+
@new_field_names = %w{religion member holiday}
|
180
|
+
@new_fields = @new_field_names.map{|x| MockField.new(x)}
|
181
|
+
|
182
|
+
# now assign the fields, which should clear out @field_names
|
183
|
+
@fs.fields = @new_fields
|
184
|
+
end
|
185
|
+
|
186
|
+
should_have_empty_names
|
187
|
+
|
188
|
+
should "have new names" do
|
189
|
+
assert_equal @new_field_names, @fs.field_names
|
190
|
+
end
|
191
|
+
|
192
|
+
should "be different to old names" do
|
193
|
+
assert_not_equal @fields, @fs.fields
|
194
|
+
assert_not_equal @field_names, @fs.field_names
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "qualify field names" do
|
199
|
+
setup do
|
200
|
+
@qualifier = 'table_name'
|
201
|
+
@fs.qualifier = @qualifier
|
202
|
+
end
|
203
|
+
|
204
|
+
should "qualify" do
|
205
|
+
assert_equal %Q{"table_name"."id"}, @fs.qualify( 'id' )
|
206
|
+
assert_equal %Q{"scheman"."id"}, @fs.qualify( 'scheman', 'id' )
|
207
|
+
assert_equal %Q{"scheman"."tbk"."id"}, @fs.qualify( ['scheman', 'tbk'], 'id' )
|
208
|
+
assert_equal %Q{"scheman"."tbk"."id"}, @fs.qualify( %w{scheman tbk id} )
|
209
|
+
assert_equal %Q{"scheman"."tbk"."id"}, @fs.qualify( 'scheman', 'tbk', 'id' )
|
210
|
+
end
|
211
|
+
|
212
|
+
context "TableName" do
|
213
|
+
setup do
|
214
|
+
@qualifier = TableName.new( 'more.than.one' )
|
215
|
+
@fs.qualifier = @qualifier
|
216
|
+
end
|
217
|
+
|
218
|
+
should "qualify" do
|
219
|
+
assert_equal %Q{"more"."than"."one"."id"}, @fs.qualify( 'id' )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
should "no qualifier" do
|
224
|
+
assert_equal @field_names.map{|x| %Q{"#{x}"}}.join(', '), @fs.list
|
225
|
+
end
|
226
|
+
|
227
|
+
should "have qualifier" do
|
228
|
+
assert_equal [ @qualifier ], @fs.qualifier
|
229
|
+
end
|
230
|
+
|
231
|
+
should "with builtin qualifier" do
|
232
|
+
assert_equal @field_names.map{|x| %Q{"#{@qualifier}"."#{x}"}}.join(', '), @fs.qualified_list
|
233
|
+
end
|
234
|
+
|
235
|
+
should "with other qualifier" do
|
236
|
+
qualifier = 'hello'
|
237
|
+
assert_equal @field_names.map{|x| %Q{"#{qualifier}"."#{x}"}}.join(', '), @fs.list( qualifier )
|
238
|
+
end
|
239
|
+
|
240
|
+
context "quoter" do
|
241
|
+
should_eventually "test alternative quoter"
|
242
|
+
should_eventually "test with DBI quoter"
|
243
|
+
end
|
244
|
+
|
245
|
+
context "array qualifier" do
|
246
|
+
setup do
|
247
|
+
@qualifier = %w{namespace user}
|
248
|
+
@fs.qualifier = @qualifier
|
249
|
+
end
|
250
|
+
|
251
|
+
should "list" do
|
252
|
+
assert_equal @field_names.map{|x| %Q{"namespace"."user"."#{x}"}}.join(', '), @fs.qualified_list
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context "Collections" do
|
258
|
+
should "be enumerable" do
|
259
|
+
assert_kind_of Enumerable, @fs
|
260
|
+
end
|
261
|
+
|
262
|
+
should "enumerate" do
|
263
|
+
@fs.each do |field|
|
264
|
+
assert_instance_of Symbol, field
|
265
|
+
assert @field_names.include?( field )
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "values" do
|
270
|
+
should "fetch values only" do
|
271
|
+
assert_equal( (@values - [ 'movie' ]), @fs.values( @hash_values ) )
|
272
|
+
end
|
273
|
+
|
274
|
+
should "fetch values transformed" do
|
275
|
+
assert_equal( (@values - [ 'movie' ]), @fs.values( @hash_values ){|x| x} )
|
276
|
+
end
|
277
|
+
|
278
|
+
should "fetch quoted values only" do
|
279
|
+
assert_equal( (@quoted_values - [ "'movie'" ]), @fs.quoted_values( @hash_values ) )
|
280
|
+
end
|
281
|
+
|
282
|
+
should "fetch hash" do
|
283
|
+
assert_equal @hash_values.reject{ |k,v| v == 'movie' }, @fs.hash_values( @hash_values )
|
284
|
+
end
|
285
|
+
|
286
|
+
should "fetch hash transformed" do
|
287
|
+
assert_equal @hash_values.reject{ |k,v| v == 'movie' }, @fs.hash_values( @hash_values ){|x| x}
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
context "express" do
|
293
|
+
should "fail on array size mismatch" do
|
294
|
+
assert_raise( RuntimeError ) { @fs.express( 1,2,3 ) }
|
295
|
+
end
|
296
|
+
|
297
|
+
should_eventually "test with Array"
|
298
|
+
should_eventually "test with Hash"
|
299
|
+
should_eventually "test with to_hash"
|
300
|
+
end
|
301
|
+
|
302
|
+
context "generate SQL fragments" do
|
303
|
+
context "comparison" do
|
304
|
+
setup do
|
305
|
+
@fs.qualifier = "table"
|
306
|
+
|
307
|
+
@result = <<EOF.chomp
|
308
|
+
( "table"."id" = "other"."id" and "table"."name" = "other"."name" and "table"."key" = "other"."key" and "table"."value" = "other"."value" )
|
309
|
+
EOF
|
310
|
+
|
311
|
+
@value_result = <<EOF.chomp
|
312
|
+
( "table"."id" = '700822' and "table"."name" = 'Egoyan' and "table"."key" = 'cut' and "table"."value" = 15 )
|
313
|
+
EOF
|
314
|
+
end
|
315
|
+
|
316
|
+
context "no local qualifier" do
|
317
|
+
setup do
|
318
|
+
@fs.qualifier = nil
|
319
|
+
end
|
320
|
+
|
321
|
+
should "raise exception" do
|
322
|
+
assert_nil @fs.qualifier
|
323
|
+
assert_raise( RuntimeError ) { @fs.comparison( 'other' ) }
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
should "other table" do
|
328
|
+
assert_not_nil @fs.qualifier
|
329
|
+
assert_equal @result, @fs.comparison( 'other' )
|
330
|
+
assert_equal @result.gsub( '=', '<' ), @fs.comparison( 'other', '<' )
|
331
|
+
end
|
332
|
+
|
333
|
+
should "values" do
|
334
|
+
assert_equal @value_result, @fs.comparison( @values )
|
335
|
+
assert_equal @value_result, @fs.comparison( @hash_values )
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
context "update" do
|
340
|
+
setup do
|
341
|
+
@table_result = <<EOF.chomp
|
342
|
+
"id" = "oops"."id", "name" = "oops"."name", "key" = "oops"."key", "value" = "oops"."value"
|
343
|
+
EOF
|
344
|
+
|
345
|
+
@value_result = <<EOF.chomp
|
346
|
+
"id" = '700822', "name" = 'Egoyan', "key" = 'cut', "value" = 15
|
347
|
+
EOF
|
348
|
+
end
|
349
|
+
|
350
|
+
should "produce update fragment" do
|
351
|
+
assert_equal @value_result, @fs.update( @hash_values )
|
352
|
+
assert_equal @value_result, @fs.update( @values )
|
353
|
+
assert_equal @table_result, @fs.update( 'oops' )
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
context "definition" do
|
358
|
+
should "create table" do
|
359
|
+
result = "id int, name varchar(20), key varchar(50), value varchar(200)"
|
360
|
+
assert_equal result, @fs.definitions
|
361
|
+
assert_equal result.gsub(', ', ",\n"), @fs.definitions( :joiner => ",\n" )
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context "Set operations" do
|
367
|
+
|
368
|
+
context "difference" do
|
369
|
+
setup do
|
370
|
+
@other = FieldSet.new( @fields[1..-1] )
|
371
|
+
@difference = @fs - @other
|
372
|
+
end
|
373
|
+
|
374
|
+
should "have only id" do
|
375
|
+
assert_not_nil @difference
|
376
|
+
# Field objects
|
377
|
+
assert_equal [ @fields.first ], @difference.fields
|
378
|
+
|
379
|
+
# strings
|
380
|
+
assert_equal [ @fields.first ], ( @fs - ( @field_names - [:id] ) ).fields
|
381
|
+
|
382
|
+
# symbols
|
383
|
+
assert_equal [ @fields.first ], ( @fs - ( @field_names.map{|x| x.to_sym} - [:id] ) ).fields
|
384
|
+
end
|
385
|
+
|
386
|
+
should "copy qualifier" do
|
387
|
+
assert_equal @fs.qualifier, @difference.qualifier
|
388
|
+
end
|
389
|
+
|
390
|
+
should "copy quoter" do
|
391
|
+
assert_equal @fs.quoter, @difference.quoter
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
context "append" do
|
396
|
+
setup do
|
397
|
+
@timestamp_field = MockField.new( 'date', 'timestamp' )
|
398
|
+
@sum = @fs + FieldSet.new( [@timestamp_field] )
|
399
|
+
end
|
400
|
+
|
401
|
+
should "have timestamp as well" do
|
402
|
+
assert_not_nil @sum
|
403
|
+
assert_equal @fields + [@timestamp_field], @sum.fields
|
404
|
+
end
|
405
|
+
|
406
|
+
should "not fail on String extension" do
|
407
|
+
assert_nothing_raised( RuntimeError ) { @fs + [ 'date' ] }
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
context "union" do
|
412
|
+
setup do
|
413
|
+
@timestamp_field = MockField.new( :date, 'timestamp' )
|
414
|
+
@other_id_field = MockField.new( :id, 'string' )
|
415
|
+
@union = @fs | FieldSet.new( [@timestamp_field, @other_id_field] )
|
416
|
+
@string_union = @fs | %w{date id}.map( &:to_sym )
|
417
|
+
end
|
418
|
+
|
419
|
+
should "have only one extra field" do
|
420
|
+
assert_equal @fs.fields.size+1, @union.fields.size
|
421
|
+
assert_equal @fs.fields.size+1, @string_union.fields.size
|
422
|
+
end
|
423
|
+
|
424
|
+
should "have the right field names" do
|
425
|
+
assert_equal @field_names + [:date], @union.field_names
|
426
|
+
assert_equal @field_names + [:date], @string_union.field_names
|
427
|
+
end
|
428
|
+
|
429
|
+
should "have timestamp" do
|
430
|
+
assert_not_nil @union
|
431
|
+
assert_equal @fields + [@timestamp_field], @union.fields
|
432
|
+
assert_equal @field_names + [:date], @string_union.field_names
|
433
|
+
end
|
434
|
+
|
435
|
+
should "have original id only" do
|
436
|
+
assert_not_nil @union
|
437
|
+
assert_equal @fields + [@timestamp_field], @union.fields
|
438
|
+
assert_not_equal @fs.find_field('id'), @other_id_field
|
439
|
+
end
|
440
|
+
|
441
|
+
should "not fail on String extension" do
|
442
|
+
assert_nothing_raised( RuntimeError ) { @fs | [ 'date' ] }
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
context "intersection" do
|
447
|
+
setup do
|
448
|
+
@id_only = @fs & FieldSet.new( ['id'] )
|
449
|
+
end
|
450
|
+
|
451
|
+
should "accept String subset" do
|
452
|
+
assert_nothing_raised( Exception ) { @fs & [ 'date' ] }
|
453
|
+
end
|
454
|
+
|
455
|
+
should "should have only id" do
|
456
|
+
#~ puts "id_only.field_names: #{@id_only.field_names.inspect}"
|
457
|
+
assert_equal [:id], @id_only.field_names
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
end
|
462
|
+
end
|