mini_sql 1.0 → 1.1.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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG +20 -0
- data/README.md +36 -0
- 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/topic_perf.rb +21 -327
- data/bench/topic_wide_perf.rb +92 -0
- data/lib/mini_sql.rb +17 -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 +63 -30
- data/lib/mini_sql/inline_param_encoder.rb +4 -5
- data/lib/mini_sql/mysql/connection.rb +13 -3
- data/lib/mini_sql/mysql/deserializer_cache.rb +3 -3
- 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 +47 -0
- data/lib/mini_sql/postgres/connection.rb +21 -7
- data/lib/mini_sql/postgres/deserializer_cache.rb +5 -5
- 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 +39 -0
- data/lib/mini_sql/postgres_jdbc/connection.rb +3 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +3 -3
- data/lib/mini_sql/result.rb +10 -0
- data/lib/mini_sql/serializer.rb +29 -15
- data/lib/mini_sql/sqlite/connection.rb +14 -2
- data/lib/mini_sql/sqlite/deserializer_cache.rb +3 -3
- 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 +43 -0
- data/lib/mini_sql/version.rb +1 -1
- metadata +20 -3
@@ -12,14 +12,14 @@ module MiniSql
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def materialize(result, decorator_module = nil)
|
15
|
-
key = result.fields
|
15
|
+
key = result.fields.join(',')
|
16
16
|
|
17
17
|
# trivial fast LRU implementation
|
18
18
|
materializer = @cache.delete(key)
|
19
19
|
if materializer
|
20
20
|
@cache[key] = materializer
|
21
21
|
else
|
22
|
-
materializer = @cache[key] =
|
22
|
+
materializer = @cache[key] = new_row_materializer(result)
|
23
23
|
@cache.shift if @cache.length > @max_size
|
24
24
|
end
|
25
25
|
|
@@ -34,7 +34,7 @@ module MiniSql
|
|
34
34
|
|
35
35
|
private
|
36
36
|
|
37
|
-
def
|
37
|
+
def new_row_materializer(result)
|
38
38
|
fields = result.fields
|
39
39
|
|
40
40
|
Class.new do
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mini_sql/abstract/prepared_cache"
|
4
|
+
|
5
|
+
module MiniSql
|
6
|
+
module Mysql
|
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,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Mysql
|
5
|
+
class PreparedConnection < Connection
|
6
|
+
|
7
|
+
attr_reader :unprepared
|
8
|
+
|
9
|
+
def initialize(unprepared_connection)
|
10
|
+
@unprepared = unprepared_connection
|
11
|
+
@raw_connection = unprepared_connection.raw_connection
|
12
|
+
@param_encoder = unprepared_connection.param_encoder
|
13
|
+
|
14
|
+
@prepared_cache = PreparedCache.new(@raw_connection)
|
15
|
+
@param_binder = PreparedBinds.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def build(_)
|
19
|
+
raise 'Builder can not be called on prepared connections, instead of `::MINI_SQL.prepared.build(sql).query` use `::MINI_SQL.build(sql).prepared.query`'
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepared(condition = true)
|
23
|
+
condition ? self : @unprepared
|
24
|
+
end
|
25
|
+
|
26
|
+
def deserializer_cache
|
27
|
+
@unprepared.deserializer_cache
|
28
|
+
end
|
29
|
+
|
30
|
+
private def run(sql, as, params)
|
31
|
+
prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
|
32
|
+
statement = @prepared_cache.prepare_statement(prepared_sql)
|
33
|
+
statement.execute(
|
34
|
+
*binds,
|
35
|
+
as: as,
|
36
|
+
database_timezone: :utc,
|
37
|
+
application_timezone: :utc,
|
38
|
+
cast_booleans: true,
|
39
|
+
cast: true,
|
40
|
+
cache_rows: true,
|
41
|
+
symbolize_keys: false
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module MiniSql
|
4
4
|
module Postgres
|
5
5
|
class Connection < MiniSql::Connection
|
6
|
-
attr_reader :raw_connection, :
|
6
|
+
attr_reader :raw_connection, :param_encoder, :deserializer_cache
|
7
7
|
|
8
8
|
def self.default_deserializer_cache
|
9
9
|
@deserializer_cache ||= DeserializerCache.new
|
@@ -46,6 +46,14 @@ module MiniSql
|
|
46
46
|
@type_map ||= self.class.type_map(raw_connection)
|
47
47
|
end
|
48
48
|
|
49
|
+
def prepared(condition = true)
|
50
|
+
if condition
|
51
|
+
@prepared ||= PreparedConnection.new(self)
|
52
|
+
else
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
49
57
|
# Returns a flat array containing all results.
|
50
58
|
# Note, if selecting multiple columns array will be flattened
|
51
59
|
#
|
@@ -90,14 +98,16 @@ module MiniSql
|
|
90
98
|
def query(sql, *params)
|
91
99
|
result = run(sql, params)
|
92
100
|
result.type_map = type_map
|
93
|
-
|
101
|
+
deserializer_cache.materialize(result)
|
94
102
|
ensure
|
95
103
|
result.clear if result
|
96
104
|
end
|
97
105
|
|
98
106
|
def query_each(sql, *params)
|
99
107
|
raise StandardError, "Please supply a block when calling query_each" if !block_given?
|
100
|
-
|
108
|
+
if params && params.length > 0
|
109
|
+
sql = param_encoder.encode(sql, *params)
|
110
|
+
end
|
101
111
|
|
102
112
|
raw_connection.send_query(sql)
|
103
113
|
raw_connection.set_single_row_mode
|
@@ -111,7 +121,7 @@ module MiniSql
|
|
111
121
|
if result.ntuples == 0
|
112
122
|
# skip, this happens at the end when we get totals
|
113
123
|
else
|
114
|
-
materializer ||=
|
124
|
+
materializer ||= deserializer_cache.materializer(result)
|
115
125
|
result.type_map = type_map
|
116
126
|
i = 0
|
117
127
|
# technically we should only get 1 row here
|
@@ -128,7 +138,9 @@ module MiniSql
|
|
128
138
|
|
129
139
|
def query_each_hash(sql, *params)
|
130
140
|
raise StandardError, "Please supply a block when calling query_each_hash" if !block_given?
|
131
|
-
|
141
|
+
if params && params.length > 0
|
142
|
+
sql = param_encoder.encode(sql, *params)
|
143
|
+
end
|
132
144
|
|
133
145
|
raw_connection.send_query(sql)
|
134
146
|
raw_connection.set_single_row_mode
|
@@ -160,7 +172,7 @@ module MiniSql
|
|
160
172
|
def query_decorator(decorator, sql, *params)
|
161
173
|
result = run(sql, params)
|
162
174
|
result.type_map = type_map
|
163
|
-
|
175
|
+
deserializer_cache.materialize(result, decorator)
|
164
176
|
ensure
|
165
177
|
result.clear if result
|
166
178
|
end
|
@@ -191,7 +203,9 @@ module MiniSql
|
|
191
203
|
private
|
192
204
|
|
193
205
|
def run(sql, params)
|
194
|
-
|
206
|
+
if params && params.length > 0
|
207
|
+
sql = param_encoder.encode(sql, *params)
|
208
|
+
end
|
195
209
|
raw_connection.async_exec(sql)
|
196
210
|
end
|
197
211
|
|
@@ -12,13 +12,13 @@ module MiniSql
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def materializer(result)
|
15
|
-
key = result.fields
|
15
|
+
key = result.fields.join(',')
|
16
16
|
|
17
17
|
materializer = @cache.delete(key)
|
18
18
|
if materializer
|
19
19
|
@cache[key] = materializer
|
20
20
|
else
|
21
|
-
materializer = @cache[key] =
|
21
|
+
materializer = @cache[key] = new_row_materializer(result)
|
22
22
|
@cache.shift if @cache.length > @max_size
|
23
23
|
end
|
24
24
|
|
@@ -28,14 +28,14 @@ module MiniSql
|
|
28
28
|
def materialize(result, decorator_module = nil)
|
29
29
|
return [] if result.ntuples == 0
|
30
30
|
|
31
|
-
key = result.fields
|
31
|
+
key = result.fields.join(',')
|
32
32
|
|
33
33
|
# trivial fast LRU implementation
|
34
34
|
materializer = @cache.delete(key)
|
35
35
|
if materializer
|
36
36
|
@cache[key] = materializer
|
37
37
|
else
|
38
|
-
materializer = @cache[key] =
|
38
|
+
materializer = @cache[key] = new_row_materializer(result)
|
39
39
|
@cache.shift if @cache.length > @max_size
|
40
40
|
end
|
41
41
|
|
@@ -55,7 +55,7 @@ module MiniSql
|
|
55
55
|
|
56
56
|
private
|
57
57
|
|
58
|
-
def
|
58
|
+
def new_row_materializer(result)
|
59
59
|
fields = result.fields
|
60
60
|
|
61
61
|
i = 0
|
@@ -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,39 @@
|
|
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)
|
10
|
+
@unprepared = unprepared_connection
|
11
|
+
@raw_connection = unprepared_connection.raw_connection
|
12
|
+
@type_map = unprepared_connection.type_map
|
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
|
+
def deserializer_cache
|
28
|
+
@unprepared.deserializer_cache
|
29
|
+
end
|
30
|
+
|
31
|
+
private def run(sql, params)
|
32
|
+
prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
|
33
|
+
prepare_statement_key = @prepared_cache.prepare_statement(prepared_sql)
|
34
|
+
raw_connection.exec_prepared(prepare_statement_key, binds)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -89,7 +89,9 @@ module MiniSql
|
|
89
89
|
private
|
90
90
|
|
91
91
|
def run(sql, params)
|
92
|
-
|
92
|
+
if params && params.length > 0
|
93
|
+
sql = param_encoder.encode(sql, *params)
|
94
|
+
end
|
93
95
|
conn = raw_connection
|
94
96
|
conn.typemap = self.class.typemap
|
95
97
|
conn.execute(sql)
|
@@ -15,14 +15,14 @@ module MiniSql
|
|
15
15
|
|
16
16
|
return [] if result.ntuples == 0
|
17
17
|
|
18
|
-
key = result.fields
|
18
|
+
key = result.fields.join(',')
|
19
19
|
|
20
20
|
# trivial fast LRU implementation
|
21
21
|
materializer = @cache.delete(key)
|
22
22
|
if materializer
|
23
23
|
@cache[key] = materializer
|
24
24
|
else
|
25
|
-
materializer = @cache[key] =
|
25
|
+
materializer = @cache[key] = new_row_materializer(result)
|
26
26
|
@cache.shift if @cache.length > @max_size
|
27
27
|
end
|
28
28
|
|
@@ -44,7 +44,7 @@ module MiniSql
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
def
|
47
|
+
def new_row_materializer(result)
|
48
48
|
fields = result.fields
|
49
49
|
|
50
50
|
Class.new do
|
data/lib/mini_sql/result.rb
CHANGED
@@ -16,5 +16,15 @@ module MiniSql
|
|
16
16
|
def values
|
17
17
|
instance_variables.map { |f| instance_variable_get(f) }
|
18
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
|
19
29
|
end
|
20
30
|
end
|
data/lib/mini_sql/serializer.rb
CHANGED
@@ -1,26 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module MiniSql
|
4
|
-
|
4
|
+
class Serializer < Array
|
5
5
|
MAX_CACHE_SIZE = 500
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
18
|
|
19
|
-
|
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
|
20
35
|
end
|
21
36
|
|
22
|
-
def self.
|
23
|
-
wrapper = JSON.parse(json)
|
37
|
+
def self.materialize(wrapper)
|
24
38
|
if !wrapper["data"]
|
25
39
|
[]
|
26
40
|
else
|
@@ -11,6 +11,14 @@ module MiniSql
|
|
11
11
|
@deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
|
12
12
|
end
|
13
13
|
|
14
|
+
def prepared(condition = true)
|
15
|
+
if condition
|
16
|
+
@prepared ||= PreparedConnection.new(self)
|
17
|
+
else
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
14
22
|
def query_single(sql, *params)
|
15
23
|
# a bit lazy can be optimized
|
16
24
|
run(sql, *params).flatten!
|
@@ -63,10 +71,14 @@ module MiniSql
|
|
63
71
|
private
|
64
72
|
|
65
73
|
def run(sql, *params)
|
66
|
-
|
74
|
+
if params && params.length > 0
|
75
|
+
sql = param_encoder.encode(sql, *params)
|
76
|
+
end
|
67
77
|
if block_given?
|
68
78
|
stmt = SQLite3::Statement.new(raw_connection, sql)
|
69
|
-
yield stmt.execute
|
79
|
+
result = yield stmt.execute
|
80
|
+
stmt.close
|
81
|
+
result
|
70
82
|
else
|
71
83
|
raw_connection.execute(sql)
|
72
84
|
end
|