Skip to content

Commit

Permalink
Sort caches before marshalling when using schema_caching, index_cachi…
Browse files Browse the repository at this point in the history
…ng, static_cache_cache, and pg_auto_constraint_validations

The main reason to do this is so the cache value is deterministic
regardless of model load order.  This does not fully ensure the
cache value is deterministic, as that depends on whether the
cache values are deterministic.
  • Loading branch information
jeremyevans committed Aug 17, 2023
1 parent b50a05c commit af9c196
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
=== master

* Sort caches before marshalling when using schema_caching, index_caching, static_cache_cache, and pg_auto_constraint_validations (jeremyevans)

* Change the defaults_setter plugin do a deep-copy of database default hash/array values and delegates (jeremyevans) (#2069)

* Add pg_auto_parameterize_in_array extension, for converting IN/NOT IN to = ANY or != ALL for more types (jeremyevans)
Expand Down
6 changes: 5 additions & 1 deletion lib/sequel/extensions/index_caching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ def self.extended(db)

# Dump the index cache to the filename given in Marshal format.
def dump_index_cache(file)
File.open(file, 'wb'){|f| f.write(Marshal.dump(@indexes))}
indexes = {}
@indexes.sort.each do |k, v|
indexes[k] = v
end
File.open(file, 'wb'){|f| f.write(Marshal.dump(indexes))}
nil
end

Expand Down
2 changes: 1 addition & 1 deletion lib/sequel/extensions/schema_caching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module SchemaCaching
# Dump the cached schema to the filename given in Marshal format.
def dump_schema_cache(file)
sch = {}
@schemas.each do |k,v|
@schemas.sort.each do |k,v|
sch[k] = v.map do |c, h|
h = Hash[h]
h.delete(:callable_default)
Expand Down
6 changes: 5 additions & 1 deletion lib/sequel/plugins/pg_auto_constraint_validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ module ClassMethods
# Dump the in-memory cached metadata to the cache file.
def dump_pg_auto_constraint_validations_cache
raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
pg_auto_constraint_validations_cache = {}
@pg_auto_constraint_validations_cache.sort.each do |k, v|
pg_auto_constraint_validations_cache[k] = v
end
File.open(file, 'wb'){|f| f.write(Marshal.dump(pg_auto_constraint_validations_cache))}
nil
end

Expand Down
6 changes: 5 additions & 1 deletion lib/sequel/plugins/static_cache_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ def self.configure(model, file)
module ClassMethods
# Dump the in-memory cached rows to the cache file.
def dump_static_cache_cache
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(@static_cache_cache))}
static_cache_cache = {}
@static_cache_cache.sort.each do |k, v|
static_cache_cache[k] = v
end
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(static_cache_cache))}
nil
end

Expand Down
8 changes: 8 additions & 0 deletions spec/extensions/index_caching_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
File.size(@filename).must_be :>, 0
end

it "Database#dump_index_cache should dump index information sorted by table" do
@indexes['"foo"'] = {:table_idx_unique=>{:columns=>[:first_col, :second_col], :unique=>true, :deferrable=>nil}}
@db.dump_index_cache(@filename)
@db.instance_variable_get(:@indexes).keys.must_equal ['"table"', '"foo"']
@db.load_index_cache(@filename)
@db.instance_variable_get(:@indexes).keys.must_equal ['"foo"', '"table"']
end

it "Database#load_index_cache should load the index cache from the given file dumped by #dump_index_cache" do
@db.dump_index_cache(@filename)
db = Sequel::Database.new.extension(:index_caching)
Expand Down
35 changes: 35 additions & 0 deletions spec/extensions/pg_auto_constraint_validations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,41 @@ def c.name; 'Foo' end
end
end

it "should sort cache file by table name" do
cache_file = "spec/files/pgacv-spec-#{$$}.cache"
begin
c = Class.new(Sequel::Model)
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file

@ds = @db[:items]
@ds.send(:columns=, [:id, :i])
@db.fetch = @metadata_results.dup
@db.sqls
c1 = c::Model(@ds)
def c1.name; 'Foo' end
@db.sqls.length.must_equal 5

@ds = @db[:bars]
@ds.send(:columns=, [:id, :i])
@db.fetch = @metadata_results.dup
@db.sqls
c2 = c::Model(@ds)
c2.set_dataset @ds
def c2.name; 'Bar' end
@db.sqls.length.must_equal 5

c.instance_variable_get(:@pg_auto_constraint_validations_cache).keys.must_equal %w["items" "bars"]
c.dump_pg_auto_constraint_validations_cache
c.instance_variable_get(:@pg_auto_constraint_validations_cache).keys.must_equal %w["items" "bars"]

c3 = Class.new(Sequel::Model)
c3.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
c3.instance_variable_get(:@pg_auto_constraint_validations_cache).keys.must_equal %w["bars" "items"]
ensure
File.delete(cache_file) if File.file?(cache_file)
end
end

it "should raise error if attempting to dump cached metadata when not using caching" do
proc{@c.dump_pg_auto_constraint_validations_cache}.must_raise Sequel::Error
end
Expand Down
8 changes: 8 additions & 0 deletions spec/extensions/schema_caching_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
File.size(@filename).must_be :>, 0
end

it "Database#dump_schema_cache should dump schema sorted by table name" do
@schemas['"foo"'] = [[:column, {:db_type=>"integer", :default=>"nextval('table_id_seq'::regclass)", :allow_null=>false, :primary_key=>true, :type=>:integer, :ruby_default=>nil}]]
@db.dump_schema_cache(@filename)
@db.instance_variable_get(:@schemas).keys.must_equal ['"table"', '"foo"']
@db.load_schema_cache(@filename)
@db.instance_variable_get(:@schemas).keys.must_equal ['"foo"', '"table"']
end

it "Database#dump_schema_cache/load_schema_cache should work with :callable_default values set in schema_post_process" do
@schemas['"table"'][0][1][:callable_default] = lambda{1}
@schemas['"table"'][0][1][:default] = 'call_1'
Expand Down
18 changes: 18 additions & 0 deletions spec/extensions/static_cache_cache_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,22 @@ def c.name; 'Foo' end
@db.sqls.must_be_empty
@c.all.must_equal [@c.load(:id=>1, :name=>'A'), @c.load(:id=>2, :name=>'B')]
end

it "should sort cache file by model name" do
@c.plugin :static_cache_cache, @file
c1 = Class.new(@c)
def c1.name; 'Foo' end
c1.plugin :static_cache
c2 = Class.new(@c)
def c2.name; 'Bar' end
c2.plugin :static_cache

@c.instance_variable_get(:@static_cache_cache).keys.must_equal %w'Foo Bar'
@c.dump_static_cache_cache
@c.instance_variable_get(:@static_cache_cache).keys.must_equal %w'Foo Bar'

c = Class.new(Sequel::Model)
c.plugin :static_cache_cache, @file
c.instance_variable_get(:@static_cache_cache).keys.must_equal %w'Bar Foo'
end
end

0 comments on commit af9c196

Please sign in to comment.