Skip to content

Commit 02dca71

Browse files
authored
Add sql trigger test for different data types (#876)
* add productcolumntypestriggertest * try running only csharp * try running js * comment out column values check temporarily * skip date and byte checks * fix build error * skip byte check * remove date/time, add back binary * fix build error * remove only binary check * remove only nchar and nvarchar * check equality of nchar and nvarchar * fix tests * change nchar to test
1 parent 1c0b9dc commit 02dca71

File tree

13 files changed

+285
-0
lines changed

13 files changed

+285
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using DotnetIsolatedTests.Common;
6+
using Microsoft.Extensions.Logging;
7+
using Microsoft.Azure.Functions.Worker;
8+
using Microsoft.Azure.Functions.Worker.Extensions.Sql;
9+
10+
namespace DotnetIsolatedTests
11+
{
12+
public static class ProductsColumnTypesTrigger
13+
{
14+
/// <summary>
15+
/// Simple trigger function used to verify different column types are serialized correctly.
16+
/// </summary>
17+
[Function(nameof(ProductsColumnTypesTrigger))]
18+
public static void Run(
19+
[SqlTrigger("[dbo].[ProductsColumnTypes]", "SqlConnectionString")]
20+
IReadOnlyList<SqlChange<ProductColumnTypes>> changes,
21+
FunctionContext context)
22+
{
23+
ILogger logger = context.GetLogger("ProductsColumnTypesTrigger");
24+
logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes));
25+
}
26+
}
27+
}

test/Integration/SqlTriggerBindingIntegrationTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,91 @@ JOIN sys.columns c
654654

