sequel 3.33.0 → 3.34.0

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