tiny_tds 1.0.4 → 2.1.5

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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +20 -0
  3. data/.gitattributes +1 -0
  4. data/.rubocop.yml +31 -0
  5. data/.travis.yml +25 -0
  6. data/{CHANGELOG → CHANGELOG.md} +102 -26
  7. data/Gemfile +4 -1
  8. data/ISSUE_TEMPLATE.md +35 -2
  9. data/README.md +131 -56
  10. data/Rakefile +31 -88
  11. data/VERSION +1 -1
  12. data/appveyor.yml +38 -17
  13. data/docker-compose.yml +22 -0
  14. data/ext/tiny_tds/client.c +147 -60
  15. data/ext/tiny_tds/client.h +11 -5
  16. data/ext/tiny_tds/extconf.rb +41 -297
  17. data/ext/tiny_tds/extconsts.rb +7 -7
  18. data/ext/tiny_tds/result.c +40 -15
  19. data/lib/tiny_tds/bin.rb +45 -27
  20. data/lib/tiny_tds/client.rb +46 -34
  21. data/lib/tiny_tds/error.rb +0 -1
  22. data/lib/tiny_tds/gem.rb +32 -0
  23. data/lib/tiny_tds/result.rb +2 -3
  24. data/lib/tiny_tds/version.rb +1 -1
  25. data/lib/tiny_tds.rb +38 -14
  26. data/{ports/patches/freetds/1.00 → patches/freetds/1.00.27}/0001-mingw_missing_inet_pton.diff +4 -4
  27. data/patches/freetds/1.00.27/0002-Don-t-use-MSYS2-file-libws2_32.diff +28 -0
  28. data/patches/libiconv/1.14/1-avoid-gets-error.patch +17 -0
  29. data/tasks/native_gem.rake +14 -0
  30. data/tasks/package.rake +8 -0
  31. data/tasks/ports/freetds.rb +37 -0
  32. data/tasks/ports/libiconv.rb +43 -0
  33. data/tasks/ports/openssl.rb +62 -0
  34. data/tasks/ports/recipe.rb +52 -0
  35. data/tasks/ports.rake +85 -0
  36. data/tasks/test.rake +9 -0
  37. data/test/appveyor/dbsetup.ps1 +1 -1
  38. data/test/bin/install-freetds.sh +20 -0
  39. data/test/bin/install-openssl.sh +18 -0
  40. data/test/bin/setup.sh +19 -0
  41. data/test/client_test.rb +124 -66
  42. data/test/gem_test.rb +179 -0
  43. data/test/result_test.rb +128 -42
  44. data/test/schema/sqlserver_2016.sql +140 -0
  45. data/test/schema_test.rb +23 -23
  46. data/test/test_helper.rb +65 -7
  47. data/test/thread_test.rb +1 -1
  48. data/tiny_tds.gemspec +9 -7
  49. metadata +60 -20
  50. /data/bin/{defncopy → defncopy-ttds} +0 -0
  51. /data/bin/{tsql → tsql-ttds} +0 -0
data/test/result_test.rb CHANGED
@@ -80,7 +80,7 @@ class ResultTest < TinyTds::TestCase
80
80
  @client.execute("DELETE FROM [datatypes]").do
81
81
  @client.execute("INSERT INTO [datatypes] ([char_10], [varchar_50]) VALUES ('1', '2')").do
82
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"
83
+ _(result).must_equal "1 test2"
84
84
  end
85
85
  end
86
86
 
@@ -135,10 +135,10 @@ class ResultTest < TinyTds::TestCase
135
135
  text = 'test affected rows sql'
136
136
  @client.execute("DELETE FROM [datatypes]").do
137
137
  afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
138
- assert_instance_of Fixnum, afrows
138
+ _(['Fixnum', 'Integer']).must_include afrows.class.name
139
139
  @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('#{text}')").do
140
140
  pk1 = @client.execute(@client.identity_sql).each.first['Ident']
141
- assert_instance_of Fixnum, pk1, 'we it be able to CAST to bigint'
141
+ _(['Fixnum', 'Integer']).must_include pk1.class.name, 'we it be able to CAST to bigint'
142
142
  @client.execute("UPDATE [datatypes] SET [varchar_50] = NULL WHERE [varchar_50] = '#{text}'").do
143
143
  afrows = @client.execute("SELECT @@ROWCOUNT AS AffectedRows").each.first['AffectedRows']
144
144
  assert_equal 1, afrows
@@ -215,34 +215,34 @@ class ResultTest < TinyTds::TestCase
215
215
  @client.execute("DELETE FROM [datatypes]").do
216
216
  @client.execute("ROLLBACK TRANSACTION").do
217
217
  count = @client.execute("SELECT COUNT(*) AS [count] FROM [datatypes]").each.first['count']
218
- 0.wont_equal count
218
+ _(count).wont_equal 0
219
219
  end
220
220
 
221
221
  it 'has a #fields accessor with logic default and valid outcome' do
222
222
  result = @client.execute(@query1)
223
- result.fields.must_equal ['one']
223
+ _(result.fields).must_equal ['one']
224
224
  result.each
225
- result.fields.must_equal ['one']
225
+ _(result.fields).must_equal ['one']
226
226
  end
227
227
 
228
228
  it 'always returns an array for fields for all sql' do
229
229
  result = @client.execute("USE [tinytdstest]")
230
- result.fields.must_equal []
230
+ _(result.fields).must_equal []
231
231
  result.do
232
- result.fields.must_equal []
232
+ _(result.fields).must_equal []
233
233
  end
