sequel 5.22.0 → 5.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +78 -0
  3. data/README.rdoc +1 -1
  4. data/doc/dataset_filtering.rdoc +15 -0
  5. data/doc/opening_databases.rdoc +3 -0
  6. data/doc/postgresql.rdoc +2 -2
  7. data/doc/release_notes/5.23.0.txt +56 -0
  8. data/doc/release_notes/5.24.0.txt +56 -0
  9. data/doc/release_notes/5.25.0.txt +32 -0
  10. data/doc/release_notes/5.26.0.txt +35 -0
  11. data/doc/release_notes/5.27.0.txt +21 -0
  12. data/doc/testing.rdoc +1 -0
  13. data/lib/sequel/adapters/jdbc.rb +7 -1
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  15. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  16. data/lib/sequel/adapters/mysql2.rb +0 -1
  17. data/lib/sequel/adapters/shared/mssql.rb +9 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +30 -7
  19. data/lib/sequel/adapters/shared/sqlite.rb +23 -4
  20. data/lib/sequel/adapters/tinytds.rb +12 -0
  21. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  22. data/lib/sequel/database/logging.rb +7 -1
  23. data/lib/sequel/database/schema_generator.rb +11 -2
  24. data/lib/sequel/database/schema_methods.rb +2 -0
  25. data/lib/sequel/dataset/actions.rb +3 -2
  26. data/lib/sequel/dataset/features.rb +6 -0
  27. data/lib/sequel/dataset/sql.rb +17 -4
  28. data/lib/sequel/extensions/named_timezones.rb +51 -9
  29. data/lib/sequel/extensions/pg_array.rb +4 -0
  30. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  31. data/lib/sequel/extensions/pg_enum.rb +4 -1
  32. data/lib/sequel/extensions/pg_json.rb +88 -17
  33. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  34. data/lib/sequel/extensions/pg_range.rb +9 -0
  35. data/lib/sequel/extensions/pg_row.rb +3 -1
  36. data/lib/sequel/extensions/sql_comments.rb +2 -2
  37. data/lib/sequel/model/base.rb +12 -5
  38. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  39. data/lib/sequel/plugins/association_proxies.rb +3 -2
  40. data/lib/sequel/plugins/caching.rb +3 -0
  41. data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
  42. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  43. data/lib/sequel/plugins/dirty.rb +3 -9
  44. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  45. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  46. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  47. data/lib/sequel/plugins/sharding.rb +11 -5
  48. data/lib/sequel/plugins/static_cache.rb +8 -3
  49. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  51. data/lib/sequel/sql.rb +4 -1
  52. data/lib/sequel/timezones.rb +50 -11
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +135 -7
  55. data/spec/adapters/sqlite_spec.rb +1 -1
  56. data/spec/bin_spec.rb +2 -2
  57. data/spec/core/database_spec.rb +50 -0
  58. data/spec/core/dataset_spec.rb +23 -1
  59. data/spec/core/expression_filters_spec.rb +22 -3
  60. data/spec/core/schema_spec.rb +18 -0
  61. data/spec/core/spec_helper.rb +1 -1
  62. data/spec/core_extensions_spec.rb +1 -1
  63. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  64. data/spec/extensions/dirty_spec.rb +33 -0
  65. data/spec/extensions/insert_conflict_spec.rb +103 -0
  66. data/spec/extensions/named_timezones_spec.rb +109 -2
  67. data/spec/extensions/nested_attributes_spec.rb +48 -0
  68. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  69. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  70. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  71. data/spec/extensions/pg_json_spec.rb +12 -0
  72. data/spec/extensions/pg_range_spec.rb +19 -2
  73. data/spec/extensions/sharding_spec.rb +8 -0
  74. data/spec/extensions/spec_helper.rb +9 -2
  75. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  76. data/spec/guards_helper.rb +1 -1
  77. data/spec/integration/dataset_test.rb +25 -0
  78. data/spec/integration/plugin_test.rb +28 -1
  79. data/spec/integration/schema_test.rb +16 -2
  80. data/spec/integration/spec_helper.rb +7 -1
  81. data/spec/model/spec_helper.rb +1 -1
  82. metadata +32 -2
