mini_sql 0.2.3 → 1.0.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.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "mini_sql"
@@ -8,6 +8,9 @@ require_relative "mini_sql/connection"
8
8
  require_relative "mini_sql/deserializer_cache"
9
9
  require_relative "mini_sql/builder"
10
10
  require_relative "mini_sql/inline_param_encoder"
11
+ require_relative "mini_sql/decoratable"
12
+ require_relative "mini_sql/serializer"
13
+ require_relative "mini_sql/result"
11
14
 
12
15
  module MiniSql
13
16
  if RUBY_ENGINE == 'jruby'
@@ -66,5 +66,14 @@ class MiniSql::Builder
66
66
  RUBY
67
67
  end
68
68
 
69
- end
69
+ def query_decorator(decorator, hash_args = nil)
70
+ hash_args = @args.merge(hash_args) if hash_args && @args
71
+ hash_args ||= @args
72
+ if hash_args
73
+ @connection.query_decorator(decorator, to_sql, hash_args)
74
+ else
75
+ @connection.query_decorator(decorator, to_sql)
76
+ end
77
+ end
70
78
 
79
+ end
@@ -4,7 +4,7 @@ module MiniSql
4
4
  class Connection
5
5
 
6
6
  def self.get(raw_connection, options = {})
7
- if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
7
+ if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
8
8
  Postgres::Connection.new(raw_connection, options)
9
9
  elsif (defined? ::ArJdbc)
10
10
  Postgres::Connection.new(raw_connection, options)
@@ -31,11 +31,23 @@ module MiniSql
31
31
  raise NotImplementedError, "must be implemented by child connection"
32
32
  end
33
33
 
34
- def exec(sql, *params)
34
+ def query_hash(sql, *params)
35
35
  raise NotImplementedError, "must be implemented by child connection"
36
36
  end
37
37
 
38
- def query_hash(sql, *params)
38
+ def query_decorator(sql, *params)
39
+ raise NotImplementedError, "must be implemented by child connection"
40
+ end
41
+
42
+ def query_each(sql, *params)
43
+ raise NotImplementedError, "must be implemented by child connection"
44
+ end
45
+
46
+ def query_each_hash(sql, *params)
47
+ raise NotImplementedError, "must be implemented by child connection"
48
+ end
49
+
50
+ def exec(sql, *params)
39
51
  raise NotImplementedError, "must be implemented by child connection"
40
52
  end
41
53
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Decoratable
5
+ def decorated(mod)
6
+ @decoratorated_classes ||= {}
7
+ @decoratorated_classes[mod] ||=
8
+ Class.new(self) do
9
+ include(mod)
10
+ instance_eval <<~RUBY
11
+ def decorator
12
+ #{mod}
13
+ end
14
+ RUBY
15
+ end
16
+ end
17
+
18
+ def decorator
19
+ nil
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  class DeserializerCache
3
5
  # method takes a raw result and converts to proper objects
@@ -9,8 +9,6 @@ module MiniSql
9
9
  end
10
10
 
11
11
  def encode(sql, *params)
12
- return sql unless params && params.length > 0
13
-
14
12
  if Hash === (hash = params[0])
15
13
  raise ArgumentError, "Only one hash param is allowed, multiple were sent" if params.length > 1
16
14
  encode_hash(sql, hash)
@@ -50,19 +48,19 @@ module MiniSql
50
48
 
51
49
  def quote_val(value)
52
50
  case value
51
+ when String then "'#{conn.escape_string(value.to_s)}'"
52
+ when Numeric then value.to_s
53
+ when BigDecimal then value.to_s("F")
54
+ when Date, Time then "'#{quoted_date(value)}'"
55
+ when Symbol then "'#{conn.escape_string(value.to_s)}'"
56
+ when true then "true"
57
+ when false then "false"
58
+ when nil then "NULL"
59
+ when [] then "NULL"
53
60
  when Array
54
61
  value.map do |v|
55
62
  quote_val(v)
56
63
  end.join(', ')