234
234
 
235
235
  it 'returns fields even when no results are found' do
236
236
  no_results_query = "SELECT [id], [varchar_50] FROM [datatypes] WHERE [varchar_50] = 'NOTFOUND'"
237
237
  # Fields before each.
238
238
  result = @client.execute(no_results_query)
239
- result.fields.must_equal ['id','varchar_50']
239
+ _(result.fields).must_equal ['id','varchar_50']
240
240
  result.each
241
- result.fields.must_equal ['id','varchar_50']
241
+ _(result.fields).must_equal ['id','varchar_50']
242
242
  # Each then fields
243
243
  result = @client.execute(no_results_query)
244
244
  result.each
245
- result.fields.must_equal ['id','varchar_50']
245
+ _(result.fields).must_equal ['id','varchar_50']
246
246
  end
247
247
 
248
248
  it 'allows the result to be canceled before reading' do
@@ -254,27 +254,27 @@ class ResultTest < TinyTds::TestCase
254
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
255
  # Default state.
256
256
  @client = TinyTds::Client.new(connection_options)
257
- @client.sqlsent?.must_equal false
258
- @client.canceled?.must_equal false
257
+ _(@client.sqlsent?).must_equal false
258
+ _(@client.canceled?).must_equal false
259
259
  # With active result before and after cancel.
260
260
  result = @client.execute(@query1)
261
- @client.sqlsent?.must_equal true
262
- @client.canceled?.must_equal false
261
+ _(@client.sqlsent?).must_equal true
262
+ _(@client.canceled?).must_equal false
263
263
  result.cancel
264
- @client.sqlsent?.must_equal false
265
- @client.canceled?.must_equal true
264
+ _(@client.sqlsent?).must_equal false
265
+ _(@client.canceled?).must_equal true
266
266
  assert result.cancel, 'must be safe to call again'
267
267
  # With each and no block.
268
268
  @client.execute(@query1).each
269
- @client.sqlsent?.must_equal false
270
- @client.canceled?.must_equal false
269
+ _(@client.sqlsent?).must_equal false
270
+ _(@client.canceled?).must_equal false
271
271
  # With each and block.
272
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
273
+ _(@client.sqlsent?).must_equal true, 'when iterating over each row in a block'
274
+ _(@client.canceled?).must_equal false
275
275
  end
276
- @client.sqlsent?.must_equal false
277
- @client.canceled?.must_equal false
276
+ _(@client.sqlsent?).must_equal false
277
+ _(@client.canceled?).must_equal false
278
278
  # With each and block canceled half way thru.
279
279
  count = @client.execute("SELECT COUNT([id]) AS [count] FROM [datatypes]").each[0]['count']
280
280
  assert count > 10, 'since we want to cancel early for test'
@@ -284,25 +284,25 @@ class ResultTest < TinyTds::TestCase
284
284
  break if index > 10
285
285
  index += 1
286
286
  end
287
- @client.sqlsent?.must_equal true
288
- @client.canceled?.must_equal false
287
+ _(@client.sqlsent?).must_equal true
288
+ _(@client.canceled?).must_equal false
289
289
  result.cancel
290
- @client.sqlsent?.must_equal false
291
- @client.canceled?.must_equal true
290
+ _(@client.sqlsent?).must_equal false
291
+ _(@client.canceled?).must_equal true
292
292
  # With do method.
293
293
  @client.execute(@query1).do
294
- @client.sqlsent?.must_equal false
295
- @client.canceled?.must_equal true
294
+ _(@client.sqlsent?).must_equal false
295
+ _(@client.canceled?).must_equal true
296
296
  # With insert method.
297
297
  rollback_transaction(@client) do
298
298
  @client.execute("INSERT INTO [datatypes] ([varchar_50]) VALUES ('test')").insert
299
- @client.sqlsent?.must_equal false
300
- @client.canceled?.must_equal true
299
+ _(@client.sqlsent?).must_equal false
300
+ _(@client.canceled?).must_equal true
301
301
  end
302
302
  # With first
303
303
  @client.execute("SELECT [id] FROM [datatypes]").each(:first => true)
304
- @client.sqlsent?.must_equal false
305
- @client.canceled?.must_equal true
304
+ _(@client.sqlsent?).must_equal false
305
+ _(@client.canceled?).must_equal true
306
306
  end
307
307
 
308
308
  it 'use same string object for hash keys' do
@@ -335,6 +335,11 @@ class ResultTest < TinyTds::TestCase
335
335
  assert_nil result.return_code
336
336
  end
337
337
 
338
+ it 'with LOGINPROPERTY function' do
339
+ v = @client.execute("SELECT LOGINPROPERTY('sa', 'IsLocked') as v").first['v']
340
+ _(v).must_equal 0
341
+ end
342
+
338
343
  describe 'with multiple result sets' do
339
344
 
340
345
  before do
@@ -573,7 +578,7 @@ class ResultTest < TinyTds::TestCase
573
578
 
574
579
  it 'returns nil for NULL' do
575
580
  value = @client.execute('SELECT NULL AS [null]').each.first['null']
576
- assert_equal nil, value
581
+ assert_nil value
577
582
  end
578
583
 
579
584
  end
@@ -609,6 +614,88 @@ class ResultTest < TinyTds::TestCase
609
614
 
610
615
  if sqlserver?
611
616
 