@@ -1069,7 +1069,7 @@ module Sequel
1069
1069
  @new = true
1070
1070
  @modified = true
1071
1071
  initialize_set(values)
1072
- _changed_columns.clear
1072
+ _clear_changed_columns(:initialize)
1073
1073
  yield self if block_given?
1074
1074
  end
1075
1075
 
@@ -1626,6 +1626,13 @@ module Sequel
1626
1626
  def _changed_columns
1627
1627
  @changed_columns ||= []
1628
1628
  end
1629
+
1630
+ # Clear the changed columns. Reason is the reason for clearing
1631
+ # the columns, and should be one of: :initialize, :refresh, :create
1632
+ # or :update.
1633
+ def _clear_changed_columns(_reason)
1634
+ _changed_columns.clear
1635
+ end
1629
1636
 
1630
1637
  # Do the deletion of the object's dataset, and check that the row
1631
1638
  # was actually deleted.
@@ -1716,7 +1723,7 @@ module Sequel
1716
1723
  # is used for reading newly inserted values from the database
1717
1724
  def _refresh(dataset)
1718
1725
  _refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
1719
- _changed_columns.clear
1726
+ _clear_changed_columns(:refresh)
1720
1727
  end
1721
1728
 
1722
1729
  # Get the row of column data from the database.
@@ -1754,7 +1761,7 @@ module Sequel
1754
1761
  @this = nil
1755
1762
  @new = false
1756
1763
  @modified = false
1757
- pk ? _save_refresh : _changed_columns.clear
1764
+ pk ? _save_refresh : _clear_changed_columns(:create)
1758
1765
  after_create
1759
1766
  true
1760
1767
  end
@@ -1771,7 +1778,7 @@ module Sequel
1771
1778
  cc.clear
1772
1779
  else
1773
1780
  columns_updated = _save_update_all_columns_hash
1774
- _changed_columns.clear
1781
+ _clear_changed_columns(:update)
1775
1782
  end
1776
1783
  else # update only the specified columns
1777
1784
  columns = Array(columns)
@@ -1798,7 +1805,7 @@ module Sequel
1798
1805
  # can be overridden to avoid the refresh.
1799
1806
  def _save_refresh
1800
1807
  _save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
1801
- _changed_columns.clear
1808
+ _clear_changed_columns(:create)
1802
1809
  end
1803
1810
 
1804
1811
  # Set values to the provided hash. Called after a create,
