sequel 3.30.0 → 3.31.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 (45) hide show
  1. data/CHANGELOG +40 -0
  2. data/Rakefile +12 -2
  3. data/doc/association_basics.rdoc +28 -0
  4. data/doc/dataset_filtering.rdoc +8 -0
  5. data/doc/opening_databases.rdoc +1 -0
  6. data/doc/release_notes/3.31.0.txt +146 -0
  7. data/lib/sequel/adapters/jdbc.rb +7 -6
  8. data/lib/sequel/adapters/jdbc/derby.rb +5 -0
  9. data/lib/sequel/adapters/jdbc/h2.rb +6 -1
  10. data/lib/sequel/adapters/mock.rb +21 -2
  11. data/lib/sequel/adapters/shared/db2.rb +10 -0
  12. data/lib/sequel/adapters/shared/mssql.rb +40 -5
  13. data/lib/sequel/adapters/shared/mysql.rb +19 -2
  14. data/lib/sequel/adapters/shared/oracle.rb +13 -1
  15. data/lib/sequel/adapters/shared/postgres.rb +52 -8
  16. data/lib/sequel/adapters/shared/sqlite.rb +4 -3
  17. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  18. data/lib/sequel/database/schema_generator.rb +9 -2
  19. data/lib/sequel/dataset/actions.rb +37 -19
  20. data/lib/sequel/dataset/features.rb +10 -0
  21. data/lib/sequel/dataset/prepared_statements.rb +0 -10
  22. data/lib/sequel/dataset/query.rb +13 -1
  23. data/lib/sequel/dataset/sql.rb +6 -1
  24. data/lib/sequel/model/associations.rb +14 -4
  25. data/lib/sequel/model/base.rb +10 -0
  26. data/lib/sequel/plugins/serialization.rb +82 -43
  27. data/lib/sequel/version.rb +1 -1
  28. data/spec/adapters/mssql_spec.rb +46 -0
  29. data/spec/adapters/mysql_spec.rb +3 -0
  30. data/spec/adapters/postgres_spec.rb +61 -24
  31. data/spec/core/database_spec.rb +31 -18
  32. data/spec/core/dataset_spec.rb +90 -13
  33. data/spec/core/mock_adapter_spec.rb +37 -0
  34. data/spec/extensions/instance_filters_spec.rb +1 -0
  35. data/spec/extensions/nested_attributes_spec.rb +1 -1
  36. data/spec/extensions/serialization_spec.rb +49 -5
  37. data/spec/extensions/sharding_spec.rb +1 -1
  38. data/spec/integration/associations_test.rb +15 -0
  39. data/spec/integration/dataset_test.rb +71 -0
  40. data/spec/integration/prepared_statement_test.rb +8 -0
  41. data/spec/model/association_reflection_spec.rb +27 -0
  42. data/spec/model/associations_spec.rb +18 -3
  43. data/spec/model/base_spec.rb +20 -0
  44. data/spec/model/eager_loading_spec.rb +21 -0
  45. metadata +4 -2
@@ -20,7 +20,7 @@ module Sequel
20
20
  # DB.select(1).where(DB[:items].exists)
21
21
  # # SELECT 1 WHERE (EXISTS (SELECT * FROM items))
22
22
  def exists
23
- LiteralString.new("EXISTS (#{select_sql})")
23
+ SQL::PlaceholderLiteralString.new("EXISTS ?", [self], true)
24
24
  end
25
25
 
26
26
  # Returns an INSERT SQL query string. See +insert+.
@@ -1196,7 +1196,12 @@ module Sequel
1196
1196
  def select_group_sql(sql)
1197
1197
  if group = @opts[:group]
1198
1198
  sql << GROUP_BY
1199
+ if go = @opts[:group_options]
1200
+ sql << go.to_s.upcase
1201
+ sql << PAREN_OPEN
1202
+ end
1199
1203
  expression_list_append(sql, group)
1204
+ sql << PAREN_CLOSE if go
1200
1205
  end
1201
1206
  end
1202
1207
 
@@ -761,6 +761,10 @@ module Sequel
761
761
  # :key :: foreign key in current model's table that references
762
762
  # associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
763
763
  # array of symbols for a composite key association.
764
+ # :key_column :: Similar to, and usually identical to, :key, but :key refers to the model method
765
+ # to call, where :key_column refers to the underlying column. Should only be
766
+ # used if the association has the same name as the foreign key column, in conjunction
767
+ # with defining a model alias method for the key column.
764
768
  # :primary_key :: column in the associated table that :key option references, as a symbol.
