mini_sql 0.2.5 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|