mini_sql 1.0.1 → 1.1.0
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 +4 -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 +61 -30
- data/lib/mini_sql/inline_param_encoder.rb +4 -3
- data/lib/mini_sql/mysql/connection.rb +6 -0
- 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 +44 -0
- data/lib/mini_sql/postgres/connection.rb +6 -0
- 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 +36 -0
- 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 +9 -1
- 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 +40 -0
- data/lib/mini_sql/version.rb +1 -1
- metadata +19 -2
@@ -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
|
@@ -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
|
#
|
@@ -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,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
|
@@ -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
|
@@ -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)
|
@@ -68,7 +74,9 @@ module MiniSql
|
|
68
74
|
end
|
69
75
|
if block_given?
|
70
76
|
stmt = SQLite3::Statement.new(raw_connection, sql)
|
71
|
-
yield stmt.execute
|
77
|
+
result = yield stmt.execute
|
78
|
+
stmt.close
|
79
|
+
result
|
72
80
|
else
|
73
81
|
raw_connection.execute(sql)
|
74
82
|
end
|
@@ -13,14 +13,14 @@ module MiniSql
|
|
13
13
|
|
14
14
|
def materialize(result, decorator_module = nil)
|
15
15
|
|
16
|
-
key = result.columns
|
16
|
+
key = result.columns.join(',')
|
17
17
|
|
18
18
|
# trivial fast LRU implementation
|
19
19
|
materializer = @cache.delete(key)
|
20
20
|
if materializer
|
21
21
|
@cache[key] = materializer
|
22
22
|
else
|
23
|
-
materializer = @cache[key] =
|
23
|
+
materializer = @cache[key] = new_row_materializer(result)
|
24
24
|
@cache.shift if @cache.length > @max_size
|
25
25
|
end
|
26
26
|
|
@@ -41,7 +41,7 @@ module MiniSql
|
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
def
|
44
|
+
def new_row_materializer(result)
|
45
45
|
fields = result.columns
|
46
46
|
|
47
47
|
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 Sqlite
|
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,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Sqlite
|
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, params)
|
28
|
+
prepared_sql, binds, _bind_names = @param_binder.bind(sql, params)
|
29
|
+
statement = @prepared_cache.prepare_statement(prepared_sql)
|
30
|
+
statement.bind_params(binds)
|
31
|
+
if block_given?
|
32
|
+
yield statement.execute
|
33
|
+
else
|
34
|
+
statement.execute.to_a
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|