odbc_adapter 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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