sequel 4.29.0 → 4.30.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f1baf17a3ed5aedeadf0002e4e68ac8e2e27bdc2
4
- data.tar.gz: 9ceec8f5898df4025b6cf5239f461dee5595ebeb
3
+ metadata.gz: fe7b61c71980b22f3aa98f3815a5852d11e0880d
4
+ data.tar.gz: f7b80005c8ad61d472e8f011e3532eb72c40f74b
5
5
  SHA512:
6
- metadata.gz: a2c9116e35e8ff8a536530cff484f2d82fac1916db18d8d4b4666359c6ce1f6a67aeb855feb173128f874a179bc75ef10a22bd326ecf48ad6c3cfbe6f3f3b8d3
7
- data.tar.gz: 07d8ee3fb38aeae9caa9344756826a7e43fa19bd5786948525b427e9d64c3714ee06d452299b310bb7d30666e16ee0af64f663de7bb9a3dbd3b2fd0f2c5255dd
6
+ metadata.gz: fe4a59d1e06d7f5267ff9f1201b052b06717e24b8d86b99db3cdfc0187b9056cf7bee88fae1da62a15215046c6babbce17aa97382269354941f6822eba4ba87e
7
+ data.tar.gz: 0750aa6e4143b4a074630060cef6033852930be6ab448f6d5a330b502f92bb7cbe86c26dd4d69c10356524c4a02a299becec6982077c5260f7bba329b81bd54d
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 4.30.0 (2016-01-04)
2
+
3
+ * Add Dataset#insert_conflict and #insert_ignore on SQLite for handling uniqueness violations (Sharpie) (#1121)
4
+
5
+ * Make Database#row_type in pg_row extension handle different formats of schema-qualified types (jeremyevans) (#1119)
6
+
7
+ * Add identifier_columns plugin for handling column names containing 2 or more consecutive underscores when saving (jeremyevans) (#1117)
8
+
9
+ * Support :eager_limit and :eager_limit_strategy dataset options in model eager loaders for per-call limits and strategies (chanks) (#1115)
10
+
11
+ * Allow IPv6 addresses in database URLs on ruby 1.9+ (hellvinz, jeremyevans) (#1113)
12
+
13
+ * Make Database#schema :db_type entries include sizes for string types on DB2 (jeremyevans)
14
+
15
+ * Make Database#schema :db_type entries include sizes for string and decimal types in the jdbc adapter's schema parsing (jeremyevans)
16
+
17
+ * Recognize another disconnect error in the tinytds adapter (jeremyevans)
18
+
1
19
  === 4.29.0 (2015-12-01)
2
20
 
3
21
  * Add Model#json_serializer_opts method to json_serializer plugin, allowing for setting to_json defaults on per-instance basis (jeremyevans)
@@ -287,9 +287,9 @@ the window function strategy:
287
287
  :eager_limit_strategy=>:window_function
288
288
  Artist.where(:id=>[1,2]).eager(:first_10_albums).all
289
289
  # SELECT * FROM (
290
- # SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY release_date) AS x_sequel_row_number_x
291
- # FROM tracks
292
- # WHERE (tracks.album_id IN (1, 2))
290
+ # SELECT *, row_number() OVER (PARTITION BY albums.artist_id ORDER BY release_date) AS x_sequel_row_number_x
291
+ # FROM albums
292
+ # WHERE (albums.artist_id IN (1, 2))
293
293
  # ) AS t1
294
294
  # WHERE (x_sequel_row_number_x <= 10)
295
295
 
@@ -297,6 +297,29 @@ Alternatively, you can use the :ruby strategy, which will fall back to
297
297
  retrieving all records, and then will slice the resulting array to get
298
298
  the first 10 after retrieval.
299
299
 
300
+ === Dynamic Eager Loading Limits
301
+
302
+ If you need to eager load variable numbers of records (with limits that aren't
303
+ known at the time of the association definition), Sequel supports an
304
+ :eager_limit dataset option that can be defined in an eager loading callback:
305
+
306
+ Artist.one_to_many :albums
307
+ Artist.where(:id => [1, 2]).eager(:albums => proc{|ds| ds.order(:release_date).clone(:eager_limit => 3)}).all
308
+ # SELECT * FROM (
309
+ # SELECT *, row_number() OVER (PARTITION BY albums.artist_id ORDER BY release_date) AS x_sequel_row_number_x
310
+ # FROM albums
311
+ # WHERE (albums.artist_id IN (1, 2))
312
+ # ) AS t1
313
+ # WHERE (x_sequel_row_number_x <= 3)
314
+
315
+ You can also customize the :eager_limit_strategy on a case-by-case basis by passing in that option in the same way:
316
+
317
+ Artist.where(:id => [1, 2]).eager(:albums => proc{|ds| ds.order(:release_date).clone(:eager_limit => 3, :eager_limit_strategy => :ruby)}).all
318
+ # SELECT * FROM albums WHERE (albums.artist_id IN (1, 2)) ORDER BY release_date
319
+
320
+ The :eager_limit and :eager_limit_strategy options currently only work when
321
+ eager loading via #eager, not with #eager_graph.
322
+
300
323
  === Eager Loading via eager_graph_with_options
301
324
 
302
325
  When eager loading an association via eager_graph (which uses JOINs), the
@@ -0,0 +1,37 @@
1
+ = New Features
2
+
3
+ * Overriding the :limit and :eager_limit_strategy association options
4
+ can now be done on a per-call basis when eager loading, by using an
5
+ eager block callback and setting the :eager_limit or
6
+ :eager_limit_strategy dataset options. Example:
7
+
8
+ Album.eager(:tracks=>proc{|ds| ds.clone(:eager_limit=>5)}).all
9
+
10
+ * Dataset#insert_conflict and #insert_ignore have been added on
11
+ SQLite, adding support for the INSERT OR ... SQL syntax:
12
+
13
+ DB[:table].insert_ignore.insert(:a=>1, :b=>2)
14
+ # INSERT OR IGNORE INTO TABLE (a, b) VALUES (1, 2)
15
+
16
+ DB[:table].insert_conflict(:replace).insert(:a=>1, :b=>2)
17
+ # INSERT OR REPLACE INTO TABLE (a, b) VALUES (1, 2)
18
+
19
+ * An identifier_columns plugin has been added, which allows
20
+ Sequel::Model#save to work when column names contain double
21
+ underscores.
22
+
23
+ = Other Improvements
24
+
25
+ * IPv6 addresses can now be used in connection URLs when using
26
+ ruby 1.9.3+.
27
+
28
+ * The :db_type entries in column schema hashes now include sizes
29
+ for string and decimal types on DB2 and when using the jdbc
30
+ adapter's generic schema parsing.
31
+
32
+ * Database#row_type in the pg_row extension now handles different
33
+ formats of specifying schema qualified types. So a row type
34
+ registered via :schema__type can be found using
35
+ Sequel.qualify(:schema, :type).
36
+
37
+ * Another disconnect error is recognized in the tinytds adapter.
@@ -597,6 +597,19 @@ module Sequel
597
597
  def setup_connection(conn)
598
598
  conn
599
599
  end
600
+
601
+ def schema_column_set_db_type(schema)
602
+ case schema[:type]
603
+ when :string
604
+ if schema[:db_type] =~ /\A(character( varying)?|n?(var)?char2?)\z/io && schema[:column_size] > 0
605
+ schema[:db_type] += "(#{schema[:column_size]})"
606
+ end
607
+ when :decimal
608
+ if schema[:db_type] =~ /\A(decimal|numeric)\z/io && schema[:column_size] > 0 && schema[:scale] >= 0
609
+ schema[:db_type] += "(#{schema[:column_size]}, #{schema[:scale]})"
610
+ end
611
+ end
612
+ end
600
613
 
601
614
  # Parse the table schema for the given table.
602
615
  def schema_parse_table(table, opts=OPTS)
@@ -626,6 +639,7 @@ module Sequel
626
639
  if s[:db_type] =~ DECIMAL_TYPE_RE && s[:scale] == 0
627
640
  s[:type] = :integer
628
641
  end
642
+ schema_column_set_db_type(s)
629
643
  schemas << h[:table_schem] unless schemas.include?(h[:table_schem])
630
644
  ts << [m.call(h[:column_name]), s]
631
645
  end
@@ -35,11 +35,14 @@ module Sequel
35
35
  im = input_identifier_meth(opts[:dataset])
36
36
  metadata_dataset.with_sql("SELECT * FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = #{literal(im.call(table))} ORDER BY COLNO").
37
37
  collect do |column|
38
- column[:db_type] = column.delete(:typename)
39
- if column[:db_type] == "DECIMAL"
38
+ column[:db_type] = column.delete(:typename)
39
+ if column[:db_type] =~ /\A(VAR)?CHAR\z/
40
+ column[:db_type] << "(#{column[:length]})"
41
+ end
42
+ if column[:db_type] == "DECIMAL"
40
43
  column[:db_type] << "(#{column[:longlength]},#{column[:scale]})"
41
44
  end
42
- column[:allow_null] = column.delete(:nulls) == 'Y'
45
+ column[:allow_null] = column.delete(:nulls) == 'Y'
43
46
  identity = column.delete(:identity) == 'Y'
44
47
  if column[:primary_key] = identity || !column[:keyseq].nil?
45
48
  column[:auto_increment] = identity
@@ -514,9 +514,10 @@ module Sequel
514
514
  DATE_OPEN = "date(".freeze
515
515
  DATETIME_OPEN = "datetime(".freeze
516
516
  ONLY_OFFSET = " LIMIT -1 OFFSET ".freeze
517
+ OR = " OR ".freeze
517
518
 
518
519
  Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
519
- Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert into columns values'], ["else", %w'insert into columns values']])
520
+ Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values'], ["else", %w'insert conflict into columns values']])
520
521
  Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
521
522
 
522
523
  def cast_sql_append(sql, expr, type)
@@ -604,7 +605,32 @@ module Sequel
604
605
  super
605
606
  end
606
607
  end
607
-
608
+
609
+ # Handle uniqueness violations when inserting, by using a specified
610
+ # resolution algorithm. With no options, uses INSERT OR REPLACE. SQLite
611
+ # supports the following conflict resolution algoriths: ROLLBACK, ABORT,
612
+ # FAIL, IGNORE and REPLACE.
613
+ #
614
+ # Examples:
615
+ #
616
+ # DB[:table].insert_conflict.insert(:a=>1, :b=>2)
617
+ # # INSERT OR IGNORE INTO TABLE (a, b) VALUES (1, 2)
618
+ #
619
+ # DB[:table].insert_conflict(:replace).insert(:a=>1, :b=>2)
620
+ # # INSERT OR REPLACE INTO TABLE (a, b) VALUES (1, 2)
621
+ def insert_conflict(resolution = :ignore)
622
+ clone(:insert_conflict => resolution)
623
+ end
624
+
625
+ # Ignore uniqueness/exclusion violations when inserting, using INSERT OR IGNORE.
626
+ # Exists mostly for compatibility to MySQL's insert_ignore. Example:
627
+ #
628
+ # DB[:table].insert_ignore.insert(:a=>1, :b=>2)
629
+ # # INSERT OR IGNORE INTO TABLE (a, b) VALUES (1, 2)
630
+ def insert_ignore
631
+ insert_conflict(:ignore)
632
+ end
633
+
608
634
  # SQLite 3.8.3+ supports common table expressions.
609
635
  def supports_cte?(type=:select)
610
636
  db.sqlite_version >= 30803
@@ -679,6 +705,13 @@ module Sequel
679
705
  columns.map{|i| quote_identifier(i)}.join(COMMA)
680
706
  end
681
707
 
708
+ # Add OR clauses to SQLite INSERT statements
709
+ def insert_conflict_sql(sql)
710
+ if resolution = @opts[:insert_conflict]
711
+ sql << OR << resolution.to_s.upcase
712
+ end
713
+ end
714
+
682
715
  # SQLite uses a preceding X for hex escaping strings
683
716
  def literal_blob_append(sql, v)
684
717
  sql << BLOB_START << v.unpack(HSTAR).first << APOS
@@ -129,7 +129,7 @@ module Sequel
129
129
  end
130
130
  end
131
131
 
132
- TINYTDS_DISCONNECT_ERRORS = /\A(Attempt to initiate a new Adaptive Server operation with results pending|The request failed to run because the batch is aborted, this can be caused by abort signal sent from client)/
132
+ TINYTDS_DISCONNECT_ERRORS = /\A(Attempt to initiate a new Adaptive Server operation with results pending|The request failed to run because the batch is aborted, this can be caused by abort signal sent from client|Adaptive Server connection timed out)/
133
133
  # Return true if the :conn argument is present and not active.
134
134
  def disconnect_error?(e, opts)
135
135
  super || (opts[:conn] && !opts[:conn].active?) || ((e.is_a?(::TinyTds::Error) && TINYTDS_DISCONNECT_ERRORS.match(e.message)))
@@ -78,11 +78,13 @@ module Sequel
78
78
  # Converts a uri to an options hash. These options are then passed
79
79
  # to a newly created database object.
80
80
  def self.uri_to_options(uri)
81
- { :user => uri.user,
81
+ {
82
+ :user => uri.user,
82
83
  :password => uri.password,
83
- :host => uri.host,
84
84
  :port => uri.port,
85
- :database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
85
+ :host => RUBY_VERSION < '1.9.3' ? uri.host : uri.hostname,
86
+ :database => (m = /\/(.*)/.match(uri.path)) && (m[1])
87
+ }
86
88
  end
87
89
  private_class_method :uri_to_options
88
90
 
@@ -298,7 +298,7 @@ module Sequel
298
298
  #
299
299
  # +insert+ handles a number of different argument formats:
300
300
  # no arguments or single empty hash :: Uses DEFAULT VALUES
301
- # single hash :: Most common format, treats keys as columns an values as values
301
+ # single hash :: Most common format, treats keys as columns and values as values
302
302
  # single array :: Treats entries as values, with no columns
303
303
  # two arrays :: Treats first array as columns, second array as values
304
304
  # single Dataset :: Treats as an insert based on a selection from the dataset given,
@@ -488,7 +488,7 @@ module Sequel
488
488
  PGArray.register(array_type_name, :oid=>array_oid, :converter=>parser, :type_procs=>@conversion_procs, :scalar_typecast=>schema_type_symbol)
489
489
  end
490
490
 
491
- @row_types[db_type] = opts.merge(:parser=>parser)
491
+ @row_types[literal(db_type)] = opts.merge(:parser=>parser, :type=>db_type)
492
492
  @row_schema_types[schema_type_string] = schema_type_symbol
493
493
  @schema_type_classes[schema_type_symbol] = ROW_TYPE_CLASSES
494
494
  @row_type_method_module.class_eval do
@@ -508,8 +508,8 @@ module Sequel
508
508
  def reset_conversion_procs
509
509
  procs = super
510
510
 
511
- row_types.each do |db_type, opts|
512
- register_row_type(db_type, opts)
511
+ row_types.values.each do |opts|
512
+ register_row_type(opts[:type], opts)
513
513
  end
514
514
 
515
515
  procs
@@ -519,7 +519,7 @@ module Sequel
519
519
  # In general, the given database type should already be registered,
520
520
  # but if obj is an array, this will handled unregistered types.
521
521
  def row_type(db_type, obj)
522
- (type_hash = @row_types[db_type]) &&
522
+ (type_hash = @row_types[literal(db_type)]) &&
523
523
  (parser = type_hash[:parser])
524
524
 
525
525
  case obj
@@ -105,12 +105,12 @@ module Sequel
105
105
 
106
106
  # Apply an eager limit strategy to the dataset, or return the dataset
107
107
  # unmodified if it doesn't need an eager limit strategy.
108
- def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy)
108
+ def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy, limit_and_offset=limit_and_offset())
109
109
  case strategy
110
110
  when :distinct_on
111
111
  apply_distinct_on_eager_limit_strategy(ds)
112
112
  when :window_function
113
- apply_window_function_eager_limit_strategy(ds)
113
+ apply_window_function_eager_limit_strategy(ds, limit_and_offset)
114
114
  else
115
115
  ds
116
116
  end
@@ -123,7 +123,7 @@ module Sequel
123
123
  end
124
124
 
125
125
  # Use a window function to limit the results of the eager loading dataset.
126
- def apply_window_function_eager_limit_strategy(ds)
126
+ def apply_window_function_eager_limit_strategy(ds, limit_and_offset=limit_and_offset())
127
127
  rn = ds.row_number_column
128
128
  limit, offset = limit_and_offset
129
129
  ds = ds.unordered.select_append{|o| o.row_number{}.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
@@ -143,13 +143,13 @@ module Sequel
143
143
 
144
144
  # If the ruby eager limit strategy is being used, slice the array using the slice
145
145
  # range to return the object(s) at the correct offset/limit.
146
- def apply_ruby_eager_limit_strategy(rows)
146
+ def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
147
147
  name = self[:name]
148
148
  if returns_array?
149
- range = slice_range
149
+ range = slice_range(limit_and_offset)
150
150
  rows.each{|o| o.associations[name] = o.associations[name][range] || []}
151
- elsif slice_range
152
- offset = slice_range.begin
151
+ elsif sr = slice_range(limit_and_offset)
152
+ offset = sr.begin
153
153
  rows.each{|o| o.associations[name] = o.associations[name][offset]}
154
154
  end
155
155
  end
@@ -245,12 +245,29 @@ module Sequel
245
245
  end
246
246
  strategy = eager_limit_strategy
247
247
  cascade = eo[:associations]
248
+ eager_limit = nil
248
249
 
249
250
  if eo[:eager_block] || eo[:loader] == false
251
+ ds = eager_loading_dataset(eo)
252
+
253
+ strategy = ds.opts[:eager_limit_strategy] || strategy
254
+
255
+ eager_limit =
256
+ if el = ds.opts[:eager_limit]
257
+ strategy ||= true_eager_graph_limit_strategy
258
+ if el.is_a?(Array)
259
+ el
260
+ else
261
+ [el, nil]
262
+ end
263
+ else
264
+ limit_and_offset
265
+ end
266
+
250
267
  strategy = true_eager_graph_limit_strategy if strategy == :union
251
268
  # Correlated subqueries are not supported for regular eager loading
252
269
  strategy = :ruby if strategy == :correlated_subquery
253
- objects = apply_eager_limit_strategy(eager_loading_dataset(eo), strategy).all
270
+ objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
254
271
  elsif strategy == :union
255
272
  objects = []
256
273
  ds = associated_dataset
@@ -269,7 +286,7 @@ module Sequel
269
286
 
270
287
  objects.each(&block)
271
288
  if strategy == :ruby
272
- apply_ruby_eager_limit_strategy(rows)
289
+ apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
273
290
  end
274
291
  end
275
292
 
@@ -461,7 +478,7 @@ module Sequel
461
478
  end
462
479
 
463
480
  # The range used for slicing when using the :ruby eager limit strategy.
464
- def slice_range
481
+ def slice_range(limit_and_offset = limit_and_offset())
465
482
  limit, offset = limit_and_offset
466
483
  if limit || offset
467
484
  (offset||0)..(limit ? (offset||0)+limit-1 : -1)
@@ -1856,13 +1856,16 @@ module Sequel
1856
1856
 
1857
1857
  # Insert into the given dataset and return the primary key created (if any).
1858
1858
  def _insert_raw(ds)
1859
- ds.insert(@values)
1859
+ ds.insert(_insert_values)
1860
1860
  end
1861
1861
 
1862
1862
  # Insert into the given dataset and return the hash of column values.
1863
1863
  def _insert_select_raw(ds)
1864
- ds.insert_select(@values)
1864
+ ds.insert_select(_insert_values)
1865
1865
  end
1866
+
1867
+ # The values hash to use when inserting a new record.
1868
+ alias _insert_values values
1866
1869
 
1867
1870
  # Refresh using a particular dataset, used inside save to make sure the same server
1868
1871
  # is used for reading newly inserted values from the database
@@ -0,0 +1,45 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The identifier_columns plugin makes Sequel automatically
4
+ # handle column names containing 2 or more consecutive
5
+ # underscores when creating or updating model objects.
6
+ # By default, this doesn't work correctly in Sequel, as it
7
+ # handles such symbols specially.
8
+ #
9
+ # This behavior isn't the default as it hurts performance,
10
+ # and is rarely necessary.
11
+ #
12
+ # Usage:
13
+ #
14
+ # # Make all model subclasses handle column names
15
+ # # with two or more underscores when saving
16
+ # Sequel::Model.plugin :identifier_columns
17
+ #
18
+ # # Make the Album class handle column names
19
+ # # with two or more underscores when saving
20
+ # Album.plugin :identifier_columns
21
+ module IdentifierColumns
22
+ module InstanceMethods
23
+ private
24
+
25
+ # Use identifiers for value hash keys when inserting.
26
+ def _insert_values
27
+ identifier_hash(super)
28
+ end
29
+
30
+ # Use identifiers for value hash keys when updating.
31
+ def _update_without_checking(columns)
32
+ super(identifier_hash(columns))
33
+ end
34
+
35
+ # Convert the given columns hash from symbol
36
+ # keys to Sequel::SQL::Identifier keys.
37
+ def identifier_hash(columns)
38
+ h = {}
39
+ columns.each{|k,v| h[Sequel.identifier(k)] = v}
40
+ h
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 29
6
+ MINOR = 30
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
@@ -3406,6 +3406,9 @@ describe 'PostgreSQL row-valued/composite types' do
3406
3406
  end
3407
3407
  @db.register_row_type(:domain_check)
3408
3408
  @db.get(@db.row_type(:domain_check, [1])).must_equal(:id=>1)
3409
+ @db.register_row_type(:public__domain_check)
3410
+ @db.get(@db.row_type(:public__domain_check, [1])).must_equal(:id=>1)
3411
+ @db.get(@db.row_type(Sequel.qualify(:public, :domain_check), [1])).must_equal(:id=>1)
3409
3412
  ensure
3410
3413
  @db.drop_table(:domain_check)
3411
3414
  @db << "DROP DOMAIN positive_integer"
@@ -371,6 +371,41 @@ describe "SQLite::Dataset#update" do
371
371
  end
372
372
  end
373
373
 
374
+ describe "SQLite::Dataset#insert_conflict" do
375
+ before(:all) do
376
+ DB.create_table! :ic_test do
377
+ primary_key :id
378
+ String :name
379
+ end
380
+ end
381
+
382
+ after(:each) do
383
+ DB[:ic_test].delete
384
+ end
385
+
386
+ after(:all) do
387
+ DB.drop_table?(:ic_test)
388
+ end
389
+
390
+ it "Dataset#insert_ignore and insert_constraint should ignore uniqueness violations" do
391
+ DB[:ic_test].insert(:id => 1, :name => "one")
392
+ proc {DB[:ic_test].insert(:id => 1, :name => "one")}.must_raise Sequel::ConstraintViolation
393
+
394
+ DB[:ic_test].insert_ignore.insert(:id => 1, :name => "one")
395
+ DB[:ic_test].all.must_equal([{:id => 1, :name => "one"}])
396
+
397
+ DB[:ic_test].insert_conflict(:ignore).insert(:id => 1, :name => "one")
398
+ DB[:ic_test].all.must_equal([{:id => 1, :name => "one"}])
399
+ end
400
+
401
+ it "Dataset#insert_constraint should handle replacement" do
402
+ DB[:ic_test].insert(:id => 1, :name => "one")
403
+
404
+ DB[:ic_test].insert_conflict(:replace).insert(:id => 1, :name => "two")
405
+ DB[:ic_test].all.must_equal([{:id => 1, :name => "two"}])
406
+ end
407
+ end
408
+
374
409
  describe "SQLite dataset" do
375
410
  before do
376
411
  DB.create_table! :test do
@@ -222,6 +222,10 @@ describe "A new Database" do
222
222
  db = Sequel.connect('mock:///?keep_reference=f')
223
223
  Sequel::DATABASES.wont_include(db)
224
224
  end
225
+
226
+ it 'should strip square brackets for ipv6 hosts' do
227
+ Sequel.connect('mock://[::1]').opts[:host].must_equal "::1"
228
+ end if RUBY_VERSION >= '1.9.3'
225
229
  end
226
230
 
227
231
  describe "Database#disconnect" do
@@ -0,0 +1,17 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "identifier_columns plugin" do
4
+ before do
5
+ @db = Sequel.mock(:numrows=>1, :fetch=>{:id=>1, :a__b=>2}, :autoid=>1)
6
+ @c = Class.new(Sequel::Model(@db[:test]))
7
+ @ds = @c.dataset
8
+ @c.columns :id, :a__b
9
+ @c.plugin :identifier_columns
10
+ @db.sqls
11
+ end
12
+
13
+ it "should not use qualification when updating or inserting values" do
14
+ @c.create(:a__b=>2).save
15
+ @db.sqls.must_equal ["INSERT INTO test (a__b) VALUES (2)", "SELECT * FROM test WHERE (id = 1) LIMIT 1", "UPDATE test SET a__b = 2 WHERE (id = 1)"]
16
+ end
17
+ end
@@ -1914,6 +1914,29 @@ describe "Sequel::Model Simple Associations" do
1914
1914
  artists.each{|a| a.first_two_albums.length.must_equal 1}
1915
1915
  end
1916
1916
 
1917
+ it "should handle the :eager_limit option in eager-loading callbacks" do
1918
+ @db[:artists].import([:name], (1..4).map{|i| ['test']})
1919
+ artist_ids = @db[:artists].where(:name => 'test').select_map([:id])
1920
+ @db[:albums].import([:artist_id], artist_ids * 3)
1921
+ ads = Artist.where(:id => artist_ids)
1922
+
1923
+ artists = ads.eager(:albums => proc{|ds| ds.clone(:eager_limit => 1)}).all
1924
+ artists.length.must_equal 4
1925
+ artists.each{|a| a.albums.length.must_equal 1}
1926
+
1927
+ artists = ads.eager(:albums => proc{|ds| ds.clone(:eager_limit => 2)}).all
1928
+ artists.length.must_equal 4
1929
+ artists.each{|a| a.albums.length.must_equal 2}
1930
+
1931
+ artists = ads.eager(:albums => proc{|ds| ds.clone(:eager_limit => 3)}).all
1932
+ artists.length.must_equal 4
1933
+ artists.each{|a| a.albums.length.must_equal 3}
1934
+
1935
+ artists = ads.eager(:albums => proc{|ds| ds.clone(:eager_limit => 4)}).all
1936
+ artists.length.must_equal 4
1937
+ artists.each{|a| a.albums.length.must_equal 3}
1938
+ end
1939
+
1917
1940
  it "should handle many_to_one associations with same name as :key" do
1918
1941
  Album.def_column_alias(:artist_id_id, :artist_id)
1919
1942
  Album.many_to_one :artist_id, :key_column =>:artist_id, :class=>Artist
@@ -159,6 +159,18 @@ describe "Database schema parser" do
159
159
  DB.schema(:items).first.last[:type].must_equal :boolean
160
160
  end
161
161
 
162
+ it "should round trip database types from the schema properly" do
163
+ DB.create_table!(:items){String :number, :size=>50}
164
+ db_type = DB.schema(:items).first.last[:db_type]
165
+ DB.create_table!(:items){column :number, db_type}
166
+ DB.schema(:items).first.last[:db_type].must_equal db_type
167
+
168
+ DB.create_table!(:items){Numeric :number, :size=>[11,3]}
169
+ db_type = DB.schema(:items).first.last[:db_type]
170
+ DB.create_table!(:items){column :number, db_type}
171
+ DB.schema(:items).first.last[:db_type].must_equal db_type
172
+ end
173
+
162
174
  it "should parse maximum length for string columns" do
163
175
  DB.create_table!(:items){String :a, :size=>4}
164
176
  DB.schema(:items).first.last[:max_length].must_equal 4
@@ -1113,6 +1113,50 @@ describe Sequel::Model, "#eager" do
1113
1113
  EagerBand.eager(:good_albums => proc {|ds| ds.select(:name)}).all
1114
1114
  DB.sqls.must_equal ['SELECT * FROM bands', "SELECT name FROM albums WHERE ((name = 'good') AND (albums.band_id IN (2)))"]
1115
1115
  end
1116
+
1117
+ it "should respect an :eager_limit option passed in a custom callback" do
1118
+ # Should default to a window function on its own.
1119
+ def (EagerTrack.dataset).supports_window_functions?() true end
1120
+ a = EagerAlbum.eager(:tracks=> proc{|ds| ds.clone(:eager_limit=>5)}).all
1121
+ a.must_equal [EagerAlbum.load(:id => 1, :band_id=> 2)]
1122
+ sqls = DB.sqls
1123
+ sqls.must_equal ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x <= 5)']
1124
+ a = a.first
1125
+ a.tracks.must_equal [EagerTrack.load(:id => 3, :album_id => 1)]
1126
+ DB.sqls.must_equal []
1127
+ end
1128
+
1129
+ it "should respect an :eager_limit option that includes an offset" do
1130
+ def (EagerTrack.dataset).supports_window_functions?() true end
1131
+ EagerAlbum.eager(:tracks=> proc{|ds| ds.clone(:eager_limit=>[5, 5])}).all
1132
+ DB.sqls.must_equal ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE ((x_sequel_row_number_x >= 6) AND (x_sequel_row_number_x < 11))']
1133
+ end
1134
+
1135
+ it "should have an :eager_limit option passed in a custom callback override a :limit defined in the association" do
1136
+ def (EagerTrack.dataset).supports_window_functions?() true end
1137
+ EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>2
1138
+ EagerAlbum.eager(:first_two_tracks=> proc{|ds| ds.clone(:eager_limit=>5)}).all
1139
+ DB.sqls.must_equal ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x <= 5)']
1140
+ end
1141
+
1142
+ it "should respect an :eager_limit_strategy option passed in a custom callback" do
1143
+ def (EagerTrack.dataset).supports_window_functions?() true end
1144
+ EagerTrack.dataset._fetch = (1..4).map{|i| {:album_id=>1, :id=>i}}
1145
+ a = EagerAlbum.eager(:tracks=> proc{|ds| ds.clone(:eager_limit=>2, :eager_limit_strategy=>:ruby)}).all
1146
+ a.must_equal [EagerAlbum.load(:id => 1, :band_id=> 2)]
1147
+ sqls = DB.sqls
1148
+ sqls.must_equal ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
1149
+ a = a.first
1150
+ a.tracks.must_equal [EagerTrack.load(:id => 1, :album_id => 1), EagerTrack.load(:id => 2, :album_id => 1)]
1151
+ DB.sqls.must_equal []
1152
+ end
1153
+
1154
+ it "should have an :eager_limit_strategy option passed in a custom callback override a :eager_limit_strategy defined in the association" do
1155
+ def (EagerTrack.dataset).supports_window_functions?() true end
1156
+ EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>2, :eager_limit_strategy=>:ruby
1157
+ EagerAlbum.eager(:first_two_tracks=> proc{|ds| ds.clone(:eager_limit_strategy=>:window_function)}).all
1158
+ DB.sqls.must_equal ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x <= 2)']
1159
+ end
1116
1160
  end
