Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/SqlAsyncCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public enum QueryType
/// <typeparam name="T">A user-defined POCO that represents a row of the user's table</typeparam>
internal class SqlAsyncCollector<T> : IAsyncCollector<T>, IDisposable
{
private static readonly string[] UnsupportedTypes = { "NTEXT(*)", "TEXT(*)", "IMAGE(*)" };
private const string RowDataParameter = "@rowData";
private const string ColumnName = "COLUMN_NAME";
private const string ColumnDefinition = "COLUMN_DEFINITION";
Expand Down Expand Up @@ -231,7 +232,15 @@ private async Task UpsertRowsAsync(IList<T> rows, SqlAttribute attribute, IConfi
throw ex;
}

IEnumerable<string> bracketedColumnNamesFromItem = GetColumnNamesFromItem(rows.First())
IEnumerable<string> columnNamesFromItem = GetColumnNamesFromItem(rows.First());
IEnumerable<string> unsupportedColumns = columnNamesFromItem.Where(prop => UnsupportedTypes.Contains(tableInfo.Columns[prop], StringComparer.OrdinalIgnoreCase));
if (unsupportedColumns.Any())
{
string message = $"The type(s) of the following column(s) are not supported: {string.Join(", ", unsupportedColumns.ToArray())}. See https://github.com/Azure/azure-functions-sql-extension#output-bindings for more details.";
throw new InvalidOperationException(message);
}

IEnumerable<string> bracketedColumnNamesFromItem = columnNamesFromItem
.Where(prop => !tableInfo.PrimaryKeys.Any(k => k.IsIdentity && string.Equals(k.Name, prop, StringComparison.Ordinal))) // Skip any identity columns, those should never be updated
.Select(prop => prop.AsBracketQuotedString());
if (!bracketedColumnNamesFromItem.Any())
Expand Down
32 changes: 32 additions & 0 deletions test-outofproc/AddProductUnsupportedTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;
using DotnetIsolatedTests.Common;
using Microsoft.AspNetCore.Http;

namespace DotnetIsolatedTests
{
public static class AddProductUnsupportedTypes
{
/// <summary>
/// This output binding should fail since the target table has unsupported column types.
/// </summary>
[Function("AddProductUnsupportedTypes")]
[SqlOutput("dbo.ProductsUnsupportedTypes", "SqlConnectionString")]
public static ProductUnsupportedTypes Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproduct-unsupportedtypes")]
HttpRequest req)
{
var product = new ProductUnsupportedTypes
{
ProductId = 1,
TextCol = "test",
NtextCol = "test",
ImageCol = new byte[] { 1, 2, 3 }
};
return product;
}
}
}
3 changes: 2 additions & 1 deletion test-outofproc/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductMissingColumnsExceptionFunction.Run(Microsoft.AspNetCore.Http.HttpRequest)~DotnetIsolatedTests.Common.ProductMissingColumns")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductsNoPartialUpsert.Run(Microsoft.AspNetCore.Http.HttpRequest)~System.Collections.Generic.List{DotnetIsolatedTests.Common.Product}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.GetProductsColumnTypesSerialization.Run(Microsoft.AspNetCore.Http.HttpRequest,System.Collections.Generic.IEnumerable{DotnetIsolatedTests.Common.ProductColumnTypes})~System.Collections.Generic.IEnumerable{DotnetIsolatedTests.Common.ProductColumnTypes}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductIncorrectCasing.Run(Microsoft.Azure.Functions.Worker.Http.HttpRequestData)~DotnetIsolatedTests.Common.ProductIncorrectCasing")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductIncorrectCasing.Run(Microsoft.Azure.Functions.Worker.Http.HttpRequestData)~DotnetIsolatedTests.Common.ProductIncorrectCasing")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductUnsupportedTypes.Run(Microsoft.AspNetCore.Http.HttpRequest)~DotnetIsolatedTests.Common.ProductUnsupportedTypes")]
11 changes: 11 additions & 0 deletions test-outofproc/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,15 @@ public class ProductMissingColumns

public string Name { get; set; }
}

