mini_sql 0.2.2-java
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 +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
|
+
|