sequel 3.33.0 → 3.34.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 (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -0,0 +1,35 @@
1
+ # The select_remove extension adds Sequel::Dataset#select_remove for removing existing selected
2
+ # columns from a dataset. It's not part of Sequel core as it is rarely needed and has
3
+ # some corner cases where it can't work correctly.
4
+
5
+ module Sequel
6
+ class Dataset
7
+ # Remove columns from the list of selected columns. If any of the currently selected
8
+ # columns use expressions/aliases, this will remove selected columns with the given
9
+ # aliases. It will also remove entries from the selection that match exactly:
10
+ #
11
+ # # Assume columns a, b, and c in items table
12
+ # DB[:items] # SELECT * FROM items
13
+ # DB[:items].select_remove(:c) # SELECT a, b FROM items
14
+ # DB[:items].select(:a, :b___c, :c___b).select_remove(:c) # SELECT a, c AS b FROM items
15
+ # DB[:items].select(:a, :b___c, :c___b).select_remove(:c___b) # SELECT a, b AS c FROM items
16
+ #
17
+ # Note that there are a few cases where this method may not work correctly:
18
+ #
19
+ # * This dataset joins multiple tables and does not have an existing explicit selection.
20
+ # In this case, the code will currently use unqualified column names for all columns
21
+ # the dataset returns, except for the columns given.
22
+ # * This dataset has an existing explicit selection containing an item that returns
23
+ # multiple database columns (e.g. :table.*, 'column1, column2'.lit). In this case,
24
+ # the behavior is undefined and this method should not be used.
25
+ #
26
+ # There may be other cases where this method does not work correctly, use it with caution.
27
+ def select_remove(*cols)
28
+ if (sel = @opts[:select]) && !sel.empty?
29
+ select(*(columns.zip(sel).reject{|c, s| cols.include?(c)}.map{|c, s| s} - cols))
30
+ else
31
+ select(*(columns - cols))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,6 +1,6 @@
1
1
  # The sql_expr extension adds the sql_expr method to every object, which
2
- # returns an object that works nicely with Sequel's DSL. This is
3
- # best shown by example:
2
+ # returns an wrapped object that works nicely with Sequel's DSL by calling
3
+ # Sequel.expr:
4
4
  #
5
5
  # 1.sql_expr < :a # 1 < a
6
6
  # false.sql_expr & :a # FALSE AND a
@@ -8,115 +8,9 @@
8
8
  # ~nil.sql_expr # NOT NULL
9
9
  # "a".sql_expr + "b" # 'a' || 'b'
10
10
 
11
- module Sequel
12
- module SQL
13
- # The GenericComplexExpression acts like a
14
- # GenericExpression in terms of methods,
15
- # but has an internal structure of a
16
- # ComplexExpression. It is used by Object#sql_expr.
17
- # Since we don't know what specific type of object
18
- # we are dealing with it, we treat it similarly to
19
- # how we treat symbols or literal strings, allowing
20
- # many different types of methods.
21
- class GenericComplexExpression < ComplexExpression
22
- include AliasMethods
23
- include BooleanMethods
24
- include CastMethods
25
- include ComplexExpressionMethods
26
- include InequalityMethods
27
- include NumericMethods
28
- include OrderMethods
29
- include StringMethods
30
- include SubscriptMethods
31
- end
32
- end
33
- end
34
-
35
11
  class Object
36
- # Return a copy of the object wrapped in a
37
- # Sequel::SQL::GenericComplexExpression. Allows easy use
38
- # of the Object with Sequel's DSL. You'll probably have
39
- # to make sure that Sequel knows how to literalize the
40
- # object properly, though.
41
- def sql_expr
42
- Sequel::SQL::GenericComplexExpression.new(:NOOP, self)
43
- end
44
- end
45
-
46
- class FalseClass
47
- # Returns a copy of the object wrapped in a
48
- # Sequel::SQL::BooleanExpression, allowing easy use
49
- # of Sequel's DSL:
50
- #
51
- # false.sql_expr & :a # FALSE AND a
52
- def sql_expr
53
- Sequel::SQL::BooleanExpression.new(:NOOP, self)
54
- end
55
- end
56
-
57
- class NilClass
58
- # Returns a copy of the object wrapped in a
59
- # Sequel::SQL::BooleanExpression, allowing easy use
60
- # of Sequel's DSL:
61
- #
62
- # ~nil.sql_expr # NOT NULL
63
- def sql_expr
64
- Sequel::SQL::BooleanExpression.new(:NOOP, self)
65
- end
66
- end
67
-
68
- class Numeric
69
- # Returns a copy of the object wrapped in a
70
- # Sequel::SQL::NumericExpression, allowing easy use
71
- # of Sequel's DSL:
72
- #
73
- # 1.sql_expr < :a # 1 < a
74
- def sql_expr
75
- Sequel::SQL::NumericExpression.new(:NOOP, self)
76
- end
77
- end
78
-
79
- class Proc
80
- # Evaluates the proc as a virtual row block.
81
- # If a hash or array of two element arrays is returned,
82
- # they are converted to a Sequel::SQL::BooleanExpression. Otherwise,
83
- # unless the object returned is already an Sequel::SQL::Expression,
84
- # convert the object to an Sequel::SQL::GenericComplexExpression.
85
- #
86
- # proc{a(b)}.sql_expr + 1 # a(b) + 1
87
- # proc{{a=>b}}.sql_expr | true # (a = b) OR TRUE
88
- # proc{1}.sql_expr + :a # 1 + a
89
- def sql_expr
90
- o = Sequel.virtual_row(&self)
91
- if Sequel.condition_specifier?(o)
92
- Sequel::SQL::BooleanExpression.from_value_pairs(o, :AND)
93
- elsif o.is_a?(Sequel::SQL::Expression)
94
- o
95
- else
96
- Sequel::SQL::GenericComplexExpression.new(:NOOP, o)
97
- end
98
- end
99
- end
100
-
101
- class String
102
- # Returns a copy of the object wrapped in a
103
- # Sequel::SQL::StringExpression, allowing easy use
104
- # of Sequel's DSL:
105
- #
106
- # "a".sql_expr + :a # 'a' || a
12
+ # Return the object wrapper in an appropriate Sequel expression object.
107
13
  def sql_expr
108
- Sequel::SQL::StringExpression.new(:NOOP, self)
14
+ Sequel.expr(self)
109
15
  end
110
16
  end
111
-
112
- class TrueClass
113
- # Returns a copy of the object wrapped in a
114
- # Sequel::SQL::BooleanExpression, allowing easy use
115
- # of Sequel's DSL:
116
- #
117
- # true.sql_expr | :a # TRUE OR a
118
- def sql_expr
119
- Sequel::SQL::BooleanExpression.new(:NOOP, self)
120
- end
121
- end
122
-
@@ -47,7 +47,7 @@ module Sequel
47
47
  @stack.push(@i)
48
48
  case e
49
49
  when LiteralString
50
- dot "#{e.inspect}.lit"
50
+ dot "#{e.inspect}.lit" # core_sql use
51
51
  when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
52
52
  dot e.inspect
53
53
  when Array
data/lib/sequel/model.rb CHANGED
@@ -35,13 +35,18 @@ module Sequel
35
35
  # dataset # => DB1[:comments]
36
36
  # end
37
37
  def self.Model(source)
38
- Model::ANONYMOUS_MODEL_CLASSES[source] ||= if source.is_a?(Database)
38
+ if Sequel::Model.cache_anonymous_models && (klass = Model::ANONYMOUS_MODEL_CLASSES[source])
39
+ return klass
40
+ end
41
+ klass = if source.is_a?(Database)
39
42
  c = Class.new(Model)
40
43
  c.db = source
41
44
  c
42
45
  else
43
46
  Class.new(Model).set_dataset(source)
44
47
  end
48
+ Model::ANONYMOUS_MODEL_CLASSES[source] = klass if Sequel::Model.cache_anonymous_models
49
+ klass
45
50
  end
46
51
 
47
52
  # <tt>Sequel::Model</tt> is an object relational mapper built on top of Sequel core. Each
@@ -58,6 +63,9 @@ module Sequel
58
63
  # You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
59
64
  # make Sequel not load the associations plugin by default.
60
65
  class Model
66
+ # Cache anonymous models created by Sequel::Model()
67
+ @cache_anonymous_models = true
68
+
61
69
  # Map that stores model classes created with <tt>Sequel::Model()</tt>, to allow the reopening
62
70
  # of classes when dealing with code reloading.
63
71
  ANONYMOUS_MODEL_CLASSES = {}
@@ -105,7 +113,8 @@ module Sequel
105
113
  :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
106
114
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
107
115
  :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
108
- :@use_after_commit_rollback=>nil}
116
+ :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
117
+ :@fast_instance_delete_sql=>nil}
109
118
 
