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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abb973879fc47b098296d28aff0023d3c3a734acff63da3f59cde4c3129ea08d
4
- data.tar.gz: 6283be1e1037e203a1c842e8371f6a0b4eef1b506c2ef1a7b260a6ffe89aa9ac
3
+ metadata.gz: c66544eb3a8d9b5067dc3ba1fb1df6a542e4adeabc2f4b08d4392fab7955b382
4
+ data.tar.gz: c3701417f3667ae360470d24a2bdd60b002350a1690a28cb1e9c95b0040d9c96
5
5
  SHA512:
6
- metadata.gz: 2ef6234f201e71933e61fa965fa718fad2c4db88c70e9ef724b3de754105b519b45ffc415fe0df72a2c8dfb1e4c9f29fe9b0ae304a0c6658a6aa7ca9944ecbb0
7
- data.tar.gz: 67a511de5b14d5b28265a273bf0a0cb706027c49da8d1885328eab9cbc6c8443ea69aacadb77dc2eba8a5159864b8f05be1ccdbd9586f97b2ae993128d39d1e9
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 current version of the PG gem you should be careful to clear results. If you do not you risk memory bloat.
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
@@ -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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module MiniSql
2
- VERSION = "0.1.9"
2
+ VERSION = "0.1.10"
3
3
  end
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.9
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-20 00:00:00.000000000 Z
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