sequel 3.46.0 → 3.47.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +96 -0
  3. data/Rakefile +7 -1
  4. data/bin/sequel +6 -4
  5. data/doc/active_record.rdoc +1 -1
  6. data/doc/advanced_associations.rdoc +14 -35
  7. data/doc/association_basics.rdoc +66 -4
  8. data/doc/migration.rdoc +4 -0
  9. data/doc/opening_databases.rdoc +6 -0
  10. data/doc/postgresql.rdoc +302 -0
  11. data/doc/release_notes/3.47.0.txt +270 -0
  12. data/doc/security.rdoc +6 -0
  13. data/lib/sequel/adapters/ibmdb.rb +9 -9
  14. data/lib/sequel/adapters/jdbc.rb +22 -7
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -2
  16. data/lib/sequel/adapters/mock.rb +2 -0
  17. data/lib/sequel/adapters/postgres.rb +44 -13
  18. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  20. data/lib/sequel/adapters/shared/postgres.rb +94 -55
  21. data/lib/sequel/adapters/shared/sqlite.rb +3 -1
  22. data/lib/sequel/adapters/sqlite.rb +2 -2
  23. data/lib/sequel/adapters/utils/pg_types.rb +1 -14
  24. data/lib/sequel/adapters/utils/split_alter_table.rb +3 -3
  25. data/lib/sequel/connection_pool/threaded.rb +1 -1
  26. data/lib/sequel/core.rb +1 -1
  27. data/lib/sequel/database/connecting.rb +2 -2
  28. data/lib/sequel/database/features.rb +5 -0
  29. data/lib/sequel/database/misc.rb +47 -5
  30. data/lib/sequel/database/query.rb +2 -2
  31. data/lib/sequel/dataset/actions.rb +4 -2
  32. data/lib/sequel/dataset/misc.rb +1 -1
  33. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  34. data/lib/sequel/dataset/query.rb +8 -6
  35. data/lib/sequel/dataset/sql.rb +8 -6
  36. data/lib/sequel/extensions/constraint_validations.rb +5 -2
  37. data/lib/sequel/extensions/migration.rb +10 -8
  38. data/lib/sequel/extensions/pagination.rb +3 -0
  39. data/lib/sequel/extensions/pg_array.rb +85 -25
  40. data/lib/sequel/extensions/pg_hstore.rb +8 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +4 -1
  42. data/lib/sequel/extensions/pg_inet.rb +16 -13
  43. data/lib/sequel/extensions/pg_interval.rb +6 -2
  44. data/lib/sequel/extensions/pg_json.rb +18 -11
  45. data/lib/sequel/extensions/pg_range.rb +17 -2
  46. data/lib/sequel/extensions/pg_range_ops.rb +7 -5
  47. data/lib/sequel/extensions/pg_row.rb +29 -12
  48. data/lib/sequel/extensions/pretty_table.rb +3 -0
  49. data/lib/sequel/extensions/query.rb +3 -0
  50. data/lib/sequel/extensions/schema_caching.rb +2 -0
  51. data/lib/sequel/extensions/schema_dumper.rb +3 -1
  52. data/lib/sequel/extensions/select_remove.rb +3 -0
  53. data/lib/sequel/model.rb +8 -2
  54. data/lib/sequel/model/associations.rb +39 -27
  55. data/lib/sequel/model/base.rb +99 -38
  56. data/lib/sequel/model/plugins.rb +25 -0
  57. data/lib/sequel/plugins/association_autoreloading.rb +27 -22
  58. data/lib/sequel/plugins/association_dependencies.rb +1 -7
  59. data/lib/sequel/plugins/auto_validations.rb +110 -0
  60. data/lib/sequel/plugins/boolean_readers.rb +1 -6
  61. data/lib/sequel/plugins/caching.rb +6 -13
  62. data/lib/sequel/plugins/class_table_inheritance.rb +1 -0
  63. data/lib/sequel/plugins/composition.rb +14 -7
  64. data/lib/sequel/plugins/constraint_validations.rb +2 -13
  65. data/lib/sequel/plugins/defaults_setter.rb +1 -6
  66. data/lib/sequel/plugins/dirty.rb +8 -0
  67. data/lib/sequel/plugins/error_splitter.rb +54 -0
  68. data/lib/sequel/plugins/force_encoding.rb +1 -5
  69. data/lib/sequel/plugins/hook_class_methods.rb +1 -6
  70. data/lib/sequel/plugins/input_transformer.rb +79 -0
  71. data/lib/sequel/plugins/instance_filters.rb +7 -1
  72. data/lib/sequel/plugins/instance_hooks.rb +7 -1
  73. data/lib/sequel/plugins/json_serializer.rb +5 -10
  74. data/lib/sequel/plugins/lazy_attributes.rb +20 -7
  75. data/lib/sequel/plugins/list.rb +1 -6
  76. data/lib/sequel/plugins/many_through_many.rb +1 -2
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +23 -39
  78. data/lib/sequel/plugins/optimistic_locking.rb +1 -5
  79. data/lib/sequel/plugins/pg_row.rb +4 -2
  80. data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -7
  81. data/lib/sequel/plugins/prepared_statements.rb +1 -5
  82. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -11
  83. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  84. data/lib/sequel/plugins/serialization.rb +11 -13
  85. data/lib/sequel/plugins/serialization_modification_detection.rb +13 -1
  86. data/lib/sequel/plugins/single_table_inheritance.rb +4 -4
  87. data/lib/sequel/plugins/static_cache.rb +67 -19
  88. data/lib/sequel/plugins/string_stripper.rb +7 -27
  89. data/lib/sequel/plugins/subclasses.rb +3 -5
  90. data/lib/sequel/plugins/tactical_eager_loading.rb +2 -2
  91. data/lib/sequel/plugins/timestamps.rb +2 -7
  92. data/lib/sequel/plugins/touch.rb +5 -8
  93. data/lib/sequel/plugins/tree.rb +1 -6
  94. data/lib/sequel/plugins/typecast_on_load.rb +1 -5
  95. data/lib/sequel/plugins/update_primary_key.rb +26 -14
  96. data/lib/sequel/plugins/validation_class_methods.rb +31 -16
  97. data/lib/sequel/plugins/validation_helpers.rb +50 -26
  98. data/lib/sequel/plugins/xml_serializer.rb +3 -6
  99. data/lib/sequel/sql.rb +1 -1
  100. data/lib/sequel/version.rb +1 -1
  101. data/spec/adapters/postgres_spec.rb +131 -15
  102. data/spec/adapters/sqlite_spec.rb +1 -1
  103. data/spec/core/connection_pool_spec.rb +16 -17
  104. data/spec/core/database_spec.rb +111 -40
  105. data/spec/core/dataset_spec.rb +65 -74
  106. data/spec/core/expression_filters_spec.rb +6 -5
  107. data/spec/core/object_graph_spec.rb +0 -1
  108. data/spec/core/schema_spec.rb +23 -23
  109. data/spec/core/spec_helper.rb +5 -1
  110. data/spec/extensions/association_dependencies_spec.rb +1 -1
  111. data/spec/extensions/association_proxies_spec.rb +1 -1
  112. data/spec/extensions/auto_validations_spec.rb +90 -0
  113. data/spec/extensions/caching_spec.rb +6 -0
  114. data/spec/extensions/class_table_inheritance_spec.rb +8 -1
  115. data/spec/extensions/composition_spec.rb +12 -5
  116. data/spec/extensions/constraint_validations_spec.rb +4 -4
  117. data/spec/extensions/core_refinements_spec.rb +29 -79
  118. data/spec/extensions/dirty_spec.rb +14 -0
  119. data/spec/extensions/error_splitter_spec.rb +18 -0
  120. data/spec/extensions/identity_map_spec.rb +0 -1
  121. data/spec/extensions/input_transformer_spec.rb +54 -0
  122. data/spec/extensions/instance_filters_spec.rb +6 -0
  123. data/spec/extensions/instance_hooks_spec.rb +12 -1
  124. data/spec/extensions/json_serializer_spec.rb +0 -1
  125. data/spec/extensions/lazy_attributes_spec.rb +64 -55
  126. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  127. data/spec/extensions/many_through_many_spec.rb +3 -4
  128. data/spec/extensions/many_to_one_pk_lookup_spec.rb +53 -15
  129. data/spec/extensions/migration_spec.rb +16 -0
  130. data/spec/extensions/null_dataset_spec.rb +1 -1
  131. data/spec/extensions/pg_array_spec.rb +48 -1
  132. data/spec/extensions/pg_hstore_ops_spec.rb +10 -2
  133. data/spec/extensions/pg_hstore_spec.rb +5 -0
  134. data/spec/extensions/pg_inet_spec.rb +5 -0
  135. data/spec/extensions/pg_interval_spec.rb +7 -3
  136. data/spec/extensions/pg_json_spec.rb +6 -1
  137. data/spec/extensions/pg_range_ops_spec.rb +4 -1
  138. data/spec/extensions/pg_range_spec.rb +5 -0
  139. data/spec/extensions/pg_row_plugin_spec.rb +13 -0
  140. data/spec/extensions/pg_row_spec.rb +28 -19
  141. data/spec/extensions/pg_typecast_on_load_spec.rb +6 -1
  142. data/spec/extensions/prepared_statements_associations_spec.rb +1 -1
  143. data/spec/extensions/query_literals_spec.rb +1 -1
  144. data/spec/extensions/rcte_tree_spec.rb +2 -2
  145. data/spec/extensions/schema_spec.rb +2 -2
  146. data/spec/extensions/serialization_modification_detection_spec.rb +8 -0
  147. data/spec/extensions/serialization_spec.rb +15 -1
  148. data/spec/extensions/sharding_spec.rb +1 -1
  149. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  150. data/spec/extensions/static_cache_spec.rb +59 -9
  151. data/spec/extensions/tactical_eager_loading_spec.rb +19 -4
  152. data/spec/extensions/update_primary_key_spec.rb +17 -1
  153. data/spec/extensions/validation_class_methods_spec.rb +25 -0
  154. data/spec/extensions/validation_helpers_spec.rb +59 -3
  155. data/spec/integration/associations_test.rb +5 -5
  156. data/spec/integration/eager_loader_test.rb +32 -63
  157. data/spec/integration/model_test.rb +2 -2
  158. data/spec/integration/plugin_test.rb +88 -56
  159. data/spec/integration/prepared_statement_test.rb +1 -1
  160. data/spec/integration/schema_test.rb +1 -1
  161. data/spec/integration/timezone_test.rb +0 -1
  162. data/spec/integration/transaction_test.rb +0 -1
  163. data/spec/model/association_reflection_spec.rb +1 -1
  164. data/spec/model/associations_spec.rb +106 -84
  165. data/spec/model/base_spec.rb +4 -4
  166. data/spec/model/eager_loading_spec.rb +8 -8
  167. data/spec/model/model_spec.rb +27 -9
  168. data/spec/model/plugins_spec.rb +71 -0
  169. data/spec/model/record_spec.rb +99 -13
  170. metadata +12 -2