110
119
  # Regular expression that determines if a method name is normal in the sense that
111
120
  # it could be used literally in ruby code without using send. Used to
@@ -1373,6 +1373,14 @@ module Sequel
1373
1373
  def associations
1374
1374
  @associations ||= {}
1375
1375
  end
1376
+
1377
+ # Freeze the associations cache when freezing the object. Note that
1378
+ # retrieving associations after freezing will still work in most cases,
1379
+ # but the associations will not be cached in the association cache.
1380
+ def freeze
1381
+ associations.freeze
1382
+ super
1383
+ end
1376
1384
 
1377
1385
  # Formally used internally by the associations code, like pk but doesn't raise
1378
1386
  # an Error if the model has no primary key. Not used any longer, deprecated.
@@ -1424,15 +1432,29 @@ module Sequel
1424
1432
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
1425
1433
  end
1426
1434
 
1435
+ # Return the associated single object for the given association reflection and dynamic options
1436
+ # (or nil if no associated object).
1437
+ def _load_associated_object(opts, dynamic_opts)
1438
+ _load_associated_object_array(opts, dynamic_opts).first
1439
+ end
1440
+
1441
+ # Load the associated objects for the given association reflection and dynamic options
1442
+ # as an array.
1443
+ def _load_associated_object_array(opts, dynamic_opts)
1444
+ _associated_dataset(opts, dynamic_opts).all
1445
+ end
1446
+
1427
1447
  # Return the associated objects from the dataset, without association callbacks, reciprocals, and caching.