617
+ describe 'using :message_handler option' do
618
+ let(:messages) { Array.new }
619
+
620
+ before do
621
+ close_client
622
+ @client = new_connection message_handler: Proc.new { |m| messages << m }
623
+ end
624
+
625
+ after do
626
+ messages.clear
627
+ end
628
+
629
+ it 'has a message handler that responds to call' do
630
+ assert @client.message_handler.respond_to?(:call)
631
+ end
632
+
633
+ it 'calls the provided message handler when severity is 10 or less' do
634
+ (1..10).to_a.each do |severity|
635
+ messages.clear
636
+ msg = "Test #{severity} severity"
637
+ state = rand(1..255)
638
+ @client.execute("RAISERROR(N'#{msg}', #{severity}, #{state})").do
639
+ m = messages.first
640
+ assert_equal 1, messages.length, 'there should be one message after one raiserror'
641
+ assert_equal msg, m.message, 'message text'
642
+ assert_equal severity, m.severity, 'message severity' unless severity == 10 && m.severity.to_i == 0
643
+ assert_equal state, m.os_error_number, 'message state'
644
+ end
645
+ end
646
+
647
+ it 'calls the provided message handler for `print` messages' do
648
+ messages.clear
649
+ msg = 'hello'
650
+ @client.execute("PRINT '#{msg}'").do
651
+ m = messages.first
652
+ assert_equal 1, messages.length, 'there should be one message after one print statement'
653
+ assert_equal msg, m.message, 'message text'
654
+ end
655
+
656
+ it 'must raise an error preceded by a `print` message' do
657
+ messages.clear
658
+ action = lambda { @client.execute("EXEC tinytds_TestPrintWithError").do }
659
+ assert_raise_tinytds_error(action) do |e|
660
+ assert_equal 'hello', messages.first.message, 'message text'
661
+
662
+ assert_equal "Error following print", e.message
663
+ assert_equal 16, e.severity
664
+ assert_equal 50000, e.db_error_number
665
+ end
666
+ end
667
+
668
+ it 'calls the provided message handler for each of a series of `print` messages' do
669
+ messages.clear
670
+ @client.execute("EXEC tinytds_TestSeveralPrints").do
671
+ assert_equal ['hello 1', 'hello 2', 'hello 3'], messages.map { |e| e.message }, 'message list'
672
+ end
673
+
674
+ it 'should flush info messages before raising error in cases of timeout' do
675
+ @client = new_connection timeout: 1, message_handler: Proc.new { |m| messages << m }
676
+ action = lambda { @client.execute("print 'hello'; waitfor delay '00:00:02'").do }
677
+ messages.clear
678
+ assert_raise_tinytds_error(action) do |e|
679
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
680
+ assert_equal 6, e.severity
681
+ assert_equal 20003, e.db_error_number
682
+ assert_equal 'hello', messages.first&.message, 'message text'
683
+ end
684
+ end
685
+
686
+ it 'should print info messages before raising error in cases of timeout' do
687
+ @client = new_connection timeout: 1, message_handler: Proc.new { |m| messages << m }
688
+ action = lambda { @client.execute("raiserror('hello', 1, 1) with nowait; waitfor delay '00:00:02'").do }
689
+ messages.clear
690
+ assert_raise_tinytds_error(action) do |e|
691
+ assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
692
+ assert_equal 6, e.severity
693
+ assert_equal 20003, e.db_error_number
694
+ assert_equal 'hello', messages.first&.message, 'message text'
695
+ end
696
+ end
697
+ end
698
+
612
699
  it 'must not raise an error when severity is 10 or less' do
613
700
  (1..10).to_a.each do |severity|
614
701
  @client.execute("RAISERROR(N'Test #{severity} severity', #{severity}, 1)").do
@@ -639,7 +726,7 @@ class ResultTest < TinyTds::TestCase
639
726
  end
640
727
 
641
728
  it 'throws an error when you execute another query with other results pending' do
642
- result1 = @client.execute(@query1)
729
+ @client.execute(@query1)
643
730
  action = lambda { @client.execute(@query1) }
644
731
  assert_raise_tinytds_error(action) do |e|
645
732
  assert_match %r|with results pending|i, e.message
@@ -672,22 +759,22 @@ class ResultTest < TinyTds::TestCase
672
759
  it 'must not error at all from reading non-convertable charcters and just use ? marks' do
673
760
  close_client
674
761
  @client = new_connection :encoding => 'ASCII'
675
- @client.charset.must_equal 'ASCII'
676
- find_value(202, :nvarchar_50).must_equal 'test nvarchar_50 ??'
762
+ _(@client.charset).must_equal 'ASCII'
763
+ _(find_value(202, :nvarchar_50)).must_equal 'test nvarchar_50 ??'
677
764
  end
678
765
 
679
766
  it 'must error gracefully from writing non-convertable characters' do
680
767
  close_client
681
768
  @client = new_connection :encoding => 'ASCII'
682
- @client.charset.must_equal 'ASCII'
769
+ _(@client.charset).must_equal 'ASCII'
683
770
  rollback_transaction(@client) do
684
771
  text = 'Test ✓'
685
772
  @client.execute("DELETE FROM [datatypes] WHERE [nvarchar_50] IS NOT NULL").do
686
773
  action = lambda { @client.execute("INSERT INTO [datatypes] ([nvarchar_50]) VALUES ('#{text}')").do }
687
774
  assert_raise_tinytds_error(action) do |e|
688
- e.message.must_match %r{Unclosed quotation mark}i
689
- e.severity.must_equal 15
690
- e.db_error_number.must_equal 105
775
+ _(e.message).must_match %r{Unclosed quotation mark}i
776
+ _(e.severity).must_equal 15
777
+ _(e.db_error_number).must_equal 105
691
778
  end
