sequel 3.28.0 → 3.29.0

Sign up to get free protection for your applications and to get access to all the features.
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