1428
1448
  # Still apply the dynamic callback if present.
1429
1449
  def _load_associated_objects(opts, dynamic_opts={})
1430
- if opts.returns_array?
1431
- opts.can_have_associated_objects?(self) ? _associated_dataset(opts, dynamic_opts).all : []
1432
- else
1433
- if opts.can_have_associated_objects?(self)
1434
- _associated_dataset(opts, dynamic_opts).all.first
1450
+ if opts.can_have_associated_objects?(self)
1451
+ if opts.returns_array?
1452
+ _load_associated_object_array(opts, dynamic_opts)
1453
+ else
1454
+ _load_associated_object(opts, dynamic_opts)
1435
1455
  end
1456
+ else
1457
+ [] if opts.returns_array?
1436
1458
  end
1437
1459
  end
1438
1460
 
@@ -1466,6 +1488,7 @@ module Sequel
1466
1488
 
1467
1489
  # Add/Set the current object to/as the given object's reciprocal association.
1468
1490
  def add_reciprocal_object(opts, o)
1491
+ return if o.frozen?
1469
1492
  return unless reciprocal = opts.reciprocal
1470
1493
  if opts.reciprocal_array?
1471
1494
  if array = o.associations[reciprocal] and !array.include?(self)
@@ -1514,9 +1537,14 @@ module Sequel
1514
1537
  add_reciprocal_object(opts, objs)
1515
1538
  end
1516
1539
  end
1517
- associations[name] = objs
1540
+
1541
+ # If the current object is frozen, you can't update the associations
1542
+ # cache. This can cause issues for after_load procs that expect
1543
+ # the objects to be already cached in the associations, but
1544
+ # unfortunately that case cannot be handled.
1545
+ associations[name] = objs unless frozen?
1518
1546
  run_association_callbacks(opts, :after_load, objs)
1519
- associations[name]
1547
+ frozen? ? objs : associations[name]
1520
1548
  end
1521
1549
  end
1522
1550
 
@@ -13,6 +13,11 @@ module Sequel
13
13
  # Which columns should be the only columns allowed in a call to a mass assignment method (e.g. set)
14
14
  # (default: not set, so all columns not otherwise restricted are allowed).
15
15
  attr_reader :allowed_columns
16
+
17
+ # Whether to cache the anonymous models created by Sequel::Model(). This is
18
+ # required for reloading them correctly (avoiding the superclass mismatch). True
19
+ # by default for backwards compatibility.
20
+ attr_accessor :cache_anonymous_models
16
21
 