@@ -0,0 +1,83 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The association_multi_add_remove plugin allows adding, removing and setting
6
+ # multiple associated objects in a single method call.
7
+ # By default Sequel::Model defines singular <tt>add_*</tt> and <tt>remove_*</tt>
8
+ # methods that operate on a single associated object, this adds plural forms
9
+ # that operate on multiple associated objects. Example:
10
+ #
11
+ # artist.albums # => [album1]
12
+ # artist.add_albums([album2, album3])
13
+ # artist.albums # => [album1, album2, album3]
14
+ # artist.remove_albums([album3, album1])
15
+ # artist.albums # => [album2]
16
+ # artist.albums = [album2, album3]
17
+ # artist.albums # => [album2, album3]
18
+ #
19
+ # It can handle all situations that the normal singular methods handle, but there is
20
+ # no attempt to optimize behavior, so using these methods will not improve performance.
21
+ #
22
+ # The add/remove/set methods defined by this plugin use a transaction,
23
+ # so if one add/remove/set fails and raises an exception, all adds/removes/set
24
+ # will be rolled back. If you are using database sharding and want to save
25
+ # to a specific shard, call Model#set_server to set the server for this instance,
26
+ # as the transaction will be opened on that server.
27
+ #
28
+ # You can customize the method names used for adding/removing multiple associated
29
+ # objects using the :multi_add_method and :multi_remove_method association options.
30
+ #
31
+ # Usage:
32
+ #
33
+ # # Allow adding/removing/setting multiple associated objects in a single call
34
+ # # for all model subclass instances (called before loading subclasses):
35
+ # Sequel::Model.plugin :association_multi_add_remove
36
+ #
37
+ # # Allow adding/removing/setting multiple associated objects in a single call
38
+ # # for Album instances (called before defining associations in the class):
39
+ # Album.plugin :association_multi_add_remove
40
+ module AssociationMultiAddRemove
41
+ module ClassMethods
42
+ # Define the methods use to add/remove/set multiple associated objects
43
+ # in a single method call.
44
+ def def_association_instance_methods(opts)
45
+ super
46
+
47
+ if opts[:adder]
48
+ add_method = opts[:add_method]
49
+ multi_add_method = opts[:multi_add_method] || :"add_#{opts[:name]}"
50
+ multi_add_method = nil if add_method == multi_add_method
51
+ if multi_add_method
52
+ association_module_def(multi_add_method, opts) do |objs, *args|
53
+ db.transaction(:server=>@server){objs.map{|obj| send(add_method, obj, *args)}.compact}
54
+ end
55
+ end
56
+ end
57
+
58
+ if opts[:remover]
59
+ remove_method = opts[:remove_method]
60
+ multi_remove_method = opts[:multi_remove_method] || :"remove_#{opts[:name]}"
61
+ multi_remove_method = nil if remove_method == multi_remove_method
62
+ if multi_remove_method
63
+ association_module_def(multi_remove_method, opts) do |objs, *args|
64
+ db.transaction(:server=>@server){objs.map{|obj| send(remove_method, obj, *args)}.compact}
65
+ end
66
+ end
67
+ end
68
+
69
+ if multi_add_method && multi_remove_method
70
+ association_module_def(:"#{opts[:name]}=", opts) do |objs, *args|
71
+ db.transaction(:server=>@server) do
72
+ existing_objs = send(opts.association_method)
73
+ send(multi_remove_method, (existing_objs - objs), *args)
74
+ send(multi_add_method, (objs - existing_objs), *args)
75
+ nil
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -64,6 +64,8 @@ module Sequel
64
64
  array = [].freeze
65
65
 
66
66
  if RUBY_VERSION < '2.6'
67
+ # :nocov:
68
+
67
69
  # Default proc used to determine whether to send the method to the dataset.
68
70
  # If the array would respond to it, sends it to the array instead of the dataset.
69
71
  DEFAULT_PROXY_TO_DATASET = proc do |opts|
@@ -73,10 +75,9 @@ module Sequel
73
75
  end
74
76
  !array_method
75
77
  end
76
- else
77
78
  # :nocov:
79
+ else
78
80
  DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
79
- # :nocov:
80
81
  end
81
82
 
82
83
  # Set the association reflection to use, and whether the association should be
@@ -26,6 +26,9 @@ module Sequel
26
26
  # * Model.with_pk!
27
27
  # * Model.[] # when argument is not hash or nil
28
28
  # * many_to_one association method # without dynamic callback, when primary key matches
29
+ #
30
+ # You should not use this plugin if you are using sharding and there are different
31
+ # rows for the same primary key on different shards.
29
32
  #
30
33
  # Usage:
31
34
  #
@@ -108,6 +108,16 @@ module Sequel
108
108
  # a = Executive.where{{employees[:id]=>1}}.first # works
109
109
  # a = Executive.where{{executives[:id]=>1}}.first # doesn't work
110
110
  #
111
+ # Note that because subclass datasets select from a subquery, you cannot update,
112
+ # delete, or insert into them directly. To delete related rows, you need to go
113
+ # through the related tables and remove the related rows. Code that does this would
114
+ # be similar to:
115
+ #
116
+ # pks = Executive.where{num_staff < 10}.select_map(:id)
117
+ # Executive.cti_tables.reverse_each do |table|
118
+ # DB.from(table).where(:id=>pks).delete
119
+ # end
120
+ #
111
121
  # = Usage
112
122
  #
113
123
  # # Use the default of storing the class name in the sti_key
@@ -65,8 +65,6 @@ module Sequel
65
65
  # # Add CSV output capability to Album class instances
