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
@@ -0,0 +1,178 @@
|
|
1
|
+
# Expects only one parameter, the directory of the new dbgeni directory structure
|
2
|
+
|
3
|
+
ARGV << '--help' if ARGV.empty?
|
4
|
+
|
5
|
+
if ARGV.length > 1 or %w(-h --help).include? ARGV[0]
|
6
|
+
puts "Error: only one parameter is allowed for the new command" if ARGV.length > 1
|
7
|
+
puts <<-EOF
|
8
|
+
Usage: dbgeni new <path/to/directory/for/new/installer/structure>
|
9
|
+
EOF
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'fileutils'
|
14
|
+
|
15
|
+
directory = ARGV.shift
|
16
|
+
|
17
|
+
# There can be two commands here - first is to create the directory
|
18
|
+
# structure, the second is new-conf to just create the file, but only if
|
19
|
+
# the directory exists.
|
20
|
+
if %w(n new).include? $initial_command
|
21
|
+
if File.directory?(directory)
|
22
|
+
puts "Error: The directory already exists"
|
23
|
+
exit(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
# create the base directory
|
27
|
+
begin
|
28
|
+
puts "creating directory: #{directory}"
|
29
|
+
FileUtils.mkdir_p(directory)
|
30
|
+
rescue Exception => e
|
31
|
+
puts "error: failed to create #{directory} - #{e.to_s}"
|
32
|
+
exit(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
# create the directory to hold migrations
|
36
|
+
begin
|
37
|
+
puts "creating directory: #{directory}/migrations"
|
38
|
+
FileUtils.mkdir_p(directory+'/migrations')
|
39
|
+
rescue Exception => e
|
40
|
+
puts "error: failed to create #{directory}/migrations - #{e.to_s}"
|
41
|
+
exit(1)
|
42
|
+
end
|
43
|
+
# create the directory to hold code
|
44
|
+
begin
|
45
|
+
puts "creating directory: #{directory}/code"
|
46
|
+
FileUtils.mkdir_p(directory+'/code')
|
47
|
+
rescue Exception => e
|
48
|
+
puts "error: failed to create #{directory}/code - #{e.to_s}"
|
49
|
+
exit(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
# create the directory to hold dml
|
53
|
+
begin
|
54
|
+
puts "creating directory: #{directory}/dml"
|
55
|
+
FileUtils.mkdir_p(directory+'/dml')
|
56
|
+
rescue Exception => e
|
57
|
+
puts "error: failed to create #{directory}/dml - #{e.to_s}"
|
58
|
+
exit(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
else
|
62
|
+
unless File.directory?(directory)
|
63
|
+
puts "Error: The directory #{directory} does not exist"
|
64
|
+
exit(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Create the initial version of the configuration file
|
70
|
+
begin
|
71
|
+
puts "creating file: #{directory}/.dbgeni"
|
72
|
+
conf = File.open("#{directory}/.dbgeni", "w")
|
73
|
+
conf.puts <<-EOF
|
74
|
+
|
75
|
+
# This directory specifies the location of the migrations directory
|
76
|
+
migrations_directory "./migrations"
|
77
|
+
|
78
|
+
# This directory specifies the location of the DML files directory
|
79
|
+
dml_directory "./dml"
|
80
|
+
|
81
|
+
# This directory specifies the location of any code modules
|
82
|
+
code_directory "./code"
|
83
|
+
|
84
|
+
# This specifes the location of the plugin directory. Specifying this parameter
|
85
|
+
# enables plugins and the directory must exist
|
86
|
+
# plugin_directory "./dbgeni_plugins"
|
87
|
+
|
88
|
+
# This specifies the type of database this installer is applied against
|
89
|
+
# Valid values are oracle, mysql, sqlite, sybase however this is not validated
|
90
|
+
# to enable different database plugins to easily be added.
|
91
|
+
database_type "sqlite"
|
92
|
+
|
93
|
+
# This is the table the installer logs applied migrations in the database
|
94
|
+
# The default is dbgeni_migrations
|
95
|
+
database_table "dbgeni_migrations"
|
96
|
+
|
97
|
+
# Use the include_file option to load another config file, perhaps
|
98
|
+
# containing environment details for many different environments in one place
|
99
|
+
#
|
100
|
+
# include_file '/path/to/include/file'
|
101
|
+
|
102
|
+
|
103
|
+
# Environment Section
|
104
|
+
#
|
105
|
+
# There must be at least one environment, and at a minimum each environment
|
106
|
+
# should define a username, database and password (except sqlite which only
|
107
|
+
# requires a database to be specified)
|
108
|
+
#
|
109
|
+
# Typically there will be more than one enviroment block detailling development,
|
110
|
+
# test and production but any number of environments are valid provided there is at least one.
|
111
|
+
#
|
112
|
+
# The environments can be defined here or in an included file.
|
113
|
+
|
114
|
+
# Defaults - this section is optional and can be used to define parameters that must
|
115
|
+
# appear in every environment, for example install_schema. If a parameter is defined
|
116
|
+
# here, and also defined in the environment, then the value in the environment block
|
117
|
+
# will override the default. If many default blocks are specified, in included files
|
118
|
+
# for example, the parameters are merged. If any parameters are duplicated, the last to
|
119
|
+
# be loaded will used.
|
120
|
+
#
|
121
|
+
# defaults {
|
122
|
+
# username 'scott'
|
123
|
+
# }
|
124
|
+
|
125
|
+
environment('development') {
|
126
|
+
|
127
|
+
### SQLITE
|
128
|
+
database 'testdb.sqlite' # This is the only required connection parameter for sqlite
|
129
|
+
|
130
|
+
### ORACLE
|
131
|
+
#
|
132
|
+
# database 'DEV1' # This is the name of an entry in the tns_names.ora file
|
133
|
+
# username 'scott' # This is the username to connect as, and also the default schema
|
134
|
+
# password 'tiger' # This is the password for the username
|
135
|
+
#
|
136
|
+
# install_schema 'other' # Optional: If dbgeni connects as a database user, but the application is owned
|
137
|
+
# by another user, set the application schema here
|
138
|
+
|
139
|
+
### MYSQL
|
140
|
+
#
|
141
|
+
# database 'DEV1' # This is the database to use after connection
|
142
|
+
# username 'scott' # This is the username to connect as
|
143
|
+
# password 'tiger' # This is the password for the username
|
144
|
+
# hostname '127.0.0.1' # This is the hostname or IP mysql is running on
|
145
|
+
# For localhost use the IP 127.0.0.1 or it will not work.
|
146
|
+
# port '3306' # This the port of the mysql service
|
147
|
+
|
148
|
+
### SYBASE
|
149
|
+
#
|
150
|
+
# database 'DEV1' # This is the database to use after connection
|
151
|
+
# username 'scott' # This is the username to connect as
|
152
|
+
# password 'tiger' # This is the password for the username
|
153
|
+
# hostname '127.0.0.1' # This is the hostname or IP sybase is running on
|
154
|
+
# port '3306' # This the port of the sybase service
|
155
|
+
# sybase_service 'dev1' # THis is the sybase service name defined in the sql.ini file
|
156
|
+
|
157
|
+
|
158
|
+
# Other parameters can be defined here to be used as replacement parameters in scripts
|
159
|
+
# or in plugins in future versions of dbgeni.
|
160
|
+
# param_name 'value'
|
161
|
+
}
|
162
|
+
|
163
|
+
#
|
164
|
+
# environment('test') {
|
165
|
+
# username 'user'
|
166
|
+
# database 'TEST.WORLD'
|
167
|
+
# password ''
|
168
|
+
# }
|
169
|
+
#
|
170
|
+
|
171
|
+
|
172
|
+
EOF
|
173
|
+
rescue Exception => e
|
174
|
+
puts "error: failed to create #{directory}/.dbgeni - #{e.to_s}"
|
175
|
+
exit(1)
|
176
|
+
end
|
177
|
+
|
178
|
+
exit(0)
|
@@ -0,0 +1,325 @@
|
|
1
|
+
module DBGeni
|
2
|
+
|
3
|
+
# Config understands the following:
|
4
|
+
|
5
|
+
# migrations_directory 'value' # This will be checked to ensure its a valid dir
|
6
|
+
# # and defaults to 'migrations'
|
7
|
+
# environment('development') {
|
8
|
+
# username 'user' # this must be here, or it will error
|
9
|
+
# database 'MYDB.WORLD' # this must be here, or it will error. For Oracle, this is the TNS Name
|
10
|
+
# password '' # If this value is missing, it will be promoted for if the env is used.
|
11
|
+
# }
|
12
|
+
#
|
13
|
+
# environment('other environment') {
|
14
|
+
# username '' # the environment block can be repeated for many environments
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# global_parameters { # These are common parameters to all environments, but they can be
|
18
|
+
# # overriden. Basically take global, merge in environment
|
19
|
+
# param_name 'value'
|
20
|
+
# }
|
21
|
+
|
22
|
+
class Config
|
23
|
+
attr_reader :environments
|
24
|
+
attr_reader :current_environment
|
25
|
+
attr_reader :migration_directory
|
26
|
+
attr_reader :dml_directory
|
27
|
+
# ideally wnat code_dir to be code_directory, but then it clashes with the
|
28
|
+
# setter used in the config file, so need to change it. Probably more sensible
|
29
|
+
# like this than the migrations_directory vs migration_directory
|
30
|
+
# _-_
|
31
|
+
#
|
32
|
+
attr_reader :code_dir
|
33
|
+
attr_reader :db_type # oracle, mysql, sqlite etc, default sqlite
|
34
|
+
attr_reader :db_table # defaults to dbgeni_migrations
|
35
|
+
attr_reader :config_file
|
36
|
+
attr_reader :base_directory
|
37
|
+
|
38
|
+
DEFAULTS_ENV = '__defaults__'
|
39
|
+
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@migration_directory = 'migrations'
|
43
|
+
@dml_directory = 'dml'
|
44
|
+
@code_dir = 'code'
|
45
|
+
@plugin_dir = nil
|
46
|
+
@db_type = 'sqlite'
|
47
|
+
@db_table = 'dbgeni_migrations'
|
48
|
+
@base_dir = '.'
|
49
|
+
@environments = Hash.new
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def self.load_from_file(filename)
|
54
|
+
cfg = self.new
|
55
|
+
cfg.load_from_file(filename)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def load_from_file(filename)
|
60
|
+
if filename == nil
|
61
|
+
raise DBGeni::ConfigFileNotSpecified
|
62
|
+
end
|
63
|
+
raw_config = ''
|
64
|
+
self.base_directory = File.expand_path(File.dirname(filename))
|
65
|
+
@config_file = File.expand_path(filename)
|
66
|
+
begin
|
67
|
+
File.open(@config_file) do |f|
|
68
|
+
raw_config = f.read
|
69
|
+
end
|
70
|
+
rescue Errno::ENOENT
|
71
|
+
raise DBGeni::ConfigFileNotExist, "#{@config_location} (expanded from #{filename}) does not exist"
|
72
|
+
end
|
73
|
+
load(raw_config)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Normally this is assumed to be the location the config file is in.
|
78
|
+
# All relative file operations come from this directory
|
79
|
+
def base_directory=(dir)
|
80
|
+
@base_directory = dir
|
81
|
+
# If change the base directory, then unless migration dir is
|
82
|
+
# an absolute path, it will need to change too.
|
83
|
+
@migration_directory = File.join(@base_directory, @migration_directory) unless is_absolute_path?(@migration_directory)
|
84
|
+
@dml_directory = File.join(@base_directory, @dml_directory) unless is_absolute_path?(@dml_directory)
|
85
|
+
@code_dir = File.join(@base_directory, @code_dir) unless is_absolute_path?(@code_dir)
|
86
|
+
if @plugin_dir
|
87
|
+
@plugin_dir = File.join(@base_directory, @plugin_dir) unless is_absolute_path?(@plugin_dir)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# def get_environment(name)
|
93
|
+
# if name == nil
|
94
|
+
# # if there is only a single environment defined, then return it
|
95
|
+
# if @environments.keys.length == 1
|
96
|
+
# @environments[@environments.keys.first]
|
97
|
+
# else
|
98
|
+
# raise DBGeni::ConfigAmbiguousEnvironment, "More than one environment is defined"
|
99
|
+
# end
|
100
|
+
# else
|
101
|
+
# unless @environments.has_key?(name)
|
102
|
+
# raise DBGeni::EnvironmentNotExist
|
103
|
+
# end
|
104
|
+
# @environments[name]
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
|
108
|
+
|
109
|
+
def env
|
110
|
+
current_env
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def current_env
|
115
|
+
if @current_environment
|
116
|
+
@environments[@current_environment]
|
117
|
+
elsif @environments.keys.reject{|i| i == DEFAULTS_ENV}.length == 1
|
118
|
+
@environments[@environments.keys.reject{|i| i == DEFAULTS_ENV}.first]
|
119
|
+
else
|
120
|
+
raise DBGeni::ConfigAmbiguousEnvironment, "More than one environment is defined"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def set_env(name=nil)
|
126
|
+
if name == nil
|
127
|
+
valid_envs = @environments.keys.reject{|i| i == DEFAULTS_ENV}
|
128
|
+
if valid_envs.length == 1
|
129
|
+
@current_environment = valid_envs.first
|
130
|
+
else
|
131
|
+
raise DBGeni::ConfigAmbiguousEnvironment, "More than one environment is defined"
|
132
|
+
end
|
133
|
+
elsif @environments.has_key?(name)
|
134
|
+
@current_environment = name
|
135
|
+
else
|
136
|
+
raise DBGeni::EnvironmentNotExist
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def load(raw_config, recursed=false)
|
142
|
+
begin
|
143
|
+
self.instance_eval(raw_config)
|
144
|
+
rescue Exception => e
|
145
|
+
raise DBGeni::ConfigSyntaxError, e.to_s
|
146
|
+
end
|
147
|
+
merge_defaults unless recursed
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
def merge_defaults
|
152
|
+
if @environments.has_key? DEFAULTS_ENV
|
153
|
+
@environments.keys.each do |k|
|
154
|
+
next if k == DEFAULTS_ENV
|
155
|
+
@environments[k].__merge_defaults(@environments[DEFAULTS_ENV].__params)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
str = ''
|
162
|
+
str << "migrations_directory => #{@migration_directory}\n"
|
163
|
+
@environments.keys.sort.each do |k|
|
164
|
+
str << "\n\nEnvironment: #{k}\n"
|
165
|
+
str << "=============#{(1..k.length).map do "=" end.join}\n\n"
|
166
|
+
str << @environments[k].__to_s
|
167
|
+
end
|
168
|
+
str
|
169
|
+
end
|
170
|
+
|
171
|
+
######################################
|
172
|
+
# Methods below here are for the DSL #
|
173
|
+
######################################
|
174
|
+
|
175
|
+
# mig_dir could be defined as a file in current directory 'migrations'
|
176
|
+
# or it could be a relative directory './somedir/migrations'
|
177
|
+
# or it could be a full path '/somedir/migrations' or in windows 'C:\somedir\migrations'
|
178
|
+
# To use the migrations it needs to be expanded to a full path *somehow*.
|
179
|
+
# If it begins / or <LETTER>:\ then assume its full path, otherwise concatenate
|
180
|
+
# to the full path of the config file.
|
181
|
+
def migrations_directory(*p)
|
182
|
+
if p.length == 0
|
183
|
+
@migration_directory
|
184
|
+
else
|
185
|
+
if is_absolute_path?(p[0])
|
186
|
+
# it looks like an absolute path
|
187
|
+
@migration_directory = p[0]
|
188
|
+
else
|
189
|
+
# it looks like a relative path
|
190
|
+
if @base_directory
|
191
|
+
@migration_directory = File.join(@base_directory, p[0])
|
192
|
+
else
|
193
|
+
@migration_directory = p[0]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def dml_directory(*p)
|
200
|
+
if p.length == 0
|
201
|
+
@dml_directory
|
202
|
+
else
|
203
|
+
if is_absolute_path?(p[0])
|
204
|
+
# it looks like an absolute path
|
205
|
+
@dml_directory = p[0]
|
206
|
+
else
|
207
|
+
# it looks like a relative path
|
208
|
+
if @base_directory
|
209
|
+
@dml_directory = File.join(@base_directory, p[0])
|
210
|
+
else
|
211
|
+
@dml_directory = p[0]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def code_directory(*p)
|
218
|
+
if p.length == 0
|
219
|
+
@code_dir
|
220
|
+
else
|
221
|
+
if is_absolute_path?(p[0])
|
222
|
+
# it looks like an absolute path
|
223
|
+
@code_dir = p[0]
|
224
|
+
else
|
225
|
+
# it looks like a relative path
|
226
|
+
if @base_directory
|
227
|
+
@code_dir = File.join(@base_directory, p[0])
|
228
|
+
else
|
229
|
+
@code_dir = p[0]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def plugin_directory(*p)
|
236
|
+
if p.length == 0
|
237
|
+
@plugin_dir
|
238
|
+
else
|
239
|
+
if is_absolute_path?(p[0])
|
240
|
+
# it looks like an absolute path
|
241
|
+
@plugin_dir = p[0]
|
242
|
+
else
|
243
|
+
# it looks like a relative path
|
244
|
+
if @base_directory
|
245
|
+
@plugin_dir = File.join(@base_directory, p[0])
|
246
|
+
else
|
247
|
+
@plugin_dir = p[0]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
def database_type(*p)
|
255
|
+
# TODO - consider putting validation here
|
256
|
+
@db_type = p[0]
|
257
|
+
end
|
258
|
+
|
259
|
+
def database_table(*p)
|
260
|
+
# TODO - consider putting validation here
|
261
|
+
@db_table = p[0]
|
262
|
+
end
|
263
|
+
|
264
|
+
# For some reason I cannot work out, this method is never getting
|
265
|
+
# call in the evaluated block. Ideally wanted to have migrations_directory
|
266
|
+
# and migrations_directory= so that it doesn't matter which is called.
|
267
|
+
# def migrations_directory=(value)
|
268
|
+
# puts "called the equals one"
|
269
|
+
# @migration_directory = value
|
270
|
+
# end
|
271
|
+
|
272
|
+
|
273
|
+
# Given a block of environment details, generate a new environment
|
274
|
+
# object. eg
|
275
|
+
# environment('some_name') do
|
276
|
+
# database ''
|
277
|
+
# user ''
|
278
|
+
# password prompt
|
279
|
+
#
|
280
|
+
# some_dir_path '/path_to_directory'
|
281
|
+
def environment(name, &block)
|
282
|
+
env = Environment.new(name)
|
283
|
+
block.arity < 1 ? env.instance_eval(&block) : block.call(env)
|
284
|
+
env.__completed_loading
|
285
|
+
if @environments.has_key?(name)
|
286
|
+
@environments[name].__merge_environment(env)
|
287
|
+
else
|
288
|
+
@environments[name] = env
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def defaults(&block)
|
293
|
+
environment(DEFAULTS_ENV, &block)
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
def include_file(*p)
|
298
|
+
file = p[0]
|
299
|
+
if !is_absolute_path?(file)
|
300
|
+
file = File.join(@base_directory, file)
|
301
|
+
end
|
302
|
+
begin
|
303
|
+
raw_config = ''
|
304
|
+
File.open(file) do |f|
|
305
|
+
raw_config = f.read
|
306
|
+
end
|
307
|
+
self.load(raw_config)
|
308
|
+
rescue Errno::ENOENT
|
309
|
+
raise DBGeni::ConfigFileNotExist, "Included config #{file} does not exist"
|
310
|
+
rescue DBGeni::ConfigSyntaxError
|
311
|
+
raise DBGeni::ConfigSyntaxError, "Included config #{file} contains errors: #{e.to_s}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def is_absolute_path?(path)
|
318
|
+
if path =~ /^\/|[a-zA-Z]:\\/
|
319
|
+
true
|
320
|
+
else
|
321
|
+
false
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module DBGeni
|
2
|
+
|
3
|
+
module Connector
|
4
|
+
|
5
|
+
def self.initialize(config)
|
6
|
+
required_class = setup(config.db_type)
|
7
|
+
conn = nil
|
8
|
+
begin
|
9
|
+
required_method = required_class.method("connect")
|
10
|
+
rescue NameError
|
11
|
+
raise DBGeni::InvalidConnectorForDBType, config.db_type
|
12
|
+
end
|
13
|
+
if config.db_type == 'sqlite' or (config.db_type == 'oracle' and RUBY_PLATFORM != 'java')
|
14
|
+
# don't need a host or port here, so make this a seperate call block
|
15
|
+
conn = required_method.call(config.env.username,
|
16
|
+
# SQLITE doesn't need a password, so prevent asking for it
|
17
|
+
# or it may be promoted for
|
18
|
+
config.db_type == 'sqlite' ? '' : config.env.password,
|
19
|
+
config.env.database)
|
20
|
+
else
|
21
|
+
conn = required_method.call(config.env.username,
|
22
|
+
config.env.password,
|
23
|
+
config.env.database,
|
24
|
+
config.env.hostname,
|
25
|
+
config.env.port)
|
26
|
+
end
|
27
|
+
if config.db_type == 'oracle'
|
28
|
+
if config.env.install_schema
|
29
|
+
if config.env.username != config.env.install_schema
|
30
|
+
conn.execute("alter session set current_schema=#{config.env.install_schema}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
conn
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.setup(db_type)
|
40
|
+
begin
|
41
|
+
require "dbgeni/connectors/#{db_type}"
|
42
|
+
rescue Exception => e
|
43
|
+
puts "Error requiring connector: #{e.to_s}"
|
44
|
+
raise DBGeni::NoConnectorForDBType, db_type
|
45
|
+
end
|
46
|
+
|
47
|
+
required_class = nil
|
48
|
+
if Connector.const_defined?(db_type.capitalize)
|
49
|
+
required_class = Connector.const_get(db_type.capitalize)
|
50
|
+
else
|
51
|
+
raise DBGeni::NoConnectorForDBType, db_type
|
52
|
+
end
|
53
|
+
required_class
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module DBGeni
|
2
|
+
|
3
|
+
module Connector
|
4
|
+
|
5
|
+
class Mysql
|
6
|
+
if RUBY_PLATFORM == 'java'
|
7
|
+
require 'java'
|
8
|
+
java_import 'com.mysql.jdbc.Driver'
|
9
|
+
java_import 'java.sql.DriverManager'
|
10
|
+
else
|
11
|
+
require 'mysql'
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :connection
|
15
|
+
attr_reader :database
|
16
|
+
|
17
|
+
def self.connect(user, password, database, host, port)
|
18
|
+
self.new(user, password, database, host, port=nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
def disconnect
|
22
|
+
@connection.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute(sql, *binds)
|
26
|
+
if RUBY_PLATFORM == 'java'
|
27
|
+
execute_jdbc(sql, *binds)
|
28
|
+
else
|
29
|
+
execute_native(sql, *binds)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def ping
|
35
|
+
results = self.execute('select 1 + 1 from dual')
|
36
|
+
if results.length == 1
|
37
|
+
true
|
38
|
+
else
|
39
|
+
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def commit
|
45
|
+
@connection.commit
|
46
|
+
end
|
47
|
+
|
48
|
+
def rollback
|
49
|
+
@connection.rollback
|
50
|
+
end
|
51
|
+
|
52
|
+
def date_placeholder(bind_var)
|
53
|
+
"?" # ":#{bind_var}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def date_as_string(dtm)
|
57
|
+
dtm.strftime '%Y-%m-%d %H:%M:%S'
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def initialize(user, password, database, host, port=3306)
|
63
|
+
@database = database
|
64
|
+
begin
|
65
|
+
if RUBY_PLATFORM == 'java'
|
66
|
+
@connection = DriverManager.get_connection("jdbc:mysql://#{host}:#{port||3306}/#{database}", user, password)
|
67
|
+
else
|
68
|
+
@connection = ::Mysql.connect(host, user, password, database, port)
|
69
|
+
end
|
70
|
+
rescue Exception => e
|
71
|
+
raise DBGeni::DBConnectionError, e.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute_native(sql, *binds)
|
76
|
+
# strange exception handling here. If you issue a select, the fetch works fine.
|
77
|
+
# However, something like create table blows up when fetch is called with noMethodError
|
78
|
+
# so it is being caught and thrown away ... hackish, but the mysql drive seems to be
|
79
|
+
# at fault, is badly documented and seems to be a bit rubbish. Ideall uses DBD::Mysql + DBI.
|
80
|
+
|
81
|
+
results = Array.new
|
82
|
+
if sql =~ /^drop\s+(procedure|function|trigger|table)/i
|
83
|
+
# cannot prepare these statements, need to just execute
|
84
|
+
@connection.query(sql)
|
85
|
+
return results
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
query = @connection.prepare(sql)
|
90
|
+
query.execute(*binds)
|
91
|
+
|
92
|
+
results = Array.new
|
93
|
+
if query.num_rows > 0
|
94
|
+
while r = query.fetch()
|
95
|
+
results.push r
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# everthing is auto commit right now ...
|
99
|
+
@connection.commit
|
100
|
+
rescue NoMethodError
|
101
|
+
ensure
|
102
|
+
begin
|
103
|
+
query.close()
|
104
|
+
rescue
|
105
|
+
end
|
106
|
+
end
|
107
|
+
results
|
108
|
+
end
|
109
|
+
|
110
|
+
def execute_jdbc(sql, *binds)
|
111
|
+
begin
|
112
|
+
query = @connection.prepare_statement(sql)
|
113
|
+
binds.each_with_index do |b, i|
|
114
|
+
if b.is_a?(String)
|
115
|
+
query.setString(i+1, b)
|
116
|
+
elsif b.is_a?(Fixnum)
|
117
|
+
query.setInt(i+1, b)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
results = Array.new
|
121
|
+
unless sql =~ /^\s*(select|show)/i
|
122
|
+
query.execute()
|
123
|
+
else
|
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
|
+
a.push rset.get_object(i)
|
130
|
+
end
|
131
|
+
results.push a
|
132
|
+
end
|
133
|
+
end
|
134
|
+
results
|
135
|
+
ensure
|
136
|
+
begin
|
137
|
+
query.close
|
138
|
+
rescue Exception => e
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|