mini_sql 0.2.2-java

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.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ class Connection
5
+
6
+ def self.get(raw_connection, options = {})
7
+ if (defined? ::PG::Connection) && (PG::Connection === raw_connection)
8
+ Postgres::Connection.new(raw_connection, options)
9
+ elsif (defined? ::ArJdbc)
10
+ Postgres::Connection.new(raw_connection, options)
11
+ elsif (defined? ::SQLite3::Database) && (SQLite3::Database === raw_connection)
12
+ Sqlite::Connection.new(raw_connection, options)
13
+ else
14
+ raise ArgumentError, 'unknown connection type!'
15
+ end
16
+ end
17
+
18
+ # Returns a flat array containing all results.
19
+ # Note, if selecting multiple columns array will be flattened
20
+ #
21
+ # @param sql [String] the query to run
22
+ # @param params [Array or Hash], params to apply to query
23
+ # @return [Object] a flat array containing all results
24
+ def query_single(sql, *params)
25
+ raise NotImplementedError, "must be implemented by child connection"
26
+ end
27
+
28
+ def query(sql, *params)
29
+ raise NotImplementedError, "must be implemented by child connection"
30
+ end
31
+
32
+ def exec(sql, *params)
33
+ raise NotImplementedError, "must be implemented by child connection"
34
+ end
35
+
36
+ def query_hash(sql, *params)
37
+ raise NotImplementedError, "must be implemented by child connection"
38
+ end
39
+
40
+ def build(sql)
41
+ Builder.new(self, sql)
42
+ end
43
+
44
+ def escape_string(str)
45
+ raise NotImplementedError, "must be implemented by child connection"
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ module MiniSql
2
+ class DeserializerCache
3
+ # method takes a raw result and converts to proper objects
4
+ def materialize(result)
5
+ raise NotImplementedError, "must be implemented by child"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ class InlineParamEncoder
5
+ attr_reader :conn
6
+
7
+ def initialize(conn)
8
+ @conn = conn
9
+ end
10
+
11
+ def encode(sql, *params)
12
+ return sql unless params && params.length > 0
13
+
14
+ if Hash === (hash = params[0])
15
+ raise ArgumentError, "Only one hash param is allowed, multiple were sent" if params.length > 1
16
+ encode_hash(sql, hash)
17
+ else
18
+ encode_array(sql, params)
19
+ end
20
+ end
21
+
22
+ def encode_hash(sql, hash)
23
+ sql = sql.dup
24
+
25
+ hash.each do |k, v|
26
+ sql.gsub!(":#{k}") do
27
+ # ignore ::int and stuff like that
28
+ # $` is previous to match
29
+ if $` && $`[-1] != ":"
30
+ quote_val(v)
31
+ else
32
+ ":#{k}"
33
+ end
34
+ end
35
+ end
36
+ sql
37
+ end
38
+
39
+ def encode_array(sql, array)
40
+ i = -1
41
+ sql.gsub("?") do |p|
42
+ i += 1
43
+ quote_val(array[i])
44
+ end
45
+ end
46
+
47
+ def quoted_date(value)
48
+ value.utc.iso8601
49
+ end
50
+
51
+ def quote_val(value)
52
+ case value
53
+ when Array
54
+ value.map do |v|
55
+ quote_val(v)
56
+ 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
+ else raise TypeError, "can't quote #{value.class.name}"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,32 @@
1
+ module MiniSql
2
+ module Postgres
3
+ module Coders
4
+ class NumericCoder < PG::SimpleDecoder
5
+ def decode(string, tuple = nil, field = nil)
6
+ BigDecimal(string)
7
+ end
8
+ end
9
+
10
+ class IPAddrCoder < PG::SimpleDecoder
11
+ def decode(string, tuple = nil, field = nil)
12
+ IPAddr.new(string)
13
+ end
14
+ end
15
+
16
+ class TimestampUtc < PG::SimpleDecoder
17
+ # exact same implementation as Rails here
18
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
19
+
20
+ def decode(string, tuple = nil, field = nil)
21
+ if string =~ ISO_DATETIME
22
+ microsec = ($7.to_r * 1_000_000).to_i
23
+ Time.utc $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
24
+ else
25
+ STDERR.puts "unexpected date time format #{string}"
26
+ string
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Postgres
5
+ class Connection < MiniSql::Connection
6
+ attr_reader :raw_connection, :type_map, :param_encoder
7
+
8
+ def self.default_deserializer_cache
9
+ @deserializer_cache ||= DeserializerCache.new
10
+ end
11
+
12
+ def self.type_map(conn)
13
+ @type_map ||=
14
+ begin
15
+ map = PG::BasicTypeMapForResults.new(conn)
16
+ map.add_coder(MiniSql::Postgres::Coders::NumericCoder.new(name: "numeric", oid: 1700, format: 0))
17
+ map.add_coder(MiniSql::Postgres::Coders::IPAddrCoder.new(name: "inet", oid: 869, format: 0))
18
+ map.add_coder(MiniSql::Postgres::Coders::IPAddrCoder.new(name: "cidr", oid: 650, format: 0))
19
+ map.add_coder(PG::TextDecoder::String.new(name: "tsvector", oid: 3614, format: 0))
20
+
21
+ map.rm_coder(0, 1114)
22
+ if defined? PG::TextDecoder::TimestampUtc
23
+ # treat timestamp without zone as utc
24
+ # new to PG 1.1
25
+ map.add_coder(PG::TextDecoder::TimestampUtc.new(name: "timestamp", oid: 1114, format: 0))
26
+ else
27
+ map.add_coder(MiniSql::Postgres::Coders::TimestampUtc.new(name: "timestamp", oid: 1114, format: 0))
28
+ end
29
+ map
30
+ end
31
+ end
32
+
33
+ # Initialize a new MiniSql::Postgres::Connection object
34
+ #
35
+ # @param raw_connection [PG::Connection] an active connection to PG
36
+ # @param deserializer_cache [MiniSql::DeserializerCache] a cache of field names to deserializer, can be nil
37
+ # @param type_map [PG::TypeMap] a type mapper for all results returned, can be nil
38
+ def initialize(raw_connection, args = nil)
39
+ @raw_connection = raw_connection
40
+ @deserializer_cache = (args && args[:deserializer_cache]) || self.class.default_deserializer_cache
41
+ @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
42
+ end
43
+
44
+ def type_map
45
+ @type_map ||= self.class.type_map(raw_connection)
46
+ end
47
+
48
+ # Returns a flat array containing all results.
49
+ # Note, if selecting multiple columns array will be flattened
50
+ #
51
+ # @param sql [String] the query to run
52
+ # @param params [Array or Hash], params to apply to query
53
+ # @return [Object] a flat array containing all results
54
+ def query_single(sql, *params)
55
+ result = run(sql, params)
56
+ result.type_map = type_map
57
+ if result.nfields == 1
58
+ result.column_values(0)
59
+ else
60
+ tuples = result.ntuples
61
+ fields = result.nfields
62
+
63
+ array = []
64
+ f = 0
65
+ row = 0
66
+
67
+ while row < tuples
68
+ while f < fields
69
+ array << result.getvalue(row, f)
70
+ f += 1
71
+ end
72
+ f = 0
73
+ row += 1
74
+ end
75
+ array
76
+ end
77
+ ensure
78
+ result.clear if result
79
+ end
80
+
81
+ def query(sql, *params)
82
+ result = run(sql, params)
83
+ result.type_map = type_map
84
+ @deserializer_cache.materialize(result)
85
+ ensure
86
+ result.clear if result
87
+ end
88
+
89
+ def exec(sql, *params)
90
+ result = run(sql, params)
91
+ result.cmd_tuples
92
+ ensure
93
+ result.clear if result
94
+ end
95
+
96
+ def query_hash(sql, *params)
97
+ result = run(sql, params)
98
+ result.type_map = type_map
99
+ result.to_a
100
+ ensure
101
+ result.clear if result
102
+ end
103
+
104
+ def build(sql)
105
+ Builder.new(self, sql)
106
+ end
107
+
108
+ def escape_string(str)
109
+ raw_connection.escape_string(str)
110
+ end
111
+
112
+ private
113
+
114
+ def run(sql, params)
115
+ if params && params.length > 0
116
+ sql = param_encoder.encode(sql, *params)
117
+ end
118
+ raw_connection.async_exec(sql)
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,67 @@
1
+ module MiniSql
2
+ module Postgres
3
+ class DeserializerCache
4
+
5
+ DEFAULT_MAX_SIZE = 500
6
+
7
+ def initialize(max_size = nil)
8
+ @cache = {}
9
+ @max_size = max_size || DEFAULT_MAX_SIZE
10
+ end
11
+
12
+ def materialize(result)
13
+
14
+ return [] if result.ntuples == 0
15
+
16
+ key = result.fields
17
+
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
26
+
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
36
+
37
+ private
38
+
39
+ def new_row_matrializer(result)
40
+ fields = result.fields
41
+
42
+ Class.new do
43
+ attr_accessor(*fields)
44
+
45
+ # AM serializer support
46
+ alias :read_attribute_for_serialization :send
47
+
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
55
+
56
+ instance_eval <<~RUBY
57
+ def materialize(pg_result, index)
58
+ r = self.new
59
+ #{col=-1; fields.map{|f| "r.#{f} = pg_result.getvalue(index, #{col+=1})"}.join("; ")}
60
+ r
61
+ end
62
+ RUBY
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniSql
4
+ module Postgres
5
+ class Connection < MiniSql::Connection
6
+ class NumericCoder
7
+ def decode(string)
8
+ BigDecimal(string)
9
+ end
10
+ end
11
+
12
+ class IPAddrCoder
13
+ def decode(string)
14
+ IPAddr.new(string)
15
+ end
16
+ end
17
+
18
+ attr_reader :raw_connection, :type_map, :param_encoder
19
+
20
+ def self.default_deserializer_cache
21
+ @deserializer_cache ||= DeserializerCache.new
22
+ end
23
+
24
+ def self.typemap
25
+ @type_map ||= {
26
+ "numeric" => NumericCoder.new,
27
+ "inet" => IPAddrCoder.new,
28
+ "cidr" => IPAddrCoder.new
29
+ }
30
+ end
31
+
32
+ # Initialize a new MiniSql::Postgres::Connection object
33
+ #
34
+ # @param raw_connection [PG::Connection] an active connection to PG
35
+ # @param deserializer_cache [MiniSql::DeserializerCache] a cache of field names to deserializer, can be nil
36
+ # @param type_map [PG::TypeMap] a type mapper for all results returned, can be nil
37
+ def initialize(raw_connection, args = nil)
38
+ @raw_connection = raw_connection
39
+ @deserializer_cache = (args && args[:deserializer_cache]) || self.class.default_deserializer_cache
40
+ @param_encoder = (args && args[:param_encoder]) || InlineParamEncoder.new(self)
41
+ end
42
+
43
+ # Returns a flat array containing all results.
44
+ # Note, if selecting multiple columns array will be flattened
45
+ #
46
+ # @param sql [String] the query to run
47
+ # @param params [Array or Hash], params to apply to query
48
+ # @return [Object] a flat array containing all results
49
+ def query_single(sql, *params)
50
+ result = run(sql, params)
51
+ if result.length == 1
52
+ result.values[0]
53
+ else
54
+ result.values.each_with_object([]) { |value, array| array.concat value }
55
+ end
56
+ end
57
+
58
+ def query(sql, *params)
59
+ result = run(sql, params)
60
+ @deserializer_cache.materialize(result)
61
+ end
62
+
63
+ def exec(sql, *params)
64
+ result = run(sql, params)
65
+ if result.kind_of? Integer
66
+ result
67
+ else
68
+ result.length
69
+ end
70
+ end
71
+
72
+ def query_hash(sql, *params)
73
+ run(sql, params).to_a
74
+ end
75
+
76
+ def build(sql)
77
+ Builder.new(self, sql)
78
+ end
79
+
80
+ def escape_string(str)
81
+ raw_connection.escape_string(str)
82
+ end
83
+
84
+ private
85
+
86
+ def run(sql, params)
87
+ sql = param_encoder.encode(sql, *params) if params && params.length > 0
88
+ conn = raw_connection
89
+ conn.typemap = self.class.typemap
90
+ conn.execute(sql)
91
+ ensure
92
+ # Force unsetting of typemap since we don't want mixed AR usage to continue to use these extra converters.
93
+ conn.typemap = nil
94
+ end
95
+ end
96
+ end
97
+ end