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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Postgres
3
5
  module Coders
@@ -39,12 +39,19 @@ module MiniSql
39
39
  @raw_connection = raw_connection
40
40
  @deserializer_cache = (args && args[:deserializer_cache]) || self.class.default_deserializer_cache
41
41
  @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
42
+ @type_map = args && args[:type_map]
43
+
44
+ @prepared = PreparedConnection.new(self, @deserializer_cache)
42
45
  end
43
46
 
44
47
  def type_map
45
48
  @type_map ||= self.class.type_map(raw_connection)
46
49
  end
47
50
 
51
+ def prepared(condition = true)
52
+ condition ? @prepared : self
53
+ end
54
+
48
55
  # Returns a flat array containing all results.
49
56
  # Note, if selecting multiple columns array will be flattened
50
57
  #
@@ -78,6 +85,14 @@ module MiniSql
78
85
  result.clear if result
79
86
  end
80
87
 
88
+ def query_array(sql, *params)
89
+ result = run(sql, params)
90
+ result.type_map = type_map
91
+ result.values
92
+ ensure
93
+ result.clear if result
94
+ end
95
+
81
96
  def query(sql, *params)
82
97
  result = run(sql, params)
83
98
  result.type_map = type_map
@@ -86,6 +101,80 @@ module MiniSql
86
101
  result.clear if result
87
102
  end
88
103
 
104
+ def query_each(sql, *params)
105
+ raise StandardError, "Please supply a block when calling query_each" if !block_given?
106
+ if params && params.length > 0
107
+ sql = param_encoder.encode(sql, *params)
108
+ end
109
+
110
+ raw_connection.send_query(sql)
111
+ raw_connection.set_single_row_mode
112
+
113
+ loop do
114
+ result = raw_connection.get_result
115
+ break if !result
116
+
117
+ result.check
118
+
119
+ if result.ntuples == 0
120
+ # skip, this happens at the end when we get totals
121
+ else
122
+ materializer ||= @deserializer_cache.materializer(result)
123
+ result.type_map = type_map
124
+ i = 0
125
+ # technically we should only get 1 row here
126
+ # but protect against future batching changes
127
+ while i < result.ntuples
128
+ yield materializer.materialize(result, i)
129
+ i += 1
130
+ end
131
+ end
132
+
133
+ result.clear
134
+ end
135
+ end
136
+
137
+ def query_each_hash(sql, *params)
138
+ raise StandardError, "Please supply a block when calling query_each_hash" if !block_given?
139
+ if params && params.length > 0
140
+ sql = param_encoder.encode(sql, *params)
141
+ end
142
+
143
+ raw_connection.send_query(sql)
144
+ raw_connection.set_single_row_mode
145
+
146
+ loop do
147
+ result = raw_connection.get_result
148
+ break if !result
149
+
150
+ result.check
151
+
152
+ if result.ntuples == 0
153
+ # skip, this happens at the end when we get totals
154
+ else
155
+ result.type_map = type_map
156
+ i = 0
157
+
158
+ # technically we should only get 1 row here
159
+ # but protect against future batching changes
160
+ while i < result.ntuples
161
+ yield result[i]
162
+ i += 1
163
+ end
164
+ end
165
+
166
+ result.clear
167
+ end
168
+ end
169
+
170
+ def query_decorator(decorator, sql, *params)
171
+ result = run(sql, params)
172
+ result.type_map = type_map
173
+ @deserializer_cache.materialize(result, decorator)
174
+ ensure
175
+ result.clear if result
176
+ end
177
+
89
178
  def exec(sql, *params)
90
179
  result = run(sql, params)
91
180
  result.cmd_tuples
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Postgres
3
5
  class DeserializerCache
@@ -9,21 +11,38 @@ module MiniSql
9
11
  @max_size = max_size || DEFAULT_MAX_SIZE
10
12
  end
11
13
 
12
- def materialize(result)
14
+ def materializer(result)
15
+ key = result.fields.join(',')
13
16
 
17
+ materializer = @cache.delete(key)
18
+ if materializer
19
+ @cache[key] = materializer
20
+ else
21
+ materializer = @cache[key] = new_row_materializer(result)
22
+ @cache.shift if @cache.length > @max_size
23
+ end
24
+
25
+ materializer
26
+ end
27
+
28
+ def materialize(result, decorator_module = nil)
14
29
  return [] if result.ntuples == 0
