adapter_extensions 0.9.5 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +14 -0
- data/CHANGELOG +5 -0
- data/HOW_TO_RELEASE +4 -4
- data/LICENSE +1 -1
- data/README.md +121 -14
- data/Rakefile +38 -53
- data/adapter_extensions.gemspec +4 -2
- data/lib/adapter_extensions/active_record/adapters/abstract_adapter.rb +9 -0
- data/lib/adapter_extensions/active_record/adapters/mysql2_adapter.rb +6 -0
- data/lib/adapter_extensions/active_record/adapters/mysql_adapter.rb +6 -0
- data/lib/adapter_extensions/active_record/adapters/postgresql_adapter.rb +6 -0
- data/lib/adapter_extensions/active_record/adapters/sqlserver_adapter.rb +6 -0
- data/lib/adapter_extensions/adapters/abstract_adapter.rb +44 -0
- data/lib/adapter_extensions/adapters/mysql_adapter.rb +73 -0
- data/lib/adapter_extensions/adapters/postgresql_adapter.rb +47 -0
- data/lib/adapter_extensions/adapters/sqlserver_adapter.rb +63 -0
- data/lib/adapter_extensions/base.rb +19 -0
- data/lib/adapter_extensions/version.rb +1 -1
- data/lib/adapter_extensions.rb +17 -8
- data/test/abstract_adapter_test.rb +10 -4
- data/test/config/database.yml +15 -0
- data/test/{connection/mysql/setup.sql → config/databases/mysql_setup.sql} +0 -0
- data/test/{connection/postgresql/setup.sql → config/databases/postgresql_setup.sql} +0 -0
- data/test/config/gemfiles/.gitignore +1 -0
- data/test/config/gemfiles/Gemfile.rails-3.0.x +3 -0
- data/test/config/gemfiles/Gemfile.rails-3.1.x +3 -0
- data/test/config/gemfiles/Gemfile.rails-3.2.x +3 -0
- data/test/config/gemfiles/common.rb +22 -0
- data/test/integration/adapter_test.rb +23 -7
- data/test/test_helper.rb +3 -4
- data/test/unit/sqlserver_test_ignored.rb +89 -0
- metadata +86 -88
- data/lib/adapter_extensions/connection_adapters/abstract_adapter.rb +0 -50
- data/lib/adapter_extensions/connection_adapters/mysql_adapter.rb +0 -90
- data/lib/adapter_extensions/connection_adapters/postgresql_adapter.rb +0 -52
- data/lib/adapter_extensions/connection_adapters/sqlserver_adapter.rb +0 -44
- data/test/connection/mysql/connection.rb +0 -27
- data/test/connection/postgresql/connection.rb +0 -27
data/.travis.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
language: ruby
|
2
|
+
gemfile:
|
3
|
+
- test/config/gemfiles/Gemfile.rails-3.2.x
|
4
|
+
- test/config/gemfiles/Gemfile.rails-3.1.x
|
5
|
+
- test/config/gemfiles/Gemfile.rails-3.0.x
|
6
|
+
rvm:
|
7
|
+
- 1.9.3
|
8
|
+
- 1.8.7
|
9
|
+
env:
|
10
|
+
- DB=mysql2
|
11
|
+
- DB=mysql
|
12
|
+
- DB=postgresql
|
13
|
+
before_script:
|
14
|
+
- rake ci:create_db
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
1.0.0.rc1 - March 3, 2012
|
2
|
+
* complete rewrite: Rails 3+ required
|
3
|
+
* "mysql2" adapter support
|
4
|
+
* work in progress SQLServer bulk import, requiring freebcp
|
5
|
+
|
1
6
|
0.9.5 - November 8, 2011
|
2
7
|
* Add in REPLACE to LOAD DATA INFILE command (jayzes/kookster)
|
3
8
|
* Remove FasterCSV from dependencies (not used apparently)
|
data/HOW_TO_RELEASE
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
* update lib/adapter_extensions/version
|
2
2
|
* push your changes
|
3
|
-
*
|
3
|
+
* build the gem:
|
4
4
|
|
5
|
-
|
5
|
+
gem build adapter_extensions.gemspec
|
6
6
|
|
7
|
-
*
|
7
|
+
* create tag and push to rubygem:
|
8
8
|
|
9
|
-
|
9
|
+
rake release
|
10
10
|
|
11
11
|
* you can list changes using github:
|
12
12
|
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2011 Anthony Eden
|
1
|
+
Copyright (c) 2011-2012 Anthony Eden, Thibaut Barrère
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
4
4
|
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
data/README.md
CHANGED
@@ -1,23 +1,130 @@
|
|
1
|
-
|
1
|
+
AdapterExtensions add extra abilities to Rails ActiveRecord adapters, including:
|
2
2
|
|
3
|
-
|
3
|
+
* bulk load
|
4
|
+
* truncate table
|
5
|
+
* copy table
|
6
|
+
* add select into table
|
7
|
+
|
8
|
+
### Compatibility matrix
|
4
9
|
|
5
|
-
|
10
|
+
See Travis for up-to-date info: [![Build Status](https://secure.travis-ci.org/activewarehouse/adapter_extensions.png?branch=master)](http://travis-ci.org/activewarehouse/adapter_extensions)
|
6
11
|
|
7
|
-
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<th></th>
|
15
|
+
<th>v1.0.0.rc1</th>
|
16
|
+
<th>v0.9.5</th>
|
17
|
+
</tr>
|
18
|
+
<tr>
|
19
|
+
<th>ActiveRecord adapters</th>
|
20
|
+
<td></td>
|
21
|
+
<td></td>
|
22
|
+
<tr>
|
23
|
+
<td>mysql</td>
|
24
|
+
<td>OK</td>
|
25
|
+
<td>OK</td>
|
26
|
+
</tr>
|
27
|
+
<tr>
|
28
|
+
<td>mysql2</td>
|
29
|
+
<td>OK</td>
|
30
|
+
<td>Unsupported</td>
|
31
|
+
</tr>
|
32
|
+
<tr>
|
33
|
+
<td>postgresql</td>
|
34
|
+
<td>OK</td>
|
35
|
+
<td>OK</td>
|
36
|
+
</tr>
|
37
|
+
<tr>
|
38
|
+
<td>sqlserver</td>
|
39
|
+
<td>Work in progress</td>
|
40
|
+
<td>Broken</td>
|
41
|
+
</tr>
|
42
|
+
<tr>
|
43
|
+
<th>ActiveRecord/ActiveSupport versions</th>
|
44
|
+
<td>>= 3</td>
|
45
|
+
<td>< 3</td>
|
46
|
+
</tr>
|
47
|
+
<tr>
|
48
|
+
<th>Ruby versions</th>
|
49
|
+
<td></td>
|
50
|
+
<td></td>
|
51
|
+
</tr>
|
52
|
+
<tr>
|
53
|
+
<td>MRI 1.9.3</td>
|
54
|
+
<td>OK</td>
|
55
|
+
<td>Untested</td>
|
56
|
+
</tr>
|
57
|
+
<tr>
|
58
|
+
<td>MRI 1.8.7</td>
|
59
|
+
<td>OK</td>
|
60
|
+
<td>Should be OK</td>
|
61
|
+
</tr>
|
62
|
+
</table>
|
8
63
|
|
9
|
-
|
64
|
+
### Important notes on MySQL support
|
10
65
|
|
11
|
-
|
66
|
+
#### Security warning
|
12
67
|
|
13
|
-
|
14
|
-
rake test
|
15
|
-
|
16
|
-
One test should fail with 'known issue with MySQL' (see commit 75da4b08).
|
68
|
+
Be sure to first read and understand the [security implications](http://dev.mysql.com/doc/refman/5.0/en/load-data-local.html) of `LOAD DATA LOCAL INFILE`. In particular, having this enabled on a web app is probably not a good idea.
|
17
69
|
|
18
|
-
####
|
70
|
+
#### v0.9.5
|
19
71
|
|
20
|
-
|
72
|
+
v0.9.5 will always use `LOAD DATA LOCAL INFILE` for bulk load - this is not configurable.
|
21
73
|
|
22
|
-
|
23
|
-
|
74
|
+
#### v1.0.0.rc1
|
75
|
+
|
76
|
+
This version should by default use `LOAD DATA INFILE` for safer defaults.
|
77
|
+
|
78
|
+
You can override this by passing `:local_infile => true` though.
|
79
|
+
|
80
|
+
#### Troubleshooting LOCAL INFILE
|
81
|
+
|
82
|
+
If you enable `:local_infile => true` and meet the following error, then you'll have to work it around.
|
83
|
+
|
84
|
+
```
|
85
|
+
The used command is not allowed with this MySQL version: LOAD DATA LOCAL INFILE
|
86
|
+
```
|
87
|
+
|
88
|
+
For mysql2 with AR >= 3.1:
|
89
|
+
|
90
|
+
- use [this fork](https://github.com/activewarehouse/mysql2) until [this pull-request](https://github.com/brianmario/mysql2/pull/242) is merged
|
91
|
+
- add `local_infile: true` to your database.yml
|
92
|
+
|
93
|
+
For mysql2 with AR < 3.1:
|
94
|
+
|
95
|
+
- no current easy solution afaik
|
96
|
+
|
97
|
+
For mysql:
|
98
|
+
|
99
|
+
- try this [untested patch](https://github.com/activewarehouse/adapter_extensions/issues/7)
|
100
|
+
|
101
|
+
### Notes on SQLServer support
|
102
|
+
|
103
|
+
v0.9.5 had a broken support for SQLServer.
|
104
|
+
|
105
|
+
v1.0.0.rc1 has a work-in-progress bulk import using `freebcp`. More tweaking needed.
|
106
|
+
|
107
|
+
### Running the tests
|
108
|
+
|
109
|
+
You may have to tweak the Rakefile and database.yml a bit, but roughly:
|
110
|
+
|
111
|
+
```
|
112
|
+
rake ci:create_db
|
113
|
+
rake "ci:run_one[mysql2,test/config/gemfiles/Gemfile.rails-3.2.x]"
|
114
|
+
```
|
115
|
+
|
116
|
+
You can also run a matrix of tests using:
|
117
|
+
|
118
|
+
```
|
119
|
+
rake ci:run_matrix
|
120
|
+
```
|
121
|
+
|
122
|
+
### License
|
123
|
+
|
124
|
+
MIT
|
125
|
+
|
126
|
+
### Contributors
|
127
|
+
|
128
|
+
* Thibaut Barrère (current maintainer)
|
129
|
+
* original code by Anthony Eden and probably others (let me know if you read this!)
|
130
|
+
* thanks to [Zach Dennis](https://github.com/zdennis/activerecord-import) for his work where I borrowed ideas for the rails 3 rewrite
|
data/Rakefile
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
2
3
|
require 'rake'
|
3
4
|
require 'rake/testtask'
|
4
|
-
require 'rdoc'
|
5
|
-
require 'rdoc/task'
|
6
5
|
|
7
6
|
desc 'Default: run unit tests.'
|
8
7
|
task :default => :test
|
@@ -15,63 +14,49 @@ Rake::TestTask.new(:test) do |t|
|
|
15
14
|
# TODO: reset the database
|
16
15
|
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
rm_f 'coverage.data'
|
22
|
-
mkdir 'coverage' unless File.exist?('coverage')
|
23
|
-
rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
|
24
|
-
system("#{rcov} test/*_test.rb test/**/*_test.rb")
|
25
|
-
system("open coverage/index.html") if PLATFORM['darwin']
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
desc 'Generate documentation for the AdapterExtensions library.'
|
30
|
-
Rake::RDocTask.new(:rdoc) do |rdoc|
|
31
|
-
rdoc.rdoc_dir = 'rdoc'
|
32
|
-
rdoc.title = 'Extensions for Rails adapters'
|
33
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
34
|
-
rdoc.rdoc_files.include('README')
|
35
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
|
+
def system!(cmd)
|
18
|
+
puts cmd
|
19
|
+
raise "Command failed!" unless system(cmd)
|
36
20
|
end
|
37
21
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
22
|
+
# experimental tasks to reproduce the Travis behaviour locally
|
23
|
+
namespace :ci do
|
24
|
+
|
25
|
+
desc "Create required databases for tests (db in [mysql, mysql2, postgresql])"
|
26
|
+
task :create_db, :db do |t, args|
|
27
|
+
db = args[:db] || ENV['DB']
|
28
|
+
case db
|
29
|
+
when /mysql/;
|
30
|
+
# TODO - extract this info from database.yml
|
31
|
+
system! "mysql -e 'create database adapter_extensions_test;'"
|
32
|
+
system! "mysql adapter_extensions_test < test/config/databases/mysql_setup.sql"
|
33
|
+
when /postgres/;
|
34
|
+
system! "psql -c 'create database adapter_extensions_test;' -U postgres"
|
35
|
+
system! "psql -d adapter_extensions_test -U postgres -f test/config/databases/postgresql_setup.sql"
|
36
|
+
else abort("I don't know how to create the database for DB=#{db}!")
|
51
37
|
end
|
52
|
-
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
53
|
-
|
54
|
-
total_lines += lines
|
55
|
-
total_codelines += codelines
|
56
|
-
|
57
|
-
lines, codelines = 0, 0
|
58
38
|
end
|
59
39
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
40
|
+
desc "For current RVM, run the tests for one db and one gemfile"
|
41
|
+
task :run_one, :db, :gemfile do |t, args|
|
42
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path(args[:gemfile] || (File.dirname(__FILE__) + '/test/config/gemfiles/Gemfile.rails-3.2.x'))
|
43
|
+
ENV['DB'] = args[:db] || 'mysql2'
|
44
|
+
system! "bundle install && bundle exec rake"
|
45
|
+
end
|
66
46
|
|
67
|
-
for
|
68
|
-
|
69
|
-
|
70
|
-
|
47
|
+
desc "For current RVM, run the tests for all the combination in travis configuration"
|
48
|
+
task :run_matrix do
|
49
|
+
require 'cartesian'
|
50
|
+
config = YAML.load_file('.travis.yml')
|
51
|
+
config['env'].cartesian(config['gemfile']).each do |*x|
|
52
|
+
env, gemfile = *x.flatten
|
53
|
+
db = env.gsub('DB=', '')
|
54
|
+
print [db, gemfile].inspect.ljust(40) + ": "
|
55
|
+
cmd = "rake \"ci:run_one[#{db},#{gemfile}]\""
|
56
|
+
result = system "#{cmd} > /dev/null 2>&1"
|
57
|
+
result = result ? "OK" : "FAILED! - re-run with: #{cmd}"
|
58
|
+
puts result
|
59
|
+
end
|
71
60
|
end
|
72
|
-
end
|
73
61
|
|
74
|
-
desc "Publish the API documentation (UNTESTED CURRENTLY)"
|
75
|
-
task :pdoc => [:rdoc] do
|
76
|
-
Rake::SshDirPublisher.new("aeden@rubyforge.org", "/var/www/gforge-projects/activewarehouse/adapter_extensions/rdoc", "rdoc").upload
|
77
62
|
end
|
data/adapter_extensions.gemspec
CHANGED
@@ -18,8 +18,10 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.required_rubygems_version = ">= 1.3.6"
|
19
19
|
|
20
20
|
s.add_runtime_dependency('rake', '>= 0.8.3')
|
21
|
-
s.add_runtime_dependency('activesupport', '>=
|
22
|
-
s.add_runtime_dependency('activerecord', '>=
|
21
|
+
s.add_runtime_dependency('activesupport', '>= 3.0.0')
|
22
|
+
s.add_runtime_dependency('activerecord', '>= 3.0.0')
|
23
|
+
s.add_development_dependency('flexmock')
|
24
|
+
s.add_development_dependency('cartesian')
|
23
25
|
|
24
26
|
s.files = `git ls-files`.split("\n")
|
25
27
|
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module AdapterExtensions::AbstractAdapter
|
2
|
+
|
3
|
+
# Truncate the specified table - allow to pass an optional string
|
4
|
+
# to let the called add extra parameters like RESET IDENTITY for pg
|
5
|
+
def truncate(table_name, options=nil)
|
6
|
+
statement = [
|
7
|
+
'TRUNCATE TABLE',
|
8
|
+
table_name,
|
9
|
+
options
|
10
|
+
].compact.join(' ')
|
11
|
+
execute(statement)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Bulk loading interface. Load the data from the specified file into the
|
15
|
+
# given table. Note that options will be adapter-dependent.
|
16
|
+
def bulk_load(file, table_name, options={})
|
17
|
+
raise ArgumentError, "#{file} does not exist" unless File.exist?(file)
|
18
|
+
raise ArgumentError, "#{table_name} does not exist" unless tables.include?(table_name)
|
19
|
+
do_bulk_load(file, table_name, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# SQL select into statement constructs a new table from the results
|
23
|
+
# of a select. It is used to select data from a table and create a new
|
24
|
+
# table with its result set at the same time. Note that this method
|
25
|
+
# name does not necessarily match the implementation. E.g. MySQL's
|
26
|
+
# version of this is 'CREATE TABLE ... AS SELECT ...'
|
27
|
+
def support_select_into_table?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add a chunk of SQL to the given query that will create a new table and
|
32
|
+
# execute the select into that table.
|
33
|
+
def add_select_into_table(new_table_name, sql_query)
|
34
|
+
raise NotImplementedError, "add_select_into_table is an abstract method"
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# for subclasses to implement
|
40
|
+
def do_bulk_load(file, table_name, options={})
|
41
|
+
raise NotImplementedError, "do_bulk_load is an abstract method"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module AdapterExtensions::MysqlAdapter
|
2
|
+
|
3
|
+
def support_select_into_table?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
# Inserts an INTO table_name clause to the sql_query.
|
8
|
+
def add_select_into_table(new_table_name, sql_query)
|
9
|
+
"CREATE TABLE #{new_table_name} " + sql_query
|
10
|
+
end
|
11
|
+
|
12
|
+
# Copy the specified table.
|
13
|
+
def copy_table(old_table_name, new_table_name)
|
14
|
+
transaction do
|
15
|
+
execute "CREATE TABLE #{new_table_name} LIKE #{old_table_name}"
|
16
|
+
execute "INSERT INTO #{new_table_name} SELECT * FROM #{old_table_name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def disable_keys(table)
|
21
|
+
execute("ALTER TABLE #{table} DISABLE KEYS")
|
22
|
+
end
|
23
|
+
|
24
|
+
def enable_keys(table)
|
25
|
+
execute("ALTER TABLE #{table} ENABLE KEYS")
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_keys_disabled(table)
|
29
|
+
disable_keys(table)
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
enable_keys(table)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
# Call +bulk_load+, as that method wraps this method.
|
37
|
+
#
|
38
|
+
# Bulk load the data in the specified file.
|
39
|
+
#
|
40
|
+
# Options:
|
41
|
+
# * <tt>:ignore</tt> -- Ignore the specified number of lines from the source file
|
42
|
+
# * <tt>:columns</tt> -- Array of column names defining the source file column order
|
43
|
+
# * <tt>:fields</tt> -- Hash of options for fields:
|
44
|
+
# * <tt>:delimited_by</tt> -- The field delimiter
|
45
|
+
# * <tt>:enclosed_by</tt> -- The field enclosure
|
46
|
+
# * <tt>:replace</tt> -- Add in REPLACE to LOAD DATA INFILE command
|
47
|
+
# * <tt>:disable_keys</tt> -- if set to true, disable keys, loads, then enables again
|
48
|
+
# * <tt>:local_infile</tt>::
|
49
|
+
# if set to true, use LOAD DATA LOCAL INFILE rather than LOAD DATA INFILE
|
50
|
+
# see http://dev.mysql.com/doc/refman/5.0/en/load-data-local.html for security issues with this
|
51
|
+
def do_bulk_load(file, table_name, options={})
|
52
|
+
return if File.size(file) == 0
|
53
|
+
|
54
|
+
replace = options[:replace] ? 'REPLACE' : ''
|
55
|
+
local = options[:local_infile] ? 'LOCAL' : ''
|
56
|
+
q = "LOAD DATA #{local} INFILE '#{file}' #{replace} INTO TABLE #{table_name}"
|
57
|
+
if options[:fields]
|
58
|
+
q << " FIELDS"
|
59
|
+
q << " TERMINATED BY '#{options[:fields][:delimited_by]}'" if options[:fields][:delimited_by]
|
60
|
+
q << " ENCLOSED BY '#{options[:fields][:enclosed_by]}'" if options[:fields][:enclosed_by]
|
61
|
+
end
|
62
|
+
q << " IGNORE #{options[:ignore]} LINES" if options[:ignore]
|
63
|
+
q << " (#{options[:columns].map { |c| quote_column_name(c.to_s) }.join(',')})" if options[:columns]
|
64
|
+
|
65
|
+
if options[:disable_keys]
|
66
|
+
with_keys_disabled(table_name) { execute(q) }
|
67
|
+
else
|
68
|
+
execute(q)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module AdapterExtensions::PostgreSQLAdapter
|
2
|
+
|
3
|
+
def support_select_into_table?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
# Inserts an INTO table_name clause to the sql_query.
|
8
|
+
def add_select_into_table(new_table_name, sql_query)
|
9
|
+
sql_query.sub(/FROM/i, "INTO #{new_table_name} FROM")
|
10
|
+
end
|
11
|
+
|
12
|
+
# Copy the specified table.
|
13
|
+
def copy_table(old_table_name, new_table_name)
|
14
|
+
execute add_select_into_table(new_table_name, "SELECT * FROM #{old_table_name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
# Call +bulk_load+, as that method wraps this method.
|
19
|
+
#
|
20
|
+
# Bulk load the data in the specified file.
|
21
|
+
#
|
22
|
+
# Options:
|
23
|
+
# * <tt>:ignore</tt> -- Ignore the specified number of lines from the source file. In the case of PostgreSQL
|
24
|
+
# only the first line will be ignored from the source file regardless of the number of lines specified.
|
25
|
+
# * <tt>:columns</tt> -- Array of column names defining the source file column order
|
26
|
+
# * <tt>:fields</tt> -- Hash of options for fields:
|
27
|
+
# * <tt>:delimited_by</tt> -- The field delimiter
|
28
|
+
# * <tt>:null_string</tt> -- The string that should be interpreted as NULL (in addition to \N)
|
29
|
+
# * <tt>:enclosed_by</tt> -- The field enclosure
|
30
|
+
def do_bulk_load(file, table_name, options={})
|
31
|
+
q = "COPY #{table_name} "
|
32
|
+
q << "(#{options[:columns].join(',')}) " if options[:columns]
|
33
|
+
q << "FROM '#{File.expand_path(file)}' "
|
34
|
+
if options[:fields]
|
35
|
+
q << "WITH "
|
36
|
+
q << "DELIMITER '#{options[:fields][:delimited_by]}' " if options[:fields][:delimited_by]
|
37
|
+
q << "NULL '#{options[:fields][:null_string]}'" if options[:fields][:null_string]
|
38
|
+
if options[:fields][:enclosed_by] || options[:ignore] && options[:ignore] > 0
|
39
|
+
q << "CSV "
|
40
|
+
q << "HEADER " if options[:ignore] && options[:ignore] > 0
|
41
|
+
q << "QUOTE '#{options[:fields][:enclosed_by]}' " if options[:fields][:enclosed_by]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
execute(q)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module AdapterExtensions::SQLServerAdapter
|
2
|
+
|
3
|
+
def support_select_into_table?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
# Inserts an INTO table_name clause to the sql_query.
|
8
|
+
def add_select_into_table(new_table_name, sql_query)
|
9
|
+
sql_query.sub(/FROM/i, "INTO #{new_table_name} FROM")
|
10
|
+
end
|
11
|
+
|
12
|
+
# Copy the specified table.
|
13
|
+
def copy_table(old_table_name, new_table_name)
|
14
|
+
execute add_select_into_table(new_table_name, "SELECT * FROM #{old_table_name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
# Call +bulk_load+, as that method wraps this method.
|
19
|
+
#
|
20
|
+
# Bulk load the data in the specified file. This implementation relies
|
21
|
+
# on freebcp being in your PATH.
|
22
|
+
#
|
23
|
+
# Currently supported options:
|
24
|
+
# * <tt>:bin</tt> -- alternate path to freebcp
|
25
|
+
# * <tt>:max_errors</tt> -- maximum number of errors (freebcp -m parameter)
|
26
|
+
# * <tt>:env</tt> -- override ActiveRecord environment to be used
|
27
|
+
# * <tt>:fields</tt> -- Hash of options for fields:
|
28
|
+
# * <tt>:delimited_by</tt> -- The field delimiter
|
29
|
+
# :columns is currently unsupported and will raise an exception
|
30
|
+
def do_bulk_load(filename, table_name, options={})
|
31
|
+
env_name = options[:env] || Rails.env
|
32
|
+
config = ActiveRecord::Base.configurations[env_name]
|
33
|
+
|
34
|
+
raise NotImplementedError.new(":columns option is not currently supported") if options[:columns]
|
35
|
+
|
36
|
+
# work in progress.
|
37
|
+
# - http://linux.die.net/man/1/freebcp
|
38
|
+
# - http://stackoverflow.com/a/924943/20302
|
39
|
+
# - http://msdn.microsoft.com/en-us/library/ms162802.aspx
|
40
|
+
# - http://www.dbforums.com/microsoft-sql-server/1624618-how-bring-out-column-names-bcp.html
|
41
|
+
|
42
|
+
command = []
|
43
|
+
command << (options[:bin] || 'freebcp')
|
44
|
+
command << "\"#{config['database']}.dbo.#{table_name}\""
|
45
|
+
command << "in \"#{filename}\""
|
46
|
+
command << "-S \"#{config['host']}\""
|
47
|
+
command << "-U \"#{config['username']}\""
|
48
|
+
command << "-P \"#{config['password']}\""
|
49
|
+
command << "-c" # character mode
|
50
|
+
command << "-t \"#{options[:fields][:delimited_by]}\"" if options[:fields] && options[:fields][:delimited_by]
|
51
|
+
command << "-b 10000" # bulk size
|
52
|
+
|
53
|
+
command << "-m #{options[:max_errors]}" if options[:max_errors]
|
54
|
+
command << "-e \"#{filename}.in.errors\""
|
55
|
+
command = command.join(' ')
|
56
|
+
|
57
|
+
# left-overs from legacy bcp call - must see if they must remain or not
|
58
|
+
# -a8192 -q -E
|
59
|
+
|
60
|
+
# TODO - raise a better exception here
|
61
|
+
raise "bulk load failed!" unless Kernel.system(command)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "active_record"
|
3
|
+
require "active_record/version"
|
4
|
+
|
5
|
+
module AdapterExtensions
|
6
|
+
AdapterPath = File.join File.expand_path(File.dirname(__FILE__)), "/active_record/adapters"
|
7
|
+
|
8
|
+
# Loads the extensions for a specific database adapter
|
9
|
+
def self.require_adapter(adapter)
|
10
|
+
require File.join(AdapterPath,"/abstract_adapter")
|
11
|
+
specific_adapter = File.join(AdapterPath,"/#{adapter}_adapter")
|
12
|
+
require specific_adapter if File.exists?(specific_adapter + '.rb')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load_from_connection_pool(connection_pool)
|
16
|
+
require_adapter connection_pool.spec.config[:adapter]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/adapter_extensions.rb
CHANGED
@@ -1,10 +1,19 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Requiring this file will require all of the necessary files to function.
|
1
|
+
require 'adapter_extensions/base'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
class ActiveRecord::Base
|
4
|
+
class << self
|
5
|
+
# for 1.8.7, modules loaded more than once that use alias_method_chain will
|
6
|
+
# cause stack level too deep errors - make sure we avoid this
|
7
|
+
unless self.instance_methods.include?('establish_connection_without_adapter_extensions')
|
8
|
+
def establish_connection_with_adapter_extensions(*args)
|
9
|
+
establish_connection_without_adapter_extensions(*args)
|
10
|
+
ActiveSupport.run_load_hooks(:active_record_connection_established, connection_pool)
|
11
|
+
end
|
12
|
+
alias_method_chain :establish_connection, :adapter_extensions
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
8
16
|
|
9
|
-
|
10
|
-
|
17
|
+
ActiveSupport.on_load(:active_record_connection_established) do |connection_pool|
|
18
|
+
AdapterExtensions.load_from_connection_pool connection_pool
|
19
|
+
end
|