@@ -31,6 +31,6 @@ describe "LooserTypecasting Extension" do
31
31
 
32
32
  specify "should not affect conversions of other types in decimal columns" do
33
33
  @c.new(:d=>1).d.should == 1
34
- @c.new(:d=>'a').d.should be_a_kind_of(BigDecimal)
34
+ @c.new(:d=>1).d.should be_a_kind_of(BigDecimal)
35
35
  end
36
36
  end
@@ -297,7 +297,7 @@ describe Sequel::Model, "many_through_many" do
297
297
  MODEL_DB.sqls.should == ['SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1234))']
298
298
  end
299
299
 
300
- it "should set cached instance variable when accessed" do
300
+ it "should populate cache when accessed" do
301
301
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
302
302
  n = @c1.load(:id => 1234)
303
303
  n.associations[:tags].should == nil
@@ -308,7 +308,7 @@ describe Sequel::Model, "many_through_many" do
308
308
  MODEL_DB.sqls.length.should == 0
309
309
  end
310
310
 
311
- it "should use cached instance variable if available" do
311
+ it "should use cache if available" do
312
312
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
313
313
  n = @c1.load(:id => 1234)
314
314
  n.associations[:tags] = []
@@ -316,7 +316,7 @@ describe Sequel::Model, "many_through_many" do
316
316
  MODEL_DB.sqls.should == []
