pg_migrator 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 36d84444aad6de2cdd270d988eed37ef801345b1
4
+ data.tar.gz: 620a5c79de147d72ac8f5f457154d67849168fe8
5
+ SHA512:
6
+ metadata.gz: 6efb9334f109eaa4897c456483c83f43245af196c9bd7d6d31e1aeb5f8030d831b85621d76983eec6a14d8c96b4a0d313cb3046eb00f4de2c2e60b0d89a6f60b
7
+ data.tar.gz: a68e4ec38d826b20502bb44fb200c2faafbd90776238882e18ba61ee21c01cab3d37bfe797c7de6fe4a9da48b93d9175365b15c264d4d3202f9e4d25f38cf049
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Pierre BAZONNARD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ PG Migrator
2
+ ===========
3
+
4
+ * This tool provides a set of Rake tasks to apply changes on different Postgres environments.
5
+ * Every change, upgrade or rollback, is versionned.
6
+ * Each environment is described by a configuration file.
7
+ * Host, user, database name and migration SQL scripts location are defined in the configuration file.
8
+ * For example, 'config/uat' is a 'User Aceptance Testing' environment.
9
+ * 'config/dev' is the default environment used for development.
10
+
11
+
12
+ Installation
13
+ ------------
14
+
15
+ * gem install pg_migrator
16
+
17
+
18
+ Getting started
19
+ ---------------
20
+
21
+ * Create a minimal Rakefile as below, in an empty directory
22
+ * 'rake -T' displays the available tasks
23
+ * 'rake config' displays current configuration and creates minimal 'config' file if none
24
+ * Fill in correct values in 'config/dev' file
25
+ * Add other 'config' files as you need
26
+
27
+
28
+ Minimal Rakefile
29
+ ----------------
30
+
31
+ require 'pg_migrator'
32
+ PGMigrator.new
33
+
34
+
35
+ Configuration
36
+ -------------
37
+
38
+ * See 'config/dev' file.
39
+ * Every environment configuration file is built on the same template.
40
+ * The name of each environment is its configuration file name.
41
+ * Environment is select by the option 'env=name'. See examples below.
42
+
43
+
44
+ Migrations script files
45
+ -----------------------
46
+
47
+ Migration SQL scripts are stored in a directory compliant with the pattern configuration parameter.
48
+ Default directory is 'migrations'.
49
+ Each file initialize two variables '@up' and '@down' used to upgrade and rollback the database.
50
+ Version number is the first 3 digits of the script file name.
51
+ Script file names must be compliant with the pattern parameter and the 'xyz_comment.rb' format where xyz is the version number from '001' to '999'.
52
+ An empty newly created database is in version '000'.
53
+ Several files can have the same version number.
54
+
55
+ Example: 'migrations/002_add_id_3_in_tablename.rb'
56
+
57
+ @up = "INSERT INTO tablename (id, caption) VALUES (3,'something');"
58
+ @down = "DELETE FROM tablename WHERE id = 3;"
59
+
60
+
61
+ Database requirements
62
+ ---------------------
63
+
64
+ * For now, database engine MUST BE Postgres.
65
+ * The database MUST NOT have a table called 'migrations'.
66
+ * Database version is the version of the last database upgrade, stored in the 'migrations' table.
67
+ * An empty database is in version '000'.
68
+
69
+ Options
70
+ -------
71
+
72
+ * '-s' or '-q' Rake options also suppresse pg_migrator messages.
73
+ * '-v' verbose is default mode
74
+
75
+
76
+ Usage examples
77
+ --------------
78
+
79
+ Show configuration of the 'uat' environment
80
+ ```
81
+ rake config env=uat
82
+ ```
83
+
84
+ Migrate the 'uat' environment in silent mode
85
+ ```
86
+ rake db:migrate env=uat -s
87
+ ```
88
+
89
+ Show the list of migrations of the 'dev' environment
90
+ ```
91
+ rake migrations
92
+ ```
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rubygems'
6
+ require 'rubygems/package'
7
+ require 'rubygems/installer'
8
+
9
+ spec = Gem::Specification.load('pg_migrator.gemspec')
10
+ target = "#{spec.name}-#{spec.version}.gem"
11
+
12
+ CLEAN.include "#{spec.name}*.gem"
13
+
14
+ Rake::TestTask.new do |t|
15
+ # Beware: Rake.verbose can be undefined (class Object but not nil)
16
+ if Rake.verbose == true
17
+ t.verbose = true
18
+ ENV['verbose'] = 'true'
19
+ else
20
+ t.verbose = false
21
+ ENV['verbose'] = 'false'
22
+ end
23
+ end
24
+
25
+ desc 'Build gem'
26
+ task build: 'clean' do
27
+ Gem::Package.build spec, true
28
+ end
29
+
30
+ desc 'Install gem'
31
+ task install: 'build' do
32
+ gi = Gem::Installer.new target
33
+ gi.install
34
+ end
35
+
36
+ rakefiles = Rake::FileList['tasks/*.rake']
37
+ rakefiles.each do |file|
38
+ load "#{file}"
39
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+ require 'pyer/properties'
3
+
4
+ # Read configuration file
5
+ class Config
6
+ attr_reader :env, :host, :port, :username, :password, :database, :version0, :upgrade, :downgrade, :verbose
7
+
8
+ def initialize
9
+ @env = ENV['env']
10
+ @env = 'dev' if @env.nil?
11
+ config = Pyer::Properties.new("config/#{@env}")
12
+ @host = config.host
13
+ @port = config.port
14
+ @username = config.username
15
+ @password = config.password
16
+ @database = config.database
17
+ @version0 = config.version0
18
+ @upgrade = config.upgrade
19
+ @downgrade = config.downgrade
20
+ @verbose = ENV['verbose'] == 'true'
21
+ end
22
+
23
+ def create
24
+ Dir.mkdir 'config' unless File.exist?('config')
25
+ File.open("config/#{@env}", 'w') do |f|
26
+ f.write("database = \n")
27
+ f.write("host = localhost\n")
28
+ f.write("port = 5432\n")
29
+ f.write("username = \n")
30
+ f.write("password = \n")
31
+ f.write("encoding = utf8\n")
32
+ f.write("version0 = 0.00\n")
33
+ f.write("upgrade = migrations/*_up_*.sql\n")
34
+ f.write("downgrade = migrations/*_down_*.sql\n")
35
+ end unless File.exist?("config/#{@env}")
36
+ end
37
+
38
+ # Extract version number from the SQL script path name
39
+ # Version is text between last '/' and '_'
40
+ # File path must end with '.sql'
41
+ def version(path)
42
+ path.gsub(%r{.*/}, '').gsub(/_.*\.sql/, '')
43
+ end
44
+ end
data/lib/db.rb ADDED
@@ -0,0 +1,86 @@
1
+ # coding: utf-8
2
+ require 'config'
3
+ require 'postgres'
4
+
5
+ # Database managment
6
+ class DB
7
+ def self.create(config)
8
+ pg = Postgres.new(config, 'postgres')
9
+ pg.execute("CREATE DATABASE #{config.database} OWNER #{config.username};")
10
+ pg.finish
11
+ DB.version(config) # create migrations table
12
+ pg.ok?
13
+ end
14
+
15
+ def self.drop(config)
16
+ pg = Postgres.new(config, 'postgres')
17
+ pg.execute("DROP DATABASE #{config.database};")
18
+ pg.finish
19
+ pg.ok?
20
+ end
21
+
22
+ def self.version(config)
23
+ pg = Postgres.new(config)
24
+ ver = pg.value('SELECT version FROM migrations ORDER BY updated_on DESC LIMIT 1;')
25
+ if ver.empty? && pg.connected?
26
+ ver = config.version0
27
+ pg.execute("CREATE TABLE migrations (
28
+ version character varying NOT NULL,
29
+ updated_on timestamp without time zone);")
30
+ pg.execute("INSERT INTO migrations VALUES('#{ver}', now());")
31
+ end
32
+ pg.finish
33
+ ver
34
+ end
35
+
36
+ def self.migrations(config)
37
+ mig = []
38
+ pg = Postgres.new(config)
39
+ mig = pg.execute('SELECT * FROM migrations ORDER BY updated_on;') if pg.ok?
40
+ pg.finish
41
+ mig
42
+ end
43
+
44
+ def self.databases(config)
45
+ dbs = []
46
+ pg = Postgres.new(config, 'postgres')
47
+ dbs = pg.execute('SELECT * FROM pg_database ORDER BY datname;') if pg_ok?
48
+ pg.finish
49
+ dbs
50
+ end
51
+
52
+ def self.migrate(config)
53
+ db_version = DB.version(config)
54
+ return nil if db_version.empty?
55
+ Dir[config.upgrade].sort.each do |file|
56
+ next unless config.version(file) > db_version
57
+ pg = Postgres.new(config)
58
+ pg.update(file)
59
+ pg.finish
60
+ end
61
+ end
62
+
63
+ def self.forward(config)
64
+ db_version = DB.version(config)
65
+ return nil if db_version.empty?
66
+ db_version.next!
67
+ Dir[config.upgrade].sort.each do |file|
68
+ next unless config.version(file) == db_version
69
+ pg = Postgres.new(config)
70
+ pg.update(file)
71
+ pg.finish
72
+ end
73
+ end
74
+
75
+ def self.rollback(config)
76
+ db_version = DB.version(config)
77
+ return nil if db_version.empty?
78
+ Dir[config.downgrade].sort.each do |file|
79
+ next unless config.version(file) == db_version
80
+ pg = Postgres.new(config)
81
+ pg.update(file)
82
+ pg.execute("DELETE FROM migrations WHERE version='#{db_version}';")
83
+ pg.finish
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,12 @@
1
+ # coding: utf-8
2
+ #
3
+ # Load rake tasks
4
+ class PGMigrator
5
+ def initialize
6
+ $LOAD_PATH.each do |p|
7
+ Dir["#{p}/*"].each do |d|
8
+ load d if %r{pg_migrator.*/tasks/.*\.rake$}.match(d)
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/postgres.rb ADDED
@@ -0,0 +1,101 @@
1
+ # coding: utf-8
2
+ require 'pg'
3
+
4
+ # Postgres database API
5
+ class Postgres
6
+ attr_reader :config, :connection, :status
7
+
8
+ def initialize(conf, user = nil)
9
+ @status = true
10
+ @config = conf
11
+ begin
12
+ if user.nil?
13
+ @connection = PG::Connection.new(dbname: @config.database, user: @config.username, password: @config.password,
14
+ host: @config.host, port: @config.port)
15
+ else
16
+ # assuming user is postgres, dbname is also postgres
17
+ @connection = PG::Connection.new(dbname: user, user: user,
18
+ host: config.host, port: config.port)
19
+ end
20
+ rescue PG::Error => e
21
+ trace e.message
22
+ @connection = nil
23
+ @status = false
24
+ end
25
+ end
26
+
27
+ def connected?
28
+ ! @connection.nil?
29
+ end
30
+
31
+ def ok?
32
+ @status
33
+ end
34
+
35
+ def finish
36
+ @connection.finish unless @connection.nil?
37
+ @status
38
+ end
39
+
40
+ def trace(message)
41
+ puts message if @config.verbose
42
+ end
43
+
44
+ def execute(query)
45
+ begin
46
+ trace query
47
+ result = @connection.query(query)
48
+ rescue PG::Error => e
49
+ trace e.message
50
+ # puts "ERROR: failed query [#{query}]" if @config.verbose
51
+ @status = false
52
+ result = nil
53
+ end
54
+ result
55
+ end
56
+
57
+ def value(query)
58
+ return '' unless @status
59
+ begin
60
+ result = @connection.query(query)
61
+ return result.getvalue(0, 0).to_s
62
+ rescue PG::Error => e
63
+ trace e.message
64
+ @status = false
65
+ return ''
66
+ end
67
+ end
68
+
69
+ def script(file)
70
+ query = ''
71
+ file.each_line do |line|
72
+ trace line
73
+ query = query + ' ' + line
74
+ if query.end_with?(';')
75
+ @connection.query(query)
76
+ query = ''
77
+ end
78
+ end
79
+ end
80
+
81
+ def update(file_name)
82
+ puts "Executing #{file_name}" if @config.verbose
83
+ ver = @config.version(file_name)
84
+ begin
85
+ trace 'BEGIN;'
86
+ @connection.query('BEGIN;')
87
+ file = File.open(file_name, 'r')
88
+ script(file)
89
+ file.close
90
+ @connection.query("INSERT INTO migrations VALUES('#{ver}', now());")
91
+ trace 'COMMIT;'
92
+ @connection.query('COMMIT;')
93
+ rescue SystemCallError => e
94
+ trace e.message
95
+ @connection.query('ROLLBACK;')
96
+ rescue PG::Error => e
97
+ trace e.message
98
+ @connection.query('ROLLBACK;')
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'pg_migrator'
5
+ s.version = '1.0.1'
6
+ s.author = 'Pierre BAZONNARD'
7
+ s.email = ['pierre.bazonnard@gmail.com']
8
+ s.homepage = 'https://github.com/pyer/pg_migrator'
9
+ s.summary = 'Migrate Postgres databases'
10
+ s.description = 'This tool provides a set of Rake tasks to apply changes on different Postgres environments.'
11
+ s.license = 'MIT'
12
+ s.files = Dir['lib/*.rb'] + Dir['tasks/*.rake'] + ['Rakefile', 'LICENSE', 'README.md', __FILE__]
13
+ s.require_paths = %w(lib tasks)
14
+ s.required_ruby_version = '~> 2'
15
+
16
+ s.add_dependency 'rake', '~> 10'
17
+ s.add_dependency 'pg', '~> 0'
18
+ s.add_dependency 'pyer-properties', '~> 1'
19
+ s.add_development_dependency 'minitest', '~> 5'
20
+
21
+ s.requirements = 'Postgres'
22
+ end
data/tasks/config.rake ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ require 'rake'
3
+ require 'config'
4
+
5
+ @config = nil
6
+
7
+ task :load_config do
8
+ @config = Config.new
9
+ end
10
+
11
+ desc 'Show configuration'
12
+ task config: :load_config do
13
+ puts "Environment : #{@config.env}"
14
+ if @config.database.nil?
15
+ @config.create
16
+ puts 'ERROR: empty config file'
17
+ else
18
+ puts "Host : #{@config.host}"
19
+ puts "Port : #{@config.port}"
20
+ puts "User : #{@config.username}"
21
+ puts "Database : #{@config.database}"
22
+ end
23
+ end
24
+
25
+ # Environment is defined by the ENV variable
26
+ # For example:
27
+ # rake migrate env=dev
28
+ desc 'Show environments'
29
+ task environments: :load_config do
30
+ Rake::FileList['config/*'].each do |file|
31
+ puts file.split('/')[1]
32
+ end
33
+ end
34
+
35
+ task default: :config
data/tasks/db.rake ADDED
@@ -0,0 +1,51 @@
1
+ # coding: utf-8
2
+ require 'db'
3
+
4
+ desc 'Show the current database version'
5
+ task version: [:load_config] do
6
+ puts "Environment : #{@config.env}"
7
+ puts "Database : #{@config.database}"
8
+ puts "Version : #{DB.version(@config)}"
9
+ puts "Next version : #{DB.version(@config).next}"
10
+ end
11
+
12
+ desc 'List the current database migrations'
13
+ task migrations: [:load_config] do
14
+ DB.migrations(@config).each do |row|
15
+ puts row['version'] + ' ' + row['updated_on']
16
+ end
17
+ end
18
+
19
+ desc 'List the databases'
20
+ task databases: [:load_config] do
21
+ DB.databases(@config).each do |row|
22
+ puts row['datname']
23
+ end
24
+ end
25
+
26
+ namespace :db do
27
+ desc 'Create the current database'
28
+ task create: [:load_config] do
29
+ DB.create(@config)
30
+ end
31
+
32
+ desc 'Drop the current database'
33
+ task drop: [:load_config] do
34
+ DB.drop(@config)
35
+ end
36
+
37
+ desc 'Migrate the current database to the last version'
38
+ task migrate: [:load_config] do
39
+ DB.migrate(@config)
40
+ end
41
+
42
+ desc 'Roll the current database back to the previous version'
43
+ task rollback: [:environment, :load_config] do
44
+ DB.rollback(@config)
45
+ end
46
+
47
+ desc 'Push the current database to the next version'
48
+ task forward: [:environment, :load_config] do
49
+ DB.forward(@config)
50
+ end
51
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_migrator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pierre BAZONNARD
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pyer-properties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5'
69
+ description: This tool provides a set of Rake tasks to apply changes on different
70
+ Postgres environments.
71
+ email:
72
+ - pierre.bazonnard@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - lib/config.rb
78
+ - lib/db.rb
79
+ - lib/pg_migrator.rb
80
+ - lib/postgres.rb
81
+ - tasks/config.rake
82
+ - tasks/db.rake
83
+ - Rakefile
84
+ - LICENSE
85
+ - README.md
86
+ - pg_migrator.gemspec
87
+ homepage: https://github.com/pyer/pg_migrator
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ - tasks
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '2'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements:
107
+ - Postgres
108
+ rubyforge_project:
109
+ rubygems_version: 2.4.5.1
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Migrate Postgres databases
113
+ test_files: []