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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +29 -0
  3. data/bin/dbgeni +2 -0
  4. data/lib/dbgeni/base.rb +146 -0
  5. data/lib/dbgeni/base_code.rb +143 -0
  6. data/lib/dbgeni/blank_slate.rb +24 -0
  7. data/lib/dbgeni/cli.rb +96 -0
  8. data/lib/dbgeni/code.rb +235 -0
  9. data/lib/dbgeni/code_list.rb +60 -0
  10. data/lib/dbgeni/commands/code.rb +151 -0
  11. data/lib/dbgeni/commands/commands.rb +41 -0
  12. data/lib/dbgeni/commands/config.rb +36 -0
  13. data/lib/dbgeni/commands/dmls.rb +244 -0
  14. data/lib/dbgeni/commands/generate.rb +257 -0
  15. data/lib/dbgeni/commands/initialize.rb +41 -0
  16. data/lib/dbgeni/commands/migrations.rb +243 -0
  17. data/lib/dbgeni/commands/milestones.rb +52 -0
  18. data/lib/dbgeni/commands/new.rb +178 -0
  19. data/lib/dbgeni/config.rb +325 -0
  20. data/lib/dbgeni/connectors/connector.rb +59 -0
  21. data/lib/dbgeni/connectors/mysql.rb +146 -0
  22. data/lib/dbgeni/connectors/oracle.rb +149 -0
  23. data/lib/dbgeni/connectors/sqlite.rb +166 -0
  24. data/lib/dbgeni/connectors/sybase.rb +97 -0
  25. data/lib/dbgeni/dml_cli.rb +35 -0
  26. data/lib/dbgeni/environment.rb +161 -0
  27. data/lib/dbgeni/exceptions/exception.rb +69 -0
  28. data/lib/dbgeni/file_converter.rb +44 -0
  29. data/lib/dbgeni/initializers/initializer.rb +44 -0
  30. data/lib/dbgeni/initializers/mysql.rb +36 -0
  31. data/lib/dbgeni/initializers/oracle.rb +38 -0
  32. data/lib/dbgeni/initializers/sqlite.rb +33 -0
  33. data/lib/dbgeni/initializers/sybase.rb +34 -0
  34. data/lib/dbgeni/logger.rb +60 -0
  35. data/lib/dbgeni/migration.rb +302 -0
  36. data/lib/dbgeni/migration_cli.rb +204 -0
  37. data/lib/dbgeni/migration_list.rb +91 -0
  38. data/lib/dbgeni/migrators/migrator.rb +40 -0
  39. data/lib/dbgeni/migrators/migrator_interface.rb +51 -0
  40. data/lib/dbgeni/migrators/mysql.rb +82 -0
  41. data/lib/dbgeni/migrators/oracle.rb +211 -0
  42. data/lib/dbgeni/migrators/sqlite.rb +90 -0
  43. data/lib/dbgeni/migrators/sybase.rb +118 -0
  44. data/lib/dbgeni/plugin.rb +92 -0
  45. data/lib/dbgeni.rb +52 -0
  46. metadata +87 -0
