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.
Files changed (38) hide show
  1. data/.travis.yml +14 -0
  2. data/CHANGELOG +5 -0
  3. data/HOW_TO_RELEASE +4 -4
  4. data/LICENSE +1 -1
  5. data/README.md +121 -14
  6. data/Rakefile +38 -53
  7. data/adapter_extensions.gemspec +4 -2
  8. data/lib/adapter_extensions/active_record/adapters/abstract_adapter.rb +9 -0
  9. data/lib/adapter_extensions/active_record/adapters/mysql2_adapter.rb +6 -0
  10. data/lib/adapter_extensions/active_record/adapters/mysql_adapter.rb +6 -0
  11. data/lib/adapter_extensions/active_record/adapters/postgresql_adapter.rb +6 -0
  12. data/lib/adapter_extensions/active_record/adapters/sqlserver_adapter.rb +6 -0
  13. data/lib/adapter_extensions/adapters/abstract_adapter.rb +44 -0
  14. data/lib/adapter_extensions/adapters/mysql_adapter.rb +73 -0
  15. data/lib/adapter_extensions/adapters/postgresql_adapter.rb +47 -0
  16. data/lib/adapter_extensions/adapters/sqlserver_adapter.rb +63 -0
  17. data/lib/adapter_extensions/base.rb +19 -0
  18. data/lib/adapter_extensions/version.rb +1 -1
  19. data/lib/adapter_extensions.rb +17 -8
  20. data/test/abstract_adapter_test.rb +10 -4
  21. data/test/config/database.yml +15 -0
  22. data/test/{connection/mysql/setup.sql → config/databases/mysql_setup.sql} +0 -0
  23. data/test/{connection/postgresql/setup.sql → config/databases/postgresql_setup.sql} +0 -0
  24. data/test/config/gemfiles/.gitignore +1 -0
  25. data/test/config/gemfiles/Gemfile.rails-3.0.x +3 -0
  26. data/test/config/gemfiles/Gemfile.rails-3.1.x +3 -0
  27. data/test/config/gemfiles/Gemfile.rails-3.2.x +3 -0
  28. data/test/config/gemfiles/common.rb +22 -0
  29. data/test/integration/adapter_test.rb +23 -7
  30. data/test/test_helper.rb +3 -4
  31. data/test/unit/sqlserver_test_ignored.rb +89 -0
  32. metadata +86 -88
  33. data/lib/adapter_extensions/connection_adapters/abstract_adapter.rb +0 -50
  34. data/lib/adapter_extensions/connection_adapters/mysql_adapter.rb +0 -90
  35. data/lib/adapter_extensions/connection_adapters/postgresql_adapter.rb +0 -52
  36. data/lib/adapter_extensions/connection_adapters/sqlserver_adapter.rb +0 -44
  37. data/test/connection/mysql/connection.rb +0 -27
  38. 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
- * then use bundler to build + git tag + push to rubygems
3
+ * build the gem:
4
4
 
5
- rake release
5
+ gem build adapter_extensions.gemspec
6
6
 
7
- * if you remain stuck at "Pushed git commits and tags", the task may silently wait for your password. Check this if it's the case:
7
+ * create tag and push to rubygem:
8
8
 
9
- https://github.com/carlhuda/bundler/issues/980
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
- This library provides extensions to Rails' ActiveRecord adapters.
1
+ AdapterExtensions add extra abilities to Rails ActiveRecord adapters, including:
2
2
 
3
- As of version 0.9.5, adapter_extensions has dependencies on ActiveSupport and ActiveRecord 2.1.x or higher.
3
+ * bulk load
4
+ * truncate table
5
+ * copy table
6
+ * add select into table
7
+
8
+ ### Compatibility matrix
4
9
 
5
- ### How to test
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
- Currently tested on MySQL and Postgres.
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>&gt;= 3</td>
45
+ <td>&lt; 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
- #### Testing on MySQL
64
+ ### Important notes on MySQL support
10
65
 
11
- see `test/connection/mysql/connection.rb` and tweek if needed, then:
66
+ #### Security warning
12
67
 
13
- mysql -u root -p -e "create database adapter_extensions_unittest"
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
- #### Testing on Postgresql
70
+ #### v0.9.5
19
71
 
20
- see `test/connection/postgresql/connection.rb` and tweek if needed, then:
72
+ v0.9.5 will always use `LOAD DATA LOCAL INFILE` for bulk load - this is not configurable.
21
73
 
22
- createdb adapter_extensions_unittest
23
- rake test DB=postgresql
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
- namespace :rcov do
19
- desc 'Measures test coverage'
20
- task :test do
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
- desc "Generate code statistics"
39
- task :lines do
40
- lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
41
-
42
- for file_name in FileList["lib/**/*.rb"]
43
- next if file_name =~ /vendor/
44
- f = File.open(file_name)
45
-
46
- while line = f.gets
47
- lines += 1
48
- next if line =~ /^\s*$/
49
- next if line =~ /^\s*#/
50
- codelines += 1
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
- puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
61
- end
62
-
63
- desc "Publish the release files to RubyForge (UNTESTED CURRENTLY)"
64
- task :release => [ :package ] do
65
- `rubyforge login`
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 ext in %w( gem tgz zip )
68
- release_command = "rubyforge add_release activewarehouse #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
69
- puts release_command
70
- system(release_command)
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
@@ -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', '>= 2.1.0')
22
- s.add_runtime_dependency('activerecord', '>= 2.1.0')
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,9 @@
1
+ require "adapter_extensions/adapters/abstract_adapter"
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ class AbstractAdapter # :nodoc:
6
+ include AdapterExtensions::AbstractAdapter
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/mysql2_adapter"
2
+ require "adapter_extensions/adapters/mysql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::Mysql2Adapter
5
+ include AdapterExtensions::MysqlAdapter
6
+ end
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/mysql_adapter"
2
+ require "adapter_extensions/adapters/mysql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::MysqlAdapter
5
+ include AdapterExtensions::MysqlAdapter
6
+ end
@@ -0,0 +1,6 @@
1
+ require "active_record/connection_adapters/postgresql_adapter"
2
+ require "adapter_extensions/adapters/postgresql_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
5
+ include AdapterExtensions::PostgreSQLAdapter
6
+ end
@@ -0,0 +1,6 @@
1
+ require "activerecord-sqlserver-adapter"
2
+ require "adapter_extensions/adapters/sqlserver_adapter"
3
+
4
+ class ActiveRecord::ConnectionAdapters::SQLServerAdapter
5
+ include AdapterExtensions::SQLServerAdapter
6
+ end
@@ -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
@@ -1,4 +1,4 @@
1
1
  # Source file identifying the version of AdapterExtensions in this package
2
2
  module AdapterExtensions#:nodoc:
3
- VERSION = "0.9.5"
3
+ VERSION = "1.0.0.rc1"
4
4
  end
@@ -1,10 +1,19 @@
1
- # Extensions to the Rails ActiveRecord adapters.
2
- #
3
- # Requiring this file will require all of the necessary files to function.
1
+ require 'adapter_extensions/base'
4
2
 
5
- require 'rubygems'
6
- require 'active_support'
7
- require 'active_record'
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
- $:.unshift(File.dirname(__FILE__))
10
- Dir[File.dirname(__FILE__) + "/adapter_extensions/**/*.rb"].each { |file| require(file) }
17
+ ActiveSupport.on_load(:active_record_connection_established) do |connection_pool|
18
+ AdapterExtensions.load_from_connection_pool connection_pool
19
+ end