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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +21 -0
- data/CHANGELOG +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +120 -0
- data/Rakefile +16 -0
- data/bench/timestamp_perf.rb +286 -0
- data/bench/topic_perf.rb +347 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/mini_sql.rb +30 -0
- data/lib/mini_sql/builder.rb +71 -0
- data/lib/mini_sql/connection.rb +49 -0
- data/lib/mini_sql/deserializer_cache.rb +8 -0
- data/lib/mini_sql/inline_param_encoder.rb +70 -0
- data/lib/mini_sql/postgres/coders.rb +32 -0
- data/lib/mini_sql/postgres/connection.rb +123 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +67 -0
- data/lib/mini_sql/postgres_jdbc/connection.rb +97 -0
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +67 -0
- data/lib/mini_sql/sqlite/connection.rb +68 -0
- data/lib/mini_sql/sqlite/deserializer_cache.rb +66 -0
- data/lib/mini_sql/version.rb +3 -0
- data/mini_sql.gemspec +40 -0
- metadata +169 -0
@@ -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,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
|