Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

query_decorator #13

Merged
merged 3 commits into from Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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