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 +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
|