diff --git a/.rubocop.yml b/.rubocop.yml index 9055a997..28d2dcd7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ AllCops: DisplayCopNames: true DisplayStyleGuide: true - TargetRubyVersion: 2.1 + TargetRubyVersion: 2.2 Exclude: - 'vendor/**/*' diff --git a/Gemfile b/Gemfile index c0cf8f42..fa75df15 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,3 @@ source 'https://rubygems.org' gemspec - -gem 'activerecord', '5.0.1' -gem 'pry', '~> 0.11.1' diff --git a/README.md b/README.md index 0f5b5519..1b6945d9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # ODBCAdapter -[![Build Status](https://travis-ci.org/localytics/odbc_adapter.svg?branch=master)](https://travis-ci.org/localytics/odbc_adapter) -[![Gem](https://img.shields.io/gem/v/odbc_adapter.svg)](https://rubygems.org/gems/odbc_adapter) - -An ActiveRecord ODBC adapter. Master branch is working off of Rails 5.0.1. Previous work has been done to make it compatible with Rails 3.2 and 4.2; for those versions use the 3.2.x or 4.2.x gem releases. +An ActiveRecord ODBC adapter. Pattern has made some updates to get this working with Rails 6 as forked from repo was not actively being maintained to do so. This adapter will work for basic queries for most DBMSs out of the box, without support for migrations. Full support is built-in for MySQL 5 and PostgreSQL 9 databases. You can register your own adapter to get more support for your DBMS using the `ODBCAdapter.register` function. diff --git a/lib/active_record/connection_adapters/odbc_adapter.rb b/lib/active_record/connection_adapters/odbc_adapter.rb index 06686785..fe20d616 100644 --- a/lib/active_record/connection_adapters/odbc_adapter.rb +++ b/lib/active_record/connection_adapters/odbc_adapter.rb @@ -1,5 +1,4 @@ require 'active_record' -require 'arel/visitors/bind_visitor' require 'odbc' require 'odbc_utf8' @@ -75,6 +74,9 @@ class ODBCAdapter < AbstractAdapter ADAPTER_NAME = 'ODBC'.freeze BOOLEAN_TYPE = 'BOOLEAN'.freeze + VARIANT_TYPE = 'VARIANT'.freeze + DATE_TYPE = 'DATE'.freeze + JSON_TYPE = 'JSON'.freeze ERR_DUPLICATE_KEY_VALUE = 23_505 ERR_QUERY_TIMED_OUT = 57_014 @@ -137,8 +139,8 @@ def disconnect! # Build a new column object from the given options. Effectively the same # as super except that it also passes in the native type. # rubocop:disable Metrics/ParameterLists - def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, native_type = nil) - ::ODBCAdapter::Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation, native_type) + def new_column(name, default, sql_type_metadata, null, table_name, native_type = nil) + ::ODBCAdapter::Column.new(name, default, sql_type_metadata, null, table_name, native_type) end protected @@ -147,6 +149,7 @@ def new_column(name, default, sql_type_metadata, null, table_name, default_funct # Here, ODBC and ODBC_UTF8 constants are interchangeable def initialize_type_map(map) map.register_type 'boolean', Type::Boolean.new + map.register_type 'json', Type::Json.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) @@ -179,7 +182,7 @@ def initialize_type_map(map) # Translate an exception from the native DBMS to something usable by # ActiveRecord. - def translate_exception(exception, message) + def translate_exception(exception, **message) error_number = exception.message[/^\d+/].to_i if error_number == ERR_DUPLICATE_KEY_VALUE diff --git a/lib/odbc_adapter/adapters/mysql_odbc_adapter.rb b/lib/odbc_adapter/adapters/mysql_odbc_adapter.rb index eaa690ef..0d439462 100644 --- a/lib/odbc_adapter/adapters/mysql_odbc_adapter.rb +++ b/lib/odbc_adapter/adapters/mysql_odbc_adapter.rb @@ -5,12 +5,8 @@ module Adapters class MySQLODBCAdapter < ActiveRecord::ConnectionAdapters::ODBCAdapter PRIMARY_KEY = 'INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'.freeze - class BindSubstitution < Arel::Visitors::MySQL - include Arel::Visitors::BindVisitor - end - def arel_visitor - BindSubstitution.new(self) + Arel::Visitors::MySQL.new(self) end # Explicitly turning off prepared statements in the MySQL adapter because diff --git a/lib/odbc_adapter/adapters/null_odbc_adapter.rb b/lib/odbc_adapter/adapters/null_odbc_adapter.rb index 1a179905..c78e991f 100644 --- a/lib/odbc_adapter/adapters/null_odbc_adapter.rb +++ b/lib/odbc_adapter/adapters/null_odbc_adapter.rb @@ -4,15 +4,14 @@ module Adapters # registry. This allows for minimal support for DBMSs for which we don't # have an explicit adapter. class NullODBCAdapter < ActiveRecord::ConnectionAdapters::ODBCAdapter - class BindSubstitution < Arel::Visitors::ToSql - include Arel::Visitors::BindVisitor - end - + VARIANT_TYPE = 'VARIANT'.freeze + DATE_TYPE = 'DATE'.freeze + JSON_TYPE = 'JSON'.freeze # Using a BindVisitor so that the SQL string gets substituted before it is # sent to the DBMS (to attempt to get as much coverage as possible for # DBMSs we don't support). def arel_visitor - BindSubstitution.new(self) + Arel::Visitors::PostgreSQL.new(self) end # Explicitly turning off prepared_statements in the null adapter because diff --git a/lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb b/lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb index 28a28f7c..9bcc8953 100644 --- a/lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb +++ b/lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb @@ -5,6 +5,9 @@ module Adapters class PostgreSQLODBCAdapter < ActiveRecord::ConnectionAdapters::ODBCAdapter BOOLEAN_TYPE = 'bool'.freeze PRIMARY_KEY = 'SERIAL PRIMARY KEY'.freeze + VARIANT_TYPE = 'VARIANT'.freeze + DATE_TYPE = 'DATE'.freeze + JSON_TYPE = 'JSON'.freeze alias create insert @@ -35,7 +38,7 @@ def default_sequence_name(table_name, pk = nil) "#{table_name}_#{pk || 'id'}_seq" end - def sql_for_insert(sql, pk, _id_value, _sequence_name, binds) + def sql_for_insert(sql, pk, binds) unless pk table_ref = extract_table_ref_from_insert_sql(sql) pk = primary_key(table_ref) if table_ref @@ -64,10 +67,11 @@ def quote_string(string) end def disable_referential_integrity - execute(tables.map { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(';')) - yield - ensure - execute(tables.map { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(';')) + #execute(tables.map { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(';')) + #yield + #ensure + #execute(tables.map { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(';')) + [] end # Create a new PostgreSQL database. Options include :owner, diff --git a/lib/odbc_adapter/column.rb b/lib/odbc_adapter/column.rb index 36492a82..93044fea 100644 --- a/lib/odbc_adapter/column.rb +++ b/lib/odbc_adapter/column.rb @@ -5,8 +5,8 @@ class Column < ActiveRecord::ConnectionAdapters::Column # Add the native_type accessor to allow the native DBMS to report back what # it uses to represent the column internally. # rubocop:disable Metrics/ParameterLists - def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, native_type = nil, default_function = nil, collation = nil) - super(name, default, sql_type_metadata, null, table_name, default_function, collation) + def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, native_type = nil) + super(name, default, sql_type_metadata, null, table_name) @native_type = native_type end end diff --git a/lib/odbc_adapter/database_statements.rb b/lib/odbc_adapter/database_statements.rb index cac31682..859fc43a 100644 --- a/lib/odbc_adapter/database_statements.rb +++ b/lib/odbc_adapter/database_statements.rb @@ -9,11 +9,8 @@ module DatabaseStatements # Returns the number of rows affected. def execute(sql, name = nil, binds = []) log(sql, name) do - if prepared_statements - @connection.do(sql, *prepared_binds(binds)) - else - @connection.do(sql) - end + sql = bind_params(binds, sql) if prepared_statements + @connection.do(sql) end end @@ -22,12 +19,8 @@ def execute(sql, name = nil, binds = []) # the executed +sql+ statement. def exec_query(sql, name = 'SQL', binds = [], prepare: false) # rubocop:disable Lint/UnusedMethodArgument log(sql, name) do - stmt = - if prepared_statements - @connection.run(sql, *prepared_binds(binds)) - else - @connection.run(sql) - end + sql = bind_params(binds, sql) if prepared_statements + stmt = @connection.run(sql) columns = stmt.columns values = stmt.to_a @@ -81,6 +74,14 @@ def dbms_type_cast(_columns, values) values end + def bind_params(binds, sql) + prepared_binds = *prepared_binds(binds) + prepared_binds.each.with_index(1) do |val, ind| + sql = sql.gsub("$#{ind}", "'#{val}'") + end + sql + end + # Assume received identifier is in DBMS's data dictionary case. def format_case(identifier) if database_metadata.upcase_identifiers? @@ -127,8 +128,13 @@ def nullability(col_name, is_nullable, nullable) col_name == 'id' ? false : result end + # Adapt to Rails 5.2 + def prepare_statement_sub(sql) + sql.gsub(/\$\d+/, '?') + end + def prepared_binds(binds) - prepare_binds_for_database(binds).map { |bind| _type_cast(bind) } + binds.map(&:value_for_database).map { |bind| _type_cast(bind) } end end end diff --git a/lib/odbc_adapter/schema_statements.rb b/lib/odbc_adapter/schema_statements.rb index df149765..54deea9d 100644 --- a/lib/odbc_adapter/schema_statements.rb +++ b/lib/odbc_adapter/schema_statements.rb @@ -11,7 +11,7 @@ def native_database_types # current connection. def tables(_name = nil) stmt = @connection.tables - result = stmt.fetch_all || [] + result = stmt.fetch || [] stmt.drop result.each_with_object([]) do |row, table_names| @@ -76,6 +76,8 @@ def columns(table_name, _name = nil) args = { sql_type: col_sql_type, type: col_sql_type, limit: col_limit } args[:sql_type] = 'boolean' if col_native_type == self.class::BOOLEAN_TYPE + args[:sql_type] = 'json' if col_native_type == self.class::VARIANT_TYPE || col_native_type == self.class::JSON_TYPE + args[:sql_type] = 'date' if col_native_type == self.class::DATE_TYPE if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(col_sql_type) args[:scale] = col_scale || 0 diff --git a/lib/odbc_adapter/version.rb b/lib/odbc_adapter/version.rb index 4a65ac67..598c96ec 100644 --- a/lib/odbc_adapter/version.rb +++ b/lib/odbc_adapter/version.rb @@ -1,3 +1,3 @@ module ODBCAdapter - VERSION = '5.0.5'.freeze + VERSION = '5.0.6'.freeze end diff --git a/odbc_adapter.gemspec b/odbc_adapter.gemspec index 4bf0142d..0eafa155 100644 --- a/odbc_adapter.gemspec +++ b/odbc_adapter.gemspec @@ -19,11 +19,13 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + spec.add_dependency 'activerecord', '>= 5.0.1' spec.add_dependency 'ruby-odbc', '~> 0.9' - spec.add_development_dependency 'bundler', '~> 1.14' + spec.add_development_dependency 'bundler', '>= 1.14' spec.add_development_dependency 'minitest', '~> 5.10' spec.add_development_dependency 'rake', '~> 12.0' spec.add_development_dependency 'rubocop', '0.48.1' spec.add_development_dependency 'simplecov', '~> 0.14' + spec.add_development_dependency 'pry', '~> 0.11.1' end