sequel 3.36.1 → 3.37.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +13 -0
  3. data/bin/sequel +12 -16
  4. data/doc/advanced_associations.rdoc +36 -67
  5. data/doc/association_basics.rdoc +11 -16
  6. data/doc/release_notes/3.37.0.txt +338 -0
  7. data/doc/schema_modification.rdoc +4 -0
  8. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
  10. data/lib/sequel/adapters/mysql2.rb +4 -3
  11. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  12. data/lib/sequel/adapters/postgres.rb +4 -60
  13. data/lib/sequel/adapters/shared/mssql.rb +2 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +0 -5
  15. data/lib/sequel/adapters/shared/postgres.rb +68 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +17 -1
  17. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
  18. data/lib/sequel/adapters/utils/pg_types.rb +76 -0
  19. data/lib/sequel/core.rb +13 -0
  20. data/lib/sequel/database/misc.rb +41 -1
  21. data/lib/sequel/database/schema_generator.rb +23 -10
  22. data/lib/sequel/database/schema_methods.rb +26 -4
  23. data/lib/sequel/dataset/graph.rb +2 -1
  24. data/lib/sequel/dataset/query.rb +62 -2
  25. data/lib/sequel/extensions/_pretty_table.rb +7 -3
  26. data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
  27. data/lib/sequel/extensions/blank.rb +4 -0
  28. data/lib/sequel/extensions/columns_introspection.rb +13 -2
  29. data/lib/sequel/extensions/core_extensions.rb +6 -0
  30. data/lib/sequel/extensions/eval_inspect.rb +158 -0
  31. data/lib/sequel/extensions/inflector.rb +4 -0
  32. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  33. data/lib/sequel/extensions/migration.rb +4 -1
  34. data/lib/sequel/extensions/named_timezones.rb +4 -0
  35. data/lib/sequel/extensions/null_dataset.rb +4 -0
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pg_array.rb +219 -168
  38. data/lib/sequel/extensions/pg_array_ops.rb +7 -2
  39. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
  40. data/lib/sequel/extensions/pg_hstore.rb +3 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
  42. data/lib/sequel/extensions/pg_inet.rb +28 -3
  43. data/lib/sequel/extensions/pg_interval.rb +192 -0
  44. data/lib/sequel/extensions/pg_json.rb +21 -9
  45. data/lib/sequel/extensions/pg_range.rb +487 -0
  46. data/lib/sequel/extensions/pg_range_ops.rb +122 -0
  47. data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
  48. data/lib/sequel/extensions/pretty_table.rb +12 -1
  49. data/lib/sequel/extensions/query.rb +4 -0
  50. data/lib/sequel/extensions/query_literals.rb +6 -6
  51. data/lib/sequel/extensions/schema_dumper.rb +39 -38
  52. data/lib/sequel/extensions/select_remove.rb +4 -0
  53. data/lib/sequel/extensions/server_block.rb +3 -2
  54. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  55. data/lib/sequel/extensions/sql_expr.rb +4 -0
  56. data/lib/sequel/extensions/string_date_time.rb +4 -0
  57. data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
  58. data/lib/sequel/extensions/to_dot.rb +4 -0
  59. data/lib/sequel/model/associations.rb +150 -91
  60. data/lib/sequel/plugins/identity_map.rb +2 -2
  61. data/lib/sequel/plugins/list.rb +1 -0
  62. data/lib/sequel/plugins/many_through_many.rb +33 -32
  63. data/lib/sequel/plugins/nested_attributes.rb +11 -3
  64. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  65. data/lib/sequel/plugins/schema.rb +1 -1
  66. data/lib/sequel/sql.rb +14 -14
  67. data/lib/sequel/version.rb +2 -2
  68. data/spec/adapters/mysql_spec.rb +25 -0
  69. data/spec/adapters/postgres_spec.rb +572 -28
  70. data/spec/adapters/sqlite_spec.rb +16 -1
  71. data/spec/core/database_spec.rb +61 -2
  72. data/spec/core/dataset_spec.rb +92 -0
  73. data/spec/core/expression_filters_spec.rb +12 -0
  74. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  75. data/spec/extensions/boolean_readers_spec.rb +25 -25
  76. data/spec/extensions/eval_inspect_spec.rb +58 -0
  77. data/spec/extensions/json_serializer_spec.rb +0 -6
  78. data/spec/extensions/list_spec.rb +1 -1
  79. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  80. data/spec/extensions/many_through_many_spec.rb +81 -0
  81. data/spec/extensions/nested_attributes_spec.rb +21 -4
  82. data/spec/extensions/pg_array_ops_spec.rb +1 -11
  83. data/spec/extensions/pg_array_spec.rb +181 -90
  84. data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
  85. data/spec/extensions/pg_hstore_spec.rb +1 -3
  86. data/spec/extensions/pg_inet_spec.rb +6 -1
  87. data/spec/extensions/pg_interval_spec.rb +73 -0
  88. data/spec/extensions/pg_json_spec.rb +5 -9
  89. data/spec/extensions/pg_range_ops_spec.rb +49 -0
  90. data/spec/extensions/pg_range_spec.rb +372 -0
  91. data/spec/extensions/pg_statement_cache_spec.rb +1 -2
  92. data/spec/extensions/query_literals_spec.rb +1 -2
  93. data/spec/extensions/schema_dumper_spec.rb +48 -89
  94. data/spec/extensions/serialization_spec.rb +1 -5
  95. data/spec/extensions/server_block_spec.rb +2 -2
  96. data/spec/extensions/spec_helper.rb +12 -2
  97. data/spec/extensions/split_array_nil_spec.rb +24 -0
  98. data/spec/integration/associations_test.rb +4 -4
  99. data/spec/integration/database_test.rb +2 -2
  100. data/spec/integration/dataset_test.rb +4 -4
  101. data/spec/integration/eager_loader_test.rb +6 -6
  102. data/spec/integration/plugin_test.rb +2 -2
  103. data/spec/integration/spec_helper.rb +2 -2
  104. data/spec/model/association_reflection_spec.rb +5 -0
  105. data/spec/model/associations_spec.rb +156 -49
  106. data/spec/model/eager_loading_spec.rb +137 -2
  107. data/spec/model/model_spec.rb +10 -10
  108. metadata +15 -2
