skiima 0.1.000
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +8 -0
- data/Gemfile +30 -0
- data/Guardfile +14 -0
- data/README.md +87 -0
- data/Rakefile +56 -0
- data/lib/skiima/db_adapters/base_mysql_adapter.rb +308 -0
- data/lib/skiima/db_adapters/mysql2_adapter.rb +114 -0
- data/lib/skiima/db_adapters/mysql_adapter.rb +287 -0
- data/lib/skiima/db_adapters/postgresql_adapter.rb +509 -0
- data/lib/skiima/db_adapters.rb +187 -0
- data/lib/skiima/dependency.rb +84 -0
- data/lib/skiima/locales/en.yml +20 -0
- data/lib/skiima/locales/fr.yml +2 -0
- data/lib/skiima/version.rb +4 -0
- data/lib/skiima.rb +270 -0
- data/lib/skiima_helpers.rb +49 -0
- data/skiima.gemspec +53 -0
- data/spec/config/database.yml +56 -0
- data/spec/db/skiima/depends.yml +61 -0
- data/spec/db/skiima/empty_depends.yml +0 -0
- data/spec/db/skiima/init_test_db/database.skiima_test.mysql.current.sql +2 -0
- data/spec/db/skiima/init_test_db/database.skiima_test.postgresql.current.sql +2 -0
- data/spec/db/skiima/test_column_names/table.test_column_names.mysql.current.sql +8 -0
- data/spec/db/skiima/test_column_names/table.test_column_names.postgresql.current.sql +18 -0
- data/spec/db/skiima/test_index/index.test_index.mysql.current.sql +2 -0
- data/spec/db/skiima/test_index/index.test_index.postgresql.current.sql +2 -0
- data/spec/db/skiima/test_proc/proc.test_proc.mysql.current.sql +8 -0
- data/spec/db/skiima/test_proc/proc.test_proc_drop.mysql.current.drop.sql +1 -0
- data/spec/db/skiima/test_proc/proc.test_proc_drop.mysql.current.sql +8 -0
- data/spec/db/skiima/test_rule/rule.test_rule.postgresql.current.sql +6 -0
- data/spec/db/skiima/test_schema/schema.test_schema.postgresql.current.sql +2 -0
- data/spec/db/skiima/test_table/table.test_table.mysql.current.sql +7 -0
- data/spec/db/skiima/test_table/table.test_table.postgresql.current.sql +4 -0
- data/spec/db/skiima/test_view/view.test_view.mysql.current.sql +5 -0
- data/spec/db/skiima/test_view/view.test_view.postgresql.current.sql +6 -0
- data/spec/helpers/mysql_spec_helper.rb +0 -0
- data/spec/helpers/postgresql_spec_helper.rb +11 -0
- data/spec/mysql2_spec.rb +57 -0
- data/spec/mysql_spec.rb +100 -0
- data/spec/postgresql_spec.rb +138 -0
- data/spec/skiima/db_adapters/mysql_adapter_spec.rb +38 -0
- data/spec/skiima/db_adapters/postgresql_adapter_spec.rb +20 -0
- data/spec/skiima/db_adapters_spec.rb +31 -0
- data/spec/skiima/dependency_spec.rb +94 -0
- data/spec/skiima_spec.rb +97 -0
- data/spec/spec_helper.rb +35 -0
- metadata +195 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
** PRERELEASE 0.0.001
|
2
|
+
|
3
|
+
* Determining the layout for modules, classes and tasks
|
4
|
+
|
5
|
+
** PRERELEASE 0.0.010
|
6
|
+
|
7
|
+
* Major changes to interface. Added version subgroup. Removed skiima.yml. Removed ActiveRecord.
|
8
|
+
* Added support for Postgres: databases, schemas, tables, indexes, views, rules.
|
data/Gemfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
source "http://rubygems.org"
|
3
|
+
|
4
|
+
# Specify your gem's dependencies in skiima.gemspec
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'guard'
|
9
|
+
gem 'guard-minitest'
|
10
|
+
gem 'pry'
|
11
|
+
gem 'rake'
|
12
|
+
|
13
|
+
gem 'pg'
|
14
|
+
gem 'mysql'
|
15
|
+
gem 'mysql2'
|
16
|
+
end
|
17
|
+
|
18
|
+
if Config::CONFIG['target_os'] =~ /darwin/i
|
19
|
+
gem 'rb-fsevent', '>= 0.3.2'
|
20
|
+
gem 'growl', '~> 1.0.3'
|
21
|
+
end
|
22
|
+
if Config::CONFIG['target_os'] =~ /linux/i
|
23
|
+
gem 'rb-inotify', '>= 0.5.1'
|
24
|
+
gem 'libnotify', '~> 0.1.3'
|
25
|
+
end
|
26
|
+
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
27
|
+
gem 'win32console', :require => false
|
28
|
+
gem 'rb-fchange', '~> 0.0.2', :require => false
|
29
|
+
gem 'rb-notifu', '~> 0.0.4', :require => false
|
30
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'minitest', :notify => true do
|
5
|
+
# with Minitest::Unit
|
6
|
+
# watch(%r|^test/test_(.*)\.rb|)
|
7
|
+
# watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
8
|
+
# watch(%r|^test/test_helper\.rb|) { "test" }
|
9
|
+
|
10
|
+
# with Minitest::Spec
|
11
|
+
watch(%r|^spec/(.*)_spec\.rb|)
|
12
|
+
watch(%r|^lib/(.*)\.rb|) { |m| "spec/#{m[1]}_spec.rb" }
|
13
|
+
watch(%r|^spec/spec_helper\.rb|) { "spec" }
|
14
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
Skiima: an ORM-Agnostic, Rails-Independent alternative to migrations.
|
2
|
+
------------------------
|
3
|
+
|
4
|
+
#### Goals:
|
5
|
+
Skiima is a work in progress with the following goals:
|
6
|
+
|
7
|
+
* Provide a better way to integrate views and functions into Ruby applications.
|
8
|
+
* Embeddable in gems to create DB independent extensions.
|
9
|
+
* Avoid any unnecessary dependencies or complexity.
|
10
|
+
|
11
|
+
I was working on another project and added some cool features that relied on postgres views, rules, etc. And basically, I needed a place to put these scripts and a way to execute them in a specified order.
|
12
|
+
|
13
|
+
There are alot of cool tricks to use in ActiveRecord with sql objects other than tables. Not to mention there are performance benefits with the right schema. Not everything needs to happen at the application layer.
|
14
|
+
|
15
|
+
If you have any questions about how things work, look at the tests. If you have any suggestions, don't hesitate to contact me.
|
16
|
+
|
17
|
+
#### Supported Databases:
|
18
|
+
##### Postgres
|
19
|
+
|
20
|
+
- Schemas
|
21
|
+
- Tables
|
22
|
+
- Indexes
|
23
|
+
- Views
|
24
|
+
- Rules
|
25
|
+
- Triggers(soon)
|
26
|
+
- Functions(soon)
|
27
|
+
|
28
|
+
##### Mysql/Mysql2
|
29
|
+
|
30
|
+
- Tables
|
31
|
+
- Views
|
32
|
+
- Indexes
|
33
|
+
- Triggers(soon)
|
34
|
+
- Functions(soon)
|
35
|
+
- Procs(soon)
|
36
|
+
|
37
|
+
#### Interface:
|
38
|
+
##### Config Files
|
39
|
+
Skiima reads two yaml files: database.yml and depends.yml.
|
40
|
+
|
41
|
+
- By default, `database.yml` goes in your `APP_ROOT/config directory`.
|
42
|
+
- And similarly, `depends.yml` goes in `APP_ROOT/db/skiima`.
|
43
|
+
|
44
|
+
##### Groups
|
45
|
+
Skiima allows you to create groups of sql scripts to be executed during migrations. Each group of sql scripts requires its own folder inside `db/skiima`.
|
46
|
+
|
47
|
+
##### Adapter and Version
|
48
|
+
Since different databases have different capabilities and object types, you can trigger different scripts to run per adapter. Furthermore, you can execute different scripts for different versions of a database. You can also use this version tag if you want to package different sets of functionality.
|
49
|
+
|
50
|
+
##### Depends.yml Format
|
51
|
+
In the `depends.yml` configuration, add lines for each script in the format.
|
52
|
+
|
53
|
+
* `type.name.attr1.attr2`
|
54
|
+
|
55
|
+
`attr1` and `attr2` allow you to define the target SQL object and other attributes that need to be passed on a per type basis. This is only used when Skiima drops your objects without a matching 'drop' script.
|
56
|
+
|
57
|
+
##### Filename Format
|
58
|
+
Inside each group folder, create sql files with the following format. These need to match the `depends.yml` configuration.
|
59
|
+
|
60
|
+
* `type.name.adapter.version.sql`
|
61
|
+
* `type.name.adapter.version.drop.sql` to override the default drop behavior.
|
62
|
+
|
63
|
+
##### Interpolation in Scripts
|
64
|
+
You can interpolate variables in your SQL Scripts and then pass in values at runtime with the `:vars` hash. The default character to use for interpolation is `&`, but this can be changed in the Module initialization. There are default vars that are substituted, such as `&database`.
|
65
|
+
|
66
|
+
##### Module Initialization
|
67
|
+
If you're using Rails, you can add a Skiima.setup block in an intializer file to set defaults. There are other ways to integrate Skiima into your project as well.
|
68
|
+
|
69
|
+
Skiima.setup do |config|
|
70
|
+
config.root_path = Rails.root # changing
|
71
|
+
config.config_path = 'config'
|
72
|
+
config.scripts_path = 'db/skiima'
|
73
|
+
config.locale = :en
|
74
|
+
end
|
75
|
+
|
76
|
+
##### Finally, in your Migrations
|
77
|
+
Skiima reads the specified groups from depends.yml and compiles a list of scripts to run. If you're using Rails, substitute `:development` with `Rails.env`
|
78
|
+
|
79
|
+
def up
|
80
|
+
Skiima.up(:development, :group_one, :group_n, :vars => {:var_one => 'db_name'})
|
81
|
+
end
|
82
|
+
|
83
|
+
def down
|
84
|
+
Skiima.down(:development, :group_one, :group_n, :vars => {:var_one => 'db_name'})
|
85
|
+
end
|
86
|
+
|
87
|
+
#### yes, i know i am shamelessly biting activerecord code.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
require 'skiima'
|
6
|
+
|
7
|
+
SPEC_ROOT = File.join(File.dirname(__FILE__), 'spec')
|
8
|
+
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << "spec"
|
12
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
17
|
+
|
18
|
+
namespace :test do
|
19
|
+
namespace :prepare do
|
20
|
+
|
21
|
+
task :postgresql do
|
22
|
+
Skiima.setup do |config|
|
23
|
+
config.root_path = SPEC_ROOT
|
24
|
+
config.config_path = 'config'
|
25
|
+
config.scripts_path = 'db/skiima'
|
26
|
+
config.locale = :en
|
27
|
+
end
|
28
|
+
|
29
|
+
db_config = Skiima.read_db_yaml(Skiima.full_database_path)[:development]
|
30
|
+
Skiima.new(:development)
|
31
|
+
Skiima.read_sql_file('init_test_db', 'database.skiima_test.postgresql.current.sql')
|
32
|
+
end
|
33
|
+
|
34
|
+
task :mysql do
|
35
|
+
Skiima.setup do |config|
|
36
|
+
config.root_path = SPEC_ROOT
|
37
|
+
config.config_path = 'config'
|
38
|
+
config.scripts_path = 'db/skiima'
|
39
|
+
config.locale = :en
|
40
|
+
end
|
41
|
+
|
42
|
+
db_config = Skiima.read_db_yaml(Skiima.full_database_path)
|
43
|
+
Skiima.read_sql_file('init_test_db', 'database.skiima_test.mysql.current.sql')
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#desc 'Generate documentation for Skiima.'
|
50
|
+
#Rake::RDocTask.new(:rdoc) do |rdoc|
|
51
|
+
# rdoc.rdoc_dir = 'rdoc'
|
52
|
+
# rdoc.title = 'Skiima'
|
53
|
+
# rdoc.options << '--line-numbers' << '--inline-source'
|
54
|
+
# rdoc.rdoc_files.include('README.rdoc')
|
55
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
#end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Skiima
|
3
|
+
module DbAdapters
|
4
|
+
class BaseMysqlAdapter < Base
|
5
|
+
attr_accessor :version
|
6
|
+
|
7
|
+
LOST_CONNECTION_ERROR_MESSAGES = [
|
8
|
+
"Server shutdown in progress",
|
9
|
+
"Broken pipe",
|
10
|
+
"Lost connection to MySQL server during query",
|
11
|
+
"MySQL server has gone away" ]
|
12
|
+
|
13
|
+
# FIXME: Make the first parameter more similar for the two adapters
|
14
|
+
def initialize(connection, logger, connection_options, config)
|
15
|
+
super(connection, logger)
|
16
|
+
@connection_options, @config = connection_options, config
|
17
|
+
@quoted_column_names, @quoted_table_names = {}, {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def version
|
21
|
+
@version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
22
|
+
end
|
23
|
+
|
24
|
+
def adapter_name #:nodoc:
|
25
|
+
self.class::ADAPTER_NAME
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true, since this connection adapter supports migrations.
|
29
|
+
def supports_migrations?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def supports_primary_key?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns true, since this connection adapter supports savepoints.
|
38
|
+
def supports_savepoints?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Must return the Mysql error number from the exception, if the exception has an
|
43
|
+
# error number.
|
44
|
+
def error_number(exception) # :nodoc:
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def disable_referential_integrity(&block) #:nodoc:
|
49
|
+
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
50
|
+
|
51
|
+
begin
|
52
|
+
update("SET FOREIGN_KEY_CHECKS = 0")
|
53
|
+
yield
|
54
|
+
ensure
|
55
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# MysqlAdapter has to free a result after using it, so we use this method to write
|
60
|
+
# stuff in a abstract way without concerning ourselves about whether it needs to be
|
61
|
+
# explicitly freed or not.
|
62
|
+
def execute_and_free(sql, name = nil) #:nodoc:
|
63
|
+
yield execute(sql, name)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Executes the SQL statement in the context of this connection.
|
67
|
+
def execute(sql, name = nil)
|
68
|
+
if name == :skip_logging
|
69
|
+
@connection.query(sql)
|
70
|
+
else
|
71
|
+
log(sql, name) { @connection.query(sql) }
|
72
|
+
end
|
73
|
+
rescue StatementInvalid => exception
|
74
|
+
if exception.message.split(":").first =~ /Packets out of order/
|
75
|
+
raise StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
|
76
|
+
else
|
77
|
+
raise
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def begin_db_transaction
|
82
|
+
execute "BEGIN"
|
83
|
+
rescue Exception
|
84
|
+
# Transactions aren't supported
|
85
|
+
end
|
86
|
+
|
87
|
+
def commit_db_transaction #:nodoc:
|
88
|
+
execute "COMMIT"
|
89
|
+
rescue Exception
|
90
|
+
# Transactions aren't supported
|
91
|
+
end
|
92
|
+
|
93
|
+
def rollback_db_transaction #:nodoc:
|
94
|
+
execute "ROLLBACK"
|
95
|
+
rescue Exception
|
96
|
+
# Transactions aren't supported
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_savepoint
|
100
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
101
|
+
end
|
102
|
+
|
103
|
+
def rollback_to_savepoint
|
104
|
+
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
105
|
+
end
|
106
|
+
|
107
|
+
def release_savepoint
|
108
|
+
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
109
|
+
end
|
110
|
+
|
111
|
+
def supported_objects
|
112
|
+
[:database, :table, :view, :index]
|
113
|
+
end
|
114
|
+
|
115
|
+
def tables(name = nil, database = nil, like = nil)
|
116
|
+
sql = "SHOW FULL TABLES "
|
117
|
+
sql << "IN #{database} " if database
|
118
|
+
sql << "WHERE table_type = 'BASE TABLE' "
|
119
|
+
sql << "LIKE '#{like}' " if like
|
120
|
+
|
121
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
122
|
+
result.collect { |field| field.first }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def views(name = nil, database = nil, like = nil)
|
127
|
+
sql = "SHOW FULL TABLES "
|
128
|
+
sql << "IN #{database} " if database
|
129
|
+
sql << "WHERE table_type = 'VIEW' "
|
130
|
+
sql << "LIKE '#{like}' " if like
|
131
|
+
|
132
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
133
|
+
result.collect { |field| field.first }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def indexes(name = nil, database = nil, table = nil)
|
138
|
+
sql = "SHOW INDEX "
|
139
|
+
sql << "IN #{table} "
|
140
|
+
sql << "IN #{database} " if database
|
141
|
+
sql << "WHERE key_name = '#{name}'" if name
|
142
|
+
|
143
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
144
|
+
result.collect { |field| field[2] }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
"select routine_schema, routine_name, routine_type from routines;"
|
149
|
+
|
150
|
+
def procs(name = nil, database = nil, like = nil)
|
151
|
+
sql = "SELECT r.routine_name "
|
152
|
+
sql << "FROM information_schema.routines r "
|
153
|
+
sql << "WHERE r.routine_type = 'PROCEDURE' "
|
154
|
+
sql << "AND r.routine_name LIKE '#{like}' " if like
|
155
|
+
sql << "AND r.routine_schema = #{database} " if database
|
156
|
+
|
157
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
158
|
+
result.collect { |field| field.first }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def database_exists?(name)
|
163
|
+
#stub
|
164
|
+
end
|
165
|
+
|
166
|
+
def table_exists?(name)
|
167
|
+
return false unless name
|
168
|
+
return true if tables(nil, nil, name).any?
|
169
|
+
|
170
|
+
name = name.to_s
|
171
|
+
schema, table = name.split('.', 2)
|
172
|
+
|
173
|
+
unless table # A table was provided without a schema
|
174
|
+
table = schema
|
175
|
+
schema = nil
|
176
|
+
end
|
177
|
+
|
178
|
+
tables(nil, schema, table).any?
|
179
|
+
end
|
180
|
+
|
181
|
+
def view_exists?(name)
|
182
|
+
return false unless name
|
183
|
+
return true if views(nil, nil, name).any?
|
184
|
+
|
185
|
+
name = name.to_s
|
186
|
+
schema, view = name.split('.', 2)
|
187
|
+
|
188
|
+
unless view # A table was provided without a schema
|
189
|
+
view = schema
|
190
|
+
schema = nil
|
191
|
+
end
|
192
|
+
|
193
|
+
views(nil, schema, view).any?
|
194
|
+
end
|
195
|
+
|
196
|
+
def index_exists?(name, opts = {})
|
197
|
+
target = opts[:attr] ? opts[:attr][0] : nil
|
198
|
+
raise "requires target object" unless target
|
199
|
+
|
200
|
+
return false unless table_exists?(target) #mysql blows up when table doesn't exist
|
201
|
+
return false unless name
|
202
|
+
return true if indexes(name, nil, target).any?
|
203
|
+
|
204
|
+
name = name.to_s
|
205
|
+
schema, target = name.split('.', 2)
|
206
|
+
|
207
|
+
unless target # A table was provided without a schema
|
208
|
+
target = schema
|
209
|
+
schema = nil
|
210
|
+
end
|
211
|
+
|
212
|
+
indexes(name, schema, target).any?
|
213
|
+
end
|
214
|
+
|
215
|
+
def proc_exists?(name, opts = {})
|
216
|
+
return false unless name
|
217
|
+
return true if procs(nil, nil, name).any?
|
218
|
+
|
219
|
+
name = name.to_s
|
220
|
+
schema, proc = name.split('.', 2)
|
221
|
+
|
222
|
+
unless proc # A table was provided without a schema
|
223
|
+
proc = schema
|
224
|
+
schema = nil
|
225
|
+
end
|
226
|
+
|
227
|
+
procs(name, schema, proc).any?
|
228
|
+
end
|
229
|
+
|
230
|
+
def drop_database(name, opts = {})
|
231
|
+
"DROP DATABASE IF EXISTS #{name}"
|
232
|
+
end
|
233
|
+
|
234
|
+
def drop_table(name, opts = {})
|
235
|
+
"DROP TABLE IF EXISTS #{name}"
|
236
|
+
end
|
237
|
+
|
238
|
+
def drop_view(name, opts = {})
|
239
|
+
"DROP VIEW IF EXISTS #{name}"
|
240
|
+
end
|
241
|
+
|
242
|
+
def drop_index(name, opts = {})
|
243
|
+
target = opts[:attr].first if opts[:attr]
|
244
|
+
raise "requires target object" unless target
|
245
|
+
|
246
|
+
"DROP INDEX #{name} ON #{target}"
|
247
|
+
end
|
248
|
+
|
249
|
+
def column_definitions(table_name)
|
250
|
+
# "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
251
|
+
end
|
252
|
+
|
253
|
+
def column_names(table_name)
|
254
|
+
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
255
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
256
|
+
result.collect { |field| field.first }
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def quote_column_name(name) #:nodoc:
|
261
|
+
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
262
|
+
end
|
263
|
+
|
264
|
+
def quote_table_name(name) #:nodoc:
|
265
|
+
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
266
|
+
end
|
267
|
+
|
268
|
+
def current_database
|
269
|
+
select_value 'SELECT DATABASE() as db'
|
270
|
+
end
|
271
|
+
|
272
|
+
# Returns the database character set.
|
273
|
+
def charset
|
274
|
+
show_variable 'character_set_database'
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns the database collation strategy.
|
278
|
+
def collation
|
279
|
+
show_variable 'collation_database'
|
280
|
+
end
|
281
|
+
|
282
|
+
def show_variable(name)
|
283
|
+
# variables = select_all("SHOW VARIABLES LIKE '#{name}'")
|
284
|
+
# variables.first['Value'] unless variables.empty?
|
285
|
+
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def translate_exception(exception, message)
|
290
|
+
exception
|
291
|
+
# case error_number(exception)
|
292
|
+
# when 1062
|
293
|
+
# RecordNotUnique.new(message, exception)
|
294
|
+
# when 1452
|
295
|
+
# InvalidForeignKey.new(message, exception)
|
296
|
+
# else
|
297
|
+
# super
|
298
|
+
# end
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
def supports_views?
|
304
|
+
version[0] >= 5
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'skiima/db_adapters/base_mysql_adapter'
|
3
|
+
|
4
|
+
gem 'mysql2', '~> 0.3.10'
|
5
|
+
require 'mysql2'
|
6
|
+
|
7
|
+
module Skiima
|
8
|
+
def self.mysql2_connection(logger, config)
|
9
|
+
config[:username] = 'root' if config[:username].nil?
|
10
|
+
|
11
|
+
if Mysql2::Client.const_defined? :FOUND_ROWS
|
12
|
+
config[:flags] = Mysql2::Client::FOUND_ROWS
|
13
|
+
end
|
14
|
+
|
15
|
+
client = Mysql2::Client.new(Skiima.symbolize_keys(config))
|
16
|
+
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
|
17
|
+
Skiima::DbAdapters::Mysql2Adapter.new(client, logger, options, config)
|
18
|
+
end
|
19
|
+
|
20
|
+
module DbAdapters
|
21
|
+
class Mysql2Adapter < BaseMysqlAdapter
|
22
|
+
ADAPTER_NAME = 'Mysql2'
|
23
|
+
|
24
|
+
def initialize(connection, logger, connection_options, config)
|
25
|
+
super
|
26
|
+
configure_connection
|
27
|
+
end
|
28
|
+
|
29
|
+
def supports_explain?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def error_number(exception)
|
34
|
+
exception.error_number if exception.respond_to?(:error_number)
|
35
|
+
end
|
36
|
+
|
37
|
+
def active?
|
38
|
+
return false unless @connection
|
39
|
+
@connection.ping
|
40
|
+
end
|
41
|
+
|
42
|
+
def reconnect!
|
43
|
+
disconnect!
|
44
|
+
connect
|
45
|
+
end
|
46
|
+
|
47
|
+
# Disconnects from the database if already connected.
|
48
|
+
# Otherwise, this method does nothing.
|
49
|
+
def disconnect!
|
50
|
+
unless @connection.nil?
|
51
|
+
@connection.close
|
52
|
+
@connection = nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def reset!
|
57
|
+
disconnect!
|
58
|
+
connect
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute(sql, name = nil)
|
62
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
63
|
+
# made since we established the connection
|
64
|
+
# @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
|
65
|
+
|
66
|
+
# relying on formatting inside the file is precisely what i wanted to avoid...
|
67
|
+
results = sql.split(/^--={4,}/).map do |spider_monkey|
|
68
|
+
super(spider_monkey)
|
69
|
+
end
|
70
|
+
|
71
|
+
results.first
|
72
|
+
end
|
73
|
+
|
74
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
75
|
+
result = execute(sql, name)
|
76
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
77
|
+
end
|
78
|
+
|
79
|
+
alias exec_without_stmt exec_query
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def connect
|
84
|
+
@connection = Mysql2::Client.new(@config)
|
85
|
+
configure_connection
|
86
|
+
end
|
87
|
+
|
88
|
+
def configure_connection
|
89
|
+
@connection.query_options.merge!(:as => :array)
|
90
|
+
|
91
|
+
variable_assignments = get_var_assignments
|
92
|
+
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
|
93
|
+
|
94
|
+
version
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_var_assignments
|
98
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
99
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
100
|
+
variable_assignments = ['SQL_AUTO_IS_NULL=0']
|
101
|
+
encoding = @config[:encoding]
|
102
|
+
|
103
|
+
# make sure we set the encoding
|
104
|
+
variable_assignments << "NAMES '#{encoding}'" if encoding
|
105
|
+
|
106
|
+
# increase timeout so mysql server doesn't disconnect us
|
107
|
+
wait_timeout = @config[:wait_timeout]
|
108
|
+
wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
|
109
|
+
variable_assignments << "@@wait_timeout = #{wait_timeout}"
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|