sbf-data_objects 0.10.17
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/ChangeLog.markdown +115 -0
- data/LICENSE +20 -0
- data/README.markdown +18 -0
- data/Rakefile +20 -0
- data/lib/data_objects/byte_array.rb +6 -0
- data/lib/data_objects/command.rb +79 -0
- data/lib/data_objects/connection.rb +95 -0
- data/lib/data_objects/error/connection_error.rb +4 -0
- data/lib/data_objects/error/data_error.rb +4 -0
- data/lib/data_objects/error/integrity_error.rb +4 -0
- data/lib/data_objects/error/sql_error.rb +17 -0
- data/lib/data_objects/error/syntax_error.rb +4 -0
- data/lib/data_objects/error/transaction_error.rb +4 -0
- data/lib/data_objects/error.rb +4 -0
- data/lib/data_objects/extension.rb +9 -0
- data/lib/data_objects/logger.rb +247 -0
- data/lib/data_objects/pooling.rb +243 -0
- data/lib/data_objects/quoting.rb +99 -0
- data/lib/data_objects/reader.rb +45 -0
- data/lib/data_objects/result.rb +21 -0
- data/lib/data_objects/spec/lib/pending_helpers.rb +13 -0
- data/lib/data_objects/spec/lib/ssl.rb +19 -0
- data/lib/data_objects/spec/setup.rb +5 -0
- data/lib/data_objects/spec/shared/command_spec.rb +201 -0
- data/lib/data_objects/spec/shared/connection_spec.rb +148 -0
- data/lib/data_objects/spec/shared/encoding_spec.rb +161 -0
- data/lib/data_objects/spec/shared/error/sql_error_spec.rb +23 -0
- data/lib/data_objects/spec/shared/quoting_spec.rb +0 -0
- data/lib/data_objects/spec/shared/reader_spec.rb +180 -0
- data/lib/data_objects/spec/shared/result_spec.rb +67 -0
- data/lib/data_objects/spec/shared/typecast/array_spec.rb +29 -0
- data/lib/data_objects/spec/shared/typecast/bigdecimal_spec.rb +112 -0
- data/lib/data_objects/spec/shared/typecast/boolean_spec.rb +133 -0
- data/lib/data_objects/spec/shared/typecast/byte_array_spec.rb +76 -0
- data/lib/data_objects/spec/shared/typecast/class_spec.rb +53 -0
- data/lib/data_objects/spec/shared/typecast/date_spec.rb +114 -0
- data/lib/data_objects/spec/shared/typecast/datetime_spec.rb +140 -0
- data/lib/data_objects/spec/shared/typecast/float_spec.rb +115 -0
- data/lib/data_objects/spec/shared/typecast/integer_spec.rb +92 -0
- data/lib/data_objects/spec/shared/typecast/ipaddr_spec.rb +0 -0
- data/lib/data_objects/spec/shared/typecast/nil_spec.rb +107 -0
- data/lib/data_objects/spec/shared/typecast/other_spec.rb +41 -0
- data/lib/data_objects/spec/shared/typecast/range_spec.rb +29 -0
- data/lib/data_objects/spec/shared/typecast/string_spec.rb +130 -0
- data/lib/data_objects/spec/shared/typecast/time_spec.rb +111 -0
- data/lib/data_objects/transaction.rb +111 -0
- data/lib/data_objects/uri.rb +109 -0
- data/lib/data_objects/utilities.rb +18 -0
- data/lib/data_objects/version.rb +3 -0
- data/lib/data_objects.rb +20 -0
- data/spec/command_spec.rb +24 -0
- data/spec/connection_spec.rb +31 -0
- data/spec/do_mock.rb +29 -0
- data/spec/do_mock2.rb +29 -0
- data/spec/pooling_spec.rb +153 -0
- data/spec/reader_spec.rb +19 -0
- data/spec/result_spec.rb +21 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/transaction_spec.rb +37 -0
- data/spec/uri_spec.rb +23 -0
- data/tasks/release.rake +14 -0
- data/tasks/spec.rake +10 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +122 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'socket'
|
3
|
+
require 'digest'
|
4
|
+
require 'digest/sha2'
|
5
|
+
|
6
|
+
module DataObjects
|
7
|
+
class Transaction
|
8
|
+
# The local host name. Do not attempt to resolve in DNS to prevent potentially long delay
|
9
|
+
HOST = begin
|
10
|
+
Socket.gethostname.to_s
|
11
|
+
rescue
|
12
|
+
'localhost'
|
13
|
+
end
|
14
|
+
@@counter = 0
|
15
|
+
|
16
|
+
# The connection object allocated for this transaction
|
17
|
+
attr_reader :connection
|
18
|
+
# A unique ID for this transaction
|
19
|
+
attr_reader :id
|
20
|
+
|
21
|
+
# Instantiate the Transaction subclass that's appropriate for this uri scheme
|
22
|
+
def self.create_for_uri(uri)
|
23
|
+
uri = URI.parse(uri) if uri.is_a?(String)
|
24
|
+
DataObjects.const_get(uri.scheme.capitalize)::Transaction.new(uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Creates a Transaction bound to a connection for the given DataObjects::URI
|
29
|
+
#
|
30
|
+
def initialize(uri, connection = nil)
|
31
|
+
@connection = connection || DataObjects::Connection.new(uri)
|
32
|
+
# PostgreSQL can't handle the full 64 bytes. This should be enough for everyone.
|
33
|
+
@id = Digest::SHA256.hexdigest("#{HOST}:#{$PROCESS_ID}:#{Time.now.to_f}:#{@@counter += 1}")[0..-2]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Close the connection for this Transaction
|
37
|
+
def close
|
38
|
+
@connection.close
|
39
|
+
end
|
40
|
+
|
41
|
+
def begin
|
42
|
+
run 'BEGIN'
|
43
|
+
end
|
44
|
+
|
45
|
+
def commit
|
46
|
+
run 'COMMIT'
|
47
|
+
end
|
48
|
+
|
49
|
+
def rollback
|
50
|
+
run 'ROLLBACK'
|
51
|
+
end
|
52
|
+
|
53
|
+
def prepare
|
54
|
+
not_implemented
|
55
|
+
end
|
56
|
+
|
57
|
+
def begin_prepared
|
58
|
+
not_implemented
|
59
|
+
end
|
60
|
+
|
61
|
+
def commit_prepared
|
62
|
+
not_implemented
|
63
|
+
end
|
64
|
+
|
65
|
+
def rollback_prepared
|
66
|
+
not_implemented
|
67
|
+
end
|
68
|
+
|
69
|
+
def prepare
|
70
|
+
not_implemented
|
71
|
+
end
|
72
|
+
|
73
|
+
protected def run(cmd)
|
74
|
+
connection.create_command(cmd).execute_non_query
|
75
|
+
end
|
76
|
+
|
77
|
+
private def not_implemented
|
78
|
+
raise NotImplementedError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class SavePoint < Transaction
|
83
|
+
# We don't bounce through DO::<Adapter/scheme>::SavePoint because there
|
84
|
+
# doesn't appear to be any custom SQL to support this.
|
85
|
+
def self.create_for_uri(uri, connection)
|
86
|
+
uri = URI.parse(uri) if uri.is_a?(String)
|
87
|
+
DataObjects::SavePoint.new(uri, connection)
|
88
|
+
end
|
89
|
+
|
90
|
+
# SavePoints can only occur in the context of a Transaction, thus they
|
91
|
+
# re-use TXN's connection (which was acquired from the connection pool
|
92
|
+
# legitimately via DO::Connection.new). We no-op #close in SP because
|
93
|
+
# calling DO::Connection#close will release the connection back into the
|
94
|
+
# pool (before the top-level Transaction might be done with it).
|
95
|
+
def close
|
96
|
+
# no-op
|
97
|
+
end
|
98
|
+
|
99
|
+
def begin
|
100
|
+
run %(SAVEPOINT "#{@id}")
|
101
|
+
end
|
102
|
+
|
103
|
+
def commit
|
104
|
+
run %(RELEASE SAVEPOINT "#{@id}")
|
105
|
+
end
|
106
|
+
|
107
|
+
def rollback
|
108
|
+
run %(ROLLBACK TO SAVEPOINT "#{@id}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
module DataObjects
|
4
|
+
# A DataObjects URI is of the form scheme://user:password@host:port/path#fragment
|
5
|
+
#
|
6
|
+
# The elements are all optional except scheme and path:
|
7
|
+
# scheme:: The name of a DBMS for which you have a do_\<scheme\> adapter gem installed.
|
8
|
+
# If scheme is *jdbc*, the actual DBMS is in the _path_ followed by a colon.
|
9
|
+
# user:: The name of the user to authenticate to the database
|
10
|
+
# password:: The password to use in authentication
|
11
|
+
# host:: The domain name (defaulting to localhost) where the database is available
|
12
|
+
# port:: The TCP/IP port number to use for the connection
|
13
|
+
# path:: The name or path to the database
|
14
|
+
# query:: Parameters for the connection, for example encoding=utf8
|
15
|
+
# fragment:: Not currently known to be in use, but available to the adapters
|
16
|
+
class URI
|
17
|
+
attr_reader :scheme, :subscheme, :user, :password, :host, :port, :path, :query, :fragment
|
18
|
+
|
19
|
+
# Make a DataObjects::URI object by parsing a string. Simply delegates to Addressable::URI::parse.
|
20
|
+
def self.parse(uri)
|
21
|
+
return uri if uri.is_a?(self)
|
22
|
+
|
23
|
+
if uri.is_a?(Addressable::URI)
|
24
|
+
scheme = uri.scheme
|
25
|
+
elsif uri[0, 4] == 'jdbc'
|
26
|
+
scheme = uri[0, 4]
|
27
|
+
uri = Addressable::URI.parse(uri[5, uri.length])
|
28
|
+
subscheme = uri&.scheme
|
29
|
+
else
|
30
|
+
uri = Addressable::URI.parse(uri)
|
31
|
+
scheme = uri&.scheme
|
32
|
+
subscheme = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
new(
|
36
|
+
scheme: scheme,
|
37
|
+
subscheme: subscheme,
|
38
|
+
user: uri&.user,
|
39
|
+
password: uri&.password,
|
40
|
+
host: uri&.host,
|
41
|
+
port: uri&.port,
|
42
|
+
path: uri&.path,
|
43
|
+
query: uri&.query_values,
|
44
|
+
fragment: uri&.fragment,
|
45
|
+
relative: !uri.to_s.index('//').nil? # basic (naive) check for relativity / opaqueness
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(*args)
|
50
|
+
if (component = args.first).is_a?(Hash)
|
51
|
+
@scheme = component[:scheme]
|
52
|
+
@subscheme = component[:subscheme]
|
53
|
+
@user = component[:user]
|
54
|
+
@password = component[:password]
|
55
|
+
@host = component[:host]
|
56
|
+
@port = component[:port]
|
57
|
+
@path = component[:path]
|
58
|
+
@query = component[:query]
|
59
|
+
@fragment = component[:fragment]
|
60
|
+
@relative = component[:relative]
|
61
|
+
elsif args.size > 1
|
62
|
+
warn "DataObjects::URI.new with arguments is deprecated, use a Hash of URI components (#{caller.first})"
|
63
|
+
@scheme, @user, @password, @host, @port, @path, @query, @fragment = *args
|
64
|
+
else
|
65
|
+
raise ArgumentError, "argument should be a Hash of URI components, was: #{args.inspect}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def opaque?
|
70
|
+
!@relative
|
71
|
+
end
|
72
|
+
|
73
|
+
def relative?
|
74
|
+
@relative
|
75
|
+
end
|
76
|
+
|
77
|
+
# Display this URI object as a string
|
78
|
+
def to_s
|
79
|
+
string = ''
|
80
|
+
string << "#{scheme}:" if scheme
|
81
|
+
string << "#{subscheme}:" if subscheme
|
82
|
+
string << '//' if relative?
|
83
|
+
if user
|
84
|
+
string << user.to_s
|
85
|
+
string << '@'
|
86
|
+
end
|
87
|
+
string << host.to_s if host
|
88
|
+
string << ":#{port}" if port
|
89
|
+
string << path.to_s
|
90
|
+
if query
|
91
|
+
string << '?' << query.map do |key, value|
|
92
|
+
"#{key}=#{value}"
|
93
|
+
end.join('&')
|
94
|
+
end
|
95
|
+
string << "##{fragment}" if fragment
|
96
|
+
string
|
97
|
+
end
|
98
|
+
|
99
|
+
# Compare this URI to another for hashing
|
100
|
+
def eql?(other)
|
101
|
+
to_s.eql?(other.to_s)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Hash this URI
|
105
|
+
def hash
|
106
|
+
to_s.hash
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# This is here to remove DataObject's dependency on Extlib.
|
2
|
+
|
3
|
+
module DataObjects
|
4
|
+
# @param name<String> The name of the constant to get, e.g. "Merb::Router".
|
5
|
+
#
|
6
|
+
# @return <Object> The constant corresponding to the name.
|
7
|
+
def self.full_const_get(name)
|
8
|
+
list = name.split('::')
|
9
|
+
list.shift if list.first.nil? || list.first&.strip&.empty?
|
10
|
+
obj = ::Object
|
11
|
+
list.each do |x|
|
12
|
+
# This is required because const_get tries to look for constants in the
|
13
|
+
# ancestor chain, but we only want constants that are HERE
|
14
|
+
obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
|
15
|
+
end
|
16
|
+
obj
|
17
|
+
end
|
18
|
+
end
|
data/lib/data_objects.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'data_objects/version'
|
2
|
+
require 'data_objects/utilities'
|
3
|
+
require 'data_objects/logger'
|
4
|
+
require 'data_objects/byte_array'
|
5
|
+
require 'data_objects/pooling'
|
6
|
+
require 'data_objects/connection'
|
7
|
+
require 'data_objects/uri'
|
8
|
+
require 'data_objects/transaction'
|
9
|
+
require 'data_objects/command'
|
10
|
+
require 'data_objects/result'
|
11
|
+
require 'data_objects/reader'
|
12
|
+
require 'data_objects/quoting'
|
13
|
+
require 'data_objects/extension'
|
14
|
+
require 'data_objects/error'
|
15
|
+
require 'data_objects/error/sql_error'
|
16
|
+
require 'data_objects/error/connection_error'
|
17
|
+
require 'data_objects/error/data_error'
|
18
|
+
require 'data_objects/error/integrity_error'
|
19
|
+
require 'data_objects/error/syntax_error'
|
20
|
+
require 'data_objects/error/transaction_error'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Command do
|
4
|
+
before do
|
5
|
+
@connection = DataObjects::Connection.new('mock://localhost')
|
6
|
+
@command = DataObjects::Command.new(@connection, 'SQL STRING')
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
@connection.close
|
11
|
+
end
|
12
|
+
|
13
|
+
%w(connection execute_non_query execute_reader set_types).each do |meth|
|
14
|
+
it "responds to ##{meth}" do
|
15
|
+
expect(@command).to respond_to(meth.intern)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
%w(execute_non_query execute_reader set_types).each do |meth|
|
20
|
+
it "raises NotImplementedError on ##{meth}" do
|
21
|
+
expect { @command.send(meth.intern, nil) }.to raise_error(NotImplementedError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe DataObjects::Connection do
|
5
|
+
subject { connection }
|
6
|
+
|
7
|
+
let(:connection) { described_class.new(uri) }
|
8
|
+
|
9
|
+
after { connection.close }
|
10
|
+
|
11
|
+
context 'defines a standard API' do
|
12
|
+
let(:uri) { 'mock://localhost' }
|
13
|
+
|
14
|
+
it { is_expected.to respond_to(:dispose) }
|
15
|
+
it { is_expected.to respond_to(:create_command) }
|
16
|
+
|
17
|
+
its(:to_s) { is_expected.to eq 'mock://localhost' }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'initialization' do
|
21
|
+
context 'with a connection uri as a Addressable::URI' do
|
22
|
+
let(:uri) { Addressable::URI.parse('mock://localhost/database') }
|
23
|
+
|
24
|
+
it { is_expected.to be_kind_of(DataObjects::Mock::Connection) }
|
25
|
+
it { is_expected.to be_kind_of(DataObjects::Pooling) }
|
26
|
+
|
27
|
+
its(:to_s) { is_expected.to eq 'mock://localhost/database' }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/spec/do_mock.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module DataObjects
|
2
|
+
module Mock
|
3
|
+
class Connection < DataObjects::Connection
|
4
|
+
def initialize(uri)
|
5
|
+
@uri = uri
|
6
|
+
end
|
7
|
+
|
8
|
+
def dispose
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Command < DataObjects::Command
|
14
|
+
def execute_non_query(*_args)
|
15
|
+
Result.new(self, 0, nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute_reader(*_args)
|
19
|
+
Reader.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Result < DataObjects::Result
|
24
|
+
end
|
25
|
+
|
26
|
+
class Reader < DataObjects::Reader
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/do_mock2.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module DataObjects
|
2
|
+
module Mock2
|
3
|
+
class Connection < DataObjects::Connection
|
4
|
+
def initialize(uri)
|
5
|
+
@uri = uri
|
6
|
+
end
|
7
|
+
|
8
|
+
def dispose
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Command < DataObjects::Command
|
14
|
+
def execute_non_query(*_args)
|
15
|
+
Result.new(self, 0, nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute_reader(*_args)
|
19
|
+
Reader.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Result < DataObjects::Result
|
24
|
+
end
|
25
|
+
|
26
|
+
class Reader < DataObjects::Reader
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
describe 'DataObjects::Pooling' do
|
5
|
+
before do
|
6
|
+
Object.send(:remove_const, :Person) if defined?(Person)
|
7
|
+
|
8
|
+
class ::Person
|
9
|
+
include DataObjects::Pooling
|
10
|
+
|
11
|
+
attr_accessor :name
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def dispose
|
18
|
+
@name = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Object.send(:remove_const, :Overwriter) if defined?(Overwriter)
|
23
|
+
class ::Overwriter
|
24
|
+
def self.new(*args)
|
25
|
+
instance = allocate
|
26
|
+
instance.send(:initialize, *args)
|
27
|
+
instance.overwritten = true
|
28
|
+
instance
|
29
|
+
end
|
30
|
+
|
31
|
+
include DataObjects::Pooling
|
32
|
+
|
33
|
+
attr_accessor :name
|
34
|
+
|
35
|
+
def initialize(name)
|
36
|
+
@name = name
|
37
|
+
@overwritten = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def overwritten?
|
41
|
+
@overwritten
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_writer :overwritten
|
45
|
+
|
46
|
+
class << self
|
47
|
+
remove_method :pool_size if instance_methods(false).any? { |m| m.to_sym == :pool_size }
|
48
|
+
def pool_size
|
49
|
+
2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def dispose
|
54
|
+
@name = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
after :each do
|
60
|
+
DataObjects::Pooling.lock.synchronize do
|
61
|
+
DataObjects::Pooling.pools.each do |pool|
|
62
|
+
pool.lock.synchronize do
|
63
|
+
pool.dispose
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'maintains a size of 1' do
|
70
|
+
bob = Person.new('Bob')
|
71
|
+
fred = Person.new('Fred')
|
72
|
+
ted = Person.new('Ted')
|
73
|
+
|
74
|
+
Person.__pools.each_value do |pool|
|
75
|
+
expect(pool.size).to eq 1
|
76
|
+
end
|
77
|
+
|
78
|
+
bob.release
|
79
|
+
fred.release
|
80
|
+
ted.release
|
81
|
+
|
82
|
+
Person.__pools.each_value do |pool|
|
83
|
+
expect(pool.size).to eq 1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'tracks the initialized pools' do
|
88
|
+
bob = Person.new('Bob') # Ensure the pool is "primed"
|
89
|
+
expect(bob.name).to eq 'Bob'
|
90
|
+
expect(bob.instance_variable_get(:@__pool)).not_to be_nil
|
91
|
+
expect(Person.__pools.size).to eq 1
|
92
|
+
bob.release
|
93
|
+
expect(Person.__pools.size).to eq 1
|
94
|
+
|
95
|
+
expect(DataObjects::Pooling.pools).not_to be_empty
|
96
|
+
|
97
|
+
sleep(1.2)
|
98
|
+
|
99
|
+
# NOTE: This assertion is commented out, as our MockConnection objects are
|
100
|
+
# currently in the pool.
|
101
|
+
# expect(DataObjects::Pooling::pools).to be_empty
|
102
|
+
expect(bob.name).to be_nil
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'allows you to overwrite Class#new' do
|
106
|
+
bob = Overwriter.new('Bob')
|
107
|
+
expect(bob).to be_overwritten
|
108
|
+
bob.release
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'allows multiple threads to access the pool' do
|
112
|
+
t1 = Thread.new do
|
113
|
+
bob = Person.new('Bob')
|
114
|
+
sleep(1)
|
115
|
+
bob.release
|
116
|
+
end
|
117
|
+
|
118
|
+
expect do
|
119
|
+
bob = Person.new('Bob')
|
120
|
+
t1.join
|
121
|
+
bob.release
|
122
|
+
end.not_to raise_error
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'allows you to flush a pool' do
|
126
|
+
bob = Overwriter.new('Bob')
|
127
|
+
Overwriter.new('Bob').release
|
128
|
+
bob.release
|
129
|
+
|
130
|
+
expect(bob.name).to eq 'Bob'
|
131
|
+
|
132
|
+
expect(Overwriter.__pools[['Bob']].size).to eq 2
|
133
|
+
Overwriter.__pools[['Bob']].flush!
|
134
|
+
expect(Overwriter.__pools[['Bob']].size).to eq 0
|
135
|
+
|
136
|
+
expect(bob.name).to be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'wakes up the scavenger thread when exiting' do
|
140
|
+
bob = Person.new('Bob')
|
141
|
+
bob.release
|
142
|
+
DataObjects.exiting = true
|
143
|
+
sleep(1)
|
144
|
+
expect(DataObjects::Pooling.scavenger?).to be false
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'detaches an instance from the pool' do
|
148
|
+
bob = Person.new('Bob')
|
149
|
+
expect(Person.__pools[['Bob']].size).to eq 1
|
150
|
+
bob.detach
|
151
|
+
expect(Person.__pools[['Bob']].size).to eq 0
|
152
|
+
end
|
153
|
+
end
|
data/spec/reader_spec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Reader do
|
4
|
+
subject { command.execute_reader }
|
5
|
+
|
6
|
+
let(:connection) { DataObjects::Connection.new('mock://localhost') }
|
7
|
+
let(:command) { connection.create_command('SELECT * FROM example') }
|
8
|
+
|
9
|
+
after { connection.close }
|
10
|
+
|
11
|
+
context 'defines a standard API' do
|
12
|
+
it { is_expected.to be_a(Enumerable) }
|
13
|
+
it { is_expected.to respond_to(:close) }
|
14
|
+
it { is_expected.to respond_to(:next!) }
|
15
|
+
it { is_expected.to respond_to(:values) }
|
16
|
+
it { is_expected.to respond_to(:fields) }
|
17
|
+
it { is_expected.to respond_to(:each) }
|
18
|
+
end
|
19
|
+
end
|
data/spec/result_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Result do
|
4
|
+
subject { command.execute_non_query }
|
5
|
+
|
6
|
+
let(:connection) { DataObjects::Connection.new('mock://localhost') }
|
7
|
+
let(:command) { connection.create_command('SELECT * FROM example') }
|
8
|
+
|
9
|
+
after { connection.close }
|
10
|
+
|
11
|
+
context 'defines a standard API' do
|
12
|
+
it 'provides the number of affected rows' do
|
13
|
+
is_expected.to respond_to(:to_i)
|
14
|
+
expect(subject.to_i).to eq 0
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'provides the id of the inserted row' do
|
18
|
+
is_expected.to respond_to(:insert_id)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'data_objects'
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/its'
|
5
|
+
|
6
|
+
module DataObjects
|
7
|
+
module Pooling
|
8
|
+
class << self
|
9
|
+
remove_method :scavenger_interval if instance_methods(false).any? { |m| m.to_sym == :scavenger_interval }
|
10
|
+
def scavenger_interval
|
11
|
+
0.5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'do_mock'))
|
18
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'do_mock2'))
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Transaction do
|
4
|
+
before :each do
|
5
|
+
@connection = double('connection')
|
6
|
+
expect(DataObjects::Connection).to receive(:new).with('mock://mock/mock').once.and_return(@connection)
|
7
|
+
@transaction = DataObjects::Transaction.new('mock://mock/mock')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'has a HOST constant' do
|
11
|
+
expect(DataObjects::Transaction::HOST).not_to eq nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#initialize' do
|
15
|
+
it 'provides a connection' do
|
16
|
+
expect(@transaction.connection).to eq @connection
|
17
|
+
end
|
18
|
+
it 'provides an id' do
|
19
|
+
expect(@transaction.id).not_to be_nil
|
20
|
+
end
|
21
|
+
it 'provides a unique id' do
|
22
|
+
expect(DataObjects::Connection).to receive(:new).with('mock://mock/mock2').once.and_return(@connection)
|
23
|
+
expect(@transaction.id).not_to eq DataObjects::Transaction.new('mock://mock/mock2').id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
describe '#close' do
|
27
|
+
it 'closes its connection' do
|
28
|
+
expect(@connection).to receive(:close).once
|
29
|
+
expect { @transaction.close }.not_to raise_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
%i(prepare commit_prepared rollback_prepared).each do |meth|
|
33
|
+
it "raises NotImplementedError on #{meth}" do
|
34
|
+
expect { @transaction.send(meth) }.to raise_error(NotImplementedError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/uri_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::URI do
|
4
|
+
subject { described_class.parse(uri) }
|
5
|
+
|
6
|
+
context 'parsing parts' do
|
7
|
+
let(:uri) { 'mock://username:password@localhost:12345/path?encoding=utf8#fragment' }
|
8
|
+
|
9
|
+
its(:scheme) { is_expected.to eq 'mock' }
|
10
|
+
its(:user) { is_expected.to eq 'username' }
|
11
|
+
its(:password) { is_expected.to eq 'password' }
|
12
|
+
its(:host) { is_expected.to eq 'localhost' }
|
13
|
+
its(:port) { is_expected.to eq 12_345 }
|
14
|
+
its(:path) { is_expected.to eq '/path' }
|
15
|
+
its(:query) { is_expected.to eq({'encoding' => 'utf8'}) }
|
16
|
+
its(:fragment) { is_expected.to eq 'fragment' }
|
17
|
+
|
18
|
+
it 'provides a correct string representation' do
|
19
|
+
expect(subject.to_s).to eq 'mock://username@localhost:12345/path?encoding=utf8#fragment'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/tasks/release.rake
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
desc 'Builds all gems (native, binaries for JRuby and Windows)'
|
2
|
+
task :build_all do
|
3
|
+
`rake clean`
|
4
|
+
`rake build`
|
5
|
+
end
|
6
|
+
|
7
|
+
desc 'Release all gems (native, binaries for JRuby and Windows)'
|
8
|
+
task release_all: :build_all do
|
9
|
+
Dir.children("pkg").each do |gem_path|
|
10
|
+
command = "gem push pkg/#{gem_path}"
|
11
|
+
puts "Executing #{command.inspect}:"
|
12
|
+
sh command
|
13
|
+
end
|
14
|
+
end
|
data/tasks/spec.rake
ADDED