@@ -11,7 +11,7 @@ describe "NestedAttributes plugin" do
11
11
 
12
12
  def check_sql_array(*shoulds)
13
13
  sqls = @db.sqls
14
- shoulds.length.should == sqls.length
14
+ sqls.length.should == shoulds.length
15
15
  shoulds.zip(sqls){|s, i| check_sqls(s, i)}
16
16
  end
17
17
 
@@ -59,9 +59,8 @@ describe "NestedAttributes plugin" do
59
59
  @db.sqls.should == []
60
60
  a.save
61
61
  check_sql_array(["INSERT INTO artists (name, id) VALUES ('Ar', 1)", "INSERT INTO artists (id, name) VALUES (1, 'Ar')"],
62
- "INSERT INTO albums (name) VALUES ('Al')",
63
- "UPDATE albums SET artist_id = NULL WHERE ((artist_id = 1) AND (id != 2))",
64
- ["UPDATE albums SET artist_id = 1, name = 'Al' WHERE (id = 2)", "UPDATE albums SET name = 'Al', artist_id = 1 WHERE (id = 2)"])
62
+ "UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)",
63
+ ["INSERT INTO albums (artist_id, name) VALUES (1, 'Al')", "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)"])
65
64
  end
66
65
 
67
66
  it "should support creating new one_to_many objects" do
@@ -555,4 +554,22 @@ describe "NestedAttributes plugin" do
555
554
  proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