765
769
  # Defaults to the primary key of the associated table. Can use an
766
770
  # array of symbols for a composite key association.
@@ -1121,8 +1125,13 @@ module Sequel
1121
1125
  name = opts[:name]
1122
1126
  model = self
1123
1127
  opts[:key] = opts.default_key unless opts.include?(:key)
1124
- key = opts[:key]
1125
- cks = opts[:keys] = Array(opts[:key])
1128
+ key_column = key = opts[:key]
1129
+ cks = opts[:graph_keys] = opts[:keys] = Array(key)
1130
+ if opts[:key_column]
1131
+ key_column = opts[:key_column]
1132
+ opts[:eager_loader_key] ||= key_column
1133
+ opts[:graph_keys] = Array(key_column)
1134
+ end
1126
1135
  opts[:qualified_key] = opts.qualify_cur(key)
1127
1136
  if opts[:primary_key]
1128
1137
  cpks = Array(opts[:primary_key])
@@ -1136,7 +1145,7 @@ module Sequel
1136
1145
  klass.filter(Array(opts.qualified_primary_key).zip(cks.map{|k| send(k)}))
1137
1146
  end
1138
1147
  opts[:eager_loader] ||= proc do |eo|
1139
- h = eo[:key_hash][key]
1148
+ h = eo[:key_hash][key_column]
1140
1149
  keys = h.keys
1141
1150
  # Default the cached association to nil, so any object that doesn't have it
1142
1151
  # populated will have cached the negative lookup.
@@ -1158,9 +1167,10 @@ module Sequel
1158
1167
  only_conditions = opts[:graph_only_conditions]
1159
1168
  conditions = opts[:graph_conditions]
1160
1169
  graph_block = opts[:graph_block]
1170
+ graph_cks = opts[:graph_keys]
1161
1171
  opts[:eager_grapher] ||= proc do |eo|
1162
1172
  ds = eo[:self]
1163
- ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(cks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1173
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1164
1174
  end
1165
1175
 
1166
1176
  def_association_dataset_methods(opts)
@@ -234,6 +234,16 @@ module Sequel
234
234
  @db_schema ||= get_db_schema
235
235
  end
236
236
 
237
+ # Create a column alias, where the column methods have one name, but the underlying storage uses a
238
+ # different name.
239
+ def def_column_alias(meth, column)
240
+ clear_setter_methods_cache
241
+ overridable_methods_module.module_eval do
242
+ define_method(meth){self[column]}
243
+ define_method("#{meth}="){|v| self[column] = v}
244
+ end
245
+ end
246
+
237
247
  # If a block is given, define a method on the dataset (if the model currently has an dataset) with the given argument name using
238
248
  # the given block. Also define a class method on the model that calls the
239
249
  # dataset method. Stores the method name and block so that it can be reapplied if the model's
@@ -11,8 +11,17 @@ module Sequel
11
11
  # set the @deserialized_values entry. This plugin adds a before_save hook
12
12
  # that serializes all @deserialized_values to @values.
13
13
  #
14
- # You can use either marshal, yaml, or json as the serialization format.
15
- # If you use yaml or json, you should require them by yourself.
14
+ # You can specify the serialization format as a pair of serializer/deserializer
15
+ # callable objects. You can also specify the serialization format as a single
16
+ # symbol, if such a symbol has a registered serializer/deserializer pair in the
17
+ # plugin. By default, the plugin registers the :marshal, :yaml, and :json
18
+ # serialization formats. To register your own serialization formats, use
19
+ # Sequel::Plugins::Serialization.register_format.
20
+ # If you use yaml or json format, you need to require the libraries, Sequel
21
+ # does not do the requiring for you.
22
+ #
23
+ # You can specify the columns to serialize when loading the plugin, or later
24
+ # using the serialize_attributes class method.
16
25
  #
17
26
  # Because of how this plugin works, it must be used inside each model class
18
27
  # that needs serialization, after any set_dataset method calls in that class.
@@ -22,56 +31,96 @@ module Sequel
22
31
  # == Example
23
32
  #
24
33
  # require 'sequel'
34
+ # # Require json, as the plugin doesn't require it for you.
25
35
  # require 'json'
36
+ #
37
+ # # Register custom serializer/deserializer pair
38
+ # Sequel::Plugins::Serialization.register_format(:reverse,
39
+ # lambda{|v| v.reverse},
40
+ # lambda{|v| v.reverse})
41
+ #
26
42
  # class User < Sequel::Model
43
+ # # Built-in format support when loading the plugin
27
44
  # plugin :serialization, :json, :permissions
28
- # # or
45
+ #
46
+ # # Built-in format support after loading the plugin using serialize_attributes
29
47
  # plugin :serialization
30
- # serialize_attributes :marshal, :permissions, :attributes
48
+ # serialize_attributes :marshal, :permissions
49
+ #
50
+ # # Use custom registered serialization format just like built-in format
51
+ # serialize_attributes :reverse, :password
52
+ #
53
+ # # Use a custom serializer/deserializer pair without registering
54
+ # serialize_attributes [lambda{|v| v.reverse}, lambda{|v| v.reverse}], :password
31
55
  # end
32
56
  # user = User.create
33
57
  # user.permissions = { :global => 'read-only' }
34
58
  # user.save
35
59
  module Serialization
60
+ # The default serializers supported by the serialization module.
61
+ # Use register_format to add serializers to this hash.
62
+ REGISTERED_FORMATS = {}
63
+
36
64
  # Set up the column readers to do deserialization and the column writers
37
65
  # to save the value in deserialized_values.
38
66
  def self.apply(model, *args)
39
- model.instance_eval{@serialization_map = {}}
67
+ model.instance_eval do
68
+ @deserialization_map = {}
69
+ @serialization_map = {}
70
+ end
40
71
  end
41
72
 
73
+ # Automatically call serialize_attributes with the format and columns unless
74
+ # no columns were provided.
42
75
  def self.configure(model, format=nil, *columns)
43
76
  model.serialize_attributes(format, *columns) unless columns.empty?
44
77
  end
45
78
 
79
+ # Register a serializer/deserializer pair with a format symbol, to allow
80
+ # models to pick this format by name. Both serializer and deserializer
81
+ # should be callable objects.
82
+ def self.register_format(format, serializer, deserializer)
83
+ REGISTERED_FORMATS[format] = [serializer, deserializer]
84
+ end
85
+ register_format(:marshal, lambda{|v| [Marshal.dump(v)].pack('m')}, lambda{|v| Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)})
86
+ register_format(:yaml, lambda{|v| v.to_yaml}, lambda{|v| YAML.load(v)})
87
+ register_format(:json, lambda{|v| v.to_json}, lambda{|v| JSON.parse(v)})
88
+
46
89
  module ClassMethods
