mini_sql 0.2.1 → 0.3
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/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +8 -1
- data/CHANGELOG +20 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -0
- data/README.md +90 -2
- data/Rakefile +10 -2
- 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 +20 -8
- data/lib/mini_sql/builder.rb +0 -2
- data/lib/mini_sql/connection.rb +18 -2
- 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 +69 -0
- data/lib/mini_sql/mysql/deserializer_cache.rb +63 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +83 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +29 -3
- data/lib/mini_sql/postgres_jdbc/connection.rb +102 -0
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +71 -0
- data/lib/mini_sql/sqlite/connection.rb +11 -1
- data/lib/mini_sql/sqlite/deserializer_cache.rb +7 -3
- data/lib/mini_sql/version.rb +2 -1
- data/mini_sql.gemspec +23 -5
- metadata +78 -26
data/Rakefile
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
4
|
require "rake/testtask"
|
3
5
|
|
6
|
+
if RUBY_ENGINE == 'jruby' # Excluding sqlite3 tests
|
7
|
+
test_glob = "test/**/{inline_param_encoder_test.rb,postgres/*_test.rb}"
|
8
|
+
else
|
9
|
+
test_glob = "test/**/*_test.rb"
|
10
|
+
end
|
11
|
+
|
4
12
|
Rake::TestTask.new(:test) do |t|
|
5
13
|
t.libs << "test"
|
6
14
|
t.libs << "lib"
|
7
|
-
t.test_files = FileList[
|
15
|
+
t.test_files = FileList[test_glob]
|
8
16
|
end
|
9
17
|
|
10
|
-
task :
|
18
|
+
task default: :test
|
data/bench/timestamp_perf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/inline'
|
2
4
|
|
3
5
|
gemfile do
|
@@ -21,8 +23,8 @@ require 'benchmark/ips'
|
|
21
23
|
require 'mini_sql'
|
22
24
|
|
23
25
|
ActiveRecord::Base.establish_connection(
|
24
|
-
:
|
25
|
-
:
|
26
|
+
adapter: "postgresql",
|
27
|
+
database: "test_db"
|
26
28
|
)
|
27
29
|
|
28
30
|
Sequel.default_timezone = :utc
|
@@ -47,20 +49,20 @@ SQL
|
|
47
49
|
class Timestamp < ActiveRecord::Base
|
48
50
|
end
|
49
51
|
|
50
|
-
class TimestampSequel< Sequel::Model(:timestamps)
|
52
|
+
class TimestampSequel < Sequel::Model(:timestamps)
|
51
53
|
end
|
52
54
|
|
53
|
-
|
54
55
|
Timestamp.transaction do
|
55
56
|
stamps = {
|
56
57
|
}
|
57
58
|
Timestamp.columns.each do |c|
|
58
|
-
stamps[c.name.to_sym] =
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
stamps[c.name.to_sym] =
|
60
|
+
case c.type
|
61
|
+
when :integer then 1
|
62
|
+
when :datetime then Time.now
|
63
|
+
when :boolean then false
|
64
|
+
else "HELLO WORLD" * 2
|
65
|
+
end
|
64
66
|
end
|
65
67
|
|
66
68
|
1000.times do |id|
|
@@ -71,7 +73,7 @@ end
|
|
71
73
|
|
72
74
|
$conn = ActiveRecord::Base.connection.raw_connection
|
73
75
|
|
74
|
-
def ar_pluck_times(l=1000)
|
76
|
+
def ar_pluck_times(l = 1000)
|
75
77
|
s = +""
|
76
78
|
Timestamp.limit(l).order(:id).pluck(:time1, :time2).each do |time1, time2|
|
77
79
|
s << time1.to_f.to_s
|
@@ -80,7 +82,7 @@ def ar_pluck_times(l=1000)
|
|
80
82
|
s
|
81
83
|
end
|
82
84
|
|
83
|
-
def ar_select_times(l=1000)
|
85
|
+
def ar_select_times(l = 1000)
|
84
86
|
s = +""
|
85
87
|
Timestamp.limit(l).order(:id).select(:time1, :time2).each do |t|
|
86
88
|
s << t.time1.to_f.to_s
|
@@ -91,7 +93,7 @@ end
|
|
91
93
|
|
92
94
|
$mini_sql = MiniSql::Connection.new($conn)
|
93
95
|
|
94
|
-
def pg_times_params(l=1000)
|
96
|
+
def pg_times_params(l = 1000)
|
95
97
|
s = +""
|
96
98
|
# use the safe pattern here
|
97
99
|
r = $conn.async_exec_params(-"select time1, time2 from timestamps order by id limit $1", [l])
|
@@ -110,7 +112,7 @@ def pg_times_params(l=1000)
|
|
110
112
|
s
|
111
113
|
end
|
112
114
|
|
113
|
-
def pg_times(l=1000)
|
115
|
+
def pg_times(l = 1000)
|
114
116
|
s = +""
|
115
117
|
# use the safe pattern here
|
116
118
|
r = $conn.async_exec("select time1, time2 from timestamps order by id limit #{l}")
|
@@ -129,7 +131,7 @@ def pg_times(l=1000)
|
|
129
131
|
s
|
130
132
|
end
|
131
133
|
|
132
|
-
def mini_sql_times(l=1000)
|
134
|
+
def mini_sql_times(l = 1000)
|
133
135
|
s = +""
|
134
136
|
$mini_sql.query(-"select time1, time2 from timestamps order by id limit ?", l).each do |t|
|
135
137
|
s << t.time1.to_f.to_s
|
@@ -138,7 +140,7 @@ def mini_sql_times(l=1000)
|
|
138
140
|
s
|
139
141
|
end
|
140
142
|
|
141
|
-
def sequel_times(l=1000)
|
143
|
+
def sequel_times(l = 1000)
|
142
144
|
s = +""
|
143
145
|
TimestampSequel.limit(l).order(:id).select(:time1, :time2).each do |t|
|
144
146
|
s << t.time1.to_f.to_s
|
@@ -147,7 +149,7 @@ def sequel_times(l=1000)
|
|
147
149
|
s
|
148
150
|
end
|
149
151
|
|
150
|
-
def sequel_pluck_times(l=1000)
|
152
|
+
def sequel_pluck_times(l = 1000)
|
151
153
|
s = +""
|
152
154
|
TimestampSequel.limit(l).order(:id).select_map([:time1, :time2]).each do |t|
|
153
155
|
s << t[0].to_f.to_s
|
@@ -156,7 +158,7 @@ def sequel_pluck_times(l=1000)
|
|
156
158
|
s
|
157
159
|
end
|
158
160
|
|
159
|
-
def sequel_raw_times(l=1000)
|
161
|
+
def sequel_raw_times(l = 1000)
|
160
162
|
s = +""
|
161
163
|
DB[-"select time1, time2 from timestamps order by id limit ?", l].map([:time1, :time2]).each do |time1, time2|
|
162
164
|
s << time1.to_f.to_s
|
@@ -166,13 +168,13 @@ def sequel_raw_times(l=1000)
|
|
166
168
|
end
|
167
169
|
|
168
170
|
# usage is not really recommended but just to compare to pluck lets have it
|
169
|
-
def mini_sql_times_single(l=1000)
|
171
|
+
def mini_sql_times_single(l = 1000)
|
170
172
|
s = +""
|
171
173
|
i = 0
|
172
174
|
r = $mini_sql.query_single(-"select time1, time2 from timestamps order by id limit ?", l)
|
173
175
|
while i < r.length
|
174
176
|
s << r[i].to_f.to_s
|
175
|
-
s << r[i+1].to_f.to_s
|
177
|
+
s << r[i + 1].to_f.to_s
|
176
178
|
i += 2
|
177
179
|
end
|
178
180
|
s
|
@@ -190,7 +192,6 @@ end
|
|
190
192
|
# s
|
191
193
|
# end
|
192
194
|
|
193
|
-
|
194
195
|
results = [
|
195
196
|
ar_select_times,
|
196
197
|
ar_pluck_times,
|
@@ -0,0 +1,304 @@
|
|
1
|
+
require 'bundler/inline'
|
2
|
+
|
3
|
+
gemfile do
|
4
|
+
source 'https://rubygems.org'
|
5
|
+
gem 'mysql2'
|
6
|
+
gem 'mini_sql', path: '../'
|
7
|
+
gem 'activesupport'
|
8
|
+
gem 'activerecord'
|
9
|
+
gem 'activemodel'
|
10
|
+
gem 'memory_profiler'
|
11
|
+
gem 'benchmark-ips'
|
12
|
+
gem 'sequel', github: 'jeremyevans/sequel'
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'mysql2'
|
16
|
+
require 'sequel'
|
17
|
+
require 'active_record'
|
18
|
+
require 'memory_profiler'
|
19
|
+
require 'benchmark/ips'
|
20
|
+
require 'mini_sql'
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection(
|
23
|
+
:adapter => "mysql2",
|
24
|
+
:database => "test_db",
|
25
|
+
:username => "root",
|
26
|
+
:password => ''
|
27
|
+
)
|
28
|
+
|
29
|
+
DB = Sequel.connect("mysql2://root:@localhost/test_db")
|
30
|
+
|
31
|
+
mysql = ActiveRecord::Base.connection.raw_connection
|
32
|
+
|
33
|
+
mysql.query <<SQL
|
34
|
+
drop table if exists topics
|
35
|
+
SQL
|
36
|
+
|
37
|
+
mysql.query <<~SQL
|
38
|
+
CREATE TABLE `topics` (
|
39
|
+
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
40
|
+
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
41
|
+
`last_posted_at` datetime DEFAULT NULL,
|
42
|
+
`created_at` datetime NOT NULL,
|
43
|
+
`updated_at` datetime NOT NULL,
|
44
|
+
`views` int(11) NOT NULL DEFAULT '0',
|
45
|
+
`posts_count` int(11) NOT NULL DEFAULT '0',
|
46
|
+
`user_id` int(11) DEFAULT NULL,
|
47
|
+
`last_post_user_id` int(11) NOT NULL,
|
48
|
+
`reply_count` int(11) NOT NULL DEFAULT '0',
|
49
|
+
`featured_user1_id` int(11) DEFAULT NULL,
|
50
|
+
`featured_user2_id` int(11) DEFAULT NULL,
|
51
|
+
`featured_user3_id` int(11) DEFAULT NULL,
|
52
|
+
`avg_time` int(11) DEFAULT NULL,
|
53
|
+
`deleted_at` datetime DEFAULT NULL,
|
54
|
+
`highest_post_number` int(11) NOT NULL DEFAULT '0',
|
55
|
+
`image_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
56
|
+
`like_count` int(11) NOT NULL DEFAULT '0',
|
57
|
+
`incoming_link_count` int(11) NOT NULL DEFAULT '0',
|
58
|
+
`category_id` int(11) DEFAULT NULL,
|
59
|
+
`visible` tinyint(1) NOT NULL DEFAULT '1',
|
60
|
+
`moderator_posts_count` int(11) NOT NULL DEFAULT '0',
|
61
|
+
`closed` tinyint(1) NOT NULL DEFAULT '0',
|
62
|
+
`archived` tinyint(1) NOT NULL DEFAULT '0',
|
63
|
+
`bumped_at` datetime NOT NULL,
|
64
|
+
`has_summary` tinyint(1) NOT NULL DEFAULT '0',
|
65
|
+
`archetype` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'regular',
|
66
|
+
`featured_user4_id` int(11) DEFAULT NULL,
|
67
|
+
`notify_moderators_count` int(11) NOT NULL DEFAULT '0',
|
68
|
+
`spam_count` int(11) NOT NULL DEFAULT '0',
|
69
|
+
`pinned_at` datetime DEFAULT NULL,
|
70
|
+
`score` float DEFAULT NULL,
|
71
|
+
`percent_rank` float NOT NULL DEFAULT '1',
|
72
|
+
`subtype` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
73
|
+
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
74
|
+
`deleted_by_id` int(11) DEFAULT NULL,
|
75
|
+
`participant_count` int(11) DEFAULT '1',
|
76
|
+
`word_count` int(11) DEFAULT NULL,
|
77
|
+
`excerpt` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
78
|
+
`pinned_globally` tinyint(1) NOT NULL DEFAULT '0',
|
79
|
+
`pinned_until` datetime DEFAULT NULL,
|
80
|
+
`fancy_title` varchar(400) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
81
|
+
`highest_staff_post_number` int(11) NOT NULL DEFAULT '0',
|
82
|
+
`featured_link` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
83
|
+
`reviewable_score` float NOT NULL DEFAULT '0',
|
84
|
+
PRIMARY KEY (`id`)
|
85
|
+
)
|
86
|
+
SQL
|
87
|
+
|
88
|
+
class Topic < ActiveRecord::Base
|
89
|
+
end
|
90
|
+
|
91
|
+
class TopicSequel < Sequel::Model(:topics)
|
92
|
+
end
|
93
|
+
|
94
|
+
Topic.transaction do
|
95
|
+
topic = {
|
96
|
+
}
|
97
|
+
Topic.columns.each do |c|
|
98
|
+
topic[c.name.to_sym] = case c.type
|
99
|
+
when :integer then 1
|
100
|
+
when :datetime then Time.now
|
101
|
+
when :boolean then false
|
102
|
+
when :float then 1.0
|
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
|
+
def ar_title_id_pluck
|
116
|
+
s = +""
|
117
|
+
Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
|
118
|
+
s << id.to_s
|
119
|
+
s << title
|
120
|
+
end
|
121
|
+
s
|
122
|
+
end
|
123
|
+
|
124
|
+
def ar_title_id
|
125
|
+
s = +""
|
126
|
+
Topic.limit(1000).order(:id).select(:id, :title).each do |t|
|
127
|
+
s << t.id.to_s
|
128
|
+
s << t.title
|
129
|
+
end
|
130
|
+
s
|
131
|
+
end
|
132
|
+
|
133
|
+
def mysql_title_id
|
134
|
+
s = +""
|
135
|
+
# use the safe pattern here
|
136
|
+
r = $conn.query(-"select id, title from topics order by id limit 1000", as: :array)
|
137
|
+
|
138
|
+
r.each do |row|
|
139
|
+
s << row[0].to_s
|
140
|
+
s << row[1]
|
141
|
+
end
|
142
|
+
s
|
143
|
+
end
|
144
|
+
|
145
|
+
$mini_sql = MiniSql::Connection.get($conn)
|
146
|
+
|
147
|
+
def mini_sql_title_id
|
148
|
+
s = +""
|
149
|
+
$mini_sql.query(-"select id, title from topics order by id limit 1000").each do |t|
|
150
|
+
s << t.id.to_s
|
151
|
+
s << t.title
|
152
|
+
end
|
153
|
+
s
|
154
|
+
end
|
155
|
+
|
156
|
+
def sequel_select_title_id
|
157
|
+
s = +""
|
158
|
+
TopicSequel.limit(1000).order(:id).select(:id, :title).each do |t|
|
159
|
+
s << t.id.to_s
|
160
|
+
s << t.title
|
161
|
+
end
|
162
|
+
s
|
163
|
+
end
|
164
|
+
|
165
|
+
def sequel_pluck_title_id
|
166
|
+
s = +""
|
167
|
+
TopicSequel.limit(1000).order(:id).select_map([:id, :title]).each do |t|
|
168
|
+
s << t[0].to_s
|
169
|
+
s << t[1]
|
170
|
+
end
|
171
|
+
s
|
172
|
+
end
|
173
|
+
|
174
|
+
# usage is not really recommended but just to compare to pluck lets have it
|
175
|
+
def mini_sql_title_id_query_single
|
176
|
+
s = +""
|
177
|
+
i = 0
|
178
|
+
r = $mini_sql.query_single(-"select id, title from topics order by id limit 1000")
|
179
|
+
while i < r.length
|
180
|
+
s << r[i].to_s
|
181
|
+
s << r[i + 1]
|
182
|
+
i += 2
|
183
|
+
end
|
184
|
+
s
|
185
|
+
end
|
186
|
+
|
187
|
+
results = [
|
188
|
+
ar_title_id,
|
189
|
+
ar_title_id_pluck,
|
190
|
+
mysql_title_id,
|
191
|
+
mini_sql_title_id,
|
192
|
+
sequel_pluck_title_id,
|
193
|
+
sequel_select_title_id,
|
194
|
+
mini_sql_title_id_query_single
|
195
|
+
]
|
196
|
+
|
197
|
+
exit(-1) unless results.uniq.length == 1
|
198
|
+
|
199
|
+
Benchmark.ips do |r|
|
200
|
+
r.report("ar select title id") do |n|
|
201
|
+
while n > 0
|
202
|
+
ar_title_id
|
203
|
+
n -= 1
|
204
|
+
end
|
205
|
+
end
|
206
|
+
r.report("ar select title id pluck") do |n|
|
207
|
+
while n > 0
|
208
|
+
ar_title_id_pluck
|
209
|
+
n -= 1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
r.report("sequel title id select") do |n|
|
213
|
+
while n > 0
|
214
|
+
sequel_select_title_id
|
215
|
+
n -= 1
|
216
|
+
end
|
217
|
+
end
|
218
|
+
r.report("mysql select title id") do |n|
|
219
|
+
while n > 0
|
220
|
+
mysql_title_id
|
221
|
+
n -= 1
|
222
|
+
end
|
223
|
+
end
|
224
|
+
r.report("mini_sql select title id") do |n|
|
225
|
+
while n > 0
|
226
|
+
mini_sql_title_id
|
227
|
+
n -= 1
|
228
|
+
end
|
229
|
+
end
|
230
|
+
r.report("sequel title id pluck") do |n|
|
231
|
+
while n > 0
|
232
|
+
sequel_pluck_title_id
|
233
|
+
n -= 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
r.report("mini_sql query_single title id") do |n|
|
237
|
+
while n > 0
|
238
|
+
mini_sql_title_id_query_single
|
239
|
+
n -= 1
|
240
|
+
end
|
241
|
+
end
|
242
|
+
r.compare!
|
243
|
+
end
|
244
|
+
|
245
|
+
def wide_topic_ar
|
246
|
+
Topic.first
|
247
|
+
end
|
248
|
+
|
249
|
+
def wide_topic_mysql
|
250
|
+
r = $conn.query("select * from topics limit 1", as: :hash)
|
251
|
+
row = r.first
|
252
|
+
row
|
253
|
+
end
|
254
|
+
|
255
|
+
def wide_topic_sequel
|
256
|
+
TopicSequel.first
|
257
|
+
end
|
258
|
+
|
259
|
+
def wide_topic_mini_sql
|
260
|
+
$conn.query("select * from topics limit 1").first
|
261
|
+
end
|
262
|
+
|
263
|
+
Benchmark.ips do |r|
|
264
|
+
r.report("wide topic ar") do |n|
|
265
|
+
while n > 0
|
266
|
+
wide_topic_ar
|
267
|
+
n -= 1
|
268
|
+
end
|
269
|
+
end
|
270
|
+
r.report("wide topic sequel") do |n|
|
271
|
+
while n > 0
|
272
|
+
wide_topic_sequel
|
273
|
+
n -= 1
|
274
|
+
end
|
275
|
+
end
|
276
|
+
r.report("wide topic mysql") do |n|
|
277
|
+
while n > 0
|
278
|
+
wide_topic_mysql
|
279
|
+
n -= 1
|
280
|
+
end
|
281
|
+
end
|
282
|
+
r.report("wide topic mini sql") do |n|
|
283
|
+
while n > 0
|
284
|
+
wide_topic_mini_sql
|
285
|
+
n -= 1
|
286
|
+
end
|
287
|
+
end
|
288
|
+
r.compare!
|
289
|
+
end
|
290
|
+
|
291
|
+
# Comparison:
|
292
|
+
# mysql select title id: 485.0 i/s
|
293
|
+
# mini_sql query_single title id: 447.2 i/s - same-ish: difference falls within error
|
294
|
+
# mini_sql select title id: 417.4 i/s - 1.16x slower
|
295
|
+
# sequel title id pluck: 370.2 i/s - 1.31x slower
|
296
|
+
# sequel title id select: 351.0 i/s - 1.38x slower
|
297
|
+
# ar select title id pluck: 317.1 i/s - 1.53x slower
|
298
|
+
# ar select title id: 102.3 i/s - 4.74x slower
|
299
|
+
|
300
|
+
# Comparison:
|
301
|
+
# wide topic mini sql: 6768.7 i/s
|
302
|
+
# wide topic mysql: 6063.9 i/s - same-ish: difference falls within error
|
303
|
+
# wide topic sequel: 4908.6 i/s - same-ish: difference falls within error
|
304
|
+
# wide topic ar: 2630.2 i/s - 2.57x slower
|