sequel 3.30.0 → 3.31.0

Sign up to get free protection for your applications and to get access to all the features.
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