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/lib/active_record/connection_adapters/odbc_adapter.rb b/lib/active_record/connection_adapters/odbc_adapter.rb index 06686785..e4887723 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) 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..05fa17e0 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 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..3df30c9c 100644 --- a/lib/odbc_adapter/schema_statements.rb +++ b/lib/odbc_adapter/schema_statements.rb @@ -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