Skip to content

Commit

Permalink
FEATURE: add support query_decorator (#13)
Browse files Browse the repository at this point in the history
query_decorator can be used to grab specialized object instances implementing custom decorators
  • Loading branch information
ermolaev committed Feb 7, 2020
1 parent 4a940a5 commit 2b147e8
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 24 deletions.
11 changes: 11 additions & 0 deletions README.md
Expand Up @@ -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]

Expand Down
131 changes: 112 additions & 19 deletions bench/topic_perf.rb
Expand Up @@ -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'
Expand Down Expand Up @@ -219,27 +220,119 @@ 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_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|
Expand Down
5 changes: 5 additions & 0 deletions lib/mini_sql/mysql/connection.rb
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion lib/mini_sql/mysql/deserializer_cache.rb
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions lib/mini_sql/postgres/connection.rb
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lib/mini_sql/postgres/deserializer_cache.rb
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions lib/mini_sql/postgres_jdbc/connection.rb
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion lib/mini_sql/postgres_jdbc/deserializer_cache.rb
Expand Up @@ -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

Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions lib/mini_sql/sqlite/connection.rb
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion lib/mini_sql/sqlite/deserializer_cache.rb
Expand Up @@ -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

Expand All @@ -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?
Expand Down
11 changes: 11 additions & 0 deletions test/mini_sql/connection_tests.rb
Expand Up @@ -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

0 comments on commit 2b147e8

Please sign in to comment.