Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial fixes to support Rails 7.2, mostly postgres fix #1167

Merged
merged 15 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion activerecord-jdbc-adapter.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Gem::Specification.new do |gem|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^test/})

gem.add_dependency 'activerecord', '~> 7.1.3'
gem.add_dependency "activerecord", "~> 7.2.2"

#gem.add_development_dependency 'test-unit', '2.5.4'
#gem.add_development_dependency 'test-unit-context', '>= 0.3.0'
Expand Down
2 changes: 1 addition & 1 deletion lib/arjdbc/abstract/database_statements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def exec_update(sql, name = 'SQL', binds = NO_BINDS)
alias :exec_delete :exec_update

# overridden to support legacy binds
def select_all(arel, name = nil, binds = NO_BINDS, preparable: nil, async: false)
def select_all(arel, name = nil, binds = NO_BINDS, preparable: nil, async: false, allow_retry: false)
binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
super
end
Expand Down
2 changes: 1 addition & 1 deletion lib/arjdbc/mysql.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
require 'arjdbc'
require 'arjdbc/mysql/adapter'
require 'arjdbc/mysql/connection_methods'
# require 'arjdbc/mysql/connection_methods'
8 changes: 7 additions & 1 deletion lib/arjdbc/mysql/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
require 'arjdbc/abstract/statement_cache'
require 'arjdbc/abstract/transaction_support'

require "arjdbc/mysql/adapter_hash_config"

require "arjdbc/abstract/relation_query_attribute_monkey_patch"

module ActiveRecord
Expand All @@ -34,6 +36,7 @@ class Mysql2Adapter < AbstractMysqlAdapter
include ArJdbc::Abstract::TransactionSupport

include ArJdbc::MySQL
include ArJdbc::MysqlConfig

class << self
def jdbc_connection_class
Expand Down Expand Up @@ -68,14 +71,17 @@ def initialize(...)

@config[:flags] ||= 0

# assign arjdbc extra connection params
conn_params = build_connection_config(@config.compact)

# JDBC mysql appears to use found rows by default: https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-connection.html
# if @config[:flags].kind_of? Array
# @config[:flags].push "FOUND_ROWS"
# else
# @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
# end

@connection_parameters ||= @config
@connection_parameters = conn_params
end

def self.database_exists?(config)
Expand Down
149 changes: 149 additions & 0 deletions lib/arjdbc/mysql/adapter_hash_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# frozen_string_literal: true

module ArJdbc
module MysqlConfig
def build_connection_config(config)
config = config.deep_dup

load_jdbc_driver

config[:driver] ||= database_driver_name

host = (config[:host] ||= "localhost")
port = (config[:port] ||= 3306)

# jdbc:mysql://[host][,failoverhost...][:port]/[database]
# - alternate fail-over syntax: [host:port],[host:port]/[database]
config[:url] ||= "jdbc:mysql://#{host}:#{port}/#{config[:database]}"

config[:properties] = build_properties(config)

config
end

private

def load_jdbc_driver
require "jdbc/mysql"

::Jdbc::MySQL.load_driver(:require) if defined?(::Jdbc::MySQL.load_driver)
rescue LoadError
# assuming driver.jar is on the class-path
end

def database_driver_name
return ::Jdbc::MySQL.driver_name if defined?(::Jdbc::MySQL.driver_name)

"com.mysql.jdbc.Driver"
end

def build_properties(config)
properties = config[:properties] || {}

properties["zeroDateTimeBehavior"] ||= "CONVERT_TO_NULL"

properties["jdbcCompliantTruncation"] ||= false

charset_name = convert_mysql_encoding(config)

# do not set characterEncoding
if charset_name.eql?(false)
properties["character_set_server"] = config[:encoding] || "utf8"
else
properties["characterEncoding"] = charset_name
end

# driver also executes: "SET NAMES " + (useutf8mb4 ? "utf8mb4" : "utf8")
# thus no need to do it on configure_connection :
config[:encoding] = nil if config.key?(:encoding)

properties["connectionCollation"] ||= config[:collation] if config[:collation]

properties["autoReconnect"] ||= reconnect.to_s unless config[:reconnect].nil?

properties["noDatetimeStringSync"] = true unless properties.key?("noDatetimeStringSync")

sslcert = config[:sslcert]
sslca = config[:sslca]