556
555
  proc{al.set(:tags_attributes=>[{:name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
557
556
  end
557
+
558
+ it "should accept a proc for the :fields option that accepts the associated object and returns an array of fields" do
559
+ @Tag.columns :id, :name, :number
560
+ @Album.nested_attributes :tags, :destroy=>true, :remove=>true, :fields=>proc{|object| object.is_a?(@Tag) ? [:name] : []}
561
+
562
+ al = @Album.load(:id=>10, :name=>'Al')
563
+ t = @Tag.load(:id=>30, :name=>'T', :number=>10)
564
+ al.associations[:tags] = [t]
565
+ al.set(:tags_attributes=>[{:id=>30, :name=>'T2'}, {:name=>'T3'}])
566
+ @db.sqls.should == []
567
+ al.save
568
+ check_sql_array("UPDATE albums SET name = 'Al' WHERE (id = 10)",
569
+ "UPDATE tags SET name = 'T2' WHERE (id = 30)",
570
+ "INSERT INTO tags (name) VALUES ('T3')",
571
+ ["INSERT INTO at (album_id, tag_id) VALUES (10, 1)", "INSERT INTO at (tag_id, album_id) VALUES (1, 10)"])
572
+ proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
573
+ proc{al.set(:tags_attributes=>[{:name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
574
+ end
558
575
  end
@@ -1,16 +1,6 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Sequel::Postgres::ArrayOp" do
4
- # Attempt to load pg_array first to test PGArray -> ArrayOp code
5
- pg_array_loaded = begin
6
- Sequel.extension :pg_array
7
- true
8
- rescue LoadError
9
- false
10
- end
11
-
12
- Sequel.extension :pg_array_ops
13
-
14
4
  before do
15
5
  @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
16
6
  @a = :a.pg_array
@@ -101,5 +91,5 @@ describe "Sequel::Postgres::ArrayOp" do
101
91
 
102
92
  it "should allow transforming PGArray instances into ArrayOp instances" do
103
93
  @db.literal([1,2].pg_array.op.push(3)).should == "(ARRAY[1,2] || 3)"
104
- end if pg_array_loaded
94
+ end
105
95
  end
@@ -1,108 +1,119 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
- begin
4
- Sequel.extension :pg_array
5
- rescue LoadError => e
6
- skip_warn "can't load pg_array extension (#{e.class}: #{e})"
7
- else
8
3
  describe "pg_array extension" do
9
4
  before do
10
5
  @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
11
- @db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
6
+ @db.extend(Module.new{def bound_variable_arg(arg, conn) arg end; def get_conversion_procs(conn) {} end})
7
+ @db.extend_datasets(Module.new{def supports_timestamp_timezones?; false; end; def supports_timestamp_usecs?; false; end})
8
+ @db.extension(:pg_array)
12
9
  @m = Sequel::Postgres
10
+ @convertor = @m::PG_TYPES
13
11
  end
14
12
 
15
13
  it "should parse single dimensional text arrays" do
16
- @m::PGStringArray.parse("{a}").to_a.first.should be_a_kind_of(String)
17
- @m::PGStringArray.parse("{}").to_a.should == []
18
- @m::PGStringArray.parse("{a}").to_a.should == ['a']
19
- @m::PGStringArray.parse('{"a b"}').to_a.should == ['a b']
20
- @m::PGStringArray.parse('{a,b}').to_a.should == ['a', 'b']
14
+ c = @convertor[1009]
15
+ c.call("{a}").to_a.first.should be_a_kind_of(String)
16
+ c.call("{}").to_a.should == []
17
+ c.call("{a}").to_a.should == ['a']
18
+ c.call('{"a b"}').to_a.should == ['a b']
19
+ c.call('{a,b}').to_a.should == ['a', 'b']
21
20
  end
22
21
 
23
22
  it "should parse multi-dimensional text arrays" do
24
- @m::PGStringArray.parse("{{}}").to_a.should == [[]]
25
- @m::PGStringArray.parse("{{a},{b}}").to_a.should == [['a'], ['b']]
26
- @m::PGStringArray.parse('{{"a b"},{c}}').to_a.should == [['a b'], ['c']]
27
- @m::PGStringArray.parse('{{{a},{b}},{{c},{d}}}').to_a.should == [[['a'], ['b']], [['c'], ['d']]]
28
- @m::PGStringArray.parse('{{{a,e},{b,f}},{{c,g},{d,h}}}').to_a.should == [[['a', 'e'], ['b', 'f']], [['c', 'g'], ['d', 'h']]]
23
+ c = @convertor[1009]
24
+ c.call("{{}}").to_a.should == [[]]
25
+ c.call("{{a},{b}}").to_a.should == [['a'], ['b']]
26
+ c.call('{{"a b"},{c}}').to_a.should == [['a b'], ['c']]
27
+ c.call('{{{a},{b}},{{c},{d}}}').to_a.should == [[['a'], ['b']], [['c'], ['d']]]
28
+ c.call('{{{a,e},{b,f}},{{c,g},{d,h}}}').to_a.should == [[['a', 'e'], ['b', 'f']], [['c', 'g'], ['d', 'h']]]
29
29
  end
30
30
 
31
31
  it "should parse text arrays with embedded deliminaters" do
32
- @m::PGStringArray.parse('{{"{},","\\",\\,\\\\\\"\\""}}').to_a.should == [['{},', '",,\\""']]
32
+ c = @convertor[1009]
33
+ c.call('{{"{},","\\",\\,\\\\\\"\\""}}').to_a.should == [['{},', '",,\\""']]
33
34
  end
34
35
 
35
36
  it "should parse single dimensional integer arrays" do
36
- @m::PGIntegerArray.parse("{1}").to_a.first.should be_a_kind_of(Integer)
37
- @m::PGIntegerArray.parse("{}").to_a.should == []
38
- @m::PGIntegerArray.parse("{1}").to_a.should == [1]
39
- @m::PGIntegerArray.parse('{2,3}').to_a.should == [2, 3]
40
- @m::PGIntegerArray.parse('{3,4,5}').to_a.should == [3, 4, 5]
37
+ c = @convertor[1007]
38
+ c.call("{1}").to_a.first.should be_a_kind_of(Integer)
39
+ c.call("{}").to_a.should == []
40
+ c.call("{1}").to_a.should == [1]
41
+ c.call('{2,3}').to_a.should == [2, 3]
42
+ c.call('{3,4,5}').to_a.should == [3, 4, 5]
41
43
  end
42
44
 
43
45
  it "should parse multiple dimensional integer arrays" do
44
- @m::PGIntegerArray.parse("{{}}").to_a.should == [[]]
45
- @m::PGIntegerArray.parse("{{1}}").to_a.should == [[1]]
46
- @m::PGIntegerArray.parse('{{2},{3}}').to_a.should == [[2], [3]]
47
- @m::PGIntegerArray.parse('{{{1,2},{3,4}},{{5,6},{7,8}}}').to_a.should == [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
46
+ c = @convertor[1007]
47
+ c.call("{{}}").to_a.should == [[]]
48
+ c.call("{{1}}").to_a.should == [[1]]
49
+ c.call('{{2},{3}}').to_a.should == [[2], [3]]
50
+ c.call('{{{1,2},{3,4}},{{5,6},{7,8}}}').to_a.should == [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
48
51
  end
49
52
 
50
53
  it "should parse single dimensional float arrays" do
51
- @m::PGFloatArray.parse("{}").to_a.should == []
52
- @m::PGFloatArray.parse("{1.5}").to_a.should == [1.5]
53
- @m::PGFloatArray.parse('{2.5,3.5}').to_a.should == [2.5, 3.5]
54
- @m::PGFloatArray.parse('{3.5,4.5,5.5}').to_a.should == [3.5, 4.5, 5.5]
54
+ c = @convertor[1022]
55
+ c.call("{}").to_a.should == []
56
+ c.call("{1.5}").to_a.should == [1.5]
57
+ c.call('{2.5,3.5}').to_a.should == [2.5, 3.5]
58
+ c.call('{3.5,4.5,5.5}').to_a.should == [3.5, 4.5, 5.5]
55
59
  end
56
60
 
57
61
  it "should parse multiple dimensional float arrays" do
58
- @m::PGFloatArray.parse("{{}}").to_a.should == [[]]
59
- @m::PGFloatArray.parse("{{1.5}}").to_a.should == [[1.5]]
60
- @m::PGFloatArray.parse('{{2.5},{3.5}}').to_a.should == [[2.5], [3.5]]
61
- @m::PGFloatArray.parse('{{{1.5,2.5},{3.5,4.5}},{{5.5,6.5},{7.5,8.5}}}').to_a.should == [[[1.5, 2.5], [3.5, 4.5]], [[5.5, 6.5], [7.5, 8.5]]]
62
+ c = @convertor[1022]
63
+ c.call("{{}}").to_a.should == [[]]
64
+ c.call("{{1.5}}").to_a.should == [[1.5]]
65
+ c.call('{{2.5},{3.5}}').to_a.should == [[2.5], [3.5]]
66
+ c.call('{{{1.5,2.5},{3.5,4.5}},{{5.5,6.5},{7.5,8.5}}}').to_a.should == [[[1.5, 2.5], [3.5, 4.5]], [[5.5, 6.5], [7.5, 8.5]]]
62
67
  end
63
68
 
64
69
  it "should parse integers in float arrays as floats" do
65
- @m::PGFloatArray.parse("{1}").to_a.first.should be_a_kind_of(Float)
66
- @m::PGFloatArray.parse("{1}").to_a.should == [1.0]
67
- @m::PGFloatArray.parse('{{{1,2},{3,4}},{{5,6},{7,8}}}').to_a.should == [[[1.0, 2.0], [3.0, 4.0]], [[5.0, 6.0], [7.0, 8.0]]]
70
+ c = @convertor[1022]
71
+ c.call("{1}").to_a.first.should be_a_kind_of(Float)
72
+ c.call("{1}").to_a.should == [1.0]
73
+ c.call('{{{1,2},{3,4}},{{5,6},{7,8}}}').to_a.should == [[[1.0, 2.0], [3.0, 4.0]], [[5.0, 6.0], [7.0, 8.0]]]
68
74
  end
69
75
 
70
76
  it "should parse single dimensional decimal arrays" do
71
- @m::PGDecimalArray.parse("{}").to_a.should == []
72
- @m::PGDecimalArray.parse("{1.5}").to_a.should == [BigDecimal.new('1.5')]
73
- @m::PGDecimalArray.parse('{2.5,3.5}').to_a.should == [BigDecimal.new('2.5'), BigDecimal.new('3.5')]
74
- @m::PGDecimalArray.parse('{3.5,4.5,5.5}').to_a.should == [BigDecimal.new('3.5'), BigDecimal.new('4.5'), BigDecimal.new('5.5')]
77
+ c = @convertor[1231]
78
+ c.call("{}").to_a.should == []
79
+ c.call("{1.5}").to_a.should == [BigDecimal.new('1.5')]
80
+ c.call('{2.5,3.5}').to_a.should == [BigDecimal.new('2.5'), BigDecimal.new('3.5')]
81
+ c.call('{3.5,4.5,5.5}').to_a.should == [BigDecimal.new('3.5'), BigDecimal.new('4.5'), BigDecimal.new('5.5')]
75
82
  end
76
83
 
77
84
  it "should parse multiple dimensional decimal arrays" do
78
- @m::PGDecimalArray.parse("{{}}").to_a.should == [[]]
79
- @m::PGDecimalArray.parse("{{1.5}}").to_a.should == [[BigDecimal.new('1.5')]]
80
- @m::PGDecimalArray.parse('{{2.5},{3.5}}').to_a.should == [[BigDecimal.new('2.5')], [BigDecimal.new('3.5')]]
81
- @m::PGDecimalArray.parse('{{{1.5,2.5},{3.5,4.5}},{{5.5,6.5},{7.5,8.5}}}').to_a.should == [[[BigDecimal.new('1.5'), BigDecimal.new('2.5')], [BigDecimal.new('3.5'), BigDecimal.new('4.5')]], [[BigDecimal.new('5.5'), BigDecimal.new('6.5')], [BigDecimal.new('7.5'), BigDecimal.new('8.5')]]]
85
+ c = @convertor[1231]
86
+ c.call("{{}}").to_a.should == [[]]
87
+ c.call("{{1.5}}").to_a.should == [[BigDecimal.new('1.5')]]
88
+ c.call('{{2.5},{3.5}}').to_a.should == [[BigDecimal.new('2.5')], [BigDecimal.new('3.5')]]
89
+ c.call('{{{1.5,2.5},{3.5,4.5}},{{5.5,6.5},{7.5,8.5}}}').to_a.should == [[[BigDecimal.new('1.5'), BigDecimal.new('2.5')], [BigDecimal.new('3.5'), BigDecimal.new('4.5')]], [[BigDecimal.new('5.5'), BigDecimal.new('6.5')], [BigDecimal.new('7.5'), BigDecimal.new('8.5')]]]
82
90
  end
83
91
 
84
92
  it "should parse decimal values with arbitrary precision" do
85
- @m::PGDecimalArray.parse("{1.000000000000000000005}").to_a.should == [BigDecimal.new('1.000000000000000000005')]
86
- @m::PGDecimalArray.parse("{{1.000000000000000000005,2.000000000000000000005},{3.000000000000000000005,4.000000000000000000005}}").to_a.should == [[BigDecimal.new('1.000000000000000000005'), BigDecimal.new('2.000000000000000000005')], [BigDecimal.new('3.000000000000000000005'), BigDecimal.new('4.000000000000000000005')]]
93
+ c = @convertor[1231]
94
+ c.call("{1.000000000000000000005}").to_a.should == [BigDecimal.new('1.000000000000000000005')]
95
+ c.call("{{1.000000000000000000005,2.000000000000000000005},{3.000000000000000000005,4.000000000000000000005}}").to_a.should == [[BigDecimal.new('1.000000000000000000005'), BigDecimal.new('2.000000000000000000005')], [BigDecimal.new('3.000000000000000000005'), BigDecimal.new('4.000000000000000000005')]]
87
96
  end
88
97
 
89
98
  it "should parse integers in decimal arrays as BigDecimals" do
90
- @m::PGDecimalArray.parse("{1}").to_a.first.should be_a_kind_of(BigDecimal)
91
- @m::PGDecimalArray.parse("{1}").to_a.should == [BigDecimal.new('1')]
92
- @m::PGDecimalArray.parse('{{{1,2},{3,4}},{{5,6},{7,8}}}').to_a.should == [[[BigDecimal.new('1'), BigDecimal.new('2')], [BigDecimal.new('3'), BigDecimal.new('4')]], [[BigDecimal.new('5'), BigDecimal.new('6')], [BigDecimal.new('7'), BigDecimal.new('8')]]]
99
+ c = @convertor[1231]
100
+ c.call("{1}").to_a.first.should be_a_kind_of(BigDecimal)
101
+ c.call("{1}").to_a.should == [BigDecimal.new('1')]
102
+ c.call('{{{1,2},{3,4}},{{5,6},{7,8}}}').to_a.should == [[[BigDecimal.new('1'), BigDecimal.new('2')], [BigDecimal.new('3'), BigDecimal.new('4')]], [[BigDecimal.new('5'), BigDecimal.new('6')], [BigDecimal.new('7'), BigDecimal.new('8')]]]
93
103
  end
94
104
 
95
105
  it "should parse arrays with NULL values" do
96
- [@m::PGStringArray, @m::PGIntegerArray, @m::PGFloatArray, @m::PGDecimalArray].each do |c|
97
- c.parse("{NULL}").should == [nil]
98
- c.parse("{NULL,NULL}").should == [nil,nil]
99
- c.parse("{{NULL,NULL},{NULL,NULL}}").should == [[nil,nil],[nil,nil]]
106
+ @convertor.values_at(1007, 1009, 1022, 1231).each do |c|
107
+ c.call("{NULL}").should == [nil]
108
+ c.call("{NULL,NULL}").should == [nil,nil]
109
+ c.call("{{NULL,NULL},{NULL,NULL}}").should == [[nil,nil],[nil,nil]]
100
110
  end
101
111
  end
102
112
 
103
113
  it 'should parse arrays with "NULL" values' do
104
- @m::PGStringArray.parse('{NULL,"NULL",NULL}').to_a.should == [nil, "NULL", nil]
105
- @m::PGStringArray.parse('{NULLA,"NULL",NULL}').to_a.should == ["NULLA", "NULL", nil]
114
+ c = @convertor[1009]
115
+ c.call('{NULL,"NULL",NULL}').to_a.should == [nil, "NULL", nil]
116
+ c.call('{NULLA,"NULL",NULL}').to_a.should == ["NULLA", "NULL", nil]
106
117
  end
107
118
 
108
119
  it "should literalize arrays without types correctly" do
@@ -135,24 +146,6 @@ describe "pg_array extension" do
135
146
  @db.literal(@m::PGArray.new([nil, "{},[]'\""], :"varchar(255)")).should == "ARRAY[NULL,'{},[]''\"']::varchar(255)[]"
136
147
  end
137
148
 
138
- it "should have reasonable default types" do
139
- @db.literal(@m::PGArray.new([])).should == 'ARRAY[]'
140
- @db.literal(@m::PGIntegerArray.new([])).should == 'ARRAY[]::int4[]'
141
- @db.literal(@m::PGFloatArray.new([])).should == 'ARRAY[]::double precision[]'
142
- @db.literal(@m::PGStringArray.new([])).should == 'ARRAY[]::text[]'
143
- @db.literal(@m::PGDecimalArray.new([])).should == 'ARRAY[]::decimal[]'
144
- end
145
-
146
- it "should use varchar type for char arrays without length" do
147
- @db.literal(@m::PGStringArray.new([], :char)).should == 'ARRAY[]::varchar[]'
148
- @db.literal(@m::PGStringArray.new([], 'char')).should == 'ARRAY[]::varchar[]'
149
- end
150
-
151
- it "should use given type for char arrays with length" do
152
- @db.literal(@m::PGStringArray.new([], :'char(2)')).should == 'ARRAY[]::char(2)[]'
153
- @db.literal(@m::PGStringArray.new([], 'char(1)')).should == 'ARRAY[]::char(1)[]'
154
- end
155
-
156
149
  it "should have Array#pg_array method for easy PGArray creation" do
157
150
  @db.literal([1].pg_array).should == 'ARRAY[1]'
158
151
  @db.literal([1, 2].pg_array(:int4)).should == 'ARRAY[1,2]::int4[]'
@@ -160,37 +153,135 @@ describe "pg_array extension" do
160
153
  end
161
154
 
162
155
  it "should support using arrays as bound variables" do
163
- @db.extend Sequel::Postgres::PGArray::DatabaseMethods
164
156
  @db.bound_variable_arg(1, nil).should == 1
165
157
  @db.bound_variable_arg([1,2].pg_array, nil).should == '{1,2}'
166
158
  @db.bound_variable_arg([1,2], nil).should == '{1,2}'
167
159
  @db.bound_variable_arg([[1,2]], nil).should == '{{1,2}}'
168
160
  @db.bound_variable_arg([1.0,2.0], nil).should == '{1.0,2.0}'
161
+ @db.bound_variable_arg([Sequel.lit('a'), Sequel.blob("a\0'\"")], nil).should == '{a,"a\\\\000\\\\047\\""}'
169
162
  @db.bound_variable_arg(["\\ \"", 'NULL', nil], nil).should == '{"\\\\ \\"","NULL",NULL}'
170
163
  end
171
164
 
172
165
  it "should parse array types from the schema correctly" do
173
- @db.extend Sequel::Postgres::PGArray::DatabaseMethods
174
166
  @db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'integer[]'}, {:name=>'f', :db_type=>'real[]'}, {:name=>'d', :db_type=>'numeric[]'}, {:name=>'t', :db_type=>'text[]'}]
175
167
  @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :integer_array, :float_array, :decimal_array, :string_array]
176
168
  end
177
169
 
178
170
  it "should support typecasting of the various array types" do
179
- @db.extend Sequel::Postgres::PGArray::DatabaseMethods
180
- a = [1, 2]
181
- o = a.pg_array
182
- %w[integer float decimal string].each do |x|
183
- @db.typecast_value(:"#{x}_array", o).should equal(o)
184
- @db.typecast_value(:"#{x}_array", a).should == a
185
- @db.typecast_value(:"#{x}_array", a).should be_a_kind_of(eval("Sequel::Postgres::PG#{x.capitalize}Array"))
186
- @db.typecast_value(:"#{x}_array", '{}').should == []
187
- @db.typecast_value(:"#{x}_array", '{}').should be_a_kind_of(eval("Sequel::Postgres::PG#{x.capitalize}Array"))
171
+ {
172
+ :integer=>{:class=>Integer, :convert=>['1', '1', 1, '1']},
173
+ :float=>{:db_type=>'double precision', :class=>Float, :convert=>['1.1', '1.1', 1.1, '1.1']},
174
+ :decimal=>{:db_type=>'numeric', :class=>BigDecimal, :convert=>['1.00000000000000000000000001', '1.00000000000000000000000001', BigDecimal.new('1.00000000000000000000000001'), '1.00000000000000000000000001']},
175
+ :string=>{:db_type=>'text', :class=>String, :convert=>['1', 1, '1', "'1'"]},
176
+ :bigint=>{:class=>Integer, :convert=>['1', '1', 1, '1']},
177
+ :boolean=>{:class=>TrueClass, :convert=>['t', 't', true, 'true']},
178
+ :blob=>{:db_type=>'bytea', :class=>Sequel::SQL::Blob, :convert=>['1', '1', '1', "'1'"]},
179
+ :date=>{:class=>Date, :convert=>['2011-10-12', '2011-10-12', Date.new(2011, 10, 12), "'2011-10-12'"]},
180
+ :time=>{:db_type=>'time without time zone', :class=>Sequel::SQLTime, :convert=>['01:02:03', '01:02:03', Sequel::SQLTime.create(1, 2, 3), "'01:02:03'"]},
181
+ :datetime=>{:db_type=>'timestamp without time zone', :class=>Time, :convert=>['2011-10-12 01:02:03', '2011-10-12 01:02:03', Time.local(2011, 10, 12, 1, 2, 3), "'2011-10-12 01:02:03'"]},
182
+ :time_timezone=>{:db_type=>'time with time zone', :class=>Sequel::SQLTime, :convert=>['01:02:03', '01:02:03', Sequel::SQLTime.create(1, 2, 3), "'01:02:03'"]},
183
+ :datetime_timezone=>{:db_type=>'timestamp with time zone', :class=>Time, :convert=>['2011-10-12 01:02:03', '2011-10-12 01:02:03', Time.local(2011, 10, 12, 1, 2, 3), "'2011-10-12 01:02:03'"]},
184
+ }.each do |type, h|
185
+ meth = :"#{type}_array"
186
+ db_type = h[:db_type]||type
187
+ klass = h[:class]
188
+ text_in, array_in, value, output = h[:convert]
189
+
190
+ ["{#{text_in}}", [array_in]].each do |input|
191
+ v = @db.typecast_value(meth, input)
192
+ v.should == [value]
193
+ v.first.should be_a_kind_of(klass)
194
+ v.array_type.should_not be_nil
195
+ @db.typecast_value(meth, [value].pg_array).should == v
196
+ @db.typecast_value(meth, v).should equal(v)
197
+ end
198
+
199
+ ["{{#{text_in}}}", [[array_in]]].each do |input|
200
+ v = @db.typecast_value(meth, input)
201
+ v.should == [[value]]
202
+ v.first.first.should be_a_kind_of(klass)
203
+ v.array_type.should_not be_nil
204
+ @db.typecast_value(meth, [[value]].pg_array).should == v
205
+ @db.typecast_value(meth, v).should equal(v)
206
+ end
207
+
208
+ @db.literal(@db.typecast_value(meth, [array_in])).should == "ARRAY[#{output}]::#{db_type}[]"
209
+ @db.typecast_value(meth, '{}').should == []
210
+ @db.literal(@db.typecast_value(meth, '{}')).should == "ARRAY[]::#{db_type}[]"
188
211
  end
189
- @db.typecast_value(:integer_array, '{1}').should == [1]
190
- @db.typecast_value(:float_array, '{1}').should == [1.0]
191
- @db.typecast_value(:decimal_array, '{1}').should == [BigDecimal.new('1')]
192
- @db.typecast_value(:string_array, '{1}').should == ['1']
193
212
  proc{@db.typecast_value(:integer_array, {})}.should raise_error(Sequel::InvalidValue)
194
213
  end
195
- end
214
+
215
+ it "should support registering custom array types" do
216
+ Sequel::Postgres::PGArray.register('foo')
217
+ @db.typecast_value(:foo_array, []).should be_a_kind_of(Sequel::Postgres::PGArray)
218
+ @db.fetch = [{:name=>'id', :db_type=>'foo[]'}]
219
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:foo_array]
220
+ end
221
+
222
+ it "should support registering custom types with :type_symbol option" do
223
+ Sequel::Postgres::PGArray.register('foo', :type_symbol=>:bar)
224
+ @db.typecast_value(:bar_array, []).should be_a_kind_of(Sequel::Postgres::PGArray)
225
+ @db.fetch = [{:name=>'id', :db_type=>'foo[]'}]
226
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:bar_array]
227
+ end
228
+
229
+ it "should support using a block as a custom conversion proc given as block" do
230
+ Sequel::Postgres::PGArray.register('foo'){|s| (s*2).to_i}
231
+ @db.typecast_value(:foo_array, '{1}').should == [11]
232
+ end
233
+
234
+ it "should support using a block as a custom conversion proc given as :converter option" do
235
+ Sequel::Postgres::PGArray.register('foo', :converter=>proc{|s| (s*2).to_i})
236
+ @db.typecast_value(:foo_array, '{1}').should == [11]
237
+ end
238
+
239
+ it "should support using an existing scaler conversion proc via the :scalar_oid option" do
240
+ Sequel::Postgres::PGArray.register('foo', :scalar_oid=>16)
241
+ @db.typecast_value(:foo_array, '{"t"}').should == [true]
242
+ end
243
+
244
+ it "should raise an error if using :scalar_oid option with unexisting scalar conversion proc" do
245
+ proc{Sequel::Postgres::PGArray.register('foo', :scalar_oid=>0)}.should raise_error(Sequel::Error)
246
+ end
247
+
248
+ it "should raise an error if using :converter option and a block argument" do
249
+ proc{Sequel::Postgres::PGArray.register('foo', :converter=>proc{}){}}.should raise_error(Sequel::Error)
250
+ end
251
+
252
+ it "should raise an error if using :scalar_oid option and a block argument" do
253
+ proc{Sequel::Postgres::PGArray.register('foo', :scalar_oid=>16){}}.should raise_error(Sequel::Error)
254
+ end
255
+
256
+ it "should support registering custom types with :oid option" do
257
+ Sequel::Postgres::PGArray.register('foo', :oid=>1)
258
+ Sequel::Postgres::PG_TYPES[1].call('{1}').should be_a_kind_of(Sequel::Postgres::PGArray)
259
+ end
260
+
261
+ it "should support registering custom types with :parser=>:json option" do
262
+ Sequel::Postgres::PGArray.register('foo', :oid=>2, :parser=>:json)
263
+ Sequel::Postgres::PG_TYPES[2].should be_a_kind_of(Sequel::Postgres::PGArray::JSONCreator)
264
+ end
265
+
266
+ it "should support registering convertors with :parser=>:json option" do
267
+ Sequel::Postgres::PGArray.register('foo', :oid=>4, :parser=>:json){|s| s * 2}
268
+ Sequel::Postgres::PG_TYPES[4].call('{{1, 2}, {3, 4}}').should == [[2, 4], [6, 8]]
269
+ end
270
+
271
+ it "should support registering custom types with :array_type option" do
272
+ Sequel::Postgres::PGArray.register('foo', :oid=>3, :array_type=>:blah)
273
+ @db.literal(Sequel::Postgres::PG_TYPES[3].call('{}')).should == 'ARRAY[]::blah[]'
274
+ end
275
+
276
+ it "should use and not override existing database typecast method if :typecast_method option is given" do
277
+ Sequel::Postgres::PGArray.register('foo', :typecast_method=>:float)
278
+ @db.fetch = [{:name=>'id', :db_type=>'foo[]'}]
279
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:float_array]
280
+ end
281
+
282
+ it "should set appropriate timestamp conversion procs when getting conversion procs" do
283
+ procs = @db.send(:get_conversion_procs, nil)
284
+ procs[1185].call('{"2011-10-20 11:12:13"}').should == [Time.local(2011, 10, 20, 11, 12, 13)]
285
+ procs[1115].call('{"2011-10-20 11:12:13"}').should == [Time.local(2011, 10, 20, 11, 12, 13)]
286
+ end
196
287
  end
@@ -5,7 +5,7 @@ describe "pg_auto_parameterize extension" do
5
5
  @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
6
6
  @db.synchronize{|c| def c.escape_bytea(v) v*2 end}
7
7
  @db.extend_datasets{def use_cursor(*) clone end}
8
- @db.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
8
+ @db.extension :pg_auto_parameterize
9
9
  end
10
10
 
11
11
  it "should automatically parameterize queries strings, blobs, numerics, dates, and times" do
@@ -20,7 +20,7 @@ describe "pg_auto_parameterize extension" do
20
20
  pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::int8)', 18446744073709551616)
21
21
  pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::double precision)', 1.1)
22
22
  pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::numeric)', BigDecimal.new('1.01'))
23
- pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::text)', "a")
23
+ pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1)', "a")
24
24
  pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::bytea)', "a\0b".to_sequel_blob)