317
317
  end
318
318
 
319
- it "should not use cached instance variable if asked to reload" do
319
+ it "should not use cache if asked to reload" do
320
320
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
321
321
  n = @c1.load(:id => 1234)
322
322
  n.associations[:tags] = []
@@ -354,7 +354,6 @@ describe Sequel::Model, "many_through_many" do
354
354
  end
355
355
 
356
356
  it "should support a :uniq option that removes duplicates from the association" do
357
- h = []
358
357
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :uniq=>true
359
358
  @c2.dataset._fetch = [{:id=>20}, {:id=>30}, {:id=>20}, {:id=>30}]
360
359
  @c1.load(:id=>10).tags.should == [@c2.load(:id=>20), @c2.load(:id=>30)]
@@ -2,7 +2,9 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Sequel::Plugins::ManyToOnePkLookup" do
4
4
  before do
5
- class ::LookupModel < ::Sequel::Model
5
+ @db = Sequel.mock
6
+
7
+ class ::LookupModel < ::Sequel::Model(@db)
6
8
  plugin :many_to_one_pk_lookup
7
9
  columns :id, :caching_model_id, :caching_model_id2
8
10
  many_to_one :caching_model
@@ -10,12 +12,10 @@ describe "Sequel::Plugins::ManyToOnePkLookup" do
10
12
  end
11
13
  @c = LookupModel
12
14
 
13
- class ::CachingModel < Sequel::Model
15
+ class ::CachingModel < Sequel::Model(@db)
14
16
  columns :id, :id2
15
17
  end
16
18
  @cc = CachingModel
17
-
18
- @db = MODEL_DB
19
19
  end
20
20
  after do
21
21
  Object.send(:remove_const, :CachingModel)
@@ -23,25 +23,25 @@ describe "Sequel::Plugins::ManyToOnePkLookup" do
23
23
  end
24
24
 
25
25
  shared_examples_for "many_to_one_pk_lookup with composite keys" do
26
- it "should use a simple primary key lookup when retrieving many_to_one associated records" do
26
+ it "should use a simple primary key lookup when retrieving many_to_one associated records with a composite key" do
27
27
  @db.sqls.should == []
28
28
  @c.load(:id=>3, :caching_model_id=>1, :caching_model_id2=>2).caching_model2.should equal(@cm12)
29
29
  @c.load(:id=>3, :caching_model_id=>2, :caching_model_id2=>1).caching_model2.should equal(@cm21)
30
30
  @db.sqls.should == []
31
- @c.load(:id=>4, :caching_model_id=>2, :caching_model_id2=>2).caching_model2
32
- @db.sqls.should_not == []
31
+ @cc.dataset._fetch = []
32
+ @c.load(:id=>4, :caching_model_id=>2, :caching_model_id2=>2).caching_model2.should == nil
33
33
  end
34
34
  end
35
35
 
36
36
  shared_examples_for "many_to_one_pk_lookup" do
37
- it "should use a simple primary key lookup when retrieving many_to_one associated records via a composite key" do
37
+ it "should use a simple primary key lookup when retrieving many_to_one associated records" do
38
38
  @cc.set_primary_key([:id, :id2])
39
39
  @db.sqls.should == []
40
40
  @c.load(:id=>3, :caching_model_id=>1).caching_model.should equal(@cm1)
41
41
  @c.load(:id=>4, :caching_model_id=>2).caching_model.should equal(@cm2)
42
42
  @db.sqls.should == []
