schema-evolution-manager 0.9.57 → 0.9.58

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ece8bcecc0151f8979530da79102bdf066a3cc1dfae14cd07074c0ae497cee8f
4
- data.tar.gz: e7af8d5f30e83690323fde18ed2cd78d0f557303545d30eb466e491c0748886f
3
+ metadata.gz: 5a59b34cee82db218b0008110f365a64567ec8dcbaa9f59a0fcd30acce9586f0
4
+ data.tar.gz: 5a6066de334be67b4915debfc0670e27c56d40eeb008c60bbc2348da8be37603
5
5
  SHA512:
6
- metadata.gz: bdfa161a91f7c86f29ff39fe2fb339468b43fa02ece9139ceecd523e875408a2b4dcbf6b92204f43a0dbe353efcca2c77d4d6bfd1c174c15a09b51df5c5c551b
7
- data.tar.gz: 5f593c267b185fdaf0dcf7bd3f31bddc0dc1bd099c245abcaf833cad29c10c7209487c24f0862b17ac09295c0d2c431ea0c5ef30db52d28816cfb183fb890a0c
6
+ metadata.gz: 21fe48093f5386013d3eed04dec8a9ebd512a670608a6b5a663b2c68c0b9af3fedb910577a58c3e3abe8098d3e66ac76cc25019cad0ebaa47e22014aa061c10c
7
+ data.tar.gz: 447e2949614cb4f2c332f317596195b796ff811016d8d6259b42a351e59b8a04c324b8bbb8bae3e8248b9a521bb3ba719db4d35676ea2ad1ebbc011ce6984caf
data/README.md CHANGED
@@ -133,7 +133,7 @@ There are three ways to install schema evolution manager:
133
133
 
134
134
  git clone git@github.com:mbryzek/schema-evolution-manager.git
135
135
  cd schema-evolution-manager
136
- git checkout 0.9.57
136
+ git checkout 0.9.58
137
137
  ruby ./configure.rb
138
138
  sudo ./install.rb
139
139
 
data/bin/sem-apply CHANGED
@@ -34,6 +34,16 @@ begin
34
34
  if count == 0
35
35
  puts " All scripts have been previously applied"
36
36
  end
37
+
38
+ version_file = "./VERSION"
39
+ if !util.dry_run? && File.exist?(version_file)
40
+ version = IO.read(version_file).strip
41
+ if !version.empty?
42
+ if SchemaEvolutionManager::Versions.new(db).record!(version)
43
+ puts " Recorded schema version #{version}"
44
+ end
45
+ end
46
+ end
37
47
  rescue SchemaEvolutionManager::ScriptError => e
38
48
  puts ""
39
49
  puts "ERROR applying script: %s" % e.path
data/bin/sem-dist CHANGED
@@ -55,6 +55,7 @@ else
55
55
  end
56
56
  end
57
57
  SchemaEvolutionManager::Library.git_assert_tag_exists(tag)
58
+ SchemaEvolutionManager::Versions.validate_version!(tag)
58
59
 
59
60
  changes = SchemaEvolutionManager::Library.git_changes(:tag => tag)
60
61
  repo_path = SchemaEvolutionManager::Library.normalize_path(`pwd`.strip)
@@ -77,6 +78,7 @@ SchemaEvolutionManager::Library.with_temp_file do |tmp|
77
78
 
78
79
  SchemaEvolutionManager::Library.system_or_error("cp -R %s %s" % [File.join(repo_path, "scripts"), File.join(tmpdir, "scripts")])
79
80
  File.open(File.join(tmpdir, "CHANGES"), "w") { |out| out << changes }
81
+ File.open(File.join(tmpdir, "VERSION"), "w") { |out| out << tag }
80
82
 
81
83
  # Clear extended attributes to avoid tar warnings on Linux
82
84
  SchemaEvolutionManager::Library.system_or_error("xattr -cr #{tmpdir} 2>/dev/null || true")
data/bin/sem-info CHANGED
@@ -17,6 +17,12 @@
17
17
  # Output information on the next tag. Defaults to micro
18
18
  # version increment from latest tag
19
19
  #
20
+ # sem-info db version --url <database url>
21
+ # Display the latest recorded deployed schema version
22
+ #
23
+ # sem-info db scripts --url <database url>
24
+ # Display the applied script filenames
25
+ #
20
26
  # Description
21
27
  #
22
28
  # Utility script to pull information from the schema-evolution-manager
@@ -31,6 +37,9 @@ command = ARGV.shift.to_s.strip
31
37
  if command == "tag"
32
38
  puts SchemaEvolutionManager::SemInfo.tag(ARGV)