15
30
 
16
- key = result.fields
31
+ key = result.fields.join(',')
17
32
 
18
33
  # trivial fast LRU implementation
19
34
  materializer = @cache.delete(key)
20
35
  if materializer
21
36
  @cache[key] = materializer
22
37
  else
23
- materializer = @cache[key] = new_row_matrializer(result)
38
+ materializer = @cache[key] = new_row_materializer(result)
24
39
  @cache.shift if @cache.length > @max_size
25
40
  end
26
41
 
42
+ if decorator_module
43
+ materializer = materializer.decorated(decorator_module)
44
+ end
45
+
27
46
  i = 0
28
47
  r = []
29
48
  # quicker loop
@@ -36,27 +55,28 @@ module MiniSql
36
55
 
37
56
  private
38
57
 
39
- def new_row_matrializer(result)
58
+ def new_row_materializer(result)
40
59
  fields = result.fields
41
60
 
42
- Class.new do
43
- attr_accessor(*fields)
61
+ i = 0
62
+ while i < fields.length
63
+ # special handling for unamed column
64
+ if fields[i] == "?column?"
65
+ fields[i] = "column#{i}"
66
+ end
67
+ i += 1
68
+ end
44
69
 
45
- # AM serializer support
46
- alias :read_attribute_for_serialization :send
70
+ Class.new do
71
+ extend MiniSql::Decoratable
72
+ include MiniSql::Result
47
73
 
48
- def to_h
49
- r = {}
50
- instance_variables.each do |f|
51
- r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
52
- end
53
- r
54
- end
74
+ attr_accessor(*fields)
55
75
 
56
76
  instance_eval <<~RUBY
57
77
  def materialize(pg_result, index)
58
78
  r = self.new
59
- #{col=-1; fields.map{|f| "r.#{f} = pg_result.getvalue(index, #{col+=1})"}.join("; ")}
79
+ #{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
60
80
  r
61
81
  end
62
82
  RUBY
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_binds"
4
+
5
+ module MiniSql
6
+ module Postgres
7
+ class PreparedBinds < ::MiniSql::Abstract::PreparedBinds
8
+
9
+ def bind_output(i)
10
+ "$#{i}"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_cache"
4
+
5
+ module MiniSql
6
+ module Postgres
7
+ class PreparedCache < ::MiniSql::Abstract::PreparedCache
8
+
9
+ private
10
+
11
+ def alloc(sql)
12
+ alloc_key = next_key
13
+ @connection.prepare(alloc_key, sql)
14
+
15
+ alloc_key
16
+ end
17
+
18
+ def dealloc(key)
19
+ @connection.query "DEALLOCATE #{key}" if @connection.status == PG::CONNECTION_OK
20
+ rescue PG::Error
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Postgres
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
+ @type_map = unprepared_connection.type_map
14
+ @param_encoder = unprepared_connection.param_encoder
15
+
16
+ @prepared_cache = PreparedCache.new(@raw_connection)
17
+ @param_binder = PreparedBinds.new
18
+ end
19
+
20
+ def build(_)
21
+ raise 'Builder can not be called on prepared connections, instead of `::MINI_SQL.prepared.build(sql).query` use `::MINI_SQL.build(sql).prepared.query`'
22
+ end
23
+
24
+ def prepared(condition = true)
25
+ condition ? self : @unprepared
26
+ end
27
+
28
+ private def run(sql, params)
29
+ prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
30
+ prepare_statement_key = @prepared_cache.prepare_statement(prepared_sql)
31
+ raw_connection.exec_prepared(prepare_statement_key, binds)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -60,6 +60,11 @@ module MiniSql
60
60
  @deserializer_cache.materialize(result)
61
61
  end
62
62
 
63
+ def query_decorator(decorator, sql, *params)
64
+ result = run(sql, params)
65
+ @deserializer_cache.materialize(result, decorator)
66
+ end
67
+
63
68
  def exec(sql, *params)
64
69
  result = run(sql, params)
65
70
  if result.kind_of? Integer
@@ -84,7 +89,9 @@ module MiniSql
84
89
  private
85
90
 
86
91
  def run(sql, params)
