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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +66 -0
- data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG +22 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -0
- data/README.md +125 -1
- data/Rakefile +3 -1
- data/bench/builder_perf.rb +138 -0
- data/bench/decorator_perf.rb +143 -0
- data/bench/mini_sql_methods_perf.rb +80 -0
- data/bench/prepared_perf.rb +59 -0
- data/bench/shared/generate_data.rb +133 -0
- data/bench/timestamp_perf.rb +22 -21
- data/bench/topic_mysql_perf.rb +1 -7
- data/bench/topic_perf.rb +27 -169
- data/bench/topic_wide_perf.rb +92 -0
- data/bin/console +1 -0
- data/lib/mini_sql.rb +20 -8
- data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
- data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
- data/lib/mini_sql/builder.rb +64 -24
- data/lib/mini_sql/connection.rb +15 -3
- data/lib/mini_sql/decoratable.rb +22 -0
- data/lib/mini_sql/deserializer_cache.rb +2 -0
- data/lib/mini_sql/inline_param_encoder.rb +12 -13
- data/lib/mini_sql/mysql/connection.rb +18 -3
- data/lib/mini_sql/mysql/deserializer_cache.rb +14 -16
- data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
- data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
- data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +89 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +36 -16
- data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
- data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
- data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
- data/lib/mini_sql/postgres_jdbc/connection.rb +8 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
- data/lib/mini_sql/result.rb +30 -0
- data/lib/mini_sql/serializer.rb +84 -0
- data/lib/mini_sql/sqlite/connection.rb +20 -2
- data/lib/mini_sql/sqlite/deserializer_cache.rb +14 -16
- data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
- data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
- data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
- data/lib/mini_sql/version.rb +1 -1
- data/mini_sql.gemspec +7 -2
- metadata +75 -11
- data/.travis.yml +0 -26
@@ -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
|
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] =
|
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
|
58
|
+
def new_row_materializer(result)
|
40
59
|
fields = result.fields
|
41
60
|
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
70
|
+
Class.new do
|
71
|
+
extend MiniSql::Decoratable
|
72
|
+
include MiniSql::Result
|
47
73
|
|
48
|
-
|
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
|
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,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
|
-
|
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
|
-
|
7
|
+
DEFAULT_MAX_SIZE = 500
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def initialize(max_size = nil)
|
10
|
+
@cache = {}
|
11
|
+
@max_size = max_size || DEFAULT_MAX_SIZE
|
12
|
+
end
|
11
13
|
|
12
|
-
|
14
|
+
def materialize(result, decorator_module = nil)
|
13
15
|
|
14
|
-
|
16
|
+
return [] if result.ntuples == 0
|
15
17
|
|
16
|
-
|
18
|
+
key = result.fields.join(',')
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
40
|
-
fields = result.fields
|
45
|
+
private
|
41
46
|
|
42
|
-
|
43
|
-
|
47
|
+
def new_row_materializer(result)
|
48
|
+
fields = result.fields
|
44
49
|
|
45
|
-
|
46
|
-
|
50
|
+
Class.new do
|
51
|
+
extend MiniSql::Decoratable
|
52
|
+
include MiniSql::Result
|
47
53
|
|
48
|
-
|
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
|
-
|
56
|
+
instance_eval <<~RUBY
|
57
57
|
def materialize(pg_result, index)
|
58
58
|
r = self.new
|
59
|
-
#{col
|
59
|
+
#{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
|
60
60
|
r
|
61
61
|
end
|
62
|
-
|
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
|