mini_sql 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
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