47
- # A map of the serialized columns for this model. Keys are column
48
- # symbols, values are serialization formats (:marshal, :yaml, or :json).
90
+ # A hash with column name symbols and callable values, with the value
91
+ # called to deserialize the column.
92
+ attr_reader :deserialization_map
93
+
94
+ # A hash with column name symbols and callable values, with the value
95
+ # called to serialize the column.
49
96
  attr_reader :serialization_map
50
97
 
51
98
  # Module to store the serialized column accessor methods, so they can
52
99
  # call be overridden and call super to get the serialization behavior
53
100
  attr_accessor :serialization_module
54
101
 
55
- # Copy the serialization format and columns to serialize into the subclass.
102
+ # Copy the serialization_map and deserialization map into the subclass.
56
103
  def inherited(subclass)
57
104
  super
58
105
  sm = serialization_map.dup
59
- subclass.instance_eval{@serialization_map = sm}
60
- end
61
-
62
- # The first value in the serialization map. This is only for
63
- # backwards compatibility, use serialization_map in new code.
64
- def serialization_format
65
- serialization_map.values.first
106
+ dsm = deserialization_map.dup
107
+ subclass.instance_eval do
108
+ @deserialization_map = dsm
109
+ @serialization_map = sm
110
+ end
66
111
  end
67
112
 
68
113
  # Create instance level reader that deserializes column values on request,
69
- # and instance level writer that stores new deserialized value in deserialized
70
- # columns
114
+ # and instance level writer that stores new deserialized values.
71
115
  def serialize_attributes(format, *columns)
72
- raise(Error, "Unsupported serialization format (#{format}), should be :marshal, :yaml, or :json") unless [:marshal, :yaml, :json].include?(format)
116
+ if format.is_a?(Symbol)
117
+ unless format = REGISTERED_FORMATS[format]
118
+ raise(Error, "Unsupported serialization format: #{format} (valid formats: #{REGISTERED_FORMATS.keys.map{|k| k.inspect}.join})")
119
+ end
120
+ end
121
+ serializer, deserializer = format
73
122
  raise(Error, "No columns given. The serialization plugin requires you specify which columns to serialize") if columns.empty?