43
- @c.load(:id=>4, :caching_model_id=>3).caching_model
44
- @db.sqls.should_not == []
43
+ @cc.dataset._fetch = []
44
+ @c.load(:id=>4, :caching_model_id=>3).caching_model.should == nil
45
45
  end
46
46
 
47
47
  it "should not use a simple primary key lookup if the assocation has a nil :key option" do
@@ -62,10 +62,41 @@ describe "Sequel::Plugins::ManyToOnePkLookup" do
62
62
  @db.sqls.should_not == []
63
63
  end
64
64
 
65
+ it "should not use a simple primary key lookup if the assocation has :conditions" do
66
+ @c.many_to_one :caching_model, :conditions=>{:a=>1}
67
+ @c.load(:id=>3, :caching_model_id=>1).caching_model
68
+ @db.sqls.should_not == []
69
+ end
70
+
71
+ it "should not use a simple primary key lookup if the assocation has :select" do
72
+ @c.many_to_one :caching_model, :select=>[:a, :b]
73
+ @c.load(:id=>3, :caching_model_id=>1).caching_model
74
+ @db.sqls.should_not == []
75
+ end
76
+
77
+ it "should not use a simple primary key lookup if the assocation has a block" do
78
+ @c.many_to_one(:caching_model){|ds| ds.where{a > 1}}
79
+ @c.load(:id=>3, :caching_model_id=>1).caching_model
80
+ @db.sqls.should_not == []
81
+ end
82
+
83
+ it "should not use a simple primary key lookup if the assocation has a non-default :dataset option" do
84
+ cc = @cc
85
+ @c.many_to_one :caching_model, :dataset=>proc{cc.where(:id=>caching_model_id)}
86
+ @c.load(:id=>3, :caching_model_id=>1).caching_model
87
+ @db.sqls.should_not == []
88
+ end
89
+
90
+ it "should use a simple primary key lookup if explicitly set" do
91
+ @c.many_to_one :caching_model, :select=>[:a, :b], :many_to_one_pk_lookup=>true
92
+ @c.load(:id=>3, :caching_model_id=>1).caching_model
93
+ @db.sqls.should == []
94
+ end
95
+
65
96
  it "should not use a simple primary key lookup if the prepared_statements_associations method is being used" do
66
- c2 = Class.new(Sequel::Model(:not_caching_model))
97
+ c2 = Class.new(Sequel::Model(@db[:not_caching_model]))
67
98
  c2.dataset._fetch = {:id=>1}
68
- c = Class.new(Sequel::Model(:lookup_model))
99
+ c = Class.new(Sequel::Model(@db[:lookup_model]))
69
100
  c.class_eval do
70
101
  plugin :prepared_statements_associations
71
102
  plugin :many_to_one_pk_lookup
@@ -106,7 +137,7 @@ describe "Sequel::Plugins::ManyToOnePkLookup" do
106
137
  @cm12 = @cc[1, 2]
107
138
  @cm21 = @cc[2, 1]
108
139
 
109
- @db.reset
140
+ @db.sqls
110
141
  end
111
142
 
112
143
  it_should_behave_like "many_to_one_pk_lookup"
@@ -119,10 +150,17 @@ describe "Sequel::Plugins::ManyToOnePkLookup" do
119
150
  @cc.plugin :static_cache
120
151
  @cm1 = @cc[1]
121
152
  @cm2 = @cc[2]
122
- @db.reset
153
+ @db.sqls
123
154
  end
124
155
 
125
156
  it_should_behave_like "many_to_one_pk_lookup"
157
+
158
+ it "should not issue regular query if primary key lookup returns no rows" do
159
+ def @cc.primary_key_lookup(pk); end
160
+ @c.many_to_one :caching_model
161
+ @c.load(:id=>3, :caching_model_id=>1).caching_model
162
+ @db.sqls.should == []
163
+ end
126
164
  end
127
165
 
128
166
  describe "With static_cache plugin with composite key" do
@@ -132,7 +170,7 @@ describe "Sequel::Plugins::ManyToOnePkLookup" do
132
170
  @cc.plugin :static_cache
133
171
  @cm12 = @cc[[1, 2]]
134
172
  @cm21 = @cc[[2, 1]]
135
- @db.reset
173
+ @db.sqls
136
174
  end
137
175
 
138
176
  it_should_behave_like "many_to_one_pk_lookup with composite keys"
@@ -259,6 +259,10 @@ describe "Sequel::IntegerMigrator" do
259
259
  specify "should raise and error if there is a missing integer migration version" do
260
260
  proc{Sequel::Migrator.apply(@db, "spec/files/missing_integer_migrations")}.should raise_error(Sequel::Migrator::Error)
261
261
  end
262
+
263
+ specify "should not raise and error if there is a missing integer migration version and allow_missing_migration_files is true" do
264
+ Sequel::Migrator.run(@db, "spec/files/missing_integer_migrations", :allow_missing_migration_files => true).should_not raise_error(Sequel::Migrator::Error)
265
+ end
262
266
 
263
267
  specify "should raise and error if there is a duplicate integer migration version" do
264
268
  proc{Sequel::Migrator.apply(@db, "spec/files/duplicate_integer_migrations")}.should raise_error(Sequel::Migrator::Error)
