simple-sql 0.3.7 → 0.4.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/.rubocop.yml +4 -1
- data/Gemfile.lock +1 -1
- data/lib/simple/sql.rb +30 -107
- data/lib/simple/sql/connection.rb +13 -42
- data/lib/simple/sql/connection_adapter.rb +81 -0
- data/lib/simple/sql/decoder.rb +16 -16
- data/lib/simple/sql/encoder.rb +3 -6
- data/lib/simple/sql/insert.rb +0 -1
- data/lib/simple/sql/logging.rb +37 -5
- data/lib/simple/sql/reflection.rb +2 -2
- data/lib/simple/sql/simple_transactions.rb +46 -0
- data/lib/simple/sql/version.rb +1 -1
- data/scripts/watch +2 -0
- data/spec/simple/sql_insert_spec.rb +1 -1
- data/spec/support/003_factories.rb +1 -1
- metadata +5 -4
- data/spec/simple/sql_record_spec.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2aa68ab4036da8b4bc0eae03f6e4245bd4e8620
|
4
|
+
data.tar.gz: 02eef57ff0d00d3a1d35ef861d1efc88976c1e74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1262f806ebf0db280660ead752521fd3890f9c9472a8ba62f21ab87aa263c369319dab4a300035dabe8288997b68fa1e250c202ba5021b33af438c536c46d63f
|
7
|
+
data.tar.gz: 895728b860f7861b0a7e8f46507abcbe07b70d03ec36520893c507d3939c4848e838f7b0d0804467409daa611485003c66e180c4634b1d0443ace09e3d31798c
|
data/.rubocop.yml
CHANGED
@@ -10,7 +10,7 @@ AllCops:
|
|
10
10
|
- 'Rakefile'
|
11
11
|
|
12
12
|
Metrics/LineLength:
|
13
|
-
Max:
|
13
|
+
Max: 120
|
14
14
|
|
15
15
|
Style/SpecialGlobalVars:
|
16
16
|
Enabled: false
|
@@ -57,3 +57,6 @@ Style/NumericPredicate:
|
|
57
57
|
|
58
58
|
Style/RegexpLiteral:
|
59
59
|
Enabled: false
|
60
|
+
|
61
|
+
Style/ClassVars:
|
62
|
+
Enabled: false
|
data/Gemfile.lock
CHANGED
data/lib/simple/sql.rb
CHANGED
@@ -6,132 +6,50 @@ require_relative "sql/decoder.rb"
|
|
6
6
|
require_relative "sql/encoder.rb"
|
7
7
|
require_relative "sql/config.rb"
|
8
8
|
require_relative "sql/logging.rb"
|
9
|
+
require_relative "sql/simple_transactions.rb"
|
10
|
+
require_relative "sql/connection_adapter.rb"
|
9
11
|
require_relative "sql/connection.rb"
|
10
12
|
require_relative "sql/reflection.rb"
|
11
13
|
require_relative "sql/insert.rb"
|
12
14
|
require_relative "sql/duplicate.rb"
|
13
15
|
|
14
|
-
# rubocop:disable Metrics/MethodLength
|
15
|
-
|
16
16
|
module Simple
|
17
17
|
# The Simple::SQL module
|
18
18
|
module SQL
|
19
19
|
extend self
|
20
|
-
|
21
|
-
def logger
|
22
|
-
@logger ||= default_logger
|
23
|
-
end
|
24
|
-
|
25
|
-
def logger=(logger)
|
26
|
-
@logger = logger
|
27
|
-
end
|
28
|
-
|
29
|
-
def default_logger
|
30
|
-
logger = ActiveRecord::Base.logger if defined?(ActiveRecord)
|
31
|
-
return logger if logger
|
32
|
-
|
33
|
-
logger = Logger.new(STDERR)
|
34
|
-
logger.level = Logger::INFO
|
35
|
-
logger
|
36
|
-
end
|
37
|
-
|
38
|
-
# execute one or more sql statements. This method does not allow to pass in
|
39
|
-
# arguments - since the pg client does not support this - but it allows to
|
40
|
-
# run multiple sql statements separated by ";"
|
41
|
-
def exec(sql)
|
42
|
-
Logging.yield_logged sql do
|
43
|
-
connection.exec sql
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Runs a query, with optional arguments, and returns the result. If the SQL
|
48
|
-
# query returns rows with one column, this method returns an array of these
|
49
|
-
# values. Otherwise it returns an array of arrays.
|
50
|
-
#
|
51
|
-
# Example:
|
52
|
-
#
|
53
|
-
# - <tt>Simple::SQL.all("SELECT id FROM users")</tt> returns an array of id values
|
54
|
-
# - <tt>Simple::SQL.all("SELECT id, email FROM users")</tt> returns an array of
|
55
|
-
# arrays `[ <id>, <email> ]`.
|
56
|
-
#
|
57
|
-
# Simple::SQL.all "SELECT id, email FROM users" do |id, email|
|
58
|
-
# # do something
|
59
|
-
# end
|
60
|
-
|
61
|
-
def all(sql, *args, into: nil, &block)
|
62
|
-
result = exec_logged(sql, *args)
|
63
|
-
enumerate(result, into: into, &block)
|
64
|
-
end
|
65
|
-
|
66
|
-
# Runs a query and returns the first result row of a query.
|
67
|
-
#
|
68
|
-
# Examples:
|
69
|
-
#
|
70
|
-
# - <tt>Simple::SQL.ask "SELECT id FROM users WHERE email=$?", "foo@local"</tt>
|
71
|
-
# returns a number (or +nil+)
|
72
|
-
# - <tt>Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local"</tt>
|
73
|
-
# returns an array <tt>[ <id>, <email> ]</tt> (or +nil+)
|
74
|
-
def ask(sql, *args, into: nil)
|
75
|
-
catch(:ok) do
|
76
|
-
all(sql, *args, into: into) { |row| throw :ok, row }
|
77
|
-
nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# [Deprecated] Runs a query, with optional arguments, and returns the
|
82
|
-
# result as an array of Hashes.
|
83
|
-
def records(sql, *args, into: Hash, &block)
|
84
|
-
all sql, *args, into: (into || Hash), &block
|
85
|
-
end
|
86
|
-
|
87
|
-
# [Deprecated] Runs a query and returns the first result row of a query
|
88
|
-
# as a Hash.
|
89
|
-
def record(sql, *args, into: Hash)
|
90
|
-
ask sql, *args, into: (into || Hash)
|
91
|
-
end
|
92
|
-
|
93
20
|
extend Forwardable
|
21
|
+
delegate [:ask, :all, :each] => :connection
|
94
22
|
delegate [:transaction, :wait_for_notify] => :connection
|
95
23
|
|
96
|
-
|
24
|
+
delegate [:logger, :logger=] => ::Simple::SQL::Logging
|
97
25
|
|
98
|
-
|
99
|
-
Logging.yield_logged sql, *args do
|
100
|
-
connection.exec_params(sql, Encoder.encode_args(args))
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def enumerate(result, into:, &block)
|
105
|
-
decoder = Decoder.new(result, into: into)
|
106
|
-
|
107
|
-
if block
|
108
|
-
result.each_row do |row|
|
109
|
-
yield decoder.decode(row)
|
110
|
-
end
|
111
|
-
self
|
112
|
-
else
|
113
|
-
ary = []
|
114
|
-
result.each_row { |row| ary << decoder.decode(row) }
|
115
|
-
ary
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def resolve_type(ftype, fmod)
|
120
|
-
@resolved_types ||= {}
|
121
|
-
@resolved_types[[ftype, fmod]] ||= connection.exec("SELECT format_type($1,$2)", [ftype, fmod]).getvalue(0, 0)
|
122
|
-
end
|
26
|
+
private
|
123
27
|
|
124
28
|
def connection
|
125
|
-
|
29
|
+
connector.call
|
126
30
|
end
|
127
31
|
|
32
|
+
# The connector attribute returns a lambda, which, when called, returns a connection
|
33
|
+
# object.
|
34
|
+
#
|
35
|
+
# If this seems weird: this is for interacting with ActiveRecord. To be in sync how
|
36
|
+
# Rails handles ActiveRecord connections (it checks it out of a connection pool when
|
37
|
+
# needed for the first time in a request cycle, and checks it in afterwards) we need
|
38
|
+
# to make sure not to keep a reference to the actual connection object around. Instead
|
39
|
+
# we need to be able to call a function (in that case ActiveRecord::Base.connection).
|
40
|
+
#
|
41
|
+
# In non-Rails mode the connector really is a lambda which just returns an object.
|
42
|
+
#
|
43
|
+
# In any case the connector is stored in a thread-safe fashion. This is not necessary
|
44
|
+
# in Rails mode (because AR::B.connection itself is thread-safe already), but in non-
|
45
|
+
# Rails-mode we make sure to manage one connection per thread.
|
128
46
|
def connector=(connector)
|
129
|
-
|
47
|
+
Thread.current[:"Simple::SQL.connector"] = connector
|
130
48
|
end
|
131
49
|
|
132
|
-
|
133
|
-
connect_to_active_record
|
134
|
-
|
50
|
+
def connector
|
51
|
+
Thread.current[:"Simple::SQL.connector"] ||= lambda { connect_to_active_record }
|
52
|
+
end
|
135
53
|
|
136
54
|
def connect_to_active_record
|
137
55
|
return Connection.active_record_connection if defined?(ActiveRecord)
|
@@ -161,12 +79,17 @@ module Simple
|
|
161
79
|
def connect!(database_url = :auto)
|
162
80
|
database_url = Config.determine_url if database_url == :auto
|
163
81
|
|
164
|
-
|
82
|
+
Logging.info "Connecting to #{database_url}"
|
165
83
|
config = Config.parse_url(database_url)
|
166
84
|
|
167
85
|
require "pg"
|
168
86
|
connection = Connection.pg_connection(PG::Connection.new(config))
|
169
87
|
self.connector = lambda { connection }
|
170
88
|
end
|
89
|
+
|
90
|
+
# disconnects the current connection.
|
91
|
+
def disconnect!
|
92
|
+
self.connector = nil
|
93
|
+
end
|
171
94
|
end
|
172
95
|
end
|
@@ -1,6 +1,3 @@
|
|
1
|
-
# rubocop:disable Metrics/MethodLength
|
2
|
-
# rubocop:disable Style/IfUnlessModifier
|
3
|
-
|
4
1
|
# private
|
5
2
|
module Simple::SQL::Connection
|
6
3
|
def self.active_record_connection
|
@@ -11,60 +8,34 @@ module Simple::SQL::Connection
|
|
11
8
|
PgConnection.new(connection)
|
12
9
|
end
|
13
10
|
|
11
|
+
# A PgConnection object is built around a raw connection. It includes
|
12
|
+
# the ConnectionAdapter, which implements ask, all, + friends, and also
|
13
|
+
# includes a quiet simplistic Transaction implementation
|
14
14
|
class PgConnection
|
15
|
-
|
16
|
-
delegate %w(exec_params exec escape wait_for_notify) => :@raw_connection
|
15
|
+
attr_reader :raw_connection
|
17
16
|
|
18
17
|
def initialize(raw_connection)
|
19
18
|
@raw_connection = raw_connection
|
20
|
-
@tx_nesting_level = 0
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
def transaction(&_block)
|
26
|
-
# Notes: by using "ensure" (as opposed to rescue) we are rolling back
|
27
|
-
# both when an exception was raised and when a value was thrown. This
|
28
|
-
# also means we have to track whether or not to rollback. i.e. do roll
|
29
|
-
# back when we yielded to &block but not otherwise.
|
30
|
-
#
|
31
|
-
# Also the transaction support is a bit limited: you cannot rollback.
|
32
|
-
# Rolling back from inside a nested transaction would require SAVEPOINT
|
33
|
-
# support; without the code is simpler at least :)
|
34
|
-
|
35
|
-
if @tx_nesting_level == 0
|
36
|
-
exec "BEGIN"
|
37
|
-
tx_started = true
|
38
|
-
end
|
39
|
-
|
40
|
-
@tx_nesting_level += 1
|
21
|
+
include ::Simple::SQL::ConnectionAdapter # all, ask, first, etc.
|
22
|
+
include ::Simple::SQL::SimpleTransactions # transactions
|
41
23
|
|
42
|
-
|
43
|
-
|
44
|
-
# Only commit if we started a transaction here.
|
45
|
-
if tx_started
|
46
|
-
exec "COMMIT"
|
47
|
-
tx_committed = true
|
48
|
-
end
|
49
|
-
|
50
|
-
return_value
|
51
|
-
ensure
|
52
|
-
@tx_nesting_level -= 1
|
53
|
-
if tx_started && !tx_committed
|
54
|
-
exec "ROLLBACK"
|
55
|
-
end
|
56
|
-
end
|
24
|
+
extend Forwardable
|
25
|
+
delegate [:wait_for_notify] => :raw_connection # wait_for_notify
|
57
26
|
end
|
58
27
|
|
59
28
|
module ActiveRecordConnection
|
60
29
|
extend self
|
61
30
|
|
31
|
+
extend ::Simple::SQL::ConnectionAdapter # all, ask, first, etc.
|
32
|
+
|
62
33
|
extend Forwardable
|
63
|
-
delegate
|
64
|
-
delegate [:
|
34
|
+
delegate [:transaction] => :connection # transactions
|
35
|
+
delegate [:wait_for_notify] => :connection # wait_for_notify
|
65
36
|
|
66
37
|
def raw_connection
|
67
|
-
connection.raw_connection
|
38
|
+
ActiveRecord::Base.connection.raw_connection
|
68
39
|
end
|
69
40
|
|
70
41
|
def connection
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# rubocop:disable Metrics/MethodLength
|
2
|
+
|
3
|
+
# This module implements an adapter between the Simple::SQL interface
|
4
|
+
# (i.e. ask, all, first, transaction) and a raw connection.
|
5
|
+
#
|
6
|
+
# This module can be mixed onto objects that implement a raw_connection
|
7
|
+
# method, which must return a Pg::Connection.
|
8
|
+
module Simple::SQL::ConnectionAdapter
|
9
|
+
Logging = Simple::SQL::Logging
|
10
|
+
Encoder = Simple::SQL::Encoder
|
11
|
+
Decoder = Simple::SQL::Decoder
|
12
|
+
|
13
|
+
# execute one or more sql statements. This method does not allow to pass in
|
14
|
+
# arguments - since the pg client does not support this - but it allows to
|
15
|
+
# run multiple sql statements separated by ";"
|
16
|
+
def exec(sql)
|
17
|
+
Logging.yield_logged sql do
|
18
|
+
raw_connection.exec sql
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Runs a query, with optional arguments, and returns the result. If the SQL
|
23
|
+
# query returns rows with one column, this method returns an array of these
|
24
|
+
# values. Otherwise it returns an array of arrays.
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
#
|
28
|
+
# - <tt>Simple::SQL.all("SELECT id FROM users")</tt> returns an array of id values
|
29
|
+
# - <tt>Simple::SQL.all("SELECT id, email FROM users")</tt> returns an array of
|
30
|
+
# arrays `[ <id>, <email> ]`.
|
31
|
+
#
|
32
|
+
# Simple::SQL.all "SELECT id, email FROM users" do |id, email|
|
33
|
+
# # do something
|
34
|
+
# end
|
35
|
+
|
36
|
+
def all(sql, *args, into: nil, &block)
|
37
|
+
result = exec_logged(sql, *args)
|
38
|
+
enumerate(result, into: into, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Runs a query and returns the first result row of a query.
|
42
|
+
#
|
43
|
+
# Examples:
|
44
|
+
#
|
45
|
+
# - <tt>Simple::SQL.ask "SELECT id FROM users WHERE email=$?", "foo@local"</tt>
|
46
|
+
# returns a number (or +nil+)
|
47
|
+
# - <tt>Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local"</tt>
|
48
|
+
# returns an array <tt>[ <id>, <email> ]</tt> (or +nil+)
|
49
|
+
def ask(sql, *args, into: nil)
|
50
|
+
catch(:ok) do
|
51
|
+
all(sql, *args, into: into) { |row| throw :ok, row }
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def exec_logged(sql, *args)
|
57
|
+
Logging.yield_logged sql, *args do
|
58
|
+
raw_connection.exec_params(sql, Encoder.encode_args(raw_connection, args))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def enumerate(result, into:, &block)
|
63
|
+
decoder = Decoder.new(self, result, into: into)
|
64
|
+
|
65
|
+
if block
|
66
|
+
result.each_row do |row|
|
67
|
+
yield decoder.decode(row)
|
68
|
+
end
|
69
|
+
self
|
70
|
+
else
|
71
|
+
ary = []
|
72
|
+
result.each_row { |row| ary << decoder.decode(row) }
|
73
|
+
ary
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def resolve_type(ftype, fmod)
|
78
|
+
@resolved_types ||= {}
|
79
|
+
@resolved_types[[ftype, fmod]] ||= raw_connection.exec("SELECT format_type($1,$2)", [ftype, fmod]).getvalue(0, 0)
|
80
|
+
end
|
81
|
+
end
|
data/lib/simple/sql/decoder.rb
CHANGED
@@ -4,12 +4,12 @@ require "time"
|
|
4
4
|
module Simple::SQL::Decoder
|
5
5
|
extend self
|
6
6
|
|
7
|
-
def new(result, into:)
|
8
|
-
if into == Hash then HashRecord.new(result)
|
9
|
-
elsif into == :struct then StructRecord.new(result)
|
10
|
-
elsif into then Record.new(result, into: into)
|
11
|
-
elsif result.nfields == 1 then SingleColumn.new(result)
|
12
|
-
else MultiColumns.new(result)
|
7
|
+
def new(connection, result, into:)
|
8
|
+
if into == Hash then HashRecord.new(connection, result)
|
9
|
+
elsif into == :struct then StructRecord.new(connection, result)
|
10
|
+
elsif into then Record.new(connection, result, into: into)
|
11
|
+
elsif result.nfields == 1 then SingleColumn.new(connection, result)
|
12
|
+
else MultiColumns.new(connection, result)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -82,8 +82,8 @@ module Simple::SQL::Decoder
|
|
82
82
|
end
|
83
83
|
|
84
84
|
class Simple::SQL::Decoder::SingleColumn
|
85
|
-
def initialize(result)
|
86
|
-
typename =
|
85
|
+
def initialize(connection, result)
|
86
|
+
typename = connection.resolve_type(result.ftype(0), result.fmod(0))
|
87
87
|
@field_type = typename.to_sym
|
88
88
|
end
|
89
89
|
|
@@ -94,9 +94,9 @@ class Simple::SQL::Decoder::SingleColumn
|
|
94
94
|
end
|
95
95
|
|
96
96
|
class Simple::SQL::Decoder::MultiColumns
|
97
|
-
def initialize(result)
|
97
|
+
def initialize(connection, result)
|
98
98
|
@field_types = 0.upto(result.fields.length - 1).map do |idx|
|
99
|
-
typename =
|
99
|
+
typename = connection.resolve_type(result.ftype(idx), result.fmod(idx))
|
100
100
|
typename.to_sym
|
101
101
|
end
|
102
102
|
end
|
@@ -109,8 +109,8 @@ class Simple::SQL::Decoder::MultiColumns
|
|
109
109
|
end
|
110
110
|
|
111
111
|
class Simple::SQL::Decoder::HashRecord < Simple::SQL::Decoder::MultiColumns
|
112
|
-
def initialize(result)
|
113
|
-
super(result)
|
112
|
+
def initialize(connection, result)
|
113
|
+
super(connection, result)
|
114
114
|
@field_names = result.fields.map(&:to_sym)
|
115
115
|
end
|
116
116
|
|
@@ -121,8 +121,8 @@ class Simple::SQL::Decoder::HashRecord < Simple::SQL::Decoder::MultiColumns
|
|
121
121
|
end
|
122
122
|
|
123
123
|
class Simple::SQL::Decoder::Record < Simple::SQL::Decoder::HashRecord
|
124
|
-
def initialize(result, into:)
|
125
|
-
super(result)
|
124
|
+
def initialize(connection, result, into:)
|
125
|
+
super(connection, result)
|
126
126
|
@into = into
|
127
127
|
end
|
128
128
|
|
@@ -134,8 +134,8 @@ end
|
|
134
134
|
class Simple::SQL::Decoder::StructRecord < Simple::SQL::Decoder::MultiColumns
|
135
135
|
@@struct_cache = {}
|
136
136
|
|
137
|
-
def initialize(result)
|
138
|
-
super(result)
|
137
|
+
def initialize(connection, result)
|
138
|
+
super(connection, result)
|
139
139
|
|
140
140
|
field_names = result.fields.map(&:to_sym)
|
141
141
|
@into = @@struct_cache[field_names] ||= Struct.new(*field_names)
|
data/lib/simple/sql/encoder.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
# private
|
2
2
|
module Simple::SQL::Encoder
|
3
3
|
extend self
|
4
|
-
extend Forwardable
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
def encode_args(args)
|
9
|
-
args.map { |arg| encode_arg(arg) }
|
5
|
+
def encode_args(connection, args)
|
6
|
+
args.map { |arg| encode_arg(connection, arg) }
|
10
7
|
end
|
11
8
|
|
12
|
-
def encode_arg(arg)
|
9
|
+
def encode_arg(connection, arg)
|
13
10
|
return arg unless arg.is_a?(Array)
|
14
11
|
|
15
12
|
if arg.first.is_a?(String)
|
data/lib/simple/sql/insert.rb
CHANGED
data/lib/simple/sql/logging.rb
CHANGED
@@ -1,20 +1,52 @@
|
|
1
|
-
# rubocop:disable Metrics/AbcSize
|
2
|
-
# rubocop:disable Metrics/LineLength
|
3
|
-
|
4
1
|
module Simple
|
5
2
|
module SQL
|
6
3
|
module Logging
|
7
4
|
extend self
|
8
5
|
|
6
|
+
# The logger object.
|
7
|
+
#
|
8
|
+
# If no logger was set via <tt>Simple::SQL::Logging.logger = <foo></tt>
|
9
|
+
# this returns a default logger.
|
10
|
+
def logger
|
11
|
+
@logger ||= default_logger
|
12
|
+
end
|
13
|
+
|
14
|
+
# The logger object.
|
15
|
+
def logger=(logger)
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def default_logger
|
22
|
+
# return the ActiveRecord logger, if it exists.
|
23
|
+
if defined?(ActiveRecord)
|
24
|
+
logger = ActiveRecord::Base.logger
|
25
|
+
return logger if logger
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns a stderr_logger
|
29
|
+
@stderr_logger ||= begin
|
30
|
+
logger = Logger.new(STDERR)
|
31
|
+
logger.level = Logger::INFO
|
32
|
+
logger
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
public
|
37
|
+
|
38
|
+
extend Forwardable
|
39
|
+
delegate [:debug, :info, :warn, :error, :fatal] => :logger
|
40
|
+
|
9
41
|
def yield_logged(sql, *args, &_block)
|
10
42
|
r0 = Time.now
|
11
43
|
rv = yield
|
12
44
|
realtime = Time.now - r0
|
13
|
-
|
45
|
+
debug "[sql] %.3f secs: %s" % [realtime, format_query(sql, *args)]
|
14
46
|
rv
|
15
47
|
rescue StandardError => e
|
16
48
|
realtime = Time.now - r0
|
17
|
-
|
49
|
+
warn "[sql] %.3f secs: %s:\n\tfailed with error %s" % [realtime, format_query(sql, *args), e.message]
|
18
50
|
raise
|
19
51
|
end
|
20
52
|
|
@@ -47,7 +47,7 @@ module Simple
|
|
47
47
|
"table_schema || '.' || table_name AS name, *"
|
48
48
|
end
|
49
49
|
|
50
|
-
recs =
|
50
|
+
recs = all <<~SQL, schema, into: Hash
|
51
51
|
SELECT #{columns}
|
52
52
|
FROM information_schema.tables
|
53
53
|
WHERE table_schema=$1
|
@@ -57,7 +57,7 @@ module Simple
|
|
57
57
|
|
58
58
|
def column_info(table_name)
|
59
59
|
schema, table_name = parse_table_name(table_name)
|
60
|
-
recs =
|
60
|
+
recs = all <<~SQL, schema, table_name, into: Hash
|
61
61
|
SELECT
|
62
62
|
column_name AS name,
|
63
63
|
*
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# rubocop:disable Metrics/MethodLength
|
2
|
+
# rubocop:disable Style/IfUnlessModifier
|
3
|
+
|
4
|
+
# private
|
5
|
+
module Simple::SQL::SimpleTransactions
|
6
|
+
def tx_nesting_level
|
7
|
+
@tx_nesting_level ||= 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def tx_nesting_level=(tx_nesting_level)
|
11
|
+
@tx_nesting_level = tx_nesting_level
|
12
|
+
end
|
13
|
+
|
14
|
+
def transaction(&_block)
|
15
|
+
# Notes: by using "ensure" (as opposed to rescue) we are rolling back
|
16
|
+
# both when an exception was raised and when a value was thrown. This
|
17
|
+
# also means we have to track whether or not to rollback. i.e. do roll
|
18
|
+
# back when we yielded to &block but not otherwise.
|
19
|
+
#
|
20
|
+
# Also the transaction support is a bit limited: you cannot rollback.
|
21
|
+
# Rolling back from inside a nested transaction would require SAVEPOINT
|
22
|
+
# support; without the code is simpler at least :)
|
23
|
+
|
24
|
+
if tx_nesting_level == 0
|
25
|
+
exec "BEGIN"
|
26
|
+
tx_started = true
|
27
|
+
end
|
28
|
+
|
29
|
+
self.tx_nesting_level += 1
|
30
|
+
|
31
|
+
return_value = yield
|
32
|
+
|
33
|
+
# Only commit if we started a transaction here.
|
34
|
+
if tx_started
|
35
|
+
exec "COMMIT"
|
36
|
+
tx_committed = true
|
37
|
+
end
|
38
|
+
|
39
|
+
return_value
|
40
|
+
ensure
|
41
|
+
self.tx_nesting_level -= 1
|
42
|
+
if tx_started && !tx_committed
|
43
|
+
exec "ROLLBACK"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/simple/sql/version.rb
CHANGED
data/scripts/watch
ADDED
@@ -11,7 +11,7 @@ describe "Simple::SQL.insert" do
|
|
11
11
|
expect(initial_ids).not_to include(id)
|
12
12
|
expect(SQL.ask("SELECT count(*) FROM users")).to eq(USER_COUNT+1)
|
13
13
|
|
14
|
-
user = SQL.
|
14
|
+
user = SQL.ask("SELECT * FROM users WHERE id=$1", id, into: OpenStruct)
|
15
15
|
expect(user.first_name).to eq("foo")
|
16
16
|
expect(user.last_name).to eq("bar")
|
17
17
|
expect(user.created_at).to be_a(Time)
|
@@ -29,5 +29,5 @@ end
|
|
29
29
|
def create(table)
|
30
30
|
table_name = table.to_s.pluralize
|
31
31
|
id = Simple::SQL.insert(table_name, attrs(table))
|
32
|
-
Simple::SQL.
|
32
|
+
Simple::SQL.ask("SELECT * FROM #{table_name} WHERE id=$1", id, into: OpenStruct)
|
33
33
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radiospiel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-04-
|
12
|
+
date: 2018-04-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg_array_parser
|
@@ -170,14 +170,17 @@ files:
|
|
170
170
|
- lib/simple/sql.rb
|
171
171
|
- lib/simple/sql/config.rb
|
172
172
|
- lib/simple/sql/connection.rb
|
173
|
+
- lib/simple/sql/connection_adapter.rb
|
173
174
|
- lib/simple/sql/decoder.rb
|
174
175
|
- lib/simple/sql/duplicate.rb
|
175
176
|
- lib/simple/sql/encoder.rb
|
176
177
|
- lib/simple/sql/insert.rb
|
177
178
|
- lib/simple/sql/logging.rb
|
178
179
|
- lib/simple/sql/reflection.rb
|
180
|
+
- lib/simple/sql/simple_transactions.rb
|
179
181
|
- lib/simple/sql/version.rb
|
180
182
|
- log/.gitkeep
|
183
|
+
- scripts/watch
|
181
184
|
- simple-sql.gemspec
|
182
185
|
- spec/simple/sql/version_spec.rb
|
183
186
|
- spec/simple/sql_all_into_spec.rb
|
@@ -189,7 +192,6 @@ files:
|
|
189
192
|
- spec/simple/sql_duplicate_spec.rb
|
190
193
|
- spec/simple/sql_duplicate_unique_spec.rb
|
191
194
|
- spec/simple/sql_insert_spec.rb
|
192
|
-
- spec/simple/sql_record_spec.rb
|
193
195
|
- spec/simple/sql_reflection_spec.rb
|
194
196
|
- spec/spec_helper.rb
|
195
197
|
- spec/support/001_database.rb
|
@@ -232,7 +234,6 @@ test_files:
|
|
232
234
|
- spec/simple/sql_duplicate_spec.rb
|
233
235
|
- spec/simple/sql_duplicate_unique_spec.rb
|
234
236
|
- spec/simple/sql_insert_spec.rb
|
235
|
-
- spec/simple/sql_record_spec.rb
|
236
237
|
- spec/simple/sql_reflection_spec.rb
|
237
238
|
- spec/spec_helper.rb
|
238
239
|
- spec/support/001_database.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
describe "Simple::SQL.record" do
|
4
|
-
let!(:users) { 1.upto(USER_COUNT).map { create(:user) } }
|
5
|
-
|
6
|
-
it "calls the database" do
|
7
|
-
r = SQL.record("SELECT COUNT(*) AS count FROM users")
|
8
|
-
expect(r).to eq({count: 2})
|
9
|
-
end
|
10
|
-
|
11
|
-
it "returns nil when there is no record" do
|
12
|
-
r = SQL.record("SELECT * FROM users WHERE FALSE", into: OpenStruct)
|
13
|
-
expect(r).to be_nil
|
14
|
-
end
|
15
|
-
|
16
|
-
it "supports the into: option" do
|
17
|
-
r = SQL.record("SELECT COUNT(*) AS count FROM users", into: OpenStruct)
|
18
|
-
expect(r).to be_a(OpenStruct)
|
19
|
-
expect(r).to eq(OpenStruct.new(count: 2))
|
20
|
-
end
|
21
|
-
|
22
|
-
it "supports the into: option even with parameters" do
|
23
|
-
r = SQL.record("SELECT $1::integer AS count FROM users", 2, into: OpenStruct)
|
24
|
-
expect(r).to be_a(OpenStruct)
|
25
|
-
expect(r).to eq(OpenStruct.new(count: 2))
|
26
|
-
end
|
27
|
-
end
|