foundries 0.1.6 → 0.1.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -10
- data/lib/foundries/base.rb +3 -1
- data/lib/foundries/snapshot/store.rb +35 -15
- data/lib/foundries/snapshot.rb +14 -0
- data/lib/foundries/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26171ce16e8737361c8ac3dcfbed573f17ec11d274eade9a26b465b6a687bfd2
|
|
4
|
+
data.tar.gz: 384a4c0d4f68ed3bb706262a3cea621666549d96e825b53884797c56f906cf8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53632c7bc3809bb486bad25597d4044d75c3b923a4657bca554d4ca57b58de2524bcd9d054fc86fdf1c3dae71920ab93c873cd72252b70e447a6b8af6b74da68
|
|
7
|
+
data.tar.gz: f73d427438035744750939c0a1f75491a3c930b1943bce3b97e007868a8e06ff0daeffaea0acd2e7937a322759991f074259a3d9a886d1371df8a6d78db93ce4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,21 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
|
-
## [0.1.
|
|
8
|
+
## [0.1.7] - 2026-09-10
|
|
9
9
|
|
|
10
|
-
###
|
|
10
|
+
### Added
|
|
11
11
|
|
|
12
|
-
- Snapshot
|
|
13
|
-
- Restoring a snapshot no longer rewinds a sequence below its current value (df66836)
|
|
12
|
+
- Foundries::Snapshot.with_lock for application-owned snapshot caches (fe91e06)
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
### Fixed
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
- Serialize snapshot publication and restoration across workers (fe91e06)
|
|
17
|
+
- Reject incomplete snapshots before restoring rows (fe91e06)
|
|
18
|
+
- Clean staging directories after failed snapshot captures (fe91e06)
|
|
19
|
+
- Restore parent context when a nested blueprint block raises (7fd6e4b)
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
## [0.1.6] - 2026-08-25
|
|
20
22
|
|
|
21
23
|
### Fixed
|
|
22
24
|
|
|
23
|
-
- Snapshot
|
|
24
|
-
-
|
|
25
|
-
- Releases write a SHA512 checksum again (813a2ae)
|
|
25
|
+
- Snapshot files are read and written in binary, so captured data with non-ASCII bytes round-trips (6a7d3a3)
|
|
26
|
+
- Restoring a snapshot no longer rewinds a sequence below its current value (df66836)
|
data/lib/foundries/base.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "tmpdir"
|
|
4
5
|
|
|
5
6
|
module Foundries
|
|
6
7
|
module Snapshot
|
|
@@ -18,9 +19,7 @@ module Foundries
|
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def cached?
|
|
21
|
-
|
|
22
|
-
cache_dir.join(".fingerprint").exist? &&
|
|
23
|
-
cache_dir.join(".fingerprint").read.strip == @fingerprint.current
|
|
22
|
+
with_lock { valid_cache? }
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
# Record which tables are empty before the preset block runs.
|
|
@@ -55,8 +54,8 @@ module Foundries
|
|
|
55
54
|
|
|
56
55
|
tables = @capturable_tables || @adapter.table_names
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
tmp_dir.
|
|
57
|
+
FileUtils.mkdir_p(@storage_path)
|
|
58
|
+
tmp_dir = Pathname.new(Dir.mktmpdir("#{@preset_name}.", @storage_path))
|
|
60
59
|
|
|
61
60
|
tables.each do |table|
|
|
62
61
|
tmp_dir.join("#{table}.dat").open("wb") do |f|
|
|
@@ -64,29 +63,50 @@ module Foundries
|
|
|
64
63
|
end
|
|
65
64
|
end
|
|
66
65
|
|
|
66
|
+
tmp_dir.join(".tables").write(tables.join("\n"))
|
|
67
67
|
tmp_dir.join(".fingerprint").write(@fingerprint.current)
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
with_lock do
|
|
70
|
+
FileUtils.rm_rf(cache_dir)
|
|
71
|
+
FileUtils.mv(tmp_dir, cache_dir)
|
|
72
|
+
end
|
|
73
|
+
ensure
|
|
74
|
+
FileUtils.rm_rf(tmp_dir) if tmp_dir
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
def restore
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
with_lock do
|
|
79
|
+
raise "Invalid or incomplete snapshot: #{@preset_name}" unless valid_cache?
|
|
80
|
+
|
|
81
|
+
@adapter.disable_referential_integrity do
|
|
82
|
+
captured_tables.each do |table|
|
|
83
|
+
file = cache_dir.join("#{table}.dat")
|
|
84
|
+
next unless file.size > 0
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@adapter.restore(table, f)
|
|
86
|
+
file.open("rb") { |io| @adapter.restore(table, io) }
|
|
87
|
+
@adapter.reset_sequence(table)
|
|
82
88
|
end
|
|
83
|
-
@adapter.reset_sequence(table)
|
|
84
89
|
end
|
|
85
90
|
end
|
|
86
91
|
end
|
|
87
92
|
|
|
88
93
|
private
|
|
89
94
|
|
|
95
|
+
def with_lock(&block)
|
|
96
|
+
Snapshot.with_lock(@preset_name, storage_path: @storage_path, &block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def captured_tables
|
|
100
|
+
cache_dir.join(".tables").read.lines.map(&:chomp)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def valid_cache?
|
|
104
|
+
stamp = cache_dir.join(".fingerprint")
|
|
105
|
+
stamp.file? && stamp.read.strip == @fingerprint.current &&
|
|
106
|
+
cache_dir.join(".tables").file? &&
|
|
107
|
+
captured_tables.all? { |table| cache_dir.join("#{table}.dat").file? }
|
|
108
|
+
end
|
|
109
|
+
|
|
90
110
|
# Pre-populated tables whose row count moved while the preset ran. Their
|
|
91
111
|
# new rows can't be told apart from the ones that were already there, so
|
|
92
112
|
# the snapshot would silently omit them.
|
data/lib/foundries/snapshot.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
|
+
require "fileutils"
|
|
4
5
|
require_relative "snapshot/fingerprint"
|
|
5
6
|
require_relative "snapshot/adapter"
|
|
6
7
|
require_relative "snapshot/adapters/postgres_adapter"
|
|
@@ -12,6 +13,19 @@ module Foundries
|
|
|
12
13
|
class << self
|
|
13
14
|
attr_writer :storage_path, :connection, :enabled, :source_paths
|
|
14
15
|
|
|
16
|
+
# Keep the lock outside the replaceable cache directory. Readers and
|
|
17
|
+
# publishers must hold it for the entire filesystem operation.
|
|
18
|
+
def with_lock(name, storage_path: self.storage_path)
|
|
19
|
+
FileUtils.mkdir_p(storage_path)
|
|
20
|
+
lock_path = File.join(storage_path, "#{name}.lock")
|
|
21
|
+
File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock|
|
|
22
|
+
lock.flock(File::LOCK_EX)
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
lock.flock(File::LOCK_UN)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
15
29
|
def storage_path
|
|
16
30
|
@storage_path || "tmp/foundries"
|
|
17
31
|
end
|
data/lib/foundries/version.rb
CHANGED