mini_sql 0.2.1 → 0.3
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/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +8 -1
- data/CHANGELOG +20 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -0
- data/README.md +90 -2
- data/Rakefile +10 -2
- 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 +20 -8
- data/lib/mini_sql/builder.rb +0 -2
- data/lib/mini_sql/connection.rb +18 -2
- 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 +69 -0
- data/lib/mini_sql/mysql/deserializer_cache.rb +63 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +83 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +29 -3
- data/lib/mini_sql/postgres_jdbc/connection.rb +102 -0
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +71 -0
- data/lib/mini_sql/sqlite/connection.rb +11 -1
- data/lib/mini_sql/sqlite/deserializer_cache.rb +7 -3
- data/lib/mini_sql/version.rb +2 -1
- data/mini_sql.gemspec +23 -5
- metadata +78 -26
@@ -0,0 +1,63 @@
|
|
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
|
+
materializer.include(decorator_module) if decorator_module
|
27
|
+
|
28
|
+
result.map do |data|
|
29
|
+
materializer.materialize(data)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def new_row_matrializer(result)
|
36
|
+
fields = result.fields
|
37
|
+
|
38
|
+
Class.new do
|
39
|
+
attr_accessor(*fields)
|
40
|
+
|
41
|
+
# AM serializer support
|
42
|
+
alias :read_attribute_for_serialization :send
|
43
|
+
|
44
|
+
def to_h
|
45
|
+
r = {}
|
46
|
+
instance_variables.each do |f|
|
47
|
+
r[f.to_s.sub('@', '').to_sym] = instance_variable_get(f)
|
48
|
+
end
|
49
|
+
r
|
50
|
+
end
|
51
|
+
|
52
|
+
instance_eval <<~RUBY
|
53
|
+
def materialize(data)
|
54
|
+
r = self.new
|
55
|
+
#{col = -1; fields.map { |f| "r.#{f} = data[#{col += 1}]" }.join("; ")}
|
56
|
+
r
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
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,80 @@ 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
|
+
if params && params.length > 0
|
101
|
+
sql = param_encoder.encode(sql, *params)
|
102
|
+
end
|
103
|
+
|
104
|
+
raw_connection.send_query(sql)
|
105
|
+
raw_connection.set_single_row_mode
|
106
|
+
|
107
|
+
loop do
|
108
|
+
result = raw_connection.get_result
|
109
|
+
break if !result
|
110
|
+
|
111
|
+
result.check
|
112
|
+
|
113
|
+
if result.ntuples == 0
|
114
|
+
# skip, this happens at the end when we get totals
|
115
|
+
else
|
116
|
+
materializer ||= @deserializer_cache.materializer(result)
|
117
|
+
result.type_map = type_map
|
118
|
+
i = 0
|
119
|
+
# technically we should only get 1 row here
|
120
|
+
# but protect against future batching changes
|
121
|
+
while i < result.ntuples
|
122
|
+
yield materializer.materialize(result, i)
|
123
|
+
i += 1
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
result.clear
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def query_each_hash(sql, *params)
|
132
|
+
raise StandardError, "Please supply a block when calling query_each_hash" if !block_given?
|
133
|
+
if params && params.length > 0
|
134
|
+
sql = param_encoder.encode(sql, *params)
|
135
|
+
end
|
136
|
+
|
137
|
+
raw_connection.send_query(sql)
|
138
|
+
raw_connection.set_single_row_mode
|
139
|
+
|
140
|
+
loop do
|
141
|
+
result = raw_connection.get_result
|
142
|
+
break if !result
|
143
|
+
|
144
|
+
result.check
|
145
|
+
|
146
|
+
if result.ntuples == 0
|
147
|
+
# skip, this happens at the end when we get totals
|
148
|
+
else
|
149
|
+
result.type_map = type_map
|
150
|
+
i = 0
|
151
|
+
|
152
|
+
# technically we should only get 1 row here
|
153
|
+
# but protect against future batching changes
|
154
|
+
while i < result.ntuples
|
155
|
+
yield result[i]
|
156
|
+
i += 1
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
result.clear
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def query_decorator(decorator, sql, *params)
|
165
|
+
result = run(sql, params)
|
166
|
+
result.type_map = type_map
|
167
|
+
@deserializer_cache.materialize(result, decorator)
|
168
|
+
ensure
|
169
|
+
result.clear if result
|
170
|
+
end
|
171
|
+
|
89
172
|
def exec(sql, *params)
|
90
173
|
result = run(sql, params)
|
91
174
|
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,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,8 @@ module MiniSql
|
|
24
39
|
@cache.shift if @cache.length > @max_size
|
25
40
|
end
|
26
41
|
|
42
|
+
materializer.include(decorator_module) if decorator_module
|
43
|
+
|
27
44
|
i = 0
|
28
45
|
r = []
|
29
46
|
# quicker loop
|
@@ -39,6 +56,15 @@ module MiniSql
|
|
39
56
|
def new_row_matrializer(result)
|
40
57
|
fields = result.fields
|
41
58
|
|
59
|
+
i = 0
|
60
|
+
while i < fields.length
|
61
|
+
# special handling for unamed column
|
62
|
+
if fields[i] == "?column?"
|
63
|
+
fields[i] = "column#{i}"
|
64
|
+
end
|
65
|
+
i += 1
|
66
|
+
end
|
67
|
+
|
42
68
|
Class.new do
|
43
69
|
attr_accessor(*fields)
|
44
70
|
|
@@ -48,7 +74,7 @@ module MiniSql
|
|
48
74
|
def to_h
|
49
75
|
r = {}
|
50
76
|
instance_variables.each do |f|
|
51
|
-
r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
|
77
|
+
r[f.to_s.sub('@', '').to_sym] = instance_variable_get(f)
|
52
78
|
end
|
53
79
|
r
|
54
80
|
end
|
@@ -56,7 +82,7 @@ module MiniSql
|
|
56
82
|
instance_eval <<~RUBY
|
57
83
|
def materialize(pg_result, index)
|
58
84
|
r = self.new
|
59
|
-
#{col
|
85
|
+
#{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
|
60
86
|
r
|
61
87
|
end
|
62
88
|
RUBY
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Postgres
|
5
|
+
class Connection < MiniSql::Connection
|
6
|
+
class NumericCoder
|
7
|
+
def decode(string)
|
8
|
+
BigDecimal(string)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class IPAddrCoder
|
13
|
+
def decode(string)
|
14
|
+
IPAddr.new(string)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :raw_connection, :type_map, :param_encoder
|
19
|
+
|
20
|
+
def self.default_deserializer_cache
|
21
|
+
@deserializer_cache ||= DeserializerCache.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.typemap
|
25
|
+
@type_map ||= {
|
26
|
+
"numeric" => NumericCoder.new,
|
27
|
+
"inet" => IPAddrCoder.new,
|
28
|
+
"cidr" => IPAddrCoder.new
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initialize a new MiniSql::Postgres::Connection object
|
33
|
+
#
|
34
|
+
# @param raw_connection [PG::Connection] an active connection to PG
|
35
|
+
# @param deserializer_cache [MiniSql::DeserializerCache] a cache of field names to deserializer, can be nil
|
36
|
+
# @param type_map [PG::TypeMap] a type mapper for all results returned, can be nil
|
37
|
+
def initialize(raw_connection, args = nil)
|
38
|
+
@raw_connection = raw_connection
|
39
|
+
@deserializer_cache = (args && args[:deserializer_cache]) || self.class.default_deserializer_cache
|
40
|
+
@param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a flat array containing all results.
|
44
|
+
# Note, if selecting multiple columns array will be flattened
|
45
|
+
#
|
46
|
+
# @param sql [String] the query to run
|
47
|
+
# @param params [Array or Hash], params to apply to query
|
48
|
+
# @return [Object] a flat array containing all results
|
49
|
+
def query_single(sql, *params)
|
50
|
+
result = run(sql, params)
|
51
|
+
if result.length == 1
|
52
|
+
result.values[0]
|
53
|
+
else
|
54
|
+
result.values.each_with_object([]) { |value, array| array.concat value }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def query(sql, *params)
|
59
|
+
result = run(sql, params)
|
60
|
+
@deserializer_cache.materialize(result)
|
61
|
+
end
|
62
|
+
|
63
|
+
def query_decorator(decorator, sql, *params)
|
64
|
+
result = run(sql, params)
|
65
|
+
@deserializer_cache.materialize(result, decorator)
|
66
|
+
end
|
67
|
+
|
68
|
+
def exec(sql, *params)
|
69
|
+
result = run(sql, params)
|
70
|
+
if result.kind_of? Integer
|
71
|
+
result
|
72
|
+
else
|
73
|
+
result.length
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def query_hash(sql, *params)
|
78
|
+
run(sql, params).to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
def build(sql)
|
82
|
+
Builder.new(self, sql)
|
83
|
+
end
|
84
|
+
|
85
|
+
def escape_string(str)
|
86
|
+
raw_connection.escape_string(str)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def run(sql, params)
|
92
|
+
sql = param_encoder.encode(sql, *params) if params && params.length > 0
|
93
|
+
conn = raw_connection
|
94
|
+
conn.typemap = self.class.typemap
|
95
|
+
conn.execute(sql)
|
96
|
+
ensure
|
97
|
+
# Force unsetting of typemap since we don't want mixed AR usage to continue to use these extra converters.
|
98
|
+
conn.typemap = nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Postgres
|
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
|
+
|
16
|
+
return [] if result.ntuples == 0
|
17
|
+
|
18
|
+
key = result.fields
|
19
|
+
|
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
|
28
|
+
|
29
|
+
materializer.include(decorator_module) if decorator_module
|
30
|
+
|
31
|
+
i = 0
|
32
|
+
r = []
|
33
|
+
# quicker loop
|
34
|
+
while i < result.ntuples
|
35
|
+
r << materializer.materialize(result, i)
|
36
|
+
i += 1
|
37
|
+
end
|
38
|
+
r
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def new_row_matrializer(result)
|
44
|
+
fields = result.fields
|
45
|
+
|
46
|
+
Class.new do
|
47
|
+
attr_accessor(*fields)
|
48
|
+
|
49
|
+
# AM serializer support
|
50
|
+
alias :read_attribute_for_serialization :send
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
r = {}
|
54
|
+
instance_variables.each do |f|
|
55
|
+
r[f.to_s.sub('@', '').to_sym] = instance_variable_get(f)
|
56
|
+
end
|
57
|
+
r
|
58
|
+
end
|
59
|
+
|
60
|
+
instance_eval <<~RUBY
|
61
|
+
def materialize(pg_result, index)
|
62
|
+
r = self.new
|
63
|
+
#{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
|
64
|
+
r
|
65
|
+
end
|
66
|
+
RUBY
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -26,6 +26,10 @@ module MiniSql
|
|
26
26
|
r
|
27
27
|
end
|
28
28
|
|
29
|
+
def query_array(sql, *params)
|
30
|
+
run(sql, *params)
|
31
|
+
end
|
32
|
+
|
29
33
|
def exec(sql, *params)
|
30
34
|
|
31
35
|
start = raw_connection.total_changes
|
@@ -46,8 +50,14 @@ module MiniSql
|
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
53
|
+
def query_decorator(decorator, sql, *params)
|
54
|
+
run(sql, *params) do |set|
|
55
|
+
deserializer_cache.materialize(set, decorator)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
49
59
|
def escape_string(str)
|
50
|
-
str.gsub("'","''")
|
60
|
+
str.gsub("'", "''")
|
51
61
|
end
|
52
62
|
|
53
63
|
private
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MiniSql
|
2
4
|
module Sqlite
|
3
5
|
class DeserializerCache
|
@@ -9,7 +11,7 @@ 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
16
|
key = result.columns
|
15
17
|
|
@@ -22,6 +24,8 @@ module MiniSql
|
|
22
24
|
@cache.shift if @cache.length > @max_size
|
23
25
|
end
|
24
26
|
|
27
|
+
materializer.include(decorator_module) if decorator_module
|
28
|
+
|
25
29
|
r = []
|
26
30
|
# quicker loop
|
27
31
|
while !result.eof?
|
@@ -47,7 +51,7 @@ module MiniSql
|
|
47
51
|
def to_h
|
48
52
|
r = {}
|
49
53
|
instance_variables.each do |f|
|
50
|
-
r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
|
54
|
+
r[f.to_s.sub('@', '').to_sym] = instance_variable_get(f)
|
51
55
|
end
|
52
56
|
r
|
53
57
|
end
|
@@ -55,7 +59,7 @@ module MiniSql
|
|
55
59
|
instance_eval <<~RUBY
|
56
60
|
def materialize(data)
|
57
61
|
r = self.new
|
58
|
-
#{col
|
62
|
+
#{col = -1; fields.map { |f| "r.#{f} = data[#{col += 1}]" }.join("; ")}
|
59
63
|
r
|
60
64
|
end
|
61
65
|
RUBY
|