Skip to content

Commit

Permalink
dbslayer-adapter: some tweaks before release
Browse files Browse the repository at this point in the history
git-svn-id: svn://newsprojects.nytimes.com/newsdev/gems/activerecord-dbslayer-adapter@4383 ae54da50-7589-804d-96e1-6ee4b2538e53
  • Loading branch information
harrisj committed May 1, 2008
1 parent 7a8935e commit 0ad2888
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 43 deletions.
6 changes: 6 additions & 0 deletions History.txt
@@ -1,3 +1,9 @@
== 0.0.2 2008-05-01

* 2 major enhancments:
* tests
* it actually works!

== 0.0.1 2008-04-15 == 0.0.1 2008-04-15


* 1 major enhancement: * 1 major enhancement:
Expand Down
1 change: 1 addition & 0 deletions Manifest.txt
Expand Up @@ -7,6 +7,7 @@ config/hoe.rb
config/requirements.rb config/requirements.rb
lib/active_record/connection_adapters/dbslayer_adapter.rb lib/active_record/connection_adapters/dbslayer_adapter.rb
lib/active_record/connection_adapters/dbslayer_connection.rb lib/active_record/connection_adapters/dbslayer_connection.rb
lib/activerecord-dbslayer-adapter.rb
log/debug.log log/debug.log
script/console script/console
script/destroy script/destroy
Expand Down
46 changes: 39 additions & 7 deletions README.txt
@@ -1,32 +1,64 @@
= activerecord_dbslayer_adapter = activerecord_dbslayer_adapter


* FIX (url) Jacob Harris
jharris@nytimes.com


== DESCRIPTION: == DESCRIPTION:


FIX (describe your package) 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.

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.

== MAJOR CAVEATS:

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.

This becomes a problem with transactions. Although DBSlayer can support transactions if you use semicolons to include them all as one statement like
BEGIN TRANSACTION; more SQL; COMMIT TRANSACTION
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.

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:

Restaurant.find(:all, :conditions => 'id IS NULL')

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.


== FEATURES/PROBLEMS: == FEATURES/PROBLEMS:


* FIX (list of features or problems) * More tests
* Better documentation


== SYNOPSIS: == SYNOPSIS:


FIX (code sample of usage) In your databases.yml

production:
adapter: dbslayer
host: localhost
port: 9090

All of the other normal database setting like the MySQL server, username, password, etc. are specified in the dbslayer daemon's configuration.


== REQUIREMENTS: == REQUIREMENTS:


* FIX (list of requirements) * the JSON gem
* A running DBSlayer instance


== INSTALL: == INSTALL:


* FIX (sudo gem install, anything else) * gem install activerecord-dbslayer-adapter
* in your rails project, add the following line at the end of config/environment.rb
Rails::Initializer.run do |config|
...

require 'activerecord-dbslayer-adapter'
end


== LICENSE: == LICENSE:


(The MIT License) (The MIT License)


Copyright (c) 2008 FIX Copyright (c) 2008


Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
Expand Down
8 changes: 4 additions & 4 deletions config/hoe.rb
@@ -1,10 +1,10 @@
require 'active_record/connection_adapters/dbslayer_adapter' require 'activerecord-dbslayer-adapter'


AUTHOR = 'Jacob Harris' # can also be an array of Authors AUTHOR = 'Jacob Harris' # can also be an array of Authors
EMAIL = "jharris@nytimes.com" EMAIL = "jharris@nytimes.com"
DESCRIPTION = "An ActiveRecord adapter to DBSlayer" DESCRIPTION = "An ActiveRecord adapter to DBSlayer"
GEM_NAME = 'activerecord_dbslayer_adapter' # what ppl will type to install your gem GEM_NAME = 'activerecord-dbslayer-adapter' # what ppl will type to install your gem
RUBYFORGE_PROJECT = 'activerecord_dbslayer_adapter' # The unix name for your project RUBYFORGE_PROJECT = 'activerecord-dbslayer-adapter' # The unix name for your project
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"


