sequel 3.28.0 → 3.29.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.
Files changed (148) hide show
  1. data/CHANGELOG +119 -3
  2. data/Rakefile +5 -3
  3. data/bin/sequel +1 -5
  4. data/doc/model_hooks.rdoc +9 -1
  5. data/doc/opening_databases.rdoc +49 -40
  6. data/doc/prepared_statements.rdoc +27 -6
  7. data/doc/release_notes/3.28.0.txt +2 -2
  8. data/doc/release_notes/3.29.0.txt +459 -0
  9. data/doc/sharding.rdoc +7 -1
  10. data/doc/testing.rdoc +18 -9
  11. data/doc/transactions.rdoc +41 -1
  12. data/lib/sequel/adapters/ado.rb +28 -17
  13. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  14. data/lib/sequel/adapters/amalgalite.rb +11 -7
  15. data/lib/sequel/adapters/db2.rb +122 -70
  16. data/lib/sequel/adapters/dbi.rb +15 -15
  17. data/lib/sequel/adapters/do.rb +5 -36
  18. data/lib/sequel/adapters/do/mysql.rb +0 -5
  19. data/lib/sequel/adapters/do/postgres.rb +0 -5
  20. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  21. data/lib/sequel/adapters/firebird.rb +3 -6
  22. data/lib/sequel/adapters/ibmdb.rb +24 -16
  23. data/lib/sequel/adapters/informix.rb +2 -4
  24. data/lib/sequel/adapters/jdbc.rb +47 -11
  25. data/lib/sequel/adapters/jdbc/as400.rb +5 -24
  26. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  27. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  28. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/h2.rb +10 -12
  30. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  31. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  32. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  33. data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
  34. data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
  35. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
  36. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  38. data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
  39. data/lib/sequel/adapters/mock.rb +315 -0
  40. data/lib/sequel/adapters/mysql.rb +64 -51
  41. data/lib/sequel/adapters/mysql2.rb +15 -9
  42. data/lib/sequel/adapters/odbc.rb +13 -6
  43. data/lib/sequel/adapters/odbc/db2.rb +0 -4
  44. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  45. data/lib/sequel/adapters/openbase.rb +2 -4
  46. data/lib/sequel/adapters/oracle.rb +333 -51
  47. data/lib/sequel/adapters/postgres.rb +80 -27
  48. data/lib/sequel/adapters/shared/access.rb +0 -6
  49. data/lib/sequel/adapters/shared/db2.rb +13 -15
  50. data/lib/sequel/adapters/shared/firebird.rb +6 -6
  51. data/lib/sequel/adapters/shared/mssql.rb +23 -18
  52. data/lib/sequel/adapters/shared/mysql.rb +6 -6
  53. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  54. data/lib/sequel/adapters/shared/oracle.rb +185 -30
  55. data/lib/sequel/adapters/shared/postgres.rb +35 -18
  56. data/lib/sequel/adapters/shared/progress.rb +0 -6
  57. data/lib/sequel/adapters/shared/sqlite.rb +116 -37
  58. data/lib/sequel/adapters/sqlite.rb +16 -8
  59. data/lib/sequel/adapters/swift.rb +5 -5
  60. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  61. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  62. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  63. data/lib/sequel/adapters/tinytds.rb +13 -10
  64. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
  65. data/lib/sequel/core.rb +40 -0
  66. data/lib/sequel/database/connecting.rb +1 -2
  67. data/lib/sequel/database/dataset.rb +3 -3
  68. data/lib/sequel/database/dataset_defaults.rb +58 -0
  69. data/lib/sequel/database/misc.rb +62 -2
  70. data/lib/sequel/database/query.rb +113 -49
  71. data/lib/sequel/database/schema_methods.rb +7 -2
  72. data/lib/sequel/dataset/actions.rb +37 -19
  73. data/lib/sequel/dataset/features.rb +24 -0
  74. data/lib/sequel/dataset/graph.rb +7 -6
  75. data/lib/sequel/dataset/misc.rb +11 -3
  76. data/lib/sequel/dataset/mutation.rb +2 -3
  77. data/lib/sequel/dataset/prepared_statements.rb +6 -4
  78. data/lib/sequel/dataset/query.rb +46 -15
  79. data/lib/sequel/dataset/sql.rb +28 -4
  80. data/lib/sequel/extensions/named_timezones.rb +5 -0
  81. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  82. data/lib/sequel/model.rb +2 -1
  83. data/lib/sequel/model/associations.rb +115 -33
  84. data/lib/sequel/model/base.rb +91 -31
  85. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  86. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  87. data/lib/sequel/plugins/force_encoding.rb +6 -6
  88. data/lib/sequel/plugins/identity_map.rb +1 -1
  89. data/lib/sequel/plugins/many_through_many.rb +6 -10
  90. data/lib/sequel/plugins/prepared_statements.rb +12 -1
  91. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
  92. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  93. data/lib/sequel/plugins/serialization.rb +6 -1
  94. data/lib/sequel/plugins/sharding.rb +0 -5
  95. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  96. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  97. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  98. data/lib/sequel/timezones.rb +42 -42
  99. data/lib/sequel/version.rb +1 -1
  100. data/spec/adapters/mssql_spec.rb +29 -29
  101. data/spec/adapters/mysql_spec.rb +86 -104
  102. data/spec/adapters/oracle_spec.rb +48 -76
  103. data/spec/adapters/postgres_spec.rb +98 -33
  104. data/spec/adapters/spec_helper.rb +0 -5
  105. data/spec/adapters/sqlite_spec.rb +24 -21
  106. data/spec/core/connection_pool_spec.rb +9 -15
  107. data/spec/core/core_sql_spec.rb +20 -31
  108. data/spec/core/database_spec.rb +491 -227
  109. data/spec/core/dataset_spec.rb +638 -1051
  110. data/spec/core/expression_filters_spec.rb +0 -1
  111. data/spec/core/mock_adapter_spec.rb +378 -0
  112. data/spec/core/object_graph_spec.rb +48 -114
  113. data/spec/core/schema_generator_spec.rb +3 -3
  114. data/spec/core/schema_spec.rb +51 -114
  115. data/spec/core/spec_helper.rb +3 -90
  116. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  117. data/spec/extensions/dataset_associations_spec.rb +199 -0
  118. data/spec/extensions/instance_hooks_spec.rb +71 -0
  119. data/spec/extensions/named_timezones_spec.rb +22 -2
  120. data/spec/extensions/nested_attributes_spec.rb +3 -0
  121. data/spec/extensions/schema_spec.rb +1 -1
  122. data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
  123. data/spec/extensions/serialization_spec.rb +5 -8
  124. data/spec/extensions/spec_helper.rb +4 -0
  125. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  126. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  127. data/spec/integration/associations_test.rb +123 -12
  128. data/spec/integration/dataset_test.rb +140 -47
  129. data/spec/integration/eager_loader_test.rb +19 -21
  130. data/spec/integration/model_test.rb +80 -1
  131. data/spec/integration/plugin_test.rb +179 -128
  132. data/spec/integration/prepared_statement_test.rb +92 -91
  133. data/spec/integration/schema_test.rb +42 -23
  134. data/spec/integration/spec_helper.rb +25 -31
  135. data/spec/integration/timezone_test.rb +38 -12
  136. data/spec/integration/transaction_test.rb +161 -34
  137. data/spec/integration/type_test.rb +3 -3
  138. data/spec/model/association_reflection_spec.rb +83 -7
  139. data/spec/model/associations_spec.rb +393 -676
  140. data/spec/model/base_spec.rb +186 -116
  141. data/spec/model/dataset_methods_spec.rb +7 -27
  142. data/spec/model/eager_loading_spec.rb +343 -867
  143. data/spec/model/hooks_spec.rb +160 -79
  144. data/spec/model/model_spec.rb +118 -165
  145. data/spec/model/plugins_spec.rb +7 -13
  146. data/spec/model/record_spec.rb +138 -207
  147. data/spec/model/spec_helper.rb +10 -73
  148. 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, :after_validation]
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] ||= self[:uses_composite_keys] ? self[:keys].map{|k| SQL::QualifiedIdentifier.new(associated_class.table_name, k)} : SQL::QualifiedIdentifier.new(associated_class.table_name, self[: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] ||= self[:model].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] ||= self[:uses_left_composite_keys] ? self[:left_keys].map{|k| SQL::QualifiedIdentifier.new(self[:join_table], k)} : SQL::QualifiedIdentifier.new(self[:join_table], self[:left_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
- t = opts.associated_key_table
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(SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column), opts.associated_key_alias))
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 = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
967
- l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
968
- ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), eo[:associations], eo)
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(join_table, k)]}, :table_alias=>dsa)
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.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}.zip(cks.map{|k| send(k)}))
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(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>keys} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, eo[:associations], eo).all do |assoc_record|
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
- klass = opts.associated_class
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), opts.select, eo[:associations], eo)
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[:join_table])
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[ref[:join_table]].select(*lks).where(exp).exclude(SQL::BooleanExpression.from_value_pairs(lks.zip([]), :OR))), Array(lpks))
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
 
@@ -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 an anonymous module, similar to adding
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, it is module_evaled.
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
- @dataset_module ||= Module.new
167
- @dataset_module.module_eval(&Proc.new) if block_given?
168
- dataset_extend(@dataset_module)
169
- @dataset_module
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{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg)}
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
- # Initializes a model instance as an existing record. This constructor is
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
- new(values, true)
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 = nil
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 = Proc.new{|r| load(r)}
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 ? db.literal(key.first) : nil
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
- if single_table && (schema_array = (db.schema(dataset.first_source_table, :reload=>reload) rescue nil))
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 instnace getters returning
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 :: should only be set by <tt>Model.load</tt>, forget it exists.
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