public class ProductUnsupportedTypes
{
public int ProductId { get; set; }

public string TextCol { get; set; }

public string NtextCol { get; set; }

public byte[] ImageCol { get; set; }
}
}
16 changes: 16 additions & 0 deletions test/Common/ProductUnsupportedTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common
{
public class ProductUnsupportedTypes
{
public int ProductId { get; set; }

public string TextCol { get; set; }

public string NtextCol { get; set; }

public byte[] ImageCol { get; set; }
}
}
6 changes: 6 additions & 0 deletions test/Database/Tables/ProductsUnsupportedTypes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE [ProductsUnsupportedTypes] (
[ProductId] [int] NOT NULL PRIMARY KEY,
[TextCol] [text],
[NtextCol] [ntext],
[ImageCol] [image]
)
3 changes: 2 additions & 1 deletion test/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.UnsupportedColumnTypesTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.WebJobs.Extensions.Sql.SqlChange{Microsoft.Azure.WebJobs.Extensions.Sql.Samples.Common.Product})")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Samples.InputBindingSamples.GetProductsColumnTypesSerializationAsyncEnumerable.Run(Microsoft.AspNetCore.Http.HttpRequest,System.Collections.Generic.IAsyncEnumerable{Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductColumnTypes},Microsoft.Extensions.Logging.ILogger)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Samples.InputBindingSamples.GetProductsColumnTypesSerialization.Run(Microsoft.AspNetCore.Http.HttpRequest,System.Collections.Generic.IEnumerable{Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductColumnTypes},Microsoft.Extensions.Logging.ILogger)~Microsoft.AspNetCore.Mvc.IActionResult")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.AddProductIncorrectCasing.Run(Microsoft.AspNetCore.Http.HttpRequest,Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductIncorrectCasing@)~Microsoft.AspNetCore.Mvc.IActionResult")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.AddProductIncorrectCasing.Run(Microsoft.AspNetCore.Http.HttpRequest,Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductIncorrectCasing@)~Microsoft.AspNetCore.Mvc.IActionResult")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration.AddProductUnsupportedTypes.Run(Microsoft.AspNetCore.Http.HttpRequest,Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common.ProductUnsupportedTypes@)~Microsoft.AspNetCore.Mvc.IActionResult")]
21 changes: 21 additions & 0 deletions test/Integration/SqlOutputBindingIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,26 @@ public async Task NoPropertiesThrows(SupportedLanguages lang)
// Wait 2sec for message to get processed to account for delays reading output
await foundExpectedMessageSource.Task.TimeoutAfter(TimeSpan.FromMilliseconds(2000), $"Timed out waiting for expected error message");
}

