mini_sql 0.2.4 → 1.1.0

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +66 -0
  3. data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
  4. data/.rubocop.yml +8 -0
  5. data/CHANGELOG +22 -0
  6. data/Gemfile +3 -1
  7. data/Guardfile +2 -0
  8. data/README.md +125 -1
  9. data/Rakefile +3 -1
  10. data/bench/builder_perf.rb +138 -0
  11. data/bench/decorator_perf.rb +143 -0
  12. data/bench/mini_sql_methods_perf.rb +80 -0
  13. data/bench/prepared_perf.rb +59 -0
  14. data/bench/shared/generate_data.rb +133 -0
  15. data/bench/timestamp_perf.rb +22 -21
  16. data/bench/topic_mysql_perf.rb +1 -7
  17. data/bench/topic_perf.rb +27 -169
  18. data/bench/topic_wide_perf.rb +92 -0
  19. data/bin/console +1 -0
  20. data/lib/mini_sql.rb +20 -8
  21. data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
  22. data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
  23. data/lib/mini_sql/builder.rb +64 -24
  24. data/lib/mini_sql/connection.rb +15 -3
  25. data/lib/mini_sql/decoratable.rb +22 -0
  26. data/lib/mini_sql/deserializer_cache.rb +2 -0
  27. data/lib/mini_sql/inline_param_encoder.rb +12 -13
  28. data/lib/mini_sql/mysql/connection.rb +18 -3
  29. data/lib/mini_sql/mysql/deserializer_cache.rb +14 -16
  30. data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
  31. data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
  32. data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
  33. data/lib/mini_sql/postgres/coders.rb +2 -0
  34. data/lib/mini_sql/postgres/connection.rb +89 -0
  35. data/lib/mini_sql/postgres/deserializer_cache.rb +36 -16
  36. data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
  37. data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
  38. data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
  39. data/lib/mini_sql/postgres_jdbc/connection.rb +8 -1
  40. data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
  41. data/lib/mini_sql/result.rb +30 -0
  42. data/lib/mini_sql/serializer.rb +84 -0
  43. data/lib/mini_sql/sqlite/connection.rb +20 -2
  44. data/lib/mini_sql/sqlite/deserializer_cache.rb +14 -16
  45. data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
  46. data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
  47. data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
  48. data/lib/mini_sql/version.rb +1 -1
  49. data/mini_sql.gemspec +7 -2
  50. metadata +75 -11
  51. data/.travis.yml +0 -26
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ class Serializer < Array
5
+ MAX_CACHE_SIZE = 500
6
+
7
+ def initialize(result)
8
+ replace(result)
9
+ end
10
+
11
+ def self.marshallable(result)
12
+ new(result)
13
+ end
14
+
15
+ def marshal_dump
16
+ serialize
17
+ end
18
+
19
+ def marshal_load(wrapper)
20
+ replace self.class.materialize(wrapper)
21
+ end
22
+
23
+ private
24
+
25
+ def serialize
26
+ if length == 0
27
+ {}
28
+ else
29
+ {
30
+ "decorator" => first.class.decorator.to_s,
31
+ "fields" => first.to_h.keys,
32
+ "data" => map(&:values),
33
+ }
34
+ end
35
+ end
36
+
37
+ def self.materialize(wrapper)
38
+ if !wrapper["data"]
39
+ []
40
+ else
41
+ materializer = cached_materializer(wrapper['fields'], wrapper['decorator'])
42
+ wrapper["data"].map do |row|
43
+ materializer.materialize(row)
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.cached_materializer(fields, decorator_module = nil)
49
+ @cache ||= {}
50
+ key = fields
51
+ m = @cache.delete(key)
52
+ if m
53
+ @cache[key] = m
54
+ else
55
+ m = @cache[key] = materializer(fields)
56
+ @cache.shift if @cache.length > MAX_CACHE_SIZE
57
+ end
58
+
59
+ if decorator_module && decorator_module.length > 0
60
+ decorator = Kernel.const_get(decorator_module)
61
+ m = m.decorated(decorator)
62
+ end
63
+
64
+ m
65
+ end
66
+
67
+ def self.materializer(fields)
68
+ Class.new do
69
+ extend MiniSql::Decoratable
70
+ include MiniSql::Result
71
+
72
+ attr_accessor(*fields)
73
+
74
+ instance_eval <<~RUBY
75
+ def materialize(values)
76
+ r = self.new
77
+ #{col = -1; fields.map { |f| "r.#{f} = values[#{col += 1}]" }.join("; ")}
78
+ r
79
+ end
80
+ RUBY
81
+ end
82
+ end
83
+ end
84
+ end
@@ -9,6 +9,12 @@ module MiniSql
9
9
  @raw_connection = raw_connection