17
22
  # Array of modules that extend this model's dataset. Stored
18
23
  # so that if the model's dataset is changed, it will be extended
@@ -24,6 +29,14 @@ module Sequel
24
29
  # will be applied to the new dataset.
25
30
  attr_reader :dataset_methods
26
31
 
32
+ # SQL string fragment used for faster DELETE statement creation when deleting/destroying
33
+ # model instances, or nil if the optimization should not be used. For internal use only.
34
+ attr_reader :fast_instance_delete_sql
35
+
36
+ # The dataset that instance datasets (#this) are based on. Generally a naked version of
37
+ # the model's dataset limited to one row. For internal use only.
38
+ attr_reader :instance_dataset
39
+
27
40
  # Array of plugin modules loaded by this class
28
41
  #
29
42
  # Sequel::Model.plugins
@@ -107,8 +120,8 @@ module Sequel
107
120
  # Artist[:name=>'Bob'] # SELECT * FROM artists WHERE (name = 'Bob') LIMIT 1
108
121
  # # => #<Artist {:name=>'Bob', ...}>
109
122
  def [](*args)
110
- args = args.first if (args.size == 1)
111
- args.is_a?(Hash) ? dataset[args] : primary_key_lookup(args)
123
+ args = args.first if args.size <= 1
124
+ args.is_a?(Hash) ? dataset[args] : (primary_key_lookup(args) unless args.nil?)
112
125
  end
113
126
 
114
127
  # Initializes a model instance as an existing record. This constructor is
@@ -394,7 +407,7 @@ module Sequel
394
407
  # Artist.primary_key # => nil
395
408
  def no_primary_key
396
409
  clear_setter_methods_cache
397
- @simple_pk = @primary_key = nil
410
+ self.simple_pk = @primary_key = nil
398
411
  end
399
412
 
400
413
  # Loads a plugin for use with the model class, passing optional arguments
@@ -402,16 +415,16 @@ module Sequel
402
415
  # require the plugin from either sequel/plugins/#{plugin} or
403
416
  # sequel_#{plugin}, and then attempt to load the module using a
404
417
  # the camelized plugin name under Sequel::Plugins.
405
- def plugin(plugin, *args, &blk)
418
+ def plugin(plugin, *args, &block)
406
419
  m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
407
420
  unless @plugins.include?(m)
408
421
  @plugins << m
409
- m.apply(self, *args, &blk) if m.respond_to?(:apply)
422
+ m.apply(self, *args, &block) if m.respond_to?(:apply)
410
423
  include(m::InstanceMethods) if plugin_module_defined?(m, :InstanceMethods)
411
424
  extend(m::ClassMethods)if plugin_module_defined?(m, :ClassMethods)
412
425
  dataset_extend(m::DatasetMethods) if plugin_module_defined?(m, :DatasetMethods)
413
426
  end
414
- m.configure(self, *args, &blk) if m.respond_to?(:configure)
427
+ m.configure(self, *args, &block) if m.respond_to?(:configure)
415
428
  end
416
429
 
417
430
  # Returns primary key attribute hash. If using a composite primary key
@@ -498,10 +511,10 @@ module Sequel
498
511
  inherited = opts[:inherited]
499
512
  @dataset = case ds
500
513
  when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
501
- @simple_table = db.literal(ds)
514
+ self.simple_table = db.literal(ds)
502
515
  db.from(ds)
503
516
  when Dataset
504
- @simple_table = if ds.send(:simple_select_all?)
517
+ self.simple_table = if ds.send(:simple_select_all?)
505
518
  ds.literal(ds.first_source_table)
506
519
  else
507
520
  nil
@@ -514,7 +527,7 @@ module Sequel
514
527
  @dataset.row_proc = self
515
528
  @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
516
529
  if inherited
517
- @simple_table = superclass.simple_table
530
+ self.simple_table = superclass.simple_table
518
531
  @columns = @dataset.columns rescue nil
519
532
  else
520
533
  @dataset_method_modules.each{|m| @dataset.extend(m)} if @dataset_method_modules
@@ -522,6 +535,7 @@ module Sequel
522
535
  end
523
536
  @dataset.model = self if @dataset.respond_to?(:model=)
524
537
  check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
