Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
aeden committed May 16, 2008
0 parents commit 6d11a3f
Show file tree
Hide file tree
Showing 24 changed files with 704 additions and 0 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG
@@ -0,0 +1,23 @@
0.1.0 - March 5, 2007
* Initial release

0.1.1 - March 5, 2007
* Bug fixes

0.1.2 - March 5, 2007
* Bug fixes

0.2.0 - March 6, 2007
* SQL Server adapter included (Seth Ladd)

0.3.0 - March 8, 2007
* PostgreSQL adapter included
* Added tests for bulk loading
* bulk_load method now handles table missing and file missing as error cases

0.3.1 - May 4, 2007
* Added support for modifying SELECT statements to add an INSERT INTO.

0.4 - September 17, 2007
* Added copy_table method that can copy the structure and data from one table to another. Currently implemented in MySQL (tested), PostgreSQL (tested) and SQL Server adapters (untested).
* Added support for SELECT..INTO for PostgreSQL.
8 changes: 8 additions & 0 deletions HOW_TO_RELEASE
@@ -0,0 +1,8 @@
cd trunk
rake release
cd ..
svn cp trunk tags/release-x.y.z
cd tags/release-x.y.z
svn commit
cd ../../trunk
rake pdoc
16 changes: 16 additions & 0 deletions LICENSE
@@ -0,0 +1,16 @@
Copyright (c) 2007 Anthony Eden

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5 changes: 5 additions & 0 deletions README
@@ -0,0 +1,5 @@
This library provides extensions to Rails' ActiveRecord adapters.

To use the MySQL adapter extensions with Rails 2.x, you must patch the mysql_adapter with the mysql_adapter_opt_local_infile.patch.

To execute the unit tests you must first construct a adapter_extensions_unittest database.
140 changes: 140 additions & 0 deletions Rakefile
@@ -0,0 +1,140 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/rubyforgepublisher'

require File.join(File.dirname(__FILE__), 'lib/adapter_extensions', 'version')

PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'adapter_extensions'
PKG_VERSION = AdapterExtensions::VERSION::STRING + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
PKG_DESTINATION = ENV["PKG_DESTINATION"] || "../#{PKG_NAME}"

RELEASE_NAME = "REL #{PKG_VERSION}"

RUBY_FORGE_PROJECT = "activewarehouse"
RUBY_FORGE_USER = "aeden"

desc 'Default: run unit tests.'
task :default => :test

desc 'Test the ETL application.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
# TODO: reset the database
end

namespace :rcov do
desc 'Measures test coverage'
task :test do
rm_f 'coverage.data'
mkdir 'coverage' unless File.exist?('coverage')
rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
system("#{rcov} test/*_test.rb test/**/*_test.rb")
system("open coverage/index.html") if PLATFORM['darwin']
end
end

desc 'Generate documentation for the AdapterExtensions library.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'Extensions for Rails adapters'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end

PKG_FILES = FileList[
'CHANGELOG',
'README',
'LICENSE',
'Rakefile',
'doc/**/*',
'lib/**/*',
] - [ 'test' ]

spec = Gem::Specification.new do |s|
s.name = 'adapter_extensions'
s.version = PKG_VERSION
s.summary = "Extensions to Rails ActiveRecord adapters."
s.description = <<-EOF
Provides various extensions to the Rails ActiveRecord adapters.
EOF

s.add_dependency('rake', '>= 0.7.1')
s.add_dependency('activesupport', '>= 1.3.1')
s.add_dependency('activerecord', '>= 1.14.4')
s.add_dependency('fastercsv', '>= 1.0.0')

s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false

s.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')}
s.require_path = 'lib'

s.author = "Anthony Eden"
s.email = "anthonyeden@gmail.com"
s.homepage = "http://activewarehouse.rubyforge.org/adapter_extensions"
s.rubyforge_project = "activewarehouse"
end

Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
pkg.need_tar = true
pkg.need_zip = true
end

