mini_sql 0.2.4 → 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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +66 -0
  3. data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
  4. data/.rubocop.yml +8 -0
  5. data/CHANGELOG +22 -0
  6. data/Gemfile +3 -1
  7. data/Guardfile +2 -0
  8. data/README.md +125 -1
  9. data/Rakefile +3 -1
  10. data/bench/builder_perf.rb +138 -0
  11. data/bench/decorator_perf.rb +143 -0
  12. data/bench/mini_sql_methods_perf.rb +80 -0
  13. data/bench/prepared_perf.rb +59 -0
  14. data/bench/shared/generate_data.rb +133 -0
  15. data/bench/timestamp_perf.rb +22 -21
  16. data/bench/topic_mysql_perf.rb +1 -7
  17. data/bench/topic_perf.rb +27 -169
  18. data/bench/topic_wide_perf.rb +92 -0
  19. data/bin/console +1 -0
  20. data/lib/mini_sql.rb +20 -8
  21. data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
  22. data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
  23. data/lib/mini_sql/builder.rb +64 -24
  24. data/lib/mini_sql/connection.rb +15 -3
  25. data/lib/mini_sql/decoratable.rb +22 -0
  26. data/lib/mini_sql/deserializer_cache.rb +2 -0
  27. data/lib/mini_sql/inline_param_encoder.rb +12 -13
  28. data/lib/mini_sql/mysql/connection.rb +18 -3
  29. data/lib/mini_sql/mysql/deserializer_cache.rb +14 -16
  30. data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
  31. data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
  32. data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
  33. data/lib/mini_sql/postgres/coders.rb +2 -0
  34. data/lib/mini_sql/postgres/connection.rb +89 -0
  35. data/lib/mini_sql/postgres/deserializer_cache.rb +36 -16
  36. data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
  37. data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
  38. data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
  39. data/lib/mini_sql/postgres_jdbc/connection.rb +8 -1
  40. data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
  41. data/lib/mini_sql/result.rb +30 -0
  42. data/lib/mini_sql/serializer.rb +84 -0
  43. data/lib/mini_sql/sqlite/connection.rb +20 -2
  44. data/lib/mini_sql/sqlite/deserializer_cache.rb +14 -16
  45. data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
  46. data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
  47. data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
  48. data/lib/mini_sql/version.rb +1 -1
  49. data/mini_sql.gemspec +7 -2
  50. metadata +75 -11
  51. data/.travis.yml +0 -26
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
@@ -11,7 +13,9 @@ gemfile do
11
13
  gem 'benchmark-ips'
12
14
  gem 'sequel', github: 'jeremyevans/sequel'
13
15
  gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
14
- 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
+ gem 'draper'
18
+ gem 'pry'
15
19
  end
16
20
 
17
21
  require 'sequel'
@@ -20,68 +24,14 @@ require 'memory_profiler'
20
24
  require 'benchmark/ips'
21
25
  require 'mini_sql'
22
26
 
23
- ActiveRecord::Base.establish_connection(
24
- :adapter => "postgresql",
25
- :database => "test_db"
26
- )
27
-
28
- DB = Sequel.postgres('test_db')
29
-
30
- pg = ActiveRecord::Base.connection.raw_connection
31
-
32
- pg.async_exec <<SQL
33
- drop table if exists topics
34
- SQL
27
+ require '../mini_sql/bench/shared/generate_data'
35
28
 