74
- define_serialized_attribute_accessor(format, *columns)
123
+ define_serialized_attribute_accessor(serializer, deserializer, *columns)
75
124
  end
76
125
 
77
126
  # The columns that will be serialized. This is only for
@@ -83,12 +132,13 @@ module Sequel
83
132
  private
84
133
 
85
134
  # Add serializated attribute acessor methods to the serialization_module
86
- def define_serialized_attribute_accessor(format, *columns)
135
+ def define_serialized_attribute_accessor(serializer, deserializer, *columns)
87
136
  m = self
88
137
  include(self.serialization_module ||= Module.new) unless serialization_module
89
138
  serialization_module.class_eval do
90
139
  columns.each do |column|
91
- m.serialization_map[column] = format
140
+ m.serialization_map[column] = serializer
141
+ m.deserialization_map[column] = deserializer
92
142
  define_method(column) do
93
143
  if deserialized_values.has_key?(column)
94
144
  deserialized_values[column]
@@ -127,6 +177,7 @@ module Sequel
127
177
  super
128
178
  end
129
179
 
180
+ # Initialization the deserialized values for objects retrieved from the database.
130
181
  def set_values(*)
131
182
  @deserialized_values ||= {}
132
183
  super
@@ -134,33 +185,21 @@ module Sequel
134
185
 
135
186
  private
136
187
 
137
- # Deserialize the column from either marshal or yaml format
188
+ # Deserialize the column value. Called when the model column accessor is called to
189
+ # return a deserialized value.
138
190
  def deserialize_value(column, v)
139
- return v if v.nil?
140
- case model.serialization_map[column]
141
- when :marshal
142
- Marshal.load(v.unpack('m')[0]) rescue Marshal.load(v)
143
- when :yaml
144
- YAML.load v if v
145
- when :json
146
- JSON.parse v if v
147
- else
148
- raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
191
+ unless v.nil?
192
+ raise Sequel::Error, "no entry in deserialization_map for #{column.inspect}" unless callable = model.deserialization_map[column]
193
+ callable.call(v)
149
194
  end
150
195
  end
151
196
 
152
- # Serialize the column to either marshal or yaml format
197
+ # Serialize the column value. Called before saving to ensure the serialized value
198
+ # is saved in the database.
153
199
  def serialize_value(column, v)
154
- return v if v.nil?
155
- case model.serialization_map[column]
156
- when :marshal
157
- [Marshal.dump(v)].pack('m')
158
- when :yaml
159
- v.to_yaml
160
- when :json
161
- v.to_json
162
- else
163
- raise Error, "Bad serialization format (#{model.serialization_map[column].inspect}) for column #{column.inspect}"
200
+ unless v.nil?
201
+ raise Sequel::Error, "no entry in serialization_map for #{column.inspect}" unless callable = model.serialization_map[column]
202
+ callable.call(v)
164
203
  end
165
204
  end
166
205
  end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 30
6
+ MINOR = 31
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -60,6 +60,35 @@ describe "A MSSQL database" do
60
60
  end
61
61
  end
62
62
 
63
+ # This spec is currently disabled as the SQL Server 2008 R2 Express doesn't support
64
+ # full text searching. Even if full text searching is supported,
65
+ # you may need to create a full text catalog on the database first via:
66
+ # CREATE FULLTEXT CATALOG ftscd AS DEFAULT
67
+ describe "MSSQL full_text_search" do
68
+ before do
69
+ @db = MSSQL_DB
70
+ @db.drop_table(:posts) rescue nil
71
+ end
72
+ after do
73
+ @db.drop_table(:posts) rescue nil
74
+ end
75
+
76
+ specify "should support fulltext indexes and full_text_search" do
77
+ log do
78
+ @db.create_table(:posts){Integer :id, :null=>false; String :title; String :body; index :id, :name=>:fts_id_idx, :unique=>true; full_text_index :title, :key_index=>:fts_id_idx; full_text_index [:title, :body], :key_index=>:fts_id_idx}
79
+ @db[:posts].insert(:title=>'ruby rails', :body=>'y')
80
+ @db[:posts].insert(:title=>'sequel', :body=>'ruby')
81
+ @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
82
+
83
+ @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'y'}]
84
+ @db[:posts].full_text_search([:title, :body], ['sequel', 'ruby']).all.should == [{:title=>'sequel', :body=>'ruby'}]
85
+
86
+ @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
87
+ @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
88
+ end
89
+ end
90
+ end if false
91
+
63
92
  describe "MSSQL Dataset#join_table" do
