mini_sql 0.2.2 → 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 +90 -2
- data/Rakefile +3 -1
- data/bench/timestamp_perf.rb +22 -21
- data/bench/topic_mysql_perf.rb +304 -0
- data/bench/topic_perf.rb +175 -11
- data/bin/console +1 -0
- data/lib/mini_sql.rb +8 -0
- data/lib/mini_sql/builder.rb +10 -2
- data/lib/mini_sql/connection.rb +17 -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 +9 -9
- data/lib/mini_sql/mysql/connection.rb +67 -0
- data/lib/mini_sql/mysql/deserializer_cache.rb +57 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +80 -3
- data/lib/mini_sql/postgres/deserializer_cache.rb +33 -13
- data/lib/mini_sql/postgres_jdbc/connection.rb +6 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
- data/lib/mini_sql/result.rb +20 -0
- data/lib/mini_sql/serializer.rb +70 -0
- data/lib/mini_sql/sqlite/connection.rb +12 -4
- data/lib/mini_sql/sqlite/deserializer_cache.rb +11 -13
- data/lib/mini_sql/version.rb +2 -1
- data/mini_sql.gemspec +15 -3
- metadata +80 -13
- data/.travis.yml +0 -21
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Mysql
|
5
|
+
class DeserializerCache
|
6
|
+
|
7
|
+
DEFAULT_MAX_SIZE = 500
|
8
|
+
|
9
|
+
def initialize(max_size = nil)
|
10
|
+
@cache = {}
|
11
|
+
@max_size = max_size || DEFAULT_MAX_SIZE
|
12
|
+
end
|
13
|
+
|
14
|
+
def materialize(result, decorator_module = nil)
|
15
|
+
key = result.fields
|
16
|
+
|
17
|
+
# trivial fast LRU implementation
|
18
|
+
materializer = @cache.delete(key)
|
19
|
+
if materializer
|
20
|
+
@cache[key] = materializer
|
21
|
+
else
|
22
|
+
materializer = @cache[key] = new_row_matrializer(result)
|
23
|
+
@cache.shift if @cache.length > @max_size
|
24
|
+
end
|
25
|
+
|
26
|
+
if decorator_module
|
27
|
+
materializer = materializer.decorated(decorator_module)
|
28
|
+
end
|
29
|
+
|
30
|
+
result.map do |data|
|
31
|
+
materializer.materialize(data)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def new_row_matrializer(result)
|
38
|
+
fields = result.fields
|
39
|
+
|
40
|
+
Class.new do
|
41
|
+
extend MiniSql::Decoratable
|
42
|
+
include MiniSql::Result
|
43
|
+
|
44
|
+
attr_accessor(*fields)
|
45
|
+
|
46
|
+
instance_eval <<~RUBY
|
47
|
+
def materialize(data)
|
48
|
+
r = self.new
|
49
|
+
#{col = -1; fields.map { |f| "r.#{f} = data[#{col += 1}]" }.join("; ")}
|
50
|
+
r
|
51
|
+
end
|
52
|
+
RUBY
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -39,6 +39,7 @@ 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]
|
42
43
|
end
|
43
44
|
|
44
45
|
def type_map
|
@@ -78,6 +79,14 @@ module MiniSql
|
|
78
79
|
result.clear if result
|
79
80
|
end
|
80
81
|
|
82
|
+
def query_array(sql, *params)
|
83
|
+
result = run(sql, params)
|
84
|
+
result.type_map = type_map
|
85
|
+
result.values
|
86
|
+
ensure
|
87
|
+
result.clear if result
|
88
|
+
end
|
89
|
+
|
81
90
|
def query(sql, *params)
|
82
91
|
result = run(sql, params)
|
83
92
|
result.type_map = type_map
|
@@ -86,6 +95,76 @@ module MiniSql
|
|
86
95
|
result.clear if result
|
87
96
|
end
|
88
97
|
|
98
|
+
def query_each(sql, *params)
|
99
|
+
raise StandardError, "Please supply a block when calling query_each" if !block_given?
|
100
|
+
sql = param_encoder.encode(sql, *params)
|
101
|
+
|
102
|
+
raw_connection.send_query(sql)
|
103
|
+
raw_connection.set_single_row_mode
|
104
|
+
|
105
|
+
loop do
|
106
|
+
result = raw_connection.get_result
|
107
|
+
break if !result
|
108
|
+
|
109
|
+
result.check
|
110
|
+
|
111
|
+
if result.ntuples == 0
|
112
|
+
# skip, this happens at the end when we get totals
|
113
|
+
else
|
114
|
+
materializer ||= @deserializer_cache.materializer(result)
|
115
|
+
result.type_map = type_map
|
116
|
+
i = 0
|
117
|
+
# technically we should only get 1 row here
|
118
|
+
# but protect against future batching changes
|
119
|
+
while i < result.ntuples
|
120
|
+
yield materializer.materialize(result, i)
|
121
|
+
i += 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
result.clear
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def query_each_hash(sql, *params)
|
130
|
+
raise StandardError, "Please supply a block when calling query_each_hash" if !block_given?
|
131
|
+
sql = param_encoder.encode(sql, *params)
|
132
|
+
|
133
|
+
raw_connection.send_query(sql)
|
134
|
+
raw_connection.set_single_row_mode
|
135
|
+
|
136
|
+
loop do
|
137
|
+
result = raw_connection.get_result
|
138
|
+
break if !result
|
139
|
+
|
140
|
+
result.check
|
141
|
+
|
142
|
+
if result.ntuples == 0
|
143
|
+
# skip, this happens at the end when we get totals
|
144
|
+
else
|
145
|
+
result.type_map = type_map
|
146
|
+
i = 0
|
147
|
+
|
148
|
+
# technically we should only get 1 row here
|
149
|
+
# but protect against future batching changes
|
150
|
+
while i < result.ntuples
|
151
|
+
yield result[i]
|
152
|
+
i += 1
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
result.clear
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def query_decorator(decorator, sql, *params)
|
161
|
+
result = run(sql, params)
|
162
|
+
result.type_map = type_map
|
163
|
+
@deserializer_cache.materialize(result, decorator)
|
164
|
+
ensure
|
165
|
+
result.clear if result
|
166
|
+
end
|
167
|
+
|
89
168
|
def exec(sql, *params)
|
90
169
|
result = run(sql, params)
|
91
170
|
result.cmd_tuples
|
@@ -112,9 +191,7 @@ module MiniSql
|
|
112
191
|
private
|
113
192
|
|
114
193
|
def run(sql, params)
|
115
|
-
|
116
|
-
sql = param_encoder.encode(sql, *params)
|
117
|
-
end
|
194
|
+
sql = param_encoder.encode(sql, *params)
|
118
195
|
raw_connection.async_exec(sql)
|
119
196
|
end
|
120
197
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MiniSql
|
2
4
|
module Postgres
|
3
5
|
class DeserializerCache
|
@@ -9,8 +11,21 @@ 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
|
16
|
+
|
17
|
+
materializer = @cache.delete(key)
|
18
|
+
if materializer
|
19
|
+
@cache[key] = materializer
|
20
|
+
else
|
21
|
+
materializer = @cache[key] = new_row_matrializer(result)
|
22
|
+
@cache.shift if @cache.length > @max_size
|
23
|
+
end
|
24
|
+
|
25
|
+
materializer
|
26
|
+
end
|
13
27
|
|
28
|
+
def materialize(result, decorator_module = nil)
|
14
29
|
return [] if result.ntuples == 0
|
15
30
|
|
16
31
|
key = result.fields
|
@@ -24,6 +39,10 @@ module MiniSql
|
|
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
|
@@ -39,24 +58,25 @@ module MiniSql
|
|
39
58
|
def new_row_matrializer(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
|
@@ -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,7 @@ module MiniSql
|
|
84
89
|
private
|
85
90
|
|
86
91
|
def run(sql, params)
|
87
|
-
sql = param_encoder.encode(sql, *params)
|
92
|
+
sql = param_encoder.encode(sql, *params)
|
88
93
|
conn = raw_connection
|
89
94
|
conn.typemap = self.class.typemap
|
90
95
|
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
|
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_matrializer(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_matrializer(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,20 @@
|
|
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
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Serializer
|
5
|
+
MAX_CACHE_SIZE = 500
|
6
|
+
|
7
|
+
def self.to_json(result)
|
8
|
+
wrapper =
|
9
|
+
if result.length == 0
|
10
|
+
{}
|
11
|
+
else
|
12
|
+
{
|
13
|
+
"decorator" => result[0].class.decorator.to_s,
|
14
|
+
"fields" => result[0].to_h.keys,
|
15
|
+
"data" => result.map(&:values),
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
JSON.generate(wrapper)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from_json(json)
|
23
|
+
wrapper = JSON.parse(json)
|
24
|
+
if !wrapper["data"]
|
25
|
+
[]
|
26
|
+
else
|
27
|
+
materializer = cached_materializer(wrapper['fields'], wrapper['decorator'])
|
28
|
+
wrapper["data"].map do |row|
|
29
|
+
materializer.materialize(row)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.cached_materializer(fields, decorator_module = nil)
|
35
|
+
@cache ||= {}
|
36
|
+
key = fields
|
37
|
+
m = @cache.delete(key)
|
38
|
+
if m
|
39
|
+
@cache[key] = m
|
40
|
+
else
|
41
|
+
m = @cache[key] = materializer(fields)
|
42
|
+
@cache.shift if @cache.length > MAX_CACHE_SIZE
|
43
|
+
end
|
44
|
+
|
45
|
+
if decorator_module && decorator_module.length > 0
|
46
|
+
decorator = Kernel.const_get(decorator_module)
|
47
|
+
m = m.decorated(decorator)
|
48
|
+
end
|
49
|
+
|
50
|
+
m
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.materializer(fields)
|
54
|
+
Class.new do
|
55
|
+
extend MiniSql::Decoratable
|
56
|
+
include MiniSql::Result
|
57
|
+
|
58
|
+
attr_accessor(*fields)
|
59
|
+
|
60
|
+
instance_eval <<~RUBY
|
61
|
+
def materialize(values)
|
62
|
+
r = self.new
|
63
|
+
#{col = -1; fields.map { |f| "r.#{f} = values[#{col += 1}]" }.join("; ")}
|
64
|
+
r
|
65
|
+
end
|
66
|
+
RUBY
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|