sequel 4.29.0 → 4.30.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.
- checksums.yaml +4 -4
- data/CHANGELOG +18 -0
- data/doc/advanced_associations.rdoc +26 -3
- data/doc/release_notes/4.30.0.txt +37 -0
- data/lib/sequel/adapters/jdbc.rb +14 -0
- data/lib/sequel/adapters/shared/db2.rb +6 -3
- data/lib/sequel/adapters/shared/sqlite.rb +35 -2
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/database/misc.rb +5 -3
- data/lib/sequel/dataset/actions.rb +1 -1
- data/lib/sequel/extensions/pg_row.rb +4 -4
- data/lib/sequel/model/associations.rb +27 -10
- data/lib/sequel/model/base.rb +5 -2
- data/lib/sequel/plugins/identifier_columns.rb +45 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +3 -0
- data/spec/adapters/sqlite_spec.rb +35 -0
- data/spec/core/database_spec.rb +4 -0
- data/spec/extensions/identifier_columns_spec.rb +17 -0
- data/spec/integration/associations_test.rb +23 -0
- data/spec/integration/schema_test.rb +12 -0
- data/spec/model/eager_loading_spec.rb +44 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe7b61c71980b22f3aa98f3815a5852d11e0880d
|
4
|
+
data.tar.gz: f7b80005c8ad61d472e8f011e3532eb72c40f74b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
291
|
-
# FROM
|
292
|
-
# WHERE (
|
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.
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -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]
|
39
|
-
if column[:db_type]
|
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]
|
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)))
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -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
|
-
{
|
81
|
+
{
|
82
|
+
:user => uri.user,
|
82
83
|
:password => uri.password,
|
83
|
-
:host => uri.host,
|
84
84
|
:port => uri.port,
|
85
|
-
:
|
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
|
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 |
|
512
|
-
register_row_type(
|
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 =
|
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(
|
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)
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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(
|
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(
|
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
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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
|
data/spec/core/database_spec.rb
CHANGED
@@ -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.
|
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:
|
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
|