64
93
  specify "should emulate the USING clause with ON" do
65
94
  MSSQL_DB[:items].join(:categories, [:id]).sql.should ==
@@ -219,6 +248,23 @@ describe "MSSQL dataset" do
219
248
  end
220
249
  end
221
250
 
251
+ describe "MSSQL::Dataset#import" do
252
+ before do
253
+ @db = MSSQL_DB
254
+ @db.create_table!(:test){primary_key :x; Integer :y}
255
+ @db.sqls.clear
256
+ @ds = @db[:test]
257
+ end
258
+ after do
259
+ @db.drop_table(:test) rescue nil
260
+ end
261
+
262
+ specify "#import should work correctly with an arbitrary output value" do
263
+ @ds.output(nil, [:inserted__y, :inserted__x]).import([:y], [[3], [4]]).should == [{:y=>3, :x=>1}, {:y=>4, :x=>2}]
264
+ @ds.all.should == [{:x=>1, :y=>3}, {:x=>2, :y=>4}]
265
+ end
266
+ end
267
+
222
268
  describe "MSSQL joined datasets" do
223
269
  before do
224
270
  @db = MSSQL_DB
@@ -645,6 +645,9 @@ describe "A MySQL database" do
645
645
  "SELECT * FROM `posts` WHERE (MATCH (`title`) AGAINST ('rails'))",
646
646
  "SELECT * FROM `posts` WHERE (MATCH (`title`, `body`) AGAINST ('sequel ruby'))",
647
647
  "SELECT * FROM `posts` WHERE (MATCH (`title`) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"]
648
+
649
+ @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
650
+ @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'y'}]
648
651
  end
649
652
 
650
653
  specify "should support spatial indexes" do
@@ -380,6 +380,9 @@ describe "A PostgreSQL database" do
380
380
  %{SELECT * FROM "posts" WHERE (to_tsvector('simple', (COALESCE("title", ''))) @@ to_tsquery('simple', 'rails'))},
381
381
  %{SELECT * FROM "posts" WHERE (to_tsvector('simple', (COALESCE("title", '') || ' ' || COALESCE("body", ''))) @@ to_tsquery('simple', 'yowsa | rails'))},
382
382
  %{SELECT * FROM "posts" WHERE (to_tsvector('french', (COALESCE("title", ''))) @@ to_tsquery('french', 'scooby'))}]
383
+
384
+ @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
385
+ @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
383
386
  end
384
387
 
385
388
  specify "should support spatial indexes" do
@@ -431,7 +434,7 @@ end
431
434
  describe "Postgres::Dataset#import" do
432
435
  before do
433
436
  @db = POSTGRES_DB
434
- @db.create_table!(:test){Integer :x; Integer :y}
437
+ @db.create_table!(:test){primary_key :x; Integer :y}
435
438
  @db.sqls.clear
436
439
  @ds = @db[:test]
437
440
  end
@@ -441,28 +444,45 @@ describe "Postgres::Dataset#import" do
441
444
 
442
445
  specify "#import should return separate insert statements if server_version < 80200" do
443
446
  @ds.meta_def(:server_version){80199}
444
-
445
447
  @ds.import([:x, :y], [[1, 2], [3, 4]])
446
-
447
- @db.sqls.should == [
448
- 'BEGIN',
449
- 'INSERT INTO "test" ("x", "y") VALUES (1, 2)',
450
- 'INSERT INTO "test" ("x", "y") VALUES (3, 4)',
451
- 'COMMIT'
452
- ]
448
+ @db.sqls.should == ['BEGIN', 'INSERT INTO "test" ("x", "y") VALUES (1, 2)', 'INSERT INTO "test" ("x", "y") VALUES (3, 4)', 'COMMIT']
453
449
  @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
454
450
  end
455
451
 
456
452
  specify "#import should a single insert statement if server_version >= 80200" do
457
453
  @ds.meta_def(:server_version){80200}
458
-
459
454
  @ds.import([:x, :y], [[1, 2], [3, 4]])
