mini_sql 0.2.2 → 0.2.3

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: 45fd7b2a7b68f3186fa88a3387188312f926128f3dcb24f9fee3e8d9b695683d
4
- data.tar.gz: c9ed648c9ef6976ef774863b509c63901b1c94629561b9fce63425155248b046
3
+ metadata.gz: 12406a36df764fd0c71ddb38a845d59099665dd081e0c6f6e125d396d7ec20de
4
+ data.tar.gz: 738d7ce83ff247695d8f4270f5c31f347d3c2e9af807449004b41cde03a33ec4
5
5
  SHA512:
6
- metadata.gz: 37d82773bbae9390c7c68852734b713a863df638510326a1022a8fe675b4aa3eb597b64ccb1adaef5067bbb808f6995e4f90c691f7b4276512708967d154f3cf
7
- data.tar.gz: 81523394fe465e59ab60f8d895f1cff17c4e4006b88792dd1bc5b536b17d00b8ab2c2180f5a1e7150e53dcd4d3bac5759397f7d76870d7ce92965b3f277493b6
6
+ metadata.gz: 1e510becc31ccd32edd41dc54d0ce4f85d38a47d4e3503e017edc5068bcd346e2912eaba290120c43686c4d6f06c2860fe7bf9e7a2d9c9a695d6c560e395f66c
7
+ data.tar.gz: 1bf82c5f43bda47ba1cdb8bfb9e72360e7c1ce70d65c38de6f851cfaad901d46b3e7dae2fce2e584d46601d5c1f72fed078e689452ad87574948ee6b1e556ae5
data/.travis.yml CHANGED
@@ -9,11 +9,16 @@ before_install:
9
9
  cache: bundler
10
10
  sudo: false
11
11
 
12
+ services:
13
+ - mysql
14
+
12
15
  addons:
13
16
  postgresql: 9.6
17
+ mysql: 5.7
14
18
 
15
19
  install:
16
20
  - createdb test_mini_sql
21
+ - mysql -e 'CREATE DATABASE test_mini_sql;'
17
22
  - bundle install
18
23
 
19
24
  matrix:
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ 2019-12-20 - 0.2.3
2
+
3
+ - Added support for MySQL
4
+
1
5
  2019-11-04 - 0.2.2
2
6
 
3
7
  - Added adapters for JRuby postgres support thanks to @enebo
data/README.md CHANGED
@@ -109,7 +109,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
109
109
 
110
110
  ## Contributing
111
111
 
112
- Bug reports and pull requests are welcome on GitHub at https://github.com/SamSaffron/mini_sql. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
112
+ Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/mini_sql. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
113
113
 
114
114
  ## License
115
115
 
