pg_migrate 0.1.11 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +34 -34
- data/.gitmodules +3 -3
- data/Gemfile +9 -9
- data/Gemfile.lock +39 -39
- data/LICENSE +21 -21
- data/README.md +33 -33
- data/Rakefile +2 -2
- data/bin/pg_migrate +6 -6
- data/lib/pg_migrate/builder.rb +214 -214
- data/lib/pg_migrate/command_line.rb +242 -242
- data/lib/pg_migrate/config_parser.rb +48 -48
- data/lib/pg_migrate/manifest_reader.rb +102 -102
- data/lib/pg_migrate/migration.rb +11 -11
- data/lib/pg_migrate/migrator.rb +94 -94
- data/lib/pg_migrate/package.rb +152 -152
- data/lib/pg_migrate/package_templates/Gemfile.erb +3 -3
- data/lib/pg_migrate/package_templates/bin/migrate.rb +9 -9
- data/lib/pg_migrate/package_templates/gemspec.erb +21 -21
- data/lib/pg_migrate/package_templates/lib/gem/version.rb +3 -3
- data/lib/pg_migrate/package_templates/lib/gem.rb +12 -12
- data/lib/pg_migrate/props.rb +19 -19
- data/lib/pg_migrate/sql_reader.rb +51 -51
- data/lib/pg_migrate/templates/bootstrap.erb +175 -175
- data/lib/pg_migrate/templates/up.erb +30 -30
- data/lib/pg_migrate/util.rb +73 -73
- data/lib/pg_migrate/version.rb +3 -3
- data/lib/pg_migrate.rb +40 -40
- data/pg_migrate.gemspec +29 -29
- data/spec/database.yml +8 -8
- data/spec/pg_migrate/builder_spec.rb +113 -113
- data/spec/pg_migrate/command_line_spec.rb +53 -53
- data/spec/pg_migrate/config_parser_spec.rb +18 -18
- data/spec/pg_migrate/db_utility.rb +73 -73
- data/spec/pg_migrate/input_manifests/single_manifest/manifest +3 -3
- data/spec/pg_migrate/input_manifests/single_manifest/up/single1.sql +29 -29
- data/spec/pg_migrate/manifest_reader_spec.rb +19 -19
- data/spec/pg_migrate/migrator_spec.rb +68 -68
- data/spec/pg_migrate/package_spec.rb +38 -38
- data/spec/pg_migrate/sql_reader_spec.rb +21 -21
- data/spec/spec_helper.rb +15 -15
- metadata +5 -5
@@ -1,103 +1,103 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
|
3
|
-
module PgMigrate
|
4
|
-
class ManifestReader
|
5
|
-
|
6
|
-
def initialize
|
7
|
-
@log = Logging.logger[self]
|
8
|
-
end
|
9
|
-
|
10
|
-
# returns array of migration paths
|
11
|
-
def load_input_manifest(manifest_path)
|
12
|
-
manifest, version = load_manifest(manifest_path, false)
|
13
|
-
return manifest
|
14
|
-
end
|
15
|
-
|
16
|
-
# returns [array of migration paths, version]
|
17
|
-
def load_output_manifest(manifest_path)
|
18
|
-
return load_manifest(manifest_path, true)
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
|
-
# verify that the migration files exist
|
23
|
-
def validate_migration_paths(manifest_path, manifest)
|
24
|
-
# each item in the manifest should be a valid file
|
25
|
-
manifest.each do |item|
|
26
|
-
item_path = build_migration_path(manifest_path, item.name)
|
27
|
-
if !Pathname.new(item_path).exist?
|
28
|
-
raise "manifest reference #{item.name} does not exist at path #{item_path}"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# construct a migration file path location based on the manifest basedir and the name of the migration
|
34
|
-
def build_migration_path(manifest_path, migration_name)
|
35
|
-
File.join(manifest_path, UP_DIRNAME, "#{migration_name}")
|
36
|
-
end
|
37
|
-
|
38
|
-
def hash_loaded_manifest(loaded_manifest)
|
39
|
-
hash = {}
|
40
|
-
loaded_manifest.each do |manifest|
|
41
|
-
hash[manifest.name] = manifest
|
42
|
-
end
|
43
|
-
return hash
|
44
|
-
end
|
45
|
-
|
46
|
-
# read in the manifest, saving each migration declaration in order as they are found
|
47
|
-
private
|
48
|
-
def load_manifest(manifest_path, is_output)
|
49
|
-
|
50
|
-
manifest = []
|
51
|
-
version = nil
|
52
|
-
|
53
|
-
manifest_filepath = File.join(manifest_path, MANIFEST_FILENAME)
|
54
|
-
|
55
|
-
@log.debug "loading manifest from #{manifest_filepath}"
|
56
|
-
|
57
|
-
# there should be a file called 'manifest' at this location
|
58
|
-
if !FileTest::exist?(manifest_filepath)
|
59
|
-
raise "ManifestReader: code=unloadable_manifest: manifest not found at #{manifest_filepath}"
|
60
|
-
end
|
61
|
-
|
62
|
-
manifest_lines = IO.readlines(manifest_filepath)
|
63
|
-
|
64
|
-
ordinal = 0
|
65
|
-
manifest_lines.each_with_index do |line, index|
|
66
|
-
migration_name = line.strip
|
67
|
-
|
68
|
-
@log.debug "processing line:#{index} #{line}"
|
69
|
-
|
70
|
-
# output files must have a version header as 1st line o file
|
71
|
-
if is_output
|
72
|
-
if index == 0
|
73
|
-
# the first line must be the version comment. if not, error out.
|
74
|
-
if migration_name.index(BUILDER_VERSION_HEADER) == 0 && migration_name.length > BUILDER_VERSION_HEADER.length
|
75
|
-
version = migration_name[BUILDER_VERSION_HEADER.length..-1]
|
76
|
-
@log.debug "manifest has builder_version #{version}"
|
77
|
-
else
|
78
|
-
raise "manifest invalid: missing/malformed version. expecting '# pg_migrate-VERSION' to begin first line '#{line}' of manifest file: '#{manifest_filepath}'"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# ignore comments
|
84
|
-
if migration_name.empty? or migration_name.start_with?('#')
|
85
|
-
# ignored!
|
86
|
-
else
|
87
|
-
@log.debug "adding migration #{migration_name} with ordinal #{ordinal}"
|
88
|
-
manifest.push(Migration.new(migration_name, ordinal, build_migration_path(manifest_path, migration_name)))
|
89
|
-
ordinal += 1
|
90
|
-
end
|
91
|
-
|
92
|
-
# the logic above wouldn't get upset with an empty manifest
|
93
|
-
if is_output
|
94
|
-
if version.nil?
|
95
|
-
raise "manifest invalid: empty"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
return manifest, version
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module PgMigrate
|
4
|
+
class ManifestReader
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@log = Logging.logger[self]
|
8
|
+
end
|
9
|
+
|
10
|
+
# returns array of migration paths
|
11
|
+
def load_input_manifest(manifest_path)
|
12
|
+
manifest, version = load_manifest(manifest_path, false)
|
13
|
+
return manifest
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns [array of migration paths, version]
|
17
|
+
def load_output_manifest(manifest_path)
|
18
|
+
return load_manifest(manifest_path, true)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# verify that the migration files exist
|
23
|
+
def validate_migration_paths(manifest_path, manifest)
|
24
|
+
# each item in the manifest should be a valid file
|
25
|
+
manifest.each do |item|
|
26
|
+
item_path = build_migration_path(manifest_path, item.name)
|
27
|
+
if !Pathname.new(item_path).exist?
|
28
|
+
raise "manifest reference #{item.name} does not exist at path #{item_path}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# construct a migration file path location based on the manifest basedir and the name of the migration
|
34
|
+
def build_migration_path(manifest_path, migration_name)
|
35
|
+
File.join(manifest_path, UP_DIRNAME, "#{migration_name}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def hash_loaded_manifest(loaded_manifest)
|
39
|
+
hash = {}
|
40
|
+
loaded_manifest.each do |manifest|
|
41
|
+
hash[manifest.name] = manifest
|
42
|
+
end
|
43
|
+
return hash
|
44
|
+
end
|
45
|
+
|
46
|
+
# read in the manifest, saving each migration declaration in order as they are found
|
47
|
+
private
|
48
|
+
def load_manifest(manifest_path, is_output)
|
49
|
+
|
50
|
+
manifest = []
|
51
|
+
version = nil
|
52
|
+
|
53
|
+
manifest_filepath = File.join(manifest_path, MANIFEST_FILENAME)
|
54
|
+
|
55
|
+
@log.debug "loading manifest from #{manifest_filepath}"
|
56
|
+
|
57
|
+
# there should be a file called 'manifest' at this location
|
58
|
+
if !FileTest::exist?(manifest_filepath)
|
59
|
+
raise "ManifestReader: code=unloadable_manifest: manifest not found at #{manifest_filepath}"
|
60
|
+
end
|
61
|
+
|
62
|
+
manifest_lines = IO.readlines(manifest_filepath)
|
63
|
+
|
64
|
+
ordinal = 0
|
65
|
+
manifest_lines.each_with_index do |line, index|
|
66
|
+
migration_name = line.strip
|
67
|
+
|
68
|
+
@log.debug "processing line:#{index} #{line}"
|
69
|
+
|
70
|
+
# output files must have a version header as 1st line o file
|
71
|
+
if is_output
|
72
|
+
if index == 0
|
73
|
+
# the first line must be the version comment. if not, error out.
|
74
|
+
if migration_name.index(BUILDER_VERSION_HEADER) == 0 && migration_name.length > BUILDER_VERSION_HEADER.length
|
75
|
+
version = migration_name[BUILDER_VERSION_HEADER.length..-1]
|
76
|
+
@log.debug "manifest has builder_version #{version}"
|
77
|
+
else
|
78
|
+
raise "manifest invalid: missing/malformed version. expecting '# pg_migrate-VERSION' to begin first line '#{line}' of manifest file: '#{manifest_filepath}'"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# ignore comments
|
84
|
+
if migration_name.empty? or migration_name.start_with?('#')
|
85
|
+
# ignored!
|
86
|
+
else
|
87
|
+
@log.debug "adding migration #{migration_name} with ordinal #{ordinal}"
|
88
|
+
manifest.push(Migration.new(migration_name, ordinal, build_migration_path(manifest_path, migration_name)))
|
89
|
+
ordinal += 1
|
90
|
+
end
|
91
|
+
|
92
|
+
# the logic above wouldn't get upset with an empty manifest
|
93
|
+
if is_output
|
94
|
+
if version.nil?
|
95
|
+
raise "manifest invalid: empty"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return manifest, version
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
103
|
end
|
data/lib/pg_migrate/migration.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
module PgMigrate
|
2
|
-
class Migration
|
3
|
-
attr_accessor :name, :ordinal, :created, :production, :filepath
|
4
|
-
|
5
|
-
def initialize(name, ordinal, filepath)
|
6
|
-
@name = name
|
7
|
-
@ordinal = ordinal
|
8
|
-
@filepath = filepath
|
9
|
-
end
|
10
|
-
|
11
|
-
end
|
1
|
+
module PgMigrate
|
2
|
+
class Migration
|
3
|
+
attr_accessor :name, :ordinal, :created, :production, :filepath
|
4
|
+
|
5
|
+
def initialize(name, ordinal, filepath)
|
6
|
+
@name = name
|
7
|
+
@ordinal = ordinal
|
8
|
+
@filepath = filepath
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
12
|
end
|
data/lib/pg_migrate/migrator.rb
CHANGED
@@ -1,94 +1,94 @@
|
|
1
|
-
module PgMigrate
|
2
|
-
|
3
|
-
class Migrator
|
4
|
-
|
5
|
-
attr_accessor :conn, :connection_hash, :manifest_path, :manifest, :manifest_reader, :sql_reader
|
6
|
-
|
7
|
-
# options = gem 'pg' connection_hash options, or connstring => dbname=test port=5432, or pgconn => PG::Connection object
|
8
|
-
def initialize(manifest_reader, sql_reader, options = {})
|
9
|
-
@log = Logging.logger[self]
|
10
|
-
@connection_hash = options
|
11
|
-
@manifest = nil
|
12
|
-
@builder_version = nil
|
13
|
-
@manifest_reader = manifest_reader
|
14
|
-
@sql_reader = sql_reader
|
15
|
-
end
|
16
|
-
|
17
|
-
# 'migrate' attempt to migrate your database based on the contents of your built manifest
|
18
|
-
# The manifest_path argument should point to your manifest
|
19
|
-
# manifest_path = the directory containing your 'manifest' file, 'up' directory, 'down' directory, 'test' directory
|
20
|
-
# this method will throw an exception if anything goes wrong (such as bad SQL in the migrations themselves)
|
21
|
-
def migrate(manifest_path)
|
22
|
-
@manifest_path = manifest_path
|
23
|
-
|
24
|
-
@conn = Util::get_conn(@connection_hash)
|
25
|
-
|
26
|
-
# this is used to record the version of the 'migrator' in the pg_migrate table
|
27
|
-
@conn.exec("SET application_name = 'pg_migrate_ruby-#{PgMigrate::VERSION}'")
|
28
|
-
|
29
|
-
# load the manifest, and version of the builder that made it
|
30
|
-
process_manifest()
|
31
|
-
|
32
|
-
# execute the migrations
|
33
|
-
run_migrations(@manifest)
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
# load the manifest's migration declarations, and validate that each migration points to a real file
|
38
|
-
def process_manifest
|
39
|
-
@manifest, @builder_version = @manifest_reader.load_output_manifest(@manifest_path)
|
40
|
-
@manifest_reader.validate_migration_paths(@manifest_path, @manifest)
|
41
|
-
end
|
42
|
-
|
43
|
-
# run all necessary migrations
|
44
|
-
def run_migrations(manifest)
|
45
|
-
|
46
|
-
# run bootstrap before user migrations to prepare database
|
47
|
-
run_bootstrap
|
48
|
-
|
49
|
-
# loop through the manifest, executing migrations in turn
|
50
|
-
manifest.each_with_index do |migration, index|
|
51
|
-
execute_migration(migration.name, migration.filepath)
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
# executes the bootstrap method
|
57
|
-
def run_bootstrap
|
58
|
-
bootstrap = File.join(@manifest_path, UP_DIRNAME, BOOTSTRAP_FILENAME)
|
59
|
-
execute_migration('bootstrap.sql', bootstrap)
|
60
|
-
end
|
61
|
-
|
62
|
-
# execute a single migration by loading it's statements from file, and then executing each
|
63
|
-
def execute_migration(name, filepath)
|
64
|
-
@log.info "executing migration #{filepath}"
|
65
|
-
|
66
|
-
statements = @sql_reader.load_migration(filepath)
|
67
|
-
if statements.length == 0
|
68
|
-
raise 'no statements found in migration #{filepath}'
|
69
|
-
end
|
70
|
-
run_migration(name, statements)
|
71
|
-
end
|
72
|
-
|
73
|
-
# execute all the statements of a single migration
|
74
|
-
def run_migration(name, statements)
|
75
|
-
|
76
|
-
begin
|
77
|
-
statements.each do |statement|
|
78
|
-
conn.exec(statement).clear
|
79
|
-
end
|
80
|
-
rescue Exception => e
|
81
|
-
# we make a special allowance for one exception; it just means this migration
|
82
|
-
# has already occurred, and we should just treat it like a continue
|
83
|
-
if e.message.index('pg_migrate: code=migration_exists').nil?
|
84
|
-
conn.exec("ROLLBACK")
|
85
|
-
raise e
|
86
|
-
else
|
87
|
-
conn.exec("ROLLBACK")
|
88
|
-
@log.info "migration #{name} already run"
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
1
|
+
module PgMigrate
|
2
|
+
|
3
|
+
class Migrator
|
4
|
+
|
5
|
+
attr_accessor :conn, :connection_hash, :manifest_path, :manifest, :manifest_reader, :sql_reader
|
6
|
+
|
7
|
+
# options = gem 'pg' connection_hash options, or connstring => dbname=test port=5432, or pgconn => PG::Connection object
|
8
|
+
def initialize(manifest_reader, sql_reader, options = {})
|
9
|
+
@log = Logging.logger[self]
|
10
|
+
@connection_hash = options
|
11
|
+
@manifest = nil
|
12
|
+
@builder_version = nil
|
13
|
+
@manifest_reader = manifest_reader
|
14
|
+
@sql_reader = sql_reader
|
15
|
+
end
|
16
|
+
|
17
|
+
# 'migrate' attempt to migrate your database based on the contents of your built manifest
|
18
|
+
# The manifest_path argument should point to your manifest
|
19
|
+
# manifest_path = the directory containing your 'manifest' file, 'up' directory, 'down' directory, 'test' directory
|
20
|
+
# this method will throw an exception if anything goes wrong (such as bad SQL in the migrations themselves)
|
21
|
+
def migrate(manifest_path)
|
22
|
+
@manifest_path = manifest_path
|
23
|
+
|
24
|
+
@conn = Util::get_conn(@connection_hash)
|
25
|
+
|
26
|
+
# this is used to record the version of the 'migrator' in the pg_migrate table
|
27
|
+
@conn.exec("SET application_name = 'pg_migrate_ruby-#{PgMigrate::VERSION}'")
|
28
|
+
|
29
|
+
# load the manifest, and version of the builder that made it
|
30
|
+
process_manifest()
|
31
|
+
|
32
|
+
# execute the migrations
|
33
|
+
run_migrations(@manifest)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# load the manifest's migration declarations, and validate that each migration points to a real file
|
38
|
+
def process_manifest
|
39
|
+
@manifest, @builder_version = @manifest_reader.load_output_manifest(@manifest_path)
|
40
|
+
@manifest_reader.validate_migration_paths(@manifest_path, @manifest)
|
41
|
+
end
|
42
|
+
|
43
|
+
# run all necessary migrations
|
44
|
+
def run_migrations(manifest)
|
45
|
+
|
46
|
+
# run bootstrap before user migrations to prepare database
|
47
|
+
run_bootstrap
|
48
|
+
|
49
|
+
# loop through the manifest, executing migrations in turn
|
50
|
+
manifest.each_with_index do |migration, index|
|
51
|
+
execute_migration(migration.name, migration.filepath)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# executes the bootstrap method
|
57
|
+
def run_bootstrap
|
58
|
+
bootstrap = File.join(@manifest_path, UP_DIRNAME, BOOTSTRAP_FILENAME)
|
59
|
+
execute_migration('bootstrap.sql', bootstrap)
|
60
|
+
end
|
61
|
+
|
62
|
+
# execute a single migration by loading it's statements from file, and then executing each
|
63
|
+
def execute_migration(name, filepath)
|
64
|
+
@log.info "executing migration #{filepath}"
|
65
|
+
|
66
|
+
statements = @sql_reader.load_migration(filepath)
|
67
|
+
if statements.length == 0
|
68
|
+
raise 'no statements found in migration #{filepath}'
|
69
|
+
end
|
70
|
+
run_migration(name, statements)
|
71
|
+
end
|
72
|
+
|
73
|
+
# execute all the statements of a single migration
|
74
|
+
def run_migration(name, statements)
|
75
|
+
|
76
|
+
begin
|
77
|
+
statements.each do |statement|
|
78
|
+
conn.exec(statement).clear
|
79
|
+
end
|
80
|
+
rescue Exception => e
|
81
|
+
# we make a special allowance for one exception; it just means this migration
|
82
|
+
# has already occurred, and we should just treat it like a continue
|
83
|
+
if e.message.index('pg_migrate: code=migration_exists').nil?
|
84
|
+
conn.exec("ROLLBACK")
|
85
|
+
raise e
|
86
|
+
else
|
87
|
+
conn.exec("ROLLBACK")
|
88
|
+
@log.info "migration #{name} already run"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|