mini_sql 0.1.9 → 0.1.10
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/README.md +14 -1
- data/bench/timestamp_perf.rb +249 -0
- data/lib/mini_sql/coders.rb +17 -0
- data/lib/mini_sql/connection.rb +10 -0
- data/lib/mini_sql/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c66544eb3a8d9b5067dc3ba1fb1df6a542e4adeabc2f4b08d4392fab7955b382
|
4
|
+
data.tar.gz: c3701417f3667ae360470d24a2bdd60b002350a1690a28cb1e9c95b0040d9c96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8b474bc10bd5570a2b4932f5182e97ad5c242885d2e3e68e8fa5246d21bf908b57044daf107b1968cf4895cb0e32af1af05f649c6049cd0747ddd43d2647836
|
7
|
+
data.tar.gz: 9c98da1a59f650541955b435f34fdbc3d4ed2ee7245d254d6d7decf9edbb25c8436b98a0dd0f3f4017e2f003ee51ae5f8f38e294c74f826be160d661bd361bac
|
data/README.md
CHANGED
@@ -46,6 +46,8 @@ You can use the simple query builder interface to compose queries.
|
|
46
46
|
```ruby
|
47
47
|
builder = conn.build("select * from topics /*where*/ /*limit*/")
|
48
48
|
|
49
|
+
builder.where('created_at > ?', Time.now - 400)
|
50
|
+
|
49
51
|
if look_for_something
|
50
52
|
builder.where("title = :title", title: 'something')
|
51
53
|
end
|
@@ -58,6 +60,8 @@ builder.query.each do |t|
|
|
58
60
|
end
|
59
61
|
```
|
60
62
|
|
63
|
+
The builder allows for `order_by`, `where`, `select`, `set`, `limit`, `join`, `left_join` and `offset`.
|
64
|
+
|
61
65
|
## Is it fast?
|
62
66
|
|
63
67
|
Yes, it is very fast. See benchmarks in [the bench directory](https://github.com/discourse/mini_sql/tree/master/bench).
|
@@ -85,11 +89,20 @@ end
|
|
85
89
|
|
86
90
|
## Safety
|
87
91
|
|
88
|
-
In
|
92
|
+
In PG gem version 1.0 and below you should be careful to clear results. If you do not you risk memory bloat.
|
89
93
|
See: [Sam's blog post](https://samsaffron.com/archive/2018/06/13/ruby-x27-s-external-malloc-problem).
|
90
94
|
|
91
95
|
MiniSql is careful to always clear results as soon as possible.
|
92
96
|
|
97
|
+
## Timestamp decoding
|
98
|
+
|
99
|
+
MiniSql's default type mapper prefers treating `timestamp without time zone` columns as utc. This is done to ensure widest amount of compatability and is a departure from the default in the PG 1.0 gem. If you wish to amend behavior feel free to pass in a custom type_map.
|
100
|
+
|
101
|
+
|
102
|
+
## I want more features!
|
103
|
+
|
104
|
+
MiniSql is designed to be very minimal. Even though the query builder and type materializer give you a lot of mileage, it is not intended to be a fully fledged ORM. If you are looking for an ORM I recommend investigating ActiveRecord or Sequel which provide significantly more features.
|
105
|
+
|
93
106
|
## Development
|
94
107
|
|
95
108
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
@@ -0,0 +1,249 @@
|
|
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
|
+
Sequel.default_timezone = :utc
|
29
|
+
DB = Sequel.postgres('test_db')
|
30
|
+
|
31
|
+
pg = ActiveRecord::Base.connection.raw_connection
|
32
|
+
|
33
|
+
pg.async_exec <<SQL
|
34
|
+
drop table if exists timestamps
|
35
|
+
SQL
|
36
|
+
|
37
|
+
pg.async_exec <<SQL
|
38
|
+
CREATE TABLE timestamps (
|
39
|
+
id int primary key,
|
40
|
+
time1 timestamp without time zone NOT NULL,
|
41
|
+
time2 timestamp without time zone NOT NULL,
|
42
|
+
time3 timestamp without time zone NOT NULL,
|
43
|
+
time4 timestamp without time zone NOT NULL
|
44
|
+
)
|
45
|
+
SQL
|
46
|
+
|
47
|
+
class Timestamp < ActiveRecord::Base
|
48
|
+
end
|
49
|
+
|
50
|
+
class TimestampSequel< Sequel::Model(:timestamps)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
Timestamp.transaction do
|
55
|
+
stamps = {
|
56
|
+
}
|
57
|
+
Timestamp.columns.each do |c|
|
58
|
+
stamps[c.name.to_sym] = case c.type
|
59
|
+
when :integer then 1
|
60
|
+
when :datetime then Time.now
|
61
|
+
when :boolean then false
|
62
|
+
else "HELLO WORLD" * 2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
1000.times do |id|
|
67
|
+
stamps[:id] = id
|
68
|
+
Timestamp.create!(stamps)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
$conn = ActiveRecord::Base.connection.raw_connection
|
73
|
+
|
74
|
+
def ar_pluck_times(l=1000)
|
75
|
+
s = +""
|
76
|
+
Timestamp.limit(l).order(:id).pluck(:time1, :time2).each do |time1, time2|
|
77
|
+
s << time1.iso8601
|
78
|
+
s << time2.iso8601
|
79
|
+
end
|
80
|
+
s
|
81
|
+
end
|
82
|
+
|
83
|
+
def ar_select_times(l=1000)
|
84
|
+
s = +""
|
85
|
+
Timestamp.limit(l).order(:id).select(:time1, :time2).each do |t|
|
86
|
+
s << t.time1.iso8601
|
87
|
+
s << t.time2.iso8601
|
88
|
+
end
|
89
|
+
s
|
90
|
+
end
|
91
|
+
|
92
|
+
$mini_sql = MiniSql::Connection.new($conn)
|
93
|
+
|
94
|
+
def pg_times(l=1000)
|
95
|
+
s = +""
|
96
|
+
# use the safe pattern here
|
97
|
+
r = $conn.async_exec_params(-"select time1, time2 from timestamps order by id limit $1", [l])
|
98
|
+
r.type_map = $mini_sql.type_map
|
99
|
+
r.each do |row|
|
100
|
+
s << row["time1"].iso8601
|
101
|
+
s << row["time2"].iso8601
|
102
|
+
end
|
103
|
+
r.clear
|
104
|
+
s
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def mini_sql_times(l=1000)
|
109
|
+
s = +""
|
110
|
+
$mini_sql.query(-"select time1, time2 from timestamps order by id limit ?", l).each do |t|
|
111
|
+
s << t.time1.iso8601
|
112
|
+
s << t.time2.iso8601
|
113
|
+
end
|
114
|
+
s
|
115
|
+
end
|
116
|
+
|
117
|
+
def sequel_times(l=1000)
|
118
|
+
s = +""
|
119
|
+
TimestampSequel.limit(l).order(:id).select(:time1, :time2).each do |t|
|
120
|
+
s << t.time1.iso8601
|
121
|
+
s << t.time2.iso8601
|
122
|
+
end
|
123
|
+
s
|
124
|
+
end
|
125
|
+
|
126
|
+
def sequel_pluck_times(l=1000)
|
127
|
+
s = +""
|
128
|
+
TimestampSequel.limit(l).order(:id).select_map([:time1, :time2]).each do |t|
|
129
|
+
s << t[0].iso8601
|
130
|
+
s << t[1].iso8601
|
131
|
+
end
|
132
|
+
s
|
133
|
+
end
|
134
|
+
|
135
|
+
# usage is not really recommended but just to compare to pluck lets have it
|
136
|
+
def mini_sql_times_single(l=1000)
|
137
|
+
s = +""
|
138
|
+
i = 0
|
139
|
+
r = $mini_sql.query_single(-"select time1, time2 from timestamps order by id limit ?", l)
|
140
|
+
while i < r.length
|
141
|
+
s << r[i].iso8601
|
142
|
+
s << r[i+1].iso8601
|
143
|
+
i += 2
|
144
|
+
end
|
145
|
+
s
|
146
|
+
end
|
147
|
+
|
148
|
+
$swift = Swift::DB::Postgres.new(db: "test_db", user: 'sam', password: 'password')
|
149
|
+
|
150
|
+
def swift_select_times(l=1000)
|
151
|
+
s = ""
|
152
|
+
r = $swift.execute("select time1, time2 from timestamps order by id limit $1", l)
|
153
|
+
r.each do |row|
|
154
|
+
s << row[:time1].iso8601
|
155
|
+
s << row[:time2].iso8601
|
156
|
+
end
|
157
|
+
s
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
results = [
|
162
|
+
ar_select_times,
|
163
|
+
ar_pluck_times,
|
164
|
+
pg_times,
|
165
|
+
mini_sql_times,
|
166
|
+
mini_sql_times_single,
|
167
|
+
sequel_times,
|
168
|
+
sequel_pluck_times,
|
169
|
+
]
|
170
|
+
|
171
|
+
exit(-1) unless results.uniq.length == 1
|
172
|
+
|
173
|
+
Benchmark.ips do |r|
|
174
|
+
r.report("ar select times") do |n|
|
175
|
+
while n > 0
|
176
|
+
ar_select_times
|
177
|
+
n -= 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
r.report("ar pluck times") do |n|
|
181
|
+
while n > 0
|
182
|
+
ar_pluck_times
|
183
|
+
n -= 1
|
184
|
+
end
|
185
|
+
end
|
186
|
+
r.report("sequel times") do |n|
|
187
|
+
while n > 0
|
188
|
+
sequel_times
|
189
|
+
n -= 1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
r.report("pg times") do |n|
|
193
|
+
while n > 0
|
194
|
+
pg_times
|
195
|
+
n -= 1
|
196
|
+
end
|
197
|
+
end
|
198
|
+
r.report("mini sql times") do |n|
|
199
|
+
while n > 0
|
200
|
+
mini_sql_times
|
201
|
+
n -= 1
|
202
|
+
end
|
203
|
+
end
|
204
|
+
r.report("sequel pluck times") do |n|
|
205
|
+
while n > 0
|
206
|
+
sequel_pluck_times
|
207
|
+
n -= 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
r.report("mini_sql query_single times") do |n|
|
211
|
+
while n > 0
|
212
|
+
mini_sql_times_single
|
213
|
+
n -= 1
|
214
|
+
end
|
215
|
+
end
|
216
|
+
r.report("swift_select_times") do |n|
|
217
|
+
while n > 0
|
218
|
+
swift_select_times
|
219
|
+
n -= 1
|
220
|
+
end
|
221
|
+
end
|
222
|
+
r.compare!
|
223
|
+
end
|
224
|
+
|
225
|
+
# Comparison:
|
226
|
+
# swift_select_times: 222.4 i/s
|
227
|
+
# mini_sql query_single times: 99.8 i/s - 2.23x slower
|
228
|
+
# mini sql times: 97.1 i/s - 2.29x slower
|
229
|
+
# pg times: 87.0 i/s - 2.56x slower
|
230
|
+
# ar pluck times: 31.5 i/s - 7.05x slower
|
231
|
+
# ar select times: 22.5 i/s - 9.89x slower
|
232
|
+
# sequel pluck times: 10.9 i/s - 20.42x slower
|
233
|
+
# sequel times: 10.4 i/s - 21.37x slower
|
234
|
+
#
|
235
|
+
# NOTE PG version 1.0.0 has a slower time materializer
|
236
|
+
#
|
237
|
+
# if we force it we get:
|
238
|
+
#
|
239
|
+
# swift_select_times: 222.7 i/s
|
240
|
+
# mini_sql query_single times: 48.4 i/s - 4.60x slower
|
241
|
+
# mini sql times: 46.4 i/s - 4.80x slower
|
242
|
+
# pg times: 44.2 i/s - 5.03x slower
|
243
|
+
# ar pluck times: 32.5 i/s - 6.85x slower
|
244
|
+
# ar select times: 22.1 i/s - 10.06x slower
|
245
|
+
# sequel pluck times: 10.9 i/s - 20.50x slower
|
246
|
+
# sequel times: 10.4 i/s - 21.43x slower
|
247
|
+
#
|
248
|
+
# swift has a super fast implementation, still need to determine
|
249
|
+
# why pg is so far behind
|
data/lib/mini_sql/coders.rb
CHANGED
@@ -5,10 +5,27 @@ module MiniSql
|
|
5
5
|
BigDecimal.new(string)
|
6
6
|
end
|
7
7
|
end
|
8
|
+
|
8
9
|
class IPAddrCoder < PG::SimpleDecoder
|
9
10
|
def decode(string, tuple = nil, field = nil)
|
10
11
|
IPAddr.new(string)
|
11
12
|
end
|
12
13
|
end
|
14
|
+
|
15
|
+
class TimestampUtc < PG::SimpleDecoder
|
16
|
+
# exact same implementation as Rails here
|
17
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
18
|
+
|
19
|
+
def decode(string, tuple = nil, field = nil)
|
20
|
+
if string =~ ISO_DATETIME
|
21
|
+
microsec = ($7.to_r * 1_000_000).to_i
|
22
|
+
Time.utc $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
23
|
+
else
|
24
|
+
STDERR.puts "unexpected date time format #{string}"
|
25
|
+
string
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
13
30
|
end
|
14
31
|
end
|
data/lib/mini_sql/connection.rb
CHANGED
@@ -16,6 +16,16 @@ module MiniSql
|
|
16
16
|
map.add_coder(MiniSql::Coders::IPAddrCoder.new(name: "inet", oid: 869, format: 0))
|
17
17
|
map.add_coder(MiniSql::Coders::IPAddrCoder.new(name: "cidr", oid: 650, format: 0))
|
18
18
|
map.add_coder(PG::TextDecoder::String.new(name: "tsvector", oid: 3614, format: 0))
|
19
|
+
|
20
|
+
map.rm_coder(0, 1114)
|
21
|
+
if defined? PG::TextDecoder::TimestampUtc
|
22
|
+
# treat timestamp without zone as utc
|
23
|
+
# new to PG 1.1
|
24
|
+
map.add_coder(PG::TextDecoder::TimestampUtc.new(name: "timestamp", oid: 1114, format: 0))
|
25
|
+
else
|
26
|
+
map.add_coder(MiniSql::Coders::TimestampUtc.new(name: "timestamp", oid: 1114, format: 0))
|
27
|
+
end
|
28
|
+
map
|
19
29
|
end
|
20
30
|
end
|
21
31
|
|
data/lib/mini_sql/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-06-
|
11
|
+
date: 2018-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -123,6 +123,7 @@ files:
|
|
123
123
|
- LICENSE.txt
|
124
124
|
- README.md
|
125
125
|
- Rakefile
|
126
|
+
- bench/timestamp_perf.rb
|
126
127
|
- bench/topic_perf.rb
|
127
128
|
- bin/console
|
128
129
|
- bin/setup
|