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 ADDED
@@ -0,0 +1,10 @@
1
+ == 0.0.2 2008-05-01
2
+
3
+ * 2 major enhancments:
4
+ * tests
5
+ * it actually works!
6
+
7
+ == 0.0.1 2008-04-15
8
+
9
+ * 1 major enhancement:
10
+ * Initial release
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
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
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