Expand Down Expand Up @@ -32,7 +32,7 @@ def rubyforge_username
# UNCOMMENT IF REQUIRED: # UNCOMMENT IF REQUIRED:
# REV = YAML.load(`svn info`)['Revision'] # REV = YAML.load(`svn info`)['Revision']
VERS = ActiveRecord::ConnectionAdapters::DbslayerAdapter::VERSION + (REV ? ".#{REV}" : "") VERS = ActiveRecord::ConnectionAdapters::DbslayerAdapter::VERSION + (REV ? ".#{REV}" : "")
RDOC_OPTS = ['--quiet', '--title', 'activerecord_dbslayer_adapter documentation', RDOC_OPTS = ['--quiet', '--title', 'activerecord-dbslayer-adapter documentation',
"--opname", "index.html", "--opname", "index.html",
"--line-numbers", "--line-numbers",
"--main", "README", "--main", "README",
Expand Down
33 changes: 5 additions & 28 deletions lib/active_record/connection_adapters/dbslayer_adapter.rb
Expand Up @@ -16,38 +16,15 @@ def self.dbslayer_connection(config) # :nodoc:
end end


module ConnectionAdapters module ConnectionAdapters
##
# This is just a basic inheritance of MysqlColumn
class DbslayerColumn < MysqlColumn #:nodoc: class DbslayerColumn < MysqlColumn #:nodoc:
# def extract_default(default)
# if type == :binary || type == :text
# if default.blank?
# default
# else
# raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
# end
# elsif missing_default_forged_as_empty_string?(default)
# nil
# else
# super
# end
# end

private private
def simplified_type(field_type) def simplified_type(field_type) #:nodoc:
return :boolean if DbslayerAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)") return :boolean if DbslayerAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
return :string if field_type =~ /enum/i return :string if field_type =~ /enum/i
super super
end end

# MySQL misreports NOT NULL column default when none is given.
# We can't detect this for columns which may have a legitimate ''
# default (string) but we can for others (integer, datetime, boolean,
# and the rest).
#
# Test whether the column has default '', is not null, and is not
# a type allowing default ''.
# def missing_default_forged_as_empty_string?(default)
# type != :string && !null && default == ''
# end
end end


# The DbslayerAdapter is an adapter to use Rails with the DBSlayer # The DbslayerAdapter is an adapter to use Rails with the DBSlayer
Expand All @@ -63,9 +40,9 @@ def simplified_type(field_type)
# to your environment.rb file: # to your environment.rb file:
# #
# ActiveRecord::ConnectionAdapters::DbslayerAdapter.emulate_booleans = false # ActiveRecord::ConnectionAdapters::DbslayerAdapter.emulate_booleans = false
#
# MAJOR WARNING: The MySQL adapter in Rails sets the
class DbslayerAdapter < MysqlAdapter class DbslayerAdapter < MysqlAdapter
VERSION = '0.2.0'

def initialize(connection, logger, connection_options, config) def initialize(connection, logger, connection_options, config)
super(connection, logger, connection_options, config) super(connection, logger, connection_options, config)
ActiveRecord::Base.allow_concurrency = true ActiveRecord::Base.allow_concurrency = true
Expand Down
33 changes: 33 additions & 0 deletions lib/activerecord-dbslayer-adapter.rb
@@ -0,0 +1,33 @@
# Stole this code from Nick Sieger
begin
tried_gem ||= false
require 'active_record/version'
rescue LoadError
raise if tried_gem
require 'rubygems'
gem 'activerecord'
tried_gem = true
retry
end

if ActiveRecord::VERSION::MAJOR < 2
if defined?(RAILS_CONNECTION_ADAPTERS)
RAILS_CONNECTION_ADAPTERS << %q(dbslayer)
else
RAILS_CONNECTION_ADAPTERS = %w(dbslayer)
end
if ActiveRecord::VERSION::MAJOR == 1 && ActiveRecord::VERSION::MINOR == 14
require 'active_record/connection_adapters/dbslayer_adapter'
end
else
require 'active_record'
require 'active_record/connection_adapters/dbslayer_adapter'
end

module ActiveRecord
module ConnectionAdapters
class DbslayerAdapter
VERSION = '0.2.0'
end
end
end
5 changes: 1 addition & 4 deletions test/localtest.rb
@@ -1,8 +1,5 @@
require 'rubygems' require 'rubygems'
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib])) require 'activerecord-dbslayer-adapter'

require 'active_record'
require 'active_record/connection_adapters/dbslayer_adapter'


ActiveRecord::Base.establish_connection({ ActiveRecord::Base.establish_connection({
:adapter => 'dbslayer', :adapter => 'dbslayer',
Expand Down

0 comments on commit 0ad2888

Please sign in to comment.