mini_sql 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/CHANGELOG +4 -0
  4. data/README.md +36 -0
  5. data/bench/builder_perf.rb +138 -0
  6. data/bench/decorator_perf.rb +143 -0
  7. data/bench/mini_sql_methods_perf.rb +80 -0
  8. data/bench/prepared_perf.rb +59 -0
  9. data/bench/shared/generate_data.rb +133 -0
  10. data/bench/topic_perf.rb +21 -327
  11. data/bench/topic_wide_perf.rb +92 -0
  12. data/lib/mini_sql.rb +17 -8
  13. data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
  14. data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
  15. data/lib/mini_sql/builder.rb +61 -30
  16. data/lib/mini_sql/inline_param_encoder.rb +4 -3
  17. data/lib/mini_sql/mysql/connection.rb +6 -0
  18. data/lib/mini_sql/mysql/deserializer_cache.rb +3 -3
  19. data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
  20. data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
  21. data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
  22. data/lib/mini_sql/postgres/connection.rb +6 -0
  23. data/lib/mini_sql/postgres/deserializer_cache.rb +5 -5
  24. data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
  25. data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
  26. data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
  27. data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +3 -3
  28. data/lib/mini_sql/result.rb +10 -0
  29. data/lib/mini_sql/serializer.rb +29 -15
  30. data/lib/mini_sql/sqlite/connection.rb +9 -1
  31. data/lib/mini_sql/sqlite/deserializer_cache.rb +3 -3
  32. data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
  33. data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
  34. data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
  35. data/lib/mini_sql/version.rb +1 -1
  36. metadata +19 -2
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_binds"
4
+
5
+ module MiniSql
6
+ module Mysql
7
+ class PreparedBinds < ::MiniSql::Abstract::PreparedBinds
8
+
9
+ def bind_output(i)
10
+ '?'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -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] = new_row_matrializer(result)
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] = new_row_matrializer(result)
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 new_row_matrializer(result)
58
+ def new_row_materializer(result)
59
59
  fields = result.fields
60
60
 
61
61
  i = 0
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_binds"
4
+
5
+ module MiniSql
6
+ module Postgres
7
+ class PreparedBinds < ::MiniSql::Abstract::PreparedBinds
8
+
9
+ def bind_output(i)
10
+ "$#{i}"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -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] = new_row_matrializer(result)
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 new_row_matrializer(result)
47
+ def new_row_materializer(result)
48
48
  fields = result.fields
49
49
 
50
50
  Class.new do
@@ -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
@@ -1,26 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniSql
4
- module Serializer
4
+ class Serializer < Array
5
5
  MAX_CACHE_SIZE = 500
6
6
 
7
- def self.to_json(result)
8
- wrapper =
9
- if result.length == 0
10
- {}
11
- else
12
- {
13
- "decorator" => result[0].class.decorator.to_s,
14
- "fields" => result[0].to_h.keys,
15
- "data" => result.map(&:values),
16
- }
17
- end
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
- JSON.generate(wrapper)
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.from_json(json)
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] = new_row_matrializer(result)
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 new_row_matrializer(result)
44
+ def new_row_materializer(result)
45
45
  fields = result.columns
46
46
 
47
47
  Class.new do
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_sql/abstract/prepared_binds"
4
+
5
+ module MiniSql
6
+ module Sqlite
7
+ class PreparedBinds < ::MiniSql::Abstract::PreparedBinds
8
+
9
+ def bind_output(i)
10
+ '?'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -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