Skip to content

Commit

Permalink
Merge pull request #3 from localytics/rails4
Browse files Browse the repository at this point in the history
Rails 4.2 support
  • Loading branch information
kddnewton authored Jan 23, 2017
2 parents 8f6d4d7 + 2723ed9 commit 09abd58
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 261 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ source 'https://rubygems.org'

gemspec

gem 'activerecord', '3.2.22.1'
gem 'activerecord', '4.2.7.1'
gem 'pry', '0.10.4'
57 changes: 57 additions & 0 deletions lib/active_record/connection_adapters/odbc_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class ODBCAdapter < AbstractAdapter
include ::ODBCAdapter::SchemaStatements

ADAPTER_NAME = 'ODBC'.freeze
ERR_DUPLICATE_KEY_VALUE = 23505

attr_reader :dbms

Expand Down Expand Up @@ -115,13 +116,69 @@ def reconnect!
else
ODBC::Database.new.drvconnect(options[:driver])
end
super
end
alias :reset! :reconnect!

# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
@connection.disconnect if @connection.connected?
end

protected

def initialize_type_map(map)
map.register_type ODBC::SQL_BIT, Type::Boolean.new
map.register_type ODBC::SQL_CHAR, Type::String.new
map.register_type ODBC::SQL_LONGVARCHAR, Type::Text.new
map.register_type ODBC::SQL_TINYINT, Type::Integer.new(limit: 4)
map.register_type ODBC::SQL_SMALLINT, Type::Integer.new(limit: 8)
map.register_type ODBC::SQL_INTEGER, Type::Integer.new(limit: 16)
map.register_type ODBC::SQL_BIGINT, Type::BigInteger.new(limit: 32)
map.register_type ODBC::SQL_REAL, Type::Float.new(limit: 24)
map.register_type ODBC::SQL_FLOAT, Type::Float.new
map.register_type ODBC::SQL_DOUBLE, Type::Float.new(limit: 53)
map.register_type ODBC::SQL_DECIMAL, Type::Float.new
map.register_type ODBC::SQL_NUMERIC, Type::Integer.new
map.register_type ODBC::SQL_BINARY, Type::Binary.new
map.register_type ODBC::SQL_DATE, Type::Date.new
map.register_type ODBC::SQL_DATETIME, Type::DateTime.new
map.register_type ODBC::SQL_TIME, Type::Time.new
map.register_type ODBC::SQL_TIMESTAMP, Type::DateTime.new
map.register_type ODBC::SQL_GUID, Type::String.new

alias_type map, ODBC::SQL_VARCHAR, ODBC::SQL_CHAR
alias_type map, ODBC::SQL_WCHAR, ODBC::SQL_CHAR
alias_type map, ODBC::SQL_WVARCHAR, ODBC::SQL_CHAR
alias_type map, ODBC::SQL_WLONGVARCHAR, ODBC::SQL_LONGVARCHAR
alias_type map, ODBC::SQL_VARBINARY, ODBC::SQL_BINARY
alias_type map, ODBC::SQL_LONGVARBINARY, ODBC::SQL_BINARY
alias_type map, ODBC::SQL_TYPE_DATE, ODBC::SQL_DATE
alias_type map, ODBC::SQL_TYPE_TIME, ODBC::SQL_TIME
alias_type map, ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP
end

def translate_exception(exception, message)
case exception.message[/^\d+/].to_i
when ERR_DUPLICATE_KEY_VALUE
ActiveRecord::RecordNotUnique.new(message, exception)
else
super
end
end

def new_column(name, default, cast_type, sql_type = nil, null = true, native_type = nil, scale = nil, limit = nil)
::ODBCAdapter::Column.new(name, default, cast_type, sql_type, null, native_type, scale, limit)
end

private

def alias_type(map, new_type, old_type)
map.register_type(new_type) do |_, *args|
map.lookup(old_type, *args)
end
end
end
end
end
10 changes: 7 additions & 3 deletions lib/odbc_adapter/adapters/mysql_odbc_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class BindSubstitution < Arel::Visitors::MySQL

PRIMARY_KEY = 'INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'

def truncate(table_name, name = nil)
execute("TRUNCATE TABLE #{quote_table_name(table_name)}", name)
end

def limited_update_conditions(where_sql, _quoted_table_name, _quoted_primary_key)
where_sql
end
Expand Down Expand Up @@ -98,7 +102,7 @@ def change_column_default(table_name, column_name, default)

def rename_column(table_name, column_name, new_column_name)
col = columns(table_name).detect { |c| c.name == column_name.to_s }
current_type = col.sql_type
current_type = col.native_type
current_type << "(#{col.limit})" if col.limit
execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}")
end
Expand All @@ -111,7 +115,7 @@ def indexes(table_name, name = nil)
def options_include_default?(options)
# MySQL 5.x doesn't allow DEFAULT NULL for first timestamp column in a table
if options.include?(:default) && options[:default].nil?
if options.include?(:column) && options[:column].sql_type =~ /timestamp/i
if options.include?(:column) && options[:column].native_type =~ /timestamp/i
options.delete(:default)
end
end
Expand All @@ -134,7 +138,7 @@ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
end

def last_inserted_id(_result)
@connection.last_id
select_value('SELECT LAST_INSERT_ID()').to_i
end
end
end
Expand Down
53 changes: 4 additions & 49 deletions lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,18 @@ class BindSubstitution < Arel::Visitors::PostgreSQL
include Arel::Visitors::BindVisitor
end

