activerecord-dbslayer-adapter 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/License.txt +20 -0
- data/Manifest.txt +30 -0
- data/README.txt +80 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +15 -0
- data/lib/active_record/connection_adapters/dbslayer_adapter.rb +356 -0
- data/lib/active_record/connection_adapters/dbslayer_connection.rb +149 -0
- data/lib/activerecord-dbslayer-adapter.rb +33 -0
- data/log/debug.log +0 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/test/helper.rb +76 -0
- data/test/localtest.rb +28 -0
- data/test/test_dbslayer_adapter.rb +78 -0
- data/test/test_dbslayer_column.rb +13 -0
- data/test/test_dbslayer_connection.rb +45 -0
- data/test/test_dbslayer_results.rb +54 -0
- data/website/index.html +11 -0
- data/website/index.txt +39 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- metadata +91 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 FIXME full name
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
config/hoe.rb
|
7
|
+
config/requirements.rb
|
8
|
+
lib/active_record/connection_adapters/dbslayer_adapter.rb
|
9
|
+
lib/active_record/connection_adapters/dbslayer_connection.rb
|
10
|
+
lib/activerecord-dbslayer-adapter.rb
|
11
|
+
log/debug.log
|
12
|
+
script/console
|
13
|
+
script/destroy
|
14
|
+
script/generate
|
15
|
+
script/txt2html
|
16
|
+
setup.rb
|
17
|
+
tasks/deployment.rake
|
18
|
+
tasks/environment.rake
|
19
|
+
tasks/website.rake
|
20
|
+
test/helper.rb
|
21
|
+
test/localtest.rb
|
22
|
+
test/test_dbslayer_adapter.rb
|
23
|
+
test/test_dbslayer_column.rb
|
24
|
+
test/test_dbslayer_connection.rb
|
25
|
+
test/test_dbslayer_results.rb
|
26
|
+
website/index.html
|
27
|
+
website/index.txt
|
28
|
+
website/javascripts/rounded_corners_lite.inc.js
|
29
|
+
website/stylesheets/screen.css
|
30
|
+
website/template.html.erb
|
data/README.txt
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
= activerecord_dbslayer_adapter
|
2
|
+
|
3
|
+
Jacob Harris
|
4
|
+
jharris@nytimes.com
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
An ActiveRecord adapter for using the DBSlayer proxy/pooling layer. This allows you to proxy and pool connections to the MySQL backend without worrying about scaling up the front instances more than MySQL is configured to handle (the dreaded Too Many Connections error). Of course, you can also reconfigure master/slave instances on the fly in DBSlayer without having to reconfigure or restart your Rails applications.
|
9
|
+
|
10
|
+
This adapter is really just a judicious subclassing of functionality in the MySQL adapter, so the documented methods might seem very sparse (I only have to override what's changed). Mainly, I swapped it out to execute queries as JSON-over-HTTP calls to DBSlayer rather than using the MySQL connection library.
|
11
|
+
|
12
|
+
== MAJOR CAVEATS:
|
13
|
+
|
14
|
+
DBSlayer is a stateless pooling layer. This allows it to easily scale (why do you think HTTP is stateless?), but there are certain database techniques that will have to be abandoned or modified if you use it. The main problem is that each MySQL statement may execute on a different connection (and sometimes on different servers if you've set up transparent slave pooling), so you can not assume the context of one statement is available to the next.
|
15
|
+
|
16
|
+
This becomes a problem with transactions. Although DBSlayer can support transactions if you use semicolons to include them all as one statement like
|
17
|
+
BEGIN TRANSACTION; more SQL; COMMIT TRANSACTION
|
18
|
+
There is no readily apparent way to do that via ActiveRecord's block construction (all the adapter has are begin_transaction, commit_transaction methods). So for the moment, transactions do not throw errors but they don't actually use SQL transactions either. Sorry about that. In addition, disabling referential integrity for a connection is not allowed.
|
19
|
+
|
20
|
+
The biggest problem is that Rails sets a connection variable for all MySQL connections in order to fix an error selecting null IDs (http://dev.rubyonrails.org/ticket/6778). Since DBSlayer is stateless, this fix doesn't work, but there unfortunately also doesn't seem to be a database or server setting you can alter instead. As a result, the following types of queries WILL return unexpected results when using the DBSlayer adapter:
|
21
|
+
|
22
|
+
Restaurant.find(:all, :conditions => 'id IS NULL')
|
23
|
+
|
24
|
+
The problem only occurs when searching for null autoincrement primary key columns (not other columns). With the regular MySQL adapter, this would return records with an id value (normally errors). With the DBSlayer adapter, it returns the last inserted item into the table (this is the default MySQL behavior). Luckily, this is not a common idiom in Rails, but you should be aware if you attempt to use it for finding errors in your tables.
|
25
|
+
|
26
|
+
== FEATURES/PROBLEMS:
|
27
|
+
|
28
|
+
* More tests
|
29
|
+
* Better documentation
|
30
|
+
|
31
|
+
== SYNOPSIS:
|
32
|
+
|
33
|
+
In your databases.yml
|
34
|
+
|
35
|
+
production:
|
36
|
+
adapter: dbslayer
|
37
|
+
host: localhost
|
38
|
+
port: 9090
|
39
|
+
|
40
|
+
All of the other normal database setting like the MySQL server, username, password, etc. are specified in the dbslayer daemon's configuration.
|
41
|
+
|
42
|
+
== REQUIREMENTS:
|
43
|
+
|
44
|
+
* the JSON gem
|
45
|
+
* A running DBSlayer instance
|
46
|
+
|
47
|
+
== INSTALL:
|
48
|
+
|
49
|
+
* gem install activerecord-dbslayer-adapter
|
50
|
+
* in your rails project, add the following line at the end of config/environment.rb
|
51
|
+
Rails::Initializer.run do |config|
|
52
|
+
...
|
53
|
+
|
54
|
+
require 'activerecord-dbslayer-adapter'
|
55
|
+
end
|
56
|
+
|
57
|
+
== LICENSE:
|
58
|
+
|
59
|
+
(The MIT License)
|
60
|
+
|
61
|
+
Copyright (c) 2008
|
62
|
+
|
63
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
64
|
+
a copy of this software and associated documentation files (the
|
65
|
+
'Software'), to deal in the Software without restriction, including
|
66
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
67
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
68
|
+
permit persons to whom the Software is furnished to do so, subject to
|
69
|
+
the following conditions:
|
70
|
+
|
71
|
+
The above copyright notice and this permission notice shall be
|
72
|
+
included in all copies or substantial portions of the Software.
|
73
|
+
|
74
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
75
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
76
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
77
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
78
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
79
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
80
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/config/hoe.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'activerecord-dbslayer-adapter'
|
2
|
+
|
3
|
+
AUTHOR = 'Jacob Harris' # can also be an array of Authors
|
4
|
+
EMAIL = "jharris@nytimes.com"
|
5
|
+
DESCRIPTION = "An ActiveRecord adapter to DBSlayer"
|
6
|
+
GEM_NAME = 'activerecord-dbslayer-adapter' # what ppl will type to install your gem
|
7
|
+
RUBYFORGE_PROJECT = 'ar-dbslayer' # The unix name for your project
|
8
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
9
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
|
+
|
11
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
12
|
+
@config = nil
|
13
|
+
RUBYFORGE_USERNAME = "unknown"
|
14
|
+
def rubyforge_username
|
15
|
+
unless @config
|
16
|
+
begin
|
17
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
18
|
+
rescue
|
19
|
+
puts <<-EOS
|
20
|
+
ERROR: No rubyforge config file found: #{@config_file}
|
21
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
22
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
23
|
+
EOS
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RUBYFORGE_USERNAME.replace @config["username"]
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
REV = nil
|
32
|
+
# UNCOMMENT IF REQUIRED:
|
33
|
+
# REV = YAML.load(`svn info`)['Revision']
|
34
|
+
VERS = ActiveRecord::ConnectionAdapters::DbslayerAdapter::VERSION + (REV ? ".#{REV}" : "")
|
35
|
+
RDOC_OPTS = ['--quiet', '--title', 'activerecord-dbslayer-adapter documentation',
|
36
|
+
"--opname", "index.html",
|
37
|
+
"--line-numbers",
|
38
|
+
"--main", "README",
|
39
|
+
"--inline-source"]
|
40
|
+
|
41
|
+
class Hoe
|
42
|
+
def extra_deps
|
43
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
44
|
+
@extra_deps
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generate all the Rake tasks
|
49
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
50
|
+
$hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
51
|
+
p.developer(AUTHOR, EMAIL)
|
52
|
+
p.description = DESCRIPTION
|
53
|
+
p.summary = DESCRIPTION
|
54
|
+
p.url = HOMEPATH
|
55
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
56
|
+
p.test_globs = ["test/**/test_*.rb"]
|
57
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
58
|
+
|
59
|
+
# == Optional
|
60
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
61
|
+
#p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
62
|
+
|
63
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
68
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
69
|
+
$hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
70
|
+
$hoe.rsync_args = '-av --delete --ignore-errors'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
include FileUtils
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
%w[rake hoe newgem rubigen mocha active_support active_record].each do |req_gem|
|
6
|
+
begin
|
7
|
+
require req_gem
|
8
|
+
rescue LoadError
|
9
|
+
puts "This Rakefile requires the '#{req_gem}' RubyGem."
|
10
|
+
puts "Installation: gem install #{req_gem} -y"
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
|
@@ -0,0 +1,356 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/dbslayer_connection'
|
3
|
+
require 'active_record/connection_adapters/mysql_adapter'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
class Base
|
7
|
+
# Establishes a connection to the database that's used by all Active Record objects.
|
8
|
+
def self.dbslayer_connection(config) # :nodoc:
|
9
|
+
config = config.symbolize_keys
|
10
|
+
host = config[:host]
|
11
|
+
port = config[:port]
|
12
|
+
|
13
|
+
connection = ConnectionAdapters::DbslayerConnection.new(host, port)
|
14
|
+
ConnectionAdapters::DbslayerAdapter.new(connection, logger, [host, port], config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ConnectionAdapters
|
19
|
+
##
|
20
|
+
# This is just a basic inheritance of MysqlColumn
|
21
|
+
class DbslayerColumn < MysqlColumn #:nodoc:
|
22
|
+
private
|
23
|
+
def simplified_type(field_type) #:nodoc:
|
24
|
+
return :boolean if DbslayerAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
25
|
+
return :string if field_type =~ /enum/i
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# The DbslayerAdapter is an adapter to use Rails with the DBSlayer
|
31
|
+
#
|
32
|
+
# Options:
|
33
|
+
#
|
34
|
+
# * <tt>:host</tt> -- Defaults to localhost
|
35
|
+
# * <tt>:port</tt> -- Defaults to 3306
|
36
|
+
#
|
37
|
+
# Like the MySQL adapter: by default, the MysqlAdapter will consider all columns of type tinyint(1)
|
38
|
+
# as boolean. If you wish to disable this emulation (which was the default
|
39
|
+
# behavior in versions 0.13.1 and earlier) you can add the following line
|
40
|
+
# to your environment.rb file:
|
41
|
+
#
|
42
|
+
# ActiveRecord::ConnectionAdapters::DbslayerAdapter.emulate_booleans = false
|
43
|
+
#
|
44
|
+
# MAJOR WARNING: The MySQL adapter in Rails sets the
|
45
|
+
class DbslayerAdapter < MysqlAdapter
|
46
|
+
def initialize(connection, logger, connection_options, config)
|
47
|
+
super(connection, logger, connection_options, config)
|
48
|
+
ActiveRecord::Base.allow_concurrency = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def adapter_name #:nodoc:
|
52
|
+
'DBSlayer (MySQL)'
|
53
|
+
end
|
54
|
+
|
55
|
+
# def supports_migrations? #:nodoc:
|
56
|
+
# true
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# def native_database_types #:nodoc:
|
60
|
+
# {
|
61
|
+
# :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
62
|
+
# :string => { :name => "varchar", :limit => 255 },
|
63
|
+
# :text => { :name => "text" },
|
64
|
+
# :integer => { :name => "int", :limit => 11 },
|
65
|
+
# :float => { :name => "float" },
|
66
|
+
# :decimal => { :name => "decimal" },
|
67
|
+
# :datetime => { :name => "datetime" },
|
68
|
+
# :timestamp => { :name => "datetime" },
|
69
|
+
# :time => { :name => "time" },
|
70
|
+
# :date => { :name => "date" },
|
71
|
+
# :binary => { :name => "blob" },
|
72
|
+
# :boolean => { :name => "tinyint", :limit => 1 }
|
73
|
+
# }
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# # QUOTING ==================================================
|
78
|
+
#
|
79
|
+
# def quote(value, column = nil)
|
80
|
+
# if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
81
|
+
# s = column.class.string_to_binary(value).unpack("H*")[0]
|
82
|
+
# "x'#{s}'"
|
83
|
+
# elsif value.kind_of?(BigDecimal)
|
84
|
+
# "'#{value.to_s("F")}'"
|
85
|
+
# else
|
86
|
+
# super
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# def quote_column_name(name) #:nodoc:
|
91
|
+
# "`#{name}`"
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# def quote_table_name(name) #:nodoc:
|
95
|
+
# quote_column_name(name).gsub('.', '`.`')
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# def quote_string(string) #:nodoc:
|
99
|
+
# @connection.quote(string)
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# def quoted_true
|
103
|
+
# "1"
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# def quoted_false
|
107
|
+
# "0"
|
108
|
+
# end
|
109
|
+
|
110
|
+
# REFERENTIAL INTEGRITY ====================================
|
111
|
+
|
112
|
+
def disable_referential_integrity(&block) #:nodoc:
|
113
|
+
#FIXME: I CAN'T LET YOU DO THIS
|
114
|
+
# old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
115
|
+
#
|
116
|
+
# begin
|
117
|
+
# update("SET FOREIGN_KEY_CHECKS = 0")
|
118
|
+
# yield
|
119
|
+
# ensure
|
120
|
+
# update("SET FOREIGN_KEY_CHECKS = #{old}")
|
121
|
+
# end
|
122
|
+
end
|
123
|
+
|
124
|
+
# CONNECTION MANAGEMENT ====================================
|
125
|
+
|
126
|
+
def active?
|
127
|
+
stats = @connection.mysql_stats
|
128
|
+
!stats.nil? && !stats.empty?
|
129
|
+
rescue
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
def reconnect!
|
134
|
+
# DO NOTHING, we connect on the request
|
135
|
+
end
|
136
|
+
|
137
|
+
def disconnect!
|
138
|
+
# DO NOTHING, we connect on the request
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# DATABASE STATEMENTS ======================================
|
143
|
+
|
144
|
+
def select_rows(sql, name = nil)
|
145
|
+
result = execute(sql, name)
|
146
|
+
result.rows
|
147
|
+
end
|
148
|
+
|
149
|
+
def execute(sql, name = nil) #:nodoc:
|
150
|
+
log(sql, name) {
|
151
|
+
@connection.execute(sql)
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
# def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
156
|
+
# super sql, name
|
157
|
+
# id_value || @connection.insert_id
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# def update_sql(sql, name = nil) #:nodoc:
|
161
|
+
# super
|
162
|
+
# @connection.affected_rows
|
163
|
+
# end
|
164
|
+
|
165
|
+
def begin_db_transaction #:nodoc:
|
166
|
+
# FIXME: raise exception?
|
167
|
+
# execute "BEGIN"
|
168
|
+
# rescue Exception
|
169
|
+
# Transactions aren't supported
|
170
|
+
end
|
171
|
+
|
172
|
+
def commit_db_transaction #:nodoc:
|
173
|
+
# FIXME: raise exception?
|
174
|
+
|
175
|
+
# execute "COMMIT"
|
176
|
+
# rescue Exception
|
177
|
+
# Transactions aren't supported
|
178
|
+
end
|
179
|
+
|
180
|
+
def rollback_db_transaction #:nodoc:
|
181
|
+
# FIXME: raise exception?
|
182
|
+
|
183
|
+
# execute "ROLLBACK"
|
184
|
+
# rescue Exception
|
185
|
+
# Transactions aren't supported
|
186
|
+
end
|
187
|
+
|
188
|
+
# SCHEMA STATEMENTS ========================================
|
189
|
+
|
190
|
+
# def structure_dump #:nodoc:
|
191
|
+
# if supports_views?
|
192
|
+
# sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
193
|
+
# else
|
194
|
+
# sql = "SHOW TABLES"
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# select_all(sql).inject("") do |structure, table|
|
198
|
+
# table.delete('Table_type')
|
199
|
+
# structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# def recreate_database(name) #:nodoc:
|
204
|
+
# drop_database(name)
|
205
|
+
# create_database(name)
|
206
|
+
# end
|
207
|
+
|
208
|
+
# Create a new MySQL database with optional :charset and :collation.
|
209
|
+
# Charset defaults to utf8.
|
210
|
+
#
|
211
|
+
# Example:
|
212
|
+
# create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
|
213
|
+
# create_database 'matt_development'
|
214
|
+
# create_database 'matt_development', :charset => :big5
|
215
|
+
# def create_database(name, options = {})
|
216
|
+
# if options[:collation]
|
217
|
+
# execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
218
|
+
# else
|
219
|
+
# execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# def drop_database(name) #:nodoc:
|
224
|
+
# execute "DROP DATABASE IF EXISTS `#{name}`"
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
# def current_database
|
228
|
+
# select_value 'SELECT DATABASE() as db'
|
229
|
+
# end
|
230
|
+
|
231
|
+
# Returns the database character set.
|
232
|
+
# def charset
|
233
|
+
# if @charset.nil?
|
234
|
+
# @charset = show_variable 'character_set_database'
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# @charset
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# # Returns the database collation strategy.
|
241
|
+
# def collation
|
242
|
+
# if @collation.nil?
|
243
|
+
# @collation = show_variable 'collation_database'
|
244
|
+
# end
|
245
|
+
# @collation
|
246
|
+
# end
|
247
|
+
|
248
|
+
def tables(name = nil) #:nodoc:
|
249
|
+
tables = []
|
250
|
+
execute("SHOW TABLES", name).rows.each { |row| tables << row[0] }
|
251
|
+
tables
|
252
|
+
end
|
253
|
+
|
254
|
+
# def drop_table(table_name, options = {})
|
255
|
+
# super(table_name, options)
|
256
|
+
# end
|
257
|
+
|
258
|
+
def indexes(table_name, name = nil)#:nodoc:
|
259
|
+
indexes = []
|
260
|
+
current_index = nil
|
261
|
+
execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).rows.each do |row|
|
262
|
+
if current_index != row[2]
|
263
|
+
next if row[2] == "PRIMARY" # skip the primary key
|
264
|
+
current_index = row[2]
|
265
|
+
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
|
266
|
+
end
|
267
|
+
|
268
|
+
indexes.last.columns << row[4]
|
269
|
+
end
|
270
|
+
indexes
|
271
|
+
end
|
272
|
+
|
273
|
+
def columns(table_name, name = nil)#:nodoc:
|
274
|
+
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
275
|
+
columns = []
|
276
|
+
execute(sql, name).rows.each { |row| columns << DbslayerColumn.new(row[0], row[4], row[1], row[2] == "YES") }
|
277
|
+
columns
|
278
|
+
end
|
279
|
+
|
280
|
+
# def create_table(table_name, options = {}) #:nodoc:
|
281
|
+
# super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
282
|
+
# end
|
283
|
+
#
|
284
|
+
# def rename_table(table_name, new_name)
|
285
|
+
# execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
286
|
+
# end
|
287
|
+
#
|
288
|
+
# def change_column_default(table_name, column_name, default) #:nodoc:
|
289
|
+
# current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
290
|
+
#
|
291
|
+
# execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
|
292
|
+
# end
|
293
|
+
#
|
294
|
+
# def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
295
|
+
# unless options_include_default?(options)
|
296
|
+
# if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
297
|
+
# options[:default] = column.default
|
298
|
+
# else
|
299
|
+
# raise "No such column: #{table_name}.#{column_name}"
|
300
|
+
# end
|
301
|
+
# end
|
302
|
+
#
|
303
|
+
# change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
304
|
+
# add_column_options!(change_column_sql, options)
|
305
|
+
# execute(change_column_sql)
|
306
|
+
# end
|
307
|
+
#
|
308
|
+
# def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
309
|
+
# current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
310
|
+
# execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
311
|
+
# end
|
312
|
+
|
313
|
+
# SHOW VARIABLES LIKE 'name'
|
314
|
+
def show_variable(name)
|
315
|
+
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
|
316
|
+
variables.first['Value'] unless variables.empty?
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns a table's primary key and belonging sequence.
|
320
|
+
def pk_and_sequence_for(table) #:nodoc:
|
321
|
+
keys = []
|
322
|
+
execute("describe #{quote_table_name(table)}").each_hash do |h|
|
323
|
+
keys << h["Field"]if h["Key"] == "PRI"
|
324
|
+
end
|
325
|
+
keys.length == 1 ? [keys.first, nil] : nil
|
326
|
+
end
|
327
|
+
|
328
|
+
private
|
329
|
+
def connect
|
330
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
331
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
332
|
+
##FIXME !!! execute("SET SQL_AUTO_IS_NULL=0")
|
333
|
+
end
|
334
|
+
|
335
|
+
def select(sql, name = nil)
|
336
|
+
result = execute(sql, name)
|
337
|
+
rows = result.hash_rows
|
338
|
+
rows
|
339
|
+
end
|
340
|
+
|
341
|
+
# Executes the update statement and returns the number of rows affected.
|
342
|
+
def update_sql(sql, name = nil)
|
343
|
+
execute(sql, name).rows[0][0]
|
344
|
+
end
|
345
|
+
|
346
|
+
def supports_views?
|
347
|
+
## get mysql version
|
348
|
+
version[0] >= 5
|
349
|
+
end
|
350
|
+
|
351
|
+
def version
|
352
|
+
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|