33
39
 
40
+ elsif command == "db"
41
+ SchemaEvolutionManager::SemInfo.db(ARGV)
42
+
34
43
  elsif command == "version"
35
44
  puts SchemaEvolutionManager::SemInfo.version(ARGV)
36
45
 
@@ -77,21 +77,22 @@ module SchemaEvolutionManager
77
77
  # -- sem.attribute.name = value
78
78
  #
79
79
  # and yields each matching row with |name, value|
80
+ #
81
+ # Read in binary mode so the shell locale (e.g. LANG=C in a minimal
82
+ # Docker image) cannot tag UTF-8 bytes as US-ASCII and cause String#strip
83
+ # to raise Encoding::CompatibilityError. Attribute lines are strict ASCII;
84
+ # scrubbing invalid bytes on non-attribute lines is harmless.
80
85
  def each_property
81
- IO.readlines(path).each do |l|
82
- begin
83
- stripped = l.strip.encode("UTF-8")
84
- if stripped.match(/^\-\-\s+sem\.attribute\./)
85
- stripped.sub!(/^\-\-\s+sem\.attribute\./, '')
86
- name, value = stripped.split(/\=/, 2).map(&:strip)
87
- yield name, value
88
- end
89
- rescue Encoding::InvalidByteSequenceError
90
- # Ignore - attributes must be in ascii
86
+ File.foreach(path, mode: "rb") do |raw|
87
+ line = raw.force_encoding("UTF-8")
88
+ line = line.scrub unless line.valid_encoding?
89
+ stripped = line.strip
90
+ if stripped.match(/^\-\-\s+sem\.attribute\./)
91
+ stripped.sub!(/^\-\-\s+sem\.attribute\./, '')
92
+ name, value = stripped.split(/\=/, 2).map(&:strip)
93
+ yield name, value
91
94
  end
92
95
  end
93
- rescue Encoding::InvalidByteSequenceError
94
- # Ignore - file must be in ascii in order to parse attributes
95
96
  end
96
97
 
97
98
  end
@@ -79,6 +79,16 @@ module SchemaEvolutionManager
79
79
  @db.psql_command(command)
80
80
  end
81
81
 
82
+ # Returns the sorted list of filenames already applied to this database.
83
+ def applied
84
+ if @db.schema_schema_evolution_manager_exists?
85
+ sql = "select filename from %s.%s order by filename" % [Db.schema_name, @table_name]
86
+ @db.psql_command(sql).strip.split
87
+ else
88
+ []
89
+ end
90
+ end
91
+
82
92
  private
83
93
  # Fetch the list of scripts that have already been applied to this
84
94
  # database.
@@ -6,6 +6,32 @@ module SchemaEvolutionManager
6
6
  SchemaEvolutionManager::SemVersion::VERSION.dup
7
7
  end
8
8
 
9
+ def SemInfo.db(args)
10
+ valid = ['version', 'scripts']
11
+ subcommand = args.shift.to_s.strip
12
+
13
+ if !valid.include?(subcommand)
14
+ if subcommand.empty?
15
+ puts "ERROR: Missing db subcommand. Must be one of: %s" % valid.join(", ")
16
+ else
17
+ puts "ERROR: Invalid db subcommand[%s]. Must be one of: %s" % [subcommand, valid.join(", ")]
18
+ end
19
+ exit(4)
20
+ end
21
+
22
+ db_args = SchemaEvolutionManager::Args.new(args.join(" "), :optional => ['url', 'host', 'user', 'name', 'port', 'set'])
23
+ db = SchemaEvolutionManager::Db.from_args(db_args)
24
+
25
+ if subcommand == "version"
26
+ version = SchemaEvolutionManager::Versions.new(db).latest
27
+ puts version unless version.nil?
28
+ elsif subcommand == "scripts"
29
+ SchemaEvolutionManager::Scripts.new(db, SchemaEvolutionManager::Scripts::SCRIPTS).applied.each do |filename|
30
+ puts filename
31
+ end
32
+ end
33
+ end
34
+
9
35
  def SemInfo.tag(args)
10
36
  valid = ['exists', 'latest', 'next']
11
37
 