if config[:sslkey] || sslcert
properties["useSSL"] ||= true
properties["requireSSL"] ||= true
properties["clientCertificateKeyStoreUrl"] ||= java.io.File.new(sslcert).to_url.to_s if sslcert

if sslca
properties["trustCertificateKeyStoreUrl"] ||= java.io.File.new(sslca).to_url.to_s
else
properties["verifyServerCertificate"] ||= false
end
else
# According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection
# must be established by default if explicit option isn't set :
properties["useSSL"] ||= false
end

# disables the effect of 'useTimezone'
properties["useLegacyDatetimeCode"] = false

properties
end

# See https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-charsets.html
# to charset-name (characterEncoding=...)
def convert_mysql_encoding(config)
# NOTE: this is "better" than passing what users are used to set on MRI
# e.g. 'utf8mb4' will fail cause the driver will check for a Java charset
# ... it's smart enough to detect utf8mb4 from server variables :
# "character_set_client" && "character_set_connection" (thus UTF-8)
encoding = config.key?(:encoding) ? config[:encoding] : "utf8"

value = MYSQL_ENCODINGS[encoding]

return false if value == false

value || encoding
end

MYSQL_ENCODINGS = {
"big5" => "Big5",
"dec8" => nil,
"hp8" => nil,
"latin1" => "Cp1252",
"latin2" => "ISO8859_2",
"swe7" => nil,
"ascii" => "US-ASCII",
"ujis" => "EUC_JP",
"sjis" => "SJIS",
"hebrew" => "ISO8859_8",
"tis620" => "TIS620",
"euckr" => "EUC_KR",
"gb2312" => "EUC_CN",
"greek" => "ISO8859_7",
"cp1250" => "Cp1250",
"gbk" => "GBK",
"armscii8" => nil,
"ucs2" => "UnicodeBig",
"cp866" => "Cp866",
"keybcs2" => nil,
"macce" => "MacCentralEurope",
"macroman" => "MacRoman",
"cp1251" => "Cp1251",
"cp1256" => "Cp1256",
"cp1257" => "Cp1257",
"binary" => false,
"geostd8" => nil,
"cp932" => "Cp932",
"utf8" => "UTF-8",
"utf8mb4" => false,
"utf16" => false,
"utf32" => false,
# "cp850" => "Cp850",
# "koi8r" => "KOI8-R",
# "koi8u" => "KOI8-R",
# "latin5" => "ISO-8859-9",
# "cp852" => "CP852",
# "latin7" => "ISO-8859-13",
# "eucjpms" => "eucJP-ms"
}.freeze
end
end
2 changes: 1 addition & 1 deletion lib/arjdbc/postgresql.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
require 'arjdbc'
require 'arjdbc/postgresql/adapter'
require 'arjdbc/postgresql/connection_methods'
# require 'arjdbc/postgresql/connection_methods'
14 changes: 4 additions & 10 deletions lib/arjdbc/postgresql/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
require 'arjdbc/postgresql/name'
require 'arjdbc/postgresql/database_statements'
require 'arjdbc/postgresql/schema_statements'
require "arjdbc/postgresql/adapter_hash_config"

require 'active_model'

Expand Down Expand Up @@ -855,6 +856,7 @@ class PostgreSQLAdapter < AbstractAdapter
include ArJdbc::Abstract::StatementCache
include ArJdbc::Abstract::TransactionSupport
include ArJdbc::PostgreSQL
include ArJdbc::PostgreSQLConfig

require 'arjdbc/postgresql/oid_types'
include ::ArJdbc::PostgreSQL::OIDTypes
Expand Down Expand Up @@ -900,7 +902,8 @@ def dbconsole(config, options = {})
def initialize(...)
super

conn_params = @config.compact
# assign arjdbc extra connection params
conn_params = build_connection_config(@config.compact)

@connection_parameters = conn_params

Expand All @@ -912,15 +915,6 @@ def initialize(...)
self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
end

def self.database_exists?(config)
conn = ActiveRecord::Base.postgresql_connection(config)
conn && conn.really_valid?
rescue ActiveRecord::NoDatabaseError
false
ensure
conn.disconnect! if conn
end

require 'active_record/connection_adapters/postgresql/schema_definitions'

ColumnMethods = ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods
Expand Down
92 changes: 92 additions & 0 deletions lib/arjdbc/postgresql/adapter_hash_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