692
779
  assert_followup_query
693
780
  end
@@ -725,4 +812,3 @@ class ResultTest < TinyTds::TestCase
725
812
  end
726
813
 
727
814
  end
728
-
@@ -0,0 +1,140 @@
1
+
2
+ /*
3
+
4
+ * Binary Data - Our test binary data is a 1 pixel gif. The basic (raw) data is below. Quoting this data
5
+ would involve this (encode) method and be (encoded) with the 0x prefix for raw SQL. In other clients the
6
+ (raw_db) value without the 0x prefix would need to be (packed) again yield the original (raw) value.
7
+
8
+ (raw) - "GIF89a\001\000\001\000\221\000\000\377\377\377\377\377\377\376\001\002\000\000\000!\371\004\004\024\000\377\000,\000\000\000\000\001\000\001\000\000\002\002D\001\000;"
9
+ (encode) - "0x#{raw.unpack("H*")[0]}"
10
+ (encoded) - "0x47494638396101000100910000fffffffffffffe010200000021f904041400ff002c00000000010001000002024401003b"
11
+ (raw_db) - "47494638396101000100910000fffffffffffffe010200000021f904041400ff002c00000000010001000002024401003b"
12
+ (packed) - [raw_db].pack('H*')
13
+
14
+ */
15
+
16
+ CREATE TABLE [dbo].[datatypes] (
17
+ [id] [int] NOT NULL IDENTITY(1,1) PRIMARY KEY,
18
+ [bigint] [bigint] NULL,
19
+ [binary_50] [binary](50) NULL,
20
+ [bit] [bit] NULL,
21
+ [char_10] [char](10) NULL,
22
+ [date] [date] NULL,
23
+ [datetime] [datetime] NULL,
24
+ [datetime2_7] [datetime2](7) NULL,
25
+ [datetime2_2] [datetime2](2) NULL,
26
+ [datetimeoffset_2] [datetimeoffset](2) NULL,
27
+ [datetimeoffset_7] [datetimeoffset](7) NULL,
28
+ [decimal_9_2] [decimal](9, 2) NULL,
29
+ [decimal_16_4] [decimal](16, 4) NULL,
30
+ [float] [float] NULL,
31
+ [geography] [geography] NULL,
32
+ [geometry] [geometry] NULL,
33
+ [hierarchyid] [hierarchyid] NULL,
34
+ [image] [image] NULL,
35
+ [int] [int] NULL,
36
+ [money] [money] NULL,
37
+ [nchar_10] [nchar](10) NULL,
38
+ [ntext] [ntext] NULL,
39
+ [numeric_18_0] [numeric](18, 0) NULL,
40
+ [numeric_36_2] [numeric](36, 2) NULL,
41
+ [nvarchar_50] [nvarchar](50) NULL,
42
+ [nvarchar_max] [nvarchar](max) NULL,
43
+ [real] [real] NULL,
44
+ [smalldatetime] [smalldatetime] NULL,
45
+ [smallint] [smallint] NULL,
46
+ [smallmoney] [smallmoney] NULL,
47
+ [text] [text] NULL,
48
+ [time_2] [time](2) NULL,
49
+ [time_7] [time](7) NULL,
50
+ [timestamp] [timestamp] NULL,
51
+ [tinyint] [tinyint] NULL,
52
+ [uniqueidentifier] [uniqueidentifier] NULL,
53
+ [varbinary_50] [varbinary](50) NULL,
54
+ [varbinary_max] [varbinary](max) NULL,
55
+ [varchar_50] [varchar](50) NULL,
56
+ [varchar_max] [varchar](max) NULL,
57
+ [xml] [xml] NULL
58
+ ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
59
+
60
+ SET IDENTITY_INSERT [datatypes] ON
61
+
62
+ INSERT INTO [datatypes] ([id], [bigint]) VALUES ( 11, -9223372036854775807 )
63
+ INSERT INTO [datatypes] ([id], [bigint]) VALUES ( 12, 9223372036854775806 )
64
+ INSERT INTO [datatypes] ([id], [binary_50]) VALUES ( 21, 0x47494638396101000100910000fffffffffffffe010200000021f904041400ff002c00000000010001000002024401003b )
65
+ INSERT INTO [datatypes] ([id], [bit]) VALUES ( 31, 1 )
66
+ INSERT INTO [datatypes] ([id], [bit]) VALUES ( 32, 0 )
67
+ INSERT INTO [datatypes] ([id], [char_10]) VALUES ( 41, '1234567890' )
68
+ INSERT INTO [datatypes] ([id], [char_10]) VALUES ( 42, '12345678' )
69
+ INSERT INTO [datatypes] ([id], [date]) VALUES ( 51, '0001-01-01' )
70
+ INSERT INTO [datatypes] ([id], [date]) VALUES ( 52, '9999-12-31' )
71
+ INSERT INTO [datatypes] ([id], [datetime]) VALUES ( 61, '1753-01-01T00:00:00.000' )
72
+ INSERT INTO [datatypes] ([id], [datetime]) VALUES ( 62, '9999-12-31T23:59:59.997' )
73
+ INSERT INTO [datatypes] ([id], [datetime]) VALUES ( 63, '2010-01-01T12:34:56.123' )
74
+ INSERT INTO [datatypes] ([id], [datetime2_7]) VALUES ( 71, '0001-01-01 00:00:00.0000000' )
75
+ INSERT INTO [datatypes] ([id], [datetime2_7]) VALUES ( 72, '1984-01-24 04:20:00.0000000' )
76
+ INSERT INTO [datatypes] ([id], [datetime2_7]) VALUES ( 73, '9999-12-31 23:59:59.9999999' )
77
+ INSERT INTO [datatypes] ([id], [datetime2_2]) VALUES ( 74, '9999-12-31 23:59:59.123456789' )
78
+ INSERT INTO [datatypes] ([id], [datetimeoffset_2]) VALUES ( 81, '1984-01-24T04:20:00.1234567-08:00' ) -- 1984-01-24 04:20:00.00 -08:00
79
+ INSERT INTO [datatypes] ([id], [datetimeoffset_2]) VALUES ( 82, '1984-01-24T04:20:00.0000000Z' ) -- 1984-01-24 04:20:00.00 +00:00
80
+ INSERT INTO [datatypes] ([id], [datetimeoffset_2]) VALUES ( 83, '9999-12-31T23:59:59.9999999Z' ) -- 9999-12-31 23:59:59.99 +00:00
81
+ INSERT INTO [datatypes] ([id], [datetimeoffset_7]) VALUES ( 84, '1984-01-24T04:20:59.1234567-08:00' ) -- 1984-01-24 04:20:59.1234567 -08:00
82
+ INSERT INTO [datatypes] ([id], [datetimeoffset_7]) VALUES ( 85, '1984-01-24T04:20:00.0000000Z' ) -- 1984-01-24 04:20:00.0000000 +00:00
83
+ INSERT INTO [datatypes] ([id], [datetimeoffset_7]) VALUES ( 86, '9999-12-31T23:59:59.9999999Z' ) -- 9999-12-31 23:59:59.9999999 +00:00
84
+ INSERT INTO [datatypes] ([id], [decimal_9_2]) VALUES ( 91, 12345.01 )
85
+ INSERT INTO [datatypes] ([id], [decimal_9_2]) VALUES ( 92, 1234567.89 )
86
+ INSERT INTO [datatypes] ([id], [decimal_16_4]) VALUES ( 93, 0.0 )
87
+ INSERT INTO [datatypes] ([id], [decimal_16_4]) VALUES ( 94, 123456789012.3456 )
88
+ INSERT INTO [datatypes] ([id], [float]) VALUES ( 101, 123.00000001 )
89
+ INSERT INTO [datatypes] ([id], [float]) VALUES ( 102, 0.0 )
90
+ INSERT INTO [datatypes] ([id], [float]) VALUES ( 103, 123.45 )
91
+ INSERT INTO [datatypes] ([id], [geography]) VALUES ( 111, geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326) ) -- 0xE610000001148716D9CEF7D34740D7A3703D0A975EC08716D9CEF7D34740CBA145B6F3955EC0
92
+ INSERT INTO [datatypes] ([id], [geometry]) VALUES ( 121, geometry::STGeomFromText('LINESTRING (100 100, 20 180, 180 180)', 0) ) -- 0x0000000001040300000000000000000059400000000000005940000000000000344000000000008066400000000000806640000000000080664001000000010000000001000000FFFFFFFF0000000002
93
+ INSERT INTO [datatypes] ([id], [hierarchyid]) VALUES ( 131, CAST('/1/' AS hierarchyid) ) -- 0x58
94
+ INSERT INTO [datatypes] ([id], [hierarchyid]) VALUES ( 132, CAST('/2/' AS hierarchyid) ) -- 0x68
95
+ INSERT INTO [datatypes] ([id], [image]) VALUES ( 141, 0x47494638396101000100910000fffffffffffffe010200000021f904041400ff002c00000000010001000002024401003b )
96
+ INSERT INTO [datatypes] ([id], [int]) VALUES ( 151, -2147483647 )
97
+ INSERT INTO [datatypes] ([id], [int]) VALUES ( 152, 2147483646 )
98
+ INSERT INTO [datatypes] ([id], [money]) VALUES ( 161, 4.20 )
99
+ INSERT INTO [datatypes] ([id], [money]) VALUES ( 162, -922337203685477.5807 )
100
+ INSERT INTO [datatypes] ([id], [money]) VALUES ( 163, 922337203685477.5806 )
101
+ INSERT INTO [datatypes] ([id], [nchar_10]) VALUES ( 171, N'1234567890' )
102
+ INSERT INTO [datatypes] ([id], [nchar_10]) VALUES ( 172, N'123456åå' )
103
+ INSERT INTO [datatypes] ([id], [nchar_10]) VALUES ( 173, N'abc123' )
104
+ INSERT INTO [datatypes] ([id], [ntext]) VALUES ( 181, N'test ntext' )
105
+ INSERT INTO [datatypes] ([id], [ntext]) VALUES ( 182, N'test ntext åå' )
106
+ INSERT INTO [datatypes] ([id], [numeric_18_0]) VALUES ( 191, 191 )
107
+ INSERT INTO [datatypes] ([id], [numeric_18_0]) VALUES ( 192, 123456789012345678 )
108
+ INSERT INTO [datatypes] ([id], [numeric_36_2]) VALUES ( 193, 12345678901234567890.01 )
109
+ INSERT INTO [datatypes] ([id], [numeric_36_2]) VALUES ( 194, 123.46 )
110
+ INSERT INTO [datatypes] ([id], [nvarchar_50]) VALUES ( 201, N'test nvarchar_50' )
111
+ INSERT INTO [datatypes] ([id], [nvarchar_50]) VALUES ( 202, N'test nvarchar_50 åå' )
112
+ INSERT INTO [datatypes] ([id], [nvarchar_max]) VALUES ( 211, N'test nvarchar_max' )
113
+ INSERT INTO [datatypes] ([id], [nvarchar_max]) VALUES ( 212, N'test nvarchar_max åå' )
114
+ INSERT INTO [datatypes] ([id], [real]) VALUES ( 221, 123.45 )
115
+ INSERT INTO [datatypes] ([id], [real]) VALUES ( 222, 0.0 )
116
+ INSERT INTO [datatypes] ([id], [real]) VALUES ( 223, 0.00001 )
117
+ INSERT INTO [datatypes] ([id], [smalldatetime]) VALUES ( 231, '1901-01-01T15:45:00.000Z' ) -- 1901-01-01 15:45:00
118
+ INSERT INTO [datatypes] ([id], [smalldatetime]) VALUES ( 232, '2078-06-05T04:20:00.000Z' ) -- 2078-06-05 04:20:00
119
+ INSERT INTO [datatypes] ([id], [smallint]) VALUES ( 241, -32767 )
120
+ INSERT INTO [datatypes] ([id], [smallint]) VALUES ( 242, 32766 )
121
+ INSERT INTO [datatypes] ([id], [smallmoney]) VALUES ( 251, 4.20 )
122
+ INSERT INTO [datatypes] ([id], [smallmoney]) VALUES ( 252, -214748.3647 )
123
+ INSERT INTO [datatypes] ([id], [smallmoney]) VALUES ( 253, 214748.3646 )
124
+ INSERT INTO [datatypes] ([id], [text]) VALUES ( 271, 'test text' )
125
+ INSERT INTO [datatypes] ([id], [time_2]) VALUES ( 281, '15:45:00.709714966' ) -- 15:45:00.71
126
+ INSERT INTO [datatypes] ([id], [time_2]) VALUES ( 282, '04:20:00.288321545' ) -- 04:20:00.29
127
+ INSERT INTO [datatypes] ([id], [time_7]) VALUES ( 283, '15:45:00.709714966' ) -- 15:45:00.709714900
128
+ INSERT INTO [datatypes] ([id], [time_7]) VALUES ( 284, '04:20:00.288321545' ) -- 04:20:00.288321500
129
+ INSERT INTO [datatypes] ([id], [tinyint]) VALUES ( 301, 0 )
130
+ INSERT INTO [datatypes] ([id], [tinyint]) VALUES ( 302, 255 )
131
+ INSERT INTO [datatypes] ([id], [uniqueidentifier]) VALUES ( 311, NEWID() )
132
+ INSERT INTO [datatypes] ([id], [varbinary_50]) VALUES ( 321, 0x47494638396101000100910000fffffffffffffe010200000021f904041400ff002c00000000010001000002024401003b )
133
+ INSERT INTO [datatypes] ([id], [varbinary_max]) VALUES ( 331, 0x47494638396101000100910000fffffffffffffe010200000021f904041400ff002c00000000010001000002024401003b )
134
+ INSERT INTO [datatypes] ([id], [varchar_50]) VALUES ( 341, 'test varchar_50' )
135
+ INSERT INTO [datatypes] ([id], [varchar_max]) VALUES ( 351, 'test varchar_max' )
136
+ INSERT INTO [datatypes] ([id], [xml]) VALUES ( 361, '<foo><bar>batz</bar></foo>' )
137
+
138
+ SET IDENTITY_INSERT [datatypes] OFF
139
+
140
+
data/test/schema_test.rb CHANGED
@@ -14,7 +14,7 @@ class SchemaTest < TinyTds::TestCase
14
14
  describe 'for shared types' do
