mini_sql 0.3 → 1.1.2
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 +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 +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/decoratable.rb +22 -0
- data/lib/mini_sql/inline_param_encoder.rb +4 -5
- data/lib/mini_sql/mysql/connection.rb +8 -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 +10 -2
- data/lib/mini_sql/postgres/deserializer_cache.rb +11 -17
- 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 +11 -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 +5 -4
- metadata +42 -8
- data/.travis.yml +0 -28
@@ -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, @deserializer_cache)
|
17
|
+
else
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
14
22
|
def query_single(sql, *params)
|
15
23
|
run(sql, :array, params).to_a.flatten!
|
16
24
|
end
|
@@ -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
|
@@ -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, @deserializer_cache)
|
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
|
#
|
@@ -84,7 +92,7 @@ module MiniSql
|
|
84
92
|
result.type_map = type_map
|
85
93
|
result.values
|
86
94
|
ensure
|
87
|
-
|
95
|
+
result.clear if result
|
88
96
|
end
|
89
97
|
|
90
98
|
def query(sql, *params)
|
@@ -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,18 +28,20 @@ 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
|
|
42
|
-
|
42
|
+
if decorator_module
|
43
|
+
materializer = materializer.decorated(decorator_module)
|
44
|
+
end
|
43
45
|
|
44
46
|
i = 0
|
45
47
|
r = []
|
@@ -53,7 +55,7 @@ module MiniSql
|
|
53
55
|
|
54
56
|
private
|
55
57
|
|
56
|
-
def
|
58
|
+
def new_row_materializer(result)
|
57
59
|
fields = result.fields
|
58
60
|
|
59
61
|
i = 0
|
@@ -66,18 +68,10 @@ module MiniSql
|
|
66
68
|
end
|
67
69
|
|
68
70
|
Class.new do
|
69
|
-
|
70
|
-
|
71
|
-
# AM serializer support
|
72
|
-
alias :read_attribute_for_serialization :send
|
71
|
+
extend MiniSql::Decoratable
|
72
|
+
include MiniSql::Result
|
73
73
|
|
74
|
-
|
75
|
-
r = {}
|
76
|
-
instance_variables.each do |f|
|
77
|
-
r[f.to_s.sub('@', '').to_sym] = instance_variable_get(f)
|
78
|
-
end
|
79
|
-
r
|
80
|
-
end
|
74
|
+
attr_accessor(*fields)
|
81
75
|
|
82
76
|
instance_eval <<~RUBY
|
83
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)
|
@@ -0,0 +1,30 @@
|
|
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
|
+
|
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
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
class Serializer < Array
|
5
|
+
MAX_CACHE_SIZE = 500
|
6
|
+
|
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
|
+
|
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
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.materialize(wrapper)
|
38
|
+
if !wrapper["data"]
|
39
|
+
[]
|
40
|
+
else
|
41
|
+
materializer = cached_materializer(wrapper['fields'], wrapper['decorator'])
|
42
|
+
wrapper["data"].map do |row|
|
43
|
+
materializer.materialize(row)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.cached_materializer(fields, decorator_module = nil)
|
49
|
+
@cache ||= {}
|
50
|
+
key = fields
|
51
|
+
m = @cache.delete(key)
|
52
|
+
if m
|
53
|
+
@cache[key] = m
|
54
|
+
else
|
55
|
+
m = @cache[key] = materializer(fields)
|
56
|
+
@cache.shift if @cache.length > MAX_CACHE_SIZE
|
57
|
+
end
|
58
|
+
|
59
|
+
if decorator_module && decorator_module.length > 0
|
60
|
+
decorator = Kernel.const_get(decorator_module)
|
61
|
+
m = m.decorated(decorator)
|
62
|
+
end
|
63
|
+
|
64
|
+
m
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.materializer(fields)
|
68
|
+
Class.new do
|
69
|
+
extend MiniSql::Decoratable
|
70
|
+
include MiniSql::Result
|
71
|
+
|
72
|
+
attr_accessor(*fields)
|
73
|
+
|
74
|
+
instance_eval <<~RUBY
|
75
|
+
def materialize(values)
|
76
|
+
r = self.new
|
77
|
+
#{col = -1; fields.map { |f| "r.#{f} = values[#{col += 1}]" }.join("; ")}
|
78
|
+
r
|
79
|
+
end
|
80
|
+
RUBY
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|