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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +29 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +37 -0
- data/Rakefile +10 -0
- data/bin/ci-setup +13 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/active_record/connection_adapters/odbc_adapter.rb +127 -0
- data/lib/odbc_adapter.rb +17 -0
- data/lib/odbc_adapter/adapters/mysql_odbc_adapter.rb +141 -0
- data/lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb +230 -0
- data/lib/odbc_adapter/column.rb +67 -0
- data/lib/odbc_adapter/column_metadata.rb +77 -0
- data/lib/odbc_adapter/database_limits.rb +10 -0
- data/lib/odbc_adapter/database_statements.rb +254 -0
- data/lib/odbc_adapter/dbms.rb +50 -0
- data/lib/odbc_adapter/quoting.rb +81 -0
- data/lib/odbc_adapter/schema_statements.rb +16 -0
- data/lib/odbc_adapter/type_caster.rb +42 -0
- data/lib/odbc_adapter/version.rb +3 -0
- data/odbc_adapter.gemspec +28 -0
- metadata +123 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module ODBCAdapter
|
2
|
+
# Caches SQLGetInfo output
|
3
|
+
class DBMS
|
4
|
+
FIELDS = [
|
5
|
+
ODBC::SQL_DBMS_NAME,
|
6
|
+
ODBC::SQL_DBMS_VER,
|
7
|
+
ODBC::SQL_IDENTIFIER_CASE,
|
8
|
+
ODBC::SQL_QUOTED_IDENTIFIER_CASE,
|
9
|
+
ODBC::SQL_IDENTIFIER_QUOTE_CHAR,
|
10
|
+
ODBC::SQL_MAX_IDENTIFIER_LEN,
|
11
|
+
ODBC::SQL_MAX_TABLE_NAME_LEN,
|
12
|
+
ODBC::SQL_USER_NAME,
|
13
|
+
ODBC::SQL_DATABASE_NAME
|
14
|
+
]
|
15
|
+
|
16
|
+
attr_reader :fields
|
17
|
+
|
18
|
+
def initialize(connection)
|
19
|
+
@fields = Hash[FIELDS.map { |field| [field, connection.get_info(field)] }]
|
20
|
+
end
|
21
|
+
|
22
|
+
def adapter_class
|
23
|
+
return adapter unless adapter.is_a?(Symbol)
|
24
|
+
require "odbc_adapter/adapters/#{adapter.downcase}_odbc_adapter"
|
25
|
+
Adapters.const_get(:"#{adapter}ODBCAdapter")
|
26
|
+
end
|
27
|
+
|
28
|
+
def field_for(field)
|
29
|
+
fields[field]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Maps a DBMS name to a symbol
|
35
|
+
# Different ODBC drivers might return different names for the same DBMS
|
36
|
+
def adapter
|
37
|
+
@adapter ||=
|
38
|
+
begin
|
39
|
+
reported = field_for(ODBC::SQL_DBMS_NAME).downcase.gsub(/\s/, '')
|
40
|
+
found =
|
41
|
+
ODBCAdapter.dbms_registry.detect do |pattern, adapter|
|
42
|
+
adapter if reported =~ pattern
|
43
|
+
end
|
44
|
+
|
45
|
+
raise ArgumentError, "ODBCAdapter: Unsupported database (#{reported})" if found.nil?
|
46
|
+
found.last
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module ODBCAdapter
|
2
|
+
module Quoting
|
3
|
+
# Quotes the column value to help prevent
|
4
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
5
|
+
def quote(value, column = nil)
|
6
|
+
# records are quoted as their primary key
|
7
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
8
|
+
|
9
|
+
case value
|
10
|
+
when String, ActiveSupport::Multibyte::Chars
|
11
|
+
value = value.to_s
|
12
|
+
return "'#{quote_string(value)}'" unless column
|
13
|
+
|
14
|
+
case column.type
|
15
|
+
when :binary then "'#{quote_string(column.string_to_binary(value))}'"
|
16
|
+
when :integer then value.to_i.to_s
|
17
|
+
when :float then value.to_f.to_s
|
18
|
+
else
|
19
|
+
"'#{quote_string(value)}'"
|
20
|
+
end
|
21
|
+
|
22
|
+
when true, false
|
23
|
+
if column && column.type == :integer
|
24
|
+
value ? '1' : '0'
|
25
|
+
else
|
26
|
+
value ? quoted_true : quoted_false
|
27
|
+
end
|
28
|
+
# BigDecimals need to be put in a non-normalized form and quoted.
|
29
|
+
when nil then "NULL"
|
30
|
+
when BigDecimal then value.to_s('F')
|
31
|
+
when Numeric then value.to_s
|
32
|
+
when Symbol then "'#{quote_string(value.to_s)}'"
|
33
|
+
else
|
34
|
+
if value.acts_like?(:date) || value.acts_like?(:time)
|
35
|
+
quoted_date(value)
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Quotes a string, escaping any ' (single quote) characters.
|
43
|
+
def quote_string(string)
|
44
|
+
string.gsub(/\'/, "''")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a quoted form of the column name.
|
48
|
+
def quote_column_name(name)
|
49
|
+
name = name.to_s
|
50
|
+
quote_char = dbms.field_for(ODBC::SQL_IDENTIFIER_QUOTE_CHAR).to_s.strip
|
51
|
+
|
52
|
+
return name if quote_char.length.zero?
|
53
|
+
quote_char = quote_char[0]
|
54
|
+
|
55
|
+
# Avoid quoting any already quoted name
|
56
|
+
return name if name[0] == quote_char && name[-1] == quote_char
|
57
|
+
|
58
|
+
# If DBMS's SQL_IDENTIFIER_CASE = SQL_IC_UPPER, only quote mixed
|
59
|
+
# case names.
|
60
|
+
if dbms.field_for(ODBC::SQL_IDENTIFIER_CASE) == ODBC::SQL_IC_UPPER
|
61
|
+
return name unless (name =~ /([A-Z]+[a-z])|([a-z]+[A-Z])/)
|
62
|
+
end
|
63
|
+
|
64
|
+
"#{quote_char.chr}#{name}#{quote_char.chr}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def quoted_true
|
68
|
+
'1'
|
69
|
+
end
|
70
|
+
|
71
|
+
# Ideally, we'd return an ODBC date or timestamp literal escape
|
72
|
+
# sequence, but not all ODBC drivers support them.
|
73
|
+
def quoted_date(value)
|
74
|
+
if value.acts_like?(:time) # Time, DateTime
|
75
|
+
"'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
76
|
+
else # Date
|
77
|
+
"'#{value.strftime("%Y-%m-%d")}'"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ODBCAdapter
|
2
|
+
module SchemaStatements
|
3
|
+
# Returns a Hash of mappings from the abstract data types to the native
|
4
|
+
# database types. See TableDefinition#column for details on the recognized
|
5
|
+
# abstract data types.
|
6
|
+
def native_database_types
|
7
|
+
@native_database_types ||= ColumnMetadata.new(self).native_database_types
|
8
|
+
end
|
9
|
+
|
10
|
+
# Ensure it's shorter than the maximum identifier length for the current dbms
|
11
|
+
def index_name(table_name, options)
|
12
|
+
maximum = dbms.field_for(ODBC::SQL_MAX_IDENTIFIER_LEN) || 255
|
13
|
+
super(table_name, options)[0...maximum]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ODBCAdapter
|
2
|
+
class TypeCaster
|
3
|
+
# When fetching a result set, the Ruby ODBC driver converts all ODBC
|
4
|
+
# SQL types to an equivalent Ruby type; with the exception of
|
5
|
+
# SQL_DATE, SQL_TIME and SQL_TIMESTAMP.
|
6
|
+
TYPES = [
|
7
|
+
ODBC::SQL_DATE,
|
8
|
+
ODBC::SQL_TIME,
|
9
|
+
ODBC::SQL_TIMESTAMP
|
10
|
+
]
|
11
|
+
|
12
|
+
attr_reader :idx
|
13
|
+
|
14
|
+
def initialize(idx)
|
15
|
+
@idx = idx
|
16
|
+
end
|
17
|
+
|
18
|
+
def cast(value)
|
19
|
+
case value
|
20
|
+
when ODBC::TimeStamp
|
21
|
+
Time.gm(value.year, value.month, value.day, value.hour, value.minute, value.second)
|
22
|
+
when ODBC::Time
|
23
|
+
now = DateTime.now
|
24
|
+
Time.gm(now.year, now.month, now.day, value.hour, value.minute, value.second)
|
25
|
+
when ODBC::Date
|
26
|
+
Date.new(value.year, value.month, value.day)
|
27
|
+
else
|
28
|
+
value
|
29
|
+
end
|
30
|
+
rescue
|
31
|
+
# Handle pre-epoch dates
|
32
|
+
DateTime.new(value.year, value.month, value.day, value.hour, value.minute, value.second)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Build a list of casters from a list of columns
|
36
|
+
def self.build_from(columns)
|
37
|
+
columns.each_with_index.each_with_object([]) do |(column, idx), casters|
|
38
|
+
casters << new(idx) if TYPES.include?(column.type)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'odbc_adapter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'odbc_adapter'
|
8
|
+
spec.version = ODBCAdapter::VERSION
|
9
|
+
spec.authors = ['Localytics']
|
10
|
+
spec.email = ['oss@localytics.com']
|
11
|
+
|
12
|
+
spec.summary = 'An ActiveRecord ODBC adapter'
|
13
|
+
spec.homepage = 'https://github.com/localytics/odbc_adapter'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'ruby-odbc', '~> 0.9'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: odbc_adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Localytics
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-odbc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- oss@localytics.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/ci-setup
|
83
|
+
- bin/console
|
84
|
+
- bin/setup
|
85
|
+
- lib/active_record/connection_adapters/odbc_adapter.rb
|
86
|
+
- lib/odbc_adapter.rb
|
87
|
+
- lib/odbc_adapter/adapters/mysql_odbc_adapter.rb
|
88
|
+
- lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb
|
89
|
+
- lib/odbc_adapter/column.rb
|
90
|
+
- lib/odbc_adapter/column_metadata.rb
|
91
|
+
- lib/odbc_adapter/database_limits.rb
|
92
|
+
- lib/odbc_adapter/database_statements.rb
|
93
|
+
- lib/odbc_adapter/dbms.rb
|
94
|
+
- lib/odbc_adapter/quoting.rb
|
95
|
+
- lib/odbc_adapter/schema_statements.rb
|
96
|
+
- lib/odbc_adapter/type_caster.rb
|
97
|
+
- lib/odbc_adapter/version.rb
|
98
|
+
- odbc_adapter.gemspec
|
99
|
+
homepage: https://github.com/localytics/odbc_adapter
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.5.1
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: An ActiveRecord ODBC adapter
|
123
|
+
test_files: []
|