66
66
  # Album.plugin :csv_serializer
67
67
  module CsvSerializer
68
- CSV = Object.const_defined?(:CSV) ? ::CSV : ::FasterCSV
69
-
70
68
  # Set up the column readers to do deserialization and the column writers
71
69
  # to save the value in deserialized_values
72
70
  def self.configure(model, opts = OPTS)
@@ -75,13 +73,29 @@ module Sequel
75
73
  end
76
74
  end
77
75
 
76
+ # Avoid keyword argument separation warnings on Ruby 2.7, while still
77
+ # being compatible with 1.9.
78
+ if RUBY_VERSION >= "2.0"
79
+ instance_eval(<<-END, __FILE__, __LINE__+1)
80
+ def self.csv_call(*args, opts, &block)
81
+ CSV.send(*args, **opts, &block)
82
+ end
83
+ END
84
+ else
85
+ # :nodoc:
86
+ def self.csv_call(*args, opts, &block)
87
+ CSV.send(*args, opts, &block)
88
+ end
89
+ # :nodoc:
90
+ end
91
+
78
92
  module ClassMethods
79
93
  # The default opts to use when serializing model objects to CSV
80
94
  attr_reader :csv_serializer_opts
81
95
 
82
96
  # Attempt to parse an array of instances from the given CSV string
83
97
  def array_from_csv(csv, opts = OPTS)
84
- CSV.parse(csv, process_csv_serializer_opts(opts)).map do |row|
98
+ CsvSerializer.csv_call(:parse, csv, process_csv_serializer_opts(opts)).map do |row|
85
99
  row = row.to_hash
86
100
  row.delete(nil)
87
101
  new(row)
@@ -108,7 +122,8 @@ module Sequel
108
122
  opts_cols = opts.delete(:columns)
109
123
  opts_include = opts.delete(:include)
110
124
  opts_except = opts.delete(:except)
111
- opts[:headers] ||= Array(opts.delete(:only) || opts_cols || columns) + Array(opts_include) - Array(opts_except)
125
+ only = opts.delete(:only)
126
+ opts[:headers] ||= Array(only || opts_cols || columns) + Array(opts_include) - Array(opts_except)
112
127
  opts
113
128
  end
114
129
 
@@ -130,7 +145,7 @@ module Sequel
130
145
  # :headers :: The headers to use for the CSV line. Use nil for a header
131
146
  # to specify the column should be ignored.
132
147
  def from_csv(csv, opts = OPTS)
133
- row = CSV.parse_line(csv, model.process_csv_serializer_opts(opts)).to_hash
148
+ row = CsvSerializer.csv_call(:parse_line, csv, model.process_csv_serializer_opts(opts)).to_hash
134
149
  row.delete(nil)
135
150
  set(row)
136
151
  end
@@ -146,9 +161,10 @@ module Sequel
146
161
  # attributes to include in the CSV output.
147
162
  def to_csv(opts = OPTS)
148
163
  opts = model.process_csv_serializer_opts(opts)
164
+ headers = opts[:headers]
149
165
 
150
- CSV.generate(opts) do |csv|
151
- csv << opts[:headers].map{|k| public_send(k)}
166
+ CsvSerializer.csv_call(:generate, model.process_csv_serializer_opts(opts)) do |csv|
167
+ csv << headers.map{|k| public_send(k)}
152
168
  end
153
169
  end
154
170
  end
@@ -164,10 +180,11 @@ module Sequel
164
180
  def to_csv(opts = OPTS)
165
181
  opts = model.process_csv_serializer_opts({:columns=>columns}.merge!(opts))
166
182
  items = opts.delete(:array) || self
183
+ headers = opts[:headers]
167
184
 
168
- CSV.generate(opts) do |csv|
185
+ CsvSerializer.csv_call(:generate, opts) do |csv|
169
186
  items.each do |object|
170
- csv << opts[:headers].map{|header| object.public_send(header) }
187
+ csv << headers.map{|header| object.public_send(header)}
171
188
  end
172
189
  end
173
190
  end