538
+ @instance_dataset = @dataset.limit(1).naked
525
539
  self
526
540
  end
527
541
 
@@ -542,7 +556,7 @@ module Sequel
542
556
  def set_primary_key(*key)
543
557
  clear_setter_methods_cache
544
558
  key = key.flatten
545
- @simple_pk = if key.length == 1
559
+ self.simple_pk = if key.length == 1
546
560
  (@dataset || db).literal(key.first)
547
561
  else
548
562
  nil
@@ -552,7 +566,7 @@ module Sequel
552
566
 
553
567
  # Set the columns to restrict when using mass assignment (e.g. +set+). Using this means that
554
568
  # attempts to call setter methods for the columns listed here will cause an
555
- # exception or be silently skipped (based on the +strict_param_setting+ setting.
569
+ # exception or be silently skipped (based on the +strict_param_setting+ setting).
556
570
  # If you have any virtual setter methods (methods that end in =) that you
557
571
  # want not to be used during mass assignment, they need to be listed here as well (without the =).
558
572
  #
@@ -769,13 +783,31 @@ module Sequel
769
783
 
770
784
  # Find the row in the dataset that matches the primary key. Uses
771
785
  # a static SQL optimization if the table and primary key are simple.
786
+ #
787
+ # This method should not be called with a nil primary key, in case
788
+ # it is overridden by plugins which assume that the passed argument
789
+ # is valid.
772
790
  def primary_key_lookup(pk)
773
- if t = simple_table and p = simple_pk
774
- with_sql("SELECT * FROM #{t} WHERE #{p} = #{dataset.literal(pk)}").first
791
+ if sql = @fast_pk_lookup_sql
792
+ sql = sql.dup
793
+ dataset.literal_append(sql, pk)
794
+ dataset.fetch_rows(sql){|r| return call(r)}
795
+ nil
775
796
  else
776
797
  dataset[primary_key_hash(pk)]
777
798
  end
778
799
  end
800
+
801
+ # Reset the cached fast primary lookup SQL if a simple table and primary key
802
+ # are used, or set it to nil if not used.
803
+ def reset_fast_pk_lookup_sql
804
+ @fast_pk_lookup_sql = if @simple_table && @simple_pk
805
+ "SELECT * FROM #@simple_table WHERE #@simple_pk = ".freeze
806
+ end
807
+ @fast_instance_delete_sql = if @simple_table && @simple_pk
808
+ "DELETE FROM #@simple_table WHERE #@simple_pk = ".freeze
809
+ end
810
+ end
779
811
 
780
812
  # Set the columns for this model and create accessor methods for each column.
781
813
  def set_columns(new_columns)
@@ -784,6 +816,18 @@ module Sequel
784
816
  @columns
785
817
  end
786
818
 
819
+ # Reset the fast primary key lookup SQL when the simple_pk value changes.
820
+ def simple_pk=(pk)
821
+ @simple_pk = pk
822
+ reset_fast_pk_lookup_sql
823
+ end
824
+
825
+ # Reset the fast primary key lookup SQL when the simple_table value changes.
826
+ def simple_table=(t)
827
+ @simple_table = t
828
+ reset_fast_pk_lookup_sql
829
+ end
830
+
787
831
  # Add model methods that call dataset methods