655655
//Check if LastAccessTime column exists in the GlobalState table
656656
Assert.True(1 == (int)this.ExecuteScalar("SELECT 1 FROM sys.columns WHERE Name = N'LastAccessTime' AND Object_ID = Object_ID(N'[az_func].[GlobalState]')"), $"{GlobalStateTableName} should have {LastAccessTimeColumnName} column after restarting the listener.");
657+
}
658+
659+
/// <summary>
660+
/// Ensures that all column types are serialized correctly.
661+
/// </summary>
662+
[Theory]
663+
[SqlInlineData()]
664+
public async Task ProductsColumnTypesTriggerTest(SupportedLanguages lang)
665+
{
666+
this.SetChangeTrackingForTable("ProductsColumnTypes");
667+
this.StartFunctionHost(nameof(ProductsColumnTypesTrigger), lang, true);
668+
ProductColumnTypes expectedResponse = Utils.JsonDeserializeObject<ProductColumnTypes>(/*lang=json,strict*/ "{\"ProductId\":999,\"BigInt\":999,\"Bit\":true,\"DecimalType\":1.2345,\"Money\":1.2345,\"Numeric\":1.2345,\"SmallInt\":1,\"SmallMoney\":1.2345,\"TinyInt\":1,\"FloatType\":0.1,\"Real\":0.1,\"Date\":\"2022-10-20T00:00:00.000Z\",\"Datetime\":\"2022-10-20T12:39:13.123Z\",\"Datetime2\":\"2022-10-20T12:39:13.123Z\",\"DatetimeOffset\":\"2022-10-20T12:39:13.123Z\",\"SmallDatetime\":\"2022-10-20T12:39:00.000Z\",\"Time\":\"12:39:13.1230000\",\"CharType\":\"test\",\"Varchar\":\"test\",\"Nchar\":\"test\",\"Nvarchar\":\"test\",\"Binary\":\"dGVzdA==\",\"Varbinary\":\"dGVzdA==\"}");
669+
int index = 0;
670+
string messagePrefix = "SQL Changes: ";
671+
672+
var taskCompletion = new TaskCompletionSource<bool>();
657673

674+
void MonitorOutputData(object sender, DataReceivedEventArgs e)
675+
{
676+
if (e.Data != null && (index = e.Data.IndexOf(messagePrefix, StringComparison.Ordinal)) >= 0)
677+
{
678+
string json = e.Data[(index + messagePrefix.Length)..];
679+
// Sometimes we'll get messages that have extra logging content on the same line - so to prevent that from breaking
680+
// the deserialization we look for the end of the changes array and only use that.
681+
// (This is fine since we control what content is in the array so know that none of the items have a ] in them)
682+
json = json[..(json.IndexOf(']') + 1)];
683+
IReadOnlyList<SqlChange<ProductColumnTypes>> changes;
684+
try
685+
{
686+
changes = Utils.JsonDeserializeObject<IReadOnlyList<SqlChange<ProductColumnTypes>>>(json);
687+
}
688+
catch (Exception ex)
689+
{
690+
throw new InvalidOperationException($"Exception deserializing JSON content. Error={ex.Message} Json=\"{json}\"", ex);
691+
}
692+
Assert.Equal(SqlChangeOperation.Insert, changes[0].Operation); // Expected change operation
693+
ProductColumnTypes product = changes[0].Item;
694+
Assert.NotNull(product); // Product deserialized correctly
695+
Assert.Equal(expectedResponse, product); // The product has the expected values
696+
taskCompletion.SetResult(true);
697+
}
698+
};
699+
700+
// Set up listener for the changes coming in
701+
foreach (Process functionHost in this.FunctionHostList)
702+
{
703+
functionHost.OutputDataReceived += MonitorOutputData;
704+
}
705+
706+
// Now that we've set up our listener trigger the actions to monitor
707+
string datetime = "2022-10-20 12:39:13.123";
708+
this.ExecuteNonQuery("INSERT INTO [dbo].[ProductsColumnTypes] VALUES (" +
709+
"999, " + // ProductId,
710+
"999, " + // BigInt
711+
"1, " + // Bit
712+
"1.2345, " + // DecimalType
713+
"1.2345, " + // Money
714+
"1.2345, " + // Numeric
715+
"1, " + // SmallInt
716+
"1.2345, " + // SmallMoney
717+
"1, " + // TinyInt
718+
".1, " + // FloatType
719+
".1, " + // Real
720+
$"CONVERT(DATE, '{datetime}'), " + // Date
721+
$"CONVERT(DATETIME, '{datetime}'), " + // Datetime
722+
$"CONVERT(DATETIME2, '{datetime}'), " + // Datetime2
723+
$"CONVERT(DATETIMEOFFSET, '{datetime}'), " + // DatetimeOffset
724+
$"CONVERT(SMALLDATETIME, '{datetime}'), " + // SmallDatetime
725+
$"CONVERT(TIME, '{datetime}'), " + // Time
726+
"'test', " + // CharType
727+
"'test', " + // Varchar
728+
"'test', " + // Nchar
729+
"'test', " + // Nvarchar
730+
"CONVERT(BINARY, 'test'), " + // Binary
731+
"CONVERT(VARBINARY, 'test'))"); // Varbinary
732+
733+
// Now wait until either we timeout or we've gotten all the expected changes, whichever comes first
734+
this.LogOutput($"[{DateTime.UtcNow:u}] Waiting for Insert changes (10000ms)");
735+
await taskCompletion.Task.TimeoutAfter(TimeSpan.FromMilliseconds(10000), $"Timed out waiting for Insert changes.");
736+
737+
// Unhook handler since we're done monitoring these changes so we aren't checking other changes done later
738+
foreach (Process functionHost in this.FunctionHostList)
739+
{
740+
functionHost.OutputDataReceived -= MonitorOutputData;
741+
}
658742
}
659743
}
660744
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common;
6+
using Microsoft.Extensions.Logging;
7+
8+
9+
namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration
10+
{
11+
public static class ProductsColumnTypesTrigger
12+
{
13+
/// <summary>
14+
/// Simple trigger function used to verify different column types are serialized correctly.
15+
/// </summary>
16+
[FunctionName(nameof(ProductsColumnTypesTrigger))]
17+
public static void Run(
18+
[SqlTrigger("[dbo].[ProductsColumnTypes]", "SqlConnectionString")]
19+
IReadOnlyList<SqlChange<ProductColumnTypes>> changes,
20+
ILogger logger)
21+
{
22+
logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes));
23+
}
24+
}
25+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"bindings": [
3+
{
4+
"name": "changes",
5+
"type": "sqlTrigger",
6+
"direction": "in",
7+
"tableName": "dbo.ProductsColumnTypes",
8+
"connectionStringSetting": "SqlConnectionString"
9+
}
10+
],
11+
"disabled": false
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#load "../Common/Product.csx"
2+
#r "Newtonsoft.Json"
3+
#r "Microsoft.Azure.WebJobs.Extensions.Sql"
4+
5+
using System.Net;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.Extensions.Primitives;
8+
using Newtonsoft.Json;
9+
using Microsoft.Azure.WebJobs.Extensions.Sql;
10+
11+
public static void Run(IReadOnlyList<SqlChange<ProductColumnTypes>> changes, ILogger log)
12+
{
13+
log.LogInformation("SQL Changes: " + Microsoft.Azure.WebJobs.Extensions.Sql.Utils.JsonSerializeObject(changes));
14+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for
4+
* license information.
5+
*/
6+
7+
package com.function.Common;
8+
9+
public class SqlChangeProductColumnTypes {
10+
private SqlChangeOperation Operation;
11+
private ProductColumnTypes Item;
12+
13+
public SqlChangeProductColumnTypes() {
14+
}
15+
16+
public SqlChangeProductColumnTypes(SqlChangeOperation operation, ProductColumnTypes item) {
17+
this.Operation = operation;
18+
this.Item = item;
19+
}
20+
21+
public SqlChangeOperation getOperation() {
22+
return Operation;
23+
}
24+
25+
public void setOperation(SqlChangeOperation operation) {
26+
this.Operation = operation;
27+
}
28+
29+
public ProductColumnTypes getItem() {
30+
return Item;
31+
}
32+
33+
public void setItem(ProductColumnTypes item) {
34+
this.Item = item;
35+
}
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for
4+
* license information.
5+
*/
6+
7+
package com.function;
8+
9+
import com.function.Common.SqlChangeProductColumnTypes;
10+
import com.google.gson.Gson;
11+
import com.microsoft.azure.functions.ExecutionContext;
12+
import com.microsoft.azure.functions.annotation.FunctionName;
13+
import com.microsoft.azure.functions.sql.annotation.SQLTrigger;
14+
15+
import java.util.logging.Level;
16+
17+
public class ProductsColumnTypesTrigger {
18+
@FunctionName("ProductsColumnTypesTrigger")
19+
public void run(
20+
@SQLTrigger(
21+
name = "changes",
22+
tableName = "[dbo].[ProductsColumnTypes]",
23+
connectionStringSetting = "SqlConnectionString")
24+
SqlChangeProductColumnTypes[] changes,
25+
ExecutionContext context) throws Exception {
26+
27+
context.getLogger().log(Level.INFO, "SQL Changes: " + new Gson().toJson(changes));
28+
}
29+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"bindings": [
3+
{
4+
"name": "changes",
5+
"type": "sqlTrigger",
6+
"direction": "in",
7+
"tableName": "dbo.ProductsColumnTypes",
8+
"connectionStringSetting": "SqlConnectionString"
9+
}
10+
],
11+
"disabled": false
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
module.exports = async function (context, changes) {
5+
context.log(`SQL Changes: ${JSON.stringify(changes)}`)
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"bindings": [
3+
{
4+
"name": "changes",
5+
"type": "sqlTrigger",
6+
"direction": "in",
7+
"tableName": "dbo.ProductsColumnTypes",
8+
"connectionStringSetting": "SqlConnectionString"
9+
}
10+
],
11+
"disabled": false
12+
}

0 commit comments

Comments
 (0)