@@ -159,9 +159,9 @@ module Sequel
159
159
 
160
160
  private
161
161
 
162
- # Reset the initial values when setting values.
163
- def _refresh_set_values(hash)
164
- reset_initial_values
162
+ # Reset initial values when clearing changed columns
163
+ def _clear_changed_columns(reason)
164
+ reset_initial_values if reason == :initialize || reason == :refresh
165
165
  super
166
166
  end
167
167
 
@@ -214,12 +214,6 @@ module Sequel
214
214
  self
215
215
  end
216
216
 
217
- # Reset the initial values when initializing.
218
- def initialize_set(h)
219
- super
220
- reset_initial_values
221
- end
222
-
223
217
  # Array holding column symbols that were not present initially. This is necessary
224
218
  # to differentiate between values that were not present and values that were
225
219
  # present but equal to nil.
@@ -0,0 +1,72 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The insert_conflict plugin allows handling conflicts due to unique
6
+ # constraints when saving new model instance, using the INSERT ON CONFLICT
7
+ # support in PostgreSQL 9.5+ and SQLite 3.24.0+. Example:
8
+ #
9
+ # class Album < Sequel::Model
10
+ # plugin :insert_conflict
11
+ # end
12
+ #
13
+ # Album.new(name: 'Foo', copies_sold: 1000).
14
+ # insert_conflict(
15
+ # target: :name,
16
+ # update: {copies_sold: Sequel[:excluded][:b]}
17
+ # ).
18
+ # save
19
+ #
20
+ # This example will try to insert the album, but if there is an existing
21
+ # album with the name 'Foo', this will update the copies_sold attribute
22
+ # for that album. See the PostgreSQL and SQLite adapter documention for
23
+ # the options you can pass to the insert_conflict method.
24
+ #
25
+ # Usage:
26
+ #
27
+ # # Make all model subclasses support insert_conflict
28
+ # Sequel::Model.plugin :insert_conflict
29
+ #
30
+ # # Make the Album class support insert_conflict
31
+ # Album.plugin :insert_conflict
32
+ module InsertConflict
33
+ def self.configure(model)
34
+ model.instance_exec do
35
+ if @dataset && !@dataset.respond_to?(:insert_conflict)
36
+ raise Error, "#{self}'s dataset does not support insert_conflict"
37
+ end
38
+ end
39
+ end
40
+
41
+ module InstanceMethods
42
+ # Set the insert_conflict options to pass to the dataset when inserting.
43
+ def insert_conflict(opts=OPTS)
44
+ raise Error, "Model#insert_conflict is only supported on new model instances" unless new?
45
+ @insert_conflict_opts = opts
46
+ self
47
+ end
48
+
49
+ private
50
+
51
+ # Set the dataset used for inserting to use INSERT ON CONFLICT
52
+ # Model#insert_conflict has been called on the instance previously.
53
+ def _insert_dataset
54
+ ds = super
55
+
56
+ if @insert_conflict_opts
57
+ ds = ds.insert_conflict(@insert_conflict_opts)
58
+ end
59
+
60
+ ds
61
+ end
62
+
63
+ # Disable the use of prepared insert statements, as they are not compatible
64
+ # with this plugin.
65
+ def use_prepared_statements_for?(type)
66
+ return false if type == :insert || type == :insert_select
67
+ super if defined?(super)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -113,6 +113,10 @@ module Sequel
113
113
  # value, the attribute hash is ignored.
114
114
  # :remove :: Allow disassociation of nested records (can remove the associated
115
115
  # object from the parent object, but not destroy the associated object).
116
+ # :require_modification :: Whether to require modification of nested objects when
117
+ # updating or deleting them (checking that a single row was
118
+ # updated). By default, uses the default require_modification
119
+ # setting for the nested object.
116
120
  # :transform :: A proc to transform attribute hashes before they are
117
121
  # passed to associated object. Takes two arguments, the parent object and
118
122
  # the attribute hash. Uses the return value as the new attribute hash.
