sequel 3.36.1 → 3.37.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.
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)