57
- when String
58
- "'#{conn.escape_string(value.to_s)}'"
59
- when true then "true"
60
- when false then "false"
61
- when nil then "NULL"
62
- when BigDecimal then value.to_s("F")
63
- when Numeric then value.to_s
64
- when Date, Time then "'#{quoted_date(value)}'"
65
- when Symbol then "'#{escape_string(value.to_s)}'"
66
64
  else raise TypeError, "can't quote #{value.class.name}"
67
65
  end
68
66
  end
@@ -20,6 +20,10 @@ module MiniSql
20
20
  result.to_a
21
21
  end
22
22
 
23
+ def query_array(sql, *params)
24
+ run(sql, :array, params).to_a
25
+ end
26
+
23
27
  def exec(sql, *params)
24
28
  run(sql, :array, params)
25
29
  raw_connection.affected_rows
@@ -30,6 +34,11 @@ module MiniSql
30
34
  @deserializer_cache.materialize(result)
31
35
  end
32
36
 
37
+ def query_decorator(decorator, sql, *params)
38
+ result = run(sql, :array, params)
39
+ @deserializer_cache.materialize(result, decorator)
40
+ end
41
+
33
42
  def escape_string(str)
34
43
  raw_connection.escape(str)
35
44
  end
@@ -45,9 +54,9 @@ module MiniSql
45
54
  sql = param_encoder.encode(sql, *params)
46
55
  end
