mini_sql 1.0.1 → 1.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/CHANGELOG +4 -0
  4. data/README.md +36 -0
  5. data/bench/builder_perf.rb +138 -0
  6. data/bench/decorator_perf.rb +143 -0
  7. data/bench/mini_sql_methods_perf.rb +80 -0
  8. data/bench/prepared_perf.rb +59 -0
  9. data/bench/shared/generate_data.rb +133 -0
  10. data/bench/topic_perf.rb +21 -327
  11. data/bench/topic_wide_perf.rb +92 -0
  12. data/lib/mini_sql.rb +17 -8
  13. data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
  14. data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
  15. data/lib/mini_sql/builder.rb +61 -30
  16. data/lib/mini_sql/inline_param_encoder.rb +4 -3
  17. data/lib/mini_sql/mysql/connection.rb +6 -0
  18. data/lib/mini_sql/mysql/deserializer_cache.rb +3 -3
  19. data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
  20. data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
  21. data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
  22. data/lib/mini_sql/postgres/connection.rb +6 -0
  23. data/lib/mini_sql/postgres/deserializer_cache.rb +5 -5
  24. data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
  25. data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
  26. data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
  27. data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +3 -3
  28. data/lib/mini_sql/result.rb +10 -0
  29. data/lib/mini_sql/serializer.rb +29 -15
  30. data/lib/mini_sql/sqlite/connection.rb +9 -1
  31. data/lib/mini_sql/sqlite/deserializer_cache.rb +3 -3
  32. data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
  33. data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
  34. data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
  35. data/lib/mini_sql/version.rb +1 -1
  36. metadata +19 -2
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GenerateData
4
+ class ::Topic < ActiveRecord::Base;
5
+ belongs_to :user
6
+ belongs_to :category
7
+ end
8
+ class ::User < ActiveRecord::Base; end
9
+ class ::Category < ActiveRecord::Base; end
10
+
11
+ def initialize(count_records:)
12
+ @count_records = count_records
13
+ end
14
+
15
+ def call
16
+ conn_settings = {
17
+ password: 'postgres',
18
+ user: 'postgres',
19
+ host: 'localhost'
20
+ }
21
+
22
+ db_conn = conn_settings.merge(database: "test_db", adapter: "postgresql")
23
+
24
+ pg = PG::Connection.new(conn_settings)
25
+ pg.exec "DROP DATABASE IF EXISTS test_db"
26
+ pg.exec "CREATE DATABASE test_db"
27
+ pg.close
28
+
29
+ ActiveRecord::Base.establish_connection(db_conn)
30
+ pg = ActiveRecord::Base.connection.raw_connection
31
+
32
+ pg.exec <<~SQL
33
+ drop table if exists topics;
34
+ drop table if exists users;
35
+ drop table if exists categories;
36
+ CREATE TABLE topics (
37
+ id integer NOT NULL PRIMARY KEY,
38
+ title character varying NOT NULL,
39
+ last_posted_at timestamp without time zone,
40
+ created_at timestamp without time zone NOT NULL,
41
+ updated_at timestamp without time zone NOT NULL,
42
+ views integer DEFAULT 0 NOT NULL,
43
+ posts_count integer DEFAULT 0 NOT NULL,
44
+ user_id integer,
45
+ last_post_user_id integer NOT NULL,
46
+ reply_count integer DEFAULT 0 NOT NULL,
47
+ featured_user1_id integer,
48
+ featured_user2_id integer,
49
+ featured_user3_id integer,
50
+ avg_time integer,
51
+ deleted_at timestamp without time zone,
52
+ highest_post_number integer DEFAULT 0 NOT NULL,
53
+ image_url character varying,
54
+ like_count integer DEFAULT 0 NOT NULL,
55
+ incoming_link_count integer DEFAULT 0 NOT NULL,
56
+ category_id integer,
57
+ visible boolean DEFAULT true NOT NULL,
58
+ moderator_posts_count integer DEFAULT 0 NOT NULL,
59
+ closed boolean DEFAULT false NOT NULL,
60
+ archived boolean DEFAULT false NOT NULL,
61
+ bumped_at timestamp without time zone NOT NULL,
62
+ has_summary boolean DEFAULT false NOT NULL,
63
+ vote_count integer DEFAULT 0 NOT NULL,
64
+ archetype character varying DEFAULT 'regular'::character varying NOT NULL,
65
+ featured_user4_id integer,
66
+ notify_moderators_count integer DEFAULT 0 NOT NULL,
67
+ spam_count integer DEFAULT 0 NOT NULL,
68
+ pinned_at timestamp without time zone,
69
+ score double precision,
70
+ percent_rank double precision DEFAULT 1.0 NOT NULL,
71
+ subtype character varying,
72
+ slug character varying,
73
+ deleted_by_id integer,
74
+ participant_count integer DEFAULT 1,
75
+ word_count integer,
76
+ excerpt character varying(1000),
77
+ pinned_globally boolean DEFAULT false NOT NULL,
78
+ pinned_until timestamp without time zone,
79
+ fancy_title character varying(400),
80
+ highest_staff_post_number integer DEFAULT 0 NOT NULL,
81
+ featured_link character varying
82
+ );
83
+
84
+ CREATE TABLE users (
85
+ id integer NOT NULL PRIMARY KEY,
86
+ first_name character varying NOT NULL,
87
+ last_name character varying NOT NULL
88
+ );
89
+ CREATE TABLE categories (
90
+ id integer NOT NULL PRIMARY KEY,
91
+ name character varying NOT NULL,
92
+ title character varying NOT NULL,
93
+ description character varying NOT NULL
94
+ );
95
+ SQL
96
+
97
+ generate_table(Topic)
98
+ generate_table(User)
99
+ generate_table(Category)
100
+
101
+ pg.exec <<~SQL
102
+ CREATE INDEX user_id ON topics USING btree (user_id);
103
+ CREATE INDEX category_id ON topics USING btree (category_id);
104
+ SQL
105
+
106
+ pg.exec "vacuum full analyze topics"
107
+ pg.exec "vacuum full analyze users"
108
+ pg.exec "vacuum full analyze categories"
109
+
110
+ [ActiveRecord::Base.connection, db_conn]
111
+ end
112
+
113
+ def generate_table(klass)
114
+ data =
115
+ @count_records.times.map do |id|
116
+ topic = { id: id }
117
+ klass.columns.each do |c|
118
+ topic[c.name.to_sym] = value_from_type(c.type)
119
+ end
120
+ topic
121
+ end
122
+ klass.insert_all(data)
123
+ end
124
+
125
+ def value_from_type(type)
126
+ case type
127
+ when :integer then rand(1000)
128
+ when :datetime then Time.now
129
+ when :boolean then false
130
+ else "HELLO WORLD" * 2
131
+ end
132
+ end
133
+ end
data/bench/topic_perf.rb CHANGED
@@ -13,8 +13,9 @@ gemfile do
13
13
  gem 'benchmark-ips'
