From e8040b1434d0629750711615aa8affe710cfd04d Mon Sep 17 00:00:00 2001 From: Sebastian Guarin Date: Mon, 22 Sep 2025 11:03:41 +1000 Subject: [PATCH 1/3] SG: Use DB timezone when setting time bindings --- lib/arjdbc/abstract/core.rb | 2 ++ lib/arjdbc/abstract/quoting.rb | 14 ++++++++++++++ lib/arjdbc/jdbc/adapter.rb | 2 ++ lib/arjdbc/mssql/adapter.rb | 2 ++ lib/arjdbc/mssql/quoting.rb | 18 +----------------- lib/arjdbc/mysql/adapter.rb | 4 +++- lib/arjdbc/postgresql/adapter.rb | 3 +++ src/java/arjdbc/jdbc/RubyJdbcConnection.java | 6 +++++- 8 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 lib/arjdbc/abstract/quoting.rb diff --git a/lib/arjdbc/abstract/core.rb b/lib/arjdbc/abstract/core.rb index cccfcbc59..a8f3b5977 100644 --- a/lib/arjdbc/abstract/core.rb +++ b/lib/arjdbc/abstract/core.rb @@ -65,6 +65,8 @@ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = binds.map do |bind| if bind.respond_to?(:value_for_database) bind.value_for_database + elsif bind.is_a?(Time) + time_for_database(bind) else bind end diff --git a/lib/arjdbc/abstract/quoting.rb b/lib/arjdbc/abstract/quoting.rb new file mode 100644 index 000000000..fd490780c --- /dev/null +++ b/lib/arjdbc/abstract/quoting.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ArJdbc + module Abstract + module Quoting + + # Helper to get local/UTC time (based on `ActiveRecord::default_timezone`). + def time_for_database(value) + get = ::ActiveRecord.default_timezone == :utc ? :getutc : :getlocal + value.respond_to?(get) ? value.send(get) : value + end + end + end +end \ No newline at end of file diff --git a/lib/arjdbc/jdbc/adapter.rb b/lib/arjdbc/jdbc/adapter.rb index 7ea44c35c..5ee48cedb 100644 --- a/lib/arjdbc/jdbc/adapter.rb +++ b/lib/arjdbc/jdbc/adapter.rb @@ -16,6 +16,7 @@ require 'arjdbc/abstract/connection_management' require 'arjdbc/abstract/database_statements' require 'arjdbc/abstract/transaction_support' +require 'arjdbc/abstract/quoting' module ActiveRecord module ConnectionAdapters @@ -43,6 +44,7 @@ class JdbcAdapter < AbstractAdapter include ArJdbc::Abstract::ConnectionManagement include ArJdbc::Abstract::DatabaseStatements include ArJdbc::Abstract::TransactionSupport + include ArJdbc::Abstract::Quoting attr_reader :prepared_statements diff --git a/lib/arjdbc/mssql/adapter.rb b/lib/arjdbc/mssql/adapter.rb index bfbea9838..a20fba340 100644 --- a/lib/arjdbc/mssql/adapter.rb +++ b/lib/arjdbc/mssql/adapter.rb @@ -14,6 +14,7 @@ require 'arjdbc/abstract/database_statements' require 'arjdbc/abstract/statement_cache' require 'arjdbc/abstract/transaction_support' +require 'arjdbc/abstract/quoting' require 'arjdbc/mssql/utils' require 'arjdbc/mssql/server_version' @@ -46,6 +47,7 @@ class MSSQLAdapter < AbstractAdapter # include ArJdbc::Abstract::DatabaseStatements # include ArJdbc::Abstract::StatementCache include ArJdbc::Abstract::TransactionSupport + include ArJdbc::Abstract::Quoting include ArJdbc::MSSQLConfig include MSSQL::Quoting diff --git a/lib/arjdbc/mssql/quoting.rb b/lib/arjdbc/mssql/quoting.rb index cc8fa6757..68b71a838 100644 --- a/lib/arjdbc/mssql/quoting.rb +++ b/lib/arjdbc/mssql/quoting.rb @@ -90,7 +90,7 @@ def quote(value) # The JDBC drivers does not work with 6 digits microseconds def quoted_date(value) if value.acts_like?(:time) - value = time_with_db_timezone(value) + value = time_for_database(value) end result = value.to_fs(:db) @@ -143,22 +143,6 @@ def quoted_time(value) # @private # @see #quote in old adapter BLOB_VALUE_MARKER = "''" - - private - - def time_with_db_timezone(value) - zone_conv_method = if ActiveRecord.default_timezone == :utc - :getutc - else - :getlocal - end - - if value.respond_to?(zone_conv_method) - value = value.send(zone_conv_method) - else - value - end - end end end end diff --git a/lib/arjdbc/mysql/adapter.rb b/lib/arjdbc/mysql/adapter.rb index 276205b00..10b194130 100644 --- a/lib/arjdbc/mysql/adapter.rb +++ b/lib/arjdbc/mysql/adapter.rb @@ -10,6 +10,7 @@ require 'arjdbc/abstract/database_statements' require 'arjdbc/abstract/statement_cache' require 'arjdbc/abstract/transaction_support' +require 'arjdbc/abstract/quoting' require "arjdbc/mysql/adapter_hash_config" @@ -34,6 +35,7 @@ class Mysql2Adapter < AbstractMysqlAdapter # NOTE: do not include MySQL::DatabaseStatements include ArJdbc::Abstract::StatementCache include ArJdbc::Abstract::TransactionSupport + include ArJdbc::Abstract::Quoting include ArJdbc::MySQL include ArJdbc::MysqlConfig @@ -137,7 +139,7 @@ def build_explain_clause(options = []) return "EXPLAIN" if options.empty? explain_clause = "EXPLAIN #{options.join(" ").upcase}" - + if analyze_without_explain? && explain_clause.include?("ANALYZE") explain_clause.sub("EXPLAIN ", "") else diff --git a/lib/arjdbc/postgresql/adapter.rb b/lib/arjdbc/postgresql/adapter.rb index 7b01309f1..3f065faaf 100644 --- a/lib/arjdbc/postgresql/adapter.rb +++ b/lib/arjdbc/postgresql/adapter.rb @@ -19,6 +19,8 @@ require 'arjdbc/abstract/database_statements' require 'arjdbc/abstract/statement_cache' require 'arjdbc/abstract/transaction_support' +require 'arjdbc/abstract/quoting' + require 'arjdbc/postgresql/base/array_decoder' require 'arjdbc/postgresql/base/array_encoder' require 'arjdbc/postgresql/name' @@ -864,6 +866,7 @@ class PostgreSQLAdapter < AbstractAdapter include ArJdbc::Abstract::DatabaseStatements include ArJdbc::Abstract::StatementCache include ArJdbc::Abstract::TransactionSupport + include ArJdbc::Abstract::Quoting include ArJdbc::PostgreSQLConfig # NOTE: after AR refactor quote_column_name became class and instance method diff --git a/src/java/arjdbc/jdbc/RubyJdbcConnection.java b/src/java/arjdbc/jdbc/RubyJdbcConnection.java index 5272c640d..5a1445614 100644 --- a/src/java/arjdbc/jdbc/RubyJdbcConnection.java +++ b/src/java/arjdbc/jdbc/RubyJdbcConnection.java @@ -2445,7 +2445,7 @@ protected void setStatementParameter(final ThreadContext context, value = valueForDatabase(context, attribute); } else if (timeZoneClass.isInstance(attribute)) { type = jdbcTypeFor("timestamp"); - value = attribute; + value = timeForDatabase(context, attribute); } else { type = jdbcTypeForPrimitiveAttribute(context, attribute); value = attribute; @@ -3686,6 +3686,10 @@ protected IRubyObject valueForDatabase(final ThreadContext context, final IRubyO return attribute.callMethod(context, "value_for_database"); } + protected IRubyObject timeForDatabase(final ThreadContext context, final IRubyObject attribute) { + return adapter.callMethod(context, "time_for_database", attribute); + } + // FIXME: This should not be static and will be exposed via api in connection as instance method. public static final StringCache STRING_CACHE = new StringCache(); From a38bbb5b5b319e5c6c4c7f4e5c188156eca4ef40 Mon Sep 17 00:00:00 2001 From: Sebastian Guarin Date: Thu, 25 Sep 2025 17:11:43 +1000 Subject: [PATCH 2/3] SG: Add test case checking zone query --- test/simple.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/simple.rb b/test/simple.rb index bfd2a36b9..5e2b43444 100644 --- a/test/simple.rb +++ b/test/simple.rb @@ -847,6 +847,16 @@ def test_create_bind_param_with_q_mark assert_equal 'bar?', entry.content end + def test_time_bind_param + with_timezone_config default: :utc, zone: 'Australia/Melbourne' do + time = Time.zone.local(2000, 1, 1, 16) + entry = Entry.create! updated_on: time + 1.hour + sql = 'updated_on >= ?' + entries = Entry.where(sql, time) + assert_equal 1, entries.size + end + end + class ChangeEntriesTable < ActiveRecord::Migration[4.2] def self.up change_table :entries do |t| From ef4b31f9cf0c21dac0a4804586fc95d760a9e426 Mon Sep 17 00:00:00 2001 From: Sebastian Guarin Date: Sun, 28 Sep 2025 23:21:22 +1000 Subject: [PATCH 3/3] SG: Add Quoting to sqllite adapter --- lib/arjdbc/sqlite3/adapter.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/arjdbc/sqlite3/adapter.rb b/lib/arjdbc/sqlite3/adapter.rb index 964d99b9f..f5d048af4 100644 --- a/lib/arjdbc/sqlite3/adapter.rb +++ b/lib/arjdbc/sqlite3/adapter.rb @@ -6,6 +6,8 @@ require "arjdbc/abstract/database_statements" require 'arjdbc/abstract/statement_cache' require "arjdbc/abstract/transaction_support" +require "arjdbc/abstract/quoting" + require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/statement_pool" require "active_record/connection_adapters/sqlite3/explain_pretty_printer" @@ -846,6 +848,7 @@ def jdbc_connection_class include ArJdbc::Abstract::DatabaseStatements include ArJdbc::Abstract::StatementCache include ArJdbc::Abstract::TransactionSupport + include ArJdbc::Abstract::Quoting ##