1117
1161
 
1118
1162
  describe Sequel::Model, "#eager_graph" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.29.0
4
+ version: 4.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-01 00:00:00.000000000 Z
11
+ date: 2016-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -230,6 +230,7 @@ extra_rdoc_files:
230
230
  - doc/release_notes/4.27.0.txt
231
231
  - doc/release_notes/4.28.0.txt
232
232
  - doc/release_notes/4.29.0.txt
233
+ - doc/release_notes/4.30.0.txt
233
234
  files:
234
235
  - CHANGELOG
235
236
  - MIT-LICENSE
@@ -348,6 +349,7 @@ files:
348
349
  - doc/release_notes/4.28.0.txt
349
350
  - doc/release_notes/4.29.0.txt
350
351
  - doc/release_notes/4.3.0.txt
352
+ - doc/release_notes/4.30.0.txt
351
353
  - doc/release_notes/4.4.0.txt
352
354
  - doc/release_notes/4.5.0.txt
353
355
  - doc/release_notes/4.6.0.txt
@@ -555,6 +557,7 @@ files:
555
557
  - lib/sequel/plugins/error_splitter.rb
556
558
  - lib/sequel/plugins/force_encoding.rb
557
559
  - lib/sequel/plugins/hook_class_methods.rb
560
+ - lib/sequel/plugins/identifier_columns.rb
558
561
  - lib/sequel/plugins/input_transformer.rb
559
562
  - lib/sequel/plugins/insert_returning_select.rb
560
563
  - lib/sequel/plugins/instance_filters.rb
@@ -674,6 +677,7 @@ files:
674
677
  - spec/extensions/graph_each_spec.rb
675
678
  - spec/extensions/hash_aliases_spec.rb
676
679
  - spec/extensions/hook_class_methods_spec.rb
680
+ - spec/extensions/identifier_columns_spec.rb
677
681
  - spec/extensions/inflector_spec.rb
678
682
  - spec/extensions/input_transformer_spec.rb
679
683
  - spec/extensions/insert_returning_select_spec.rb