25
25
  pr.call(@db[:table], 'SELECT * FROM table WHERE (a = 1)', '1'.lit, :nil)
26
26
  pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::time)', Sequel::SQLTime.create(1, 2, 3, 500000))
@@ -38,7 +38,7 @@ describe "pg_auto_parameterize extension" do
38
38
  @db.sqls.should == ['SELECT * FROM table WHERE (a = $1::int4) -- args: [1]']
39
39
 
40
40
  @db[:table].filter(:a=>1).update(:b=>'a').should == 1
41
- @db.sqls.should == ['UPDATE table SET b = $1::text WHERE (a = $2::int4) -- args: ["a", 1]']
41
+ @db.sqls.should == ['UPDATE table SET b = $1 WHERE (a = $2::int4) -- args: ["a", 1]']
42
42
 
43
43
  @db[:table].filter(:a=>1).delete.should == 1
44
44
  @db.sqls.should == ['DELETE FROM table WHERE (a = $1::int4) -- args: [1]']
@@ -6,6 +6,7 @@ describe "pg_hstore extension" do
6
6
  @db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
7
7
  @m = Sequel::Postgres
8
8
  @c = @m::HStore
9
+ @db.extension :pg_hstore
9
10
  end
10
11
 
11
12
  it "should parse hstore strings correctly" do
@@ -164,7 +165,6 @@ describe "pg_hstore extension" do
164
165
  end
165
166
 
166
167
  it "should support using hstores as bound variables" do
167
- @db.extend @c::DatabaseMethods
168
168
  @db.bound_variable_arg(1, nil).should == 1
169
169
  @db.bound_variable_arg({'1'=>'2'}, nil).should == '"1"=>"2"'
170
170
  @db.bound_variable_arg({'1'=>'2'}.hstore, nil).should == '"1"=>"2"'
@@ -175,13 +175,11 @@ describe "pg_hstore extension" do
175
175
  end
176
176
 
177
177
  it "should parse hstore type from the schema correctly" do
178
- @db.extend @c::DatabaseMethods
179
178
  @db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'hstore'}]
180
179
  @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :hstore]
181
180
  end
182
181
 
183
182
  it "should support typecasting for the hstore type" do
184
- @db.extend @c::DatabaseMethods
185
183
  h = {1=>2}.hstore
186
184
  @db.typecast_value(:hstore, h).should equal(h)
187
185
  @db.typecast_value(:hstore, '').should be_a_kind_of(@c)