15
15
 
16
16
  it 'casts bigint' do
17
- assert_equal -9223372036854775807, find_value(11, :bigint)
17
+ assert_equal (-9223372036854775807), find_value(11, :bigint)
18
18
  assert_equal 9223372036854775806, find_value(12, :bigint)
19
19
  end
20
20
 
@@ -27,7 +27,7 @@ class SchemaTest < TinyTds::TestCase
27
27
  it 'casts bit' do
28
28
  assert_equal true, find_value(31, :bit)
29
29
  assert_equal false, find_value(32, :bit)
30
- assert_equal nil, find_value(21, :bit)
30
+ assert_nil find_value(21, :bit)
31
31
  end
32
32
 
33
33
  it 'casts char' do
@@ -76,10 +76,10 @@ class SchemaTest < TinyTds::TestCase
76
76
 
77
77
  it 'casts decimal' do
78
78
  assert_instance_of BigDecimal, find_value(91, :decimal_9_2)
79
- assert_equal BigDecimal.new('12345.01'), find_value(91, :decimal_9_2)
80
- assert_equal BigDecimal.new('1234567.89'), find_value(92, :decimal_9_2)
81
- assert_equal BigDecimal.new('0.0'), find_value(93, :decimal_16_4)
82
- assert_equal BigDecimal.new('123456789012.3456'), find_value(94, :decimal_16_4)
79
+ assert_equal BigDecimal('12345.01'), find_value(91, :decimal_9_2)
80
+ assert_equal BigDecimal('1234567.89'), find_value(92, :decimal_9_2)
81
+ assert_equal BigDecimal('0.0'), find_value(93, :decimal_16_4)
82
+ assert_equal BigDecimal('123456789012.3456'), find_value(94, :decimal_16_4)
83
83
  end
