odbc_adapter 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e903865d2eae4fef5a33f881da4d1890b7f2400c
4
+ data.tar.gz: e30af49c10265dcd7f189113b32ec59cf9d7267b
5
+ SHA512:
6
+ metadata.gz: aa920bf163813b47069442b75e1290b3d418910f92c01904f5a0976e5809b768950382a3b693482f7f49e11a3532598a4e01c9929ac976e0aba7996115923b28
7
+ data.tar.gz: b85c71c0d26ccd11075308bf745f8592565e2434cd9038a38748f57f2866583386ca3bcadf4901a134a854122e90967b30a1f942c637bfafb891bfa0dd3399e7
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,29 @@
1
+ sudo: required
2
+ language: ruby
3
+ cache: bundler
4
+ matrix:
5
+ include:
6
+ - rvm: 2.3.1
7
+ env:
8
+ - DB=mysql
9
+ - CONN_STR='DRIVER=MySQL;SERVER=localhost;DATABASE=odbc_test;USER=root;PASSWORD=;'
10
+ addons:
11
+ mysql: "5.5"
12
+ apt:
13
+ packages:
14
+ - unixodbc
15
+ - unixodbc-dev
16
+ - libmyodbc
17
+ - mysql-client
18
+ - rvm: 2.3.1
19
+ env:
20
+ - DB=postgresql
21
+ - CONN_STR='DRIVER={PostgreSQL ANSI};SERVER=localhost;PORT=5432;DATABASE=odbc_test;UID=postgres;'
22
+ addons:
23
+ postgresql: "9.1"
24
+ apt:
25
+ packages:
26
+ - unixodbc
27
+ - unixodbc-dev
28
+ - odbc-postgresql
29
+ before_script: bin/ci-setup
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'activerecord', '3.2.22.1'
6
+ gem 'pry', '0.10.4'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2017 Localytics http://www.localytics.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # ODBCAdapter
2
+
3
+ [![Build Status](https://travis-ci.com/localytics/odbc_adapter.svg?token=kQUiABmGkzyHdJdMnCnv&branch=master)](https://travis-ci.com/localytics/odbc_adapter)
4
+
5
+ An ActiveRecord ODBC adapter.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'odbc_adapter'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install odbc_adapter
22
+
23
+ ## Usage
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/localytics/odbc_adapter.
34
+
35
+ ## License
36
+
37
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
data/bin/ci-setup ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+
3
+ case "$DB" in
4
+ mysql)
5
+ sudo odbcinst -i -d -f /usr/share/libmyodbc/odbcinst.ini
6
+ mysql -e "DROP DATABASE IF EXISTS odbc_test; CREATE DATABASE IF NOT EXISTS odbc_test;" -uroot
7
+ mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost';" -uroot
8
+ ;;
9
+ postgresql)
10
+ sudo odbcinst -i -d -f /usr/share/psqlodbc/odbcinst.ini.template
11
+ psql -c "CREATE DATABASE odbc_test;" -U postgres
12
+ ;;
13
+ esac
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'odbc_adapter'
5
+
6
+ require 'irb'
7
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,127 @@
1
+ require 'active_record'
2
+ require 'arel/visitors/bind_visitor'
3
+ require 'odbc'
4
+
5
+ require 'odbc_adapter'
6
+ require 'odbc_adapter/database_limits'
7
+ require 'odbc_adapter/database_statements'
8
+ require 'odbc_adapter/quoting'
9
+ require 'odbc_adapter/schema_statements'
10
+
11
+ require 'odbc_adapter/column'
12
+ require 'odbc_adapter/column_metadata'
13
+ require 'odbc_adapter/dbms'
14
+ require 'odbc_adapter/type_caster'
15
+ require 'odbc_adapter/version'
16
+
17
+ module ActiveRecord
18
+ class Base
19
+ class << self
20
+ def odbc_connection(config) # :nodoc:
21
+ config = config.symbolize_keys
22
+
23
+ connection, options =
24
+ if config.key?(:dsn)
25
+ odbc_dsn_connection(config)
26
+ elsif config.key?(:conn_str)
27
+ odbc_conn_str_connection(config)
28
+ else
29
+ raise ArgumentError, "No data source name (:dsn) or connection string (:conn_str) specified."
30
+ end
31
+
32
+ dbms = ::ODBCAdapter::DBMS.new(connection)
33
+ dbms.adapter_class.new(connection, logger, dbms)
34
+ end
35
+
36
+ private
37
+
38
+ def odbc_dsn_connection(config)
39
+ username = config[:username] ? config[:username].to_s : nil
40
+ password = config[:password] ? config[:password].to_s : nil
41
+ connection = ODBC.connect(config[:dsn], username, password)
42
+ options = { dsn: config[:dsn], username: username, password: password }
43
+ [connection, options]
44
+ end
45
+
46
+ # Connect using ODBC connection string
47
+ # Supports DSN-based or DSN-less connections
48
+ # e.g. "DSN=virt5;UID=rails;PWD=rails"
49
+ # "DRIVER={OpenLink Virtuoso};HOST=carlmbp;UID=rails;PWD=rails"
50
+ def odbc_conn_str_connection(config)
51
+ connstr_keyval_pairs = config[:conn_str].split(';')
52
+
53
+ driver = ODBC::Driver.new
54
+ driver.name = 'odbc'
55
+ driver.attrs = {}
56
+
57
+ connstr_keyval_pairs.each do |pair|
58
+ keyval = pair.split('=')
59
+ driver.attrs[keyval[0]] = keyval[1] if keyval.length == 2
60
+ end
61
+
62
+ connection = ODBC::Database.new.drvconnect(driver)
63
+ options = { conn_str: config[:conn_str], driver: driver }
64
+ [connection, options]
65
+ end
66
+ end
67
+ end
68
+
69
+ module ConnectionAdapters
70
+ class ODBCAdapter < AbstractAdapter
71
+ include ::ODBCAdapter::DatabaseLimits
72
+ include ::ODBCAdapter::DatabaseStatements
73
+ include ::ODBCAdapter::Quoting
74
+ include ::ODBCAdapter::SchemaStatements
75
+
76
+ ADAPTER_NAME = 'ODBC'.freeze
77
+
78
+ attr_reader :dbms
79
+
80
+ def initialize(connection, logger, dbms)
81
+ super(connection, logger)
82
+ @connection = connection
83
+ @dbms = dbms
84
+ @visitor = self.class::BindSubstitution.new(self)
85
+ end
86
+
87
+ # Returns the human-readable name of the adapter. Use mixed case - one
88
+ # can always use downcase if needed.
89
+ def adapter_name
90
+ ADAPTER_NAME
91
+ end
92
+
93
+ # Does this adapter support migrations? Backend specific, as the
94
+ # abstract adapter always returns +false+.
95
+ def supports_migrations?
96
+ true
97
+ end
98
+
99
+ # CONNECTION MANAGEMENT ====================================
100
+
101
+ # Checks whether the connection to the database is still active. This includes
102
+ # checking whether the database is actually capable of responding, i.e. whether
103
+ # the connection isn't stale.
104
+ def active?
105
+ @connection.connected?
106
+ end
107
+
108
+ # Disconnects from the database if already connected, and establishes a
109
+ # new connection with the database.
110
+ def reconnect!
111
+ disconnect!
112
+ @connection =
113
+ if options.key?(:dsn)
114
+ ODBC.connect(options[:dsn], options[:username], options[:password])
115
+ else
116
+ ODBC::Database.new.drvconnect(options[:driver])
117
+ end
118
+ end
119
+
120
+ # Disconnects from the database if already connected. Otherwise, this
121
+ # method does nothing.
122
+ def disconnect!
123
+ @connection.disconnect if @connection.connected?
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,17 @@
1
+ # Requiring with this pattern to mirror ActiveRecord
2
+ require 'active_record/connection_adapters/odbc_adapter'
3
+
4
+ module ODBCAdapter
5
+ class << self
6
+ def dbms_registry
7
+ @dbms_registry ||= {
8
+ /my.*sql/i => :MySQL,
9
+ /postgres/i => :PostgreSQL
10
+ }
11
+ end
12
+
13
+ def register(pattern, superclass, &block)
14
+ dbms_registry[pattern] = Class.new(superclass, &block)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,141 @@
1
+ module ODBCAdapter
2
+ module Adapters
3
+ # Overrides specific to MySQL. Mostly taken from
4
+ # ActiveRecord::ConnectionAdapters::MySQLAdapter
5
+ class MySQLODBCAdapter < ActiveRecord::ConnectionAdapters::ODBCAdapter
6
+ class BindSubstitution < Arel::Visitors::MySQL
7
+ include Arel::Visitors::BindVisitor
8
+ end
9
+
10
+ PRIMARY_KEY = 'INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'
11
+
12
+ def limited_update_conditions(where_sql, _quoted_table_name, _quoted_primary_key)
13
+ where_sql
14
+ end
15
+
16
+ # Taken from ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
17
+ def join_to_update(update, select) #:nodoc:
18
+ if select.limit || select.offset || select.orders.any?
19
+ subsubselect = select.clone
20
+ subsubselect.projections = [update.key]
21
+
22
+ subselect = Arel::SelectManager.new(select.engine)
23
+ subselect.project Arel.sql(update.key.name)
24
+ subselect.from subsubselect.as('__active_record_temp')
25
+
26
+ update.where update.key.in(subselect)
27
+ else
28
+ update.table select.source
29
+ update.wheres = select.constraints
30
+ end
31
+ end
32
+
33
+ # Quotes a string, escaping any ' (single quote) and \ (backslash)
34
+ # characters.
35
+ def quote_string(string)
36
+ string.gsub(/\\/, '\&\&').gsub(/'/, "''")
37
+ end
38
+
39
+ def disable_referential_integrity(&block) #:nodoc:
40
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
41
+
42
+ begin
43
+ update("SET FOREIGN_KEY_CHECKS = 0")
44
+ yield
45
+ ensure
46
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
47
+ end
48
+ end
49
+
50
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
51
+ # Charset defaults to utf8.
52
+ #
53
+ # Example:
54
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
55
+ # create_database 'matt_development'
56
+ # create_database 'matt_development', :charset => :big5
57
+ def create_database(name, options = {})
58
+ if options[:collation]
59
+ execute("CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`")
60
+ else
61
+ execute("CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`")
62
+ end
63
+ end
64
+
65
+ # Drops a MySQL database.
66
+ #
67
+ # Example:
68
+ # drop_database('sebastian_development')
69
+ def drop_database(name) #:nodoc:
70
+ execute("DROP DATABASE IF EXISTS `#{name}`")
71
+ end
72
+
73
+ def create_table(name, options = {})
74
+ super(name, { options: 'ENGINE=InnoDB' }.merge(options))
75
+ end
76
+
77
+ # Renames a table.
78
+ def rename_table(name, new_name)
79
+ execute("RENAME TABLE #{quote_table_name(name)} TO #{quote_table_name(new_name)}")
80
+ end
81
+
82
+ def change_column(table_name, column_name, type, options = {})
83
+ # column_name.to_s used in case column_name is a symbol
84
+ unless options_include_default?(options)
85
+ options[:default] = columns(table_name).find { |c| c.name == column_name.to_s }.default
86
+ end
87
+
88
+ change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
89
+ add_column_options!(change_column_sql, options)
90
+ execute(change_column_sql)
91
+ end
92
+
93
+ def change_column_default(table_name, column_name, default)
94
+ col = columns(table_name).detect { |c| c.name == column_name.to_s }
95
+ change_column(table_name, column_name, col.type,
96
+ default: default, limit: col.limit, precision: col.precision, scale: col.scale)
97
+ end
98
+
99
+ def rename_column(table_name, column_name, new_column_name)
100
+ col = columns(table_name).detect { |c| c.name == column_name.to_s }
101
+ current_type = col.sql_type
102
+ current_type << "(#{col.limit})" if col.limit
103
+ execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}")
104
+ end
105
+
106
+ def indexes(table_name, name = nil)
107
+ # Skip primary key indexes
108
+ super(table_name, name).delete_if { |i| i.unique && i.name =~ /^PRIMARY$/ }
109
+ end
110
+
111
+ def options_include_default?(options)
112
+ # MySQL 5.x doesn't allow DEFAULT NULL for first timestamp column in a table
113
+ if options.include?(:default) && options[:default].nil?
114
+ if options.include?(:column) && options[:column].sql_type =~ /timestamp/i
115
+ options.delete(:default)
116
+ end
117
+ end
118
+ super(options)
119
+ end
120
+
121
+ def structure_dump
122
+ select_all("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'").map do |table|
123
+ table.delete('Table_type')
124
+ sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
125
+ exec_query(sql).first['Create Table'] + ";\n\n"
126
+ end.join
127
+ end
128
+
129
+ protected
130
+
131
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
132
+ super
133
+ id_value || last_inserted_id(nil)
134
+ end
135
+
136
+ def last_inserted_id(_result)
137
+ @connection.last_id
138
+ end
139
+ end
140
+ end
141
+ end