10
10
  @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
11
11
  @deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
12
+
13
+ @prepared = PreparedConnection.new(self, @deserializer_cache)
14
+ end
15
+
16
+ def prepared(condition = true)
17
+ condition ? @prepared : self
12
18
  end
13
19
 
14
20
  def query_single(sql, *params)
@@ -26,6 +32,10 @@ module MiniSql
26
32
  r
27
33
  end
28
34
 
35
+ def query_array(sql, *params)
36
+ run(sql, *params)
37
+ end
38
+
29
39
  def exec(sql, *params)
30
40
 
31
41
  start = raw_connection.total_changes
@@ -46,8 +56,14 @@ module MiniSql
46
56
  end
47
57
  end
48
58
 
59
+ def query_decorator(decorator, sql, *params)
60
+ run(sql, *params) do |set|
61
+ deserializer_cache.materialize(set, decorator)
62
+ end
63
+ end
64
+
49
65
  def escape_string(str)
50
- str.gsub("'","''")
66
+ str.gsub("'", "''")
51
67
  end
52
68
 
53
69
  private
@@ -58,7 +74,9 @@ module MiniSql
58
74
  end
59
75
  if block_given?
60
76
  stmt = SQLite3::Statement.new(raw_connection, sql)
61
- yield stmt.execute
77
+ result = yield stmt.execute
78
+ stmt.close
79
+ result
62
80
  else
63
81
  raw_connection.execute(sql)
64
82
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Sqlite
3
5
  class DeserializerCache
@@ -9,19 +11,23 @@ module MiniSql
9
11
  @max_size = max_size || DEFAULT_MAX_SIZE
10
12
  end
11
13
 
12
- def materialize(result)
14
+ def materialize(result, decorator_module = nil)
13
15
 
14
- key = result.columns
16
+ key = result.columns.join(',')
15
17
 
16
18
  # trivial fast LRU implementation
17
19
  materializer = @cache.delete(key)
18
20
  if materializer
19
21
  @cache[key] = materializer
20
22
  else
21
- materializer = @cache[key] = new_row_matrializer(result)
23
+ materializer = @cache[key] = new_row_materializer(result)
22
24
  @cache.shift if @cache.length > @max_size
23
25
  end
24
26
 
27
+ if decorator_module
28
+ materializer = materializer.decorated(decorator_module)
29
+ end
30
+
25
31
  r = []
26
32
  # quicker loop
27
33
  while !result.eof?
@@ -35,27 +41,19 @@ module MiniSql
35
41
 
36
42
  private
37
43
 
38
- def new_row_matrializer(result)
44
+ def new_row_materializer(result)
39
45
  fields = result.columns
40
46
 
41
47
  Class.new do
42
- attr_accessor(*fields)
48
+ extend MiniSql::Decoratable
49
+ include MiniSql::Result
43
50
 
44
- # AM serializer support
45
- alias :read_attribute_for_serialization :send
46
-
47
- def to_h
48
- r = {}
49
- instance_variables.each do |f|
50
- r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
51
- end
52
- r
53
- end
51
+ attr_accessor(*fields)
54
52
 