84
84
 
85
85
  it 'casts float' do
@@ -96,15 +96,15 @@ class SchemaTest < TinyTds::TestCase
96
96
  end
97
97
 
98
98
  it 'casts int' do
99
- assert_equal -2147483647, find_value(151, :int)
99
+ assert_equal (-2147483647), find_value(151, :int)
100
100
  assert_equal 2147483646, find_value(152, :int)
101
101
  end
102
102
 
103
103
  it 'casts money' do
104
104
  assert_instance_of BigDecimal, find_value(161, :money)
105
- assert_equal BigDecimal.new('4.20'), find_value(161, :money)
106
- assert_equal BigDecimal.new('922337203685477.5806'), find_value(163 ,:money)
107
- assert_equal BigDecimal.new('-922337203685477.5807'), find_value(162 ,:money)
105
+ assert_equal BigDecimal('4.20'), find_value(161, :money)
106
+ assert_equal BigDecimal('922337203685477.5806'), find_value(163 ,:money)
107
+ assert_equal BigDecimal('-922337203685477.5807'), find_value(162 ,:money)
108
108
  end
109
109
 
110
110
  it 'casts nchar' do
@@ -170,15 +170,15 @@ class SchemaTest < TinyTds::TestCase
170
170
  end
171
171
 
172
172
  it 'casts smallint' do
