sequel 5.22.0 → 5.27.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 (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