swift 1.1.0 → 1.2.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.
- data/README.md +7 -7
- data/VERSION +1 -1
- data/lib/swift/adapter/em/mysql.rb +11 -0
- data/lib/swift/adapter/em/postgres.rb +11 -0
- data/lib/swift/adapter/eventmachine.rb +62 -0
- data/lib/swift/adapter/synchrony.rb +48 -0
- data/lib/swift/adapter/synchrony/mysql.rb +12 -0
- data/lib/swift/adapter/synchrony/postgres.rb +12 -0
- data/lib/swift/fiber_connection_pool.rb +0 -17
- data/swift.gemspec +8 -4
- data/test/helper.rb +0 -3
- data/test/test_synchrony.rb +10 -7
- metadata +9 -5
- data/lib/swift/eventmachine.rb +0 -61
- data/lib/swift/synchrony.rb +0 -38
data/README.md
CHANGED
@@ -264,11 +264,11 @@ which implicitly uses `rb_thread_wait_fd`
|
|
264
264
|
or use the `swift/eventmachine` api.
|
265
265
|
|
266
266
|
```ruby
|
267
|
-
require 'swift
|
268
|
-
require 'swift/adapter/postgres'
|
267
|
+
require 'swift'
|
268
|
+
require 'swift/adapter/em/postgres'
|
269
269
|
|
270
270
|
EM.run do
|
271
|
-
pool = 3.times.map { Swift.setup(:default, Swift::Adapter::Postgres, db: "swift") }
|
271
|
+
pool = 3.times.map { Swift.setup(:default, Swift::Adapter::EM::Postgres, db: "swift") }
|
272
272
|
|
273
273
|
3.times.each do |n|
|
274
274
|
defer = pool[n].execute("select pg_sleep(3 - #{n}), #{n + 1} as qid")
|
@@ -287,13 +287,13 @@ or use the `swift/eventmachine` api.
|
|
287
287
|
or use the `em-synchrony` api for `swift`
|
288
288
|
|
289
289
|
```ruby
|
290
|
-
require 'swift
|
291
|
-
require 'swift/adapter/postgres'
|
290
|
+
require 'swift'
|
291
|
+
require 'swift/adapter/synchrony/postgres'
|
292
292
|
|
293
293
|
EM.run do
|
294
294
|
3.times.each do |n|
|
295
295
|
EM.synchrony do
|
296
|
-
db = Swift.setup(:default, Swift::Adapter::Postgres, db: "swift")
|
296
|
+
db = Swift.setup(:default, Swift::Adapter::Synchrony::Postgres, db: "swift")
|
297
297
|
result = db.execute("select pg_sleep(3 - #{n}), #{n + 1} as qid")
|
298
298
|
|
299
299
|
p result.first
|
@@ -312,7 +312,7 @@ require 'swift/fiber_connection_pool'
|
|
312
312
|
|
313
313
|
EM.run do
|
314
314
|
Swift.setup(:default) do
|
315
|
-
Swift::FiberConnectionPool.new(size: 5) {Swift::Adapter::Postgres.new(db: 'swift')}
|
315
|
+
Swift::FiberConnectionPool.new(size: 5) {Swift::Adapter::Synchrony::Postgres.new(db: 'swift')}
|
316
316
|
end
|
317
317
|
|
318
318
|
5.times do
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'swift/adapter/mysql'
|
2
|
+
require 'swift/adapter/eventmachine'
|
3
|
+
|
4
|
+
# TODO: use Module#prepend when backported
|
5
|
+
module Swift
|
6
|
+
class Adapter
|
7
|
+
class Eventmachine::Mysql < Mysql
|
8
|
+
include Eventmachine
|
9
|
+
end # Eventmachine::Mysql
|
10
|
+
end # Adapter
|
11
|
+
end # Swift
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'swift/adapter/postgres'
|
2
|
+
require 'swift/adapter/eventmachine'
|
3
|
+
|
4
|
+
# TODO: use Module#prepend when backported
|
5
|
+
module Swift
|
6
|
+
class Adapter
|
7
|
+
class Eventmachine::Postgres < Postgres
|
8
|
+
include Eventmachine
|
9
|
+
end # Eventmachine::Postgres
|
10
|
+
end # Adapter
|
11
|
+
end # Swift
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'swift'
|
3
|
+
|
4
|
+
module Swift
|
5
|
+
# Eventmachine Adapter Extensions.
|
6
|
+
#
|
7
|
+
# This replaces the Adapter#execute method with a non-blocking asynchronous version.
|
8
|
+
class Adapter
|
9
|
+
module Eventmachine
|
10
|
+
class Handler < EM::Connection
|
11
|
+
def initialize adapter, record, defer
|
12
|
+
@started = Time.now
|
13
|
+
@adapter = adapter
|
14
|
+
@record = record
|
15
|
+
@defer = defer
|
16
|
+
end
|
17
|
+
|
18
|
+
def notify_readable
|
19
|
+
detach
|
20
|
+
start, command, bind = @adapter.pending.shift
|
21
|
+
@adapter.log_command(start, command, bind) if @adapter.trace?
|
22
|
+
|
23
|
+
begin
|
24
|
+
@defer.succeed(@record ? Result.new(@record, @adapter.result) : @adapter.result)
|
25
|
+
rescue Exception => e
|
26
|
+
@defer.fail(e)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end # Handler
|
30
|
+
|
31
|
+
# Execute a command asynchronously.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# defer = Swift.db.execute(User, "select * from users where id = ?", 1)
|
35
|
+
# defer.callback do |user|
|
36
|
+
# p user.id
|
37
|
+
# end
|
38
|
+
# defer.errback do |error|
|
39
|
+
# p error
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @see [Swift::Adapter]
|
43
|
+
def execute command, *bind
|
44
|
+
raise RuntimeError, 'Command already in progress' unless pending.empty?
|
45
|
+
|
46
|
+
record, command = command, bind.shift if command.kind_of?(Class) && command < Record
|
47
|
+
pending << [Time.now, command, bind]
|
48
|
+
query(command, *bind)
|
49
|
+
|
50
|
+
::EM::DefaultDeferrable.new.tap do |defer|
|
51
|
+
::EM.watch(fileno, Handler, self, record, defer) {|c| c.notify_readable = true}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def pending
|
56
|
+
@pending ||= []
|
57
|
+
end
|
58
|
+
end # Eventmachine
|
59
|
+
|
60
|
+
EM = Eventmachine
|
61
|
+
end # Adapter
|
62
|
+
end # Swift
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'em-synchrony'
|
2
|
+
|
3
|
+
module Swift
|
4
|
+
# em-synchrony support for Swift::Adapter
|
5
|
+
#
|
6
|
+
class Adapter
|
7
|
+
module Synchrony
|
8
|
+
# Execute a command asynchronously and pause the Fiber until the command finishes.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# EM.run do
|
12
|
+
# 3.times.each do |n|
|
13
|
+
# EM.synchrony do
|
14
|
+
# db = Swift.setup(:default, Swift::Adapter::Synchrony::Postgres, db: "swift_test")
|
15
|
+
# result = db.execute("select pg_sleep(3 - #{n}), #{n + 1} as qid")
|
16
|
+
#
|
17
|
+
# p result.first
|
18
|
+
# EM.stop if n == 0
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @see [Swift::Adapter]
|
24
|
+
def execute *args
|
25
|
+
res = ::EM::Synchrony.sync super(*args)
|
26
|
+
if res.kind_of?(Error)
|
27
|
+
res.set_backtrace caller.reject {|subject| subject =~ %r{swift/fiber_connection_pool}}
|
28
|
+
raise res
|
29
|
+
end
|
30
|
+
yield res if block_given?
|
31
|
+
res
|
32
|
+
end
|
33
|
+
|
34
|
+
def transaction &block
|
35
|
+
Swift.scopes.push(self)
|
36
|
+
execute('begin')
|
37
|
+
res = yield(self)
|
38
|
+
execute('commit')
|
39
|
+
res
|
40
|
+
rescue => e
|
41
|
+
execute('rollback')
|
42
|
+
raise e
|
43
|
+
ensure
|
44
|
+
Swift.scopes.pop
|
45
|
+
end
|
46
|
+
end # Synchrony
|
47
|
+
end # Adapter
|
48
|
+
end # Swift
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'swift/adapter/synchrony'
|
2
|
+
require 'swift/adapter/em/mysql'
|
3
|
+
|
4
|
+
module Swift
|
5
|
+
# em-synchrony support for Swift::Adapter
|
6
|
+
#
|
7
|
+
class Adapter
|
8
|
+
class Synchrony::Mysql < EM::Mysql
|
9
|
+
include Synchrony
|
10
|
+
end # Synchrony::Mysql
|
11
|
+
end # Adapter
|
12
|
+
end # Swift
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'swift/adapter/synchrony'
|
2
|
+
require 'swift/adapter/em/postgres'
|
3
|
+
|
4
|
+
module Swift
|
5
|
+
# em-synchrony support for Swift::Adapter
|
6
|
+
#
|
7
|
+
class Adapter
|
8
|
+
class Synchrony::Postgres < EM::Postgres
|
9
|
+
include Synchrony
|
10
|
+
end # Synchrony::Postgres
|
11
|
+
end # Adapter
|
12
|
+
end # Swift
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# Based on EM::Synchrony::ConnectionPool
|
2
2
|
|
3
|
-
require 'swift/synchrony'
|
4
|
-
|
5
3
|
module Swift
|
6
4
|
class FiberConnectionPool
|
7
5
|
|
@@ -76,19 +74,4 @@ module Swift
|
|
76
74
|
end
|
77
75
|
end
|
78
76
|
end # FiberConnectionPool
|
79
|
-
|
80
|
-
class Adapter::Sql
|
81
|
-
def serialized_transaction &block
|
82
|
-
Swift.scopes.push(self)
|
83
|
-
execute('begin')
|
84
|
-
res = yield(self)
|
85
|
-
execute('commit')
|
86
|
-
res
|
87
|
-
rescue => e
|
88
|
-
execute('rollback')
|
89
|
-
raise e
|
90
|
-
ensure
|
91
|
-
Swift.scopes.pop
|
92
|
-
end
|
93
|
-
end # Adapter::Sql
|
94
77
|
end # Swift
|
data/swift.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "swift"
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Shane Hanna", "Bharanee 'Barney' Rathna"]
|
12
|
-
s.date = "2013-
|
12
|
+
s.date = "2013-03-10"
|
13
13
|
s.description = "A rational rudimentary database abstraction."
|
14
14
|
s.email = ["shane.hanna@gmail.com", "deepfryed@gmail.com"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -24,12 +24,17 @@ Gem::Specification.new do |s|
|
|
24
24
|
"VERSION",
|
25
25
|
"lib/swift.rb",
|
26
26
|
"lib/swift/adapter.rb",
|
27
|
+
"lib/swift/adapter/em/mysql.rb",
|
28
|
+
"lib/swift/adapter/em/postgres.rb",
|
29
|
+
"lib/swift/adapter/eventmachine.rb",
|
27
30
|
"lib/swift/adapter/mysql.rb",
|
28
31
|
"lib/swift/adapter/postgres.rb",
|
29
32
|
"lib/swift/adapter/sql.rb",
|
30
33
|
"lib/swift/adapter/sqlite3.rb",
|
34
|
+
"lib/swift/adapter/synchrony.rb",
|
35
|
+
"lib/swift/adapter/synchrony/mysql.rb",
|
36
|
+
"lib/swift/adapter/synchrony/postgres.rb",
|
31
37
|
"lib/swift/attribute.rb",
|
32
|
-
"lib/swift/eventmachine.rb",
|
33
38
|
"lib/swift/fiber_connection_pool.rb",
|
34
39
|
"lib/swift/header.rb",
|
35
40
|
"lib/swift/identity_map.rb",
|
@@ -37,7 +42,6 @@ Gem::Specification.new do |s|
|
|
37
42
|
"lib/swift/record.rb",
|
38
43
|
"lib/swift/result.rb",
|
39
44
|
"lib/swift/statement.rb",
|
40
|
-
"lib/swift/synchrony.rb",
|
41
45
|
"lib/swift/type.rb",
|
42
46
|
"lib/swift/validations.rb",
|
43
47
|
"swift.gemspec",
|
data/test/helper.rb
CHANGED
@@ -18,9 +18,6 @@ class MiniTest::Spec
|
|
18
18
|
connection_defaults = { db: 'swift_test', user: Etc.getlogin, host: '127.0.0.1' }
|
19
19
|
adapters.each do |adapter|
|
20
20
|
begin
|
21
|
-
#next if Swift::Adapter::Sqlite3 == adapter
|
22
|
-
#next if Swift::Adapter::Mysql == adapter
|
23
|
-
#next if Swift::Adapter::Postgres == adapter
|
24
21
|
Swift.setup :default, adapter, connection_defaults.merge(adapter_defaults.fetch(adapter, {}))
|
25
22
|
rescue => error
|
26
23
|
warn "Unable to setup 'swift_test' db for #{adapter}, #{error.message}. Skipping..."
|
data/test/test_synchrony.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'swift/fiber_connection_pool'
|
3
|
+
require 'swift/adapter/synchrony/postgres'
|
2
4
|
|
3
5
|
describe 'fiber connection pool' do
|
4
6
|
before do
|
5
|
-
skip 'swift/synchrony re-defines Adapter#execute' unless ENV['TEST_SWIFT_SYNCHRONY']
|
6
7
|
|
7
|
-
require 'swift/fiber_connection_pool'
|
8
8
|
EM.synchrony do
|
9
9
|
Swift.setup(:default, Swift::Adapter::Postgres, db: 'swift_test')
|
10
10
|
Swift.db.execute('drop table if exists users')
|
@@ -17,21 +17,25 @@ describe 'fiber connection pool' do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
10.times { @user.create(name: 'test') }
|
20
|
+
|
21
|
+
# async on from now on
|
22
|
+
Swift.setup(:default) do
|
23
|
+
Swift::FiberConnectionPool.new(size: 2) do
|
24
|
+
Swift::Adapter::Synchrony::Postgres.new(db: 'swift_test')
|
25
|
+
end
|
26
|
+
end
|
20
27
|
EM.stop
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
31
|
it 'can synchronize queries across fibers' do
|
25
32
|
EM.run do
|
26
|
-
Swift.setup(:default) { Swift::FiberConnectionPool.new(size: 2) {Swift::Adapter::Postgres.new(db: 'swift_test')}}
|
27
|
-
|
28
33
|
@counts = []
|
29
34
|
5.times do
|
30
35
|
EM.synchrony do
|
31
36
|
@counts << @user.execute('select * from users').selected_rows
|
32
37
|
end
|
33
38
|
end
|
34
|
-
|
35
39
|
EM.add_timer(0.2) { EM.stop }
|
36
40
|
end
|
37
41
|
|
@@ -42,7 +46,6 @@ describe 'fiber connection pool' do
|
|
42
46
|
it 'sets appropriate backtrace for errors' do
|
43
47
|
EM.synchrony do
|
44
48
|
error = nil
|
45
|
-
Swift.setup(:default) { Swift::FiberConnectionPool.new(size: 2) {Swift::Adapter::Postgres.new(db: 'swift_test')}}
|
46
49
|
|
47
50
|
begin
|
48
51
|
Swift.db.execute 'foo bar baz'
|
@@ -51,7 +54,7 @@ describe 'fiber connection pool' do
|
|
51
54
|
end
|
52
55
|
|
53
56
|
assert error
|
54
|
-
assert_match %r{test/test_synchrony.rb
|
57
|
+
assert_match %r{test/test_synchrony.rb}, error.backtrace[0]
|
55
58
|
EM.stop
|
56
59
|
end
|
57
60
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: swift
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-03-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,12 +45,17 @@ files:
|
|
45
45
|
- VERSION
|
46
46
|
- lib/swift.rb
|
47
47
|
- lib/swift/adapter.rb
|
48
|
+
- lib/swift/adapter/em/mysql.rb
|
49
|
+
- lib/swift/adapter/em/postgres.rb
|
50
|
+
- lib/swift/adapter/eventmachine.rb
|
48
51
|
- lib/swift/adapter/mysql.rb
|
49
52
|
- lib/swift/adapter/postgres.rb
|
50
53
|
- lib/swift/adapter/sql.rb
|
51
54
|
- lib/swift/adapter/sqlite3.rb
|
55
|
+
- lib/swift/adapter/synchrony.rb
|
56
|
+
- lib/swift/adapter/synchrony/mysql.rb
|
57
|
+
- lib/swift/adapter/synchrony/postgres.rb
|
52
58
|
- lib/swift/attribute.rb
|
53
|
-
- lib/swift/eventmachine.rb
|
54
59
|
- lib/swift/fiber_connection_pool.rb
|
55
60
|
- lib/swift/header.rb
|
56
61
|
- lib/swift/identity_map.rb
|
@@ -58,7 +63,6 @@ files:
|
|
58
63
|
- lib/swift/record.rb
|
59
64
|
- lib/swift/result.rb
|
60
65
|
- lib/swift/statement.rb
|
61
|
-
- lib/swift/synchrony.rb
|
62
66
|
- lib/swift/type.rb
|
63
67
|
- lib/swift/validations.rb
|
64
68
|
- swift.gemspec
|
@@ -89,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
93
|
requirements:
|
90
94
|
- - ! '>='
|
91
95
|
- !ruby/object:Gem::Version
|
92
|
-
hash: -
|
96
|
+
hash: -4534175981099401006
|
93
97
|
version: '0'
|
94
98
|
segments:
|
95
99
|
- 0
|
data/lib/swift/eventmachine.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
require 'swift'
|
3
|
-
|
4
|
-
module Swift
|
5
|
-
# Eventmachine Adapter Extensions.
|
6
|
-
#
|
7
|
-
# This replaces the Adapter#execute method with a non-blocking asynchronous version.
|
8
|
-
class Adapter
|
9
|
-
alias :blocking_execute :execute
|
10
|
-
|
11
|
-
class EMHandler < EM::Connection
|
12
|
-
def initialize adapter, record, defer
|
13
|
-
@started = Time.now
|
14
|
-
@adapter = adapter
|
15
|
-
@record = record
|
16
|
-
@defer = defer
|
17
|
-
end
|
18
|
-
|
19
|
-
def notify_readable
|
20
|
-
detach
|
21
|
-
start, command, bind = @adapter.pending.shift
|
22
|
-
@adapter.log_command(start, command, bind) if @adapter.trace?
|
23
|
-
|
24
|
-
begin
|
25
|
-
@defer.succeed(@record ? Result.new(@record, @adapter.result) : @adapter.result)
|
26
|
-
rescue Exception => e
|
27
|
-
@defer.fail(e)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Execute a command asynchronously.
|
33
|
-
#
|
34
|
-
# @example
|
35
|
-
# defer = Swift.db.execute(User, "select * from users where id = ?", 1)
|
36
|
-
# defer.callback do |user|
|
37
|
-
# p user.id
|
38
|
-
# end
|
39
|
-
# defer.errback do |error|
|
40
|
-
# p error
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# @see [Swift::Adapter]
|
44
|
-
def execute command, *bind
|
45
|
-
raise RuntimeError, 'Command already in progress' unless pending.empty?
|
46
|
-
|
47
|
-
start = Time.now
|
48
|
-
record, command = command, bind.shift if command.kind_of?(Class) && command < Record
|
49
|
-
pending << [start, command, bind]
|
50
|
-
query(command, *bind)
|
51
|
-
|
52
|
-
EM::DefaultDeferrable.new.tap do |defer|
|
53
|
-
EM.watch(fileno, EMHandler, self, record, defer) {|c| c.notify_readable = true }
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def pending
|
58
|
-
@pending ||= []
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
data/lib/swift/synchrony.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'em-synchrony'
|
2
|
-
require 'swift/eventmachine'
|
3
|
-
|
4
|
-
module Swift
|
5
|
-
# em-synchrony support for Swift::Adapter
|
6
|
-
#
|
7
|
-
# This replaces the default Adapter#execute with a version that uses EM::Synchrony.sync to wait for the
|
8
|
-
# defered command to complete. It assumes that the execute method is called inside a em-synchrony Fiber.
|
9
|
-
class Adapter
|
10
|
-
alias :aexecute :execute
|
11
|
-
|
12
|
-
# Execute a command asynchronously and pause the Fiber until the command finishes.
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# EM.run do
|
16
|
-
# 3.times.each do |n|
|
17
|
-
# EM.synchrony do
|
18
|
-
# db = Swift.setup(:default, Swift::Adapter::Postgres, db: "swift_test")
|
19
|
-
# result = db.execute("select pg_sleep(3 - #{n}), #{n + 1} as qid")
|
20
|
-
#
|
21
|
-
# p result.first
|
22
|
-
# EM.stop if n == 0
|
23
|
-
# end
|
24
|
-
# end
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# @see [Swift::Adapter]
|
28
|
-
def execute *args
|
29
|
-
res = EM::Synchrony.sync aexecute(*args)
|
30
|
-
if res.kind_of?(Error)
|
31
|
-
res.set_backtrace caller.reject {|subject| subject =~ %r{swift/fiber_connection_pool}}
|
32
|
-
raise res
|
33
|
-
end
|
34
|
-
yield res if block_given?
|
35
|
-
res
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|