@@ -0,0 +1,62 @@
1
+ module SchemaEvolutionManager
2
+
3
+ # Records and reads the released repository version applied to a database.
4
+ # The version is written at apply time and the latest row (max id) is the
5
+ # deployed schema version.
6
+ class Versions
7
+
8
+ if !defined?(MAX_VERSION_LENGTH)
9
+ MAX_VERSION_LENGTH = 100
10
+ end
11
+
12
+ # Validates a version string. Raises RuntimeError with an actionable
13
+ # message if invalid; returns the version if valid.
14
+ def Versions.validate_version!(version)
15
+ Preconditions.assert_class(version, String)
16
+ stripped = version.strip
17
+ if stripped.empty?
18
+ raise "Version cannot be blank"
19
+ end
20
+ if stripped.length > MAX_VERSION_LENGTH
21
+ raise "Version '%s' is %d chars; max %d" % [stripped, stripped.length, MAX_VERSION_LENGTH]
22
+ end
23
+ # Strict whitelist: letters, digits, and . _ + - only. Covers semver
24
+ # (including pre-release/build metadata) and rejects anything unsafe
25
+ # (whitespace, quotes, SQL metacharacters, etc.).
26
+ unless stripped.match(/\A[A-Za-z0-9._+-]+\z/)
27
+ raise "Version '%s' is invalid; only letters, digits, and . _ + - are allowed" % stripped
28
+ end
29
+ stripped
30
+ end
31
+
32
+ def initialize(db)
33
+ @db = Preconditions.assert_class(db, Db)
34
+ end
35
+
36
+ # Inserts the version only if it differs from the latest recorded value.
37
+ # Returns true if a row was inserted, false if unchanged.
38
+ def record!(version)
39
+ clean = Versions.validate_version!(version)
40
+ return false if latest == clean
41
+ @db.psql_command("insert into %s.versions (version) values ('%s')" % [Db.schema_name, clean])
42
+ true
43
+ end
44
+
45
+ # Latest recorded version, or nil if none recorded / table absent.
46
+ def latest
47
+ return nil unless versions_table_exists?
48
+ value = @db.psql_command("select version from %s.versions order by id desc limit 1" % Db.schema_name).strip
49
+ value.empty? ? nil : value
50
+ end
51
+
52
+ private
53
+
54
+ def versions_table_exists?
55
+ return false unless @db.schema_schema_evolution_manager_exists?
56
+ sql = "select count(*) from information_schema.tables where table_schema = '%s' and table_name = 'versions'" % Db.schema_name
57
+ @db.psql_command(sql).to_i > 0
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -18,6 +18,7 @@ load File.join(lib_dir, 'ask.rb')
18
18
  load File.join(lib_dir, 'version.rb')
19
19
  load File.join(lib_dir, 'args.rb')
20
20
  load File.join(lib_dir, 'scripts.rb')
21
+ load File.join(lib_dir, 'versions.rb')
21
22
  load File.join(lib_dir, 'connection_data.rb')
22
23
  load File.join(lib_dir, 'db.rb')
23
24
  load File.join(lib_dir, 'apply_util.rb')
@@ -0,0 +1,27 @@
1
+ -- Records the released repository version applied to this database. The latest
2
+ -- row (max id) is the deployed schema version. Written at apply time by sem-apply
3
+ -- from the VERSION file baked into the distribution by sem-dist.
4
+ --
5
+ -- Existence-guarded via to_regclass so the script is idempotent. The original
6
+ -- table_exists() helper cannot be used here: it is dropped at the end of the
7
+ -- first bootstrap script (scripts/20130318-105434.sql).
8
+
9
+ set search_path to schema_evolution_manager;
10
+
11
+ do $$
12
+ begin
13
+ if to_regclass('schema_evolution_manager.versions') is null then
14
+
15
+ create table schema_evolution_manager.versions (
16
+ id bigserial,
17
+ version varchar(100) not null,
18
+ created_at timestamp with time zone default now() not null
19
+ );
20
+
21
+ alter table schema_evolution_manager.versions add constraint versions_id_pk primary key(id);
22
+
23
+ comment on table schema_evolution_manager.versions is
24
+ 'Records the released repository version applied to this database. Latest row (max id) is the deployed schema version.';
25
+
26
+ end if;
27
+ end$$;
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schema-evolution-manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.57
4
+ version: 0.9.58
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bryzek
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-12-11 00:00:00.000000000 Z
10
+ date: 2026-07-17 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: '["Michael Bryzek"]'
13
13
  email: mbryzek@alum.mit.edu
@@ -48,8 +48,10 @@ files:
48
48
  - lib/schema-evolution-manager/sem_version.rb
49
49
  - lib/schema-evolution-manager/template.rb
50
50
  - lib/schema-evolution-manager/version.rb
51
+ - lib/schema-evolution-manager/versions.rb
51
52
  - scripts/20130318-105434.sql
52
53
  - scripts/20130318-105456.sql
54
+ - scripts/20260717-000000.sql
53
55
  - template/README.md
54
56
  - template/dev.rb
55
57
  homepage: https://github.com/gilt/schema-evolution-manager