Add JRuby postgresql support. This is still WIP since it fails one test involving custom type maping. Type mapping will end up in a followup commit since JRuby arjdbc has no configurable type mapping system.

Add JRuby postgresql support. This is still WIP since it fails one test involving custom type maping. Type mapping will end up in a followup commit since JRuby arjdbc has no configurable type mapping system.

This gem will work with ARJDBC 52.2+ as that is the first version which augmented its internal Result object to be even more like PG::Result (note: as of this commit 52.2 has not been released yet).

diff --git a/Rakefile b/Rakefile
index d433a1e..c3359c1 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,10 +1,16 @@
 require "bundler/gem_tasks"
 require "rake/testtask"
 
+if RUBY_ENGINE == 'jruby' # Excluding sqlite3 tests
+  test_glob = "test/**/{inline_param_encoder_test.rb,postgres/*_test.rb}"
+else
+  test_glob = "test/**/*_test.rb"
+end
+
 Rake::TestTask.new(:test) do |t|
   t.libs << "test"
   t.libs << "lib"
-  t.test_files = FileList["test/**/*_test.rb"]
+  t.test_files = FileList[test_glob]
 end
 
 task :default => :test
diff --git a/lib/mini_sql.rb b/lib/mini_sql.rb
index 0023f08..d437382 100644
--- a/lib/mini_sql.rb
+++ b/lib/mini_sql.rb
@@ -10,14 +10,21 @@ require_relative "mini_sql/builder"
 require_relative "mini_sql/inline_param_encoder"
 
 module MiniSql
-  module Postgres
-    autoload :Coders, "mini_sql/postgres/coders"
-    autoload :Connection, "mini_sql/postgres/connection"
-    autoload :DeserializerCache, "mini_sql/postgres/deserializer_cache"
-  end
+  if RUBY_ENGINE == 'jruby'
+    module Postgres
+      autoload :Connection, "mini_sql/postgres_jdbc/connection"
+      autoload :DeserializerCache, "mini_sql/postgres_jdbc/deserializer_cache"
+    end
+  else
+    module Postgres
+      autoload :Coders, "mini_sql/postgres/coders"
+      autoload :Connection, "mini_sql/postgres/connection"
+      autoload :DeserializerCache, "mini_sql/postgres/deserializer_cache"
+    end
 
-  module Sqlite
-    autoload :Connection, "mini_sql/sqlite/connection"
-    autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
+    module Sqlite
+      autoload :Connection, "mini_sql/sqlite/connection"
+      autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
+    end
   end
 end
diff --git a/lib/mini_sql/connection.rb b/lib/mini_sql/connection.rb
index 005ebcb..f5c702c 100644
--- a/lib/mini_sql/connection.rb
+++ b/lib/mini_sql/connection.rb
@@ -4,7 +4,9 @@ module MiniSql
   class Connection
 
     def self.get(raw_connection, options = {})
-      if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
+      if (defined? ::PG::Connection) && (PG::Connection === raw_connection) 
+        Postgres::Connection.new(raw_connection, options)
+      elsif (defined? ::ArJdbc)
         Postgres::Connection.new(raw_connection, options)
       elsif (defined? ::SQLite3::Database) && (SQLite3::Database === raw_connection)
         Sqlite::Connection.new(raw_connection, options)