module ArJdbc
module PostgreSQLConfig
def build_connection_config(config)
config = config.deep_dup

load_jdbc_driver

config[:driver] ||= database_driver_name

host = (config[:host] ||= config[:hostaddr] || ENV["PGHOST"] || "localhost")
port = (config[:port] ||= ENV["PGPORT"] || 5432)
database = config[:database] || config[:dbname] || ENV["PGDATABASE"]

config[:url] ||= "jdbc:postgresql://#{host}:#{port}/#{database}"

config[:url] << config[:pg_params] if config[:pg_params]

config[:username] ||= config[:user] || ENV["PGUSER"] || ENV_JAVA["user.name"]
config[:password] ||= ENV["PGPASSWORD"] unless config.key?(:password)

config[:properties] = build_properties(config)

config
end

private

def load_jdbc_driver
require "jdbc/postgres"

::Jdbc::Postgres.load_driver(:require) if defined?(::Jdbc::Postgres.load_driver)
rescue LoadError
# assuming driver.jar is on the class-path
end

def database_driver_name
return ::Jdbc::Postgres.driver_name if defined?(::Jdbc::Postgres.driver_name)

"org.postgresql.Driver"
end

def build_properties(config)
properties = config[:properties] || {}

# PG :connect_timeout - maximum time to wait for connection to succeed
connect_timeout = config[:connect_timeout] || ENV["PGCONNECT_TIMEOUT"]

properties["socketTimeout"] ||= connect_timeout if connect_timeout

login_timeout = config[:login_timeout]

properties["loginTimeout"] ||= login_timeout if login_timeout

sslmode = config.key?(:sslmode) ? config[:sslmode] : config[:requiressl]
# NOTE: makes not much sense since this needs some JVM options :
sslmode = ENV["PGSSLMODE"] || ENV["PGREQUIRESSL"] if sslmode.nil?

# PG :sslmode - disable|allow|prefer|require
unless sslmode.nil? || !(sslmode == true || sslmode.to_s == "require")
# JRuby/JVM needs to be started with :
# -Djavax.net.ssl.trustStore=mystore -Djavax.net.ssl.trustStorePassword=...
# or a non-validating connection might be used (for testing) :
# :sslfactory = 'org.postgresql.ssl.NonValidatingFactory'

if config[:driver].start_with?("org.postgresql.")
properties["sslfactory"] ||= "org.postgresql.ssl.NonValidatingFactory"
end

properties["ssl"] ||= "true"
end

properties["tcpKeepAlive"] ||= config[:keepalives] if config.key?(:keepalives)
properties["kerberosServerName"] ||= config[:krbsrvname] if config[:krbsrvname]

prepared_statements = config.fetch(:prepared_statements, true)

prepared_statements = false if prepared_statements == "false"

if prepared_statements
# this makes the pgjdbc driver handle hot compatibility internally
properties["autosave"] ||= "conservative"
else
# If prepared statements are off, lets make sure they are really *off*
properties["prepareThreshold"] = 0
end

properties
end
end
end
4 changes: 3 additions & 1 deletion lib/arjdbc/postgresql/base/array_encoder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ def initialize(name:, delimiter:)
'text'.freeze
else
base_type = name.chomp('[]').to_sym
ActiveRecord::Base.connection.native_database_types[base_type][:name]
ActiveRecord::Base.with_connection do |connection|
connection.native_database_types[base_type][:name]
end
end
end

Expand Down
4 changes: 2 additions & 2 deletions lib/arjdbc/postgresql/oid_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ def initialize_type_map_inner(m)
m.register_type "int4", Type::Integer.new(limit: 4)
m.register_type "int8", Type::Integer.new(limit: 8)
m.register_type "oid", OID::Oid.new
m.register_type "float4", Type::Float.new
m.alias_type "float8", "float4"
m.register_type "float4", Type::Float.new(limit: 24)
m.register_type "float8", Type::Float.new
m.register_type "text", Type::Text.new
register_class_with_limit m, "varchar", Type::String
m.alias_type "char", "varchar"
Expand Down
2 changes: 1 addition & 1 deletion lib/arjdbc/sqlite3.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
require 'arjdbc'
require 'arjdbc/sqlite3/adapter'
require 'arjdbc/sqlite3/connection_methods'
# require 'arjdbc/sqlite3/connection_methods'
Loading
Loading