@@ -0,0 +1,149 @@
1
+ module DBGeni
2
+
3
+ module Connector
4
+
5
+ class Oracle
6
+
7
+ if RUBY_PLATFORM == 'java'
8
+ require 'java'
9
+ java_import 'oracle.jdbc.OracleDriver'
10
+ java_import 'java.sql.DriverManager'
11
+ else
12
+ require 'oci8'
13
+ end
14
+
15
+ attr_reader :connection
16
+ attr_reader :database
17
+
18
+ def self.connect(user, password, database, host=nil, port=nil)
19
+ self.new(user, password, database, host, port)
20
+ end
21
+
22
+ def disconnect
23
+ @connection.logoff
24
+ end
25
+
26
+ def execute(sql, *binds)
27
+ if RUBY_PLATFORM == 'java'
28
+ return execute_jdbc(sql, *binds)
29
+ end
30
+
31
+ begin
32
+ # OCI doesn't like the ? bind variables, so need to convert them
33
+ index = 0
34
+ new_sql = sql.gsub(/\?/) do |m|
35
+ index += 1
36
+ ":b#{index.to_s}"
37
+ end
38
+
39
+ query = @connection.parse(new_sql)
40
+ query.exec(*binds)
41
+
42
+ results = nil
43
+ if query.type == OCI8::STMT_SELECT
44
+ results = Array.new
45
+ while r = query.fetch()
46
+ results.push r
47
+ end
48
+ else
49
+ # everthing is auto commit right now ...
50
+ @connection.commit
51
+ end
52
+ ensure
53
+ begin
54
+ query.close()
55
+ rescue
56
+ end
57
+ end
58
+ results
59
+ end
60
+
61
+ def ping
62
+ results = self.execute('select dummy from dual')
63
+ if results.length == 1
64
+ true
65
+ else
66
+ false
67
+ end
68
+ end
69
+
70
+ def commit
71
+ @connection.commit
72
+ end
73
+
74
+ def rollback
75
+ @connection.rollback
76
+ end
77
+
78
+ def date_placeholder(bind_var)
79
+ "to_date(:#{bind_var}, 'YYYYMMDDHH24MISS')"
80
+ end
81
+
82
+ def date_as_string(dtm)
83
+ dtm.strftime '%Y%m%d%H%M%S'
84
+ end
85
+
86
+ private
87
+
88
+ def initialize(user, password, database, host=nil, port=1521)
89
+ @database = database
90
+
91
+ if RUBY_PLATFORM == 'java'
92
+ oradriver = OracleDriver.new
93
+ DriverManager.registerDriver oradriver
94
+ begin
95
+ @connection = DriverManager.get_connection("jdbc:oracle:thin:@#{host}:#{port}/#{database}", user, password)
96
+ @connection.auto_commit = true
97
+ rescue Exception => e
98
+ raise DBGeni::DBConnectionError, e.to_s
99
+ end
100
+ else
101
+ begin
102
+ @connection = OCI8.new(user, password, database)
103
+ rescue Exception => e
104
+ raise DBGeni::DBConnectionError, e.to_s
105
+ end
106
+ end
107
+ end
108
+
109
+ def execute_jdbc(sql, *binds)
110
+ begin
111
+ query = @connection.prepare_statement(sql)
112
+ binds.each_with_index do |b, i|
113
+ if b.is_a?(String)
114
+ query.setString(i+1, b)
115
+ elsif b.is_a?(Fixnum)
116
+ query.setInt(i+1, b)
117
+ end
118
+ end
119
+ results = nil
120
+ unless sql =~ /^\s*select/i
121
+ query.execute()
122
+ else
123
+ results = Array.new
124
+ rset = query.execute_query()
125
+ cols = rset.get_meta_data.get_column_count
126
+ while(r = rset.next) do
127
+ a = Array.new
128
+ 1.upto(cols) do |i|
129
+ if rset.get_meta_data.get_column_type_name(i) == 'NUMBER'
130
+ a.push rset.get_float(i)
131
+ else
132
+ a.push rset.get_object(i)
133
+ end
134
+ end
135
+ results.push a
136
+ end
137
+ end
138
+ results
139
+ ensure
140
+ begin
141
+ query.close()
142
+ rescue Exception => e
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,166 @@
1
+ module DBGeni
2
+
3
+ module Connector
4
+ class Sqlite
5
+
6
+ if RUBY_PLATFORM == 'java'
7
+ require 'rubygems'
8
+ require 'jdbc/sqlite3'
9
+ Java::org.sqlite.JDBC #initialize the driver
10
+ else
11
+ require 'sqlite3'
12
+ end
13
+
14
+ attr_reader :connection
15
+ attr_reader :database
16
+
17
+ def self.db_file_path(base, file)
18
+ # starts with a ., eg ./filename, or does not
19
+ # contain any forward or backslashes, eg db.sqlite3
20
+ if file =~ /^\./ || file !~ /\/|\\/
21
+ # it is a relative path, so join to base directory
22
+ File.join(base, file)
23
+ else
24
+ file
25
+ end
26
+ end
27
+
28
+ def self.connect(user, password, database)
29
+ raise DBGeni::DatabaseNotSpecified unless database
30
+ self.new(database)
31
+ end
32
+
33
+ def disconnect
34
+ @connection.close
35
+ end
36
+
37
+ def execute(sql, *binds)
38
+ # unless @connection.transaction_active?
39
+ # @connection.transaction
40
+ # end
41
+ if RUBY_PLATFORM == 'java'
42
+ return execute_jdbc(sql, *binds)
43
+ end
44
+ begin
45
+ # Why am I re-establishing the database connection? Well, something has changed
46
+ # somewhere in the sqlite3 application or ruby drivers, and without this line
47
+ # I get lots of errors in the test suite:
48
+ #
49
+ # sqlite 3.7.8 QLite3::SQLException: SQL logic error or missing database
50
+ #
51
+ # This line fixes it be reconnecting. I am not sure if this is a non-reentrant
52
+ # issue, but it used to work just fine :-/
53
+ # SQLITE + DBGeni is not really intended as a production combo, so this is probably
54
+ # ok.
55
+ @connection = SQLite3::Database.new(@database)
56
+ query = @connection.prepare(sql)
57
+ binds.each_with_index do |b, i|
58
+ query.bind_param(i+1, b)
59
+ end
60
+ results = query.execute!
61
+ # This shouldn't even be needed, as there are never transactions started.
62
+ # by default everthing in sqlite is autocommit
63
+ if @connection.transaction_active?
64
+ @connection.commit
65
+ end
66
+ results
67
+ ensure
68
+ begin
69
+ query.close
70
+ rescue Exception => e
71
+ end
72
+ end
73
+ end
74
+
75
+ def execute_jdbc(sql, *binds)
76
+ query = @connection.prepare_statement(sql)
77
+ binds.each_with_index do |b, i|
78
+ if b.is_a?(String)
79
+ query.setString(i+1, b)
80
+ elsif b.is_a?(Fixnum)
81
+ query.setInt(i+1, b)
82
+ end
83
+ end
84
+ results = Array.new
85
+ unless sql =~ /^\s*select/i
86
+ query.execute()
87
+ else
88
+ rset = query.execute_query()
89
+ cols = rset.get_meta_data.get_column_count
90
+ while(r = rset.next) do
91
+ a = Array.new
92
+ 1.upto(cols) do |i|
93
+ a.push rset.get_object(i)
94
+ end
95
+ results.push a
96
+ end
97
+ end
98
+ query.close
99
+ results
100
+ end
101
+
102
+ def ping
103
+ ! @connection.closed?
104
+ end
105
+
106
+ def commit
107
+ @connection.commit
108
+ end
109
+
110
+ def rollback
111
+ @connection.rollback
112
+ end
113
+
114
+ def date_placeholder(bind_var)
115
+ "time(:#{bind_var})"
116
+ end
117
+
118
+ def date_as_string(dtm)
119
+ dtm.strftime '%Y-%m-%d %H:%M:%S'
120
+ end
121
+
122
+ private
123
+
124
+ def initialize(database)
125
+ @database = database
126
+ begin
127
+ if RUBY_PLATFORM == 'java'
128
+ @connection = ::JavaSql::DriverManager.getConnection("jdbc:sqlite:#{database}")
129
+ else
130
+ @connection = SQLite3::Database.new(database)
131
+ end
132
+ rescue Exception => e
133
+ raise DBGeni::DBConnectionError, e.to_s
134
+ end
135
+ end
136
+
137
+ def execute_jdbc(sql, *binds)
138
+ query = @connection.prepare_statement(sql)
139
+ binds.each_with_index do |b, i|
140
+ if b.is_a?(String)
141
+ query.setString(i+1, b)
142
+ elsif b.is_a?(Fixnum)
143
+ query.setInt(i+1, b)
144
+ end
145
+ end
146
+ results = Array.new
147
+ unless sql =~ /^\s*select/i
148
+ query.execute()
149
+ else
150
+ rset = query.execute_query()
151
+ cols = rset.get_meta_data.get_column_count
152
+ while(r = rset.next) do
153
+ a = Array.new
154
+ 1.upto(cols) do |i|
155
+ a.push rset.get_object(i)
156
+ end
157
+ results.push a
158
+ end
159
+ end
160
+ query.close
161
+ results
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,97 @@
1
+ module DBGeni
2
+
3
+ module Connector
4
+
5
+ class Sybase
6
+
7
+ unless RUBY_PLATFORM == 'java'
8
+ raise DBGeni::UnsupportedRubyPlatform
9
+ end
10
+
11
+ require 'java'
12
+ java_import 'net.sourceforge.jtds.jdbc.Driver'
13
+ java_import 'java.sql.DriverManager'
14
+
15
+ attr_reader :connection
16
+ attr_reader :database
17
+
18
+ def self.connect(user, password, database, host=nil, port=nil)
19
+ self.new(user, password, database, host, port)
20
+ end
21
+
22
+ def disconnect
23
+ @connection.close
24
+ end
25
+
26
+ def execute(sql, *binds)
27
+ query = @connection.prepare_statement(sql)
28
+ binds.each_with_index do |b, i|
29
+ if b.is_a?(String)
30
+ query.setString(i+1, b)
31
+ elsif b.is_a?(Fixnum)
32
+ query.setInt(i+1, b)
33
+ end
34
+ end
35
+ results = Array.new
36
+ unless sql =~ /^\s*select/i
37
+ query.execute()
38
+ else
39
+ rset = query.execute_query()
40
+ cols = rset.get_meta_data.get_column_count
41
+ while(r = rset.next) do
42
+ a = Array.new
43
+ 1.upto(cols) do |i|
44
+ a.push rset.get_object(i)
45
+ end
46
+ results.push a
47
+ end
48
+ end
49
+ query.close
50
+ results
51
+ end
52
+
53
+ def ping
54
+ results = self.execute('select 1')
55
+ if results.length == 1
56
+ true
57
+ else
58
+ false
59
+ end
60
+ end
61
+
62
+ def commit
63
+ @connection.commit
64
+ end
65
+
66
+ def rollback
67
+ @connection.rollback
68
+ end
69
+
70
+ def date_placeholder(bind_var)
71
+ "cast(? as datetime)"
72
+ end
73
+
74
+ def date_as_string(dtm)
75
+ dtm.strftime '%Y-%m-%d %H:%M:%S'
76
+ end
77
+
78
+ private
79
+
80
+
81
+ #
82
+ def initialize(user, password, database, host=nil, port=nil)
83
+ @database = database
84
+
85
+ sybdriver = Driver.new
86
+ DriverManager.registerDriver sybdriver
87
+ begin
88
+ @connection = DriverManager.get_connection("jdbc:jtds:sybase://#{host}:#{port}/#{database}", user, password)
89
+ @connection.auto_commit = true
90
+ rescue Exception => e
91
+ raise DBGeni::DBConnectionError, e.to_s
92
+ end
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,35 @@
1
+ module DBGeni
2
+
3
+ class DmlCLI < MigrationCLI
4
+
5
+ BEFORE_UP_RUN_PLUGIN = :before_dml_up
6
+ AFTER_UP_RUN_PLUGIN = :after_dml_up
7
+ BEFORE_DOWN_RUN_PLUGIN = :before_dml_down
8
+ AFTER_DOWN_RUN_PLUGIN = :after_dml_down
9
+ BEFORE_RUNNING_PLUGIN = :before_running_dml
10
+ AFTER_RUNNING_PLUGIN = :after_running_dml
11
+
12
+ def migrations
13
+ @migration_list ||= DBGeni::MigrationList.new_dml_migrations(@config.dml_directory) unless @migration_list
14
+ @migration_list.migrations
15
+ end
16
+
17
+ private
18
+
19
+ def find_migration(migration_name)
20
+ m = Migration.initialize_from_internal_name(@config.dml_directory, migration_name)
21
+ end
22
+
23
+ def set_plugin_hooks
24
+ @before_up_run_plugin = :before_dml_up
25
+ @after_up_run_plugin = :after_dml_up
26
+ @before_down_run_plugin = :before_dml_down
27
+ @after_down_run_plugin = :after_dml_down
28
+ @before_running_plugin = :before_running_dmls
29
+ @after_running_plugin = :after_running_dmls
30
+ end
31
+
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,161 @@
1
+ module DBGeni
2
+
3
+ class Environment < BlankSlate
4
+
5
+ def initialize(environment_name)
6
+ @environment_name = environment_name
7
+ @params = Hash.new
8
+ @loading = true
9
+ end
10
+
11
+
12
+ def __merge_environment(new_env)
13
+ @params.merge!(new_env.__params)
14
+ end
15
+
16
+ def __enable_loading
17
+ @loading = true
18
+ end
19
+
20
+ def __completed_loading
21
+ @loading = false
22
+ end
23
+
24
+ def __is_loadable?
25
+ @loading
26
+ end
27
+
28
+ def __environment_name
29
+ @environment_name
30
+ end
31
+
32
+ def __params
33
+ @params
34
+ end
35
+
36
+ def __merge_defaults(default_params)
37
+ all_params = default_params.merge(@params)
38
+ @params = all_params
39
+ end
40
+
41
+ def method_missing(name, *args, &block)
42
+ str_name = name.to_s.gsub(/=$/, '')
43
+ if __is_loadable?
44
+ __load_parameter(str_name, *args)
45
+ else
46
+ __get_parameter(str_name, *args)
47
+ end
48
+ end
49
+
50
+ def __to_s
51
+ str = ''
52
+ # get the longest key length to pad the keys
53
+ max_length = @params.keys.map{ |k| k.length }.sort.last
54
+ @params.keys.sort.each do |k|
55
+ str << "#{k.ljust(max_length, ' ')} => #{@params[k]}\n"
56
+ end
57
+ str
58
+ end
59
+
60
+ private
61
+
62
+ def __load_parameter(name, *args)
63
+ if @params.has_key?(name)
64
+ warn "Parameter #{name} was previously defined"
65
+ end
66
+ # TODO - Not sure whether to raise or just let it be nil
67
+ #if nil == args[0]
68
+ # raise ""
69
+ #end
70
+ @params[name] = args[0] unless args[0] == ''
71
+ end
72
+
73
+ def __get_parameter(name, *args)
74
+ unless @params.has_key?(name)
75
+ # Password is a special case - if it is not defined it should be prompted for
76
+ if name == 'password'
77
+ puts "Please enter the password for #{@params['database']} in the #{@environment_name} environment\n"
78
+ password = ''
79
+ begin
80
+ while (password == '')
81
+ print "password: "
82
+ unless is_windows?
83
+ system "stty -echo"
84
+ end
85
+ password = gets.chomp
86
+ unless is_windows?
87
+ system "stty echo"
88
+ end
89
+ print "\n"
90
+ end
91
+ rescue Interrupt
92
+ unless is_windows?
93
+ system "stty echo"
94
+ end
95
+ exit(1)
96
+ end
97
+ @params[name] = password
98
+ password
99
+ end
100
+ else
101
+ @params[name]
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+
109
+ # class Environment < BlankSlate
110
+
111
+ # def initialize(name)
112
+ # @__environment_name = name
113
+ # end
114
+
115
+ # def metaclass
116
+ # class << self
117
+ # self
118
+ # end
119
+ # end
120
+
121
+ # def lock
122
+ # metaclass.send(:alias_method, :__method_missing, :method_missing)
123
+ # metaclass.send(:undef_method, :method_missing)
124
+ # end
125
+
126
+ # def unlock
127
+ # metaclass.send(:alias_method, :method_missing, :__method_missing)
128
+ # end
129
+
130
+ # def method_missing(name, *args, &block)
131
+ # # TODO - add list of illegal param names (ie the list of kept methods
132
+ # #
133
+ # # if the method was a meth= type one, we need to get rid of the = sign
134
+ # #
135
+ # # This allows variables to be defined as
136
+ # # param 'value'
137
+ # # or
138
+ # # param = 'value'
139
+ # str_name = name.to_s.gsub(/=$/, '')
140
+ # method_name = ('@'+str_name).intern
141
+ # self.instance_variable_set(method_name, args[0])
142
+ # instance_eval("
143
+ # def #{str_name}(__param = nil)
144
+ # return @#{str_name} unless __param
145
+ # @#{str_name} = __param
146
+ # end
147
+ # ")
148
+ # metaclass.send(:alias_method, "#{str_name}=".intern, str_name.intern)
149
+ # end
150
+
151
+
152
+
153
+ # Method to define a generic getter setter.
154
+ #def font_size(size = nil)
155
+ # return @font_size unless size
156
+ # @font_size = size
157
+ # end
158
+ #alias_method :font_size=, :font_size
159
+
160
+ # end
161
+ #end
@@ -0,0 +1,69 @@
1
+ module DBGeni
2
+
3
+ class DBConnectionError < Exception; end
4
+ class NoLoggerLocation < Exception; end
5
+ class MigrationDirectoryNotExist < Exception; end
6
+ class MigrationFilenameInvalid < Exception; end
7
+ class MigrationFileNotExist < Exception; end
8
+ class MigrationAlreadyApplied < Exception; end
9
+ class MigrationNotApplied < Exception; end
10
+ class MigrationApplyFailed < Exception; end
11
+ class MigrationContainsErrors < Exception; end
12
+ class MigrationNotOutstanding < Exception; end
13
+
14
+ class EnvironmentNotExist < Exception; end
15
+
16
+ class ConfigFileNotExist < Exception; end
17
+ class ConfigAmbiguousEnvironment < Exception; end
18
+ class ConfigFileNotSpecified < Exception; end
19
+ class ConfigSyntaxError < Exception; end
20
+
21
+ class NoEnvironmentSelected < Exception; end
22
+
23
+ ## Initializer
24
+
25
+ # If an attempt is made to load an initializer that doesn't exist
26
+ class NoInitializerForDBType < Exception; end
27
+ # If the initializer is not corretly defined, this will be raise.
28
+ class InvalidInitializerForDBType < Exception; end
29
+
30
+ ## Connectors
31
+ class NoConnectorForDBType < Exception; end
32
+ class InvalidConnectorForDBType < Exception; end
33
+
34
+ ## Migrators
35
+ class NoMigratorForDBType < Exception; end
36
+ class InvalidMigratorForDBType < Exception; end
37
+
38
+ class DatabaseAlreadyInitialized < Exception; end
39
+ class DatabaseNotInitialized < Exception; end
40
+ class DBGeni::DatabaseNotSpecified < Exception; end
41
+ class NoOutstandingMigrations < Exception; end
42
+ class NoAppliedMigrations < Exception; end
43
+ class MigratorCouldNotConnect < Exception; end
44
+
45
+ ## code
46
+ class UnknownCodeType < Exception; end
47
+ class CodeModuleCurrent < Exception; end
48
+ class CodeApplyFailed < Exception; end
49
+ class CodeFileNotExist < Exception; end
50
+ class CodeDirectoryNotExist < Exception; end
51
+ class NoOutstandingCode < Exception; end
52
+ class NoCodeFilesExist < Exception; end
53
+ class CannotRemoveUnknownObject < Exception; end
54
+
55
+ class DBCLINotOnPath < Exception; end
56
+ class CodeRemoveFailed < Exception; end
57
+ class NotImplemented < Exception; end
58
+
59
+ class MilestoneHasNoMigration < Exception; end
60
+ class UnsupportedRubyPlatform < Exception; end
61
+ class ConnectionError < Exception; end
62
+
63
+ # Plugins
64
+ class InvalidHook < Exception; end
65
+ class PluginDoesNotRespondToRun < Exception; end
66
+ class PluginDirectoryNotAccessible < Exception; end
67
+ class PluginException < Exception; end
68
+
69
+ end