data_objects 0.9.11 → 0.9.12
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/Manifest.txt +19 -1
- data/Rakefile +6 -80
- data/lib/data_objects.rb +1 -6
- data/lib/data_objects/command.rb +51 -1
- data/lib/data_objects/connection.rb +13 -2
- data/lib/data_objects/logger.rb +40 -32
- data/lib/data_objects/quoting.rb +28 -32
- data/lib/data_objects/reader.rb +6 -5
- data/lib/data_objects/result.rb +7 -1
- data/lib/data_objects/spec/command_spec.rb +191 -0
- data/lib/data_objects/spec/connection_spec.rb +106 -0
- data/lib/data_objects/spec/encoding_spec.rb +31 -0
- data/lib/data_objects/spec/quoting_spec.rb +0 -0
- data/lib/data_objects/spec/reader_spec.rb +156 -0
- data/lib/data_objects/spec/result_spec.rb +58 -0
- data/lib/data_objects/spec/typecast/array_spec.rb +36 -0
- data/lib/data_objects/spec/typecast/bigdecimal_spec.rb +107 -0
- data/lib/data_objects/spec/typecast/boolean_spec.rb +107 -0
- data/lib/data_objects/spec/typecast/byte_array_spec.rb +86 -0
- data/lib/data_objects/spec/typecast/class_spec.rb +63 -0
- data/lib/data_objects/spec/typecast/date_spec.rb +108 -0
- data/lib/data_objects/spec/typecast/datetime_spec.rb +110 -0
- data/lib/data_objects/spec/typecast/float_spec.rb +111 -0
- data/lib/data_objects/spec/typecast/integer_spec.rb +86 -0
- data/lib/data_objects/spec/typecast/ipaddr_spec.rb +0 -0
- data/lib/data_objects/spec/typecast/nil_spec.rb +116 -0
- data/lib/data_objects/spec/typecast/range_spec.rb +36 -0
- data/lib/data_objects/spec/typecast/string_spec.rb +86 -0
- data/lib/data_objects/spec/typecast/time_spec.rb +64 -0
- data/lib/data_objects/transaction.rb +20 -13
- data/lib/data_objects/uri.rb +24 -2
- data/lib/data_objects/version.rb +2 -1
- data/spec/command_spec.rb +1 -17
- data/spec/connection_spec.rb +1 -23
- data/spec/lib/pending_helpers.rb +11 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/result_spec.rb +0 -3
- data/tasks/gem.rake +49 -0
- data/tasks/install.rake +13 -0
- data/tasks/release.rake +74 -0
- data/tasks/spec.rake +18 -0
- metadata +51 -30
- data/.gitignore +0 -2
- data/spec/dataobjects_spec.rb +0 -1
- data/spec/spec.opts +0 -2
data/lib/data_objects/reader.rb
CHANGED
@@ -1,30 +1,31 @@
|
|
1
1
|
module DataObjects
|
2
|
+
# Abstract class to read rows from a query result
|
2
3
|
class Reader
|
3
4
|
|
5
|
+
# Return the array of field names
|
4
6
|
def fields
|
5
7
|
raise NotImplementedError.new
|
6
8
|
end
|
7
9
|
|
10
|
+
# Return the array of field values for the current row. Not legal after next! has returned false or before it's been called
|
8
11
|
def values
|
9
12
|
raise NotImplementedError.new
|
10
13
|
end
|
11
14
|
|
15
|
+
# Close the reader discarding any unread results.
|
12
16
|
def close
|
13
17
|
raise NotImplementedError.new
|
14
18
|
end
|
15
19
|
|
16
|
-
#
|
20
|
+
# Discard the current row (if any) and read the next one (returning true), or return nil if there is no further row.
|
17
21
|
def next!
|
18
22
|
raise NotImplementedError.new
|
19
23
|
end
|
20
24
|
|
25
|
+
# Return the number of fields in the result set.
|
21
26
|
def field_count
|
22
27
|
raise NotImplementedError.new
|
23
28
|
end
|
24
29
|
|
25
|
-
def row_count
|
26
|
-
raise NotImplementedError.new
|
27
|
-
end
|
28
|
-
|
29
30
|
end
|
30
31
|
end
|
data/lib/data_objects/result.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
module DataObjects
|
2
|
+
# The Result class is returned from Connection#execute_non_query.
|
2
3
|
class Result
|
3
|
-
|
4
|
+
# The ID of a row inserted by the Command
|
5
|
+
attr_accessor :insert_id
|
6
|
+
# The number of rows affected by the Command
|
7
|
+
attr_accessor :affected_rows
|
4
8
|
|
9
|
+
# Create a new Result. Used internally in the adapters.
|
5
10
|
def initialize(command, affected_rows, insert_id = nil)
|
6
11
|
@command, @affected_rows, @insert_id = command, affected_rows, insert_id
|
7
12
|
end
|
8
13
|
|
14
|
+
# Return the number of affected rows
|
9
15
|
def to_i
|
10
16
|
@affected_rows
|
11
17
|
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
WINDOWS = Gem.win_platform?
|
2
|
+
|
3
|
+
share_examples_for 'a Command' do
|
4
|
+
|
5
|
+
include DataObjectsSpecHelpers
|
6
|
+
|
7
|
+
before :all do
|
8
|
+
setup_test_environment
|
9
|
+
end
|
10
|
+
|
11
|
+
before :each do
|
12
|
+
@connection = DataObjects::Connection.new(CONFIG.uri)
|
13
|
+
@command = @connection.create_command("INSERT INTO users (name) VALUES (?)")
|
14
|
+
@reader = @connection.create_command("SELECT code, name FROM widgets WHERE ad_description = ?")
|
15
|
+
end
|
16
|
+
|
17
|
+
after :each do
|
18
|
+
@connection.close
|
19
|
+
end
|
20
|
+
|
21
|
+
it { @command.should be_kind_of(DataObjects::Command) }
|
22
|
+
|
23
|
+
it { @command.should respond_to(:execute_non_query) }
|
24
|
+
|
25
|
+
describe 'execute_non_query' do
|
26
|
+
|
27
|
+
describe 'with an invalid statement' do
|
28
|
+
|
29
|
+
before :each do
|
30
|
+
@invalid_command = @connection.create_command("INSERT INTO non_existent_table (tester) VALUES (1)")
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should raise an error on an invalid query' do
|
34
|
+
lambda { @invalid_command.execute_non_query }.should raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should raise an error with too few binding parameters' do
|
38
|
+
lambda { @command.execute_non_query("Too", "Many") }.should raise_error(ArgumentError, "Binding mismatch: 2 for 1")
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should raise an error with too many binding parameters' do
|
42
|
+
lambda { @command.execute_non_query }.should raise_error(ArgumentError, "Binding mismatch: 0 for 1")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'with a valid statement' do
|
48
|
+
|
49
|
+
it 'should not raise an error with an explicit nil as parameter' do
|
50
|
+
lambda { @command.execute_non_query(nil) }.should_not raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
it { @command.should respond_to(:execute_reader) }
|
58
|
+
|
59
|
+
describe 'execute_reader' do
|
60
|
+
|
61
|
+
describe 'with an invalid reader' do
|
62
|
+
|
63
|
+
before :each do
|
64
|
+
@invalid_reader = @connection.create_command("SELECT * FROM non_existent_widgets WHERE ad_description = ?")
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should raise an error on an invalid query' do
|
68
|
+
lambda { @invalid_reader.execute_reader }.should raise_error
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should raise an error with too few binding parameters' do
|
72
|
+
lambda { @reader.execute_reader("Too", "Many") }.should raise_error(ArgumentError, "Binding mismatch: 2 for 1")
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should raise an error with too many binding parameters' do
|
76
|
+
lambda { @reader.execute_reader }.should raise_error(ArgumentError, "Binding mismatch: 0 for 1")
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'with a valid reader' do
|
82
|
+
|
83
|
+
it 'should not raise an error with an explicit nil as parameter' do
|
84
|
+
lambda { @reader.execute_reader(nil) }.should_not raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
it { @command.should respond_to(:set_types) }
|
92
|
+
|
93
|
+
describe 'set_types' do
|
94
|
+
|
95
|
+
describe 'is invalid when used with a statement' do
|
96
|
+
|
97
|
+
before :each do
|
98
|
+
@command.set_types(String)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should raise an error when types are set' do
|
102
|
+
lambda { @command.execute_non_query }.should raise_error
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'with an invalid reader' do
|
108
|
+
|
109
|
+
it 'should raise an error with too few types' do
|
110
|
+
@reader.set_types(String)
|
111
|
+
lambda { @reader.execute_reader("One parameter") }.should raise_error(ArgumentError, "Field-count mismatch. Expected 1 fields, but the query yielded 2")
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should raise an error with too many types' do
|
115
|
+
@reader.set_types(String, String, BigDecimal)
|
116
|
+
lambda { @reader.execute_reader("One parameter") }.should raise_error(ArgumentError, "Field-count mismatch. Expected 3 fields, but the query yielded 2")
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'with a valid reader' do
|
122
|
+
|
123
|
+
it 'should not raise an error with correct number of types' do
|
124
|
+
@reader.set_types(String, String)
|
125
|
+
lambda { @result = @reader.execute_reader('Buy this product now!') }.should_not raise_error
|
126
|
+
lambda { @result.next! }.should_not raise_error
|
127
|
+
lambda { @result.values }.should_not raise_error
|
128
|
+
@result.close
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should also support old style array argument types' do
|
132
|
+
@reader.set_types([String, String])
|
133
|
+
lambda { @result = @reader.execute_reader('Buy this product now!') }.should_not raise_error
|
134
|
+
lambda { @result.next! }.should_not raise_error
|
135
|
+
lambda { @result.values }.should_not raise_error
|
136
|
+
@result.close
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
it { @command.should respond_to(:to_s) }
|
144
|
+
|
145
|
+
describe 'to_s' do
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
share_examples_for 'a Command with async' do
|
153
|
+
|
154
|
+
include DataObjectsSpecHelpers
|
155
|
+
|
156
|
+
before :all do
|
157
|
+
setup_test_environment
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'running queries in parallel' do
|
161
|
+
|
162
|
+
before :each do
|
163
|
+
|
164
|
+
threads = []
|
165
|
+
|
166
|
+
@start = Time.now
|
167
|
+
4.times do |i|
|
168
|
+
threads << Thread.new do
|
169
|
+
connection = DataObjects::Connection.new(CONFIG.uri)
|
170
|
+
command = connection.create_command(CONFIG.sleep)
|
171
|
+
result = command.execute_non_query
|
172
|
+
connection.close
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
threads.each{|t| t.join }
|
177
|
+
@finish = Time.now
|
178
|
+
end
|
179
|
+
|
180
|
+
after :each do
|
181
|
+
@connection.close
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should finish within 2 seconds" do
|
185
|
+
pending_if("Ruby on Windows doesn't support asynchronious operations", WINDOWS) do
|
186
|
+
(@finish - @start).should < 2
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
share_examples_for 'a Connection' do
|
2
|
+
|
3
|
+
include DataObjectsSpecHelpers
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
setup_test_environment
|
7
|
+
end
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
@connection = DataObjects::Connection.new(CONFIG.uri)
|
11
|
+
end
|
12
|
+
|
13
|
+
after :each do
|
14
|
+
@connection.close
|
15
|
+
end
|
16
|
+
|
17
|
+
it { @connection.should be_kind_of(DataObjects::Connection) }
|
18
|
+
it { @connection.should be_kind_of(Extlib::Pooling) }
|
19
|
+
|
20
|
+
it { @connection.should respond_to(:dispose) }
|
21
|
+
|
22
|
+
describe 'dispose' do
|
23
|
+
|
24
|
+
describe 'on open connection' do
|
25
|
+
before do
|
26
|
+
@open_connection = DataObjects::Connection.new("#{@driver}://#{@user}:#{@password}@#{@host}:#{@port}#{@database}")
|
27
|
+
@open_connection.detach
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
@open_connection.close
|
32
|
+
end
|
33
|
+
|
34
|
+
it { @open_connection.dispose.should be_true }
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'on closed connection' do
|
38
|
+
before do
|
39
|
+
@closed_connection = DataObjects::Connection.new("#{@driver}://#{@user}:#{@password}@#{@host}:#{@port}#{@database}")
|
40
|
+
@closed_connection.detach
|
41
|
+
@closed_connection.dispose
|
42
|
+
end
|
43
|
+
|
44
|
+
after do
|
45
|
+
@closed_connection.close
|
46
|
+
end
|
47
|
+
|
48
|
+
it { @closed_connection.dispose.should be_false }
|
49
|
+
|
50
|
+
it 'should raise an error on creating a command' do
|
51
|
+
lambda { @closed_connection.create_command("INSERT INTO non_existant_table (tester) VALUES (1)").execute_non_query }.should raise_error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
it { @connection.should respond_to(:create_command) }
|
58
|
+
|
59
|
+
describe 'create_command' do
|
60
|
+
it { @connection.create_command('This is a dummy command').should be_kind_of(DataObjects::Command) }
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
share_examples_for 'a Connection with authentication support' do
|
66
|
+
|
67
|
+
before :all do
|
68
|
+
%w[ @driver @user @password @host @port @database ].each do |ivar|
|
69
|
+
raise "+#{ivar}+ should be defined in before block" unless instance_variable_get(ivar)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'with an invalid URI' do
|
75
|
+
|
76
|
+
def connecting_with(uri)
|
77
|
+
lambda { DataObjects::Connection.new(uri) }
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should raise an error if no database specified' do
|
81
|
+
connecting_with("#{@driver}://#{@user}:#{@password}@#{@host}:#{@port}").should raise_error
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should raise an error if bad username is given' do
|
85
|
+
connecting_with("#{@driver}://thisreallyshouldntexist:#{@password}@#{@host}:#{@port}#{@database}").should raise_error
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should raise an error if bad password is given' do
|
89
|
+
connecting_with("#{@driver}://#{@user}:completelyincorrectpassword:#{@host}:#{@port}#{@database}").should raise_error
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should raise an error if an invalid port is given' do
|
93
|
+
connecting_with("#{@driver}://#{@user}:#{@password}:#{@host}:648646543#{@database}").should raise_error
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should raise an error if an invalid database is given' do
|
97
|
+
connecting_with("#{@driver}://#{@user}:#{@password}:#{@host}:#{@port}/someweirddatabase").should raise_error
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should raise an error with a meaningless URI' do
|
101
|
+
connecting_with("#{@driver}://peekaboo$2!@#4543").should raise_error
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
share_examples_for 'a driver supporting encodings' do
|
2
|
+
|
3
|
+
before :each do
|
4
|
+
@connection = DataObjects::Connection.new(CONFIG.uri)
|
5
|
+
end
|
6
|
+
|
7
|
+
after :each do
|
8
|
+
@connection.close
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
it { @connection.should respond_to(:character_set) }
|
13
|
+
|
14
|
+
describe 'character_set' do
|
15
|
+
|
16
|
+
it 'uses utf8 by default' do
|
17
|
+
@connection.character_set.should == 'utf8'
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'sets the character set through the URI' do
|
21
|
+
before do
|
22
|
+
@latin1_connection = DataObjects::Connection.new("#{CONFIG.scheme}://#{CONFIG.user}:#{CONFIG.pass}@#{CONFIG.host}:#{CONFIG.port}#{CONFIG.database}?encoding=latin1")
|
23
|
+
end
|
24
|
+
|
25
|
+
after { @latin1_connection.close }
|
26
|
+
|
27
|
+
it { @latin1_connection.character_set.should == 'latin1' }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
File without changes
|
@@ -0,0 +1,156 @@
|
|
1
|
+
share_examples_for 'a Reader' do
|
2
|
+
|
3
|
+
include DataObjectsSpecHelpers
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
setup_test_environment
|
7
|
+
end
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
@connection = DataObjects::Connection.new(CONFIG.uri)
|
11
|
+
@reader = @connection.create_command("SELECT code, name FROM widgets WHERE ad_description = ? order by id").execute_reader('Buy this product now!')
|
12
|
+
end
|
13
|
+
|
14
|
+
after :each do
|
15
|
+
@reader.close
|
16
|
+
@connection.close
|
17
|
+
end
|
18
|
+
|
19
|
+
it { @reader.should respond_to(:fields) }
|
20
|
+
|
21
|
+
describe 'fields' do
|
22
|
+
|
23
|
+
it 'should return the correct fields in the reader' do
|
24
|
+
# we downcase the field names as some drivers such as do_derby, do_h2,
|
25
|
+
# do_hsqldb return the field names as uppercase
|
26
|
+
@reader.fields.map{ |f| f.downcase }.should == ['code', 'name']
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
it { @reader.should respond_to(:values) }
|
32
|
+
|
33
|
+
describe 'values' do
|
34
|
+
|
35
|
+
describe 'when the reader is uninitialized' do
|
36
|
+
|
37
|
+
it 'should raise an error' do
|
38
|
+
lambda { @reader.values }.should raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'when the reader is moved to the first result' do
|
44
|
+
|
45
|
+
before do
|
46
|
+
@reader.next!
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should return the correct first set of in the reader' do
|
50
|
+
@reader.values.should == ["W0000001", "Widget 1"]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'when the reader is moved to the second result' do
|
56
|
+
|
57
|
+
before do
|
58
|
+
@reader.next!; @reader.next!
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should return the correct first set of in the reader' do
|
62
|
+
@reader.values.should == ["W0000002", "Widget 2"]
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'when the reader is moved to the end' do
|
68
|
+
|
69
|
+
before do
|
70
|
+
while @reader.next! ; end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should raise an error again' do
|
74
|
+
lambda { @reader.values }.should raise_error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
it { @reader.should respond_to(:close) }
|
81
|
+
|
82
|
+
describe 'close' do
|
83
|
+
|
84
|
+
describe 'on an open reader' do
|
85
|
+
|
86
|
+
it 'should return true' do
|
87
|
+
@reader.close.should be_true
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'on an already closed reader' do
|
93
|
+
|
94
|
+
before do
|
95
|
+
@reader.close
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should return false' do
|
99
|
+
@reader.close.should be_false
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
it { @reader.should respond_to(:next!) }
|
107
|
+
|
108
|
+
describe 'next!' do
|
109
|
+
|
110
|
+
describe 'successfully moving the cursor initially' do
|
111
|
+
|
112
|
+
it 'should return true' do
|
113
|
+
@reader.next!.should be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'moving the cursor' do
|
119
|
+
|
120
|
+
before do
|
121
|
+
@reader.next!
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should move the cursor to the next value' do
|
125
|
+
lambda { @reader.next! }.should change(@reader, :values).
|
126
|
+
from(["W0000001", "Widget 1"]).
|
127
|
+
to(["W0000002", "Widget 2"])
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'arriving at the end of the reader' do
|
133
|
+
|
134
|
+
before do
|
135
|
+
while @reader.next!; end
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should return false when the end is reached' do
|
139
|
+
@reader.next!.should be_false
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
it { @reader.should respond_to(:field_count) }
|
147
|
+
|
148
|
+
describe 'field_count' do
|
149
|
+
|
150
|
+
it 'should count the number of fields' do
|
151
|
+
@reader.field_count.should == 2
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|