@@ -282,6 +286,9 @@ module Sequel
282
286
  obj = Array(public_send(reflection[:name])).find{|x| Array(x.pk).map(&:to_s) == pk}
283
287
  end
284
288
  if obj
289
+ unless (require_modification = meta[:require_modification]).nil?
290
+ obj.require_modification = require_modification
291
+ end
285
292
  attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
286
293
  if meta[:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
287
294
  nested_attributes_remove(meta, obj, :destroy=>true)
@@ -45,6 +45,31 @@ module Sequel
45
45
  # to be associated to particular column(s), and use a specific error message:
46
46
  #
47
47
  # Album.pg_auto_constraint_validation_override(:constraint_name, [:column1], "validation error message")
48
+ #
49
+ # Using the pg_auto_constraint_validations plugin requires 5 queries per
50
+ # model at load time in order to gather the necessary metadata. For applications
51
+ # with a large number of models, this can result in a noticeable delay during model
52
+ # initialization. To mitigate this issue, you can cache the necessary metadata in
53
+ # a file with the :cache_file option:
54
+ #
55
+ # Sequel::Model.plugin :pg_auto_constraint_validations, cache_file: 'db/pgacv.cache'
56
+ #
57
+ # The file does not have to exist when loading the plugin. If it exists, the plugin
58
+ # will load the cache and use the cached results instead of issuing queries if there
59
+ # is an entry in the cache. If there is no entry in the cache, it will update the
60
+ # in-memory cache with the metadata results. To save the in in-memory cache back to
61
+ # the cache file, run:
62
+ #
63
+ # Sequel::Model.dump_pg_auto_constraint_validations_cache
64
+ #
65
+ # Note that when using the :cache_file option, it is up to the application to ensure
66
+ # that the dumped cached metadata reflects the current state of the database. Sequel
67
+ # does no checking to ensure this, as checking would take time and the
68
+ # purpose of this code is to take a shortcut.
69
+ #
70
+ # The cached schema is dumped in Marshal format, since it is the fastest
71
+ # and it handles all ruby objects used in the metadata. Because of this,
72
+ # you should not attempt to load the metadata from a untrusted file.
48
73
  #
49
74
  # Usage:
50
75
  #
@@ -67,13 +92,28 @@ module Sequel
67
92
  }.freeze).each_value(&:freeze)
68
93
 
69
94
  # Setup the constraint violation metadata. Options:
95
+ # :cache_file :: File storing cached metadata, to avoid queries for each model
70
96
  # :messages :: Override the default error messages for each constraint
71
97
  # violation type (:not_null, :check, :unique, :foreign_key, :referenced_by)
72
98
  def self.configure(model, opts=OPTS)
73
99
  model.instance_exec do
100
+ if @pg_auto_constraint_validations_cache_file = opts[:cache_file]
101
+ @pg_auto_constraint_validations_cache = if ::File.file?(@pg_auto_constraint_validations_cache_file)
102
+ cache = Marshal.load(File.read(@pg_auto_constraint_validations_cache_file))
103
+ cache.each_value do |hash|
104
+ hash.freeze.each_value(&:freeze)
105
+ end
106
+ else
107
+ {}
108
+ end
109
+ else
110
+ @pg_auto_constraint_validations_cache = nil
111
+ end
112
+
74
113
  setup_pg_auto_constraint_validations
75
114
  @pg_auto_constraint_validations_messages = (@pg_auto_constraint_validations_messages || DEFAULT_ERROR_MESSAGES).merge(opts[:messages] || OPTS).freeze
76
115
  end
116
+ nil
77
117
  end
78
118
 
79
119
  module ClassMethods
@@ -85,9 +125,16 @@ module Sequel
85
125
  # generated validation failures.
86
126
  attr_reader :pg_auto_constraint_validations_messages
87
127
 
88
- Plugins.inherited_instance_variables(self, :@pg_auto_constraint_validations=>nil, :@pg_auto_constraint_validations_messages=>nil)
128
+ Plugins.inherited_instance_variables(self, :@pg_auto_constraint_validations=>nil, :@pg_auto_constraint_validations_messages=>nil, :@pg_auto_constraint_validations_cache=>nil, :@pg_auto_constraint_validations_cache_file=>nil)
89
129
  Plugins.after_set_dataset(self, :setup_pg_auto_constraint_validations)
