Skip to content

Commit

Permalink
Add sql trigger test for different data types (#876)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
lucyzhang929 authored Jun 28, 2023
1 parent 1c0b9dc commit 02dca71
Show file tree
Hide file tree
Showing 13 changed files with 285 additions and 0 deletions.
27 changes: 27 additions & 0 deletions test-outofproc/ProductsColumnTypesTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class ProductsColumnTypesTrigger
{
/// <summary>
/// Simple trigger function used to verify different column types are serialized correctly.
/// </summary>
[Function(nameof(ProductsColumnTypesTrigger))]
public static void Run(
[SqlTrigger("[dbo].[ProductsColumnTypes]", "SqlConnectionString")]
IReadOnlyList<SqlChange<ProductColumnTypes>> changes,
FunctionContext context)
{
ILogger logger = context.GetLogger("ProductsColumnTypesTrigger");
logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes));
}
}
}
84 changes: 84 additions & 0 deletions test/Integration/SqlTriggerBindingIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,91 @@ JOIN sys.columns c

//Check if LastAccessTime column exists in the GlobalState table
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.");
}

/// <summary>
/// Ensures that all column types are serialized correctly.
/// </summary>
[Theory]
[SqlInlineData()]
public async Task ProductsColumnTypesTriggerTest(SupportedLanguages lang)
{
this.SetChangeTrackingForTable("ProductsColumnTypes");
this.StartFunctionHost(nameof(ProductsColumnTypesTrigger), lang, true);
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==\"}");
int index = 0;
string messagePrefix = "SQL Changes: ";

var taskCompletion = new TaskCompletionSource<bool>();

void MonitorOutputData(object sender, DataReceivedEventArgs e)
{
if (e.Data != null && (index = e.Data.IndexOf(messagePrefix, StringComparison.Ordinal)) >= 0)
{
string json = e.Data[(index + messagePrefix.Length)..];
// Sometimes we'll get messages that have extra logging content on the same line - so to prevent that from breaking
// the deserialization we look for the end of the changes array and only use that.
// (This is fine since we control what content is in the array so know that none of the items have a ] in them)
json = json[..(json.IndexOf(']') + 1)];
IReadOnlyList<SqlChange<ProductColumnTypes>> changes;
try
{
changes = Utils.JsonDeserializeObject<IReadOnlyList<SqlChange<ProductColumnTypes>>>(json);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Exception deserializing JSON content. Error={ex.Message} Json=\"{json}\"", ex);
}
Assert.Equal(SqlChangeOperation.Insert, changes[0].Operation); // Expected change operation
ProductColumnTypes product = changes[0].Item;
Assert.NotNull(product); // Product deserialized correctly
Assert.Equal(expectedResponse, product); // The product has the expected values
taskCompletion.SetResult(true);
}
};

// Set up listener for the changes coming in
foreach (Process functionHost in this.FunctionHostList)
{
functionHost.OutputDataReceived += MonitorOutputData;
}

// Now that we've set up our listener trigger the actions to monitor
string datetime = "2022-10-20 12:39:13.123";
this.ExecuteNonQuery("INSERT INTO [dbo].[ProductsColumnTypes] VALUES (" +
"999, " + // ProductId,
"999, " + // BigInt
"1, " + // Bit
"1.2345, " + // DecimalType
"1.2345, " + // Money
"1.2345, " + // Numeric
"1, " + // SmallInt
"1.2345, " + // SmallMoney
"1, " + // TinyInt
".1, " + // FloatType
".1, " + // Real
$"CONVERT(DATE, '{datetime}'), " + // Date
$"CONVERT(DATETIME, '{datetime}'), " + // Datetime
$"CONVERT(DATETIME2, '{datetime}'), " + // Datetime2
$"CONVERT(DATETIMEOFFSET, '{datetime}'), " + // DatetimeOffset
$"CONVERT(SMALLDATETIME, '{datetime}'), " + // SmallDatetime
$"CONVERT(TIME, '{datetime}'), " + // Time
"'test', " + // CharType
"'test', " + // Varchar
"'test', " + // Nchar
"'test', " + // Nvarchar
"CONVERT(BINARY, 'test'), " + // Binary
"CONVERT(VARBINARY, 'test'))"); // Varbinary

// Now wait until either we timeout or we've gotten all the expected changes, whichever comes first
this.LogOutput($"[{DateTime.UtcNow:u}] Waiting for Insert changes (10000ms)");
await taskCompletion.Task.TimeoutAfter(TimeSpan.FromMilliseconds(10000), $"Timed out waiting for Insert changes.");