788
832
  DATASET_METHODS.each{|arg| class_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
789
833
 
@@ -814,7 +858,7 @@ module Sequel
814
858
  # same name, caching the result in an instance variable. Define
815
859
  # standard attr_writer method for modifying that instance variable.
816
860
  def self.class_attr_overridable(*meths) # :nodoc:
817
- meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (@#{meth} = self.class.#{meth}) : @#{meth} end", __FILE__, __LINE__)}
861
+ meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (frozen? ? self.class.#{meth} : (@#{meth} = self.class.#{meth})) : @#{meth} end", __FILE__, __LINE__)}
818
862
  attr_writer(*meths)
819
863
  end
820
864
 
@@ -823,7 +867,7 @@ module Sequel
823
867
  #
824
868
  # define_method(meth){self.class.send(meth)}
825
869
  def self.class_attr_reader(*meths) # :nodoc:
826
- meths.each{|meth| class_eval("def #{meth}; model.#{meth} end", __FILE__, __LINE__)}
870
+ meths.each{|meth| class_eval("def #{meth}; self.class.#{meth} end", __FILE__, __LINE__)}
827
871
  end
828
872
 
829
873
  private_class_method :class_attr_overridable, :class_attr_reader
@@ -888,9 +932,10 @@ module Sequel
888
932
  # If the column isn't in @values, we can't assume it is
889
933
  # NULL in the database, so assume it has changed.
890
934
  v = typecast_value(column, value)
891
- if new? || !@values.include?(column) || v != (c = @values[column]) || v.class != c.class
935
+ vals = @values
936
+ if new? || !vals.include?(column) || v != (c = vals[column]) || v.class != c.class
892
937
  changed_columns << column unless changed_columns.include?(column)
893
- @values[column] = v
938
+ vals[column] = v
894
939
  end
895
940
  end
896
941
 
@@ -941,6 +986,7 @@ module Sequel
941
986
  # Artist[1].delete # DELETE FROM artists WHERE (id = 1)
942
987
  # # => #<Artist {:id=>1, ...}>
943
988
  def delete
989
+ raise Sequel::Error, "can't delete frozen object" if frozen?
944
990
  _delete
945
991
  self
946
992
  end
@@ -955,6 +1001,7 @@ module Sequel
955
1001
  # Artist[1].destroy # BEGIN; DELETE FROM artists WHERE (id = 1); COMMIT;
956
1002
  # # => #<Artist {:id=>1, ...}>
957
1003
  def destroy(opts = {})
1004
+ raise Sequel::Error, "can't destroy frozen object" if frozen?
958
1005
  checked_save_failure(opts){checked_transaction(opts){_destroy(opts)}}
959
1006
  end
960
1007
 
@@ -1002,6 +1049,19 @@ module Sequel
1002
1049
  @singleton_setter_added = true
1003
1050
  super
1004
1051
  end
1052
+
1053
+ # Freeze the object in such a way that it is still usable but not modifiable.
1054
+ # Once an object is frozen, you cannot modify it's values, changed_columns,
1055
+ # errors, or dataset.
1056
+ def freeze
1057
+ values.freeze
1058
+ changed_columns.freeze
1059
+ errors
1060
+ validate
1061
+ errors.freeze
1062
+ this.freeze unless new?
1063
+ super
1064
+ end
1005
1065
 
1006
1066
  # Value that should be unique for objects with the same class and pk (if pk is not nil), or
1007
1067
  # the same class and values (if pk is nil).
@@ -1106,7 +1166,12 @@ module Sequel
1106
1166
  # Artist[[1, 2]].pk # => [1, 2]
1107
1167
  def pk
1108
1168
  raise(Error, "No primary key is associated with this model") unless key = primary_key
1109
- key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
1169
+ if key.is_a?(Array)
1170
+ vals = @values
1171
+ key.map{|k| vals[k]}
1172
+ else
1173
+ @values[key]
1174
+ end
1110
1175
  end
1111
1176
 
1112
1177
  # Returns a hash identifying mapping the receivers primary key column(s) to their values.
@@ -1126,6 +1191,7 @@ module Sequel
1126
1191
  # a.refresh
1127
1192
  # a.name # => 'Bob'
1128
1193
  def refresh
1194
+ raise Sequel::Error, "can't refresh frozen object" if frozen?
1129
1195
  _refresh(this)
1130
1196
  end
1131
1197
 
@@ -1162,6 +1228,7 @@ module Sequel
1162
1228
  # +use_transactions+ setting
1163
1229
  # :validate :: set to false to skip validation
1164
1230
  def save(*columns)
1231
+ raise Sequel::Error, "can't save frozen object" if frozen?
1165
1232
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
1166
1233
  set_server(opts[:server]) if opts[:server]
1167
1234
  if opts[:validate] != false
@@ -1220,14 +1287,56 @@ module Sequel
1220
1287
  # For each of the fields in the given array +fields+, call the setter
1221
1288
  # method with the value of that +hash+ entry for the field. Returns self.
1222
1289
  #
1290
+ # You can provide an options hash, with the following options currently respected:
1291
+ # :missing :: Can be set to :skip to skip missing entries or :raise to raise an
1292
+ # Error for missing entries. The default behavior is not to check for
1293
+ # missing entries, in which case the default value is used. To be
1294
+ # friendly with most web frameworks, the missing check will also check
1295
+ # for the string version of the argument in the hash if given a symbol.
1296
+ #
1297
+ # Examples:
1298
+ #
1223
1299
  # artist.set_fields({:name=>'Jim'}, [:name])
1224
1300
  # artist.name # => 'Jim'
1225
1301
  #
1226
1302
  # artist.set_fields({:hometown=>'LA'}, [:name])
1227
1303
  # artist.name # => nil
1228
1304
  # artist.hometown # => 'Sac'
1229
- def set_fields(hash, fields)
1230
- fields.each{|f| send("#{f}=", hash[f])}
1305
+ #
1306
+ # artist.name # => 'Jim'
1307
+ # artist.set_fields({}, [:name], :missing=>:skip)
1308
+ # artist.name # => 'Jim'
1309
+ #
1310
+ # artist.name # => 'Jim'
1311
+ # artist.set_fields({}, [:name], :missing=>:raise)
1312
+ # # Sequel::Error raised
1313
+ def set_fields(hash, fields, opts=nil)
1314
+ if opts
1315
+ case opts[:missing]
1316
+ when :skip
1317
+ fields.each do |f|
1318
+ if hash.has_key?(f)
1319
+ send("#{f}=", hash[f])
1320
+ elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1321
+ send("#{sf}=", hash[sf])
1322
+ end
1323
+ end
1324
+ when :raise
1325
+ fields.each do |f|
1326
+ if hash.has_key?(f)
1327
+ send("#{f}=", hash[f])
1328
+ elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1329
+ send("#{sf}=", hash[sf])
1330
+ else
1331
+ raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
1332
+ end
1333
+ end
1334
+ else
1335
+ fields.each{|f| send("#{f}=", hash[f])}
1336
+ end
1337
+ else
1338
+ fields.each{|f| send("#{f}=", hash[f])}
1339
+ end
1231
1340
  self
1232
1341
  end
1233
1342
 
@@ -1268,7 +1377,7 @@ module Sequel
1268
1377
  # Artist[1].this
1269
1378
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1270
1379
  def this
1271
- @this ||= use_server(model.dataset.filter(pk_hash).limit(1).naked)
1380
+ @this ||= use_server(model.instance_dataset.filter(pk_hash))
1272
1381
  end
1273
1382
 
1274
1383
  # Runs #set with the passed hash and then runs save_changes.
@@ -1296,16 +1405,16 @@ module Sequel
1296
1405
  update_restricted(hash, false, except.flatten)
1297
1406
  end
1298
1407
 
1299
- # Update the instances values by calling +set_fields+ with the +hash+
1300
- # and +fields+, then save any changes to the record. Returns self.
1408
+ # Update the instances values by calling +set_fields+ with the arguments, then
1409
+ # saves any changes to the record. Returns self.
1301
1410
  #
1302
1411
  # artist.update_fields({:name=>'Jim'}, [:name])
1303
1412
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1304
1413
  #
1305
1414
  # artist.update_fields({:hometown=>'LA'}, [:name])
1306
1415
  # # UPDATE artists SET name = NULL WHERE (id = 1)
1307
- def update_fields(hash, fields)
1308
- set_fields(hash, fields)
1416
+ def update_fields(hash, fields, opts=nil)
1417
+ set_fields(hash, fields, opts)
1309
1418
  save_changes
1310
1419
  end
1311
1420
 
@@ -1358,14 +1467,20 @@ module Sequel
1358
1467
  # Actually do the deletion of the object's dataset. Return the
1359
1468
  # number of rows modified.
1360
1469
  def _delete_without_checking
1361
- _delete_dataset.delete
1470
+ if sql = (m = model).fast_instance_delete_sql
1471
+ sql = sql.dup
1472
+ (ds = m.dataset).literal_append(sql, pk)
1473
+ ds.with_sql_delete(sql)
1474
+ else
1475
+ _delete_dataset.delete
1476
+ end
1362
1477
  end
1363
1478
 
1364
1479
  # Internal destroy method, separted from destroy to
1365
1480
  # allow running inside a transaction
1366
1481
  def _destroy(opts)
1367
1482
  sh = {:server=>this_server}
1368
- db.after_rollback(sh){after_destroy_rollback} if use_after_commit_rollback
1483
+ db.after_rollback(sh){after_destroy_rollback} if uacr = use_after_commit_rollback
1369
1484
  called = false
1370
1485
  around_destroy do
1371
1486
  called = true
@@ -1375,7 +1490,7 @@ module Sequel
1375
1490
  true
1376
1491
  end
1377
1492
  raise_hook_failure(:destroy) unless called
1378
- db.after_commit(sh){after_destroy_commit} if use_after_commit_rollback
1493
+ db.after_commit(sh){after_destroy_commit} if uacr
1379
1494
  self
1380
1495
  end
1381
1496
 
@@ -1391,14 +1506,14 @@ module Sequel
1391
1506
  def _insert
1392
1507
  ds = _insert_dataset
1393
1508
  if !ds.opts[:select] and ds.supports_insert_select? and h = _insert_select_raw(ds)
1394
- @values = h
1509
+ set_values(h)
1395
1510
  nil
1396
1511
  else
1397
1512
  iid = _insert_raw(ds)
1398
1513
  # if we have a regular primary key and it's not set in @values,
1399
1514
  # we assume it's the last inserted id
1400
- if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
1401
- @values[pk] = iid
1515
+ if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !(vals = @values)[pk]
1516
+ vals[pk] = iid
1402
1517
  end
1403
1518
  pk
1404
1519
  end
@@ -1407,7 +1522,7 @@ module Sequel
1407
1522
  # The dataset to use when inserting a new object. The same as the model's
1408
1523
  # dataset by default.
1409
1524
  def _insert_dataset
1410
- use_server(model.dataset)
1525
+ use_server(model.instance_dataset)
1411
1526
  end
1412
1527
 
1413
1528
  # Insert into the given dataset and return the primary key created (if any).
@@ -1437,7 +1552,7 @@ module Sequel
1437
1552
  # it's own transaction.
1438
1553
  def _save(columns, opts)
1439
1554
  sh = {:server=>this_server}
1440
- db.after_rollback(sh){after_rollback} if use_after_commit_rollback
1555
+ db.after_rollback(sh){after_rollback} if uacr = use_after_commit_rollback
1441
1556
  was_new = false
1442
1557
  pk = nil
1443
1558
  called_save = false
@@ -1491,7 +1606,7 @@ module Sequel
1491
1606
  @columns_updated = nil
1492
1607
  end
1493
1608
  @modified = false
1494
- db.after_commit(sh){after_commit} if use_after_commit_rollback
1609
+ db.after_commit(sh){after_commit} if uacr
1495
1610
  self
1496
1611
  end
1497
1612
 
@@ -1544,6 +1659,7 @@ module Sequel
1544
1659
  # failures will be raised as HookFailure exceptions. If it is
1545
1660
  # +false+, +false+ will be returned instead.
1546
1661
  def _valid?(raise_errors, opts)
1662
+ return errors.empty? if frozen?
1547
1663
  errors.clear
1548
1664
  called = false
1549
1665
  error = false
@@ -1597,7 +1713,7 @@ module Sequel
1597
1713
  # exists so it can be overridden. This is called only for new records, before
1598
1714
  # changed_columns is cleared.
1599
1715
  def initialize_set(h)
1600
- set(h)
1716
+ set(h) unless h.empty?
1601
1717
  end
1602
1718
 
1603
1719
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
@@ -1621,6 +1737,7 @@ module Sequel
1621
1737
 
1622
1738
  # Set the columns, filtered by the only and except arrays.
1623
1739
  def set_restricted(hash, only, except)
1740
+ return self if hash.empty?
1624
1741
  meths = if only.nil? && except.nil? && !@singleton_setter_added
1625
1742
  model.setter_methods
1626
1743
  else
@@ -1679,7 +1796,13 @@ module Sequel
1679
1796
  # The server/shard that the model object's dataset uses, or :default if the
1680
1797
  # model object's dataset does not have an associated shard.
1681
1798
  def this_server
1682
- primary_key ? (this.opts[:server] || :default) : (model.dataset.opts[:server] || :default)
1799
+ if (s = @server)
1800
+ s
1801
+ elsif (t = @this)
1802
+ t.opts[:server] || :default
1803
+ else
1804
+ model.dataset.opts[:server] || :default
1805
+ end
1683
1806
  end
1684
1807
 
1685
1808
  # Typecast the value to the column's type if typecasting. Calls the database's