/// <summary>
/// Tests that an error is thrown when the upserted item contains a unsupported column type.
/// </summary>
[Theory]
[SqlInlineData()]
[UnsupportedLanguages(SupportedLanguages.OutOfProc)]
public async Task AddProductUnsupportedTypesTest(SupportedLanguages lang)
{
var foundExpectedMessageSource = new TaskCompletionSource<bool>();
this.StartFunctionHost(nameof(AddProductUnsupportedTypes), lang, true, (object sender, DataReceivedEventArgs e) =>
{
if (e.Data.Contains("The type(s) of the following column(s) are not supported: TextCol, NtextCol, ImageCol. See https://github.com/Azure/azure-functions-sql-extension#output-bindings for more details."))
{
foundExpectedMessageSource.SetResult(true);
}
});

Assert.Throws<AggregateException>(() => this.SendOutputGetRequest("addproduct-unsupportedtypes").Wait());
await foundExpectedMessageSource.Task.TimeoutAfter(TimeSpan.FromMilliseconds(2000), $"Timed out waiting for expected error message");
}
}
}
29 changes: 29 additions & 0 deletions test/Integration/test-csharp/AddProductUnsupportedTypes.cs
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.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common;
namespace Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration
{
public static class AddProductUnsupportedTypes
{
// This output binding should throw an exception because the target table has unsupported column types.
[FunctionName("AddProductUnsupportedTypes")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "addproduct-unsupportedtypes")]
HttpRequest req,
[Sql("dbo.ProductsUnsupportedTypes", "SqlConnectionString")] out ProductUnsupportedTypes product)
{
product = new ProductUnsupportedTypes()
{
ProductId = 1,
TextCol = "test",
NtextCol = "test",
ImageCol = new byte[] { 1, 2, 3 }
};
return new CreatedResult($"/api/addproduct-unsupportedtypes", product);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* 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.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.OutputBinding;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import com.microsoft.azure.functions.sql.annotation.SQLOutput;
import com.function.Common.ProductUnsupportedTypes;

import java.util.Optional;


public class AddProductUnsupportedTypes {
// This output binding should throw an exception because the target table has unsupported column types.
@FunctionName("AddProductUnsupportedTypes")
public HttpResponseMessage run(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.ANONYMOUS,
route = "addproduct-unsupportedtypes")
HttpRequestMessage<Optional<String>> request,
@SQLOutput(
name = "product",
commandText = "dbo.ProductsUnsupportedTypes",
connectionStringSetting = "SqlConnectionString")
OutputBinding<ProductUnsupportedTypes> product) {

ProductUnsupportedTypes p = new ProductUnsupportedTypes(
0,
"test",
"test",
"dGVzdA=="
);
product.setValue(p);
return request.createResponseBuilder(HttpStatus.OK).header("Content-Type", "application/json").body(product).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* 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;

import com.fasterxml.jackson.annotation.JsonProperty;

public class ProductUnsupportedTypes {
@JsonProperty("ProductId")
private int ProductId;
@JsonProperty("TextCol")
private String TextCol;
@JsonProperty("NtextCol")
private String NtextCol;
@JsonProperty("ImageCol")
private String ImageCol;

public ProductUnsupportedTypes() {
}

public ProductUnsupportedTypes(int productId, String textCol, String ntextCol, String imageCol) {
ProductId = productId;
TextCol = textCol;
NtextCol = ntextCol;
ImageCol = imageCol;
}

public int getProductId() {
return ProductId;
}

public void setProductId(int productId) {
ProductId = productId;
}

public String getTextCol() {
return TextCol;
}

public void setTextCol(String textCol) {
TextCol = textCol;
}

public String getNtextCol() {
return NtextCol;
}

public void setNtextCol(String ntextCol) {
NtextCol = ntextCol;
}

public String getImageCol() {
return ImageCol;
}

public void setImageCol(String imageCol) {
ImageCol = imageCol;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"bindings": [
{
"authLevel": "function",
"name": "req",
"direction": "in",
"type": "httpTrigger",
"methods": [
"get"
],
"route": "addproduct-unsupportedtypes"
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"name": "product",
"type": "sql",
"direction": "out",
"commandText": "[dbo].[ProductsUnsupportedTypes]",
"connectionStringSetting": "SqlConnectionString"
}
],
"disabled": false
}
17 changes: 17 additions & 0 deletions test/Integration/test-js/AddProductUnsupportedTypes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

// This output binding should throw an exception because the target table has unsupported column types.
module.exports = async function (context, req) {
context.bindings.product = {
ProductId: 0,
TextCol: "test",
NtextCol: "test",
ImageCol: "dGVzdA=="
}

return {
status: 201,
body: req.body
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"bindings": [
{
"authLevel": "function",
"name": "Request",
"direction": "in",
"type": "httpTrigger",
"methods": [
"get"
],
"route": "addproduct-unsupportedtypes"
},
{
"name": "response",
"type": "http",
"direction": "out"
},
{
"name": "product",
"type": "sql",
"direction": "out",
"commandText": "[dbo].[ProductsUnsupportedTypes]",
"connectionStringSetting": "SqlConnectionString"
}
],
"disabled": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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

# This output binding should throw an exception because the target table has unsupported column types.
param($Request)

$req_body = [ordered]@{
ProductId=0;
TextCol="test";
NtextCol="test";
ImageCol="dGVzdA==";
}

Push-OutputBinding -Name product -Value $req_body

Push-OutputBinding -Name response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = $req_body
})
Loading