dbgeni 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|