460
-
461
- @db.sqls.should == [
462
- 'BEGIN',
463
- 'INSERT INTO "test" ("x", "y") VALUES (1, 2), (3, 4)',
464
- 'COMMIT'
465
- ]
455
+ @db.sqls.should == ['BEGIN', 'INSERT INTO "test" ("x", "y") VALUES (1, 2), (3, 4)', 'COMMIT']
456
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
457
+ end
458
+
459
+ specify "#import should work correctly when returning primary keys for server_version < 80200" do
460
+ @ds.meta_def(:server_version){80199}
461
+ @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key).should == [1, 3]
462
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
463
+ end
464
+
465
+ specify "#import should work correctly when returning primary keys for server_version >= 80200" do
466
+ @ds.meta_def(:server_version){80200}
467
+ @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key).should == [1, 3]
468
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
469
+ end
470
+
471
+ specify "#import should work correctly when returning primary keys with :slice option for server_version < 80200" do
472
+ @ds.meta_def(:server_version){80199}
473
+ @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key, :slice=>1).should == [1, 3]
474
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
475
+ end
476
+
477
+ specify "#import should work correctly when returning primary keys with :slice option for server_version >= 80200" do
478
+ @ds.meta_def(:server_version){80200}
479
+ @ds.import([:x, :y], [[1, 2], [3, 4]], :return=>:primary_key, :slice=>1).should == [1, 3]
480
+ @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
481
+ end
482
+
483
+ specify "#import should work correctly with an arbitrary returning value" do
484
+ @ds.meta_def(:server_version){80200}
485
+ @ds.returning(:y, :x).import([:x, :y], [[1, 2], [3, 4]]).should == [{:y=>2, :x=>1}, {:y=>4, :x=>3}]
466
486
  @ds.all.should == [{:x=>1, :y=>2}, {:x=>3, :y=>4}]
467
487
  end
468
488
  end
@@ -500,21 +520,26 @@ describe "Postgres::Dataset#insert" do
500
520
  @ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}, {:xid=>3, :value=>13}]
501
521
  end
502
522
 
503
- specify "should call execute_insert if server_version < 80200" do
523
+ specify "should insert correctly if server_version < 80200" do
504
524
  @ds.meta_def(:server_version){80100}
505
- @ds.should_receive(:execute_insert).once.with('INSERT INTO "test5" ("value") VALUES (10)', :table=>:test5, :values=>{:value=>10})
506
- @ds.insert(:value=>10)
525
+ @ds.insert(:value=>10).should == 1
526
+ @ds.all.should == [{:xid=>1, :value=>10}]
507
527
  end
508
528
 
509
- specify "should call execute_insert if disabling insert returning" do
510
- @ds.disable_insert_returning!
511
- @ds.should_receive(:execute_insert).once.with('INSERT INTO "test5" ("value") VALUES (10)', :table=>:test5, :values=>{:value=>10})
512
- @ds.insert(:value=>10)
529
+ specify "should insert correctly if disabling insert returning" do
530
+ @ds.disable_insert_returning.insert(:value=>10).should == 1
531
+ @ds.all.should == [{:xid=>1, :value=>10}]
532
+ end
533
+
534
+ specify "should insert correctly if using a column array and a value array and server_version < 80200" do
535
+ @ds.meta_def(:server_version){80100}
536
+ @ds.insert([:value], [10]).should == 1
537
+ @ds.all.should == [{:xid=>1, :value=>10}]
513
538
  end
514
539
 
515
540
  specify "should use INSERT RETURNING if server_version >= 80200" do
516
541
  @ds.meta_def(:server_version){80201}
517
- @ds.insert(:value=>10)
542
+ @ds.insert(:value=>10).should == 1
518
543
  @db.sqls.last.should == 'INSERT INTO "test5" ("value") VALUES (10) RETURNING "xid"'
519
544
  end
520
545
 
@@ -605,6 +630,18 @@ describe "Postgres::Database schema qualified tables" do
605
630
  POSTGRES_DB.drop_table(:domains)
606
631
  end
607
632
 
633
+ specify "#schema should raise an exception if columns from tables in two separate schema are returned" do
634
+ POSTGRES_DB.create_table!(:public__domains){integer :d}
635
+ POSTGRES_DB.create_table(:schema_test__domains){integer :i}
636
+ begin
637
+ proc{POSTGRES_DB.schema(:domains)}.should raise_error(Sequel::Error)
638
+ POSTGRES_DB.schema(:public__domains).map{|x| x.first}.should == [:d]
639
+ POSTGRES_DB.schema(:schema_test__domains).map{|x| x.first}.should == [:i]
640
+ ensure
641
+ POSTGRES_DB.drop_table(:public__domains)
642
+ end
643
+ end
644
+
608
645
  specify "#table_exists? should see if the table is in a given schema" do
609
646
  POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
610
647
  POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true