tiny_tds 0.6.2-x64-mingw32
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 +15 -0
- data/.gitignore +19 -0
- data/CHANGELOG +180 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +23 -0
- data/README.md +385 -0
- data/Rakefile +114 -0
- data/compile/rake-compiler-dev-box.patch +31 -0
- data/ext/patch/Makefile.in.diff +29 -0
- data/ext/patch/dblib-30-char-username.diff +11 -0
- data/ext/patch/sspi_w_kerberos.diff +42 -0
- data/ext/tiny_tds/client.c +408 -0
- data/ext/tiny_tds/client.h +46 -0
- data/ext/tiny_tds/extconf.rb +102 -0
- data/ext/tiny_tds/result.c +599 -0
- data/ext/tiny_tds/result.h +36 -0
- data/ext/tiny_tds/tiny_tds_ext.c +12 -0
- data/ext/tiny_tds/tiny_tds_ext.h +15 -0
- data/lib/tiny_tds.rb +19 -0
- data/lib/tiny_tds/client.rb +96 -0
- data/lib/tiny_tds/error.rb +29 -0
- data/lib/tiny_tds/result.rb +8 -0
- data/lib/tiny_tds/version.rb +3 -0
- data/tasks/ports.rake +79 -0
- data/test/benchmark/query.rb +77 -0
- data/test/benchmark/query_odbc.rb +106 -0
- data/test/benchmark/query_tinytds.rb +126 -0
- data/test/client_test.rb +170 -0
- data/test/result_test.rb +732 -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/sybase_ase.sql +138 -0
- data/test/schema_test.rb +305 -0
- data/test/test_helper.rb +195 -0
- data/test/thread_test.rb +95 -0
- metadata +171 -0
@@ -0,0 +1,126 @@
|
|
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
|
+
Query Tinytds
|
88
|
+
=============
|
89
|
+
Author: Ken Collins
|
90
|
+
Date: September 11, 2011
|
91
|
+
Summary: Benchmark TinyTds Querys
|
92
|
+
|
93
|
+
System Information
|
94
|
+
------------------
|
95
|
+
Operating System: Mac OS X 10.7.1 (11B26)
|
96
|
+
CPU: Quad-Core Intel Xeon 2.66 GHz
|
97
|
+
Processor Count: 4
|
98
|
+
Memory: 24 GB
|
99
|
+
ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-darwin11.1.0], MBARI 0x6770, Ruby Enterprise Edition 2011.03
|
100
|
+
|
101
|
+
----------------------------------------------------
|
102
|
+
(before 64bit times) (after 64bit times)
|
103
|
+
Nothing 0.287657022476196 secs Nothing 0.289273977279663 secs
|
104
|
+
Bits 0.406533002853394 secs Bits 0.424988031387329 secs
|
105
|
+
Guids 0.419962882995605 secs Guids 0.427381992340088 secs
|
106
|
+
Floats 0.452103137969971 secs Floats 0.455377101898193 secs
|
107
|
+
Moneys 0.481696844100952 secs Moneys 0.485175132751465 secs
|
108
|
+
Integers 0.496185064315796 secs Integers 0.525003910064697 secs
|
109
|
+
Binaries 0.538873195648193 secs Decimals 0.541536808013916 secs
|
110
|
+
Decimals 0.540570974349976 secs Binaries 0.542865991592407 secs
|
111
|
+
Dates 0.761389970779419 secs Dates 1.51440119743347 secs
|
112
|
+
Chars 0.793163061141968 secs Chars 0.666505098342896 secs
|
113
|
+
All 4.4630811214447 secs All 5.17242312431335 secs
|
114
|
+
|
115
|
+
=end
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class ClientTest < TinyTds::TestCase
|
5
|
+
|
6
|
+
describe 'With valid credentials' do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@client = new_connection
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'must not be closed' do
|
13
|
+
assert !@client.closed?
|
14
|
+
assert @client.active?
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'allows client connection to be closed' do
|
18
|
+
assert @client.close
|
19
|
+
assert @client.closed?
|
20
|
+
assert !@client.active?
|
21
|
+
action = lambda { @client.execute('SELECT 1 as [one]').each }
|
22
|
+
assert_raise_tinytds_error(action) do |e|
|
23
|
+
assert_match %r{closed connection}i, e.message, 'ignore if non-english test run'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has getters for the tds version information (brittle since conf takes precedence)' do
|
28
|
+
if sybase_ase?
|
29
|
+
assert_equal 7, @client.tds_version
|
30
|
+
assert_equal 'DBTDS_5_0 - 5.0 SQL Server', @client.tds_version_info
|
31
|
+
else
|
32
|
+
assert_equal 9, @client.tds_version
|
33
|
+
assert_equal 'DBTDS_7_1/DBTDS_8_0 - Microsoft SQL Server 2000', @client.tds_version_info
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'uses UTF-8 client charset/encoding by default' do
|
38
|
+
assert_equal 'UTF-8', @client.charset
|
39
|
+
assert_equal Encoding.find('UTF-8'), @client.encoding
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'has a #escape method used for quote strings' do
|
43
|
+
assert_equal "''hello''", @client.escape("'hello'")
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'allows valid iconv character set' do
|
47
|
+
['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
|
48
|
+
client = new_connection(:encoding => encoding)
|
49
|
+
assert_equal encoding, client.charset
|
50
|
+
assert_equal Encoding.find(encoding), client.encoding
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'must be able to use :host/:port connection' do
|
55
|
+
client = new_connection :dataserver => nil, :host => ENV['TINYTDS_UNIT_HOST'], :port => ENV['TINYTDS_UNIT_PORT'] || 1433
|
56
|
+
end unless sqlserver_azure?
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'With in-valid options' do
|
61
|
+
|
62
|
+
it 'raises an argument error when no :host given and :dataserver is blank' do
|
63
|
+
assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'raises an argument error when no :username is supplied' do
|
67
|
+
assert_raises(ArgumentError) { TinyTds::Client.new :username => nil }
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'raises TinyTds exception with undefined :dataserver' do
|
71
|
+
options = connection_options :login_timeout => 1, :dataserver => '127.0.0.2'
|
72
|
+
action = lambda { new_connection(options) }
|
73
|
+
assert_raise_tinytds_error(action) do |e|
|
74
|
+
assert [20008,20009].include?(e.db_error_number)
|
75
|
+
assert_equal 9, e.severity
|
76
|
+
assert_match %r{unable to (open|connect)}i, e.message, 'ignore if non-english test run'
|
77
|
+
end
|
78
|
+
assert_new_connections_work
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'raises TinyTds exception with long query past :timeout option' do
|
82
|
+
client = new_connection :timeout => 1
|
83
|
+
action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
|
84
|
+
assert_raise_tinytds_error(action) do |e|
|
85
|
+
assert_equal 20003, e.db_error_number
|
86
|
+
assert_equal 6, e.severity
|
87
|
+
assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
|
88
|
+
end
|
89
|
+
assert_client_works(client)
|
90
|
+
assert_new_connections_work
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'must not timeout per sql batch when not under transaction' do
|
94
|
+
client = new_connection :timeout => 2
|
95
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
96
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
97
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'must not timeout per sql batch when under transaction' do
|
101
|
+
client = new_connection :timeout => 2
|
102
|
+
begin
|
103
|
+
client.execute("BEGIN TRANSACTION").do
|
104
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
105
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
106
|
+
client.execute("WaitFor Delay '00:00:01'").do
|
107
|
+
ensure
|
108
|
+
client.execute("COMMIT TRANSACTION").do
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'must run this test to prove we account for dropped connections' do
|
113
|
+
skip
|
114
|
+
begin
|
115
|
+
client = new_connection :login_timeout => 2, :timeout => 2
|
116
|
+
assert_client_works(client)
|
117
|
+
STDOUT.puts "Disconnect network!"
|
118
|
+
sleep 10
|
119
|
+
STDOUT.puts "This should not get stuck past 6 seconds!"
|
120
|
+
action = lambda { client.execute('SELECT 1 as [one]').each }
|
121
|
+
assert_raise_tinytds_error(action) do |e|
|
122
|
+
assert_equal 20003, e.db_error_number
|
123
|
+
assert_equal 6, e.severity
|
124
|
+
assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
|
125
|
+
end
|
126
|
+
ensure
|
127
|
+
STDOUT.puts "Reconnect network!"
|
128
|
+
sleep 10
|
129
|
+
action = lambda { client.execute('SELECT 1 as [one]').each }
|
130
|
+
assert_raise_tinytds_error(action) do |e|
|
131
|
+
assert_equal 20047, e.db_error_number
|
132
|
+
assert_equal 1, e.severity
|
133
|
+
assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
|
134
|
+
end
|
135
|
+
assert_new_connections_work
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'raises TinyTds exception with wrong :username' do
|
140
|
+
options = connection_options :username => 'willnotwork'
|
141
|
+
action = lambda { new_connection(options) }
|
142
|
+
assert_raise_tinytds_error(action) do |e|
|
143
|
+
if sqlserver_azure?
|
144
|
+
assert_match %r{server name cannot be determined}i, e.message, 'ignore if non-english test run'
|
145
|
+
else
|
146
|
+
assert_equal sybase_ase? ? 4002 : 18456, e.db_error_number
|
147
|
+
assert_equal 14, e.severity
|
148
|
+
assert_match %r{login failed}i, e.message, 'ignore if non-english test run'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
assert_new_connections_work
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'fails miserably with unknown encoding option' do
|
155
|
+
options = connection_options :encoding => 'ISO-WTF'
|
156
|
+
action = lambda { new_connection(options) }
|
157
|
+
assert_raise_tinytds_error(action) do |e|
|
158
|
+
assert_equal 20002, e.db_error_number
|
159
|
+
assert_equal 9, e.severity
|
160
|
+
assert_match %r{connection failed}i, e.message, 'ignore if non-english test run'
|
161
|
+
end
|
162
|
+
assert_new_connections_work
|
163
|
+
end unless sybase_ase?
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
end
|
170
|
+
|
data/test/result_test.rb
ADDED
@@ -0,0 +1,732 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class ResultTest < TinyTds::TestCase
|
5
|
+
|
6
|
+
describe 'Basic query and result' do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@@current_schema_loaded ||= load_current_schema
|
10
|
+
@client = new_connection
|
11
|
+
@query1 = 'SELECT 1 AS [one]'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has included Enumerable' do
|
15
|
+
assert TinyTds::Result.ancestors.include?(Enumerable)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'responds to #each' do
|
19
|
+
result = @client.execute(@query1)
|
20
|
+
assert result.respond_to?(:each)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns 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
|
+
it 'returns 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
|
+
it 'allows 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
|
+
it 'returns 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
|
+
it 'returns 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
|
+
it 'returns 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
|
+
it 'returns 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
|
+
it 'allows sql concat + to work' do
|
79
|
+
rollback_transaction(@client) do
|
80
|
+
@client.execute("DELETE FROM [datatypes]").do
|
81
|
+
@client.execute("INSERT INTO [datatypes] ([char_10], [varchar_50]) VALUES ('1', '2')").do
|
82
|
+
result = @client.execute("SELECT TOP (1) [char_10] + 'test' + [varchar_50] AS [test] FROM [datatypes]").each.first['test']
|
83
|
+
result.must_equal "1 test2"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'must be able to turn :cache_rows option off' do
|
88
|
+
result = @client.execute(@query1)
|
89
|
+
local = []
|
90
|
+
result.each(:cache_rows => false) do |row|
|
91
|
+
local << row
|
92
|
+
end
|
93
|
+
assert local.first, 'should have iterated over each row'
|
94
|
+
assert_equal [], result.each, 'should not have been cached'
|
95
|
+
assert_equal ['one'], result.fields, 'should still cache field names'
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'must be able to get the first result row only' do
|
99
|
+
load_current_schema
|
100
|
+
big_query = "SELECT [id] FROM [datatypes]"
|
101
|
+
one = @client.execute(big_query).each(:first => true)
|
102
|
+
many = @client.execute(big_query).each
|
103
|
+
assert many.size > 1
|
104
|
+
assert one.size == 1
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'copes with no results when using first option' do
|
108
|
+
data = @client.execute("SELECT [id] FROM [datatypes] WHERE [id] = -1").each(:first => true)
|
109
|
+
assert_equal [], data
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'must delete, insert and find data' do
|
113
|
+
rollback_transaction(@client) do
|
114
|
+
text = 'test insert and delete'
|
115
|
+
@client.execute("DELETE FROM [datatypes] WHERE [varchar_50] IS NOT NULL").do
|
116
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
117
|
+
row = @client.execute("SELECT [varchar_50] FROM [datatypes] WHERE [varchar_50] IS NOT NULL").each.first
|
118
|
+
assert row
|
119
|
+
assert_equal text, row['varchar_50']
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'must insert and find unicode data' do
|
124
|
+
rollback_transaction(@client) do
|
125
|
+
text = '✓'
|
126
|
+
@client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
|
127
|
+
@client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES (N'#{text}')").do
|
128
|
+
row = @client.execute("SELECT [nvarchar_50] FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").each.first
|
129
|
+
assert_equal text, row['nvarchar_50']
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'must delete and update with affected rows support and insert with identity support in native sql' do
|
134
|
+
rollback_transaction(@client) do
|
135
|
+
text = 'test affected rows sql'
|
136
|
+
@client.execute("DELETE FROM [datatypes]").do
|
137
|
+
afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
|
138
|
+
assert_instance_of Fixnum, afrows
|
139
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
140
|
+
pk1 = @client.execute(@client.identity_sql).each.first['Ident']
|
141
|
+
assert_instance_of Fixnum, pk1, 'we it be able to CAST to bigint'
|
142
|
+
@client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
|
143
|
+
afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
|
144
|
+
assert_equal 1, afrows
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'has a #do method that cancels result rows and returns affected rows natively' do
|
149
|
+
rollback_transaction(@client) do
|
150
|
+
text = 'test affected rows native'
|
151
|
+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
|
152
|
+
deleted_rows = @client.execute("DELETE FROM [datatypes]").do
|
153
|
+
assert_equal count, deleted_rows, 'should have deleted rows equal to count'
|
154
|
+
inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
155
|
+
assert_equal 1, inserted_rows, 'should have inserted row for one above'
|
156
|
+
updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
|
157
|
+
assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'allows native affected rows using #do to work under transaction' do
|
162
|
+
rollback_transaction(@client) do
|
163
|
+
text = 'test affected rows native in transaction'
|
164
|
+
@client.execute("BEGIN TRANSACTION").do
|
165
|
+
@client.execute("DELETE FROM [datatypes]").do
|
166
|
+
inserted_rows = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
167
|
+
assert_equal 1, inserted_rows, 'should have inserted row for one above'
|
168
|
+
updated_rows = @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
|
169
|
+
assert_equal 1, updated_rows, 'should have updated row for one above' unless sqlserver_2000? # Will report -1
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'has an #insert method that cancels result rows and returns IDENTITY natively' do
|
174
|
+
rollback_transaction(@client) do
|
175
|
+
text = 'test scope identity rows native'
|
176
|
+
@client.execute("DELETE FROM [datatypes] WHERE [varchar_50] = '#{text}'").do
|
177
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
|
178
|
+
sql_identity = @client.execute(@client.identity_sql).each.first['Ident']
|
179
|
+
native_identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").insert
|
180
|
+
assert_equal sql_identity+1, native_identity
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'returns bigint for #insert when needed' do
|
185
|
+
return if sqlserver_azure? # We can not alter clustered index like this test does.
|
186
|
+
return if sybase_ase? # On Sybase, sp_helpindex cannot be used inside a transaction since it does a
|
187
|
+
# 'CREATE TABLE' command is not allowed within a multi-statement transaction
|
188
|
+
# and and sp_helpindex creates a temporary table #spindtab.
|
189
|
+
rollback_transaction(@client) do
|
190
|
+
seed = 9223372036854775805
|
191
|
+
@client.execute("DELETE FROM [datatypes]").do
|
192
|
+
id_constraint_name = @client.execute("EXEC sp_helpindex [datatypes]").detect{ |row| row['index_keys'] == 'id' }['index_name']
|
193
|
+
@client.execute("ALTER TABLE [datatypes] DROP CONSTRAINT [#{id_constraint_name}]").do
|
194
|
+
@client.execute("ALTER TABLE [datatypes] DROP COLUMN [id]").do
|
195
|
+
@client.execute("ALTER TABLE [datatypes] ADD [id] [bigint] NOT NULL IDENTITY(1,1) PRIMARY KEY").do
|
196
|
+
@client.execute("DBCC CHECKIDENT ('datatypes', RESEED, #{seed})").do
|
197
|
+
identity = @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('something')").insert
|
198
|
+
assert_equal seed, identity
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'must be able to begin/commit transactions with raw sql' do
|
203
|
+
rollback_transaction(@client) do
|
204
|
+
@client.execute("BEGIN TRANSACTION").do
|
205
|
+
@client.execute("DELETE FROM [datatypes]").do
|
206
|
+
@client.execute("COMMIT TRANSACTION").do
|
207
|
+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
|
208
|
+
assert_equal 0, count
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'must be able to begin/rollback transactions with raw sql' do
|
213
|
+
load_current_schema
|
214
|
+
@client.execute("BEGIN TRANSACTION").do
|
215
|
+
@client.execute("DELETE FROM [datatypes]").do
|
216
|
+
@client.execute("ROLLBACK TRANSACTION").do
|
217
|
+
count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
|
218
|
+
0.wont_equal count
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'has a #fields accessor with logic default and valid outcome' do
|
222
|
+
result = @client.execute(@query1)
|
223
|
+
result.fields.must_equal ['one']
|
224
|
+
result.each
|
225
|
+
result.fields.must_equal ['one']
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'always returns an array for fields for all sql' do
|
229
|
+
result = @client.execute("USE [tinytdstest]")
|
230
|
+
result.fields.must_equal []
|
231
|
+
result.do
|
232
|
+
result.fields.must_equal []
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'returns fields even when no results are found' do
|
236
|
+
no_results_query = "SELECT [id], [varchar_50] FROM [datatypes] WHERE [varchar_50] = 'NOTFOUND'"
|
237
|
+
# Fields before each.
|
238
|
+
result = @client.execute(no_results_query)
|
239
|
+
result.fields.must_equal ['id','varchar_50']
|
240
|
+
result.each
|
241
|
+
result.fields.must_equal ['id','varchar_50']
|
242
|
+
# Each then fields
|
243
|
+
result = @client.execute(no_results_query)
|
244
|
+
result.each
|
245
|
+
result.fields.must_equal ['id','varchar_50']
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'allows the result to be canceled before reading' do
|
249
|
+
result = @client.execute(@query1)
|
250
|
+
result.cancel
|
251
|
+
@client.execute(@query1).each
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'works in tandem with the client when needing to find out if client has sql sent and result is canceled or not' do
|
255
|
+
# Default state.
|
256
|
+
@client = TinyTds::Client.new(connection_options)
|
257
|
+
@client.sqlsent?.must_equal false
|
258
|
+
@client.canceled?.must_equal false
|
259
|
+
# With active result before and after cancel.
|
260
|
+
result = @client.execute(@query1)
|
261
|
+
@client.sqlsent?.must_equal true
|
262
|
+
@client.canceled?.must_equal false
|
263
|
+
result.cancel
|
264
|
+
@client.sqlsent?.must_equal false
|
265
|
+
@client.canceled?.must_equal true
|
266
|
+
assert result.cancel, 'must be safe to call again'
|
267
|
+
# With each and no block.
|
268
|
+
@client.execute(@query1).each
|
269
|
+
@client.sqlsent?.must_equal false
|
270
|
+
@client.canceled?.must_equal false
|
271
|
+
# With each and block.
|
272
|
+
@client.execute(@query1).each do |row|
|
273
|
+
@client.sqlsent?.must_equal true, 'when iterating over each row in a block'
|
274
|
+
@client.canceled?.must_equal false
|
275
|
+
end
|
276
|
+
@client.sqlsent?.must_equal false
|
277
|
+
@client.canceled?.must_equal false
|
278
|
+
# With each and block canceled half way thru.
|
279
|
+
count = @client.execute("SELECT COUNT([id]) AS [count] FROM [datatypes]").each[0]['count']
|
280
|
+
assert count > 10, 'since we want to cancel early for test'
|
281
|
+
result = @client.execute("SELECT [id] FROM [datatypes]")
|
282
|
+
index = 0
|
283
|
+
result.each do |row|
|
284
|
+
break if index > 10
|
285
|
+
index += 1
|
286
|
+
end
|
287
|
+
@client.sqlsent?.must_equal true
|
288
|
+
@client.canceled?.must_equal false
|
289
|
+
result.cancel
|
290
|
+
@client.sqlsent?.must_equal false
|
291
|
+
@client.canceled?.must_equal true
|
292
|
+
# With do method.
|
293
|
+
@client.execute(@query1).do
|
294
|
+
@client.sqlsent?.must_equal false
|
295
|
+
@client.canceled?.must_equal true
|
296
|
+
# With insert method.
|
297
|
+
rollback_transaction(@client) do
|
298
|
+
@client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test')").insert
|
299
|
+
@client.sqlsent?.must_equal false
|
300
|
+
@client.canceled?.must_equal true
|
301
|
+
end
|
302
|
+
# With first
|
303
|
+
@client.execute("SELECT [id] FROM [datatypes]").each(:first => true)
|
304
|
+
@client.sqlsent?.must_equal false
|
305
|
+
@client.canceled?.must_equal true
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'use same string object for hash keys' do
|
309
|
+
data = @client.execute("SELECT [id], [bigint] FROM [datatypes]").each
|
310
|
+
assert_equal data.first.keys.map{ |r| r.object_id }, data.last.keys.map{ |r| r.object_id }
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'has properly encoded column names with symbol keys' do
|
314
|
+
col_name = "öäüß"
|
315
|
+
@client.execute("DROP TABLE [test_encoding]").do rescue nil
|
316
|
+
@client.execute("CREATE TABLE [dbo].[test_encoding] ( [#{col_name}] [nvarchar](10) NOT NULL )").do
|
317
|
+
@client.execute("INSERT INTO [test_encoding] ([#{col_name}]) VALUES (N'#{col_name}')").do
|
318
|
+
result = @client.execute("SELECT [#{col_name}] FROM [test_encoding]")
|
319
|
+
row = result.each(:as => :hash, :symbolize_keys => true).first
|
320
|
+
assert_instance_of Symbol, result.fields.first
|
321
|
+
assert_equal col_name.to_sym, result.fields.first
|
322
|
+
assert_instance_of Symbol, row.keys.first
|
323
|
+
assert_equal col_name.to_sym, row.keys.first
|
324
|
+
end unless sqlserver_azure?
|
325
|
+
|
326
|
+
it 'allows #return_code to work with stored procedures and reset per sql batch' do
|
327
|
+
assert_nil @client.return_code
|
328
|
+
result = @client.execute("EXEC tinytds_TestReturnCodes")
|
329
|
+
assert_equal [{"one"=>1}], result.each
|
330
|
+
assert_equal 420, @client.return_code
|
331
|
+
assert_equal 420, result.return_code
|
332
|
+
result = @client.execute('SELECT 1 as [one]')
|
333
|
+
result.each
|
334
|
+
assert_nil @client.return_code
|
335
|
+
assert_nil result.return_code
|
336
|
+
end
|
337
|
+
|
338
|
+
describe 'with multiple result sets' do
|
339
|
+
|
340
|
+
before do
|
341
|
+
@empty_select = "SELECT 1 AS [rs1] WHERE 1 = 0"
|
342
|
+
@double_select = "SELECT 1 AS [rs1]
|
343
|
+
SELECT 2 AS [rs2]"
|
344
|
+
@triple_select_1st_empty = "SELECT 1 AS [rs1] WHERE 1 = 0
|
345
|
+
SELECT 2 AS [rs2]
|
346
|
+
SELECT 3 AS [rs3]"
|
347
|
+
@triple_select_2nd_empty = "SELECT 1 AS [rs1]
|
348
|
+
SELECT 2 AS [rs2] WHERE 1 = 0
|
349
|
+
SELECT 3 AS [rs3]"
|
350
|
+
@triple_select_3rd_empty = "SELECT 1 AS [rs1]
|
351
|
+
SELECT 2 AS [rs2]
|
352
|
+
SELECT 3 AS [rs3] WHERE 1 = 0"
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'handles a command buffer with double selects' do
|
356
|
+
result = @client.execute(@double_select)
|
357
|
+
result_sets = result.each
|
358
|
+
assert_equal 2, result_sets.size
|
359
|
+
assert_equal [{'rs1' => 1}], result_sets.first
|
360
|
+
assert_equal [{'rs2' => 2}], result_sets.last
|
361
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
362
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
363
|
+
# As array
|
364
|
+
result = @client.execute(@double_select)
|
365
|
+
result_sets = result.each(:as => :array)
|
366
|
+
assert_equal 2, result_sets.size
|
367
|
+
assert_equal [[1]], result_sets.first
|
368
|
+
assert_equal [[2]], result_sets.last
|
369
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
370
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'yields each row for each result set' do
|
374
|
+
data = []
|
375
|
+
result_sets = @client.execute(@double_select).each { |row| data << row }
|
376
|
+
assert_equal data.first, result_sets.first[0]
|
377
|
+
assert_equal data.last, result_sets.last[0]
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'works from a stored procedure' do
|
381
|
+
if sqlserver?
|
382
|
+
results1, results2 = @client.execute("EXEC sp_helpconstraint '[datatypes]'").each
|
383
|
+
assert_equal [{"Object Name"=>"[datatypes]"}], results1
|
384
|
+
constraint_info = results2.first
|
385
|
+
assert constraint_info.key?("constraint_keys")
|
386
|
+
assert constraint_info.key?("constraint_type")
|
387
|
+
assert constraint_info.key?("constraint_name")
|
388
|
+
elsif sybase_ase?
|
389
|
+
results1, results2 = @client.execute("EXEC sp_helpconstraint 'datatypes'").each
|
390
|
+
assert results1['name'] =~ /^datatypes_bit/
|
391
|
+
assert results1['defintion'] == 'DEFAULT 0'
|
392
|
+
assert results2['name'] =~ /^datatypes_id/
|
393
|
+
assert results2['defintion'] =~ /^PRIMARY KEY/
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe 'using :empty_sets TRUE' do
|
398
|
+
|
399
|
+
before do
|
400
|
+
@old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
|
401
|
+
TinyTds::Client.default_query_options[:empty_sets] = true
|
402
|
+
@client = new_connection
|
403
|
+
end
|
404
|
+
|
405
|
+
after do
|
406
|
+
TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'handles a basic empty result set' do
|
410
|
+
result = @client.execute(@empty_select)
|
411
|
+
assert_equal [], result.each
|
412
|
+
assert_equal ['rs1'], result.fields
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'includes empty result sets by default - using 1st empty buffer' do
|
416
|
+
result = @client.execute(@triple_select_1st_empty)
|
417
|
+
result_sets = result.each
|
418
|
+
assert_equal 3, result_sets.size
|
419
|
+
assert_equal [], result_sets[0]
|
420
|
+
assert_equal [{'rs2' => 2}], result_sets[1]
|
421
|
+
assert_equal [{'rs3' => 3}], result_sets[2]
|
422
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
423
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
424
|
+
# As array
|
425
|
+
result = @client.execute(@triple_select_1st_empty)
|
426
|
+
result_sets = result.each(:as => :array)
|
427
|
+
assert_equal 3, result_sets.size
|
428
|
+
assert_equal [], result_sets[0]
|
429
|
+
assert_equal [[2]], result_sets[1]
|
430
|
+
assert_equal [[3]], result_sets[2]
|
431
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
432
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'includes empty result sets by default - using 2nd empty buffer' do
|
436
|
+
result = @client.execute(@triple_select_2nd_empty)
|
437
|
+
result_sets = result.each
|
438
|
+
assert_equal 3, result_sets.size
|
439
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
440
|
+
assert_equal [], result_sets[1]
|
441
|
+
assert_equal [{'rs3' => 3}], result_sets[2]
|
442
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
443
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
444
|
+
# As array
|
445
|
+
result = @client.execute(@triple_select_2nd_empty)
|
446
|
+
result_sets = result.each(:as => :array)
|
447
|
+
assert_equal 3, result_sets.size
|
448
|
+
assert_equal [[1]], result_sets[0]
|
449
|
+
assert_equal [], result_sets[1]
|
450
|
+
assert_equal [[3]], result_sets[2]
|
451
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
452
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
453
|
+
end
|
454
|
+
|
455
|
+
it 'includes empty result sets by default - using 3rd empty buffer' do
|
456
|
+
result = @client.execute(@triple_select_3rd_empty)
|
457
|
+
result_sets = result.each
|
458
|
+
assert_equal 3, result_sets.size
|
459
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
460
|
+
assert_equal [{'rs2' => 2}], result_sets[1]
|
461
|
+
assert_equal [], result_sets[2]
|
462
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
463
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
464
|
+
# As array
|
465
|
+
result = @client.execute(@triple_select_3rd_empty)
|
466
|
+
result_sets = result.each(:as => :array)
|
467
|
+
assert_equal 3, result_sets.size
|
468
|
+
assert_equal [[1]], result_sets[0]
|
469
|
+
assert_equal [[2]], result_sets[1]
|
470
|
+
assert_equal [], result_sets[2]
|
471
|
+
assert_equal [['rs1'], ['rs2'], ['rs3']], result.fields
|
472
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|
476
|
+
|
477
|
+
describe 'using :empty_sets FALSE' do
|
478
|
+
|
479
|
+
before do
|
480
|
+
@old_query_option_value = TinyTds::Client.default_query_options[:empty_sets]
|
481
|
+
TinyTds::Client.default_query_options[:empty_sets] = false
|
482
|
+
@client = new_connection
|
483
|
+
end
|
484
|
+
|
485
|
+
after do
|
486
|
+
TinyTds::Client.default_query_options[:empty_sets] = @old_query_option_value
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'handles a basic empty result set' do
|
490
|
+
result = @client.execute(@empty_select)
|
491
|
+
assert_equal [], result.each
|
492
|
+
assert_equal ['rs1'], result.fields
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'must not include empty result sets by default - using 1st empty buffer' do
|
496
|
+
result = @client.execute(@triple_select_1st_empty)
|
497
|
+
result_sets = result.each
|
498
|
+
assert_equal 2, result_sets.size
|
499
|
+
assert_equal [{'rs2' => 2}], result_sets[0]
|
500
|
+
assert_equal [{'rs3' => 3}], result_sets[1]
|
501
|
+
assert_equal [['rs2'], ['rs3']], result.fields
|
502
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
503
|
+
# As array
|
504
|
+
result = @client.execute(@triple_select_1st_empty)
|
505
|
+
result_sets = result.each(:as => :array)
|
506
|
+
assert_equal 2, result_sets.size
|
507
|
+
assert_equal [[2]], result_sets[0]
|
508
|
+
assert_equal [[3]], result_sets[1]
|
509
|
+
assert_equal [['rs2'], ['rs3']], result.fields
|
510
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
511
|
+
end
|
512
|
+
|
513
|
+
it 'must not include empty result sets by default - using 2nd empty buffer' do
|
514
|
+
result = @client.execute(@triple_select_2nd_empty)
|
515
|
+
result_sets = result.each
|
516
|
+
assert_equal 2, result_sets.size
|
517
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
518
|
+
assert_equal [{'rs3' => 3}], result_sets[1]
|
519
|
+
assert_equal [['rs1'], ['rs3']], result.fields
|
520
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
521
|
+
# As array
|
522
|
+
result = @client.execute(@triple_select_2nd_empty)
|
523
|
+
result_sets = result.each(:as => :array)
|
524
|
+
assert_equal 2, result_sets.size
|
525
|
+
assert_equal [[1]], result_sets[0]
|
526
|
+
assert_equal [[3]], result_sets[1]
|
527
|
+
assert_equal [['rs1'], ['rs3']], result.fields
|
528
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
529
|
+
end
|
530
|
+
|
531
|
+
it 'must not include empty result sets by default - using 3rd empty buffer' do
|
532
|
+
result = @client.execute(@triple_select_3rd_empty)
|
533
|
+
result_sets = result.each
|
534
|
+
assert_equal 2, result_sets.size
|
535
|
+
assert_equal [{'rs1' => 1}], result_sets[0]
|
536
|
+
assert_equal [{'rs2' => 2}], result_sets[1]
|
537
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
538
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
539
|
+
# As array
|
540
|
+
result = @client.execute(@triple_select_3rd_empty)
|
541
|
+
result_sets = result.each(:as => :array)
|
542
|
+
assert_equal 2, result_sets.size
|
543
|
+
assert_equal [[1]], result_sets[0]
|
544
|
+
assert_equal [[2]], result_sets[1]
|
545
|
+
assert_equal [['rs1'], ['rs2']], result.fields
|
546
|
+
assert_equal result.each.object_id, result.each.object_id, 'same cached rows'
|
547
|
+
end
|
548
|
+
|
549
|
+
end
|
550
|
+
|
551
|
+
end
|
552
|
+
|
553
|
+
describe 'Complex query with multiple results sets but no actual results' do
|
554
|
+
it 'must not cancel the query until complete' do
|
555
|
+
@client.execute("
|
556
|
+
BACKUP DATABASE tinytdstest
|
557
|
+
TO DISK = 'C:\\Users\\Public\\tinytdstest.bak'
|
558
|
+
").do
|
559
|
+
end
|
560
|
+
|
561
|
+
after do
|
562
|
+
begin
|
563
|
+
File.delete 'C:\\Users\\Public\\tinytdstest.bak'
|
564
|
+
rescue
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
describe 'when casting to native ruby values' do
|
570
|
+
|
571
|
+
it 'returns fixnum for 1' do
|
572
|
+
value = @client.execute('SELECT 1 AS [fixnum]').each.first['fixnum']
|
573
|
+
assert_equal 1, value
|
574
|
+
end
|
575
|
+
|
576
|
+
it 'returns nil for NULL' do
|
577
|
+
value = @client.execute('SELECT NULL AS [null]').each.first['null']
|
578
|
+
assert_equal nil, value
|
579
|
+
end
|
580
|
+
|
581
|
+
end
|
582
|
+
|
583
|
+
describe 'with data type' do
|
584
|
+
|
585
|
+
describe 'char max' do
|
586
|
+
|
587
|
+
before do
|
588
|
+
@big_text = 'x' * 2_000_000
|
589
|
+
@old_textsize = @client.execute("SELECT @@TEXTSIZE AS [textsize]").each.first['textsize'].inspect
|
590
|
+
@client.execute("SET TEXTSIZE #{(@big_text.length*2)+1}").do
|
591
|
+
end
|
592
|
+
|
593
|
+
it 'must insert and select large varchar_max' do
|
594
|
+
insert_and_select_datatype :varchar_max
|
595
|
+
end
|
596
|
+
|
597
|
+
it 'must insert and select large nvarchar_max' do
|
598
|
+
insert_and_select_datatype :nvarchar_max
|
599
|
+
end
|
600
|
+
|
601
|
+
end unless sqlserver_2000? || sybase_ase?
|
602
|
+
|
603
|
+
end
|
604
|
+
|
605
|
+
describe 'when shit happens' do
|
606
|
+
|
607
|
+
it 'copes with nil or empty buffer' do
|
608
|
+
assert_raises(TypeError) { @client.execute(nil) }
|
609
|
+
assert_equal [], @client.execute('').each
|
610
|
+
end
|
611
|
+
|
612
|
+
if sqlserver?
|
613
|
+
|
614
|
+
it 'must not raise an error when severity is 10 or less' do
|
615
|
+
(1..10).to_a.each do |severity|
|
616
|
+
@client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'raises an error when severity is greater than 10' do
|
621
|
+
action = lambda { @client.execute("RAISERROR(N'Test 11 severity', 11, 1)").do }
|
622
|
+
assert_raise_tinytds_error(action) do |e|
|
623
|
+
assert_equal "Test 11 severity", e.message
|
624
|
+
assert_equal 11, e.severity
|
625
|
+
assert_equal 50000, e.db_error_number
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
else
|
630
|
+
|
631
|
+
it 'raises an error' do
|
632
|
+
action = lambda { @client.execute("RAISERROR 50000 N'Hello World'").do }
|
633
|
+
assert_raise_tinytds_error(action) do |e|
|
634
|
+
assert_equal "Hello World", e.message
|
635
|
+
assert_equal 16, e.severity # predefined on ASE
|
636
|
+
assert_equal 50000, e.db_error_number
|
637
|
+
end
|
638
|
+
assert_followup_query
|
639
|
+
end
|
640
|
+
|
641
|
+
end
|
642
|
+
|
643
|
+
it 'throws an error when you execute another query with other results pending' do
|
644
|
+
result1 = @client.execute(@query1)
|
645
|
+
action = lambda { @client.execute(@query1) }
|
646
|
+
assert_raise_tinytds_error(action) do |e|
|
647
|
+
assert_match %r|with results pending|i, e.message
|
648
|
+
assert_equal 7, e.severity
|
649
|
+
assert_equal 20019, e.db_error_number
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
it 'must error gracefully with bad table name' do
|
654
|
+
action = lambda { @client.execute('SELECT * FROM [foobar]').each }
|
655
|
+
assert_raise_tinytds_error(action) do |e|
|
656
|
+
pattern = sybase_ase? ? /foobar not found/ : %r|invalid object name.*foobar|i
|
657
|
+
assert_match pattern, e.message
|
658
|
+
assert_equal 16, e.severity
|
659
|
+
assert_equal 208, e.db_error_number
|
660
|
+
end
|
661
|
+
assert_followup_query
|
662
|
+
end
|
663
|
+
|
664
|
+
it 'must error gracefully with incorrect syntax' do
|
665
|
+
action = lambda { @client.execute('this will not work').each }
|
666
|
+
assert_raise_tinytds_error(action) do |e|
|
667
|
+
assert_match %r|incorrect syntax|i, e.message
|
668
|
+
assert_equal 15, e.severity
|
669
|
+
assert_equal 156, e.db_error_number
|
670
|
+
end
|
671
|
+
assert_followup_query
|
672
|
+
end
|
673
|
+
|
674
|
+
it 'must not error at all from reading non-convertable charcters and just use ? marks' do
|
675
|
+
@client = new_connection :encoding => 'ASCII'
|
676
|
+
@client.charset.must_equal 'ASCII'
|
677
|
+
find_value(202, :nvarchar_50).must_equal 'test nvarchar_50 ??'
|
678
|
+
end
|
679
|
+
|
680
|
+
it 'must error gracefully from writing non-convertable characters' do
|
681
|
+
@client = new_connection :encoding => 'ASCII'
|
682
|
+
@client.charset.must_equal 'ASCII'
|
683
|
+
rollback_transaction(@client) do
|
684
|
+
text = 'Test ✓'
|
685
|
+
@client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
|
686
|
+
action = lambda { @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES ('#{text}')").do }
|
687
|
+
assert_raise_tinytds_error(action) do |e|
|
688
|
+
e.message.must_match %r{Error converting characters into server's character set}i
|
689
|
+
e.severity.must_equal 4
|
690
|
+
e.db_error_number.must_equal 2402
|
691
|
+
end
|
692
|
+
assert_followup_query
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
it 'errors gracefully with incorrect syntax in sp_executesql' do
|
697
|
+
if @client.freetds_091_or_higer?
|
698
|
+
action = lambda { @client.execute("EXEC sp_executesql N'this will not work'").each }
|
699
|
+
assert_raise_tinytds_error(action) do |e|
|
700
|
+
assert_match %r|incorrect syntax|i, e.message
|
701
|
+
assert_equal 15, e.severity
|
702
|
+
assert_equal 156, e.db_error_number
|
703
|
+
end
|
704
|
+
assert_followup_query
|
705
|
+
else
|
706
|
+
skip 'FreeTDS 0.91 and higher can only pass this test.'
|
707
|
+
end
|
708
|
+
end unless sybase_ase?
|
709
|
+
|
710
|
+
end
|
711
|
+
|
712
|
+
end
|
713
|
+
|
714
|
+
|
715
|
+
protected
|
716
|
+
|
717
|
+
def assert_followup_query
|
718
|
+
result = @client.execute(@query1)
|
719
|
+
assert_equal 1, result.each.first['one']
|
720
|
+
end
|
721
|
+
|
722
|
+
def insert_and_select_datatype(datatype)
|
723
|
+
rollback_transaction(@client) do
|
724
|
+
@client.execute("DELETE FROM [datatypes] WHERE [#{datatype}] IS NOT NULL").do
|
725
|
+
id = @client.execute("INSERT INTO [datatypes] ([#{datatype}]) VALUES (N'#{@big_text}')").insert
|
726
|
+
found_text = find_value id, datatype
|
727
|
+
flunk "Large #{datatype} data with a length of #{@big_text.length} did not match found text with length of #{found_text.length}" unless @big_text == found_text
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
end
|
732
|
+
|