@@ -0,0 +1,310 @@
1
+ require 'bundler/inline'
2
+
3
+ gemfile do
4
+ source 'https://rubygems.org'
5
+ gem 'mysql2'
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
+ end
14
+
15
+ require 'mysql2'
16
+ require 'sequel'
17
+ require 'active_record'
18
+ require 'memory_profiler'
19
+ require 'benchmark/ips'
20
+ require 'mini_sql'
21
+
22
+ ActiveRecord::Base.establish_connection(
23
+ :adapter => "mysql2",
24
+ :database => "test_db",
25
+ :username => "root",
26
+ :password => ''
27
+ )
28
+
29
+ DB = Sequel.connect("mysql2://root:@localhost/test_db")
30
+
31
+ mysql = ActiveRecord::Base.connection.raw_connection
32
+
33
+ mysql.query <<SQL
34
+ drop table if exists topics
35
+ SQL
36
+
37
+ mysql.query <<~SQL
38
+ CREATE TABLE `topics` (
39
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
40
+ `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
41
+ `last_posted_at` datetime DEFAULT NULL,
42
+ `created_at` datetime NOT NULL,
43
+ `updated_at` datetime NOT NULL,
44
+ `views` int(11) NOT NULL DEFAULT '0',
45
+ `posts_count` int(11) NOT NULL DEFAULT '0',
46
+ `user_id` int(11) DEFAULT NULL,
47
+ `last_post_user_id` int(11) NOT NULL,
48
+ `reply_count` int(11) NOT NULL DEFAULT '0',
49
+ `featured_user1_id` int(11) DEFAULT NULL,
50
+ `featured_user2_id` int(11) DEFAULT NULL,
51
+ `featured_user3_id` int(11) DEFAULT NULL,
52
+ `avg_time` int(11) DEFAULT NULL,
53
+ `deleted_at` datetime DEFAULT NULL,
54
+ `highest_post_number` int(11) NOT NULL DEFAULT '0',
55
+ `image_url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
56
+ `like_count` int(11) NOT NULL DEFAULT '0',
57
+ `incoming_link_count` int(11) NOT NULL DEFAULT '0',
58
+ `category_id` int(11) DEFAULT NULL,
59
+ `visible` tinyint(1) NOT NULL DEFAULT '1',
60
+ `moderator_posts_count` int(11) NOT NULL DEFAULT '0',
61
+ `closed` tinyint(1) NOT NULL DEFAULT '0',
62
+ `archived` tinyint(1) NOT NULL DEFAULT '0',
63
+ `bumped_at` datetime NOT NULL,
64
+ `has_summary` tinyint(1) NOT NULL DEFAULT '0',
65
+ `archetype` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'regular',
66
+ `featured_user4_id` int(11) DEFAULT NULL,
67
+ `notify_moderators_count` int(11) NOT NULL DEFAULT '0',
68
+ `spam_count` int(11) NOT NULL DEFAULT '0',
69
+ `pinned_at` datetime DEFAULT NULL,
70
+ `score` float DEFAULT NULL,
71
+ `percent_rank` float NOT NULL DEFAULT '1',
72
+ `subtype` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
73
+ `slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
74
+ `deleted_by_id` int(11) DEFAULT NULL,
75
+ `participant_count` int(11) DEFAULT '1',
76
+ `word_count` int(11) DEFAULT NULL,
77
+ `excerpt` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
78
+ `pinned_globally` tinyint(1) NOT NULL DEFAULT '0',
79
+ `pinned_until` datetime DEFAULT NULL,
80
+ `fancy_title` varchar(400) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
81
+ `highest_staff_post_number` int(11) NOT NULL DEFAULT '0',
82
+ `featured_link` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
83
+ `reviewable_score` float NOT NULL DEFAULT '0',
84
+ PRIMARY KEY (`id`)
85
+ )
86
+ SQL
87
+
88
+ class Topic < ActiveRecord::Base
89
+ end
90
+
91
+ class TopicSequel < Sequel::Model(:topics)
92
+ end
93
+
94
+
95
+ Topic.transaction do
96
+ topic = {
97
+ }
98
+ Topic.columns.each do |c|
99
+ topic[c.name.to_sym] = case c.type
100
+ when :integer then 1
101
+ when :datetime then Time.now
102
+ when :boolean then false
103
+ when :float then 1.0
104
+ else "HELLO WORLD" * 2
105
+ end
106
+ end
107
+
108
+ 1000.times do |id|
109
+ topic[:id] = id
110
+ Topic.create!(topic)
111
+ end
112
+ end
113
+
114
+ $conn = ActiveRecord::Base.connection.raw_connection
115
+
116
+ def ar_title_id_pluck
117
+ s = +""
118
+ Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
119
+ s << id.to_s
120
+ s << title
121
+ end
122
+ s
123
+ end
124
+
125
+ def ar_title_id
126
+ s = +""
127
+ Topic.limit(1000).order(:id).select(:id, :title).each do |t|
128
+ s << t.id.to_s
129
+ s << t.title
130
+ end
131
+ s
132
+ end
133
+
134
+ def mysql_title_id
135
+ s = +""
136
+ # use the safe pattern here
137
+ r = $conn.query(-"select id, title from topics order by id limit 1000", as: :array)
138
+
139
+ r.each do |row|
140
+ s << row[0].to_s
141
+ s << row[1]
142
+ end
143
+ s
144
+ end
145
+
146
+ $mini_sql = MiniSql::Connection.get($conn)
147
+
148
+ def mini_sql_title_id
149
+ s = +""
150
+ $mini_sql.query(-"select id, title from topics order by id limit 1000").each do |t|
151
+ s << t.id.to_s
152
+ s << t.title
153
+ end
154
+ s
155
+ end
156
+
157
+ def sequel_select_title_id
158
+ s = +""
159
+ TopicSequel.limit(1000).order(:id).select(:id, :title).each do |t|
160
+ s << t.id.to_s
161
+ s << t.title
162
+ end
163
+ s
164
+ end
165
+
166
+ def sequel_pluck_title_id
167
+ s = +""
168
+ TopicSequel.limit(1000).order(:id).select_map([:id, :title]).each do |t|
169
+ s << t[0].to_s
170
+ s << t[1]
171
+ end
172
+ s
173
+ end
174
+
175
+ # usage is not really recommended but just to compare to pluck lets have it
176
+ def mini_sql_title_id_query_single
177
+ s = +""
178
+ i = 0
179
+ r = $mini_sql.query_single(-"select id, title from topics order by id limit 1000")
180
+ while i < r.length
181
+ s << r[i].to_s
182
+ s << r[i+1]
183
+ i += 2
184
+ end
185
+ s
186
+ end
187
+
188
+ results = [
189
+ ar_title_id,
190
+ ar_title_id_pluck,
191
+ mysql_title_id,
192
+ mini_sql_title_id,
193
+ sequel_pluck_title_id,
194
+ sequel_select_title_id,
195
+ mini_sql_title_id_query_single
196
+ ]
197
+
198
+ exit(-1) unless results.uniq.length == 1
199
+
200
+
201
+ Benchmark.ips do |r|
202
+ r.report("ar select title id") do |n|
203
+ while n > 0
204
+ ar_title_id
205
+ n -= 1
206
+ end
207
+ end
208
+ r.report("ar select title id pluck") do |n|
209
+ while n > 0
210
+ ar_title_id_pluck
211
+ n -= 1
212
+ end
213
+ end
214
+ r.report("sequel title id select") do |n|
215
+ while n > 0
216
+ sequel_select_title_id
217
+ n -= 1
218
+ end
219
+ end
220
+ r.report("mysql select title id") do |n|
221
+ while n > 0
222
+ mysql_title_id
223
+ n -= 1
224
+ end
225
+ end
226
+ r.report("mini_sql select title id") do |n|
227
+ while n > 0
228
+ mini_sql_title_id
229
+ n -= 1
230
+ end
231
+ end
232
+ r.report("sequel title id pluck") do |n|
233
+ while n > 0
234
+ sequel_pluck_title_id
235
+ n -= 1
236
+ end
237
+ end
238
+ r.report("mini_sql query_single title id") do |n|
239
+ while n > 0
240
+ mini_sql_title_id_query_single
241
+ n -= 1
242
+ end
243
+ end
244
+ r.compare!
245
+ end
246
+
247
+
248
+
249
+ def wide_topic_ar
250
+ Topic.first
251
+ end
252
+
253
+ def wide_topic_mysql
254
+ r = $conn.query("select * from topics limit 1", as: :hash)
255
+ row = r.first
256
+ row
257
+ end
258
+
259
+ def wide_topic_sequel
260
+ TopicSequel.first
261
+ end
262
+
263
+ def wide_topic_mini_sql
264
+ $conn.query("select * from topics limit 1").first
265
+ end
266
+
267
+ Benchmark.ips do |r|
268
+ r.report("wide topic ar") do |n|
269
+ while n > 0
270
+ wide_topic_ar
271
+ n -= 1
272
+ end
273
+ end
274
+ r.report("wide topic sequel") do |n|
275
+ while n > 0
276
+ wide_topic_sequel
277
+ n -= 1
278
+ end
279
+ end
280
+ r.report("wide topic mysql") do |n|
281
+ while n > 0
282
+ wide_topic_mysql
283
+ n -= 1
284
+ end
285
+ end
286
+ r.report("wide topic mini sql") do |n|
287
+ while n > 0
288
+ wide_topic_mini_sql
289
+ n -= 1
290
+ end
291
+ end
292
+ r.compare!
293
+ end
294
+
295
+ # Comparison:
296
+ # mysql select title id: 485.0 i/s
297
+ # mini_sql query_single title id: 447.2 i/s - same-ish: difference falls within error
298
+ # mini_sql select title id: 417.4 i/s - 1.16x slower
299
+ # sequel title id pluck: 370.2 i/s - 1.31x slower
300
+ # sequel title id select: 351.0 i/s - 1.38x slower
301
+ # ar select title id pluck: 317.1 i/s - 1.53x slower
302
+ # ar select title id: 102.3 i/s - 4.74x slower
303
+
304
+
305
+ # Comparison:
306
+ # wide topic mini sql: 6768.7 i/s
307
+ # wide topic mysql: 6063.9 i/s - same-ish: difference falls within error
308
+ # wide topic sequel: 4908.6 i/s - same-ish: difference falls within error
309
+ # wide topic ar: 2630.2 i/s - 2.57x slower
310
+
data/bench/topic_perf.rb CHANGED
@@ -148,7 +148,7 @@ def pg_title_id
148
148
  s
149
149
  end
150
150
 
151
- $mini_sql = MiniSql::Connection.new($conn)
151
+ $mini_sql = MiniSql::Connection.get($conn)
152
152
 
153
153
  def mini_sql_title_id
154
154
  s = +""
data/lib/mini_sql.rb CHANGED
@@ -26,5 +26,10 @@ module MiniSql
26
26
  autoload :Connection, "mini_sql/sqlite/connection"
27
27
  autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
28
28
  end
29
+
30
+ module Mysql
31
+ autoload :Connection, "mini_sql/mysql/connection"
32
+ autoload :DeserializerCache, "mini_sql/mysql/deserializer_cache"
33
+ end
29
34
  end
30
35
  end
@@ -57,7 +57,6 @@ class MiniSql::Builder
57
57
  def #{m}(hash_args = nil)
58
58
  hash_args = @args.merge(hash_args) if hash_args && @args
59
59
  hash_args ||= @args
60
-
61
60
  if hash_args
62
61
  @connection.#{m}(to_sql, hash_args)
63
62
  else
@@ -10,6 +10,8 @@ module MiniSql
10
10
  Postgres::Connection.new(raw_connection, options)
11
11
  elsif (defined? ::SQLite3::Database) && (SQLite3::Database === raw_connection)
12
12
  Sqlite::Connection.new(raw_connection, options)
13
+ elsif (defined? ::Mysql2::Client) && (Mysql2::Client === raw_connection)
14
+ Mysql::Connection.new(raw_connection, options)
13
15
  else
14
16
  raise ArgumentError, 'unknown connection type!'
15
17
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Mysql
5
+ class Connection < MiniSql::Connection
6
+ attr_reader :param_encoder, :raw_connection, :deserializer_cache
7
+
8
+ def initialize(raw_connection, args = nil)
9
+ @raw_connection = raw_connection
10
+ @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
11
+ @deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
12
+ end
13
+
14
+ def query_single(sql, *params)
15
+ run(sql, :array, params).to_a.flatten!
16
+ end
17
+
18
+ def query_hash(sql, *params)
19
+ result = run(sql, :hash, params)
20
+ result.to_a
21
+ end
22
+
23
+ def exec(sql, *params)
24
+ run(sql, :array, params)
25
+ raw_connection.affected_rows
26
+ end
27
+
28
+ def query(sql, *params)
29
+ result = run(sql, :array, params)
30
+ @deserializer_cache.materialize(result)
31
+ end
32
+
33
+ def escape_string(str)
34
+ raw_connection.escape(str)
35
+ end
36
+
37
+ def build(sql)
38
+ Builder.new(self, sql)
39
+ end
40
+
41
+ private
42
+
43
+ def run(sql, as, params)
44
+ if params && params.length > 0
45
+ sql = param_encoder.encode(sql, *params)
46
+ end
47
+ raw_connection.query(
48
+ sql,
49
+ as: as,
50
+ database_timezone: :utc,
51
+ application_timezone: :utc,
52
+ cast_booleans: true,
53
+ cast: true,
54
+ cache_rows: true,
55
+ symbolize_keys: false
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ module MiniSql
2
+ module Mysql
3
+ class DeserializerCache
4
+
5
+ DEFAULT_MAX_SIZE = 500
6
+
7
+ def initialize(max_size = nil)
8
+ @cache = {}
9
+ @max_size = max_size || DEFAULT_MAX_SIZE
10
+ end
11
+
12
+ def materialize(result)
13
+ key = result.fields
14
+
15
+ # trivial fast LRU implementation
16
+ materializer = @cache.delete(key)
17
+ if materializer
18
+ @cache[key] = materializer
19
+ else
20
+ materializer = @cache[key] = new_row_matrializer(result)
21
+ @cache.shift if @cache.length > @max_size
22
+ end
23
+
24
+ result.map do |data|
25
+ materializer.materialize(data)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def new_row_matrializer(result)
32
+ fields = result.fields
33
+
34
+ Class.new do
35
+ attr_accessor(*fields)
36
+
37
+ # AM serializer support
38
+ alias :read_attribute_for_serialization :send
39
+
40
+ def to_h
41
+ r = {}
42
+ instance_variables.each do |f|
43
+ r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
44
+ end
45
+ r
46
+ end
47
+
48
+ instance_eval <<~RUBY
49
+ def materialize(data)
50
+ r = self.new
51
+ #{col=-1; fields.map{|f| "r.#{f} = data[#{col+=1}]"}.join("; ")}
52
+ r
53
+ end
54
+ RUBY
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module MiniSql
2
- VERSION = "0.2.2"
3
+ VERSION = "0.2.3"
3
4
  end
data/mini_sql.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency "activerecord-jdbcpostgresql-adapter", "~> 52.2"
36
36
  else
37
37
  spec.add_development_dependency "pg", "> 1"
38
+ spec.add_development_dependency "mysql2"
38
39
  spec.add_development_dependency "sqlite3", "~> 1.3"
39
40
  end
40
41
  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.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-11 00:00:00.000000000 Z
11
+ date: 2019-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: mysql2
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: sqlite3
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -139,6 +153,7 @@ files:
139
153
  - README.md
140
154
  - Rakefile
141
155
  - bench/timestamp_perf.rb
156
+ - bench/topic_mysql_perf.rb
142
157
  - bench/topic_perf.rb
143
158
  - bin/console
144
159
  - bin/setup
@@ -147,6 +162,8 @@ files:
147
162
  - lib/mini_sql/connection.rb
148
163
  - lib/mini_sql/deserializer_cache.rb
149
164
  - lib/mini_sql/inline_param_encoder.rb
165
+ - lib/mini_sql/mysql/connection.rb
166
+ - lib/mini_sql/mysql/deserializer_cache.rb
150
167
  - lib/mini_sql/postgres/coders.rb
151
168
  - lib/mini_sql/postgres/connection.rb
152
169
  - lib/mini_sql/postgres/deserializer_cache.rb