14
14
  gem 'sequel', github: 'jeremyevans/sequel'
15
15
  gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
16
- gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres'
16
+ gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres' # sudo apt-get install uuid-dev
17
17
  gem 'draper'
18
+ gem 'pry'
18
19
  end
19
20
 
20
21
  require 'sequel'
@@ -23,68 +24,14 @@ require 'memory_profiler'
23
24
  require 'benchmark/ips'
24
25
  require 'mini_sql'
25
26
 
26
- ActiveRecord::Base.establish_connection(
27
- :adapter => "postgresql",
28
- :database => "test_db"
29
- )
27
+ require '../mini_sql/bench/shared/generate_data'
30
28
 
31
- DB = Sequel.postgres('test_db')
32
-
33
- pg = ActiveRecord::Base.connection.raw_connection
34
-
35
- pg.async_exec <<SQL
36
- drop table if exists topics
37
- SQL
38
-
39
- pg.async_exec <<SQL
40
- CREATE TABLE topics (
41
- id integer NOT NULL PRIMARY KEY,
42
- title character varying NOT NULL,
43
- last_posted_at timestamp without time zone,
44
- created_at timestamp without time zone NOT NULL,
45
- updated_at timestamp without time zone NOT NULL,
46
- views integer DEFAULT 0 NOT NULL,
47
- posts_count integer DEFAULT 0 NOT NULL,
48
- user_id integer,
49
- last_post_user_id integer NOT NULL,
50
- reply_count integer DEFAULT 0 NOT NULL,
51
- featured_user1_id integer,
52
- featured_user2_id integer,
53
- featured_user3_id integer,
54
- avg_time integer,
55
- deleted_at timestamp without time zone,
56
- highest_post_number integer DEFAULT 0 NOT NULL,
57
- image_url character varying,
58
- like_count integer DEFAULT 0 NOT NULL,
59
- incoming_link_count integer DEFAULT 0 NOT NULL,
60
- category_id integer,
61
- visible boolean DEFAULT true NOT NULL,
62
- moderator_posts_count integer DEFAULT 0 NOT NULL,
63
- closed boolean DEFAULT false NOT NULL,
64
- archived boolean DEFAULT false NOT NULL,
65
- bumped_at timestamp without time zone NOT NULL,
66
- has_summary boolean DEFAULT false NOT NULL,
67
- vote_count integer DEFAULT 0 NOT NULL,
68
- archetype character varying DEFAULT 'regular'::character varying NOT NULL,
69
- featured_user4_id integer,
70
- notify_moderators_count integer DEFAULT 0 NOT NULL,
71
- spam_count integer DEFAULT 0 NOT NULL,
72
- pinned_at timestamp without time zone,
73
- score double precision,
74
- percent_rank double precision DEFAULT 1.0 NOT NULL,
75
- subtype character varying,
76
- slug character varying,
77
- deleted_by_id integer,
78
- participant_count integer DEFAULT 1,
79
- word_count integer,
80
- excerpt character varying(1000),
81
- pinned_globally boolean DEFAULT false NOT NULL,
82
- pinned_until timestamp without time zone,
83
- fancy_title character varying(400),
84
- highest_staff_post_number integer DEFAULT 0 NOT NULL,
85
- featured_link character varying
86
- )
87
- SQL
29
+ ar_connection, conn_config = GenerateData.new(count_records: 1_000).call
30
+ PG_CONN = ar_connection.raw_connection
31
+ MINI_SQL = MiniSql::Connection.get(PG_CONN)
32
+ DB = Sequel.connect(ar_connection.instance_variable_get(:@config).slice(:database, :user, :password, :host, :adapter))
33
+ # connects over unix socket
34
+ SWIFT = Swift::DB::Postgres.new(db: conn_config[:database], user: conn_config[:user], password: conn_config[:password], host: conn_config[:host])
88
35
 