desc "Generate code statistics"
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0

for file_name in FileList["lib/**/*.rb"]
next if file_name =~ /vendor/
f = File.open(file_name)

while line = f.gets
lines += 1
next if line =~ /^\s*$/
next if line =~ /^\s*#/
codelines += 1
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"

total_lines += lines
total_codelines += codelines

lines, codelines = 0, 0
end

puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
end

desc "Publish the release files to RubyForge."
task :release => [ :package ] do
`rubyforge login`

for ext in %w( gem tgz zip )
release_command = "rubyforge add_release activewarehouse #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
puts release_command
system(release_command)
end
end

desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("aeden@rubyforge.org", "/var/www/gforge-projects/activewarehouse/adapter_extensions/rdoc", "rdoc").upload
end

desc "Reinstall the gem from a local package copy"
task :reinstall => [:package] do
windows = RUBY_PLATFORM =~ /mswin/
sudo = windows ? '' : 'sudo'
gem = windows ? 'gem.bat' : 'gem'
`#{sudo} #{gem} uninstall -x -i #{PKG_NAME}`
`#{sudo} #{gem} install pkg/#{PKG_NAME}-#{PKG_VERSION}`
end
23 changes: 23 additions & 0 deletions lib/adapter_extensions.rb
@@ -0,0 +1,23 @@
# Extensions to the Rails ActiveRecord adapters.
#
# Requiring this file will require all of the necessary files to function.

puts "Using AdapterExtensions"

require 'rubygems'
unless Kernel.respond_to?(:gem)
Kernel.send :alias_method, :gem, :require_gem
end

unless defined?(ActiveSupport)
gem 'activesupport'
require 'active_support'
end

unless defined?(ActiveRecord)
gem 'activerecord'
require 'active_record'
end

$:.unshift(File.dirname(__FILE__))
Dir[File.dirname(__FILE__) + "/adapter_extensions/**/*.rb"].each { |file| require(file) }
44 changes: 44 additions & 0 deletions lib/adapter_extensions/connection_adapters/abstract_adapter.rb
@@ -0,0 +1,44 @@
# This source file contains extensions to the abstract adapter.
module ActiveRecord #:nodoc:
module ConnectionAdapters #:nodoc:
# Extensions to the AbstractAdapter. In some cases a default implementation
# is provided, in others it is adapter-dependent and the method will
# raise a NotImplementedError if the adapter does not implement that method
class AbstractAdapter
# Truncate the specified table
def truncate(table_name)
execute("TRUNCATE TABLE #{table_name}")
end

# Bulk loading interface. Load the data from the specified file into the
# given table. Note that options will be adapter-dependent.
def bulk_load(file, table_name, options={})
raise ArgumentError, "#{file} does not exist" unless File.exist?(file)
raise ArgumentError, "#{table_name} does not exist" unless tables.include?(table_name)
do_bulk_load(file, table_name, options)
end

# SQL select into statement constructs a new table from the results
# of a select. It is used to select data from a table and create a new
# table with its result set at the same time. Note that this method
# name does not necessarily match the implementation. E.g. MySQL's
# version of this is 'CREATE TABLE ... AS SELECT ...'
def support_select_into_table?
false
end

# Add a chunk of SQL to the given query that will create a new table and
# execute the select into that table.
def add_select_into_table(new_table_name, sql_query)
raise NotImplementedError, "add_select_into_table is an abstract method"
end

protected

# for subclasses to implement
def do_bulk_load(file, table_name, options={})
raise NotImplementedError, "do_bulk_load is an abstract method"
end
end
end
end
51 changes: 51 additions & 0 deletions lib/adapter_extensions/connection_adapters/mysql_adapter.rb
@@ -0,0 +1,51 @@
# Source code for the MysqlAdapter extensions.
module ActiveRecord #:nodoc:
module ConnectionAdapters #:nodoc:
# Adds new functionality to ActiveRecord MysqlAdapter.
class MysqlAdapter < AbstractAdapter

