pg_migrator 1.0.1

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 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: []