@@ -609,6 +613,18 @@ describe "Sequel::TimestampMigrator" do
609
613
  @db[:schema_migrations].select_order_map(:filename).should == %w'1273253849_create_sessions.rb 1273253851_create_nodes.rb 1273253853_3_create_users.rb'
610
614
  end
611
615
 
616
+ specify "should not raise error for applied migrations not in file system if :allow_missing_migration_files is true" do
617
+ @dir = 'spec/files/timestamped_migrations'
618
+ @m.apply(@db, @dir)
619
+ [:schema_migrations, :sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).should be_true}
620
+ @db[:schema_migrations].select_order_map(:filename).should == %w'1273253849_create_sessions.rb 1273253851_create_nodes.rb 1273253853_3_create_users.rb'
621
+
622
+ @dir = 'spec/files/missing_timestamped_migrations'
623
+ proc{@m.run(@db, @dir, :allow_missing_migration_files => true)}.should_not raise_error(Sequel::Migrator::Error)
624
+ [:schema_migrations, :sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).should be_true}
625
+ @db[:schema_migrations].select_order_map(:filename).should == %w'1273253849_create_sessions.rb 1273253851_create_nodes.rb 1273253853_3_create_users.rb'
626
+ end
627
+
612
628
  specify "should raise error missing column name in existing schema_migrations table" do
613
629
  @dir = 'spec/files/timestamped_migrations'
614
630
  @m.apply(@db, @dir)
@@ -71,7 +71,7 @@ describe "null_dataset extension" do
71
71
  @i.should == 1
72
72
  end
73
73
 
74
- it "should have null! method modify receiver" do
74
+ it "should have nullify! method modify receiver" do
75
75
  ds = @db[:table]
76
76
  ds.nullify!.should equal(ds)
77
77
  ds.each(&@pr)
@@ -14,6 +14,7 @@ describe "pg_array extension" do
14
14
  @db.extension(:pg_array)
15
15
  @m = Sequel::Postgres
16
16
  @converter = @m::PG_TYPES
17
+ @db.sqls
17
18
  end
18
19
 
19
20
  it "should parse single dimensional text arrays" do
@@ -300,7 +301,7 @@ describe "pg_array extension" do
300
301
  Sequel::Postgres::PG_TYPES[2].should be_a_kind_of(Sequel::Postgres::PGArray::JSONCreator)
301
302
  end
302
303
 
303
- it "should support registering converters with :parser=>:json option" do
304
+ it "should support registering converters with :parser=>:json option and blocks" do
304
305
  Sequel::Postgres::PGArray.register('foo', :oid=>4, :parser=>:json){|s| s * 2}
305
306
  Sequel::Postgres::PG_TYPES[4].call('{{1, 2}, {3, 4}}').should == [[2, 4], [6, 8]]
306
307
  end
@@ -316,9 +317,55 @@ describe "pg_array extension" do
316
317
  @db.schema(:items).map{|e| e[1][:type]}.should == [:float_array]
317
318
  end
318
319
 
320
+ it "should support registering custom array types on a per-Database basis" do
321
+ @db.register_array_type('banana', :oid=>7865){|s| s}
322
+ @db.typecast_value(:banana_array, []).should be_a_kind_of(Sequel::Postgres::PGArray)
323
+ @db.fetch = [{:name=>'id', :db_type=>'banana[]'}]
324
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:banana_array]
325
+ @db.conversion_procs.should have_key(7865)
326
+ @db.respond_to?(:typecast_value_banana_array, true).should be_true
327
+
328
+ db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
329
+ db.extend_datasets(Module.new{def supports_timestamp_timezones?; false; end; def supports_timestamp_usecs?; false; end})
330
+ db.extension(:pg_array)
331
+ db.fetch = [{:name=>'id', :db_type=>'banana[]'}]
332
+ db.schema(:items).map{|e| e[1][:type]}.should == [nil]
333
+ db.conversion_procs.should_not have_key(7865)
334
+ db.respond_to?(:typecast_value_banana_array, true).should be_false
335
+ end
336
+
337
+ it "should automatically look up the array and scalar oids when registering per-Database types" do
338
+ @db.fetch = [[{:oid=>21, :typarray=>7866}], [{:name=>'id', :db_type=>'banana[]'}]]
339
+ @db.register_array_type('banana', :scalar_typecast=>:integer)
340
+ @db.sqls.should == ["SELECT typarray, oid FROM pg_type WHERE (typname = 'banana') LIMIT 1"]
341
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:banana_array]
342
+ @db.conversion_procs[7866].call("{1,2}").should == [1,2]
343
+ @db.typecast_value(:banana_array, %w'1 2').should == [1,2]
344
+ end
345
+
346
+ it "should not automatically look up oids if given both scalar and array oids" do
347
+ @db.register_array_type('banana', :oid=>7866, :scalar_oid=>21, :scalar_typecast=>:integer)
348
+ @db.sqls.should == []
349
+ @db.conversion_procs[7866].call("{1,2}").should == [1,2]
350
+ @db.typecast_value(:banana_array, %w'1 2').should == [1,2]
351
+ end
352
+
353
+ it "should not automatically look up oids if given array oid and block" do
354
+ @db.register_array_type('banana', :oid=>7866, :scalar_typecast=>:integer){|s| s.to_i}
355
+ @db.sqls.should == []
356
+ @db.conversion_procs[7866].call("{1,2}").should == [1,2]
357
+ @db.typecast_value(:banana_array, %w'1 2').should == [1,2]
358
+ end
359
+
319
360
  it "should set appropriate timestamp conversion procs when getting conversion procs" do