87
- sql = param_encoder.encode(sql, *params) if params && params.length > 0
92
+ if params && params.length > 0
93
+ sql = param_encoder.encode(sql, *params)
94
+ end
88
95
  conn = raw_connection
89
96
  conn.typemap = self.class.typemap
90
97
  conn.execute(sql)
@@ -1,67 +1,67 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Postgres
3
5
  class DeserializerCache
4
6
 
5
- DEFAULT_MAX_SIZE = 500
7
+ DEFAULT_MAX_SIZE = 500
6
8
 
7
- def initialize(max_size = nil)
8
- @cache = {}
9
- @max_size = max_size || DEFAULT_MAX_SIZE
10
- end
9
+ def initialize(max_size = nil)
10
+ @cache = {}
11
+ @max_size = max_size || DEFAULT_MAX_SIZE
12
+ end
11
13
 
12
- def materialize(result)
14
+ def materialize(result, decorator_module = nil)
13
15
 
14
- return [] if result.ntuples == 0
16
+ return [] if result.ntuples == 0
15
17
 
16
- key = result.fields
18
+ key = result.fields.join(',')
17
19
 
18
- # trivial fast LRU implementation
19
- materializer = @cache.delete(key)
20
- if materializer
21
- @cache[key] = materializer
22
- else
23
- materializer = @cache[key] = new_row_matrializer(result)
24
- @cache.shift if @cache.length > @max_size
25
- end
20
+ # trivial fast LRU implementation
21
+ materializer = @cache.delete(key)
22
+ if materializer
23
+ @cache[key] = materializer
24
+ else
25
+ materializer = @cache[key] = new_row_materializer(result)
26
+ @cache.shift if @cache.length > @max_size
27
+ end
26
28
 
27
- i = 0
28
- r = []
29
- # quicker loop
30
- while i < result.ntuples
31
- r << materializer.materialize(result, i)
32
- i += 1
33
- end
34
- r
35
- end
29
+ materializer.include(decorator_module) if decorator_module
36
30
 
37
- private
31
+ if decorator_module
32
+ materializer = materializer.decorated(decorator_module)
33
+ end
34
+
35
+ i = 0
36
+ r = []
37
+ # quicker loop
38
+ while i < result.ntuples
39
+ r << materializer.materialize(result, i)
40
+ i += 1
41
+ end
42
+ r
43
+ end
38
44
 
39
- def new_row_matrializer(result)
40
- fields = result.fields
45
+ private
41
46
 
42
- Class.new do
43
- attr_accessor(*fields)
47
+ def new_row_materializer(result)
48
+ fields = result.fields
44
49
 
45
- # AM serializer support
46
- alias :read_attribute_for_serialization :send
50
+ Class.new do
51
+ extend MiniSql::Decoratable
52
+ include MiniSql::Result
47
53
 
48
- def to_h
49
- r = {}
50
- instance_variables.each do |f|
51
- r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
52
- end
53
- r
54
- end
54
+ attr_accessor(*fields)
55
55
 
56
- instance_eval <<~RUBY
56
+ instance_eval <<~RUBY
57
57
  def materialize(pg_result, index)
58
58
  r = self.new
59
- #{col=-1; fields.map{|f| "r.#{f} = pg_result.getvalue(index, #{col+=1})"}.join("; ")}
59
+ #{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
60
60
  r
61
61
  end
62
- RUBY
62
+ RUBY
63
+ end
63
64
  end
64
65
  end
65
66
  end
66
67
  end
67
- end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Result
5
+ # AM serializer support
6
+ alias :read_attribute_for_serialization :send
7
+
8
+ def to_h
9
+ r = {}
10
+ instance_variables.each do |f|
11
+ r[f.to_s.delete_prefix('@').to_sym] = instance_variable_get(f)
12
+ end
13
+ r
14
+ end
15
+
16
+ def values
17
+ instance_variables.map { |f| instance_variable_get(f) }
18
+ end
19
+
20
+ def ==(other_result)
21
+ self.class.decorator == other_result.class.decorator &&
22
+ self.instance_variables == other_result.instance_variables &&
23
+ self.values == other_result.values
24
+ end
25
+
26
+ def eql?(other_result)
27
+ self == other_result
28
+ end
29
+ end
30
+ end