Skip to content

Commit 7ec3284

Browse files
tmasternakMarcWils
andauthored
Use SQL system catalog views to check for the presence of a Recoverable column. This removes the need for SELECT permissions to send a message to a queue table. (#1452)
Co-authored-by: Marc Wils <[email protected]>
1 parent c6b5359 commit 7ec3284

File tree

11 files changed

+45
-39
lines changed

11 files changed

+45
-39
lines changed

src/NServiceBus.Transport.SqlServer.AcceptanceTests/NativeTimeouts/When_configured_to_purge_expired_messages_at_startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.AcceptanceTests.NativeTimeouts
1+
namespace NServiceBus.Transport.SqlServer.AcceptanceTests.NativeTimeouts
22
{
33
using System;
44
using System.Collections.Generic;

src/NServiceBus.Transport.SqlServer.IntegrationTests/When_checking_schema.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.IntegrationTests
1+
namespace NServiceBus.Transport.SqlServer.IntegrationTests
22
{
33
using System;
44
using System.Threading;
@@ -23,7 +23,7 @@ public async Task SetUp()
2323

2424
await ResetQueue(addressParser, sqlConnectionFactory);
2525

26-
queue = new TableBasedQueue(addressParser.Parse(QueueTableName).QualifiedTableName, QueueTableName, false);
26+
queue = new TableBasedQueue(addressParser.Parse(QueueTableName), QueueTableName, false);
2727
}
2828

2929
[Test]

src/NServiceBus.Transport.SqlServer.IntegrationTests/When_dispatching_messages.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.IntegrationTests
1+
namespace NServiceBus.Transport.SqlServer.IntegrationTests
22
{
33
using System;
44
using System.Collections.Generic;
@@ -112,8 +112,7 @@ async Task PrepareAsync(CancellationToken cancellationToken = default)
112112
Task PurgeOutputQueue(QueueAddressTranslator addressTranslator, CancellationToken cancellationToken = default)
113113
{
114114
purger = new QueuePurger(sqlConnectionFactory);
115-
var queueAddress = addressTranslator.Parse(ValidAddress).QualifiedTableName;
116-
queue = new TableBasedQueue(queueAddress, ValidAddress, true);
115+
queue = new TableBasedQueue(addressTranslator.Parse(ValidAddress), ValidAddress, true);
117116

118117
return purger.Purge(queue, cancellationToken);
119118
}

src/NServiceBus.Transport.SqlServer.IntegrationTests/When_message_receive_takes_long.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.IntegrationTests
1+
namespace NServiceBus.Transport.SqlServer.IntegrationTests
22
{
33
using System;
44
using System.Collections.Generic;
@@ -28,7 +28,7 @@ public async Task SetUp()
2828

2929
await CreateQueueIfNotExists(addressParser, sqlConnectionFactory);
3030

31-
queue = new TableBasedQueue(addressParser.Parse(QueueTableName).QualifiedTableName, QueueTableName, true);
31+
queue = new TableBasedQueue(addressParser.Parse(QueueTableName), QueueTableName, true);
3232
}
3333

3434
[Test]

src/NServiceBus.Transport.SqlServer.IntegrationTests/When_receiving_messages.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.IntegrationTests
1+
namespace NServiceBus.Transport.SqlServer.IntegrationTests
22
{
33
using System;
44
using System.Linq;
@@ -23,7 +23,7 @@ public async Task Should_stop_receiving_messages_after_first_unsuccessful_receiv
2323

2424
var parser = new QueueAddressTranslator("nservicebus", "dbo", null, null);
2525
var inputQueueName = "input";
26-
var inputQueueAddress = parser.Parse(inputQueueName).Address;
26+
var inputQueueAddress = parser.Parse(inputQueueName);
2727
var inputQueue = new FakeTableBasedQueue(inputQueueAddress, queueSize, successfulReceives);
2828

2929
var connectionString = Environment.GetEnvironmentVariable("SqlServerTransportConnectionString") ?? @"Data Source=.\SQLEXPRESS;Initial Catalog=nservicebus;Integrated Security=True";
@@ -35,7 +35,7 @@ public async Task Should_stop_receiving_messages_after_first_unsuccessful_receiv
3535
};
3636

3737
transport.Testing.QueueFactoryOverride = qa =>
38-
qa == inputQueueAddress ? inputQueue : new TableBasedQueue(parser.Parse(qa).QualifiedTableName, qa, true);
38+
qa == inputQueueAddress.Address ? inputQueue : new TableBasedQueue(parser.Parse(qa), qa, true);
3939

4040
var receiveSettings = new ReceiveSettings("receiver", new Transport.QueueAddress(inputQueueName), true, false, "error");
4141
var hostSettings = new HostSettings("IntegrationTests", string.Empty, new StartupDiagnosticEntries(),
@@ -87,7 +87,7 @@ class FakeTableBasedQueue : TableBasedQueue
8787
int queueSize;
8888
int successfulReceives;
8989

90-
public FakeTableBasedQueue(string address, int queueSize, int successfulReceives) : base(address, "", true)
90+
public FakeTableBasedQueue(CanonicalQueueAddress address, int queueSize, int successfulReceives) : base(address, "", true)
9191
{
9292
this.queueSize = queueSize;
9393
this.successfulReceives = successfulReceives;

src/NServiceBus.Transport.SqlServer.IntegrationTests/When_recoverable_column_is_removed.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.IntegrationTests
1+
namespace NServiceBus.Transport.SqlServer.IntegrationTests
22
{
33
using System;
44
using System.Collections.Generic;

src/NServiceBus.Transport.SqlServer.IntegrationTests/When_using_ttbr.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NServiceBus.Transport.SqlServer.IntegrationTests
1+
namespace NServiceBus.Transport.SqlServer.IntegrationTests
22
{
33
using System;
44
using System.Collections.Generic;
@@ -134,7 +134,7 @@ Task PurgeOutputQueue(QueueAddressTranslator addressParser, CancellationToken ca
134134
{
135135
purger = new QueuePurger(sqlConnectionFactory);
136136
var queueAddress = addressParser.Parse(ValidAddress);
137-
queue = new TableBasedQueue(queueAddress.QualifiedTableName, queueAddress.Address, true);
137+
queue = new TableBasedQueue(queueAddress, queueAddress.Address, true);
138138

139139
return purger.Purge(queue, cancellationToken);
140140
}

src/NServiceBus.Transport.SqlServer/Queuing/SqlConstants.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ THEN DATEADD(ms, @TimeToBeReceivedMs, GETUTCDATE()) END,
4848
IF (@NOCOUNT = 'ON') SET NOCOUNT ON;
4949
IF (@NOCOUNT = 'OFF') SET NOCOUNT OFF;";
5050

51-
public static readonly string CheckIfTableHasRecoverableText = "SELECT TOP (0) * FROM {0} WITH (NOLOCK);";
51+
public static string CheckIfTableHasRecoverableText { get; set; } = @"
52+
SELECT COUNT(*)
53+
FROM {0}.sys.columns c
54+
WHERE c.object_id = OBJECT_ID(N'{1}')
55+
AND c.name = 'Recoverable'";
5256

5357
public static readonly string StoreDelayedMessageText =
5458
@"

src/NServiceBus.Transport.SqlServer/Queuing/TableBasedQueue.cs

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ class TableBasedQueue
1717
{
1818
public string Name { get; }
1919

20-
public TableBasedQueue(string qualifiedTableName, string queueName, bool isStreamSupported)
20+
public TableBasedQueue(CanonicalQueueAddress queueAddress, string queueName, bool isStreamSupported)
2121
{
22-
this.qualifiedTableName = qualifiedTableName;
22+
qualifiedTableName = queueAddress.QualifiedTableName;
2323
Name = queueName;
24-
receiveCommand = Format(SqlConstants.ReceiveText, this.qualifiedTableName);
25-
purgeCommand = Format(SqlConstants.PurgeText, this.qualifiedTableName);
26-
purgeExpiredCommand = Format(SqlConstants.PurgeBatchOfExpiredMessagesText, this.qualifiedTableName);
27-
checkExpiresIndexCommand = Format(SqlConstants.CheckIfExpiresIndexIsPresent, this.qualifiedTableName);
28-
checkNonClusteredRowVersionIndexCommand = Format(SqlConstants.CheckIfNonClusteredRowVersionIndexIsPresent, this.qualifiedTableName);
29-
checkHeadersColumnTypeCommand = Format(SqlConstants.CheckHeadersColumnType, this.qualifiedTableName);
24+
receiveCommand = Format(SqlConstants.ReceiveText, qualifiedTableName);
25+
purgeCommand = Format(SqlConstants.PurgeText, qualifiedTableName);
26+
purgeExpiredCommand = Format(SqlConstants.PurgeBatchOfExpiredMessagesText, qualifiedTableName);
27+
checkExpiresIndexCommand = Format(SqlConstants.CheckIfExpiresIndexIsPresent, qualifiedTableName);
28+
checkNonClusteredRowVersionIndexCommand = Format(SqlConstants.CheckIfNonClusteredRowVersionIndexIsPresent, qualifiedTableName);
29+
checkHeadersColumnTypeCommand = Format(SqlConstants.CheckHeadersColumnType, qualifiedTableName);
30+
checkRecoverableColumnCommand = Format(SqlConstants.CheckIfTableHasRecoverableText, queueAddress.Catalog, qualifiedTableName);
3031
this.isStreamSupported = isStreamSupported;
3132
}
3233

@@ -145,23 +146,24 @@ async Task<string> GetSendCommandText(SqlConnection connection, SqlTransaction t
145146
return sendCommand;
146147
}
147148

148-
var commandText = Format(SqlConstants.CheckIfTableHasRecoverableText, qualifiedTableName);
149-
using (var command = new SqlCommand(commandText, connection, transaction))
149+
using (var command = connection.CreateCommand())
150150
{
151-
using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
151+
command.CommandText = checkRecoverableColumnCommand;
152+
command.CommandType = CommandType.Text;
153+
command.Transaction = transaction;
154+
155+
var rowsCount = await command.ExecuteScalarAsync<int>(nameof(checkRecoverableColumnCommand), cancellationToken).ConfigureAwait(false);
156+
if (rowsCount > 0)
152157
{
153-
for (int fieldIndex = 0; fieldIndex < reader.FieldCount; fieldIndex++)
154-
{
155-
if (string.Equals("Recoverable", reader.GetName(fieldIndex), StringComparison.OrdinalIgnoreCase))
156-
{
157-
cachedSendCommand = Format(SqlConstants.SendTextWithRecoverable, qualifiedTableName);
158-
return cachedSendCommand;
159-
}
160-
}
158+
cachedSendCommand = Format(SqlConstants.SendTextWithRecoverable, qualifiedTableName);
159+
return cachedSendCommand;
161160
}
161+
else
162+
{
162163

163-
cachedSendCommand = Format(SqlConstants.SendText, qualifiedTableName);
164-
return cachedSendCommand;
164+
cachedSendCommand = Format(SqlConstants.SendText, qualifiedTableName);
165+
return cachedSendCommand;
166+
}
165167
}
166168
}
167169
finally
@@ -237,6 +239,7 @@ public override string ToString()
237239
string checkExpiresIndexCommand;
238240
string checkNonClusteredRowVersionIndexCommand;
239241
string checkHeadersColumnTypeCommand;
242+
string checkRecoverableColumnCommand;
240243
bool isStreamSupported;
241244

242245
readonly SemaphoreSlim sendCommandLock = new SemaphoreSlim(1, 1);

src/NServiceBus.Transport.SqlServer/Queuing/TableBasedQueueCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public TableBasedQueue Get(string destination)
1515
{
1616
var address = addressTranslator.Parse(destination);
1717
var key = Tuple.Create(address.QualifiedTableName, address.Address);
18-
var queue = cache.GetOrAdd(key, x => new TableBasedQueue(x.Item1, x.Item2, isStreamSupported));
18+
var queue = cache.GetOrAdd(key, x => new TableBasedQueue(address, x.Item2, isStreamSupported));
1919

2020
return queue;
2121
}

0 commit comments

Comments
 (0)