173
- assert_equal -32767, find_value(241, :smallint)
173
+ assert_equal (-32767), find_value(241, :smallint)
174
174
  assert_equal 32766, find_value(242, :smallint)
175
175
  end
176
176
 
177
177
  it 'casts smallmoney' do
178
178
  assert_instance_of BigDecimal, find_value(251, :smallmoney)
179
- assert_equal BigDecimal.new("4.20"), find_value(251, :smallmoney)
180
- assert_equal BigDecimal.new("-214748.3647"), find_value(252, :smallmoney)
181
- assert_equal BigDecimal.new("214748.3646"), find_value(253, :smallmoney)
179
+ assert_equal BigDecimal("4.20"), find_value(251, :smallmoney)
180
+ assert_equal BigDecimal("-214748.3647"), find_value(252, :smallmoney)
181
+ assert_equal BigDecimal("214748.3646"), find_value(253, :smallmoney)
182
182
  end
183
183
 
184
184
  it 'casts text' do
@@ -396,15 +396,15 @@ class SchemaTest < TinyTds::TestCase
396
396
  if @client.tds_73?
397
397
  assertions = lambda {
398
398
  assert_instance_of Time, v
399
- assert_equal 1984, v.year, 'Year'
400
- assert_equal 1, v.month, 'Month'
401
- assert_equal 24, v.day, 'Day'
402
- assert_equal 4, v.hour, 'Hour'
403
- assert_equal 20, v.min, 'Minute'
404
- assert_equal 59, v.sec, 'Second'
405
- assert_equal 123456, v.usec, 'Microseconds'
406
- assert_equal 123456700, v.nsec, 'Nanoseconds'
407
- assert_equal -28800, v.utc_offset, 'Offset'
399
+ assert_equal 1984, v.year, 'Year'
400
+ assert_equal 1, v.month, 'Month'
401
+ assert_equal 24, v.day, 'Day'
402
+ assert_equal 4, v.hour, 'Hour'
403
+ assert_equal 20, v.min, 'Minute'
404
+ assert_equal 59, v.sec, 'Second'
405
+ assert_equal 123456, v.usec, 'Microseconds'
406
+ assert_equal 123456700, v.nsec, 'Nanoseconds'
407
+ assert_equal (-28800), v.utc_offset, 'Offset'
408
408
  }
409
409
  assertions.call
410
410
  v = find_value 84, :datetimeoffset_7, timezone: :local
data/test/test_helper.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  require 'bundler' ; Bundler.require :development, :test
3
3
  require 'tiny_tds'
4
4
  require 'minitest/autorun'
5
+ require 'toxiproxy'
5
6
 
6
7
  TINYTDS_SCHEMAS = ['sqlserver_2000', 'sqlserver_2005', 'sqlserver_2008', 'sqlserver_2014', 'sqlserver_azure', 'sybase_ase'].freeze
7
8
 
@@ -75,8 +76,8 @@ module TinyTds
75
76
  username = (sqlserver_azure? ? ENV['TINYTDS_UNIT_AZURE_USER'] : ENV['TINYTDS_UNIT_USER']) || 'tinytds'
76
77
  password = (sqlserver_azure? ? ENV['TINYTDS_UNIT_AZURE_PASS'] : ENV['TINYTDS_UNIT_PASS']) || ''
