mini_sql 0.2.2 → 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +66 -0
- data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG +22 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -0
- data/README.md +90 -2
- data/Rakefile +3 -1
- data/bench/timestamp_perf.rb +22 -21
- data/bench/topic_mysql_perf.rb +304 -0
- data/bench/topic_perf.rb +175 -11
- data/bin/console +1 -0
- data/lib/mini_sql.rb +8 -0
- data/lib/mini_sql/builder.rb +10 -2
- data/lib/mini_sql/connection.rb +17 -3
- data/lib/mini_sql/decoratable.rb +22 -0
- data/lib/mini_sql/deserializer_cache.rb +2 -0
- data/lib/mini_sql/inline_param_encoder.rb +9 -9
- data/lib/mini_sql/mysql/connection.rb +67 -0
- data/lib/mini_sql/mysql/deserializer_cache.rb +57 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +80 -3
- data/lib/mini_sql/postgres/deserializer_cache.rb +33 -13
- data/lib/mini_sql/postgres_jdbc/connection.rb +6 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
- data/lib/mini_sql/result.rb +20 -0
- data/lib/mini_sql/serializer.rb +70 -0
- data/lib/mini_sql/sqlite/connection.rb +12 -4
- data/lib/mini_sql/sqlite/deserializer_cache.rb +11 -13
- data/lib/mini_sql/version.rb +2 -1
- data/mini_sql.gemspec +15 -3
- metadata +80 -13
- data/.travis.yml +0 -21
data/bench/topic_perf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/inline'
|
2
4
|
|
3
5
|
gemfile do
|
@@ -12,6 +14,7 @@ gemfile do
|
|
12
14
|
gem 'sequel', github: 'jeremyevans/sequel'
|
13
15
|
gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
|
14
16
|
gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres'
|
17
|
+
gem 'draper'
|
15
18
|
end
|
16
19
|
|
17
20
|
require 'sequel'
|
@@ -89,7 +92,6 @@ end
|
|
89
92
|
class TopicSequel < Sequel::Model(:topics)
|
90
93
|
end
|
91
94
|
|
92
|
-
|
93
95
|
Topic.transaction do
|
94
96
|
topic = {
|
95
97
|
}
|
@@ -148,7 +150,7 @@ def pg_title_id
|
|
148
150
|
s
|
149
151
|
end
|
150
152
|
|
151
|
-
$mini_sql = MiniSql::Connection.
|
153
|
+
$mini_sql = MiniSql::Connection.get($conn)
|
152
154
|
|
153
155
|
def mini_sql_title_id
|
154
156
|
s = +""
|
@@ -184,7 +186,7 @@ def mini_sql_title_id_query_single
|
|
184
186
|
r = $mini_sql.query_single(-"select id, title from topics order by id limit 1000")
|
185
187
|
while i < r.length
|
186
188
|
s << r[i].to_s
|
187
|
-
s << r[i+1]
|
189
|
+
s << r[i + 1]
|
188
190
|
i += 2
|
189
191
|
end
|
190
192
|
s
|
@@ -193,8 +195,8 @@ end
|
|
193
195
|
# connects over unix socket
|
194
196
|
$swift = Swift::DB::Postgres.new(db: "test_db")
|
195
197
|
|
196
|
-
def swift_select_title_id(l=1000)
|
197
|
-
s =
|
198
|
+
def swift_select_title_id(l = 1000)
|
199
|
+
s = +''
|
198
200
|
i = 0
|
199
201
|
r = $swift.execute("select id, title from topics order by id limit 1000")
|
200
202
|
while i < r.selected_rows
|
@@ -218,6 +220,174 @@ results = [
|
|
218
220
|
|
219
221
|
exit(-1) unless results.uniq.length == 1
|
220
222
|
|
223
|
+
# https://github.com/drapergem/draper
|
224
|
+
class TopicDraper < Draper::Decorator
|
225
|
+
delegate :id
|
226
|
+
|
227
|
+
def title_bang
|
228
|
+
object.title + '!!!'
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html
|
233
|
+
class TopicSimpleDelegator < SimpleDelegator
|
234
|
+
def title_bang
|
235
|
+
title + '!!!'
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class TopicDecoratorSequel < TopicSequel
|
240
|
+
def title_bang
|
241
|
+
title + '!!!'
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class TopicArModel < Topic
|
246
|
+
def title_bang
|
247
|
+
title + '!!!'
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
module TopicDecorator
|
252
|
+
def title_bang
|
253
|
+
title + '!!!'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
Benchmark.ips do |r|
|
258
|
+
r.report('query_decorator') do |n|
|
259
|
+
while n > 0
|
260
|
+
$mini_sql.query_decorator(TopicDecorator, 'select id, title from topics order by id limit 1000').each do |obj|
|
261
|
+
obj.title_bang
|
262
|
+
obj.id
|
263
|
+
end
|
264
|
+
n -= 1
|
265
|
+
end
|
266
|
+
end
|
267
|
+
r.report('extend') do |n|
|
268
|
+
while n > 0
|
269
|
+
$mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
|
270
|
+
d_obj = obj.extend(TopicDecorator)
|
271
|
+
d_obj.title_bang
|
272
|
+
d_obj.id
|
273
|
+
end
|
274
|
+
n -= 1
|
275
|
+
end
|
276
|
+
end
|
277
|
+
r.report('draper') do |n|
|
278
|
+
while n > 0
|
279
|
+
$mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
|
280
|
+
d_obj = TopicDraper.new(obj)
|
281
|
+
d_obj.title_bang
|
282
|
+
d_obj.id
|
283
|
+
end
|
284
|
+
n -= 1
|
285
|
+
end
|
286
|
+
end
|
287
|
+
r.report('simple_delegator') do |n|
|
288
|
+
while n > 0
|
289
|
+
$mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
|
290
|
+
d_obj = TopicSimpleDelegator.new(obj)
|
291
|
+
d_obj.title_bang
|
292
|
+
d_obj.id
|
293
|
+
end
|
294
|
+
n -= 1
|
295
|
+
end
|
296
|
+
end
|
297
|
+
r.report('query') do |n|
|
298
|
+
while n > 0
|
299
|
+
$mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
|
300
|
+
obj.title + '!!!'
|
301
|
+
obj.id
|
302
|
+
end
|
303
|
+
n -= 1
|
304
|
+
end
|
305
|
+
end
|
306
|
+
r.report('ar model') do |n|
|
307
|
+
while n > 0
|
308
|
+
TopicArModel.limit(1000).order(:id).select(:id, :title).each do |obj|
|
309
|
+
obj.title_bang
|
310
|
+
obj.id
|
311
|
+
end
|
312
|
+
n -= 1
|
313
|
+
end
|
314
|
+
end
|
315
|
+
r.report('sequel model') do |n|
|
316
|
+
while n > 0
|
317
|
+
TopicDecoratorSequel.limit(1000).order(:id).select(:id, :title).each do |obj|
|
318
|
+
obj.title_bang
|
319
|
+
obj.id
|
320
|
+
end
|
321
|
+
n -= 1
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
r.compare!
|
326
|
+
end
|
327
|
+
|
328
|
+
# Comparison:
|
329
|
+
# query: 828.4 i/s
|
330
|
+
# query_decorator: 819.3 i/s - same-ish: difference falls within error
|
331
|
+
# sequel model: 672.4 i/s - 1.23x slower
|
332
|
+
# extend: 519.4 i/s - 1.59x slower
|
333
|
+
# simple_delegator: 496.8 i/s - 1.67x slower
|
334
|
+
# draper: 416.2 i/s - 1.99x slower
|
335
|
+
# ar model: 113.4 i/s - 7.30x slower
|
336
|
+
|
337
|
+
Benchmark.ips do |r|
|
338
|
+
r.report('query_hash') do |n|
|
339
|
+
while n > 0
|
340
|
+
$mini_sql.query_hash('select id, title from topics order by id limit 1000').each do |hash|
|
341
|
+
[hash['id'], hash['title']]
|
342
|
+
end
|
343
|
+
n -= 1
|
344
|
+
end
|
345
|
+
end
|
346
|
+
r.report('query_array') do |n|
|
347
|
+
while n > 0
|
348
|
+
$mini_sql.query_array('select id, title from topics order by id limit 1000').each do |id, title|
|
349
|
+
[id, title]
|
350
|
+
end
|
351
|
+
n -= 1
|
352
|
+
end
|
353
|
+
end
|
354
|
+
r.report('query') do |n|
|
355
|
+
while n > 0
|
356
|
+
$mini_sql.query('select id, title from topics order by id limit 1000').each do |obj|
|
357
|
+
[obj.id, obj.title]
|
358
|
+
end
|
359
|
+
n -= 1
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
r.compare!
|
364
|
+
end
|
365
|
+
|
366
|
+
# Comparison:
|
367
|
+
# query_array: 1351.6 i/s
|
368
|
+
# query: 963.8 i/s - 1.40x slower
|
369
|
+
# query_hash: 787.4 i/s - 1.72x slower
|
370
|
+
|
371
|
+
Benchmark.ips do |r|
|
372
|
+
r.report('query_single') do |n|
|
373
|
+
while n > 0
|
374
|
+
$mini_sql.query_single('select id from topics order by id limit 1000')
|
375
|
+
n -= 1
|
376
|
+
end
|
377
|
+
end
|
378
|
+
r.report('query_array') do |n|
|
379
|
+
while n > 0
|
380
|
+
$mini_sql.query_array('select id from topics order by id limit 1000').flatten
|
381
|
+
n -= 1
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
r.compare!
|
386
|
+
end
|
387
|
+
|
388
|
+
# Comparison:
|
389
|
+
# query_single: 2368.9 i/s
|
390
|
+
# query_array: 1350.1 i/s - 1.75x slower
|
221
391
|
|
222
392
|
Benchmark.ips do |r|
|
223
393
|
r.report("ar select title id") do |n|
|
@@ -271,8 +441,6 @@ Benchmark.ips do |r|
|
|
271
441
|
r.compare!
|
272
442
|
end
|
273
443
|
|
274
|
-
|
275
|
-
|
276
444
|
def wide_topic_ar
|
277
445
|
Topic.first
|
278
446
|
end
|
@@ -320,7 +488,6 @@ Benchmark.ips do |r|
|
|
320
488
|
r.compare!
|
321
489
|
end
|
322
490
|
|
323
|
-
|
324
491
|
# Comparison:
|
325
492
|
# pg select title id: 1519.7 i/s
|
326
493
|
# mini_sql query_single title id: 1335.0 i/s - 1.14x slower
|
@@ -338,10 +505,7 @@ end
|
|
338
505
|
# wide topic sequel: 5696.8 i/s - 1.31x slower
|
339
506
|
# wide topic ar: 2515.0 i/s - 2.97x slower
|
340
507
|
|
341
|
-
|
342
|
-
|
343
508
|
# to run deep analysis run
|
344
509
|
# MemoryProfiler.report do
|
345
510
|
# ar
|
346
511
|
# end.pretty_print
|
347
|
-
|
data/bin/console
CHANGED
data/lib/mini_sql.rb
CHANGED
@@ -8,6 +8,9 @@ require_relative "mini_sql/connection"
|
|
8
8
|
require_relative "mini_sql/deserializer_cache"
|
9
9
|
require_relative "mini_sql/builder"
|
10
10
|
require_relative "mini_sql/inline_param_encoder"
|
11
|
+
require_relative "mini_sql/decoratable"
|
12
|
+
require_relative "mini_sql/serializer"
|
13
|
+
require_relative "mini_sql/result"
|
11
14
|
|
12
15
|
module MiniSql
|
13
16
|
if RUBY_ENGINE == 'jruby'
|
@@ -26,5 +29,10 @@ module MiniSql
|
|
26
29
|
autoload :Connection, "mini_sql/sqlite/connection"
|
27
30
|
autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
|
28
31
|
end
|
32
|
+
|
33
|
+
module Mysql
|
34
|
+
autoload :Connection, "mini_sql/mysql/connection"
|
35
|
+
autoload :DeserializerCache, "mini_sql/mysql/deserializer_cache"
|
36
|
+
end
|
29
37
|
end
|
30
38
|
end
|
data/lib/mini_sql/builder.rb
CHANGED
@@ -57,7 +57,6 @@ class MiniSql::Builder
|
|
57
57
|
def #{m}(hash_args = nil)
|
58
58
|
hash_args = @args.merge(hash_args) if hash_args && @args
|
59
59
|
hash_args ||= @args
|
60
|
-
|
61
60
|
if hash_args
|
62
61
|
@connection.#{m}(to_sql, hash_args)
|
63
62
|
else
|
@@ -67,5 +66,14 @@ class MiniSql::Builder
|
|
67
66
|
RUBY
|
68
67
|
end
|
69
68
|
|
70
|
-
|
69
|
+
def query_decorator(decorator, hash_args = nil)
|
70
|
+
hash_args = @args.merge(hash_args) if hash_args && @args
|
71
|
+
hash_args ||= @args
|
72
|
+
if hash_args
|
73
|
+
@connection.query_decorator(decorator, to_sql, hash_args)
|
74
|
+
else
|
75
|
+
@connection.query_decorator(decorator, to_sql)
|
76
|
+
end
|
77
|
+
end
|
71
78
|
|
79
|
+
end
|
data/lib/mini_sql/connection.rb
CHANGED
@@ -4,12 +4,14 @@ module MiniSql
|
|
4
4
|
class Connection
|
5
5
|
|
6
6
|
def self.get(raw_connection, options = {})
|
7
|
-
if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
|
7
|
+
if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
|
8
8
|
Postgres::Connection.new(raw_connection, options)
|
9
9
|
elsif (defined? ::ArJdbc)
|
10
10
|
Postgres::Connection.new(raw_connection, options)
|
11
11
|
elsif (defined? ::SQLite3::Database) && (SQLite3::Database === raw_connection)
|
12
12
|
Sqlite::Connection.new(raw_connection, options)
|
13
|
+
elsif (defined? ::Mysql2::Client) && (Mysql2::Client === raw_connection)
|
14
|
+
Mysql::Connection.new(raw_connection, options)
|
13
15
|
else
|
14
16
|
raise ArgumentError, 'unknown connection type!'
|
15
17
|
end
|
@@ -29,11 +31,23 @@ module MiniSql
|
|
29
31
|
raise NotImplementedError, "must be implemented by child connection"
|
30
32
|
end
|
31
33
|
|
32
|
-
def
|
34
|
+
def query_hash(sql, *params)
|
33
35
|
raise NotImplementedError, "must be implemented by child connection"
|
34
36
|
end
|
35
37
|
|
36
|
-
def
|
38
|
+
def query_decorator(sql, *params)
|
39
|
+
raise NotImplementedError, "must be implemented by child connection"
|
40
|
+
end
|
41
|
+
|
42
|
+
def query_each(sql, *params)
|
43
|
+
raise NotImplementedError, "must be implemented by child connection"
|
44
|
+
end
|
45
|
+
|
46
|
+
def query_each_hash(sql, *params)
|
47
|
+
raise NotImplementedError, "must be implemented by child connection"
|
48
|
+
end
|
49
|
+
|
50
|
+
def exec(sql, *params)
|
37
51
|
raise NotImplementedError, "must be implemented by child connection"
|
38
52
|
end
|
39
53
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Decoratable
|
5
|
+
def decorated(mod)
|
6
|
+
@decoratorated_classes ||= {}
|
7
|
+
@decoratorated_classes[mod] ||=
|
8
|
+
Class.new(self) do
|
9
|
+
include(mod)
|
10
|
+
instance_eval <<~RUBY
|
11
|
+
def decorator
|
12
|
+
#{mod}
|
13
|
+
end
|
14
|
+
RUBY
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def decorator
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -50,19 +50,19 @@ module MiniSql
|
|
50
50
|
|
51
51
|
def quote_val(value)
|
52
52
|
case value
|
53
|
+
when String then "'#{conn.escape_string(value.to_s)}'"
|
54
|
+
when Numeric then value.to_s
|
55
|
+
when BigDecimal then value.to_s("F")
|
56
|
+
when Date, Time then "'#{quoted_date(value)}'"
|
57
|
+
when Symbol then "'#{conn.escape_string(value.to_s)}'"
|
58
|
+
when true then "true"
|
59
|
+
when false then "false"
|
60
|
+
when nil then "NULL"
|
61
|
+
when [] then "NULL"
|
53
62
|
when Array
|
54
63
|
value.map do |v|
|
55
64
|
quote_val(v)
|
56
65
|
end.join(', ')
|
57
|
-
when String
|
58
|
-
"'#{conn.escape_string(value.to_s)}'"
|
59
|
-
when true then "true"
|
60
|
-
when false then "false"
|
61
|
-
when nil then "NULL"
|
62
|
-
when BigDecimal then value.to_s("F")
|
63
|
-
when Numeric then value.to_s
|
64
|
-
when Date, Time then "'#{quoted_date(value)}'"
|
65
|
-
when Symbol then "'#{escape_string(value.to_s)}'"
|
66
66
|
else raise TypeError, "can't quote #{value.class.name}"
|
67
67
|
end
|
68
68
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Mysql
|
5
|
+
class Connection < MiniSql::Connection
|
6
|
+
attr_reader :param_encoder, :raw_connection, :deserializer_cache
|
7
|
+
|
8
|
+
def initialize(raw_connection, args = nil)
|
9
|
+
@raw_connection = raw_connection
|
10
|
+
@param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
|
11
|
+
@deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def query_single(sql, *params)
|
15
|
+
run(sql, :array, params).to_a.flatten!
|
16
|
+
end
|
17
|
+
|
18
|
+
def query_hash(sql, *params)
|
19
|
+
result = run(sql, :hash, params)
|
20
|
+
result.to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
def query_array(sql, *params)
|
24
|
+
run(sql, :array, params).to_a
|
25
|
+
end
|
26
|
+
|
27
|
+
def exec(sql, *params)
|
28
|
+
run(sql, :array, params)
|
29
|
+
raw_connection.affected_rows
|
30
|
+
end
|
31
|
+
|
32
|
+
def query(sql, *params)
|
33
|
+
result = run(sql, :array, params)
|
34
|
+
@deserializer_cache.materialize(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
def query_decorator(decorator, sql, *params)
|
38
|
+
result = run(sql, :array, params)
|
39
|
+
@deserializer_cache.materialize(result, decorator)
|
40
|
+
end
|
41
|
+
|
42
|
+
def escape_string(str)
|
43
|
+
raw_connection.escape(str)
|
44
|
+
end
|
45
|
+
|
46
|
+
def build(sql)
|
47
|
+
Builder.new(self, sql)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def run(sql, as, params)
|
53
|
+
sql = param_encoder.encode(sql, *params)
|
54
|
+
raw_connection.query(
|
55
|
+
sql,
|
56
|
+
as: as,
|
57
|
+
database_timezone: :utc,
|
58
|
+
application_timezone: :utc,
|
59
|
+
cast_booleans: true,
|
60
|
+
cast: true,
|
61
|
+
cache_rows: true,
|
62
|
+
symbolize_keys: false
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|