320
361
  procs = @db.conversion_procs
321
362
  procs[1185].call('{"2011-10-20 11:12:13"}').should == [Time.local(2011, 10, 20, 11, 12, 13)]
322
363
  procs[1115].call('{"2011-10-20 11:12:13"}').should == [Time.local(2011, 10, 20, 11, 12, 13)]
323
364
  end
365
+
366
+ it "should return correct results for Database#schema_type_class" do
367
+ @db.register_array_type('banana', :oid=>7866, :scalar_typecast=>:integer){|s| s.to_i}
368
+ @db.schema_type_class(:banana_array).should == Sequel::Postgres::PGArray
369
+ @db.schema_type_class(:integer).should == Integer
370
+ end
324
371
  end
@@ -7,11 +7,19 @@ describe "Sequel::Postgres::HStoreOp" do
7
7
  end
8
8
 
9
9
  it "#- should use the - operator" do
10
- @ds.literal(@h - 'a').should == "(h - 'a')"
10
+ @ds.literal(@h - :a).should == "(h - a)"
11
+ end
12
+
13
+ it "#- should cast String argument to text when using - operator" do
14
+ @ds.literal(@h - 'a').should == "(h - CAST('a' AS text))"
15
+ end
16
+
17
+ it "#- should not cast LiteralString argument to text when using - operator" do
18
+ @ds.literal(@h - Sequel.lit('a')).should == "(h - a)"
11
19
  end
12
20
 
13
21
  it "#- should return an HStoreOp" do
14
- @ds.literal((@h - 'a')['a']).should == "((h - 'a') -> 'a')"
22
+ @ds.literal((@h - :a)['a']).should == "((h - a) -> 'a')"
15
23
  end
16
24
 
17
25
  it "#[] should use the -> operator" do
@@ -197,4 +197,9 @@ describe "pg_hstore extension" do
197
197
  dump = Marshal.dump(v)
198
198
  Marshal.load(dump).should == v
199
199
  end
200
+
201
+ it "should return correct results for Database#schema_type_class" do
202
+ @db.schema_type_class(:hstore).should == Sequel::Postgres::HStore
203
+ @db.schema_type_class(:integer).should == Integer
204
+ end
200
205
  end
@@ -44,4 +44,9 @@ describe "pg_inet extension" do
44
44
  proc{@db.typecast_value(:ipaddr, '')}.should raise_error(Sequel::InvalidValue)
45
45
  proc{@db.typecast_value(:ipaddr, 1)}.should raise_error(Sequel::InvalidValue)
46
46
  end
47
+
48
+ it "should return correct results for Database#schema_type_class" do
49
+ @db.schema_type_class(:ipaddr).should == IPAddr
50
+ @db.schema_type_class(:integer).should == Integer
51
+ end
47
52
  end
@@ -2,8 +2,8 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  begin
4
4
  require 'active_support/duration'
5
- rescue LoadError => e
6
- skip_warn "pg_interval plugin: can't load active_support/duration (#{e.class}: #{e})"
5
+ rescue LoadError => exc
6
+ skip_warn "pg_interval plugin: can't load active_support/duration (#{exc.class}: #{exc})"
7
7
  else
8
8
  describe "pg_interval extension" do
9
9
  before do
@@ -46,7 +46,6 @@ describe "pg_interval extension" do
46
46
  end
47
47
 
48
48
  it "should support typecasting for the interval type" do
49
- ip = IPAddr.new('127.0.0.1')
50
49
  d = ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
51
50
  @db.typecast_value(:interval, d).object_id.should == d.object_id
52
51
 
@@ -68,5 +67,10 @@ describe "pg_interval extension" do
68
67
  proc{@db.typecast_value(:interval, 'foo')}.should raise_error(Sequel::InvalidValue)
69
68
  proc{@db.typecast_value(:interval, Object.new)}.should raise_error(Sequel::InvalidValue)
70
69
  end
70
+
71
+ it "should return correct results for Database#schema_type_class" do
72
+ @db.schema_type_class(:interval).should == ActiveSupport::Duration
73
+ @db.schema_type_class(:integer).should == Integer
74
+ end
71
75
  end
72
76
  end
@@ -55,7 +55,7 @@ describe "pg_json extension" do
55
55
  end
56
56
  end
57
57
 
58
- it "should literalize HStores to strings correctly" do
58
+ it "should literalize JSONHash and JSONArray to strings correctly" do
59
59
  @db.literal(Sequel.pg_json([])).should == "'[]'::json"
60
60
  @db.literal(Sequel.pg_json([1, [2], {'a'=>'b'}])).should == "'[1,[2],{\"a\":\"b\"}]'::json"
61
61
  @db.literal(Sequel.pg_json({})).should == "'{}'::json"
@@ -110,4 +110,9 @@ describe "pg_json extension" do
110
110
  proc{@db.typecast_value(:json, '')}.should raise_error(Sequel::InvalidValue)
111
111
  proc{@db.typecast_value(:json, 1)}.should raise_error(Sequel::InvalidValue)
112
112
  end
