dbgeni 0.10.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 +7 -0
- data/Rakefile +29 -0
- data/bin/dbgeni +2 -0
- data/lib/dbgeni/base.rb +146 -0
- data/lib/dbgeni/base_code.rb +143 -0
- data/lib/dbgeni/blank_slate.rb +24 -0
- data/lib/dbgeni/cli.rb +96 -0
- data/lib/dbgeni/code.rb +235 -0
- data/lib/dbgeni/code_list.rb +60 -0
- data/lib/dbgeni/commands/code.rb +151 -0
- data/lib/dbgeni/commands/commands.rb +41 -0
- data/lib/dbgeni/commands/config.rb +36 -0
- data/lib/dbgeni/commands/dmls.rb +244 -0
- data/lib/dbgeni/commands/generate.rb +257 -0
- data/lib/dbgeni/commands/initialize.rb +41 -0
- data/lib/dbgeni/commands/migrations.rb +243 -0
- data/lib/dbgeni/commands/milestones.rb +52 -0
- data/lib/dbgeni/commands/new.rb +178 -0
- data/lib/dbgeni/config.rb +325 -0
- data/lib/dbgeni/connectors/connector.rb +59 -0
- data/lib/dbgeni/connectors/mysql.rb +146 -0
- data/lib/dbgeni/connectors/oracle.rb +149 -0
- data/lib/dbgeni/connectors/sqlite.rb +166 -0
- data/lib/dbgeni/connectors/sybase.rb +97 -0
- data/lib/dbgeni/dml_cli.rb +35 -0
- data/lib/dbgeni/environment.rb +161 -0
- data/lib/dbgeni/exceptions/exception.rb +69 -0
- data/lib/dbgeni/file_converter.rb +44 -0
- data/lib/dbgeni/initializers/initializer.rb +44 -0
- data/lib/dbgeni/initializers/mysql.rb +36 -0
- data/lib/dbgeni/initializers/oracle.rb +38 -0
- data/lib/dbgeni/initializers/sqlite.rb +33 -0
- data/lib/dbgeni/initializers/sybase.rb +34 -0
- data/lib/dbgeni/logger.rb +60 -0
- data/lib/dbgeni/migration.rb +302 -0
- data/lib/dbgeni/migration_cli.rb +204 -0
- data/lib/dbgeni/migration_list.rb +91 -0
- data/lib/dbgeni/migrators/migrator.rb +40 -0
- data/lib/dbgeni/migrators/migrator_interface.rb +51 -0
- data/lib/dbgeni/migrators/mysql.rb +82 -0
- data/lib/dbgeni/migrators/oracle.rb +211 -0
- data/lib/dbgeni/migrators/sqlite.rb +90 -0
- data/lib/dbgeni/migrators/sybase.rb +118 -0
- data/lib/dbgeni/plugin.rb +92 -0
- data/lib/dbgeni.rb +52 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 662e5bb250525f1053d901edbae3d01ac733878f
|
4
|
+
data.tar.gz: d58cfb81adf70cd4674b47fa0259992fbc15d168
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e1a1d3e269c4e96e38abd113c056b70bab9732de9533d2ccf13499a7de5882de56a36c320e442029bf1f27b438f3f810e8aad53b1113dd3d7193189dff49c64
|
7
|
+
data.tar.gz: 242e3e2f823249aed271d674b19eebe23af1553939be7af93cee81af64ef1a88e6bc082cd41b980ea3e747bf52d04b3ea7908e9542d6ef0f3ebf597334a5d7e6
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = "dbgeni"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
s.author = "Stephen O'Donnell"
|
10
|
+
s.email = "stephen@betteratoracle.com"
|
11
|
+
s.homepage = "http://somewebsite.com"
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "A generic database installer"
|
14
|
+
s.files = FileList['lib/**/*.rb', 'bin/*', '[A-Z]*', 'test/**/*'].reject{ |fn| fn.include? "temp" }
|
15
|
+
s.require_path = "lib"
|
16
|
+
s.bindir = "bin"
|
17
|
+
s.executables << 'dbgeni'
|
18
|
+
s.description = "Generic installer to manage database migrations for various databases"
|
19
|
+
# s.autorequire = "name"
|
20
|
+
# s.test_files = FileList["{test}/**/*test.rb"].to_a
|
21
|
+
s.has_rdoc = false
|
22
|
+
# s.extra_rdoc_files = ["README"]
|
23
|
+
# s.add_dependency("dependency", ">= 0.x.x")
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
27
|
+
pkg.need_tar = true
|
28
|
+
end
|
29
|
+
|
data/bin/dbgeni
ADDED
data/lib/dbgeni/base.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'dbgeni/logger'
|
2
|
+
require 'dbgeni/blank_slate'
|
3
|
+
require 'dbgeni/config'
|
4
|
+
require 'dbgeni/environment'
|
5
|
+
require 'dbgeni/file_converter'
|
6
|
+
require 'dbgeni/base_code'
|
7
|
+
require 'dbgeni/migration_list'
|
8
|
+
require 'dbgeni/migration'
|
9
|
+
require 'dbgeni/code_list'
|
10
|
+
require 'dbgeni/code'
|
11
|
+
require 'dbgeni/plugin'
|
12
|
+
require 'dbgeni/exceptions/exception'
|
13
|
+
require 'dbgeni/initializers/initializer'
|
14
|
+
require 'dbgeni/migrators/migrator'
|
15
|
+
require 'dbgeni/migrators/migrator_interface'
|
16
|
+
require 'dbgeni/connectors/connector'
|
17
|
+
require 'dbgeni/migration_cli'
|
18
|
+
require 'dbgeni/dml_cli'
|
19
|
+
|
20
|
+
require 'fileutils'
|
21
|
+
|
22
|
+
module DBGeni
|
23
|
+
|
24
|
+
class Base
|
25
|
+
attr_reader :config
|
26
|
+
# attr_reader :migrations
|
27
|
+
|
28
|
+
# This pulls in all the code related methods - listing, applying, removing
|
29
|
+
# TODO - turn this into a class like with the migrations and DML
|
30
|
+
include DBGeni::BaseModules::Code
|
31
|
+
|
32
|
+
def self.installer_for_environment(config_file, environment_name=nil)
|
33
|
+
installer = self.new(config_file)
|
34
|
+
# If environment is nil, then it assumes there is only a single environment
|
35
|
+
# defined. So pass the nil value to select_environment - if there is more than
|
36
|
+
# one environment then select_environment will error out after making a call
|
37
|
+
# to get_environment.
|
38
|
+
installer.select_environment(environment_name)
|
39
|
+
installer
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(config_file)
|
43
|
+
load_config(config_file)
|
44
|
+
initialize_logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def select_environment(environment_name)
|
48
|
+
current_environment = selected_environment_name
|
49
|
+
if current_environment != nil && current_environment != environment_name
|
50
|
+
# disconnect from database as the connection may well have changed!
|
51
|
+
disconnect
|
52
|
+
end
|
53
|
+
@config.set_env(environment_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def selected_environment_name
|
57
|
+
begin
|
58
|
+
@config.env.__environment_name
|
59
|
+
rescue DBGeni::ConfigAmbiguousEnvironment
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def run_plugin(hook, object, params={})
|
66
|
+
pdir = @config.plugin_directory
|
67
|
+
if pdir && pdir != ''
|
68
|
+
unless @plugin_manager
|
69
|
+
@plugin_manager = DBGeni::Plugin.new
|
70
|
+
@plugin_manager.load_plugins(pdir)
|
71
|
+
end
|
72
|
+
@plugin_manager.run_plugins(hook,
|
73
|
+
{
|
74
|
+
:logger => @logger,
|
75
|
+
:object => object,
|
76
|
+
:environment => @config.env,
|
77
|
+
:connection => connection
|
78
|
+
}.merge!(params)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def connect
|
85
|
+
raise DBGeni::NoEnvironmentSelected unless selected_environment_name
|
86
|
+
return @connection if @connection
|
87
|
+
|
88
|
+
@connection = DBGeni::Connector.initialize(@config)
|
89
|
+
end
|
90
|
+
|
91
|
+
def disconnect
|
92
|
+
if @connection
|
93
|
+
@connection.disconnect
|
94
|
+
end
|
95
|
+
@connection = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def connection
|
99
|
+
@connection ||= connect
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize_database
|
103
|
+
DBGeni::Initializer.initialize(connection, @config)
|
104
|
+
end
|
105
|
+
|
106
|
+
def database_initialized?
|
107
|
+
DBGeni::Initializer.initialized?(connection, @config)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def ensure_initialized
|
112
|
+
raise DBGeni::DatabaseNotInitialized unless database_initialized?
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def initialize_logger
|
118
|
+
@logger = DBGeni::Logger.instance("#{@config.base_directory}/log")
|
119
|
+
end
|
120
|
+
|
121
|
+
def load_config(config)
|
122
|
+
@config = Config.load_from_file(config)
|
123
|
+
end
|
124
|
+
|
125
|
+
def delegate_to_migration_cli(meth, *args)
|
126
|
+
@migration_cli ||= MigrationCLI.new(self, @config, @logger)
|
127
|
+
@migration_cli.send(meth, *args)
|
128
|
+
end
|
129
|
+
|
130
|
+
def delegate_to_dml_cli(meth, *args)
|
131
|
+
@dml_cli ||= DmlCLI.new(self, @config, @logger)
|
132
|
+
@dml_cli.send(meth, *args)
|
133
|
+
end
|
134
|
+
|
135
|
+
def method_missing(meth, *args, &blk)
|
136
|
+
if meth =~ /migration/
|
137
|
+
delegate_to_migration_cli(meth.intern, *args)
|
138
|
+
elsif meth =~ /dml/
|
139
|
+
delegate_to_dml_cli(meth.to_s.gsub(/dml/,'migration').intern, *args)
|
140
|
+
else
|
141
|
+
super
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module DBGeni
|
2
|
+
module BaseModules
|
3
|
+
module Code
|
4
|
+
|
5
|
+
# This module isn't much good on its own, but it is here purely to
|
6
|
+
# break up the code in the Base class. This module should be included
|
7
|
+
# into base for code operations to work correctly
|
8
|
+
|
9
|
+
def code
|
10
|
+
@code_list ||= DBGeni::CodeList.new(@config.code_dir)
|
11
|
+
@code_list.code
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_code
|
15
|
+
ensure_initialized
|
16
|
+
code
|
17
|
+
@code_list.current(@config, connection)
|
18
|
+
end
|
19
|
+
|
20
|
+
def outstanding_code
|
21
|
+
ensure_initialized
|
22
|
+
code
|
23
|
+
@code_list.outstanding(@config, connection)
|
24
|
+
end
|
25
|
+
|
26
|
+
def list_of_code(list)
|
27
|
+
ensure_initialized
|
28
|
+
code
|
29
|
+
@code_list.list(list, @config, connection)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Applying
|
33
|
+
|
34
|
+
def apply_all_code(force=nil)
|
35
|
+
ensure_initialized
|
36
|
+
code_files = code
|
37
|
+
if code_files.length == 0
|
38
|
+
raise DBGeni::NoOutstandingCode
|
39
|
+
end
|
40
|
+
apply_code_list(code_files, true)
|
41
|
+
end
|
42
|
+
|
43
|
+
def apply_outstanding_code(force=nil)
|
44
|
+
ensure_initialized
|
45
|
+
code_files = outstanding_code
|
46
|
+
if code_files.length == 0
|
47
|
+
raise DBGeni::NoOutstandingCode
|
48
|
+
end
|
49
|
+
apply_code_list(code_files, force)
|
50
|
+
end
|
51
|
+
|
52
|
+
def apply_list_of_code(object_names, force=nil)
|
53
|
+
ensure_initialized
|
54
|
+
code_files = list_of_code(object_names)
|
55
|
+
apply_code_list(code_files, force)
|
56
|
+
end
|
57
|
+
|
58
|
+
def apply_code(code_obj, force=nil)
|
59
|
+
ensure_initialized
|
60
|
+
begin
|
61
|
+
run_plugin(:before_code_apply, code_obj)
|
62
|
+
code_obj.apply!(@config, connection, force)
|
63
|
+
if code_obj.error_messages
|
64
|
+
# Oracle can apply procs that still have errors. This is expected. Other databases
|
65
|
+
# have errors raised for invalid procs, except when force is on, so this logic is
|
66
|
+
# for when they are being forced through.
|
67
|
+
if @config.db_type == 'oracle'
|
68
|
+
@logger.info "Applied #{code_obj.to_s} (with errors)\n\n#{code_obj.error_messages}\nFull errors in #{code_obj.logfile}\n\n"
|
69
|
+
else
|
70
|
+
@logger.error "Failed to apply #{code_obj.filename}. Errors in #{code_obj.logfile}\n\n#{code_obj.error_messages}\n\n"
|
71
|
+
end
|
72
|
+
else
|
73
|
+
@logger.info "Applied #{code_obj.to_s}"
|
74
|
+
end
|
75
|
+
run_plugin(:after_code_apply, code_obj)
|
76
|
+
rescue DBGeni::MigratorCouldNotConnect
|
77
|
+
@logger.error "Failed to connect to the database CLI"
|
78
|
+
raise DBGeni::CodeApplyFailed
|
79
|
+
rescue DBGeni::CodeApplyFailed => e
|
80
|
+
# The only real way code can get here is if the user had insufficient privs
|
81
|
+
# to create the proc, or there was other bad stuff in the proc file.
|
82
|
+
# In this case, dbgeni should stop - but also treat the error like a migration error
|
83
|
+
# as the error message will be in the logfile in the format standard SQL errors are.
|
84
|
+
@logger.error "Failed to apply #{code_obj.filename}. Errors in #{code_obj.logfile}\n\n#{code_obj.error_messages}\n\n"
|
85
|
+
raise DBGeni::CodeApplyFailed, e.to_s
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def remove_all_code(force=nil)
|
90
|
+
ensure_initialized
|
91
|
+
code_files = code
|
92
|
+
if code_files.length == 0
|
93
|
+
raise DBGeni::NoCodeFilesExist
|
94
|
+
end
|
95
|
+
apply_code_list(code_files, force, false)
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove_list_of_code(object_names, force=nil)
|
99
|
+
ensure_initialized
|
100
|
+
code_files = list_of_code(object_names)
|
101
|
+
apply_code_list(code_files, force, false)
|
102
|
+
end
|
103
|
+
|
104
|
+
def remove_code(code_obj, force=nil)
|
105
|
+
ensure_initialized
|
106
|
+
begin
|
107
|
+
run_plugin(:before_code_remove, code_obj)
|
108
|
+
code_obj.remove!(@config, connection, force)
|
109
|
+
@logger.info "Removed #{code_obj.to_s}"
|
110
|
+
run_plugin(:after_code_remove, code_obj)
|
111
|
+
rescue DBGeni::MigratorCouldNotConnect
|
112
|
+
@logger.error "Failed to connect to the database CLI"
|
113
|
+
raise DBGeni::CodeRemoveFailed
|
114
|
+
rescue DBGeni::CodeRemoveFailed => e
|
115
|
+
# Not sure if the code can even get here. Many if timeout waiting for lock on object?
|
116
|
+
# In this case, dbgeni should stop - but also treat the error like a migration error
|
117
|
+
|
118
|
+
# TODO - not sure this is even correct - dropping code doesn't create a logfile ...
|
119
|
+
@logger.error "Failed to remove #{code_obj.filename}. Errors in #{code_obj.logfile}"
|
120
|
+
raise DBGeni::CodeRemoveFailed
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def apply_code_list(code_list, force, up=true)
|
127
|
+
params = {
|
128
|
+
:operation => up == true ? 'apply' : 'remove'
|
129
|
+
}
|
130
|
+
run_plugin(:before_modifying_code, code_list, params)
|
131
|
+
code_list.each do |c|
|
132
|
+
if up
|
133
|
+
apply_code(c, force)
|
134
|
+
else
|
135
|
+
remove_code(c, force)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
run_plugin(:after_modifying_code, code_list, params)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DBGeni
|
2
|
+
|
3
|
+
class ::BlankSlate
|
4
|
+
KEEP_METHODS = [
|
5
|
+
:respond_to?,
|
6
|
+
:__id__,
|
7
|
+
:__send__,
|
8
|
+
:instance_eval,
|
9
|
+
:==,
|
10
|
+
:equal?,
|
11
|
+
:initialize,
|
12
|
+
:method_missing,
|
13
|
+
:instance_variable_set,
|
14
|
+
:send,
|
15
|
+
:alias_method
|
16
|
+
]
|
17
|
+
suppress_warnings {
|
18
|
+
(instance_methods - KEEP_METHODS).each do |m|
|
19
|
+
undef_method(m)
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/dbgeni/cli.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# hack to get the CLI working witout being properly installed as a gem
|
2
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
3
|
+
|
4
|
+
$config_file = './.dbgeni'
|
5
|
+
$environment_name = nil
|
6
|
+
$force = false
|
7
|
+
$username = nil
|
8
|
+
$password = nil
|
9
|
+
|
10
|
+
if index = ARGV.index('--config-file') or index = ARGV.index('-c')
|
11
|
+
unless ARGV[index+1]
|
12
|
+
puts "error: --config-file switch present, but no file specified"
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
$config_file = ARGV[index+1]
|
16
|
+
# remove all references to config file from the argument list
|
17
|
+
ARGV.delete_at(index)
|
18
|
+
ARGV.delete_at(index)
|
19
|
+
end
|
20
|
+
|
21
|
+
if index = ARGV.index('--environment-name') or index = ARGV.index('-e')
|
22
|
+
unless ARGV[index+1]
|
23
|
+
puts "error: --environment-name switch present, but no environment specified"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
$environment_name = ARGV[index+1]
|
27
|
+
# remove all references to config file from the argument list
|
28
|
+
ARGV.delete_at(index)
|
29
|
+
ARGV.delete_at(index)
|
30
|
+
end
|
31
|
+
|
32
|
+
if index = ARGV.index('--password') or index = ARGV.index('-p')
|
33
|
+
unless ARGV[index+1]
|
34
|
+
puts "error: --password switch present, but no password specified"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
$password = ARGV[index+1]
|
38
|
+
ARGV.delete_at(index)
|
39
|
+
ARGV.delete_at(index)
|
40
|
+
end
|
41
|
+
|
42
|
+
if index = ARGV.index('--username') or index = ARGV.index('-u')
|
43
|
+
unless ARGV[index+1]
|
44
|
+
puts "error: --username switch present, but no user specified"
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
$username = ARGV[index+1]
|
48
|
+
ARGV.delete_at(index)
|
49
|
+
ARGV.delete_at(index)
|
50
|
+
end
|
51
|
+
|
52
|
+
if index = ARGV.index('--force') or index = ARGV.index('-f')
|
53
|
+
$force = true
|
54
|
+
ARGV.delete_at(index)
|
55
|
+
end
|
56
|
+
|
57
|
+
$build_installer = Proc.new {
|
58
|
+
installer = DBGeni::Base.installer_for_environment($config_file, $environment_name)
|
59
|
+
if $password or $username
|
60
|
+
env = installer.config.env
|
61
|
+
env.__enable_loading
|
62
|
+
if $password
|
63
|
+
env.password $password
|
64
|
+
end
|
65
|
+
if $username
|
66
|
+
env.username $username
|
67
|
+
end
|
68
|
+
env.__completed_loading
|
69
|
+
end
|
70
|
+
installer
|
71
|
+
}
|
72
|
+
|
73
|
+
require 'dbgeni'
|
74
|
+
|
75
|
+
begin
|
76
|
+
require 'dbgeni/commands/commands'
|
77
|
+
rescue DBGeni::ConfigSyntaxError => e
|
78
|
+
puts "There is an error in the config file: #{e.to_s}"
|
79
|
+
exit(1)
|
80
|
+
rescue DBGeni::ConfigFileNotExist => e
|
81
|
+
puts "The config file #{$config_file} does not exist"
|
82
|
+
exit(1)
|
83
|
+
rescue DBGeni::ConfigFileNotSpecified => e
|
84
|
+
puts "No config file was specified"
|
85
|
+
exit(1)
|
86
|
+
rescue DBGeni::ConfigAmbiguousEnvironment => e
|
87
|
+
puts "No environment specified and config file defines more than one environment"
|
88
|
+
exit(1)
|
89
|
+
rescue DBGeni::EnvironmentNotExist => e
|
90
|
+
puts "The environment #{$environment_name} does not exist"
|
91
|
+
exit(1)
|
92
|
+
rescue DBGeni::DBConnectionError => e
|
93
|
+
puts "Failed to establish databse connection: #{e.to_s}"
|
94
|
+
exit(1)
|
95
|
+
end
|
96
|
+
|
data/lib/dbgeni/code.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
module DBGeni
|
2
|
+
class Code
|
3
|
+
|
4
|
+
attr_reader :directory, :filename, :type, :name, :logfile, :error_messages
|
5
|
+
PACKAGE_SPEC = 'PACKAGE SPEC'
|
6
|
+
PACKAGE_BODY = 'PACKAGE BODY'
|
7
|
+
PROCEDURE = 'PROCEDURE'
|
8
|
+
FUNCTION = 'FUNCTION'
|
9
|
+
TRIGGER = 'TRIGGER'
|
10
|
+
TYPE = 'TYPE'
|
11
|
+
|
12
|
+
UNKNOWN = 'UNKNOWN'
|
13
|
+
|
14
|
+
APPLIED = 'Applied'
|
15
|
+
|
16
|
+
EXT_MAP = {
|
17
|
+
'pks' => PACKAGE_SPEC,
|
18
|
+
'pkb' => PACKAGE_BODY,
|
19
|
+
'prc' => PROCEDURE,
|
20
|
+
'fnc' => FUNCTION,
|
21
|
+
'trg' => TRIGGER,
|
22
|
+
'typ' => TYPE,
|
23
|
+
'sql' => UNKNOWN
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(directory, filename)
|
27
|
+
@directory = directory
|
28
|
+
@filename = filename
|
29
|
+
@runnable_code = nil
|
30
|
+
set_type
|
31
|
+
set_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def ==(other)
|
35
|
+
if @directory == other.directory && @type == other.type && @name == other.name
|
36
|
+
true
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def sort_field
|
43
|
+
# Normally alphabetical sorting is enough, but this means pkg specs sort after
|
44
|
+
# bodies. So need to do something about that. The simplest *hack* is to replace
|
45
|
+
# pks with pka to get it to sort before pkb, just for sorting purposes
|
46
|
+
sortable = @filename.gsub(/\.pks$/, '.pka')
|
47
|
+
end
|
48
|
+
|
49
|
+
def db_hash(config, connection)
|
50
|
+
results = connection.execute("select sequence_or_hash
|
51
|
+
from #{config.db_table}
|
52
|
+
where migration_name = ?
|
53
|
+
and migration_type = ?", @name, @type)
|
54
|
+
results.length == 1 ? results[0][0] : nil
|
55
|
+
end
|
56
|
+
#" THis line is just to fix the bad syntax highlighting in emacs!
|
57
|
+
|
58
|
+
def hash
|
59
|
+
# TODO what if file is empty?
|
60
|
+
@hash ||= Digest::SHA1.file(File.join(@directory, @filename)).hexdigest
|
61
|
+
end
|
62
|
+
|
63
|
+
# if the DB hash equals the file hash then it is current
|
64
|
+
def current?(config, connection)
|
65
|
+
hash == nil_to_s(db_hash(config, connection))
|
66
|
+
end
|
67
|
+
|
68
|
+
# if a hash is found in the DB then it is applied.
|
69
|
+
def applied?(config, connection)
|
70
|
+
db_hash(config, connection) ? true : false
|
71
|
+
end
|
72
|
+
|
73
|
+
# If there is no db_hash then its outstanding.
|
74
|
+
def outstanding?(config, connection)
|
75
|
+
db_hash(config, connection) ? false : true
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_applied(config, connection)
|
79
|
+
env(config, connection)
|
80
|
+
insert_or_set_state(APPLIED)
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_removed(config, connection)
|
84
|
+
env(config, connection)
|
85
|
+
remove_db_record
|
86
|
+
end
|
87
|
+
|
88
|
+
def apply!(config, connection, force=false)
|
89
|
+
env(config, connection)
|
90
|
+
ensure_file_exists
|
91
|
+
if current?(config, connection) and force != true
|
92
|
+
raise DBGeni::CodeModuleCurrent, self.to_s
|
93
|
+
end
|
94
|
+
migrator = DBGeni::Migrator.initialize(config, connection)
|
95
|
+
convert_code(config)
|
96
|
+
begin
|
97
|
+
migrator.compile(self)
|
98
|
+
set_applied(config,connection)
|
99
|
+
rescue DBGeni::MigratorCouldNotConnect
|
100
|
+
@error_messages = ""
|
101
|
+
raise DBGeni::MigratorCouldNotConnect
|
102
|
+
rescue DBGeni::MigrationContainsErrors
|
103
|
+
# MYSQL (and sybase is like mysql) and Oracle procedures are handled different.
|
104
|
+
# In Oracle if the code fails to
|
105
|
+
# compile, it can be because missing objects are not there, but in mysql the proc
|
106
|
+
# will compile fine if objects are missing - the only reason it seems to not compile
|
107
|
+
# is if the syntax is bad.
|
108
|
+
# Also, an error loading mysql proc will result in the proc not being on the DB at all.
|
109
|
+
# Can argue either way if DBGeni should stop on code errors. As many oracle compile errors
|
110
|
+
# could be caused by objects that have not been created yet, best for Oracle to continue,
|
111
|
+
# but for mysql I think it is best to stop.
|
112
|
+
if migrator.class.to_s =~ /Oracle/
|
113
|
+
@error_messages = migrator.migration_errors
|
114
|
+
elsif migrator.class.to_s =~ /(Mysql|Sybase)/
|
115
|
+
@error_messages = migrator.code_errors
|
116
|
+
end
|
117
|
+
unless force
|
118
|
+
raise DBGeni::CodeApplyFailed
|
119
|
+
end
|
120
|
+
ensure
|
121
|
+
@logfile = migrator.logfile
|
122
|
+
# Only set this if it has not been set in the exception handler
|
123
|
+
@error_messages ||= migrator.code_errors
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def remove!(config, connection, force=false)
|
128
|
+
env(config, connection)
|
129
|
+
ensure_file_exists
|
130
|
+
migrator = DBGeni::Migrator.initialize(config, connection)
|
131
|
+
begin
|
132
|
+
migrator.remove(self)
|
133
|
+
remove_db_record
|
134
|
+
rescue Exception => e
|
135
|
+
raise DBGeni::CodeRemoveFailed, e.to_s
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_s
|
140
|
+
"#{@type.ljust(12)} #{@name}"
|
141
|
+
end
|
142
|
+
|
143
|
+
def convert_code(config)
|
144
|
+
@runnable_code = DBGeni::FileConverter.convert(@directory, @filename, config)
|
145
|
+
end
|
146
|
+
|
147
|
+
def runnable_code
|
148
|
+
if @runnable_code
|
149
|
+
@runnable_code
|
150
|
+
else
|
151
|
+
File.join(@directory, @filename)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def insert_or_set_state(state)
|
158
|
+
# no hash in the DB means there is no db record...
|
159
|
+
results = db_hash(@config, @connection)
|
160
|
+
if results == nil then
|
161
|
+
add_db_record(state)
|
162
|
+
else
|
163
|
+
update_db_record(state)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def add_db_record(state)
|
169
|
+
results = @connection.execute("insert into #{@config.db_table}
|
170
|
+
(
|
171
|
+
sequence_or_hash,
|
172
|
+
migration_name,
|
173
|
+
migration_type,
|
174
|
+
migration_state,
|
175
|
+
start_dtm
|
176
|
+
)
|
177
|
+
values
|
178
|
+
(
|
179
|
+
?,
|
180
|
+
?,
|
181
|
+
?,
|
182
|
+
?,
|
183
|
+
#{@connection.date_placeholder('sdtm')}
|
184
|
+
)", hash, @name, @type, state, @connection.date_as_string(Time.now))
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def update_db_record(state)
|
189
|
+
results = @connection.execute("update #{@config.db_table}
|
190
|
+
set sequence_or_hash = ?,
|
191
|
+
completed_dtm = #{@connection.date_placeholder('sdtm')},
|
192
|
+
migration_state = ?
|
193
|
+
where migration_type = ?
|
194
|
+
and migration_name = ?", hash, @connection.date_as_string(Time.now), state, @type, @name)
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
def remove_db_record
|
199
|
+
results = @connection.execute("delete from #{@config.db_table}
|
200
|
+
where migration_type = ?
|
201
|
+
and migration_name = ?", @type, @name)
|
202
|
+
end
|
203
|
+
|
204
|
+
def set_type
|
205
|
+
@filename =~ /\.(.+)$/
|
206
|
+
if EXT_MAP.has_key?($1)
|
207
|
+
@type = EXT_MAP[$1]
|
208
|
+
else
|
209
|
+
raise DBGeni::UnknownCodeType, $1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def set_name
|
214
|
+
# If a filename starts with <digit>+_ then that part should be
|
215
|
+
# stripped away to get the real name. It is purely for ordering.
|
216
|
+
@filename =~ /^(?:\d+_{1}){0,1}(.+)\.[a-z]{3}$/ #/^[\d+_]*(.+)\.[a-z]{3}$/
|
217
|
+
@name = $1.upcase
|
218
|
+
end
|
219
|
+
|
220
|
+
def env(config, connection)
|
221
|
+
@config = config
|
222
|
+
@connection = connection
|
223
|
+
end
|
224
|
+
|
225
|
+
def ensure_file_exists
|
226
|
+
unless File.exists? File.join(@directory, @filename)
|
227
|
+
raise DBGeni::CodeFileNotExist, File.join(@directory, @filename)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def nil_to_s(obj)
|
232
|
+
obj ? obj : ''
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|