36
- pg.async_exec <<SQL
37
- CREATE TABLE topics (
38
- id integer NOT NULL PRIMARY KEY,
39
- title character varying NOT NULL,
40
- last_posted_at timestamp without time zone,
41
- created_at timestamp without time zone NOT NULL,
42
- updated_at timestamp without time zone NOT NULL,
43
- views integer DEFAULT 0 NOT NULL,
44
- posts_count integer DEFAULT 0 NOT NULL,
45
- user_id integer,
46
- last_post_user_id integer NOT NULL,
47
- reply_count integer DEFAULT 0 NOT NULL,
48
- featured_user1_id integer,
49
- featured_user2_id integer,
50
- featured_user3_id integer,
51
- avg_time integer,
52
- deleted_at timestamp without time zone,
53
- highest_post_number integer DEFAULT 0 NOT NULL,
54
- image_url character varying,
55
- like_count integer DEFAULT 0 NOT NULL,
56
- incoming_link_count integer DEFAULT 0 NOT NULL,
57
- category_id integer,
58
- visible boolean DEFAULT true NOT NULL,
59
- moderator_posts_count integer DEFAULT 0 NOT NULL,
60
- closed boolean DEFAULT false NOT NULL,
61
- archived boolean DEFAULT false NOT NULL,
62
- bumped_at timestamp without time zone NOT NULL,
63
- has_summary boolean DEFAULT false NOT NULL,
64
- vote_count integer DEFAULT 0 NOT NULL,
65
- archetype character varying DEFAULT 'regular'::character varying NOT NULL,
66
- featured_user4_id integer,
67
- notify_moderators_count integer DEFAULT 0 NOT NULL,
68
- spam_count integer DEFAULT 0 NOT NULL,
69
- pinned_at timestamp without time zone,
70
- score double precision,
71
- percent_rank double precision DEFAULT 1.0 NOT NULL,
72
- subtype character varying,
73
- slug character varying,
74
- deleted_by_id integer,
75
- participant_count integer DEFAULT 1,
76
- word_count integer,
77
- excerpt character varying(1000),
78
- pinned_globally boolean DEFAULT false NOT NULL,
79
- pinned_until timestamp without time zone,
80
- fancy_title character varying(400),
81
- highest_staff_post_number integer DEFAULT 0 NOT NULL,
82
- featured_link character varying
83
- )
84
- 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])
85
35
 
86
36
  class Topic < ActiveRecord::Base
87
37
  end
@@ -89,27 +39,6 @@ end
89
39
  class TopicSequel < Sequel::Model(:topics)
90
40
  end
91
41
 
92
-
93
- Topic.transaction do
94
- topic = {
95
- }
96
- Topic.columns.each do |c|
97
- topic[c.name.to_sym] = case c.type
98
- when :integer then 1
99
- when :datetime then Time.now
100
- when :boolean then false
101
- else "HELLO WORLD" * 2
102
- end
103
- end
104
-
105
- 1000.times do |id|
106
- topic[:id] = id
107
- Topic.create!(topic)
108
- end
109
- end
110
-
111
- $conn = ActiveRecord::Base.connection.raw_connection
112
-
113
42
  def ar_title_id_pluck
114
43
  s = +""
115
44
  Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
@@ -131,7 +60,7 @@ end
131
60
  def pg_title_id
132
61
  s = +""
133
62
  # use the safe pattern here
134
- 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")
135
64
 
136
65
  # this seems fastest despite extra arrays, cause array of arrays is generated
137
66
  # in c code
@@ -148,11 +77,9 @@ def pg_title_id
148
77
  s
149
78
  end
150
79
 
151
- $mini_sql = MiniSql::Connection.get($conn)
152
-
153
80
  def mini_sql_title_id
154
81
  s = +""
155
- $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|
156
83
  s << t.id.to_s
157
84
  s << t.title
158
85
  end
@@ -181,22 +108,19 @@ end
181
108
  def mini_sql_title_id_query_single
182
109
  s = +""
183
110
  i = 0
184
- 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")
185
112
  while i < r.length
186
113
  s << r[i].to_s
187
- s << r[i+1]
114
+ s << r[i + 1]
188
115
  i += 2
189
116
  end
190
117
  s
191
118
  end
192
119
 
193
- # connects over unix socket
194
- $swift = Swift::DB::Postgres.new(db: "test_db")
195
-
196
- def swift_select_title_id(l=1000)
197
- s = ""
120
+ def swift_select_title_id(l = 1000)
121
+ s = +''
198
122
  i = 0
199
- 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")
200
124
  while i < r.selected_rows
201
125
  s << r.get(i, 0).to_s
202
126
  s << r.get(i, 1)