113
+
114
+ it "should return correct results for Database#schema_type_class" do
115
+ @db.schema_type_class(:json).should == [Sequel::Postgres::JSONHash, Sequel::Postgres::JSONArray]
116
+ @db.schema_type_class(:integer).should == Integer
117
+ end
113
118
  end
@@ -33,9 +33,12 @@ describe "Sequel::Postgres::RangeOp" do
33
33
  @ds.literal(@h.overlaps(@h)).should == "(h && h)"
34
34
  @ds.literal(@h.left_of(@h)).should == "(h << h)"
35
35
  @ds.literal(@h.right_of(@h)).should == "(h >> h)"
36
+ @ds.literal(@h.ends_before(@h)).should == "(h &< h)"
37
+ @ds.literal(@h.starts_after(@h)).should == "(h &> h)"
38
+ @ds.literal(@h.adjacent_to(@h)).should == "(h -|- h)"
39
+
36
40
  @ds.literal(@h.starts_before(@h)).should == "(h &< h)"
37
41
  @ds.literal(@h.ends_after(@h)).should == "(h &> h)"
38
- @ds.literal(@h.adjacent_to(@h)).should == "(h -|- h)"
39
42
  end
40
43
 
41
44
  it "should define methods for all of the the PostgreSQL range functions" do
@@ -163,6 +163,11 @@ describe "pg_range extension" do
163
163
  Sequel::Postgres::PG_TYPES[331].call('[1,3)').should be_a_kind_of(@R)
164
164
  end
165
165
 
166
+ it "should return correct results for Database#schema_type_class" do
167
+ @db.schema_type_class(:int4range).should == Sequel::Postgres::PGRange
168
+ @db.schema_type_class(:integer).should == Integer
169
+ end
170
+
166
171
  describe "parser" do
167
172
  before do
168
173
  @p = Sequel::Postgres::PG_TYPES[3904]
@@ -15,6 +15,14 @@ describe "Sequel::Plugins::PgRow" do
15
15
  @c2.columns :address
16
16
  @c2.db_schema[:address].merge!(:type=>:pg_row_address)
17
17
  end
18
+ after do
19
+ @c.dataset.opts[:from] = [:address]
20
+ end
21
+
22
+ it "should have schema_type_class include Sequel::Model" do
23
+ @c2.new.send(:schema_type_class, :address).should == @c
24
+ @db.conversion_procs[1098].call('(123 Foo St,Bar City)').should == @c.load(:street=>'123 Foo St', :city=>'Bar City')
25
+ end
18
26
 
19
27
  it "should set up a parser for the type that creates a model class" do
20
28
  @db.conversion_procs[1098].call('(123 Foo St,Bar City)').should == @c.load(:street=>'123 Foo St', :city=>'Bar City')
@@ -33,6 +41,11 @@ describe "Sequel::Plugins::PgRow" do
33
41
  @db.literal(@c.load(:street=>'123 Foo St', :city=>'Bar City')).should == "ROW('123 Foo St', 'Bar City')::address"
34
42
  end
35
43
 
44
+ it "should handle literalizing model instances when model table is aliased" do
45
+ @c.dataset.opts[:from] = [Sequel.as(:address, :a)]
46
+ @db.literal(@c.load(:street=>'123 Foo St', :city=>'Bar City')).should == "ROW('123 Foo St', 'Bar City')::address"
47
+ end
48
+
36
49
  it "should handle model instances in bound variables" do
37
50
  @db.bound_variable_arg(1, nil).should == 1
38
51
  @db.bound_variable_arg(@c.load(:street=>'123 Foo St', :city=>'Bar City'), nil).should == '("123 Foo St","Bar City")'
@@ -137,13 +137,13 @@ describe "pg_row extension" do
137
137
  it "should reload registered row types when reseting conversion procs" do
138
138
  db = Sequel.mock(:host=>'postgres')
139
139
  db.extension(:pg_row)
140
- db.conversion_procs[4] = p4 = proc{|s| s.to_i}
141
- db.conversion_procs[5] = p5 = proc{|s| s * 2}
140
+ db.conversion_procs[4] = proc{|s| s.to_i}
141
+ db.conversion_procs[5] = proc{|s| s * 2}
142
142
  db.sqls
143
143
  db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
144
144
  db.register_row_type(:foo)
145
145
  db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
146
- "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
146
+ "SELECT attname, (CASE pg_type.typbasetype WHEN 0 THEN atttypid ELSE pg_type.typbasetype END) AS atttypid FROM pg_attribute INNER JOIN pg_type ON (pg_type.oid = pg_attribute.atttypid) WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
147
147
 
148
148
  begin
149
149
  pgnt = Sequel::Postgres::PG_NAMED_TYPES.dup
@@ -151,7 +151,7 @@ describe "pg_row extension" do
151
151
  db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
152
152
  db.reset_conversion_procs
153
153
  db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
154
- "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
154
+ "SELECT attname, (CASE pg_type.typbasetype WHEN 0 THEN atttypid ELSE pg_type.typbasetype END) AS atttypid FROM pg_attribute INNER JOIN pg_type ON (pg_type.oid = pg_attribute.atttypid) WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
155
155
  ensure
156
156
  Sequel::Postgres::PG_NAMED_TYPES.replace pgnt
157
157
  end
@@ -182,7 +182,7 @@ describe "pg_row extension" do
182
182
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
183
183
  @db.register_row_type(:foo)
