tiny_tds 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/CHANGELOG +5 -0
- data/Gemfile +18 -0
- data/NOTES +14 -0
- data/README.rdoc +10 -5
- data/Rakefile +57 -0
- data/ext/tiny_tds/extconf.rb +8 -3
- data/lib/tiny_tds.rb +8 -7
- data/lib/tiny_tds/client.rb +8 -1
- data/lib/tiny_tds/version.rb +3 -0
- data/tasks/ports.rake +59 -0
- data/test/benchmark/query.rb +77 -0
- data/test/benchmark/query_odbc.rb +106 -0
- data/test/benchmark/query_tinytds.rb +111 -0
- data/test/client_test.rb +163 -0
- data/test/result_test.rb +437 -0
- data/test/schema/1px.gif +0 -0
- data/test/schema/sqlserver_2000.sql +138 -0
- data/test/schema/sqlserver_2005.sql +138 -0
- data/test/schema/sqlserver_2008.sql +138 -0
- data/test/schema/sqlserver_azure.sql +138 -0
- data/test/schema_test.rb +284 -0
- data/test/test_helper.rb +199 -0
- metadata +39 -9
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bench_press'
|
3
|
+
$:.unshift File.expand_path('../../../lib',__FILE__)
|
4
|
+
require 'tiny_tds'
|
5
|
+
|
6
|
+
extend BenchPress
|
7
|
+
|
8
|
+
author 'Ken Collins'
|
9
|
+
summary 'Benchmark TinyTds Querys'
|
10
|
+
|
11
|
+
reps 1_000
|
12
|
+
|
13
|
+
@client = TinyTds::Client.new({
|
14
|
+
:dataserver => ENV['TINYTDS_UNIT_DATASERVER'],
|
15
|
+
:username => 'tinytds',
|
16
|
+
:password => '',
|
17
|
+
:database => 'tinytdstest',
|
18
|
+
:appname => 'TinyTds Dev',
|
19
|
+
:login_timeout => 5,
|
20
|
+
:timeout => 5
|
21
|
+
})
|
22
|
+
|
23
|
+
@query_nothing = "SELECT NULL AS [null]"
|
24
|
+
@query_ints = "SELECT [int], [bigint], [smallint], [tinyint] FROM [datatypes]"
|
25
|
+
@query_binaries = "SELECT [binary_50], [image], [varbinary_50] FROM [datatypes]"
|
26
|
+
@query_bits = "SELECT [bit] FROM [datatypes]"
|
27
|
+
@query_chars = "SELECT [char_10], [nchar_10], [ntext], [nvarchar_50], [text], [varchar_50] FROM [datatypes]"
|
28
|
+
@query_dates = "SELECT [datetime], [smalldatetime] FROM [datatypes]"
|
29
|
+
@query_decimals = "SELECT [decimal_9_2], [decimal_16_4], [numeric_18_0], [numeric_36_2] FROM [datatypes]"
|
30
|
+
@query_floats = "SELECT [float], [real] FROM [datatypes]"
|
31
|
+
@query_moneys = "SELECT [money], [smallmoney] FROM [datatypes]"
|
32
|
+
@query_guids = "SELECT [uniqueidentifier] FROM [datatypes]"
|
33
|
+
@query_all = "SELECT * FROM [datatypes]"
|
34
|
+
|
35
|
+
def select_all(query)
|
36
|
+
@client.execute(query).each
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
measure "Nothing" do
|
41
|
+
select_all @query_nothing
|
42
|
+
end
|
43
|
+
|
44
|
+
measure "Integers" do
|
45
|
+
select_all @query_ints
|
46
|
+
end
|
47
|
+
|
48
|
+
measure "Binaries" do
|
49
|
+
select_all @query_binaries
|
50
|
+
end
|
51
|
+
|
52
|
+
measure "Bits" do
|
53
|
+
select_all @query_bits
|
54
|
+
end
|
55
|
+
|
56
|
+
measure "Chars" do
|
57
|
+
select_all @query_chars
|
58
|
+
end
|
59
|
+
|
60
|
+
measure "Dates" do
|
61
|
+
select_all @query_dates
|
62
|
+
end
|
63
|
+
|
64
|
+
measure "Decimals" do
|
65
|
+
select_all @query_decimals
|
66
|
+
end
|
67
|
+
|
68
|
+
measure "Floats" do
|
69
|
+
select_all @query_floats
|
70
|
+
end
|
71
|
+
|
72
|
+
measure "Moneys" do
|
73
|
+
select_all @query_moneys
|
74
|
+
end
|
75
|
+
|
76
|
+
measure "Guids" do
|
77
|
+
select_all @query_guids
|
78
|
+
end
|
79
|
+
|
80
|
+
measure "All" do
|
81
|
+
select_all @query_all
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
=begin
|
86
|
+
|
87
|
+
System Information
|
88
|
+
------------------
|
89
|
+
Operating System: Mac OS X 10.6.4 (10F569)
|
90
|
+
CPU: Intel Core 2 Duo 2.4 GHz
|
91
|
+
Processor Count: 2
|
92
|
+
Memory: 4 GB
|
93
|
+
ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]
|
94
|
+
|
95
|
+
"Nothing" is up to 89% faster over 1,000 repetitions
|
96
|
+
----------------------------------------------------
|
97
|
+
|
98
|
+
Nothing 0.357741832733154 secs Fastest
|
99
|
+
Guids 0.460683107376099 secs 22% Slower
|
100
|
+
Bits 0.483309984207153 secs 25% Slower
|
101
|
+
Floats 0.505340099334717 secs 29% Slower
|
102
|
+
Moneys 0.523844003677368 secs 31% Slower
|
103
|
+
Integers 0.616975069046021 secs 42% Slower
|
104
|
+
Binaries 0.639773845672607 secs 44% Slower
|
105
|
+
Decimals 0.670897960662842 secs 46% Slower
|
106
|
+
Chars 0.800287008285522 secs 55% Slower
|
107
|
+
Dates 0.950634956359863 secs 62% Slower
|
108
|
+
All 2.91044211387634 secs 87% Slower
|
109
|
+
|
110
|
+
=end
|
111
|
+
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ClientTest < TinyTds::TestCase
|
4
|
+
|
5
|
+
context 'With valid credentials' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@client = new_connection
|
9
|
+
end
|
10
|
+
|
11
|
+
should 'not be closed' do
|
12
|
+
assert !@client.closed?
|
13
|
+
assert @client.active?
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'allow client connection to be closed' do
|
17
|
+
assert @client.close
|
18
|
+
assert @client.closed?
|
19
|
+
assert !@client.active?
|
20
|
+
action = lambda { @client.execute('SELECT 1 as [one]').each }
|
21
|
+
assert_raise_tinytds_error(action) do |e|
|
22
|
+
assert_match %r{closed connection}i, e.message, 'ignore if non-english test run'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'have a getters for the tds version information (brittle since conf takes precedence)' do
|
27
|
+
assert_equal 9, @client.tds_version
|
28
|
+
assert_equal 'DBTDS_8_0 - Microsoft SQL Server 2000', @client.tds_version_info
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'use UTF-8 client charset/encoding by default' do
|
32
|
+
assert_equal 'UTF-8', @client.charset
|
33
|
+
assert_equal Encoding.find('UTF-8'), @client.encoding if ruby19?
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'have a #escape method used for quote strings' do
|
37
|
+
assert_equal "''hello''", @client.escape("'hello'")
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'allow valid iconv character set' do
|
41
|
+
['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
|
42
|
+
client = new_connection(:encoding => encoding)
|
43
|
+
assert_equal encoding, client.charset
|
44
|
+
assert_equal Encoding.find(encoding), client.encoding if ruby19?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'be able to use :host/:port connection' do
|
49
|
+
client = new_connection :dataserver => nil, :host => ENV['TINYTDS_UNIT_HOST'], :port => 1433
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'With in-valid options' do
|
55
|
+
|
56
|
+
should 'raise an argument error when no :host given and :dataserver is blank' do
|
57
|
+
assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'raise an argument error when no :username is supplied' do
|
61
|
+
assert_raises(ArgumentError) { TinyTds::Client.new :username => nil }
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'raise TinyTds exception with undefined :dataserver' do
|
65
|
+
options = connection_options :login_timeout => 1, :dataserver => '127.0.0.2'
|
66
|
+
action = lambda { new_connection(options) }
|
67
|
+
assert_raise_tinytds_error(action) do |e|
|
68
|
+
assert [20008,20009].include?(e.db_error_number)
|
69
|
+
assert_equal 9, e.severity
|
70
|
+
assert_match %r{unable to (open|connect)}i, e.message, 'ignore if non-english test run'
|
71
|
+
end
|
72
|
+
assert_new_connections_work
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'raise TinyTds exception with long query past :timeout option' do
|
76
|
+
client = new_connection :timeout => 1
|
77
|
+
action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
|
78
|
+
assert_raise_tinytds_error(action) do |e|
|
79
|
+
assert_equal 20003, e.db_error_number
|
80
|
+
assert_equal 6, e.severity
|
81
|
+
assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
|
82
|
+
end
|
83
|
+
assert_client_works(client)
|
84
|
+
assert_new_connections_work
|
85
|
+
end
|
86
|
+
|
87
|
+
should 'not timeout per sql batch when not under transaction' do
|
88
|
+
client = new_connection :timeout => 2
|
89
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
90
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
91
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
92
|
+
end
|
93
|
+
|
94
|
+
should 'not timeout per sql batch when under transaction' do
|
95
|
+
client = new_connection :timeout => 2
|
96
|
+
begin
|
97
|
+
client.execute("BEGIN TRANSACTION").do
|
98
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
99
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
100
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
101
|
+
ensure
|
102
|
+
client.execute("COMMIT TRANSACTION").do
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
should_eventually 'run this test to prove we account for dropped connections' do
|
107
|
+
begin
|
108
|
+
client = new_connection :login_timeout => 2, :timeout => 2
|
109
|
+
assert_client_works(client)
|
110
|
+
STDOUT.puts "Disconnect network!"
|
111
|
+
sleep 10
|
112
|
+
STDOUT.puts "This should not get stuck past 6 seconds!"
|
113
|
+
action = lambda { client.execute('SELECT 1 as [one]').each }
|
114
|
+
assert_raise_tinytds_error(action) do |e|
|
115
|
+
assert_equal 20003, e.db_error_number
|
116
|
+
assert_equal 6, e.severity
|
117
|
+
assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
|
118
|
+
end
|
119
|
+
ensure
|
120
|
+
STDOUT.puts "Reconnect network!"
|
121
|
+
sleep 10
|
122
|
+
action = lambda { client.execute('SELECT 1 as [one]').each }
|
123
|
+
assert_raise_tinytds_error(action) do |e|
|
124
|
+
assert_equal 20047, e.db_error_number
|
125
|
+
assert_equal 1, e.severity
|
126
|
+
assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
|
127
|
+
end
|
128
|
+
assert_new_connections_work
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
should 'raise TinyTds exception with wrong :username' do
|
133
|
+
options = connection_options :username => 'willnotwork'
|
134
|
+
action = lambda { new_connection(options) }
|
135
|
+
assert_raise_tinytds_error(action) do |e|
|
136
|
+
if sqlserver_azure?
|
137
|
+
assert_match %r{server name cannot be determined}i, e.message, 'ignore if non-english test run'
|
138
|
+
else
|
139
|
+
assert_equal 18456, e.db_error_number
|
140
|
+
assert_equal 14, e.severity
|
141
|
+
assert_match %r{login failed}i, e.message, 'ignore if non-english test run'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
assert_new_connections_work
|
145
|
+
end
|
146
|
+
|
147
|
+
should 'fail miserably with unknown encoding option' do
|
148
|
+
options = connection_options :encoding => 'ISO-WTF'
|
149
|
+
action = lambda { new_connection(options) }
|
150
|
+
assert_raise_tinytds_error(action) do |e|
|
151
|
+
assert_equal 20002, e.db_error_number
|
152
|
+
assert_equal 9, e.severity
|
153
|
+
assert_match %r{connection failed}i, e.message, 'ignore if non-english test run'
|
154
|
+
end
|
155
|
+
assert_new_connections_work
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
end
|
163
|
+
|
data/test/result_test.rb
ADDED
@@ -0,0 +1,437 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class ResultTest < TinyTds::TestCase
|
5
|
+
|
6
|
+
context 'Basic query and result' do
|
7
|
+
|
8
|
+
setup do
|
9
|
+
@@current_schema_loaded ||= load_current_schema
|
10
|
+
@client = new_connection
|
11
|
+
@query1 = 'SELECT 1 AS [one]'
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'have included Enumerable' do
|
15
|
+
assert TinyTds::Result.ancestors.include?(Enumerable)
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'respond to #each' do
|
19
|
+
result = @client.execute(@query1)
|
20
|
+
assert result.respond_to?(:each)
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'return all results for #each with no block' do
|
24
|
+
result = @client.execute(@query1)
|
25
|
+
data = result.each
|
26
|
+
row = data.first
|
27
|
+
assert_instance_of Array, data
|
28
|
+
assert_equal 1, data.size
|
29
|
+
assert_instance_of Hash, row, 'hash is the default query option'
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'return all results for #each with a block yielding a row at a time' do
|
33
|
+
result = @client.execute(@query1)
|
34
|
+
data = result.each do |row|
|
35
|
+
assert_instance_of Hash, row, 'hash is the default query option'
|
36
|
+
end
|
37
|
+
assert_instance_of Array, data
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'allow successive calls to each returning the same data' do
|
41
|
+
result = @client.execute(@query1)
|
42
|
+
data = result.each
|
43
|
+
result.each
|
44
|
+
assert_equal data.object_id, result.each.object_id
|
45
|
+
assert_equal data.first.object_id, result.each.first.object_id
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'return hashes with string keys' do
|
49
|
+
result = @client.execute(@query1)
|
50
|
+
row = result.each(:as => :hash, :symbolize_keys => false).first
|
51
|
+
assert_instance_of Hash, row
|
52
|
+
assert_equal ['one'], row.keys
|
53
|
+
assert_equal ['one'], result.fields
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'return hashes with symbol keys' do
|
57
|
+
result = @client.execute(@query1)
|
58
|
+
row = result.each(:as => :hash, :symbolize_keys => true).first
|
59
|
+
assert_instance_of Hash, row
|
60
|
+
assert_equal [:one], row.keys
|
61
|
+
assert_equal [:one], result.fields
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'return arrays with string fields' do
|
65
|
+
result = @client.execute(@query1)
|
66
|
+
row = result.each(:as => :array, :symbolize_keys => false).first
|
67
|
+
assert_instance_of Array, row
|
68
|
+
assert_equal ['one'], result.fields
|
69
|
+
end
|
70
|
+
|
71
|
+
should 'return arrays with symbol fields' do
|
72
|
+
result = @client.execute(@query1)
|
73
|
+
row = result.each(:as => :array, :symbolize_keys => true).first
|
74
|
+
assert_instance_of Array, row
|
75
|
+
assert_equal [:one], result.fields
|
76
|
+
end
|
77
|
+
|
78
|
+
should 'be able to turn :cache_rows option off' do
|
79
|
+
result = @client.execute(@query1)
|
80
|
+
local = []
|
81
|
+
result.each(:cache_rows => false) do |row|
|
82
|
+
local << row
|
83
|
+
end
|
84
|
+
assert local.first, 'should have iterated over each row'
|
85
|
+
assert_equal [], result.each, 'should not have been cached'
|
86
|
+
assert_equal ['one'], result.fields, 'should still cache field names'
|
87
|
+
end
|
88
|
+
|
89
|
+
should 'be able to get the first result row only' do
|
90
|
+
load_current_schema
|
91
|
+
big_query = "SELECT [id] FROM [datatypes]"
|
92
|
+
one = @client.execute(big_query).each(:first => true)
|
93
|
+
many = @client.execute(big_query).each
|
94
|
+
assert many.size > 1
|
95
|
+
assert one.size == 1
|
96
|
+
end
|
97
|
+
|
98
|
+
should 'cope with no results when using first option' do
|
99
|
+
data = @client.execute("SELECT [id] FROM [datatypes] WHERE [id] = -1").each(:first => true)
|
100
|
+
assert_equal [], data
|
101
|
+
end
|
102
|
+
|
103
|
+
should 'delete, insert and find data' do
|
104
|
+
rollback_transaction(@client) do
|
105
|
+
text = 'test insert and delete'
|
106
|
+
@client.execute("DELETE FROM [datatypes] WHERE [varchar_50] IS NOT NULL").do
|
107
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
108
|
+
row = @client.execute("SELECT [varchar_50] FROM [datatypes] WHERE [varchar_50] IS NOT NULL").each.first
|
109
|
+
assert row
|
110
|
+
assert_equal text, row['varchar_50']
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
should 'insert and find unicode data' do
|
115
|
+
rollback_transaction(@client) do
|
116
|
+
text = '✓'
|
117
|
+
@client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
|
118
|
+
@client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES (N'#{text}')").do
|
119
|
+
row = @client.execute("SELECT [nvarchar_50] FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").each.first
|
120
|
+
assert_equal text, row['nvarchar_50']
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
should 'delete and update with affected rows support and insert with identity support in native sql' do
|
125
|
+
rollback_transaction(@client) do
|
126
|
+
text = 'test affected rows sql'
|
127
|
+
@client.execute("DELETE FROM [datatypes]").do
|
128
|
+
afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
|
129
|
+
assert_instance_of Fixnum, afrows
|
130
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
131
|
+
pk1 = @client.execute("SELECT SCOPE_IDENTITY() AS Ident").each.first['Ident']
|
132
|
+
assert_instance_of BigDecimal, pk1, 'native is numeric(38,0) for SCOPE_IDENTITY() function'
|
133
|
+
pk2 = @client.execute("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident").each.first['Ident']
|
134
|
+
assert_instance_of Fixnum, pk2, 'we should be able to CAST to bigint'
|
135
|
+
assert_equal pk2, pk1.to_i, 'just making sure the 2 line up'
|
136
|
+
@client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
|
137
|
+
afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
|
138
|
+
assert_equal 1, afrows
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
should 'have a #do method that cancels result rows and returns affected rows natively' do
|
143
|
+
rollback_transaction(@client) do
|
144
|
+
text = 'test affected rows native'
|
145
|
+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
|
146
|
+
deleted_rows = @client.execute("DELETE FROM [datatypes]").do
|
147
|
+
assert_equal count, deleted_rows, 'should have deleted rows equal to count'
|
148
|
+
inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
149
|
+
assert_equal 1, inserted_rows, 'should have inserted row for one above'
|
150
|
+
updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
|
151
|
+
assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
should 'allow native affected rows using #do to work under transaction' do
|
156
|
+
rollback_transaction(@client) do
|
157
|
+
text = 'test affected rows native in transaction'
|
158
|
+
@client.execute("BEGIN TRANSACTION").do
|
159
|
+
@client.execute("DELETE FROM [datatypes]").do
|
160
|
+
inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
161
|
+
assert_equal 1, inserted_rows, 'should have inserted row for one above'
|
162
|
+
updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
|
163
|
+
assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
should 'have an #insert method that cancels result rows and returns the SCOPE_IDENTITY() natively' do
|
168
|
+
rollback_transaction(@client) do
|
169
|
+
text = 'test scope identity rows native'
|
170
|
+
@client.execute("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'").do
|
171
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
172
|
+
sql_identity = @client.execute("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident").each.first['Ident']
|
173
|
+
native_identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").insert
|
174
|
+
assert_equal sql_identity+1, native_identity
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
should 'be able to begin/commit transactions with raw sql' do
|
179
|
+
rollback_transaction(@client) do
|
180
|
+
@client.execute("BEGIN TRANSACTION").do
|
181
|
+
@client.execute("DELETE FROM [datatypes]").do
|
182
|
+
@client.execute("COMMIT TRANSACTION").do
|
183
|
+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
|
184
|
+
assert_equal 0, count
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
should 'be able to begin/rollback transactions with raw sql' do
|
189
|
+
load_current_schema
|
190
|
+
@client.execute("BEGIN TRANSACTION").do
|
191
|
+
@client.execute("DELETE FROM [datatypes]").do
|
192
|
+
@client.execute("ROLLBACK TRANSACTION").do
|
193
|
+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
|
194
|
+
0.wont_equal count
|
195
|
+
end
|
196
|
+
|
197
|
+
should 'have a #fields accessor with logic default and valid outcome' do
|
198
|
+
result = @client.execute(@query1)
|
199
|
+
result.fields.must_equal ['one']
|
200
|
+
result.each
|
201
|
+
result.fields.must_equal ['one']
|
202
|
+
end
|
203
|
+
|
204
|
+
should 'always return an array for fields for all sql' do
|
205
|
+
result = @client.execute("USE [tinytdstest]")
|
206
|
+
result.fields.must_equal []
|
207
|
+
result.do
|
208
|
+
result.fields.must_equal []
|
209
|
+
end
|
210
|
+
|
211
|
+
should 'return fields even when no results are found' do
|
212
|
+
no_results_query = "SELECT [id], [varchar_50] FROM [datatypes] WHERE [varchar_50] = 'NOTFOUND'"
|
213
|
+
# Fields before each.
|
214
|
+
result = @client.execute(no_results_query)
|
215
|
+
result.fields.must_equal ['id','varchar_50']
|
216
|
+
result.each
|
217
|
+
result.fields.must_equal ['id','varchar_50']
|
218
|
+
# Each then fields
|
219
|
+
result = @client.execute(no_results_query)
|
220
|
+
result.each
|
221
|
+
result.fields.must_equal ['id','varchar_50']
|
222
|
+
end
|
223
|
+
|
224
|
+
should 'allow the result to be canceled before reading' do
|
225
|
+
result = @client.execute(@query1)
|
226
|
+
result.cancel
|
227
|
+
@client.execute(@query1).each
|
228
|
+
end
|
229
|
+
|
230
|
+
should 'work in tandem with the client when needing to find out if client has sql sent and result is canceled or not' do
|
231
|
+
# Default state.
|
232
|
+
@client = TinyTds::Client.new(connection_options)
|
233
|
+
@client.sqlsent?.must_equal false
|
234
|
+
@client.canceled?.must_equal false
|
235
|
+
# With active result before and after cancel.
|
236
|
+
result = @client.execute(@query1)
|
237
|
+
@client.sqlsent?.must_equal true
|
238
|
+
@client.canceled?.must_equal false
|
239
|
+
result.cancel
|
240
|
+
@client.sqlsent?.must_equal false
|
241
|
+
@client.canceled?.must_equal true
|
242
|
+
assert result.cancel, 'must be safe to call again'
|
243
|
+
# With each and no block.
|
244
|
+
@client.execute(@query1).each
|
245
|
+
@client.sqlsent?.must_equal false
|
246
|
+
@client.canceled?.must_equal false
|
247
|
+
# With each and block.
|
248
|
+
@client.execute(@query1).each do |row|
|
249
|
+
@client.sqlsent?.must_equal true, 'when iterating over each row in a block'
|
250
|
+
@client.canceled?.must_equal false
|
251
|
+
end
|
252
|
+
@client.sqlsent?.must_equal false
|
253
|
+
@client.canceled?.must_equal false
|
254
|
+
# With each and block canceled half way thru.
|
255
|
+
count = @client.execute("SELECT COUNT([id]) AS [count] FROM [datatypes]").each[0]['count']
|
256
|
+
assert count > 10, 'since we want to cancel early for test'
|
257
|
+
result = @client.execute("SELECT [id] FROM [datatypes]")
|
258
|
+
index = 0
|
259
|
+
result.each do |row|
|
260
|
+
break if index > 10
|
261
|
+
index += 1
|
262
|
+
end
|
263
|
+
@client.sqlsent?.must_equal true
|
264
|
+
@client.canceled?.must_equal false
|
265
|
+
result.cancel
|
266
|
+
@client.sqlsent?.must_equal false
|
267
|
+
@client.canceled?.must_equal true
|
268
|
+
# With do method.
|
269
|
+
@client.execute(@query1).do
|
270
|
+
@client.sqlsent?.must_equal false
|
271
|
+
@client.canceled?.must_equal true
|
272
|
+
# With insert method.
|
273
|
+
rollback_transaction(@client) do
|
274
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test')").insert
|
275
|
+
@client.sqlsent?.must_equal false
|
276
|
+
@client.canceled?.must_equal true
|
277
|
+
end
|
278
|
+
# With first
|
279
|
+
@client.execute("SELECT [id] FROM [datatypes]").each(:first => true)
|
280
|
+
@client.sqlsent?.must_equal false
|
281
|
+
@client.canceled?.must_equal true
|
282
|
+
end
|
283
|
+
|
284
|
+
should 'use same string object for hash keys' do
|
285
|
+
data = @client.execute("SELECT [id], [bigint] FROM [datatypes]").each
|
286
|
+
assert_equal data.first.keys.map{ |r| r.object_id }, data.last.keys.map{ |r| r.object_id }
|
287
|
+
end
|
288
|
+
|
289
|
+
should 'have properly encoded column names' do
|
290
|
+
col_name = "öäüß"
|
291
|
+
@client.execute("DROP TABLE [test_encoding]").do rescue nil
|
292
|
+
@client.execute("CREATE TABLE [dbo].[test_encoding] ( [#{col_name}] [nvarchar](10) NOT NULL )").do
|
293
|
+
@client.execute("INSERT INTO [test_encoding] ([#{col_name}]) VALUES (N'#{col_name}')").do
|
294
|
+
result = @client.execute("SELECT [#{col_name}] FROM [test_encoding]")
|
295
|
+
row = result.each.first
|
296
|
+
assert_equal col_name, result.fields.first
|
297
|
+
assert_equal col_name, row.keys.first
|
298
|
+
assert_utf8_encoding result.fields.first
|
299
|
+
assert_utf8_encoding row.keys.first
|
300
|
+
end unless sqlserver_azure?
|
301
|
+
|
302
|
+
should 'allow #return_code to work with stored procedures and reset per sql batch' do
|
303
|
+
assert_nil @client.return_code
|
304
|
+
result = @client.execute("EXEC tinytds_TestReturnCodes")
|
305
|
+
assert_equal [{"one"=>1}], result.each
|
306
|
+
assert_equal 420, @client.return_code
|
307
|
+
assert_equal 420, result.return_code
|
308
|
+
result = @client.execute('SELECT 1 as [one]')
|
309
|
+
result.each
|
310
|
+
assert_nil @client.return_code
|
311
|
+
assert_nil result.return_code
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'with multiple result sets' do
|
315
|
+
|
316
|
+
setup do
|
317
|
+
@double_select = "SELECT 1 AS [rs1]\nSELECT 2 AS [rs2]"
|
318
|
+
end
|
319
|
+
|
320
|
+
should 'handle a command buffer with double selects' do
|
321
|
+
result = @client.execute(@double_select)
|
322
|
+
result_sets = result.each
|
323
|
+
assert_equal 2, result_sets.size
|
324
|
+
assert_equal [{'rs1' => 1}], result_sets.first
|
325
|
+
assert_equal [{'rs2' => 2}], result_sets.last
|
326
|
+
assert_equal [['rs1'],['rs2']], result.fields
|
327
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
328
|
+
# As array
|
329
|
+
result = @client.execute(@double_select)
|
330
|
+
result_sets = result.each(:as => :array)
|
331
|
+
assert_equal 2, result_sets.size
|
332
|
+
assert_equal [[1]], result_sets.first
|
333
|
+
assert_equal [[2]], result_sets.last
|
334
|
+
assert_equal [['rs1'],['rs2']], result.fields
|
335
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
336
|
+
end
|
337
|
+
|
338
|
+
should 'yield each row for each result set' do
|
339
|
+
data = []
|
340
|
+
result_sets = @client.execute(@double_select).each { |row| data << row }
|
341
|
+
assert_equal data.first, result_sets.first[0]
|
342
|
+
assert_equal data.last, result_sets.last[0]
|
343
|
+
end
|
344
|
+
|
345
|
+
should 'from a stored procedure' do
|
346
|
+
results1, results2 = @client.execute("EXEC sp_helpconstraint '[datatypes]'").each
|
347
|
+
assert_equal [{"Object Name"=>"[datatypes]"}], results1
|
348
|
+
constraint_info = results2.first
|
349
|
+
assert constraint_info.key?("constraint_keys")
|
350
|
+
assert constraint_info.key?("constraint_type")
|
351
|
+
assert constraint_info.key?("constraint_name")
|
352
|
+
end
|
353
|
+
|
354
|
+
should 'ignore empty result sets' do
|
355
|
+
rollback_transaction(@client) do
|
356
|
+
@client.execute("DELETE FROM [datatypes]").do
|
357
|
+
id = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test empty result sets')").insert
|
358
|
+
sql = %|
|
359
|
+
SET NOCOUNT ON
|
360
|
+
DECLARE @row_number TABLE (row int identity(1,1), id int)
|
361
|
+
INSERT INTO @row_number (id)
|
362
|
+
SELECT [datatypes].[id] FROM [datatypes]
|
363
|
+
SET NOCOUNT OFF
|
364
|
+
SELECT id FROM @row_number|
|
365
|
+
result = @client.execute(sql)
|
366
|
+
result.each.must_equal [{"id"=>id}]
|
367
|
+
result.fields.must_equal ['id']
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
|
373
|
+
context 'when casting to native ruby values' do
|
374
|
+
|
375
|
+
should 'return fixnum for 1' do
|
376
|
+
value = @client.execute('SELECT 1 AS [fixnum]').each.first['fixnum']
|
377
|
+
assert_equal 1, value
|
378
|
+
end
|
379
|
+
|
380
|
+
should 'return nil for NULL' do
|
381
|
+
value = @client.execute('SELECT NULL AS [null]').each.first['null']
|
382
|
+
assert_equal nil, value
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
context 'when shit happens' do
|
388
|
+
|
389
|
+
should 'cope with nil or empty buffer' do
|
390
|
+
assert_raises(TypeError) { @client.execute(nil) }
|
391
|
+
assert_equal [], @client.execute('').each
|
392
|
+
end
|
393
|
+
|
394
|
+
should 'throw an error when you execute another query with other results pending' do
|
395
|
+
result1 = @client.execute(@query1)
|
396
|
+
action = lambda { @client.execute(@query1) }
|
397
|
+
assert_raise_tinytds_error(action) do |e|
|
398
|
+
assert_match %r|with results pending|i, e.message
|
399
|
+
assert_equal 7, e.severity
|
400
|
+
assert_equal 20019, e.db_error_number
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
should 'error gracefully with bad table name' do
|
405
|
+
action = lambda { @client.execute('SELECT * FROM [foobar]').each }
|
406
|
+
assert_raise_tinytds_error(action) do |e|
|
407
|
+
assert_match %r|invalid object name.*foobar|i, e.message
|
408
|
+
assert_equal 16, e.severity
|
409
|
+
assert_equal 208, e.db_error_number
|
410
|
+
end
|
411
|
+
assert_followup_query
|
412
|
+
end
|
413
|
+
|
414
|
+
should 'error gracefully with invalid syntax' do
|
415
|
+
action = lambda { @client.execute('this will not work').each }
|
416
|
+
assert_raise_tinytds_error(action) do |e|
|
417
|
+
assert_match %r|incorrect syntax|i, e.message
|
418
|
+
assert_equal 15, e.severity
|
419
|
+
assert_equal 156, e.db_error_number
|
420
|
+
end
|
421
|
+
assert_followup_query
|
422
|
+
end
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
protected
|
430
|
+
|
431
|
+
def assert_followup_query
|
432
|
+
result = @client.execute(@query1)
|
433
|
+
assert_equal 1, result.each.first['one']
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|
437
|
+
|