sequel 3.28.0 → 3.29.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +119 -3
- data/Rakefile +5 -3
- data/bin/sequel +1 -5
- data/doc/model_hooks.rdoc +9 -1
- data/doc/opening_databases.rdoc +49 -40
- data/doc/prepared_statements.rdoc +27 -6
- data/doc/release_notes/3.28.0.txt +2 -2
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +18 -9
- data/doc/transactions.rdoc +41 -1
- data/lib/sequel/adapters/ado.rb +28 -17
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/amalgalite.rb +11 -7
- data/lib/sequel/adapters/db2.rb +122 -70
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do.rb +5 -36
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/firebird.rb +3 -6
- data/lib/sequel/adapters/ibmdb.rb +24 -16
- data/lib/sequel/adapters/informix.rb +2 -4
- data/lib/sequel/adapters/jdbc.rb +47 -11
- data/lib/sequel/adapters/jdbc/as400.rb +5 -24
- data/lib/sequel/adapters/jdbc/db2.rb +0 -5
- data/lib/sequel/adapters/jdbc/derby.rb +217 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
- data/lib/sequel/adapters/jdbc/h2.rb +10 -12
- data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
- data/lib/sequel/adapters/jdbc/informix.rb +0 -5
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
- data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
- data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
- data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
- data/lib/sequel/adapters/mock.rb +315 -0
- data/lib/sequel/adapters/mysql.rb +64 -51
- data/lib/sequel/adapters/mysql2.rb +15 -9
- data/lib/sequel/adapters/odbc.rb +13 -6
- data/lib/sequel/adapters/odbc/db2.rb +0 -4
- data/lib/sequel/adapters/odbc/mssql.rb +0 -5
- data/lib/sequel/adapters/openbase.rb +2 -4
- data/lib/sequel/adapters/oracle.rb +333 -51
- data/lib/sequel/adapters/postgres.rb +80 -27
- data/lib/sequel/adapters/shared/access.rb +0 -6
- data/lib/sequel/adapters/shared/db2.rb +13 -15
- data/lib/sequel/adapters/shared/firebird.rb +6 -6
- data/lib/sequel/adapters/shared/mssql.rb +23 -18
- data/lib/sequel/adapters/shared/mysql.rb +6 -6
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +185 -30
- data/lib/sequel/adapters/shared/postgres.rb +35 -18
- data/lib/sequel/adapters/shared/progress.rb +0 -6
- data/lib/sequel/adapters/shared/sqlite.rb +116 -37
- data/lib/sequel/adapters/sqlite.rb +16 -8
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/swift/mysql.rb +0 -5
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +6 -4
- data/lib/sequel/adapters/tinytds.rb +13 -10
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
- data/lib/sequel/core.rb +40 -0
- data/lib/sequel/database/connecting.rb +1 -2
- data/lib/sequel/database/dataset.rb +3 -3
- data/lib/sequel/database/dataset_defaults.rb +58 -0
- data/lib/sequel/database/misc.rb +62 -2
- data/lib/sequel/database/query.rb +113 -49
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/actions.rb +37 -19
- data/lib/sequel/dataset/features.rb +24 -0
- data/lib/sequel/dataset/graph.rb +7 -6
- data/lib/sequel/dataset/misc.rb +11 -3
- data/lib/sequel/dataset/mutation.rb +2 -3
- data/lib/sequel/dataset/prepared_statements.rb +6 -4
- data/lib/sequel/dataset/query.rb +46 -15
- data/lib/sequel/dataset/sql.rb +28 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/model.rb +2 -1
- data/lib/sequel/model/associations.rb +115 -33
- data/lib/sequel/model/base.rb +91 -31
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +6 -10
- data/lib/sequel/plugins/prepared_statements.rb +12 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +6 -1
- data/lib/sequel/plugins/sharding.rb +0 -5
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/timezones.rb +42 -42
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +29 -29
- data/spec/adapters/mysql_spec.rb +86 -104
- data/spec/adapters/oracle_spec.rb +48 -76
- data/spec/adapters/postgres_spec.rb +98 -33
- data/spec/adapters/spec_helper.rb +0 -5
- data/spec/adapters/sqlite_spec.rb +24 -21
- data/spec/core/connection_pool_spec.rb +9 -15
- data/spec/core/core_sql_spec.rb +20 -31
- data/spec/core/database_spec.rb +491 -227
- data/spec/core/dataset_spec.rb +638 -1051
- data/spec/core/expression_filters_spec.rb +0 -1
- data/spec/core/mock_adapter_spec.rb +378 -0
- data/spec/core/object_graph_spec.rb +48 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +51 -114
- data/spec/core/spec_helper.rb +3 -90
- data/spec/extensions/class_table_inheritance_spec.rb +1 -1
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/instance_hooks_spec.rb +71 -0
- data/spec/extensions/named_timezones_spec.rb +22 -2
- data/spec/extensions/nested_attributes_spec.rb +3 -0
- data/spec/extensions/schema_spec.rb +1 -1
- data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
- data/spec/extensions/serialization_spec.rb +5 -8
- data/spec/extensions/spec_helper.rb +4 -0
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/integration/associations_test.rb +123 -12
- data/spec/integration/dataset_test.rb +140 -47
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +179 -128
- data/spec/integration/prepared_statement_test.rb +92 -91
- data/spec/integration/schema_test.rb +42 -23
- data/spec/integration/spec_helper.rb +25 -31
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +161 -34
- data/spec/integration/type_test.rb +3 -3
- data/spec/model/association_reflection_spec.rb +83 -7
- data/spec/model/associations_spec.rb +393 -676
- data/spec/model/base_spec.rb +186 -116
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +343 -867
- data/spec/model/hooks_spec.rb +160 -79
- data/spec/model/model_spec.rb +118 -165
- data/spec/model/plugins_spec.rb +7 -13
- data/spec/model/record_spec.rb +138 -207
- data/spec/model/spec_helper.rb +10 -73
- metadata +14 -8
@@ -21,6 +21,11 @@
|
|
21
21
|
# Then, before data is stored in the database, it is converted to New
|
22
22
|
# York time. When data is retrieved from the database, it is
|
23
23
|
# converted to Los Angeles time.
|
24
|
+
#
|
25
|
+
# Note that typecasting from the database timezone to the application
|
26
|
+
# timezone when fetching rows is dependent on the database adapter,
|
27
|
+
# and only works on adapters where Sequel itself does the conversion.
|
28
|
+
# It should work on mysql, postgres, sqlite, ibmdb, and jdbc.
|
24
29
|
|
25
30
|
require 'tzinfo'
|
26
31
|
|
@@ -32,7 +32,7 @@ module Sequel
|
|
32
32
|
module ThreadLocalTimezones
|
33
33
|
%w'application database typecast'.each do |t|
|
34
34
|
class_eval("def thread_#{t}_timezone=(tz); Thread.current[:#{t}_timezone] = convert_timezone_setter_arg(tz); end", __FILE__, __LINE__)
|
35
|
-
class_eval(<<END, __FILE__, __LINE__)
|
35
|
+
class_eval(<<END, __FILE__, __LINE__ + 1)
|
36
36
|
def #{t}_timezone
|
37
37
|
if tz = Thread.current[:#{t}_timezone]
|
38
38
|
tz unless tz == :nil
|
data/lib/sequel/model.rb
CHANGED
@@ -79,7 +79,8 @@ module Sequel
|
|
79
79
|
|
80
80
|
# Hooks that are called after an action. When overriding these, it is recommended to call
|
81
81
|
# +super+ on the first line of your method, so later hooks are called after earlier hooks.
|
82
|
-
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
|
82
|
+
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
|
83
|
+
:after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
|
83
84
|
|
84
85
|
# Hooks that are called around an action. If overridden, these methods must call super
|
85
86
|
# exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
|
@@ -127,6 +127,22 @@ module Sequel
|
|
127
127
|
def need_associated_primary_key?
|
128
128
|
false
|
129
129
|
end
|
130
|
+
|
131
|
+
# Qualify +col+ with the given table name. If +col+ is an array of columns,
|
132
|
+
# return an array of qualified columns.
|
133
|
+
def qualify(table, col)
|
134
|
+
transform(col){|k| SQL::QualifiedIdentifier.new(table, k)}
|
135
|
+
end
|
136
|
+
|
137
|
+
# Qualify col with the associated model's table name.
|
138
|
+
def qualify_assoc(col)
|
139
|
+
qualify(associated_class.table_name, col)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Qualify col with the current model's table name.
|
143
|
+
def qualify_cur(col)
|
144
|
+
qualify(self[:model].table_name, col)
|
145
|
+
end
|
130
146
|
|
131
147
|
# Returns the reciprocal association variable, if one exists. The reciprocal
|
132
148
|
# association is the association in the associated class that is the opposite
|
@@ -195,6 +211,14 @@ module Sequel
|
|
195
211
|
def setter_method
|
196
212
|
:"#{self[:name]}="
|
197
213
|
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
# If +s+ is an array, map +s+ over the block. Otherwise, just call the
|
218
|
+
# block with +s+.
|
219
|
+
def transform(s)
|
220
|
+
s.is_a?(Array) ? s.map(&Proc.new) : (yield s)
|
221
|
+
end
|
198
222
|
end
|
199
223
|
|
200
224
|
class ManyToOneAssociationReflection < AssociationReflection
|
@@ -243,6 +267,11 @@ module Sequel
|
|
243
267
|
self[:primary_keys] ||= Array(primary_key)
|
244
268
|
end
|
245
269
|
alias associated_object_keys primary_keys
|
270
|
+
|
271
|
+
# #primary_key qualified by the associated table
|
272
|
+
def qualified_primary_key
|
273
|
+
self[:qualified_primary_key] ||= self[:qualify] == false ? primary_key : qualify_assoc(primary_key)
|
274
|
+
end
|
246
275
|
|
247
276
|
# True only if the reciprocal is a one_to_many association.
|
248
277
|
def reciprocal_array?
|
@@ -297,12 +326,18 @@ module Sequel
|
|
297
326
|
|
298
327
|
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
|
299
328
|
def eager_loading_predicate_key
|
300
|
-
self[:eager_loading_predicate_key] ||=
|
329
|
+
self[:eager_loading_predicate_key] ||= qualify_assoc(self[:key])
|
301
330
|
end
|
331
|
+
alias qualified_key eager_loading_predicate_key
|
302
332
|
|
303
333
|
# The column in the current table that the key in the associated table references.
|
304
334
|
def primary_key
|
305
|
-
self[:primary_key]
|
335
|
+
self[:primary_key]
|
336
|
+
end
|
337
|
+
|
338
|
+
# #primary_key qualified by the current table
|
339
|
+
def qualified_primary_key
|
340
|
+
self[:qualified_primary_key] ||= qualify_cur(primary_key)
|
306
341
|
end
|
307
342
|
|
308
343
|
# Whether the reciprocal of this association returns an array of objects instead of a single object,
|
@@ -381,13 +416,6 @@ module Sequel
|
|
381
416
|
self[:left_key]
|
382
417
|
end
|
383
418
|
|
384
|
-
# The table containing the column to use for the associated key when eagerly loading
|
385
|
-
def associated_key_table
|
386
|
-
self[:associated_key_table] ||= (
|
387
|
-
syms = associated_class.dataset.split_alias(self[:join_table]);
|
388
|
-
syms[1] || syms[0])
|
389
|
-
end
|
390
|
-
|
391
419
|
# Alias of right_primary_keys
|
392
420
|
def associated_object_keys
|
393
421
|
right_primary_keys
|
@@ -427,9 +455,16 @@ module Sequel
|
|
427
455
|
self[:eager_loader_key] ||= self[:left_primary_key]
|
428
456
|
end
|
429
457
|
|
430
|
-
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
|
458
|
+
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
|
459
|
+
# The left key qualified by the join table.
|
431
460
|
def eager_loading_predicate_key
|
432
|
-
self[:eager_loading_predicate_key] ||=
|
461
|
+
self[:eager_loading_predicate_key] ||= qualify(join_table_alias, self[:left_key])
|
462
|
+
end
|
463
|
+
alias qualified_left_key eager_loading_predicate_key
|
464
|
+
|
465
|
+
# The right key qualified by the join table.
|
466
|
+
def qualified_right_key
|
467
|
+
self[:qualified_right_key] ||= qualify(join_table_alias, self[:right_key])
|
433
468
|
end
|
434
469
|
|
435
470
|
# many_to_many associations need to select a key in an associated table to eagerly load
|
@@ -437,6 +472,25 @@ module Sequel
|
|
437
472
|
true
|
438
473
|
end
|
439
474
|
|
475
|
+
# The source of the join table. This is the join table itself, unless it
|
476
|
+
# is aliased, in which case it is the unaliased part.
|
477
|
+
def join_table_source
|
478
|
+
fetch(:join_table_source) do
|
479
|
+
split_join_table_alias
|
480
|
+
self[:join_table_source]
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
# The join table itself, unless it is aliased, in which case this
|
485
|
+
# is the alias.
|
486
|
+
def join_table_alias
|
487
|
+
fetch(:join_table_alias) do
|
488
|
+
split_join_table_alias
|
489
|
+
self[:join_table_alias]
|
490
|
+
end
|
491
|
+
end
|
492
|
+
alias associated_key_table join_table_alias
|
493
|
+
|
440
494
|
# Whether the associated object needs a primary key to be added/removed,
|
441
495
|
# true for many_to_many associations.
|
442
496
|
def need_associated_primary_key?
|
@@ -458,6 +512,11 @@ module Sequel
|
|
458
512
|
end
|
459
513
|
self[:reciprocal] = nil
|
460
514
|
end
|
515
|
+
|
516
|
+
# #right_primary_key qualified by the associated table
|
517
|
+
def qualified_right_primary_key
|
518
|
+
self[:qualified_right_primary_key] ||= qualify_assoc(right_primary_key)
|
519
|
+
end
|
461
520
|
|
462
521
|
# The primary key column(s) to use in the associated table (can be symbol or array).
|
463
522
|
def right_primary_key
|
@@ -474,6 +533,15 @@ module Sequel
|
|
474
533
|
return self[:select] if include?(:select)
|
475
534
|
self[:select] ||= Sequel::SQL::ColumnAll.new(associated_class.table_name)
|
476
535
|
end
|
536
|
+
|
537
|
+
private
|
538
|
+
|
539
|
+
# Split the join table into source and alias parts.
|
540
|
+
def split_join_table_alias
|
541
|
+
s, a = associated_class.dataset.split_alias(self[:join_table])
|
542
|
+
self[:join_table_source] = s
|
543
|
+
self[:join_table_alias] = a || s
|
544
|
+
end
|
477
545
|
end
|
478
546
|
|
479
547
|
# This module contains methods added to all association datasets
|
@@ -548,6 +616,22 @@ module Sequel
|
|
548
616
|
association_reflections.values
|
549
617
|
end
|
550
618
|
|
619
|
+
# Given an association reflection and a dataset, apply the
|
620
|
+
# :select, :conditions, :order, :eager, :distinct, and :eager_block
|
621
|
+
# association options to the given dataset and return the dataset
|
622
|
+
# or a modified copy of it.
|
623
|
+
def apply_association_dataset_opts(opts, ds)
|
624
|
+
ds = ds.select(*opts.select) if opts.select
|
625
|
+
if c = opts[:conditions]
|
626
|
+
ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
627
|
+
end
|
628
|
+
ds = ds.order(*opts[:order]) if opts[:order]
|
629
|
+
ds = ds.eager(opts[:eager]) if opts[:eager]
|
630
|
+
ds = ds.distinct if opts[:distinct]
|
631
|
+
ds = opts[:eager_block].call(ds) if opts[:eager_block]
|
632
|
+
ds
|
633
|
+
end
|
634
|
+
|
551
635
|
# Associates a related model with the current model. The following types are
|
552
636
|
# supported:
|
553
637
|
#
|
@@ -676,6 +760,10 @@ module Sequel
|
|
676
760
|
# :primary_key :: column in the associated table that :key option references, as a symbol.
|
677
761
|
# Defaults to the primary key of the associated table. Can use an
|
678
762
|
# array of symbols for a composite key association.
|
763
|
+
# :qualify :: Whether to use qualifier primary keys when loading the association. The default
|
764
|
+
# is true, so you must set to false to not qualify. Qualification rarely causes
|
765
|
+
# problems, but it's necessary to disable in some cases, such as when you are doing
|
766
|
+
# a JOIN USING operation on the column on Oracle.
|
679
767
|
# === :one_to_many and :one_to_one
|
680
768
|
# :key :: foreign key in associated model's table that references
|
681
769
|
# current model's primary key, as a symbol. Defaults to
|
@@ -763,26 +851,19 @@ module Sequel
|
|
763
851
|
|
764
852
|
# Modify and return eager loading dataset based on association options.
|
765
853
|
def eager_loading_dataset(opts, ds, select, associations, eager_options={})
|
854
|
+
ds = apply_association_dataset_opts(opts, ds)
|
766
855
|
ds = ds.select(*select) if select
|
767
|
-
if c = opts[:conditions]
|
768
|
-
ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
|
769
|
-
end
|
770
|
-
ds = ds.order(*opts[:order]) if opts[:order]
|
771
|
-
ds = ds.eager(opts[:eager]) if opts[:eager]
|
772
|
-
ds = ds.distinct if opts[:distinct]
|
773
856
|
if opts[:eager_graph]
|
774
857
|
raise(Error, "cannot eagerly load a #{opts[:type]} association that uses :eager_graph") if opts.eager_loading_use_associated_key?
|
775
858
|
ds = ds.eager_graph(opts[:eager_graph])
|
776
859
|
end
|
777
860
|
ds = ds.eager(associations) unless Array(associations).empty?
|
778
|
-
ds = opts[:eager_block].call(ds) if opts[:eager_block]
|
779
861
|
ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
|
780
862
|
if opts.eager_loading_use_associated_key?
|
781
863
|
ds = if opts[:uses_left_composite_keys]
|
782
|
-
|
783
|
-
ds.select_append(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
|
864
|
+
ds.select_append(*opts.associated_key_alias.zip(opts.eager_loading_predicate_key).map{|a, k| SQL::AliasedExpression.new(k, a)})
|
784
865
|
else
|
785
|
-
ds.select_append(SQL::AliasedExpression.new(
|
866
|
+
ds.select_append(SQL::AliasedExpression.new(opts.eager_loading_predicate_key, opts.associated_key_alias))
|
786
867
|
end
|
787
868
|
end
|
788
869
|
ds
|
@@ -963,9 +1044,9 @@ module Sequel
|
|
963
1044
|
h = eo[:key_hash][left_pk]
|
964
1045
|
rows = eo[:rows]
|
965
1046
|
rows.each{|object| object.associations[name] = []}
|
966
|
-
r =
|
967
|
-
l =
|
968
|
-
ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l),
|
1047
|
+
r = rcks.zip(opts.right_primary_keys)
|
1048
|
+
l = [[opts.qualify(opts.join_table_alias, left), h.keys]]
|
1049
|
+
ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), nil, eo[:associations], eo)
|
969
1050
|
case opts.eager_limit_strategy
|
970
1051
|
when :window_function
|
971
1052
|
delete_rn = true
|
@@ -974,7 +1055,7 @@ module Sequel
|
|
974
1055
|
when :correlated_subquery
|
975
1056
|
ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
|
976
1057
|
dsa = ds.send(:dataset_alias, 2)
|
977
|
-
xds.inner_join(join_table, r + lcks.map{|k| [k, SQL::QualifiedIdentifier.new(
|
1058
|
+
xds.inner_join(join_table, r + lcks.map{|k| [k, SQL::QualifiedIdentifier.new(opts.join_table_alias, k)]}, :table_alias=>dsa)
|
978
1059
|
end
|
979
1060
|
end
|
980
1061
|
ds.all do |assoc_record|
|
@@ -1005,7 +1086,7 @@ module Sequel
|
|
1005
1086
|
jt_graph_block = opts[:graph_join_table_block]
|
1006
1087
|
opts[:eager_grapher] ||= proc do |eo|
|
1007
1088
|
ds = eo[:self]
|
1008
|
-
ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
|
1089
|
+
ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
|
1009
1090
|
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
|
1010
1091
|
end
|
1011
1092
|
|
@@ -1037,15 +1118,17 @@ module Sequel
|
|
1037
1118
|
opts[:key] = opts.default_key unless opts.include?(:key)
|
1038
1119
|
key = opts[:key]
|
1039
1120
|
cks = opts[:keys] = Array(opts[:key])
|
1121
|
+
opts[:qualified_key] = opts.qualify_cur(key)
|
1040
1122
|
if opts[:primary_key]
|
1041
1123
|
cpks = Array(opts[:primary_key])
|
1042
1124
|
raise(Error, "mismatched number of composite keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
|
1043
1125
|
end
|
1044
1126
|
uses_cks = opts[:uses_composite_keys] = cks.length > 1
|
1127
|
+
qualify = opts[:qualify] != false
|
1045
1128
|
opts[:cartesian_product_number] ||= 0
|
1046
1129
|
opts[:dataset] ||= proc do
|
1047
1130
|
klass = opts.associated_class
|
1048
|
-
klass.filter(opts.
|
1131
|
+
klass.filter(Array(opts.qualified_primary_key).zip(cks.map{|k| send(k)}))
|
1049
1132
|
end
|
1050
1133
|
opts[:eager_loader] ||= proc do |eo|
|
1051
1134
|
h = eo[:key_hash][key]
|
@@ -1056,7 +1139,7 @@ module Sequel
|
|
1056
1139
|
# Skip eager loading if no objects have a foreign key for this association
|
1057
1140
|
unless keys.empty?
|
1058
1141
|
klass = opts.associated_class
|
1059
|
-
model.eager_loading_dataset(opts, klass.filter(
|
1142
|
+
model.eager_loading_dataset(opts, klass.filter(opts.qualified_primary_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
|
1060
1143
|
hash_key = uses_cks ? opts.primary_keys.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key)
|
1061
1144
|
next unless objects = h[hash_key]
|
1062
1145
|
objects.each{|object| object.associations[name] = assoc_record}
|
@@ -1095,8 +1178,7 @@ module Sequel
|
|
1095
1178
|
raise(Error, "mismatched number of composite keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
|
1096
1179
|
uses_cks = opts[:uses_composite_keys] = cks.length > 1
|
1097
1180
|
opts[:dataset] ||= proc do
|
1098
|
-
|
1099
|
-
klass.filter(cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cpks.map{|k| send(k)}))
|
1181
|
+
opts.associated_class.filter(Array(opts.qualified_key).zip(cpks.map{|k| send(k)}))
|
1100
1182
|
end
|
1101
1183
|
opts[:eager_loader] ||= proc do |eo|
|
1102
1184
|
h = eo[:key_hash][primary_key]
|
@@ -1109,7 +1191,7 @@ module Sequel
|
|
1109
1191
|
reciprocal = opts.reciprocal
|
1110
1192
|
klass = opts.associated_class
|
1111
1193
|
filter_keys = opts.eager_loading_predicate_key
|
1112
|
-
ds = model.eager_loading_dataset(opts, klass.filter(filter_keys=>h.keys),
|
1194
|
+
ds = model.eager_loading_dataset(opts, klass.filter(filter_keys=>h.keys), nil, eo[:associations], eo)
|
1113
1195
|
case opts.eager_limit_strategy
|
1114
1196
|
when :distinct_on
|
1115
1197
|
ds = ds.distinct(*filter_keys).order_prepend(*filter_keys)
|
@@ -1277,7 +1359,7 @@ module Sequel
|
|
1277
1359
|
|
1278
1360
|
# Dataset for the join table of the given many to many association reflection
|
1279
1361
|
def _join_table_dataset(opts)
|
1280
|
-
ds = model.db.from(opts
|
1362
|
+
ds = model.db.from(opts.join_table_source)
|
1281
1363
|
opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
|
1282
1364
|
end
|
1283
1365
|
|
@@ -1845,7 +1927,7 @@ module Sequel
|
|
1845
1927
|
if exp == SQL::Constants::FALSE
|
1846
1928
|
association_filter_handle_inversion(op, exp, Array(lpks))
|
1847
1929
|
else
|
1848
|
-
association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>model.db
|
1930
|
+
association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>model.db.from(ref[:join_table]).select(*lks).where(exp).exclude(SQL::BooleanExpression.from_value_pairs(lks.zip([]), :OR))), Array(lpks))
|
1849
1931
|
end
|
1850
1932
|
end
|
1851
1933
|
|
data/lib/sequel/model/base.rb
CHANGED
@@ -105,6 +105,17 @@ module Sequel
|
|
105
105
|
args = args.first if (args.size == 1)
|
106
106
|
args.is_a?(Hash) ? dataset[args] : primary_key_lookup(args)
|
107
107
|
end
|
108
|
+
|
109
|
+
# Initializes a model instance as an existing record. This constructor is
|
110
|
+
# used by Sequel to initialize model instances when fetching records.
|
111
|
+
# Requires that values be a hash where all keys are symbols. It
|
112
|
+
# probably should not be used by external code.
|
113
|
+
def call(values)
|
114
|
+
o = allocate
|
115
|
+
o.set_values(values)
|
116
|
+
o.after_initialize
|
117
|
+
o
|
118
|
+
end
|
108
119
|
|
109
120
|
# Clear the setter_methods cache
|
110
121
|
def clear_setter_methods_cache
|
@@ -149,9 +160,13 @@ module Sequel
|
|
149
160
|
set_dataset(ds)
|
150
161
|
end
|
151
162
|
|
152
|
-
# Extend the dataset with
|
163
|
+
# Extend the dataset with a module, similar to adding
|
153
164
|
# a plugin with the methods defined in DatasetMethods. If a block
|
154
|
-
# is given,
|
165
|
+
# is given, an anonymous module is created and the module_evaled, otherwise
|
166
|
+
# the argument should be a module. Returns the module given or the anonymous
|
167
|
+
# module created.
|
168
|
+
#
|
169
|
+
# Artist.dataset_module Sequel::ColumnsIntrospection
|
155
170
|
#
|
156
171
|
# Artist.dataset_module do
|
157
172
|
# def foo
|
@@ -162,11 +177,17 @@ module Sequel
|
|
162
177
|
# # => :bar
|
163
178
|
# Artist.foo
|
164
179
|
# # => :bar
|
165
|
-
def dataset_module
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
180
|
+
def dataset_module(mod = nil)
|
181
|
+
if mod
|
182
|
+
raise Error, "can't provide both argument and block to Model.dataset_module" if block_given?
|
183
|
+
dataset_extend(mod)
|
184
|
+
mod
|
185
|
+
else
|
186
|
+
@dataset_module ||= Module.new
|
187
|
+
@dataset_module.module_eval(&Proc.new) if block_given?
|
188
|
+
dataset_extend(@dataset_module)
|
189
|
+
@dataset_module
|
190
|
+
end
|
170
191
|
end
|
171
192
|
|
172
193
|
# Returns the database associated with the Model class.
|
@@ -237,9 +258,30 @@ module Sequel
|
|
237
258
|
@dataset_methods[meth] = block
|
238
259
|
dataset.meta_def(meth, &block) if @dataset
|
239
260
|
end
|
240
|
-
args.each
|
261
|
+
args.each do |arg|
|
262
|
+
if arg.to_s =~ NORMAL_METHOD_NAME_REGEXP
|
263
|
+
instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg, true)
|
264
|
+
else
|
265
|
+
def_model_dataset_method_block(arg)
|
266
|
+
end
|
267
|
+
end
|
241
268
|
end
|
242
|
-
|
269
|
+
|
270
|
+
module_eval(if RUBY_VERSION < '1.8.7'
|
271
|
+
<<-END
|
272
|
+
def def_model_dataset_method_block(arg)
|
273
|
+
meta_def(arg){|*args| dataset.send(arg, *args)}
|
274
|
+
end
|
275
|
+
END
|
276
|
+
else
|
277
|
+
<<-END
|
278
|
+
def def_model_dataset_method_block(arg)
|
279
|
+
meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
|
280
|
+
end
|
281
|
+
END
|
282
|
+
end, __FILE__, __LINE__ - 4)
|
283
|
+
private :def_model_dataset_method_block
|
284
|
+
|
243
285
|
# Finds a single record according to the supplied filter.
|
244
286
|
# You are encouraged to use Model.[] or Model.first instead of this method.
|
245
287
|
#
|
@@ -318,12 +360,9 @@ module Sequel
|
|
318
360
|
pluralize(underscore(demodulize(name))).to_sym
|
319
361
|
end
|
320
362
|
|
321
|
-
#
|
322
|
-
# used by Sequel to initialize model instances when fetching records.
|
323
|
-
# +load+ requires that values be a hash where all keys are symbols. It
|
324
|
-
# probably should not be used by external code.
|
363
|
+
# Calls #call with the values hash. Only for backwards compatibility.
|
325
364
|
def load(values)
|
326
|
-
|
365
|
+
call(values)
|
327
366
|
end
|
328
367
|
|
329
368
|
# Clear the setter_methods cache when a setter method is added
|
@@ -447,13 +486,17 @@ module Sequel
|
|
447
486
|
@simple_table = db.literal(ds)
|
448
487
|
db.from(ds)
|
449
488
|
when Dataset
|
450
|
-
@simple_table =
|
489
|
+
@simple_table = if ds.send(:simple_select_all?)
|
490
|
+
ds.literal(ds.first_source_table)
|
491
|
+
else
|
492
|
+
nil
|
493
|
+
end
|
451
494
|
@db = ds.db
|
452
495
|
ds
|
453
496
|
else
|
454
497
|
raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, LiteralString, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
|
455
498
|
end
|
456
|
-
@dataset.row_proc =
|
499
|
+
@dataset.row_proc = self
|
457
500
|
@require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
|
458
501
|
if inherited
|
459
502
|
@simple_table = superclass.simple_table
|
@@ -466,7 +509,7 @@ module Sequel
|
|
466
509
|
check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
|
467
510
|
self
|
468
511
|
end
|
469
|
-
|
512
|
+
|
470
513
|
# Sets the primary key for this model. You can use either a regular
|
471
514
|
# or a composite primary key. To not use a primary key, set to nil
|
472
515
|
# or use +no_primary_key+. On most adapters, Sequel can automatically
|
@@ -484,7 +527,11 @@ module Sequel
|
|
484
527
|
def set_primary_key(*key)
|
485
528
|
clear_setter_methods_cache
|
486
529
|
key = key.flatten
|
487
|
-
@simple_pk = key.length == 1
|
530
|
+
@simple_pk = if key.length == 1
|
531
|
+
(@dataset || db).literal(key.first)
|
532
|
+
else
|
533
|
+
nil
|
534
|
+
end
|
488
535
|
@primary_key = (key.length == 1) ? key[0] : key
|
489
536
|
end
|
490
537
|
|
@@ -617,10 +664,9 @@ module Sequel
|
|
617
664
|
return nil unless @dataset
|
618
665
|
schema_hash = {}
|
619
666
|
ds_opts = dataset.opts
|
620
|
-
single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
|
621
|
-
&& !ds_opts.include?(:join) && !ds_opts.include?(:sql)
|
622
667
|
get_columns = proc{check_non_connection_error{columns} || []}
|
623
|
-
|
668
|
+
schema_array = check_non_connection_error{db.schema(dataset, :reload=>reload)}
|
669
|
+
if schema_array
|
624
670
|
schema_array.each{|k,v| schema_hash[k] = v}
|
625
671
|
if ds_opts.include?(:select)
|
626
672
|
# We don't remove the columns from the schema_hash,
|
@@ -628,6 +674,7 @@ module Sequel
|
|
628
674
|
# even if they are not selected.
|
629
675
|
cols = get_columns.call
|
630
676
|
cols.each{|c| schema_hash[c] ||= {}}
|
677
|
+
def_column_accessor(*schema_hash.keys)
|
631
678
|
else
|
632
679
|
# Dataset is for a single table with all columns,
|
633
680
|
# so set the columns based on the order they were
|
@@ -741,7 +788,7 @@ module Sequel
|
|
741
788
|
# * The following instance_methods all call the class method of the same
|
742
789
|
# name: columns, db, primary_key, db_schema.
|
743
790
|
# * All of the methods in +BOOLEAN_SETTINGS+ create attr_writers allowing you
|
744
|
-
# to set values for the attribute. It also creates
|
791
|
+
# to set values for the attribute. It also creates instance getters returning
|
745
792
|
# the value of the setting. If the value has not yet been set, it
|
746
793
|
# gets the default value from the class by calling the class method of the same name.
|
747
794
|
module InstanceMethods
|
@@ -784,7 +831,7 @@ module Sequel
|
|
784
831
|
#
|
785
832
|
# Arguments:
|
786
833
|
# values :: should be a hash to pass to set.
|
787
|
-
# from_db ::
|
834
|
+
# from_db :: only for backwards compatibility, forget it exists.
|
788
835
|
#
|
789
836
|
# Artist.new(:name=>'Bob')
|
790
837
|
#
|
@@ -793,7 +840,6 @@ module Sequel
|
|
793
840
|
# end
|
794
841
|
def initialize(values = {}, from_db = false)
|
795
842
|
if from_db
|
796
|
-
@new = false
|
797
843
|
set_values(values)
|
798
844
|
else
|
799
845
|
@values = {}
|
@@ -1034,7 +1080,7 @@ module Sequel
|
|
1034
1080
|
# Artist.new.new? # => true
|
1035
1081
|
# Artist[1].new? # => false
|
1036
1082
|
def new?
|
1037
|
-
@new
|
1083
|
+
defined?(@new) ? @new : (@new = false)
|
1038
1084
|
end
|
1039
1085
|
|
1040
1086
|
# Returns the primary key value identifying the model instance.
|
@@ -1179,6 +1225,13 @@ module Sequel
|
|
1179
1225
|
set_restricted(hash, only.flatten, false)
|
1180
1226
|
end
|
1181
1227
|
|
1228
|
+
# Replace the current values with hash. Should definitely not be
|
1229
|
+
# used with untrusted input, and should probably not be called
|
1230
|
+
# directly by user code.
|
1231
|
+
def set_values(hash)
|
1232
|
+
@values = hash
|
1233
|
+
end
|
1234
|
+
|
1182
1235
|
# Clear the setter_methods cache when a method is added
|
1183
1236
|
def singleton_method_added(meth)
|
1184
1237
|
@singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
|
@@ -1286,6 +1339,8 @@ module Sequel
|
|
1286
1339
|
# Internal destroy method, separted from destroy to
|
1287
1340
|
# allow running inside a transaction
|
1288
1341
|
def _destroy(opts)
|
1342
|
+
sh = {:server=>this_server}
|
1343
|
+
db.after_rollback(sh){after_destroy_rollback}
|
1289
1344
|
called = false
|
1290
1345
|
around_destroy do
|
1291
1346
|
called = true
|
@@ -1295,6 +1350,7 @@ module Sequel
|
|
1295
1350
|
true
|
1296
1351
|
end
|
1297
1352
|
raise_hook_failure(:destroy) unless called
|
1353
|
+
db.after_commit(sh){after_destroy_commit}
|
1298
1354
|
self
|
1299
1355
|
end
|
1300
1356
|
|
@@ -1355,6 +1411,8 @@ module Sequel
|
|
1355
1411
|
# Internal version of save, split from save to allow running inside
|
1356
1412
|
# it's own transaction.
|
1357
1413
|
def _save(columns, opts)
|
1414
|
+
sh = {:server=>this_server}
|
1415
|
+
db.after_rollback(sh){after_rollback}
|
1358
1416
|
was_new = false
|
1359
1417
|
pk = nil
|
1360
1418
|
called_save = false
|
@@ -1408,6 +1466,7 @@ module Sequel
|
|
1408
1466
|
@columns_updated = nil
|
1409
1467
|
end
|
1410
1468
|
@modified = false
|
1469
|
+
db.after_commit(sh){after_commit}
|
1411
1470
|
self
|
1412
1471
|
end
|
1413
1472
|
|
@@ -1506,7 +1565,7 @@ module Sequel
|
|
1506
1565
|
|
1507
1566
|
# If transactions should be used, wrap the yield in a transaction block.
|
1508
1567
|
def checked_transaction(opts={})
|
1509
|
-
use_transaction?(opts) ? db.transaction(opts){yield} : yield
|
1568
|
+
use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
|
1510
1569
|
end
|
1511
1570
|
|
1512
1571
|
# Set the columns with the given hash. By default, the same as +set+, but
|
@@ -1563,11 +1622,6 @@ module Sequel
|
|
1563
1622
|
self
|
1564
1623
|
end
|
1565
1624
|
|
1566
|
-
# Replace the current values with hash.
|
1567
|
-
def set_values(hash)
|
1568
|
-
@values = hash
|
1569
|
-
end
|
1570
|
-
|
1571
1625
|
# Returns all methods that can be used for attribute
|
1572
1626
|
# assignment (those that end with =), modified by the only
|
1573
1627
|
# and except arguments:
|
@@ -1596,6 +1650,12 @@ module Sequel
|
|
1596
1650
|
meths
|
1597
1651
|
end
|
1598
1652
|
end
|
1653
|
+
|
1654
|
+
# The server/shard that the model object's dataset uses, or :default if the
|
1655
|
+
# model object's dataset does not have an associated shard.
|
1656
|
+
def this_server
|
1657
|
+
primary_key ? (this.opts[:server] || :default) : (model.dataset.opts[:server] || :default)
|
1658
|
+
end
|
1599
1659
|
|
1600
1660
|
# Typecast the value to the column's type if typecasting. Calls the database's
|
1601
1661
|
# typecast_value method, so database adapters can override/augment the handling
|