184
184
  @db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
185
- "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
185
+ "SELECT attname, (CASE pg_type.typbasetype WHEN 0 THEN atttypid ELSE pg_type.typbasetype END) AS atttypid FROM pg_attribute INNER JOIN pg_type ON (pg_type.oid = pg_attribute.atttypid) WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
186
186
  p1 = @db.conversion_procs[1]
187
187
  p1.columns.should == [:bar, :baz]
188
188
  p1.column_oids.should == [4, 5]
@@ -210,7 +210,7 @@ describe "pg_row extension" do
210
210
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
211
211
  @db.register_row_type(:foo__bar)
212
212
  @db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type INNER JOIN pg_namespace ON ((pg_namespace.oid = pg_type.typnamespace) AND (pg_namespace.nspname = 'foo')) WHERE ((typtype = 'c') AND (typname = 'bar')) LIMIT 1",
213
- "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
213
+ "SELECT attname, (CASE pg_type.typbasetype WHEN 0 THEN atttypid ELSE pg_type.typbasetype END) AS atttypid FROM pg_attribute INNER JOIN pg_type ON (pg_type.oid = pg_attribute.atttypid) WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
214
214
  p1 = @db.conversion_procs[1]
215
215
  p1.columns.should == [:bar, :baz]
216
216
  p1.column_oids.should == [4, 5]
@@ -230,8 +230,8 @@ describe "pg_row extension" do
230
230
  end
231
231
 
232
232
  it "should allow registering with a custom converter" do
233
- @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
234
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
233
+ @db.conversion_procs[4] = proc{|s| s.to_i}
234
+ @db.conversion_procs[5] = proc{|s| s * 2}
235
235
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
236
236
  c = proc{|h| [h]}
237
237
  @db.register_row_type(:foo, :converter=>c)
@@ -241,8 +241,8 @@ describe "pg_row extension" do
241
241
  end
242
242
 
243
243
  it "should allow registering with a custom typecaster" do
244
- @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
245
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
244
+ @db.conversion_procs[4] = proc{|s| s.to_i}
245
+ @db.conversion_procs[5] = proc{|s| s * 2}
246
246
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
247
247
  @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
248
248
  @db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(Hash)
@@ -257,16 +257,16 @@ describe "pg_row extension" do
257
257
  end
258
258
 
259
259
  it "should handle conversion procs that aren't added until later" do
260
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
260
+ @db.conversion_procs[5] = proc{|s| s * 2}
261
261
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
262
262
  c = proc{|h| [h]}
263
263
  @db.register_row_type(:foo, :converter=>c)
264
- @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
264
+ @db.conversion_procs[4] = proc{|s| s.to_i}
265
265
  @db.conversion_procs[1].call('(1,b)').should == [{:bar=>1, :baz=>'bb'}]
266
266
  end
267
267
 
268
268
  it "should handle nil values when converting columns" do
269
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
269
+ @db.conversion_procs[5] = proc{|s| s * 2}
270
270
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}]]
271
271
  called = false
272
272
  @db.conversion_procs[4] = proc{|s| called = true; s}
@@ -276,8 +276,8 @@ describe "pg_row extension" do
276
276
  end
277
277
 
278
278
  it "should registering array type for row type if type has an array oid" do
279
- @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
280
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
279
+ @db.conversion_procs[4] = proc{|s| s.to_i}
280
+ @db.conversion_procs[5] = proc{|s| s * 2}
281
281
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
282
282
  @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
283
283
  p3 = @db.conversion_procs[3]
@@ -292,8 +292,8 @@ describe "pg_row extension" do
292
292
  end
293
293
 
294
294
  it "should allow typecasting of registered row types via Database#row_type" do
295
- @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
296
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
295
+ @db.conversion_procs[4] = proc{|s| s.to_i}
296
+ @db.conversion_procs[5] = proc{|s| s * 2}
297
297
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
298
298
  @db.register_row_type(:foo, :typecaster=>proc{|h| @m::HashRow.subclass(:foo, [:bar, :baz]).new({:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2})})
299
299
  @db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
@@ -301,8 +301,8 @@ describe "pg_row extension" do
301
301
  end
302
302
 
303
303
  it "should allow parsing when typecasting registered row types via Database#row_type" do
304
- @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
305
- @db.conversion_procs[5] = p5 = proc{|s| s * 2}
304
+ @db.conversion_procs[4] = proc{|s| s.to_i}
305
+ @db.conversion_procs[5] = proc{|s| s * 2}
306
306
  @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
307
307
  @db.register_row_type(:foo, :typecaster=>proc{|h| @m::HashRow.subclass(:foo, [:bar, :baz]).new(:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2)})
308
308
  @db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
@@ -348,4 +348,13 @@ describe "pg_row extension" do
348
348
  @db.fetch = []
349
349
  proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
350
350
  end
351
+
352
+ it "should return correct results for Database#schema_type_class" do
353
+ @db.conversion_procs[4] = proc{|s| s.to_i}
354
+ @db.conversion_procs[5] = proc{|s| s * 2}
355
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
356
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
357
+ @db.schema_type_class(:pg_row_foo).should == [Sequel::Postgres::PGRow::HashRow, Sequel::Postgres::PGRow::ArrayRow]
358
+ @db.schema_type_class(:integer).should == Integer
359
+ end
351
360
  end