mini_sql 0.2.2-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +21 -0
- data/CHANGELOG +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +120 -0
- data/Rakefile +16 -0
- data/bench/timestamp_perf.rb +286 -0
- data/bench/topic_perf.rb +347 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/mini_sql.rb +30 -0
- data/lib/mini_sql/builder.rb +71 -0
- data/lib/mini_sql/connection.rb +49 -0
- data/lib/mini_sql/deserializer_cache.rb +8 -0
- data/lib/mini_sql/inline_param_encoder.rb +70 -0
- data/lib/mini_sql/postgres/coders.rb +32 -0
- data/lib/mini_sql/postgres/connection.rb +123 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +67 -0
- data/lib/mini_sql/postgres_jdbc/connection.rb +97 -0
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +67 -0
- data/lib/mini_sql/sqlite/connection.rb +68 -0
- data/lib/mini_sql/sqlite/deserializer_cache.rb +66 -0
- data/lib/mini_sql/version.rb +3 -0
- data/mini_sql.gemspec +40 -0
- metadata +169 -0
data/bench/topic_perf.rb
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
require 'bundler/inline'
|
2
|
+
|
3
|
+
gemfile do
|
4
|
+
source 'https://rubygems.org'
|
5
|
+
gem 'pg', github: 'ged/ruby-pg'
|
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
|
+
gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
|
14
|
+
gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres'
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'sequel'
|
18
|
+
require 'active_record'
|
19
|
+
require 'memory_profiler'
|
20
|
+
require 'benchmark/ips'
|
21
|
+
require 'mini_sql'
|
22
|
+
|
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
|
35
|
+
|
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
|
85
|
+
|
86
|
+
class Topic < ActiveRecord::Base
|
87
|
+
end
|
88
|
+
|
89
|
+
class TopicSequel < Sequel::Model(:topics)
|
90
|
+
end
|
91
|
+
|
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
|
+
def ar_title_id_pluck
|
114
|
+
s = +""
|
115
|
+
Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
|
116
|
+
s << id.to_s
|
117
|
+
s << title
|
118
|
+
end
|
119
|
+
s
|
120
|
+
end
|
121
|
+
|
122
|
+
def ar_title_id
|
123
|
+
s = +""
|
124
|
+
Topic.limit(1000).order(:id).select(:id, :title).each do |t|
|
125
|
+
s << t.id.to_s
|
126
|
+
s << t.title
|
127
|
+
end
|
128
|
+
s
|
129
|
+
end
|
130
|
+
|
131
|
+
def pg_title_id
|
132
|
+
s = +""
|
133
|
+
# use the safe pattern here
|
134
|
+
r = $conn.async_exec(-"select id, title from topics order by id limit 1000")
|
135
|
+
|
136
|
+
# this seems fastest despite extra arrays, cause array of arrays is generated
|
137
|
+
# in c code
|
138
|
+
values = r.values
|
139
|
+
|
140
|
+
i = 0
|
141
|
+
l = values.length
|
142
|
+
while i < l
|
143
|
+
s << values[i][0].to_s
|
144
|
+
s << values[i][1]
|
145
|
+
i += 1
|
146
|
+
end
|
147
|
+
r.clear
|
148
|
+
s
|
149
|
+
end
|
150
|
+
|
151
|
+
$mini_sql = MiniSql::Connection.new($conn)
|
152
|
+
|
153
|
+
def mini_sql_title_id
|
154
|
+
s = +""
|
155
|
+
$mini_sql.query(-"select id, title from topics order by id limit 1000").each do |t|
|
156
|
+
s << t.id.to_s
|
157
|
+
s << t.title
|
158
|
+
end
|
159
|
+
s
|
160
|
+
end
|
161
|
+
|
162
|
+
def sequel_select_title_id
|
163
|
+
s = +""
|
164
|
+
TopicSequel.limit(1000).order(:id).select(:id, :title).each do |t|
|
165
|
+
s << t.id.to_s
|
166
|
+
s << t.title
|
167
|
+
end
|
168
|
+
s
|
169
|
+
end
|
170
|
+
|
171
|
+
def sequel_pluck_title_id
|
172
|
+
s = +""
|
173
|
+
TopicSequel.limit(1000).order(:id).select_map([:id, :title]).each do |t|
|
174
|
+
s << t[0].to_s
|
175
|
+
s << t[1]
|
176
|
+
end
|
177
|
+
s
|
178
|
+
end
|
179
|
+
|
180
|
+
# usage is not really recommended but just to compare to pluck lets have it
|
181
|
+
def mini_sql_title_id_query_single
|
182
|
+
s = +""
|
183
|
+
i = 0
|
184
|
+
r = $mini_sql.query_single(-"select id, title from topics order by id limit 1000")
|
185
|
+
while i < r.length
|
186
|
+
s << r[i].to_s
|
187
|
+
s << r[i+1]
|
188
|
+
i += 2
|
189
|
+
end
|
190
|
+
s
|
191
|
+
end
|
192
|
+
|
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 = ""
|
198
|
+
i = 0
|
199
|
+
r = $swift.execute("select id, title from topics order by id limit 1000")
|
200
|
+
while i < r.selected_rows
|
201
|
+
s << r.get(i, 0).to_s
|
202
|
+
s << r.get(i, 1)
|
203
|
+
i += 1
|
204
|
+
end
|
205
|
+
s
|
206
|
+
end
|
207
|
+
|
208
|
+
results = [
|
209
|
+
ar_title_id,
|
210
|
+
ar_title_id_pluck,
|
211
|
+
pg_title_id,
|
212
|
+
mini_sql_title_id,
|
213
|
+
sequel_pluck_title_id,
|
214
|
+
sequel_select_title_id,
|
215
|
+
mini_sql_title_id_query_single,
|
216
|
+
swift_select_title_id
|
217
|
+
]
|
218
|
+
|
219
|
+
exit(-1) unless results.uniq.length == 1
|
220
|
+
|
221
|
+
|
222
|
+
Benchmark.ips do |r|
|
223
|
+
r.report("ar select title id") do |n|
|
224
|
+
while n > 0
|
225
|
+
ar_title_id
|
226
|
+
n -= 1
|
227
|
+
end
|
228
|
+
end
|
229
|
+
r.report("ar select title id pluck") do |n|
|
230
|
+
while n > 0
|
231
|
+
ar_title_id_pluck
|
232
|
+
n -= 1
|
233
|
+
end
|
234
|
+
end
|
235
|
+
r.report("sequel title id select") do |n|
|
236
|
+
while n > 0
|
237
|
+
sequel_select_title_id
|
238
|
+
n -= 1
|
239
|
+
end
|
240
|
+
end
|
241
|
+
r.report("pg select title id") do |n|
|
242
|
+
while n > 0
|
243
|
+
pg_title_id
|
244
|
+
n -= 1
|
245
|
+
end
|
246
|
+
end
|
247
|
+
r.report("mini_sql select title id") do |n|
|
248
|
+
while n > 0
|
249
|
+
mini_sql_title_id
|
250
|
+
n -= 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
r.report("sequel title id pluck") do |n|
|
254
|
+
while n > 0
|
255
|
+
sequel_pluck_title_id
|
256
|
+
n -= 1
|
257
|
+
end
|
258
|
+
end
|
259
|
+
r.report("mini_sql query_single title id") do |n|
|
260
|
+
while n > 0
|
261
|
+
mini_sql_title_id_query_single
|
262
|
+
n -= 1
|
263
|
+
end
|
264
|
+
end
|
265
|
+
r.report("swift title id") do |n|
|
266
|
+
while n > 0
|
267
|
+
swift_select_title_id
|
268
|
+
n -= 1
|
269
|
+
end
|
270
|
+
end
|
271
|
+
r.compare!
|
272
|
+
end
|
273
|
+
|
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
|
+
# 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
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mini_sql"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/mini_sql.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# we need this for a coder
|
4
|
+
require "bigdecimal"
|
5
|
+
|
6
|
+
require_relative "mini_sql/version"
|
7
|
+
require_relative "mini_sql/connection"
|
8
|
+
require_relative "mini_sql/deserializer_cache"
|
9
|
+
require_relative "mini_sql/builder"
|
10
|
+
require_relative "mini_sql/inline_param_encoder"
|
11
|
+
|
12
|
+
module MiniSql
|
13
|
+
if RUBY_ENGINE == 'jruby'
|
14
|
+
module Postgres
|
15
|
+
autoload :Connection, "mini_sql/postgres_jdbc/connection"
|
16
|
+
autoload :DeserializerCache, "mini_sql/postgres_jdbc/deserializer_cache"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
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
|
+
end
|
24
|
+
|
25
|
+
module Sqlite
|
26
|
+
autoload :Connection, "mini_sql/sqlite/connection"
|
27
|
+
autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniSql::Builder
|
4
|
+
|
5
|
+
def initialize(connection, template)
|
6
|
+
@args = nil
|
7
|
+
@sql = template
|
8
|
+
@sections = {}
|
9
|
+
@connection = connection
|
10
|
+
end
|
11
|
+
|
12
|
+
[:set, :where2, :where, :order_by, :limit, :left_join, :join, :offset, :select].each do |k|
|
13
|
+
define_method k do |data, *args|
|
14
|
+
if args && (args.length == 1) && (Hash === args[0])
|
15
|
+
@args ||= {}
|
16
|
+
@args.merge!(args[0])
|
17
|
+
elsif args && args.length > 0
|
18
|
+
data = @connection.param_encoder.encode(data, *args)
|
19
|
+
end
|
20
|
+
@sections[k] ||= []
|
21
|
+
@sections[k] << data
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_sql
|
27
|
+
sql = @sql.dup
|
28
|
+
|
29
|
+
@sections.each do |k, v|
|
30
|
+
joined = nil
|
31
|
+
case k
|
32
|
+
when :select
|
33
|
+
joined = (+"SELECT ") << v.join(" , ")
|
34
|
+
when :where, :where2
|
35
|
+
joined = (+"WHERE ") << v.map { |c| (+"(") << c << ")" }.join(" AND ")
|
36
|
+
when :join
|
37
|
+
joined = v.map { |item| (+"JOIN ") << item }.join("\n")
|
38
|
+
when :left_join
|
39
|
+
joined = v.map { |item| (+"LEFT JOIN ") << item }.join("\n")
|
40
|
+
when :limit
|
41
|
+
joined = (+"LIMIT ") << v.last.to_i.to_s
|
42
|
+
when :offset
|
43
|
+
joined = (+"OFFSET ") << v.last.to_i.to_s
|
44
|
+
when :order_by
|
45
|
+
joined = (+"ORDER BY ") << v.join(" , ")
|
46
|
+
when :set
|
47
|
+
joined = (+"SET ") << v.join(" , ")
|
48
|
+
end
|
49
|
+
|
50
|
+
sql.sub!("/*#{k}*/", joined)
|
51
|
+
end
|
52
|
+
sql
|
53
|
+
end
|
54
|
+
|
55
|
+
[:query, :query_single, :query_hash, :exec].each do |m|
|
56
|
+
class_eval <<~RUBY
|
57
|
+
def #{m}(hash_args = nil)
|
58
|
+
hash_args = @args.merge(hash_args) if hash_args && @args
|
59
|
+
hash_args ||= @args
|
60
|
+
|
61
|
+
if hash_args
|
62
|
+
@connection.#{m}(to_sql, hash_args)
|
63
|
+
else
|
64
|
+
@connection.#{m}(to_sql)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
RUBY
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|