diff --git a/lib/mini_sql/postgres_jdbc/connection.rb b/lib/mini_sql/postgres_jdbc/connection.rb
new file mode 100644
index 0000000..0d46c5e
--- /dev/null
+++ b/lib/mini_sql/postgres_jdbc/connection.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module MiniSql
+  module Postgres
+    class Connection < MiniSql::Connection
+      attr_reader :raw_connection, :type_map, :param_encoder
+
+      def self.default_deserializer_cache
+        @deserializer_cache ||= DeserializerCache.new
+      end
+
+      # Initialize a new MiniSql::Postgres::Connection object
+      #
+      # @param raw_connection [PG::Connection] an active connection to PG
+      # @param deserializer_cache [MiniSql::DeserializerCache] a cache of field names to deserializer, can be nil
+      # @param type_map [PG::TypeMap] a type mapper for all results returned, can be nil
+      def initialize(connection, args = nil)
+        @connection = connection
+        @raw_connection = connection.raw_connection
+        @deserializer_cache = (args && args[:deserializer_cache]) || self.class.default_deserializer_cache
+        @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
+      end
+
+      # Returns a flat array containing all results.
+      # Note, if selecting multiple columns array will be flattened
+      #
+      # @param sql [String] the query to run
+      # @param params [Array or Hash], params to apply to query
+      # @return [Object] a flat array containing all results
+      def query_single(sql, *params)
+        result = run(sql, params)
+        if result.length == 1
+          result.values[0]
+        else
+          result.values.each_with_object([]) { |value, array| array.concat value }
+        end
+      end
+
+      def query(sql, *params)
+        result = run(sql, params)
+        @deserializer_cache.materialize(result)
+      end
+
+      def exec(sql, *params)
+        result = run(sql, params)
+        if result.kind_of? Integer
+          result
+        else
+          result.length
+        end
+      end
+
+      def query_hash(sql, *params)
+        run(sql, params).to_a
+      end
+
+      def build(sql)
+        Builder.new(self, sql)
+      end
+
+      def escape_string(str)
+        @connection.quote_string(str)
+      end
+
+      private
+
+      def run(sql, params)
+        if params && params.length > 0
+          sql = param_encoder.encode(sql, *params)
+        end
+        raw_connection.execute(sql)
+      end
+
+    end
+  end
+end
diff --git a/lib/mini_sql/postgres_jdbc/deserializer_cache.rb b/lib/mini_sql/postgres_jdbc/deserializer_cache.rb
new file mode 100644
index 0000000..58a5373
--- /dev/null
+++ b/lib/mini_sql/postgres_jdbc/deserializer_cache.rb
@@ -0,0 +1,67 @@
+module MiniSql
+  module Postgres
+    class DeserializerCache
+
+    DEFAULT_MAX_SIZE = 500
+
+    def initialize(max_size = nil)
+      @cache = {}
+      @max_size = max_size || DEFAULT_MAX_SIZE
+    end
+
+    def materialize(result)
+
+      return [] if result.ntuples == 0
+
+      key = result.fields
+
+      # trivial fast LRU implementation
+      materializer = @cache.delete(key)
+      if materializer
+        @cache[key] = materializer
+      else
+        materializer = @cache[key] = new_row_matrializer(result)
+        @cache.shift if @cache.length > @max_size
+      end
+
+      i = 0
+      r = []
+      # quicker loop
+      while i < result.ntuples
+        r << materializer.materialize(result, i)
+        i += 1
+      end
+      r
+    end
+
+    private
+
+    def new_row_matrializer(result)
+      fields = result.fields
+
+      Class.new do
+        attr_accessor(*fields)
+
+        # AM serializer support
+        alias :read_attribute_for_serialization :send
+
+        def to_h
+          r = {}
+          instance_variables.each do |f|
+            r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
+          end
+          r
+        end
+
+        instance_eval <<~RUBY
+            def materialize(pg_result, index)
+              r = self.new
+              #{col=-1; fields.map{|f| "r.#{f} = pg_result.getvalue(index, #{col+=1})"}.join("; ")}
+              r
+            end
+        RUBY
+      end
+    end
+  end
+end
+end
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ea634d5..cb3b924 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -4,19 +4,33 @@ require "mini_sql"
 require "minitest/autorun"
 require "minitest/pride"
 
-require "pg"
-require "sqlite3"
-require "time"
+if RUBY_ENGINE == 'jruby'
+  raise ArgumentError.new("JRuby requires ENV['PASSWORD'] for testing") unless ENV['PASSWORD']
 
-def pg_connection
-   pg_conn = PG.connect(dbname: 'test_mini_sql')
-   MiniSql::Connection.get(pg_conn)
-end
+  require 'activerecord-jdbc-adapter'
+  require 'activerecord-jdbcpostgresql-adapter'
+
+  def pg_connection
+    config = {adapter: 'postgresql', dbname: 'test_mini_sql', password: ENV['PASSWORD'] }
+    pg_conn = ActiveRecord::Base.establish_connection(**config).checkout
+    MiniSql::Connection.get(pg_conn)
+  end
+else
+  require "pg"
+  require "sqlite3"
 
-def sqlite3_connection
-   @sqlite_conn = SQLite3::Database.new(':memory:')
-   MiniSql::Connection.get(@sqlite_conn)
+  def pg_connection
+    pg_conn = PG.connect(dbname: 'test_mini_sql')
+    MiniSql::Connection.get(pg_conn)
+  end
+
+  def sqlite3_connection
+    sqlite_conn = SQLite3::Database.new(':memory:')
+    MiniSql::Connection.get(sqlite_conn)
+  end
 end
 
+require "time"
+
 require_relative "mini_sql/connection_tests"
 require_relative "mini_sql/builder_tests"

GitHub sha: 5274f14e