90
130
 
131
+ # Dump the in-memory cached metadata to the cache file.
132
+ def dump_pg_auto_constraint_validations_cache
133
+ raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
134
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
135
+ nil
136
+ end
137
+
91
138
  # Override the constraint validation columns and message for a given constraint
92
139
  def pg_auto_constraint_validation_override(constraint, columns, message)
93
140
  pgacv = Hash[@pg_auto_constraint_validations]
@@ -122,39 +169,51 @@ module Sequel
122
169
  return
123
170
  end
124
171
 
125
- checks = {}
126
- indexes = {}
127
- foreign_keys = {}
128
- referenced_by = {}
172
+ cache = @pg_auto_constraint_validations_cache
173
+ literal_table_name = dataset.literal(table_name)
174
+ unless cache && (metadata = cache[literal_table_name])
175
+ checks = {}
176
+ indexes = {}
177
+ foreign_keys = {}
178
+ referenced_by = {}
129
179
 
130
- db.check_constraints(table_name).each do |k, v|
131
- checks[k] = v[:columns].dup.freeze unless v[:columns].empty?
132
- end
133
- db.indexes(table_name, :include_partial=>true).each do |k, v|
134
- if v[:unique]
135
- indexes[k] = v[:columns].dup.freeze
180
+ db.check_constraints(table_name).each do |k, v|
181
+ checks[k] = v[:columns].dup.freeze unless v[:columns].empty?
182
+ end
183
+ db.indexes(table_name, :include_partial=>true).each do |k, v|
184
+ if v[:unique]
185
+ indexes[k] = v[:columns].dup.freeze
186
+ end
187
+ end
188
+ db.foreign_key_list(table_name, :schema=>false).each do |fk|
189
+ foreign_keys[fk[:name]] = fk[:columns].dup.freeze
190
+ end
191
+ db.foreign_key_list(table_name, :reverse=>true, :schema=>false).each do |fk|
192
+ referenced_by[[fk[:schema], fk[:table], fk[:name]].freeze] = fk[:key].dup.freeze
193
+ end
194
+
195
+ schema, table = db[:pg_class].
196
+ join(:pg_namespace, :oid=>:relnamespace, db.send(:regclass_oid, table_name)=>:oid).
197
+ get([:nspname, :relname])
198
+
199
+ metadata = {
200
+ :schema=>schema,
201
+ :table=>table,
202
+ :check=>checks,
203
+ :unique=>indexes,
204
+ :foreign_key=>foreign_keys,
205
+ :referenced_by=>referenced_by,
206
+ :overrides=>OPTS
207
+ }.freeze
208
+ metadata.each_value(&:freeze)
209
+
210
+ if cache
211
+ cache[literal_table_name] = metadata
136
212
  end
137
- end
138
- db.foreign_key_list(table_name, :schema=>false).each do |fk|
139
- foreign_keys[fk[:name]] = fk[:columns].dup.freeze
140
- end
141
- db.foreign_key_list(table_name, :reverse=>true, :schema=>false).each do |fk|
142
- referenced_by[[fk[:schema], fk[:table], fk[:name]].freeze] = fk[:key].dup.freeze
143
213
  end
144
214
 
145
- schema, table = db[:pg_class].
146
- join(:pg_namespace, :oid=>:relnamespace, db.send(:regclass_oid, table_name)=>:oid).
147
- get([:nspname, :relname])
148
-
149
- (@pg_auto_constraint_validations = {
150
- :schema=>schema,
151
- :table=>table,
152
- :check=>checks,
153
- :unique=>indexes,
154
- :foreign_key=>foreign_keys,
155
- :referenced_by=>referenced_by,
156
- :overrides=>OPTS
157
- }.freeze).each_value(&:freeze)
215
+ @pg_auto_constraint_validations = metadata
216
+ nil
158
217
  end
159
218
  end
160
219