class PostgreSQLColumn < Column
def initialize(name, default, sql_type, native_type, null = true, scale = nil, native_types = nil, limit = nil)
super
@default = extract_default
end

private

def extract_default
case @default
when NilClass
nil
# Numeric types
when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ then $1
# Character types
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m then $1
# Binary data types
when /\A'(.*)'::bytea\z/m then $1
# Date/time types
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ then $1
when /\A'(.*)'::interval\z/ then $1
# Boolean type
when 'true' then true
when 'false' then false
# Geometric types
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ then $1
# Network address types
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ then $1
# Bit string types
when /\AB'(.*)'::"?bit(?: varying)?"?\z/ then $1
# XML type
when /\A'(.*)'::xml\z/m then $1
# Arrays
when /\A'(.*)'::"?\D+"?\[\]\z/ then $1
# Object identifier types
when /\A-?\d+\z/ then $1
else
# Anything else is blank, some user type, or some function
# and we can't know the value of that, so return nil.
nil
end
end
end

PRIMARY_KEY = 'SERIAL PRIMARY KEY'

# Override the default column class
def column_class
PostgreSQLColumn
end

# Filter for ODBCAdapter#tables
# Omits table from #tables if table_filter returns true
def table_filter(schema_name, table_type)
%w[information_schema pg_catalog].include?(schema_name) || table_type !~ /TABLE/i
end

def truncate(table_name, name = nil)
exec_query("TRUNCATE TABLE #{quote_table_name(table_name)}", name)
end

# Returns the sequence name for a table's primary key or some other specified key.
def default_sequence_name(table_name, pk = nil) #:nodoc:
serial_sequence(table_name, pk || 'id').split('.').last
Expand Down
66 changes: 12 additions & 54 deletions lib/odbc_adapter/column.rb
Original file line number Diff line number Diff line change
@@ -1,67 +1,25 @@
module ODBCAdapter
class Column < ActiveRecord::ConnectionAdapters::Column
def initialize(name, default, sql_type, native_type, null = true, scale = nil, native_types = nil, limit = nil)
attr_reader :native_type

def initialize(name, default, cast_type, sql_type, null, native_type, scale, limit)
@name = name
@default = default
@sql_type = native_type.to_s
@native_type = native_type.to_s
@cast_type = cast_type
@sql_type = sql_type
@null = null
@precision = extract_precision(sql_type, limit)
@scale = extract_scale(sql_type, scale)
@type = genericize(sql_type, @scale, native_types)
@primary = nil
end

private
@native_type = native_type

# Maps an ODBC SQL type to an ActiveRecord abstract data type
#
# c.f. Mappings in ConnectionAdapters::Column#simplified_type based on
# native column type declaration
#
# See also:
# Column#klass (schema_definitions.rb) for the Ruby class corresponding
# to each abstract data type.
def genericize(sql_type, scale, native_types)
case sql_type
when ODBC::SQL_BIT then :boolean
when ODBC::SQL_CHAR, ODBC::SQL_VARCHAR then :string
when ODBC::SQL_LONGVARCHAR then :text
when ODBC::SQL_WCHAR, ODBC::SQL_WVARCHAR then :string
when ODBC::SQL_WLONGVARCHAR then :text
when ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT, ODBC::SQL_INTEGER, ODBC::SQL_BIGINT then :integer
when ODBC::SQL_REAL, ODBC::SQL_FLOAT, ODBC::SQL_DOUBLE then :float
# If SQLGetTypeInfo output of ODBC driver doesn't include a mapping
# to a native type from SQL_DECIMAL/SQL_NUMERIC, map to :float
when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then numeric_type(scale, native_types)
when ODBC::SQL_BINARY, ODBC::SQL_VARBINARY, ODBC::SQL_LONGVARBINARY then :binary
# SQL_DATETIME is an alias for SQL_DATE in ODBC's sql.h & sqlext.h
when ODBC::SQL_DATE, ODBC::SQL_TYPE_DATE, ODBC::SQL_DATETIME then :date
when ODBC::SQL_TIME, ODBC::SQL_TYPE_TIME then :time
when ODBC::SQL_TIMESTAMP, ODBC::SQL_TYPE_TIMESTAMP then :timestamp
when ODBC::SQL_GUID then :string
else
# when SQL_UNKNOWN_TYPE
# (ruby-odbc driver doesn't support following ODBC SQL types:
# SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR, SQL_INTERVAL_xxx)
raise ArgumentError, "Unsupported ODBC SQL type [#{odbcSqlType}]"
if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(sql_type)
set_numeric_params(scale, limit)
end
end

# Ignore the ODBC precision of SQL types which don't take
# an explicit precision when defining a column
def extract_precision(sql_type, precision)
precision if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(sql_type)
end

# Ignore the ODBC scale of SQL types which don't take
# an explicit scale when defining a column
def extract_scale(sql_type, scale)
scale || 0 if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(sql_type)
end
private

def numeric_type(scale, native_types)
scale.nil? || scale == 0 ? :integer : (native_types[:decimal].nil? ? :float : :decimal)
def set_numeric_params(scale, limit)
@cast_type.instance_variable_set(:@scale, scale || 0)
@cast_type.instance_variable_set(:@precision, limit)
end
end
end
1 change: 0 additions & 1 deletion lib/odbc_adapter/column_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def initialize(adapter)
@adapter = adapter
end

# TODO: implement boolean column surrogates
def native_database_types
grouped = reported_types.group_by { |row| row[1] }

Expand Down
Loading

0 comments on commit 09abd58

Please sign in to comment.