From 5c04509b8e3fa97b2ff24898b2cc22a606e35052 Mon Sep 17 00:00:00 2001 From: Ermolaev Andrey Date: Thu, 30 Jan 2020 15:43:03 +0300 Subject: [PATCH 1/3] query_decorator --- README.md | 11 ++ bench/topic_perf.rb | 115 +++++++++++++++--- lib/mini_sql/mysql/connection.rb | 5 + lib/mini_sql/mysql/deserializer_cache.rb | 4 +- lib/mini_sql/postgres/connection.rb | 8 ++ lib/mini_sql/postgres/deserializer_cache.rb | 5 +- lib/mini_sql/postgres_jdbc/connection.rb | 5 + .../postgres_jdbc/deserializer_cache.rb | 4 +- lib/mini_sql/sqlite/connection.rb | 6 + lib/mini_sql/sqlite/deserializer_cache.rb | 4 +- test/mini_sql/connection_tests.rb | 11 ++ 11 files changed, 154 insertions(+), 24 deletions(-) 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..913e1cd 100644 --- a/bench/topic_perf.rb +++ b/bench/topic_perf.rb @@ -14,6 +14,7 @@ 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,103 @@ def swift_select_title_id(l = 1000) 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_without_hello + object.title.gsub('HELLO') + end +end + +# https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html +class TopicSimpleDelegator < SimpleDelegator + def title_without_hello + title.gsub('HELLO') + end +end + +class TopicArModel < Topic + def title_without_hello + title.gsub('HELLO') + end +end + +module TopicDecorator + def title_without_hello + title.gsub('HELLO') + 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_without_hello + 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_without_hello + 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_without_hello + 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_without_hello + 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.gsub('HELLO') + 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_without_hello + 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: 735.8 i/s +# query_decorator: 720.3 i/s - same-ish: difference falls within error +# simple_delegator: 453.4 i/s - 1.62x slower +# extend: 446.0 i/s - 1.65x slower +# draper: 378.6 i/s - 1.94x slower +# ar model: 110.0 i/s - 6.69x 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 @@ def query(sql, *params) @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 @@ def initialize(max_size = nil) @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 @@ def materialize(result) @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 @@ def query(sql, *params) 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 @@ def initialize(max_size = nil) @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 @@ def materialize(result) @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 @@ def query(sql, *params) @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 @@ def initialize(max_size = nil) @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 @@ def materialize(result) @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/sqlite/connection.rb b/lib/mini_sql/sqlite/connection.rb index e2f8f99..d293049 100644 --- a/lib/mini_sql/sqlite/connection.rb +++ b/lib/mini_sql/sqlite/connection.rb @@ -50,6 +50,12 @@ def query(sql, *params) end end + def query_decorator(decorator, sql, *params) + run(sql, *params) do |set| + deserializer_cache.materialize(set, decorator) + end + end + def escape_string(str) str.gsub("'", "''") end diff --git a/lib/mini_sql/sqlite/deserializer_cache.rb b/lib/mini_sql/sqlite/deserializer_cache.rb index e18daae..1302a01 100644 --- a/lib/mini_sql/sqlite/deserializer_cache.rb +++ b/lib/mini_sql/sqlite/deserializer_cache.rb @@ -11,7 +11,7 @@ def initialize(max_size = nil) @max_size = max_size || DEFAULT_MAX_SIZE end - def materialize(result) + def materialize(result, decorator_module = nil) key = result.columns @@ -24,6 +24,8 @@ def materialize(result) @cache.shift if @cache.length > @max_size end + materializer.include(decorator_module) if decorator_module + r = [] # quicker loop while !result.eof? diff --git a/test/mini_sql/connection_tests.rb b/test/mini_sql/connection_tests.rb index 9dd35f0..e0d8d7f 100644 --- a/test/mini_sql/connection_tests.rb +++ b/test/mini_sql/connection_tests.rb @@ -112,4 +112,15 @@ def test_to_h assert_equal({ str: 'a', num: 1 }, r) end + module ProductDecorator + def amount_price + price * quantity + end + end + + def test_query_decorator + r = @connection.query_decorator(ProductDecorator, 'select 20 price, 3 quantity').first + assert_equal(60, r.amount_price) + end + end From 0e10fffe6d3b28ca444643e2fb89b80ae90b0360 Mon Sep 17 00:00:00 2001 From: Ermolaev Andrey Date: Thu, 30 Jan 2020 20:16:24 +0300 Subject: [PATCH 2/3] update benchmark --- bench/topic_perf.rb | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bench/topic_perf.rb b/bench/topic_perf.rb index 913e1cd..7a30f9e 100644 --- a/bench/topic_perf.rb +++ b/bench/topic_perf.rb @@ -224,27 +224,27 @@ def swift_select_title_id(l = 1000) class TopicDraper < Draper::Decorator delegate :id - def title_without_hello - object.title.gsub('HELLO') + 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_without_hello - title.gsub('HELLO') + def title_bang + title + '!!!' end end class TopicArModel < Topic - def title_without_hello - title.gsub('HELLO') + def title_bang + title + '!!!' end end module TopicDecorator - def title_without_hello - title.gsub('HELLO') + def title_bang + title + '!!!' end end @@ -252,7 +252,7 @@ def title_without_hello 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_without_hello + obj.title_bang obj.id end n -= 1 @@ -262,7 +262,7 @@ def title_without_hello 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_without_hello + d_obj.title_bang d_obj.id end n -= 1 @@ -272,7 +272,7 @@ def title_without_hello 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_without_hello + d_obj.title_bang d_obj.id end n -= 1 @@ -282,7 +282,7 @@ def title_without_hello 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_without_hello + d_obj.title_bang d_obj.id end n -= 1 @@ -291,7 +291,7 @@ def title_without_hello 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.gsub('HELLO') + obj.title + '!!!' obj.id end n -= 1 @@ -300,7 +300,7 @@ def title_without_hello r.report('ar model') do |n| while n > 0 TopicArModel.limit(1000).order(:id).select(:id, :title).each do |obj| - obj.title_without_hello + obj.title_bang obj.id end n -= 1 @@ -311,12 +311,12 @@ def title_without_hello end # Comparison: -# query: 735.8 i/s -# query_decorator: 720.3 i/s - same-ish: difference falls within error -# simple_delegator: 453.4 i/s - 1.62x slower -# extend: 446.0 i/s - 1.65x slower -# draper: 378.6 i/s - 1.94x slower -# ar model: 110.0 i/s - 6.69x slower +# query_decorator: 818.2 i/s +# query: 816.5 i/s - same-ish: difference falls within error +# extend: 500.9 i/s - 1.63x slower +# simple_delegator: 477.9 i/s - 1.71x slower +# draper: 404.0 i/s - 2.03x slower +# ar model: 106.8 i/s - 7.66x slower Benchmark.ips do |r| r.report('query_hash') do |n| From bac2dca2975918ca769022eb4fc51df8d1696924 Mon Sep 17 00:00:00 2001 From: Ermolaev Andrey Date: Sun, 2 Feb 2020 15:13:55 +0300 Subject: [PATCH 3/3] add sequel to benchmark --- bench/topic_perf.rb | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/bench/topic_perf.rb b/bench/topic_perf.rb index 7a30f9e..19b3d23 100644 --- a/bench/topic_perf.rb +++ b/bench/topic_perf.rb @@ -236,6 +236,12 @@ def title_bang end end +class TopicDecoratorSequel < TopicSequel + def title_bang + title + '!!!' + end +end + class TopicArModel < Topic def title_bang title + '!!!' @@ -306,17 +312,27 @@ def title_bang 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: -# query_decorator: 818.2 i/s -# query: 816.5 i/s - same-ish: difference falls within error -# extend: 500.9 i/s - 1.63x slower -# simple_delegator: 477.9 i/s - 1.71x slower -# draper: 404.0 i/s - 2.03x slower -# ar model: 106.8 i/s - 7.66x 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|