FEATURE: add support query_decorator (#13)

FEATURE: add support query_decorator (#13)

query_decorator can be used to grab specialized object instances implementing custom decorators

diff --git a/README.md b/README.md
index 95ebee2..3414b15 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,17 @@ conn.query("select 1 id, 'bob' name").each do |user|
   puts user.id # 1
 end
 
+# extend result objects with additional method
+module ProductDecorator
+  def amount_price
+    price * quantity
+  end
+end
+
+conn.query_decorator(ProductDecorator, "select 20 price, 3 quantity").each do |user|
+  puts user.amount_price # 60
+end
+
 p conn.query_single('select 1 union select 2')
 # [1,2]
 
diff --git a/bench/topic_perf.rb b/bench/topic_perf.rb
index 6ed1fca..19b3d23 100644
--- a/bench/topic_perf.rb
+++ b/bench/topic_perf.rb
@@ -14,6 +14,7 @@ gemfile do
   gem 'sequel', github: 'jeremyevans/sequel'
   gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
   gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres'
+  gem 'draper'
 end
 
 require 'sequel'
@@ -219,27 +220,119 @@ results = [
 
 exit(-1) unless results.uniq.length == 1
 
-#Benchmark.ips do |r|
-#  r.report('string') do |n|
-#    while n > 0
-#      s = +''
-#      1_000.times { |i| s << i; s << i }
-#      n -= 1
-#    end
-#  end
-#  r.report('array') do |n|
-#    while n > 0
-#      1_000.times { |i| [i, i] }
-#      n -= 1
-#    end
-#  end
-#
-#  r.compare!
-#end
+# https://github.com/drapergem/draper
+class TopicDraper < Draper::Decorator
+  delegate :id
+
+  def title_bang
+    object.title + '!!!'
+  end
+end
+
+# https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html
+class TopicSimpleDelegator < SimpleDelegator
+  def title_bang
+    title + '!!!'
+  end
+end
+
+class TopicDecoratorSequel < TopicSequel
+  def title_bang
+    title + '!!!'
+  end
+end
+
+class TopicArModel < Topic
+  def title_bang
+    title + '!!!'
+  end
+end
+
+module TopicDecorator
+  def title_bang
+    title + '!!!'
+  end
+end
+
+Benchmark.ips do |r|
+  r.report('query_decorator') do |n|
+    while n > 0
+      $mini_sql.query_decorator(TopicDecorator, 'select id, title from topics order by id limit 1000').each do |obj|
+        obj.title_bang
+        obj.id
+      end
+      n -= 1
+    end
+  end
+  r.report('extend') do |n|
+    while n > 0
+      $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
+        d_obj = obj.extend(TopicDecorator)
+        d_obj.title_bang
+        d_obj.id
+      end
+      n -= 1
+    end
+  end
+  r.report('draper') do |n|
+    while n > 0
+      $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
+        d_obj = TopicDraper.new(obj)
+        d_obj.title_bang
+        d_obj.id
+      end
+      n -= 1
+    end
+  end
+  r.report('simple_delegator') do |n|
+    while n > 0
+      $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
+        d_obj = TopicSimpleDelegator.new(obj)
+        d_obj.title_bang
+        d_obj.id
+      end
+      n -= 1
+    end
+  end
+  r.report('query') do |n|
+    while n > 0
+      $mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
+        obj.title + '!!!'
+        obj.id
+      end
+      n -= 1
+    end
+  end
+  r.report('ar model') do |n|
+    while n > 0
+      TopicArModel.limit(1000).order(:id).select(:id, :title).each do |obj|
+        obj.title_bang
+        obj.id
+      end
+      n -= 1
+    end
+  end
+  r.report('sequel model') do |n|
+    while n > 0
+      TopicDecoratorSequel.limit(1000).order(:id).select(:id, :title).each do |obj|
+        obj.title_bang
+        obj.id
+      end
+      n -= 1
+    end
+  end
+
+  r.compare!
+end
 
 # Comparison:
-#   array:    13041.2 i/s
-#  string:     4254.9 i/s - 3.06x  slower
+#                query:      828.4 i/s
+#      query_decorator:      819.3 i/s - same-ish: difference falls within error
+#         sequel model:      672.4 i/s - 1.23x  slower
+#               extend:      519.4 i/s - 1.59x  slower
+#     simple_delegator:      496.8 i/s - 1.67x  slower
+#               draper:      416.2 i/s - 1.99x  slower
+#             ar model:      113.4 i/s - 7.30x  slower
 
 Benchmark.ips do |r|
   r.report('query_hash') do |n|
diff --git a/lib/mini_sql/mysql/connection.rb b/lib/mini_sql/mysql/connection.rb
index df1dfba..51e6de7 100644
--- a/lib/mini_sql/mysql/connection.rb
+++ b/lib/mini_sql/mysql/connection.rb
@@ -34,6 +34,11 @@ module MiniSql
         @deserializer_cache.materialize(result)
       end
 
+      def query_decorator(decorator, sql, *params)
+        result = run(sql, :array, params)
+        @deserializer_cache.materialize(result, decorator)
+      end
+
       def escape_string(str)
         raw_connection.escape(str)
       end
diff --git a/lib/mini_sql/mysql/deserializer_cache.rb b/lib/mini_sql/mysql/deserializer_cache.rb
index 8f2f241..5980d51 100644
--- a/lib/mini_sql/mysql/deserializer_cache.rb
+++ b/lib/mini_sql/mysql/deserializer_cache.rb
@@ -11,7 +11,7 @@ module MiniSql
         @max_size = max_size || DEFAULT_MAX_SIZE
       end
 
-      def materialize(result)
+      def materialize(result, decorator_module = nil)
         key = result.fields
 
         # trivial fast LRU implementation
@@ -23,6 +23,8 @@ module MiniSql
           @cache.shift if @cache.length > @max_size
         end
 
+        materializer.include(decorator_module) if decorator_module
+
         result.map do |data|
           materializer.materialize(data)
         end
diff --git a/lib/mini_sql/postgres/connection.rb b/lib/mini_sql/postgres/connection.rb
index 3e4952e..e11fc28 100644
--- a/lib/mini_sql/postgres/connection.rb
+++ b/lib/mini_sql/postgres/connection.rb
@@ -94,6 +94,14 @@ module MiniSql
         result.clear if result
       end
 
+      def query_decorator(decorator, sql, *params)
+        result = run(sql, params)
+        result.type_map = type_map
+        @deserializer_cache.materialize(result, decorator)
+      ensure
+        result.clear if result
+      end
+
       def exec(sql, *params)
         result = run(sql, params)
         result.cmd_tuples
diff --git a/lib/mini_sql/postgres/deserializer_cache.rb b/lib/mini_sql/postgres/deserializer_cache.rb
index f0f6f92..518c581 100644
--- a/lib/mini_sql/postgres/deserializer_cache.rb
+++ b/lib/mini_sql/postgres/deserializer_cache.rb
@@ -11,8 +11,7 @@ module MiniSql
         @max_size = max_size || DEFAULT_MAX_SIZE
       end
 
-      def materialize(result)
-
+      def materialize(result, decorator_module = nil)
         return [] if result.ntuples == 0
 
         key = result.fields
@@ -26,6 +25,8 @@ module MiniSql
           @cache.shift if @cache.length > @max_size
         end
 
+        materializer.include(decorator_module) if decorator_module
+
         i = 0
         r = []
         # quicker loop
diff --git a/lib/mini_sql/postgres_jdbc/connection.rb b/lib/mini_sql/postgres_jdbc/connection.rb
index 3b5df38..d2f665e 100644
--- a/lib/mini_sql/postgres_jdbc/connection.rb
+++ b/lib/mini_sql/postgres_jdbc/connection.rb
@@ -60,6 +60,11 @@ module MiniSql
         @deserializer_cache.materialize(result)
       end
 
+      def query_decorator(decorator, sql, *params)
+        result = run(sql, params)
+        @deserializer_cache.materialize(result, decorator)
+      end
+
       def exec(sql, *params)
         result = run(sql, params)
         if result.kind_of? Integer
diff --git a/lib/mini_sql/postgres_jdbc/deserializer_cache.rb b/lib/mini_sql/postgres_jdbc/deserializer_cache.rb
index f0f6f92..dbc082c 100644
--- a/lib/mini_sql/postgres_jdbc/deserializer_cache.rb
+++ b/lib/mini_sql/postgres_jdbc/deserializer_cache.rb
@@ -11,7 +11,7 @@ module MiniSql
         @max_size = max_size || DEFAULT_MAX_SIZE
       end
 
-      def materialize(result)
+      def materialize(result, decorator_module = nil)
 
         return [] if result.ntuples == 0
 
@@ -26,6 +26,8 @@ module MiniSql
           @cache.shift if @cache.length > @max_size
         end
 
+        materializer.include(decorator_module) if decorator_module
+
         i = 0
         r = []
         # quicker loop

[... diff too long, it was truncated ...]

GitHub sha: 2b147e83