77
78
  { :dataserver => sqlserver_azure? ? nil : ENV['TINYTDS_UNIT_DATASERVER'],
78
- :host => ENV['TINYTDS_UNIT_HOST'],
79
- :port => ENV['TINYTDS_UNIT_PORT'],
79
+ :host => ENV['TINYTDS_UNIT_HOST'] || 'localhost',
80
+ :port => ENV['TINYTDS_UNIT_PORT'] || '1433',
80
81
  :tds_version => ENV['TINYTDS_UNIT_VERSION'],
81
82
  :username => username,
82
83
  :password => password,
@@ -89,11 +90,11 @@ module TinyTds
89
90
  end
90
91
 
91
92
  def connection_timeout
92
- sqlserver_azure? ? 20 : 5
93
+ sqlserver_azure? ? 20 : 8
93
94
  end
94
95
 
95
96
  def assert_client_works(client)
96
- client.execute("SELECT 'client_works' as [client_works]").each.must_equal [{'client_works' => 'client_works'}]
97
+ _(client.execute("SELECT 'client_works' as [client_works]").each).must_equal [{'client_works' => 'client_works'}]
97
98
  end
98
99
 
99
100
  def assert_new_connections_work
@@ -142,6 +143,10 @@ module TinyTds
142
143
  RbConfig::CONFIG['host_os'] =~ /ming/
143
144
  end
144
145
 
146
+ def ruby_darwin?
147
+ RbConfig::CONFIG['host_os'] =~ /darwin/
148
+ end
149
+
145
150
  def load_current_schema
146
151
  loader = new_connection
147
152
  schema_file = File.expand_path File.join(File.dirname(__FILE__), 'schema', "#{current_schema}.sql")
@@ -149,6 +154,8 @@ module TinyTds
149
154
  loader.execute(drop_sql).do
150
155
  loader.execute(schema_sql).do
151
156
  loader.execute(sp_sql).do
157
+ loader.execute(sp_error_sql).do
158
+ loader.execute(sp_several_prints_sql).do
152
159
  loader.close
153
160
  true
154
161
  end
@@ -163,7 +170,16 @@ module TinyTds
163
170
  ) DROP TABLE datatypes
164
171
  IF EXISTS(
165
172
  SELECT 1 FROM sysobjects WHERE type = 'P' AND name = 'tinytds_TestReturnCodes'
166
- ) DROP PROCEDURE tinytds_TestReturnCodes|
173
+ ) DROP PROCEDURE tinytds_TestReturnCodes
174
+ IF EXISTS(
175
+ SELECT 1 FROM sysobjects WHERE type = 'P' AND name = 'tinytds_TestPrintWithError'
176
+ ) DROP PROCEDURE tinytds_TestPrintWithError
177
+ IF EXISTS(
178
+ SELECT 1 FROM sysobjects WHERE type = 'P' AND name = 'tinytds_TestPrintWithError'
179
+ ) DROP PROCEDURE tinytds_TestPrintWithError
180
+ IF EXISTS(
181
+ SELECT 1 FROM sysobjects WHERE type = 'P' AND name = 'tinytds_TestSeveralPrints'
182
+ ) DROP PROCEDURE tinytds_TestSeveralPrints|
167
183
  end
168
184
 
169
185
  def drop_sql_microsoft
@@ -177,7 +193,15 @@ module TinyTds
177
193
  IF EXISTS (
178
194
  SELECT name FROM sysobjects
179
195
  WHERE name = 'tinytds_TestReturnCodes' AND type = 'P'
180
- ) DROP PROCEDURE tinytds_TestReturnCodes|
196
+ ) DROP PROCEDURE tinytds_TestReturnCodes
197
+ IF EXISTS (
198
+ SELECT name FROM sysobjects
199
+ WHERE name = 'tinytds_TestPrintWithError' AND type = 'P'
200
+ ) DROP PROCEDURE tinytds_TestPrintWithError
201
+ IF EXISTS (
202
+ SELECT name FROM sysobjects
203
+ WHERE name = 'tinytds_TestSeveralPrints' AND type = 'P'
204
+ ) DROP PROCEDURE tinytds_TestSeveralPrints|
181
205
  end
182
206
 
183
207
  def sp_sql
@@ -187,6 +211,21 @@ module TinyTds
187
211
  RETURN(420) |
188
212
  end
189
213
 
214
+ def sp_error_sql
215
+ %|CREATE PROCEDURE tinytds_TestPrintWithError
216
+ AS
217
+ PRINT 'hello'
218
+ RAISERROR('Error following print', 16, 1)|
219
+ end
220
+
221
+ def sp_several_prints_sql
222
+ %|CREATE PROCEDURE tinytds_TestSeveralPrints
223
+ AS
224
+ PRINT 'hello 1'
225
+ PRINT 'hello 2'
226
+ PRINT 'hello 3'|
227
+ end
228
+
190
229
  def find_value(id, column, query_options={})
191
230
  query_options[:timezone] ||= :utc
192
231
  sql = "SELECT [#{column}] FROM [datatypes] WHERE [id] = #{id}"
@@ -208,6 +247,25 @@ module TinyTds
208
247
  client.execute("ROLLBACK TRANSACTION").do
209
248
  end
210
249
 
250
+ def init_toxiproxy
251
+ return if ENV['APPVEYOR_BUILD_FOLDER'] # only for CI using docker
252
+
253
+ # In order for toxiproxy to work for local docker instances of mssql, the containers must be on the same network
254
+ # and the host used below must match the mssql container name so toxiproxy knows where to proxy to.
255
+ # localhost from the perspective of toxiproxy's container is its own container an *not* the mssql container it needs to proxy to.
256
+ # docker-compose.yml handles this automatically for us. In instances where someone is using their own local mssql container they'll
257
+ # need to set up the networks manually and set TINYTDS_UNIT_HOST to their mssql container name
258
+ # For anything other than localhost just use the environment config
259
+ env_host = ENV['TINYTDS_UNIT_HOST_TEST'] || ENV['TINYTDS_UNIT_HOST'] || 'localhost'
260
+ host = ['localhost', '127.0.0.1', '0.0.0.0'].include?(env_host) ? 'sqlserver' : env_host
261
+ port = ENV['TINYTDS_UNIT_PORT'] || 1433
262
+ Toxiproxy.populate([
263
+ {
264
+ name: "sqlserver_test",
265
+ listen: "0.0.0.0:1234",
266
+ upstream: "#{host}:#{port}"
267
+ }
268
+ ])
269
+ end
211
270
  end
212
271
  end
213
-