mini_sql 0.2.5 → 1.1.1
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.yml +5 -2
- data/CHANGELOG +22 -0
- data/README.md +66 -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/topic_perf.rb +21 -327
- data/bench/topic_wide_perf.rb +92 -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 +66 -23
- data/lib/mini_sql/connection.rb +14 -2
- data/lib/mini_sql/decoratable.rb +22 -0
- data/lib/mini_sql/inline_param_encoder.rb +4 -5
- data/lib/mini_sql/mysql/connection.rb +6 -0
- data/lib/mini_sql/mysql/deserializer_cache.rb +9 -15
- 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/connection.rb +75 -3
- data/lib/mini_sql/postgres/deserializer_cache.rb +32 -15
- 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 +3 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +10 -14
- data/lib/mini_sql/result.rb +30 -0
- data/lib/mini_sql/serializer.rb +84 -0
- data/lib/mini_sql/sqlite/connection.rb +9 -1
- data/lib/mini_sql/sqlite/deserializer_cache.rb +9 -15
- 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 +6 -5
- metadata +49 -15
- data/.travis.yml +0 -28
@@ -9,6 +9,12 @@ module MiniSql
|
|
9
9
|
@raw_connection = raw_connection
|
10
10
|
@param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
|
11
11
|
@deserializer_cache = (args && args[:deserializer_cache]) || DeserializerCache.new
|
12
|
+
|
13
|
+
@prepared = PreparedConnection.new(self, @deserializer_cache)
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepared(condition = true)
|
17
|
+
condition ? @prepared : self
|
12
18
|
end
|
13
19
|
|
14
20
|
def query_single(sql, *params)
|
@@ -12,18 +12,20 @@ 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
|
|
26
|
-
|
26
|
+
if decorator_module
|
27
|
+
materializer = materializer.decorated(decorator_module)
|
28
|
+
end
|
27
29
|
|
28
30
|
result.map do |data|
|
29
31
|
materializer.materialize(data)
|
@@ -32,22 +34,14 @@ module MiniSql
|
|
32
34
|
|
33
35
|
private
|
34
36
|
|
35
|
-
def
|
37
|
+
def new_row_materializer(result)
|
36
38
|
fields = result.fields
|
37
39
|
|
38
40
|
Class.new do
|
39
|
-
|
40
|
-
|
41
|
-
# AM serializer support
|
42
|
-
alias :read_attribute_for_serialization :send
|
41
|
+
extend MiniSql::Decoratable
|
42
|
+
include MiniSql::Result
|
43
43
|
|
44
|
-
|
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
|
44
|
+
attr_accessor(*fields)
|
51
45
|
|
52
46
|
instance_eval <<~RUBY
|
53
47
|
def materialize(data)
|
@@ -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,44 @@
|
|
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, deserializer_cache)
|
10
|
+
@unprepared = unprepared_connection
|
11
|
+
@raw_connection = unprepared_connection.raw_connection
|
12
|
+
@deserializer_cache = deserializer_cache
|
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
|
+
private def run(sql, as, params)
|
28
|
+
prepared_sql, binds, _bind_names = @param_binder.bind(sql, *params)
|
29
|
+
statement = @prepared_cache.prepare_statement(prepared_sql)
|
30
|
+
statement.execute(
|
31
|
+
*binds,
|
32
|
+
as: as,
|
33
|
+
database_timezone: :utc,
|
34
|
+
application_timezone: :utc,
|
35
|
+
cast_booleans: true,
|
36
|
+
cast: true,
|
37
|
+
cache_rows: true,
|
38
|
+
symbolize_keys: false
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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
|
7
7
|
|
8
8
|
def self.default_deserializer_cache
|
9
9
|
@deserializer_cache ||= DeserializerCache.new
|
@@ -40,12 +40,18 @@ module MiniSql
|
|
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
42
|
@type_map = args && args[:type_map]
|
43
|
+
|
44
|
+
@prepared = PreparedConnection.new(self, @deserializer_cache)
|
43
45
|
end
|
44
46
|
|
45
47
|
def type_map
|
46
48
|
@type_map ||= self.class.type_map(raw_connection)
|
47
49
|
end
|
48
50
|
|
51
|
+
def prepared(condition = true)
|
52
|
+
condition ? @prepared : self
|
53
|
+
end
|
54
|
+
|
49
55
|
# Returns a flat array containing all results.
|
50
56
|
# Note, if selecting multiple columns array will be flattened
|
51
57
|
#
|
@@ -83,8 +89,8 @@ module MiniSql
|
|
83
89
|
result = run(sql, params)
|
84
90
|
result.type_map = type_map
|
85
91
|
result.values
|
86
|
-
|
87
|
-
|
92
|
+
ensure
|
93
|
+
result.clear if result
|
88
94
|
end
|
89
95
|
|
90
96
|
def query(sql, *params)
|
@@ -95,6 +101,72 @@ module MiniSql
|
|
95
101
|
result.clear if result
|
96
102
|
end
|
97
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
|
+
|
98
170
|
def query_decorator(decorator, sql, *params)
|
99
171
|
result = run(sql, params)
|
100
172
|
result.type_map = type_map
|
@@ -11,21 +11,37 @@ module MiniSql
|
|
11
11
|
@max_size = max_size || DEFAULT_MAX_SIZE
|
12
12
|
end
|
13
13
|
|
14
|
+
def materializer(result)
|
15
|
+
key = result.fields.join(',')
|
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
|
+
|
14
28
|
def materialize(result, decorator_module = nil)
|
15
29
|
return [] if result.ntuples == 0
|
16
30
|
|
17
|
-
key = result.fields
|
31
|
+
key = result.fields.join(',')
|
18
32
|
|
19
33
|
# trivial fast LRU implementation
|
20
34
|
materializer = @cache.delete(key)
|
21
35
|
if materializer
|
22
36
|
@cache[key] = materializer
|
23
37
|
else
|
24
|
-
materializer = @cache[key] =
|
38
|
+
materializer = @cache[key] = new_row_materializer(result)
|
25
39
|
@cache.shift if @cache.length > @max_size
|
26
40
|
end
|
27
41
|
|
28
|
-
|
42
|
+
if decorator_module
|
43
|
+
materializer = materializer.decorated(decorator_module)
|
44
|
+
end
|
29
45
|
|
30
46
|
i = 0
|
31
47
|
r = []
|
@@ -39,22 +55,23 @@ module MiniSql
|
|
39
55
|
|
40
56
|
private
|
41
57
|
|
42
|
-
def
|
58
|
+
def new_row_materializer(result)
|
43
59
|
fields = result.fields
|
44
60
|
|
45
|
-
|
46
|
-
|
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
|
47
69
|
|
48
|
-
|
49
|
-
|
70
|
+
Class.new do
|
71
|
+
extend MiniSql::Decoratable
|
72
|
+
include MiniSql::Result
|
50
73
|
|
51
|
-
|
52
|
-
r = {}
|
53
|
-
instance_variables.each do |f|
|
54
|
-
r[f.to_s.sub('@', '').to_sym] = instance_variable_get(f)
|
55
|
-
end
|
56
|
-
r
|
57
|
-
end
|
74
|
+
attr_accessor(*fields)
|
58
75
|
|
59
76
|
instance_eval <<~RUBY
|
60
77
|
def materialize(pg_result, index)
|
@@ -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
|
@@ -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,19 +15,23 @@ 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
|
|
29
29
|
materializer.include(decorator_module) if decorator_module
|
30
30
|
|
31
|
+
if decorator_module
|
32
|
+
materializer = materializer.decorated(decorator_module)
|
33
|
+
end
|
34
|
+
|
31
35
|
i = 0
|
32
36
|
r = []
|
33
37
|
# quicker loop
|
@@ -40,22 +44,14 @@ module MiniSql
|
|
40
44
|
|
41
45
|
private
|
42
46
|
|
43
|
-
def
|
47
|
+
def new_row_materializer(result)
|
44
48
|
fields = result.fields
|
45
49
|
|
46
50
|
Class.new do
|
47
|
-
|
51
|
+
extend MiniSql::Decoratable
|
52
|
+
include MiniSql::Result
|
48
53
|
|
49
|
-
|
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
|
54
|
+
attr_accessor(*fields)
|
59
55
|
|
60
56
|
instance_eval <<~RUBY
|
61
57
|
def materialize(pg_result, index)
|