prick 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/TODO +7 -0
- data/exe/prick +95 -33
- data/lib/ext/fileutils.rb +7 -0
- data/lib/prick.rb +5 -3
- data/lib/prick/builder.rb +31 -8
- data/lib/prick/cache.rb +34 -0
- data/lib/prick/constants.rb +106 -54
- data/lib/prick/database.rb +26 -18
- data/lib/prick/diff.rb +103 -25
- data/lib/prick/git.rb +31 -9
- data/lib/prick/head.rb +183 -0
- data/lib/prick/migration.rb +41 -181
- data/lib/prick/program.rb +199 -0
- data/lib/prick/project.rb +277 -0
- data/lib/prick/rdbms.rb +2 -1
- data/lib/prick/schema.rb +5 -10
- data/lib/prick/state.rb +129 -74
- data/lib/prick/version.rb +41 -28
- data/share/diff/diff.after-tables.sql +4 -0
- data/share/diff/diff.before-tables.sql +4 -0
- data/share/diff/diff.tables.sql +8 -0
- data/share/migration/diff.tables.sql +8 -0
- data/share/{release_migration → migration}/features.yml +0 -0
- data/share/migration/migrate.sql +3 -0
- data/share/{release_migration → migration}/migrate.yml +3 -0
- data/share/migration/tables.sql +3 -0
- data/share/{schemas → schema/schema}/build.yml +0 -0
- data/share/{schemas → schema/schema}/prick/build.yml +0 -0
- data/share/schema/schema/prick/data.sql +7 -0
- data/share/{schemas → schema/schema}/prick/schema.sql +0 -0
- data/share/{schemas → schema/schema}/prick/tables.sql +2 -2
- data/share/{schemas → schema/schema}/public/.keep +0 -0
- data/share/{schemas → schema/schema}/public/build.yml +0 -0
- data/share/{schemas → schema/schema}/public/schema.sql +0 -0
- data/test_refactor +34 -0
- metadata +22 -20
- data/file +0 -0
- data/lib/prick/build.rb +0 -376
- data/lib/prick/migra.rb +0 -22
- data/share/feature_migration/diff.sql +0 -2
- data/share/feature_migration/migrate.sql +0 -2
- data/share/release_migration/diff.sql +0 -3
- data/share/release_migration/migrate.sql +0 -5
- data/share/schemas/prick/data.sql +0 -7
data/lib/prick/migration.rb
CHANGED
@@ -1,210 +1,70 @@
|
|
1
1
|
|
2
2
|
module Prick
|
3
|
-
# A Migration consists of a .prick-features file, a .prick-migration file, and a
|
4
|
-
# set of SQL migration files. Features can be include as subdirectories
|
5
|
-
#
|
6
3
|
class Migration
|
7
|
-
|
8
|
-
|
9
|
-
def migration_dir() @migration_state && File.dirname(@migration_state.path) end
|
4
|
+
attr_reader :version
|
5
|
+
attr_reader :base_version
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# diff.sql, features.sql, features.yml, and the user-defined migrations.sql file
|
15
|
-
def features_dir() @features_state && File.dirname(@features_state.path) end
|
16
|
-
|
17
|
-
# Directory of the migration. Alias for #features_dir. It should be used in
|
18
|
-
# derived classes when the features and migration directory are the same
|
19
|
-
def dir() features_dir end
|
20
|
-
|
21
|
-
# Migration file
|
22
|
-
def migrations_file() File.join(dir, MIGRATIONS_FILE) end
|
23
|
-
|
24
|
-
# Version of the result of running this migration. `version` is nil for the
|
25
|
-
# current release and features that are not part of a pre-release. Needs to
|
26
|
-
# be redefined in subclasses where migration_dir is nil
|
27
|
-
forward_method :version, :version=, :@migration_state
|
28
|
-
|
29
|
-
# Base version of the migration. `base_version` is nil for the initial
|
30
|
-
# 0.0.0 migration. Needs to be redefined in subclasses where migration_dir
|
31
|
-
# is nil
|
32
|
-
forward_method :base_version, :base_version=, :@migration_state
|
33
|
-
|
34
|
-
# List of features in this migration. The features ordered in the same
|
35
|
-
# order as they're included
|
36
|
-
forward_method :features, :@features_state
|
37
|
-
|
38
|
-
# Note that migration_dir can be nil
|
39
|
-
def initialize(migration_dir, features_dir)
|
40
|
-
@migration_state = migration_dir && MigrationState.new(migration_dir)
|
41
|
-
@features_state = features_dir && FeaturesState.new(features_dir)
|
7
|
+
def initialize(version, base_version)
|
8
|
+
@version = version
|
9
|
+
@base_version = base_version
|
42
10
|
end
|
43
11
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
puts "migration_dir: #{migration_dir}"
|
48
|
-
puts "features_dir: #{features_dir}"
|
49
|
-
puts "version: #{version}"
|
50
|
-
puts "base_version: #{base_version}"
|
51
|
-
}
|
12
|
+
def self.load
|
13
|
+
state = MigrationState.load
|
14
|
+
Migration.new(state.version, state.base_version)
|
52
15
|
end
|
53
16
|
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
def load
|
59
|
-
@migration_state&.read
|
60
|
-
@features_state.read
|
61
|
-
self
|
17
|
+
# Remove content of the migration/ directory
|
18
|
+
def self.clear
|
19
|
+
FileUtils.empty!(MIGRATION_DIR)
|
62
20
|
end
|
63
21
|
|
64
|
-
def
|
65
|
-
@migration_state&.write
|
66
|
-
@features_state.write
|
67
|
-
Git.add(@migration_state.path) if @migration_state
|
68
|
-
Git.add(@features_state.path)
|
69
|
-
self
|
70
|
-
end
|
22
|
+
def exist?() MigrationState.exist? end
|
71
23
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
(@migration_state.nil? || @migration_state.exist?) && (initial? || @features_state.exist?)
|
77
|
-
end
|
78
|
-
|
79
|
-
def create(templates_pattern = nil)
|
80
|
-
if @migration_state && !@migration_state.exist?
|
81
|
-
FileUtils.mkdir_p(migration_dir) if migration_dir
|
82
|
-
@migration_state.create
|
83
|
-
Git.add(@migration_state.path)
|
84
|
-
end
|
85
|
-
if !initial? && !@features_state.exist?
|
86
|
-
FileUtils.mkdir_p(features_dir)
|
87
|
-
@features_state.create
|
88
|
-
Git.add(@features_state.path)
|
89
|
-
end
|
90
|
-
if !initial? && templates_pattern
|
91
|
-
files = Share.cp(templates_pattern, features_dir, clobber: false)
|
92
|
-
Git.add files
|
93
|
-
end
|
24
|
+
def create()
|
25
|
+
files = Share.cp "migration/*", MIGRATION_DIR
|
26
|
+
state = MigrationState.new.write(version: version, base_version: base_version)
|
27
|
+
Git.add files, state.path
|
94
28
|
self
|
95
29
|
end
|
96
30
|
|
97
|
-
def
|
98
|
-
|
99
|
-
@
|
100
|
-
|
101
|
-
|
31
|
+
def update(version)
|
32
|
+
state = MigrationState.new.read
|
33
|
+
@version = state.version = version
|
34
|
+
state.write
|
35
|
+
Git.add state.path
|
102
36
|
self
|
103
37
|
end
|
104
38
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
generate_features_yml
|
110
|
-
self
|
111
|
-
end
|
112
|
-
|
113
|
-
def generate_features_yml
|
114
|
-
features_yml_file = File.join(features_dir, "features.yml")
|
115
|
-
Share.cp("release_migration/features.yml", features_yml_file)
|
116
|
-
file = File.open(features_yml_file, "a")
|
117
|
-
features.each { |feature|
|
118
|
-
version = Version.new(feature)
|
119
|
-
if base_version == version.truncate(:pre)
|
120
|
-
file.puts "- #{version.feature}"
|
121
|
-
else
|
122
|
-
file.puts "- ../#{version.truncate(:pre)}/#{version.feature}"
|
123
|
-
end
|
124
|
-
}
|
125
|
-
end
|
126
|
-
|
127
|
-
def migrate(database)
|
128
|
-
MigrationBuilder.new(database, features_dir).build
|
129
|
-
Rdbms.exec_sql(database.name, "delete from prick.versions", user: database.user)
|
130
|
-
Rdbms.exec_file(database.name, File.join(PRICK_DIR, "data.sql"), user: database.user)
|
131
|
-
end
|
132
|
-
|
133
|
-
# This only migrates included features. It is used in pre-releases to migrate included features
|
134
|
-
# before creating a diff, so the diff only contains changes not defined in the features
|
135
|
-
def migrate_features(database)
|
136
|
-
MigrationBuilder.new(database, features_dir).build("features.yml")
|
39
|
+
def migrate(database)
|
40
|
+
base_version or raise Internal, "Can't migrate from nil to #{version}"
|
41
|
+
version or raise Internal, "Can't migrate from #{base_version} to nil"
|
42
|
+
MigrationBuilder.new(database, MIGRATION_DIR).build
|
137
43
|
end
|
138
44
|
end
|
139
45
|
|
140
|
-
class
|
141
|
-
|
142
|
-
def version=(v)
|
143
|
-
@migration_state ||= MigrationState.new(ReleaseMigration.migration_dir(v))
|
144
|
-
@migration_state.version = v
|
145
|
-
end
|
46
|
+
class FeatureMigration < Migration
|
47
|
+
attr_reader :feature
|
146
48
|
|
147
|
-
def
|
148
|
-
|
149
|
-
@
|
150
|
-
@migration_state.base_version = v
|
49
|
+
def initialize(feature, base_version)
|
50
|
+
super(base_version, base_version)
|
51
|
+
@feature = feature
|
151
52
|
end
|
152
53
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
super(migration_dir, features_dir)
|
158
|
-
if version
|
159
|
-
self.version = version
|
160
|
-
self.base_version = base_version
|
161
|
-
else
|
162
|
-
@base_migration_state = MigrationState.new(features_dir)
|
163
|
-
end
|
54
|
+
def self.load
|
55
|
+
migration_state = MigrationState.load
|
56
|
+
feature_state = FeatureState.load
|
57
|
+
FeatureMigration.new(feature_state.feature, migration_state.base_version)
|
164
58
|
end
|
165
59
|
|
166
|
-
def create()
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
features_dir = ReleaseMigration.features_dir(migration_state.base_version)
|
173
|
-
end
|
174
|
-
self.new(migration_state.version, migration_state.base_version)
|
175
|
-
end
|
176
|
-
|
177
|
-
def self.migration_dir(version) version && File.join(RELEASES_DIR, version.to_s) end
|
178
|
-
def self.features_dir(base_version) migration_dir(base_version) end
|
179
|
-
|
180
|
-
|
181
|
-
end
|
182
|
-
|
183
|
-
class MigrationMigration < Migration
|
184
|
-
attr_reader :name
|
185
|
-
|
186
|
-
def initialize(version, base_version)
|
187
|
-
@name = "#{base_version}_#{version}"
|
188
|
-
dir = File.join(MIGRATIONS_DIR, name)
|
189
|
-
super(dir, dir)
|
190
|
-
self.version = version
|
191
|
-
self.base_version = base_version
|
60
|
+
def create()
|
61
|
+
super
|
62
|
+
files = Share.cp "migration", File.join(MIGRATION_DIR, feature)
|
63
|
+
state = FeatureState.write(feature: feature)
|
64
|
+
Git.add files, state.path
|
65
|
+
self
|
192
66
|
end
|
193
|
-
|
194
|
-
def create() super("release_migration/*") end
|
195
67
|
end
|
68
|
+
end
|
196
69
|
|
197
|
-
class FeatureMigration < Migration
|
198
|
-
attr_reader :name
|
199
|
-
|
200
|
-
def initialize(name, base_version)
|
201
|
-
@name = name
|
202
|
-
dir = File.join(RELEASES_DIR, base_version.to_s, name)
|
203
|
-
super(dir, dir)
|
204
|
-
self.version = Version.new(base_version, feature: name)
|
205
|
-
self.base_version = base_version
|
206
|
-
end
|
207
70
|
|
208
|
-
def create() super("feature_migration/*") end
|
209
|
-
end
|
210
|
-
end
|
data/lib/prick/program.rb
CHANGED
@@ -3,6 +3,205 @@ require "prick.rb"
|
|
3
3
|
|
4
4
|
module Prick
|
5
5
|
# Implements the command line commands
|
6
|
+
class Program
|
7
|
+
# Lazy-constructed because Project can only be initialized when the
|
8
|
+
# directory structure is present
|
9
|
+
def project() @project ||= Project.load end
|
10
|
+
|
11
|
+
attr_accessor :quiet
|
12
|
+
attr_accessor :verbose
|
13
|
+
|
14
|
+
def initialize(quiet: false, verbose: false)
|
15
|
+
@quiet = quiet
|
16
|
+
@verbose = verbose
|
17
|
+
end
|
18
|
+
|
19
|
+
# Check if the git repository is clean. Raise an error if not
|
20
|
+
def check_clean()
|
21
|
+
Git.clean? or raise Error, "Repository is dirty - please commit your changes first"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create project directory structure
|
25
|
+
def init(name, user, directory)
|
26
|
+
!Project.exist?(directory) or raise Error, "Directory #{directory} is already initialized"
|
27
|
+
Project.create(name, user, directory)
|
28
|
+
if name != File.basename(directory)
|
29
|
+
mesg "Initialized project #{name} in #{directory}"
|
30
|
+
else
|
31
|
+
mesg "Initialized project #{name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def info
|
36
|
+
if project.tag?
|
37
|
+
puts "At v#{project.version} tag"
|
38
|
+
else
|
39
|
+
puts "On branch #{project.head.name}, #{project.version}"
|
40
|
+
end
|
41
|
+
puts " Git is " + (Git.clean? ? "clean" : "dirty")
|
42
|
+
bv = project.head.version
|
43
|
+
dv = project.database.version
|
44
|
+
sv = project.head.schema.version
|
45
|
+
puts " Database version: #{dv}" + (dv != bv ? " (mismatch)" : "")
|
46
|
+
puts " Schema version : #{sv}" + (sv != bv ? " (mismatch)" : "")
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_cache
|
50
|
+
project.cache.list.each { |l| puts l }
|
51
|
+
end
|
52
|
+
|
53
|
+
def build(database, version, nocache)
|
54
|
+
check_owned(database) if database
|
55
|
+
into_mesg = database && "into #{database}"
|
56
|
+
version_mesg = version ? "v#{version}" : "current schema"
|
57
|
+
version &&= Version.new(version)
|
58
|
+
version.nil? || Git.tag?(version) or raise Error, "Can't find tag v#{version}"
|
59
|
+
database = database ? Database.new(database, project.user) : project.database(version)
|
60
|
+
project.build(database, version: version)
|
61
|
+
project.save(database) if version && !nocache
|
62
|
+
mesg "Built", version_mesg, into_mesg
|
63
|
+
end
|
64
|
+
|
65
|
+
def make(database, version, nocache)
|
66
|
+
check_owned(database) if database
|
67
|
+
version &&= Version.new(version)
|
68
|
+
version.nil? || Git.tag?(version) or raise Error, "Can't find tag v#{version}"
|
69
|
+
if !nocache && version && project.cache.exist?(version)
|
70
|
+
load(database, version)
|
71
|
+
else
|
72
|
+
build(database, version, nocache)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def make_clean(all)
|
77
|
+
project.cache.clean.each { |file| puts "Removed cache file #{File.basename(file)}" }
|
78
|
+
drop(nil, all)
|
79
|
+
end
|
80
|
+
|
81
|
+
def load(database, file_or_version)
|
82
|
+
check_owned(database) if database
|
83
|
+
into_mesg = database && "into #{database}"
|
84
|
+
if version = Version.try(file_or_version)
|
85
|
+
database = database ? Database.new(database, project.user) : project.database(version)
|
86
|
+
project.load(database, version: version)
|
87
|
+
mesg "Loaded v#{version}", into_mesg, "from cache"
|
88
|
+
else file = file_or_version
|
89
|
+
project.load(database, file: file)
|
90
|
+
mesg "Loaded #{File.basename(file)}", into_mesg
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def save(version, file)
|
95
|
+
database = project.database(version)
|
96
|
+
database.exist? or raise "Can't find database '#{database}'"
|
97
|
+
subj_mesg = file ? file : "cache"
|
98
|
+
project.save(database, file: file)
|
99
|
+
mesg "Saved #{database} to #{subj_mesg}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def drop(database, all)
|
103
|
+
database.nil? || !all or raise Error, "Can't use --all when database is given"
|
104
|
+
if database
|
105
|
+
check_owned(database)
|
106
|
+
dbs = [database]
|
107
|
+
else
|
108
|
+
dbs = Rdbms.list_databases(Prick.database_re(project.name))
|
109
|
+
dbs << project.name if all && project.database.exist?
|
110
|
+
end
|
111
|
+
dbs += Rdbms.list_databases(Prick.tmp_databases_re(project.name)) # FIXME: Only used in dev
|
112
|
+
dbs.each { |db|
|
113
|
+
Rdbms.drop_database(db)
|
114
|
+
mesg "Dropped database #{db}"
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
# `select` is a tri-state variable that can be :tables, :no_tables, or :all
|
119
|
+
# (or any other value)
|
120
|
+
def diff(from, to, mark, select)
|
121
|
+
diff = project.diff(from && Version.new(from), to && Version.new(to))
|
122
|
+
if !diff.same?
|
123
|
+
if select != :tables && !diff.before_table_changes.empty?
|
124
|
+
puts "-- BEFORE TABLE CHANGES" if mark
|
125
|
+
puts diff.before_table_changes
|
126
|
+
end
|
127
|
+
if select != :no_tables && !diff.table_changes.empty?
|
128
|
+
puts "-- TABLE CHANGES" if mark
|
129
|
+
puts diff.table_changes
|
130
|
+
end
|
131
|
+
if select != :tables && !diff.after_table_changes.empty?
|
132
|
+
puts "-- AFTER TABLE CHANGES" if mark
|
133
|
+
puts diff.after_table_changes
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def prepare_release(fork)
|
139
|
+
project.prepare_release(fork)
|
140
|
+
end
|
141
|
+
|
142
|
+
def prepare_diff(version = nil)
|
143
|
+
# Helpful check to ensure the user has built the current version
|
144
|
+
# Diff.same?(to_db, database) or
|
145
|
+
# raise Error, "Schema and project database are not synchronized"
|
146
|
+
|
147
|
+
project.prepare_diff(version || project.version)
|
148
|
+
if FileUtils.compare_file(TABLES_DIFF_PATH, TABLES_DIFF_SHARE_PATH)
|
149
|
+
mesg "Created diff. No table changes found, no manual migration updates needed"
|
150
|
+
else
|
151
|
+
mesg "Created diff. Please inspect #{TABLES_DIFF_PATH} and incorporate the"
|
152
|
+
mesg "changes in #{MIGRATION_DIR}/migrate.sql"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_release(version = nil)
|
157
|
+
if project.prerelease_branch?
|
158
|
+
raise NotYet
|
159
|
+
elsif project.release_branch?
|
160
|
+
project.create_release(Version.new(version))
|
161
|
+
else
|
162
|
+
raise Error, "You need to be on a release or pre-release branch to create a new release"
|
163
|
+
end
|
164
|
+
mesg "Created release v#{project.head.version}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def generate_schema
|
168
|
+
project.generate_schema
|
169
|
+
end
|
170
|
+
|
171
|
+
def generate_migration
|
172
|
+
project.generate_migration
|
173
|
+
end
|
174
|
+
|
175
|
+
# TODO: Create a Backup class and a Project#backup_store object
|
176
|
+
def backup(file = nil)
|
177
|
+
file = file || File.join(SPOOL_DIR, "#{project.name}-#{Time.now.utc.strftime("%Y%m%d-%H%M%S")}.sql.gz")
|
178
|
+
project.save(file: file)
|
179
|
+
mesg "Backed-up database to #{file}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def restore(file_arg = nil)
|
183
|
+
file = file_arg || Dir.glob(File.join(SPOOL_DIR, "#{name}-*.sql.gz")).sort.last
|
184
|
+
File.exist?(file) or raise Error, "Can't find #{file_arg || "any backup file"}"
|
185
|
+
project.load(file: file)
|
186
|
+
mesg "Restored database from #{file}"
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
def check_owned(database)
|
191
|
+
database =~ ALL_DATABASES_RE or raise Error, "Not a prick database: #{database}"
|
192
|
+
project_name = $1
|
193
|
+
project_name == project.name or raise Error, "Database not owned by this prick project: #{database}"
|
194
|
+
end
|
195
|
+
|
196
|
+
def mesg(*args) puts args.compact.grep(/\S/).join(' ') if !quiet end
|
197
|
+
def verb(*args) puts args.compact.grep(/\S/).join(' ') if verbose end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
__END__
|
202
|
+
|
203
|
+
|
204
|
+
module Prick
|
6
205
|
class Program
|
7
206
|
def project() @project ||= Project.load end
|
8
207
|
|