def support_select_into_table?
true
end

# Inserts an INTO table_name clause to the sql_query.
def add_select_into_table(new_table_name, sql_query)
"CREATE TABLE #{new_table_name} " + sql_query
end

# Copy the specified table.
def copy_table(old_table_name, new_table_name)
transaction do
execute "CREATE TABLE #{new_table_name} LIKE #{old_table_name}"
execute "INSERT INTO #{new_table_name} SELECT * FROM #{old_table_name}"
end
end

protected
# Call +bulk_load+, as that method wraps this method.
#
# Bulk load the data in the specified file. This implementation always uses the LOCAL keyword
# so the file must be found locally, not on the remote server, to be loaded.
#
# Options:
# * <tt>:ignore</tt> -- Ignore the specified number of lines from the source file
# * <tt>:columns</tt> -- Array of column names defining the source file column order
# * <tt>:fields</tt> -- Hash of options for fields:
# * <tt>:delimited_by</tt> -- The field delimiter
# * <tt>:enclosed_by</tt> -- The field enclosure
def do_bulk_load(file, table_name, options={})
return if File.size(file) == 0
q = "LOAD DATA LOCAL INFILE '#{file}' INTO TABLE #{table_name}"
if options[:fields]
q << " FIELDS"
q << " TERMINATED BY '#{options[:fields][:delimited_by]}'" if options[:fields][:delimited_by]
q << " ENCLOSED BY '#{options[:fields][:enclosed_by]}'" if options[:fields][:enclosed_by]
end
q << " IGNORE #{options[:ignore]} LINES" if options[:ignore]
q << " (#{options[:columns].join(',')})" if options[:columns]
execute(q)
end

end
end
end
50 changes: 50 additions & 0 deletions lib/adapter_extensions/connection_adapters/postgresql_adapter.rb
@@ -0,0 +1,50 @@
# Source code for the PostgreSQLAdapter extensions.
module ActiveRecord #:nodoc:
module ConnectionAdapters #:nodoc:
# Adds new functionality to ActiveRecord PostgreSQLAdapter.
class PostgreSQLAdapter < AbstractAdapter
def support_select_into_table?
true
end

# Inserts an INTO table_name clause to the sql_query.
def add_select_into_table(new_table_name, sql_query)
sql_query.sub(/FROM/i, "INTO #{new_table_name} FROM")
end

# Copy the specified table.
def copy_table(old_table_name, new_table_name)
execute add_select_into_table(new_table_name, "SELECT * FROM #{old_table_name}")
end

protected
# Call +bulk_load+, as that method wraps this method.
#
# Bulk load the data in the specified file.
#
# Options:
# * <tt>:ignore</tt> -- Ignore the specified number of lines from the source file. In the case of PostgreSQL
# only the first line will be ignored from the source file regardless of the number of lines specified.
# * <tt>:columns</tt> -- Array of column names defining the source file column order
# * <tt>:fields</tt> -- Hash of options for fields:
# * <tt>:delimited_by</tt> -- The field delimiter
# * <tt>:enclosed_by</tt> -- The field enclosure
def do_bulk_load(file, table_name, options={})
q = "COPY #{table_name} "
q << "(#{options[:columns].join(',')}) " if options[:columns]
q << "FROM '#{File.expand_path(file)}' "
if options[:fields]
q << "WITH "
q << "DELIMITER '#{options[:fields][:delimited_by]}' " if options[:fields][:delimited_by]
if options[:fields][:enclosed_by]
q << "CSV "
q << "HEADER " if options[:ignore] && options[:ignore] > 0
q << "QUOTE '#{options[:fields][:enclosed_by]}' " if options[:fields][:enclosed_by]
end
end

execute(q)
end
end
end
end

0 comments on commit 6d11a3f

Please sign in to comment.