55
53
  instance_eval <<~RUBY
56
54
  def materialize(data)
57
55
  r = self.new
58
- #{col=-1; fields.map{|f| "r.#{f} = data[#{col+=1}]"}.join("; ")}
56
+ #{col = -1; fields.map { |f| "r.#{f} = data[#{col += 1}]" }.join("; ")}
59
57
  r
60
58
  end
61
59
  RUBY
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_binds"
4
+
5
+ module MiniSql
6
+ module Sqlite
7
+ class PreparedBinds < ::MiniSql::Abstract::PreparedBinds
8
+
9
+ def bind_output(i)
10
+ '?'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_cache"
4
+
5
+ module MiniSql
6
+ module Sqlite
7
+ class PreparedCache < MiniSql::Abstract::PreparedCache
8
+
9
+ private
10
+
11
+ def alloc(sql)
12
+ @connection.prepare(sql)
13
+ end
14
+
15
+ def dealloc(statement)
16
+ statement.close unless statement.closed?
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Sqlite
5
+ class PreparedConnection < Connection
6
+
7
+ attr_reader :unprepared
8
+
9
+ def initialize(unprepared_connection, deserializer_cache)
10
+ @unprepared = unprepared_connection
11
+ @raw_connection = unprepared_connection.raw_connection
12
+ @deserializer_cache = deserializer_cache
13
+ @param_encoder = unprepared_connection.param_encoder
14
+
15
+ @prepared_cache = PreparedCache.new(@raw_connection)
16
+ @param_binder = PreparedBinds.new
17
+ end
18
+
19
+ def build(_)
20
+ raise 'Builder can not be called on prepared connections, instead of `::MINI_SQL.prepared.build(sql).query` use `::MINI_SQL.build(sql).prepared.query`'
21
+ end
22
+
23
+ def prepared(condition = true)
24
+ condition ? self : @unprepared
25
+ end
26
+
27
+ private def run(sql, params)
28
+ prepared_sql, binds, _bind_names = @param_binder.bind(sql, params)
29
+ statement = @prepared_cache.prepare_statement(prepared_sql)
30
+ statement.bind_params(binds)
31
+ if block_given?
32
+ yield statement.execute
33
+ else
34
+ statement.execute.to_a
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module MiniSql
3
- VERSION = "0.2.4"
3
+ VERSION = "1.1.0"
4
4
  end
data/mini_sql.gemspec CHANGED
@@ -25,17 +25,22 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  # Specify which files should be added to the gem when it is released.
27
27
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
+ # rubocop:disable Discourse/NoChdir
29
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
29
30
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
31
  end
32
+ # rubocop:enable Discourse/NoChdir
31
33
  spec.require_paths = ["lib"]
32
34
 
33
35
  spec.add_development_dependency "bundler", "> 1.16"
34
- spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rake", "> 10"
35
37
  spec.add_development_dependency "minitest", "~> 5.0"
36
38
  spec.add_development_dependency "guard", "~> 2.14"
37
39
  spec.add_development_dependency "guard-minitest", "~> 2.4"
38
40
  spec.add_development_dependency "activesupport", "~> 5.2"
41
+ spec.add_development_dependency 'rubocop', '~> 1.4.0'
42
+ spec.add_development_dependency 'rubocop-discourse', '~> 2.4.1'
43
+ spec.add_development_dependency 'm', '~> 1.5.1'
39
44
 
40
45
  if RUBY_ENGINE == 'jruby'
41
46
  spec.add_development_dependency "activerecord-jdbcpostgresql-adapter", "~> 52.2"
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.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-16 00:00:00.000000000 Z
11
+ date: 2021-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '10'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '10'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,48 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '5.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.4.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.4.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-discourse
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.4.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.4.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: m
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.5.1
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.5.1
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: pg
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -143,8 +185,10 @@ executables: []
143
185
  extensions: []