// Unhook handler since we're done monitoring these changes so we aren't checking other changes done later
foreach (Process functionHost in this.FunctionHostList)
{
functionHost.OutputDataReceived -= MonitorOutputData;
}
}
}
}
25 changes: 25 additions & 0 deletions test/Integration/test-csharp/ProductsColumnTypesTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common;
using Microsoft.Extensions.Logging;


namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration
{
public static class ProductsColumnTypesTrigger
{
/// <summary>
/// Simple trigger function used to verify different column types are serialized correctly.
/// </summary>
[FunctionName(nameof(ProductsColumnTypesTrigger))]
public static void Run(
[SqlTrigger("[dbo].[ProductsColumnTypes]", "SqlConnectionString")]
IReadOnlyList<SqlChange<ProductColumnTypes>> changes,
ILogger logger)
{
logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes));
}
}
}
12 changes: 12 additions & 0 deletions test/Integration/test-csx/ProductsColumnTypesTrigger/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"bindings": [
{
"name": "changes",
"type": "sqlTrigger",
"direction": "in",
"tableName": "dbo.ProductsColumnTypes",
"connectionStringSetting": "SqlConnectionString"
}
],
"disabled": false
}
14 changes: 14 additions & 0 deletions test/Integration/test-csx/ProductsColumnTypesTrigger/run.csx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#load "../Common/Product.csx"
#r "Newtonsoft.Json"
#r "Microsoft.Azure.WebJobs.Extensions.Sql"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Microsoft.Azure.WebJobs.Extensions.Sql;

public static void Run(IReadOnlyList<SqlChange<ProductColumnTypes>> changes, ILogger log)
{
log.LogInformation("SQL Changes: " + Microsoft.Azure.WebJobs.Extensions.Sql.Utils.JsonSerializeObject(changes));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.function.Common;

public class SqlChangeProductColumnTypes {
private SqlChangeOperation Operation;
private ProductColumnTypes Item;

public SqlChangeProductColumnTypes() {
}

public SqlChangeProductColumnTypes(SqlChangeOperation operation, ProductColumnTypes item) {
this.Operation = operation;
this.Item = item;
}

public SqlChangeOperation getOperation() {
return Operation;
}

public void setOperation(SqlChangeOperation operation) {
this.Operation = operation;
}

public ProductColumnTypes getItem() {
return Item;
}

public void setItem(ProductColumnTypes item) {
this.Item = item;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/

package com.function;

import com.function.Common.SqlChangeProductColumnTypes;
import com.google.gson.Gson;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.sql.annotation.SQLTrigger;

import java.util.logging.Level;

public class ProductsColumnTypesTrigger {
@FunctionName("ProductsColumnTypesTrigger")
public void run(
@SQLTrigger(
name = "changes",
tableName = "[dbo].[ProductsColumnTypes]",
connectionStringSetting = "SqlConnectionString")
SqlChangeProductColumnTypes[] changes,
ExecutionContext context) throws Exception {

context.getLogger().log(Level.INFO, "SQL Changes: " + new Gson().toJson(changes));
}
}
12 changes: 12 additions & 0 deletions test/Integration/test-js/ProductsColumnTypesTrigger/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"bindings": [
{
"name": "changes",
"type": "sqlTrigger",
"direction": "in",
"tableName": "dbo.ProductsColumnTypes",
"connectionStringSetting": "SqlConnectionString"
}
],
"disabled": false
}
6 changes: 6 additions & 0 deletions test/Integration/test-js/ProductsColumnTypesTrigger/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

module.exports = async function (context, changes) {
context.log(`SQL Changes: ${JSON.stringify(changes)}`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"bindings": [
{
"name": "changes",
"type": "sqlTrigger",
"direction": "in",
"tableName": "dbo.ProductsColumnTypes",
"connectionStringSetting": "SqlConnectionString"
}
],
"disabled": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.

using namespace System.Net

param($changes)

$changesJson = $changes | ConvertTo-Json -Compress -AsArray
Write-Host "SQL Changes: $changesJson"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import logging

def main(changes):
logging.info("SQL Changes: %s", changes)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"bindings": [
{
"name": "changes",
"type": "sqlTrigger",
"direction": "in",
"tableName": "dbo.ProductsColumnTypes",
"connectionStringSetting": "SqlConnectionString"
}
],
"disabled": false
}

0 comments on commit 02dca71

Please sign in to comment.