47
56
  raw_connection.query(
48
- sql,
49
- as: as,
50
- database_timezone: :utc,
57
+ sql,
58
+ as: as,
59
+ database_timezone: :utc,
51
60
  application_timezone: :utc,
52
61
  cast_booleans: true,
53
62
  cast: true,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Mysql
3
5
  class DeserializerCache
@@ -9,7 +11,7 @@ module MiniSql
9
11
  @max_size = max_size || DEFAULT_MAX_SIZE
10
12
  end
11
13
 
12
- def materialize(result)
14
+ def materialize(result, decorator_module = nil)
13
15
  key = result.fields
14
16
 
15
17
  # trivial fast LRU implementation
@@ -21,6 +23,10 @@ module MiniSql
21
23
  @cache.shift if @cache.length > @max_size
22
24
  end
23
25
 
26
+ if decorator_module
27
+ materializer = materializer.decorated(decorator_module)
28
+ end
29
+
24
30
  result.map do |data|
25
31
  materializer.materialize(data)
26
32
  end
@@ -32,23 +38,15 @@ module MiniSql
32
38
  fields = result.fields
33
39
 
34
40
  Class.new do
35
- attr_accessor(*fields)
41
+ extend MiniSql::Decoratable
42
+ include MiniSql::Result
36
43
 
37
- # AM serializer support
38
- alias :read_attribute_for_serialization :send
39
-
40
- def to_h
41
- r = {}
42
- instance_variables.each do |f|
43
- r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
44
- end
45
- r
46
- end
44
+ attr_accessor(*fields)
47
45
 
48
46
  instance_eval <<~RUBY
49
47
  def materialize(data)
50
48
  r = self.new
51
- #{col=-1; fields.map{|f| "r.#{f} = data[#{col+=1}]"}.join("; ")}
49
+ #{col = -1; fields.map { |f| "r.#{f} = data[#{col += 1}]" }.join("; ")}
52
50
  r
53
51
  end
54
52
  RUBY
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Postgres
3
5
  module Coders
@@ -39,6 +39,7 @@ module MiniSql
39
39
  @raw_connection = raw_connection
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
+ @type_map = args && args[:type_map]
42
43
  end
43
44
 
44
45
  def type_map
@@ -78,6 +79,14 @@ module MiniSql
78
79
  result.clear if result
79
80
  end
80
81
 
82
+ def query_array(sql, *params)
83
+ result = run(sql, params)
84
+ result.type_map = type_map
85
+ result.values
86
+ ensure
87
+ result.clear if result
88
+ end
89
+
81
90
  def query(sql, *params)
82
91
  result = run(sql, params)
83
92
  result.type_map = type_map
@@ -86,6 +95,80 @@ module MiniSql
86
95
  result.clear if result
87
96
  end
88
97
 
98
+ def query_each(sql, *params)
99
+ raise StandardError, "Please supply a block when calling query_each" if !block_given?
100
+ if params && params.length > 0
101
+ sql = param_encoder.encode(sql, *params)
102
+ end
103
+
104
+ raw_connection.send_query(sql)
105
+ raw_connection.set_single_row_mode
106
+
107
+ loop do
108
+ result = raw_connection.get_result
109
+ break if !result
110
+
111
+ result.check
112
+
113
+ if result.ntuples == 0
114
+ # skip, this happens at the end when we get totals
115
+ else
116
+ materializer ||= @deserializer_cache.materializer(result)
117
+ result.type_map = type_map
118
+ i = 0
119
+ # technically we should only get 1 row here
120
+ # but protect against future batching changes
121
+ while i < result.ntuples
122
+ yield materializer.materialize(result, i)
123
+ i += 1
124
+ end
125
+ end
126
+
127
+ result.clear
128
+ end
129
+ end
130
+
131
+ def query_each_hash(sql, *params)
132
+ raise StandardError, "Please supply a block when calling query_each_hash" if !block_given?
133
+ if params && params.length > 0
134
+ sql = param_encoder.encode(sql, *params)
135
+ end
136
+
137
+ raw_connection.send_query(sql)
138
+ raw_connection.set_single_row_mode
139
+
140
+ loop do
141
+ result = raw_connection.get_result
142
+ break if !result
143
+
144
+ result.check
145
+
146
+ if result.ntuples == 0
147
+ # skip, this happens at the end when we get totals
148
+ else
149
+ result.type_map = type_map
150
+ i = 0
151
+
152
+ # technically we should only get 1 row here
153
+ # but protect against future batching changes
154
+ while i < result.ntuples
155
+ yield result[i]
156
+ i += 1
157
+ end
158
+ end
159
+
160
+ result.clear
161
+ end
162
+ end
163
+
164
+ def query_decorator(decorator, sql, *params)
165
+ result = run(sql, params)
166
+ result.type_map = type_map
167
+ @deserializer_cache.materialize(result, decorator)
168
+ ensure
169
+ result.clear if result
170
+ end
171
+
89
172
  def exec(sql, *params)
90
173
  result = run(sql, params)
91
174
  result.cmd_tuples
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Postgres
3
5
  class DeserializerCache
@@ -9,8 +11,21 @@ module MiniSql
9
11
  @max_size = max_size || DEFAULT_MAX_SIZE
10
12
  end
11
13
 
12
- def materialize(result)
14
+ def materializer(result)
15
+ key = result.fields
16
+
17
+ materializer = @cache.delete(key)
18
+ if materializer
19
+ @cache[key] = materializer
20
+ else
21
+ materializer = @cache[key] = new_row_matrializer(result)
22
+ @cache.shift if @cache.length > @max_size
23
+ end
24
+
25
+ materializer
26
+ end
13
27
 
28
+ def materialize(result, decorator_module = nil)
14
29
  return [] if result.ntuples == 0
15
30
 
16
31
  key = result.fields
@@ -24,6 +39,10 @@ module MiniSql
24
39
  @cache.shift if @cache.length > @max_size
25
40
  end
26
41
 
42
+ if decorator_module
43
+ materializer = materializer.decorated(decorator_module)
44
+ end
45
+
27
46
  i = 0
28
47
  r = []
29
48
  # quicker loop
@@ -39,24 +58,25 @@ module MiniSql
39
58
  def new_row_matrializer(result)
40
59
  fields = result.fields
41
60
 
42
- Class.new do
43
- attr_accessor(*fields)
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
44
69
 
45
- # AM serializer support
46
- alias :read_attribute_for_serialization :send
70
+ Class.new do
71
+ extend MiniSql::Decoratable
72
+ include MiniSql::Result
47
73
 
48
- def to_h
49
- r = {}
50
- instance_variables.each do |f|
51
- r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
52
- end
53
- r
54
- end
74
+ attr_accessor(*fields)
55
75
 
56
76
  instance_eval <<~RUBY
57
77
  def materialize(pg_result, index)
58
78
  r = self.new
59
- #{col=-1; fields.map{|f| "r.#{f} = pg_result.getvalue(index, #{col+=1})"}.join("; ")}
79
+ #{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
60
80
  r
61
81
  end
62
82
  RUBY
@@ -60,6 +60,11 @@ module MiniSql
60
60
  @deserializer_cache.materialize(result)
61
61
  end
62
62
 
63
+ def query_decorator(decorator, sql, *params)
64
+ result = run(sql, params)
65
+ @deserializer_cache.materialize(result, decorator)
66
+ end
67
+
63
68
  def exec(sql, *params)
64
69
  result = run(sql, params)
65
70
  if result.kind_of? Integer
@@ -84,7 +89,9 @@ module MiniSql
84
89
  private
85
90
 
86
91
  def run(sql, params)
87
- sql = param_encoder.encode(sql, *params) if params && params.length > 0
92
+ if params && params.length > 0
93
+ sql = param_encoder.encode(sql, *params)
94
+ end
88
95
  conn = raw_connection
89
96
  conn.typemap = self.class.typemap
90
97
  conn.execute(sql)
@@ -1,67 +1,67 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniSql
2
4
  module Postgres
3
5
  class DeserializerCache
4
6
 
5
- DEFAULT_MAX_SIZE = 500
7
+ DEFAULT_MAX_SIZE = 500
6
8
 
7
- def initialize(max_size = nil)
8
- @cache = {}
9
- @max_size = max_size || DEFAULT_MAX_SIZE
10
- end
9
+ def initialize(max_size = nil)
10
+ @cache = {}
11
+ @max_size = max_size || DEFAULT_MAX_SIZE
12
+ end
11
13
 
12
- def materialize(result)
14
+ def materialize(result, decorator_module = nil)
13
15
 
14
- return [] if result.ntuples == 0
16
+ return [] if result.ntuples == 0
15
17
 
16
- key = result.fields
18
+ key = result.fields
17
19
 
18
- # trivial fast LRU implementation
19
- materializer = @cache.delete(key)
20
- if materializer
21
- @cache[key] = materializer
22
- else
23
- materializer = @cache[key] = new_row_matrializer(result)
24
- @cache.shift if @cache.length > @max_size
25
- end
20
+ # trivial fast LRU implementation
21
+ materializer = @cache.delete(key)
22
+ if materializer
23
+ @cache[key] = materializer
24
+ else
25
+ materializer = @cache[key] = new_row_matrializer(result)
26
+ @cache.shift if @cache.length > @max_size
27
+ end
26
28
 
27
- i = 0
28
- r = []
29
- # quicker loop
30
- while i < result.ntuples
31
- r << materializer.materialize(result, i)
32
- i += 1
33
- end
34
- r
35
- end
29
+ materializer.include(decorator_module) if decorator_module
36
30
 
37
- private
31
+ if decorator_module
32
+ materializer = materializer.decorated(decorator_module)
33
+ end
34
+
35
+ i = 0
36
+ r = []
37
+ # quicker loop
38
+ while i < result.ntuples
39
+ r << materializer.materialize(result, i)
40
+ i += 1
41
+ end
42
+ r
43
+ end
38
44
 
39
- def new_row_matrializer(result)
40
- fields = result.fields
45
+ private
41
46
 
42
- Class.new do
43
- attr_accessor(*fields)
47
+ def new_row_matrializer(result)
48
+ fields = result.fields
44
49
 
45
- # AM serializer support
46
- alias :read_attribute_for_serialization :send
50
+ Class.new do
51
+ extend MiniSql::Decoratable
52
+ include MiniSql::Result
47
53
 
48
- def to_h
49
- r = {}
50
- instance_variables.each do |f|
51
- r[f.to_s.sub('@','').to_sym] = instance_variable_get(f)
52
- end
53
- r
54
- end
54
+ attr_accessor(*fields)
55
55
 
56
- instance_eval <<~RUBY
56
+ instance_eval <<~RUBY
57
57
  def materialize(pg_result, index)
58
58
  r = self.new
59
- #{col=-1; fields.map{|f| "r.#{f} = pg_result.getvalue(index, #{col+=1})"}.join("; ")}
59
+ #{col = -1; fields.map { |f| "r.#{f} = pg_result.getvalue(index, #{col += 1})" }.join("; ")}
60
60
  r
61
61
  end
62
- RUBY
62
+ RUBY
63
+ end
63
64
  end
64
65
  end
65
66
  end
66
67
  end
67
- end