89
36
  class Topic < ActiveRecord::Base
90
37
  end
@@ -92,26 +39,6 @@ end
92
39
  class TopicSequel < Sequel::Model(:topics)
93
40
  end
94
41
 
95
- Topic.transaction do
96
- topic = {
97
- }
98
- Topic.columns.each do |c|
99
- topic[c.name.to_sym] = case c.type
100
- when :integer then 1
101
- when :datetime then Time.now
102
- when :boolean then false
103
- else "HELLO WORLD" * 2
104
- end
105
- end
106
-
107
- 1000.times do |id|
108
- topic[:id] = id
109
- Topic.create!(topic)
110
- end
111
- end
112
-
113
- $conn = ActiveRecord::Base.connection.raw_connection
114
-
115
42
  def ar_title_id_pluck
116
43
  s = +""
117
44
  Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
@@ -133,7 +60,7 @@ end
133
60
  def pg_title_id
134
61
  s = +""
135
62
  # use the safe pattern here
136
- r = $conn.async_exec(-"select id, title from topics order by id limit 1000")
63
+ r = PG_CONN.async_exec(-"select id, title from topics order by id limit 1000")
137
64
 
138
65
  # this seems fastest despite extra arrays, cause array of arrays is generated
139
66
  # in c code
@@ -150,11 +77,9 @@ def pg_title_id
150
77
  s
151
78
  end
152
79
 
153
- $mini_sql = MiniSql::Connection.get($conn)
154
-
155
80
  def mini_sql_title_id
156
81
  s = +""
157
- $mini_sql.query(-"select id, title from topics order by id limit 1000").each do |t|
82
+ MINI_SQL.query(-"select id, title from topics order by id limit 1000").each do |t|
158
83
  s << t.id.to_s
159
84
  s << t.title
160
85
  end
@@ -183,7 +108,7 @@ end
183
108
  def mini_sql_title_id_query_single
184
109
  s = +""
185
110
  i = 0