144
186
  extra_rdoc_files: []
145
187
  files:
188
+ - ".github/workflows/ci.yml"
146
189
  - ".gitignore"
147
- - ".travis.yml"
190
+ - ".rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml"
191
+ - ".rubocop.yml"
148
192
  - CHANGELOG
149
193
  - CODE_OF_CONDUCT.md
150
194
  - Gemfile
@@ -152,25 +196,45 @@ files:
152
196
  - LICENSE.txt
153
197
  - README.md
154
198
  - Rakefile
199
+ - bench/builder_perf.rb
200
+ - bench/decorator_perf.rb
201
+ - bench/mini_sql_methods_perf.rb
202
+ - bench/prepared_perf.rb
203
+ - bench/shared/generate_data.rb
155
204
  - bench/timestamp_perf.rb
156
205
  - bench/topic_mysql_perf.rb
157
206
  - bench/topic_perf.rb
207
+ - bench/topic_wide_perf.rb
158
208
  - bin/console
159
209
  - bin/setup
160
210
  - lib/mini_sql.rb
211
+ - lib/mini_sql/abstract/prepared_binds.rb
212
+ - lib/mini_sql/abstract/prepared_cache.rb
161
213
  - lib/mini_sql/builder.rb
162
214
  - lib/mini_sql/connection.rb
215
+ - lib/mini_sql/decoratable.rb
163
216
  - lib/mini_sql/deserializer_cache.rb
164
217
  - lib/mini_sql/inline_param_encoder.rb
165
218
  - lib/mini_sql/mysql/connection.rb
166
219
  - lib/mini_sql/mysql/deserializer_cache.rb
220
+ - lib/mini_sql/mysql/prepared_binds.rb
221
+ - lib/mini_sql/mysql/prepared_cache.rb
222
+ - lib/mini_sql/mysql/prepared_connection.rb
167
223
  - lib/mini_sql/postgres/coders.rb
168
224
  - lib/mini_sql/postgres/connection.rb
169
225
  - lib/mini_sql/postgres/deserializer_cache.rb
226
+ - lib/mini_sql/postgres/prepared_binds.rb
227
+ - lib/mini_sql/postgres/prepared_cache.rb
228
+ - lib/mini_sql/postgres/prepared_connection.rb
170
229
  - lib/mini_sql/postgres_jdbc/connection.rb
171
230
  - lib/mini_sql/postgres_jdbc/deserializer_cache.rb
231
+ - lib/mini_sql/result.rb
232
+ - lib/mini_sql/serializer.rb
172
233
  - lib/mini_sql/sqlite/connection.rb
173
234
  - lib/mini_sql/sqlite/deserializer_cache.rb
235
+ - lib/mini_sql/sqlite/prepared_binds.rb
236
+ - lib/mini_sql/sqlite/prepared_cache.rb
237
+ - lib/mini_sql/sqlite/prepared_connection.rb
174
238
  - lib/mini_sql/version.rb
175
239
  - mini_sql.gemspec
176
240
  homepage: https://github.com/discourse/mini_sql
@@ -180,7 +244,7 @@ metadata:
180
244
  bug_tracker_uri: https://github.com/discourse/mini_sql/issues
181
245
  source_code_uri: https://github.com/discourse/mini_sql
182
246
  changelog_uri: https://github.com/discourse/mini_sql/blob/master/CHANGELOG
183
- post_install_message:
247
+ post_install_message:
184
248
  rdoc_options: []
185
249
  require_paths:
186
250
  - lib
@@ -195,8 +259,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
259
  - !ruby/object:Gem::Version
196
260
  version: '0'
197
261
  requirements: []
198
- rubygems_version: 3.0.3
199
- signing_key:
262
+ rubygems_version: 3.2.2
263
+ signing_key:
200
264
  specification_version: 4
201
265
  summary: A fast, safe, simple direct SQL executor
202
266
  test_files: []