@@ -218,7 +142,6 @@ results = [
218
142
 
219
143
  exit(-1) unless results.uniq.length == 1
220
144
 
221
-
222
145
  Benchmark.ips do |r|
223
146
  r.report("ar select title id") do |n|
224
147
  while n > 0
@@ -271,77 +194,12 @@ Benchmark.ips do |r|
271
194
  r.compare!
272
195
  end
273
196
 
274
-
275
-
276
- def wide_topic_ar
277
- Topic.first
278
- end
279
-
280
- def wide_topic_pg
281
- r = $conn.async_exec("select * from topics limit 1")
282
- row = r.first
283
- r.clear
284
- row
285
- end
286
-
287
- def wide_topic_sequel
288
- TopicSequel.first
289
- end
290
-
291
- def wide_topic_mini_sql
292
- $conn.query("select * from topics limit 1").first
293
- end
294
-
295
- Benchmark.ips do |r|
296
- r.report("wide topic ar") do |n|
297
- while n > 0
298
- wide_topic_ar
299
- n -= 1
300
- end
301
- end
302
- r.report("wide topic sequel") do |n|
303
- while n > 0
304
- wide_topic_sequel
305
- n -= 1
306
- end
307
- end
308
- r.report("wide topic pg") do |n|
309
- while n > 0
310
- wide_topic_pg
311
- n -= 1
312
- end
313
- end
314
- r.report("wide topic mini sql") do |n|
315
- while n > 0
316
- wide_topic_mini_sql
317
- n -= 1
318
- end
319
- end
320
- r.compare!
321
- end
322
-
323
-
324
- # Comparison:
325
- # pg select title id: 1519.7 i/s
326
- # mini_sql query_single title id: 1335.0 i/s - 1.14x slower
327
- # sequel title id pluck: 1261.6 i/s - 1.20x slower
328
- # mini_sql select title id: 1188.6 i/s - 1.28x slower
329
- # swift title id: 1077.5 i/s - 1.41x slower
330
- # sequel title id select: 969.7 i/s - 1.57x slower
331
- # ar select title id pluck: 738.7 i/s - 2.06x slower
332
- # ar select title id: 149.6 i/s - 10.16x slower
333
- #
334
- #
335
197
  # Comparison:
336
- # wide topic pg: 7474.0 i/s
337
- # wide topic mini sql: 7355.2 i/s - same-ish: difference falls within error
338
- # wide topic sequel: 5696.8 i/s - 1.31x slower
339
- # wide topic ar: 2515.0 i/s - 2.97x slower
340
-
341
-
342
-
343
- # to run deep analysis run
344
- # MemoryProfiler.report do
345
- # ar
346
- # end.pretty_print
347
-
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
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'pg', github: 'ged/ruby-pg'
8
+ gem 'mini_sql', path: '../'
9
+ gem 'activesupport'
10
+ gem 'activerecord'
11
+ gem 'activemodel'
12
+ gem 'memory_profiler'
13
+ gem 'benchmark-ips'
14
+ gem 'sequel', github: 'jeremyevans/sequel'
15
+ gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
16
+ gem 'draper'
17
+ gem 'pry'
18
+ end
19
+
20
+ require 'sequel'
21
+ require 'active_record'
22
+ require 'memory_profiler'
23
+ require 'benchmark/ips'
24
+ require 'mini_sql'
25
+
26
+ require '../mini_sql/bench/shared/generate_data'
27
+
28
+ ar_connection, conn_config = GenerateData.new(count_records: 1_000).call
29
+ PG_CONN = ar_connection.raw_connection
30
+ MINI_SQL = MiniSql::Connection.get(PG_CONN)
31
+ DB = Sequel.connect(ar_connection.instance_variable_get(:@config).slice(:database, :user, :password, :host, :adapter))
32
+
33
+ class Topic < ActiveRecord::Base
34
+ end
35
+
36
+ class TopicSequel < Sequel::Model(:topics)
37
+ end
38
+
39
+ def wide_topic_ar
40
+ Topic.first
41
+ end
42
+
43
+ def wide_topic_pg
44
+ r = PG_CONN.async_exec("select * from topics limit 1")
45
+ row = r.first
46
+ r.clear
47
+ row
48
+ end
49
+
50
+ def wide_topic_sequel
51
+ TopicSequel.first
52
+ end
53
+
54
+ def wide_topic_mini_sql
55
+ PG_CONN.query("select * from topics limit 1").first
56
+ end
57
+
58
+ Benchmark.ips do |r|
59
+ r.report("wide topic ar") do |n|
60
+ while n > 0
61
+ wide_topic_ar
62
+ n -= 1
63
+ end
64
+ end
65
+ r.report("wide topic sequel") do |n|
66
+ while n > 0
67
+ wide_topic_sequel
68
+ n -= 1
69
+ end
70
+ end
71
+ r.report("wide topic pg") do |n|
72
+ while n > 0
73
+ wide_topic_pg
74
+ n -= 1
75
+ end
76
+ end
77
+ r.report("wide topic mini sql") do |n|
78
+ while n > 0
79
+ wide_topic_mini_sql
80
+ n -= 1
81
+ end
82
+ end
83
+ r.compare!
84
+ end
85
+
86
+
87
+ #
88
+ # Comparison:
89
+ # wide topic pg: 6974.6 i/s
90
+ # wide topic mini sql: 6760.9 i/s - same-ish: difference falls within error
91
+ # wide topic sequel: 5050.5 i/s - 1.38x (± 0.00) slower
92
+ # wide topic ar: 1565.4 i/s - 4.46x (± 0.00) slower
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "mini_sql"
data/lib/mini_sql.rb CHANGED
@@ -8,28 +8,40 @@ 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'
14
17
  module Postgres
15
- autoload :Connection, "mini_sql/postgres_jdbc/connection"
18
+ autoload :Connection, "mini_sql/postgres_jdbc/connection"
16
19
  autoload :DeserializerCache, "mini_sql/postgres_jdbc/deserializer_cache"
17
20
  end
18
21
  else
19
22
  module Postgres
20
- autoload :Coders, "mini_sql/postgres/coders"
21
- autoload :Connection, "mini_sql/postgres/connection"
22
- autoload :DeserializerCache, "mini_sql/postgres/deserializer_cache"
23
+ autoload :Coders, "mini_sql/postgres/coders"
24
+ autoload :Connection, "mini_sql/postgres/connection"
25
+ autoload :DeserializerCache, "mini_sql/postgres/deserializer_cache"
26
+ autoload :PreparedConnection, "mini_sql/postgres/prepared_connection"
27
+ autoload :PreparedCache, "mini_sql/postgres/prepared_cache"
28
+ autoload :PreparedBinds, "mini_sql/postgres/prepared_binds"
23
29
  end
24
30
 
25
31
  module Sqlite
26
- autoload :Connection, "mini_sql/sqlite/connection"
27
- autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
32
+ autoload :Connection, "mini_sql/sqlite/connection"
33
+ autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
34
+ autoload :PreparedCache, "mini_sql/sqlite/prepared_cache"
35
+ autoload :PreparedBinds, "mini_sql/sqlite/prepared_binds"
36
+ autoload :PreparedConnection, "mini_sql/sqlite/prepared_connection"
28
37
  end
29
38
 
30
39
  module Mysql
31
- autoload :Connection, "mini_sql/mysql/connection"
32
- autoload :DeserializerCache, "mini_sql/mysql/deserializer_cache"
40
+ autoload :Connection, "mini_sql/mysql/connection"
41
+ autoload :DeserializerCache, "mini_sql/mysql/deserializer_cache"
42
+ autoload :PreparedCache, "mini_sql/mysql/prepared_cache"
43
+ autoload :PreparedBinds, "mini_sql/mysql/prepared_binds"
44
+ autoload :PreparedConnection, "mini_sql/mysql/prepared_connection"
33
45
  end
34
46
  end
35
47
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Abstract
5
+ class PreparedBinds
6
+
7
+ # For compatibility with Active Record
8
+ BindName = Struct.new(:name)
9
+
10
+ def bind(sql, *params)
11
+ if Hash === (hash = params[0])
12
+ bind_hash(sql, hash)
13
+ else
14
+ bind_array(sql, params)
15
+ end
16
+ end
17
+
18
+ def bind_hash(sql, hash)
19
+ sql = sql.dup
20
+ binds = []
21
+ bind_names = []
22
+ i = 0
23
+
24
+ hash.each do |k, v|
25
+ sql.gsub!(":#{k}") do
26
+ # ignore ::int and stuff like that
27
+ # $` is previous to match
28
+ if $` && $`[-1] != ":"
29
+ array_wrap(v).map do |vv|
30
+ binds << vv
31
+ bind_names << [BindName.new(k)]
32
+ bind_output(i += 1)
33
+ end.join(', ')
34
+ else
35
+ ":#{k}"
36
+ end
37
+ end
38
+ end
39
+ [sql, binds, bind_names]
40
+ end
41
+
42
+ def bind_array(sql, array)
43
+ sql = sql.dup
44
+ param_i = 0
45
+ i = 0
46
+ binds = []
47
+ bind_names = []
48
+ sql.gsub!("?") do
49
+ param_i += 1
50
+ array_wrap(array[param_i - 1]).map do |vv|
51
+ binds << vv
52
+ i += 1
53
+ bind_names << [BindName.new("$#{i}")]
54
+ bind_output(i)
55
+ end.join(', ')
56
+ end
57
+ [sql, binds, bind_names]
58
+ end
59
+
60
+ def array_wrap(object)
61
+ if object.respond_to?(:to_ary)
62
+ object.to_ary || [object]
63
+ else
64
+ [object]
65
+ end
66
+ end
67
+
68
+ def bind_output(_)
69
+ raise NotImplementedError, "must be implemented by specific database driver"
70
+ end
71
+
72
+ end
73
+ end
74
+ end