186
- r = $mini_sql.query_single(-"select id, title from topics order by id limit 1000")
111
+ r = MINI_SQL.query_single(-"select id, title from topics order by id limit 1000")
187
112
  while i < r.length
188
113
  s << r[i].to_s
189
114
  s << r[i + 1]
@@ -192,13 +117,10 @@ def mini_sql_title_id_query_single
192
117
  s
193
118
  end
194
119
 
195
- # connects over unix socket
196
- $swift = Swift::DB::Postgres.new(db: "test_db")
197
-
198
120
  def swift_select_title_id(l = 1000)
199
121
  s = +''
200
122
  i = 0
201
- r = $swift.execute("select id, title from topics order by id limit 1000")
123
+ r = SWIFT.execute("select id, title from topics order by id limit 1000")
202
124
  while i < r.selected_rows
203
125
  s << r.get(i, 0).to_s
204
126
  s << r.get(i, 1)
@@ -220,175 +142,6 @@ results = [
220
142
 
221
143
  exit(-1) unless results.uniq.length == 1
222
144
 
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
391
-
392
145
  Benchmark.ips do |r|
393
146
  r.report("ar select title id") do |n|
394
147
  while n > 0
@@ -441,71 +194,12 @@ Benchmark.ips do |r|
441
194
  r.compare!
442
195
  end
443
196
 
444
- def wide_topic_ar
445
- Topic.first
446
- end
447
-
448
- def wide_topic_pg
449
- r = $conn.async_exec("select * from topics limit 1")
450
- row = r.first
451
- r.clear
452
- row
453
- end
454
-
455
- def wide_topic_sequel
456
- TopicSequel.first
457
- end
458
-
459
- def wide_topic_mini_sql
460
- $conn.query("select * from topics limit 1").first
461
- end
462
-
463
- Benchmark.ips do |r|
464
- r.report("wide topic ar") do |n|
465
- while n > 0
466
- wide_topic_ar
467
- n -= 1
468
- end
469
- end
470
- r.report("wide topic sequel") do |n|
471
- while n > 0
472
- wide_topic_sequel
473
- n -= 1
474
- end
475
- end
476
- r.report("wide topic pg") do |n|
477
- while n > 0
478
- wide_topic_pg
479
- n -= 1
480
- end
481
- end
482
- r.report("wide topic mini sql") do |n|
483
- while n > 0
484
- wide_topic_mini_sql
485
- n -= 1
486
- end
487
- end
488
- r.compare!
489
- end
490
-
491
197
  # Comparison:
492
- # pg select title id: 1519.7 i/s
493
- # mini_sql query_single title id: 1335.0 i/s - 1.14x slower
494
- # sequel title id pluck: 1261.6 i/s - 1.20x slower
495
- # mini_sql select title id: 1188.6 i/s - 1.28x slower
496
- # swift title id: 1077.5 i/s - 1.41x slower
497
- # sequel title id select: 969.7 i/s - 1.57x slower
498
- # ar select title id pluck: 738.7 i/s - 2.06x slower
499
- # ar select title id: 149.6 i/s - 10.16x slower
500
- #
501
- #
502
- # Comparison:
503
- # wide topic pg: 7474.0 i/s
504
- # wide topic mini sql: 7355.2 i/s - same-ish: difference falls within error
505
- # wide topic sequel: 5696.8 i/s - 1.31x slower
506
- # wide topic ar: 2515.0 i/s - 2.97x slower
507
-
508
- # to run deep analysis run
509
- # MemoryProfiler.report do
510
- # ar
511
- # end.pretty_print
198
+ # pg select title id: 1315.1 i/s
199
+ # swift title id: 1268.4 i/s - same-ish: difference falls within error
200
+ # mini_sql query_single title id: 1206.3 i/s - same-ish: difference falls within error
201
+ # mini_sql select title id: 1063.8 i/s - 1.24x (± 0.00) slower
202
+ # sequel title id pluck: 1054.5 i/s - 1.25x (± 0.00) slower
203
+ # sequel title id select: 814.1 i/s - 1.62x (± 0.00) slower
204
+ # ar select title id pluck: 667.7 i/s - 1.97x (± 0.00) slower
205
+ # ar select title id: 215.8 i/s - 6.09x (± 0.00) slower