Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a0bd291
chore(deps): upgrade to .NET 10 and Microsoft.Data.SqlClient 6.x
souvikghosh04 Jun 10, 2026
1064d7f
Update SqlClient 6.1.5
RubenCerna2079 Jun 18, 2026
96f5324
Address PR review feedback and align ASP.NET Core packages to 10.x
souvikghosh04 Jun 18, 2026
3f5b597
Merge branch 'main' into Usr/sogh/upgrade-net10-sqlclient6
souvikghosh04 Jun 18, 2026
280151b
Pin MessagePack to 2.5.301 to fix NU1903 transitive vulnerability
souvikghosh04 Jun 18, 2026
1a7d309
Formatting fixes
souvikghosh04 Jun 18, 2026
a6ecbb9
Formatting fixes
souvikghosh04 Jun 18, 2026
5544ef4
Remove unused variable
souvikghosh04 Jun 18, 2026
5e3175b
Fix SqlTestHelper for Microsoft.Data.SqlClient 6.x SqlError construct…
souvikghosh04 Jun 18, 2026
f840076
Treat empty ASPNETCORE_URLS as valid in ValidateAspNetCoreUrls
souvikghosh04 Jun 18, 2026
ed820fb
Revert unrelated changes to match main (PR review feedback)
souvikghosh04 Jun 19, 2026
3735ff4
Fix .NET 10 formatter whitespace/style on Core mutation files
souvikghosh04 Jun 19, 2026
a808e13
remove unused variable due to strict check by .Net 10
souvikghosh04 Jun 19, 2026
abb49d1
Merge branch 'main' into Usr/sogh/upgrade-net10-sqlclient6
souvikghosh04 Jun 22, 2026
fbb8159
try adding DBTYPE_RESOLUTION_ERROR back
souvikghosh04 Jun 22, 2026
f3f5583
Removing unused variable which fails in pipeline
souvikghosh04 Jun 22, 2026
bd87fa0
Suppress unused variable warning
souvikghosh04 Jun 22, 2026
d19a55f
Update SqlClient to 6.1.5
RubenCerna2079 Jun 8, 2026
3cc1c0b
First additions to vector data type
RubenCerna2079 Jun 9, 2026
2fea5ce
New changes
RubenCerna2079 Jun 10, 2026
1e49376
Make vector array type
RubenCerna2079 Jun 17, 2026
98e6434
Read
RubenCerna2079 Jun 19, 2026
0137653
More changes
RubenCerna2079 Jun 22, 2026
e7d72c8
Add write abilities
RubenCerna2079 Jun 22, 2026
36ff6eb
Add writing capabilities
RubenCerna2079 Jun 22, 2026
8a0edd2
Add tests
RubenCerna2079 Jun 24, 2026
c657910
Merge branch 'main' into dev/rubencerna/first-changes-vector-type
RubenCerna2079 Jun 24, 2026
ebe6b28
Fix tests
RubenCerna2079 Jun 24, 2026
7e45fe2
Fix syntax
RubenCerna2079 Jun 24, 2026
aef7af9
Fix syntax error
RubenCerna2079 Jun 24, 2026
e479927
Changes based on copilot
RubenCerna2079 Jun 25, 2026
0e96e32
Upgrade docker sql version
RubenCerna2079 Jun 25, 2026
84b73b1
Fix utf8
RubenCerna2079 Jun 25, 2026
99954f6
Upgrade docker sql version
RubenCerna2079 Jun 25, 2026
6404bdc
Change to sql 2025 in pipeline
RubenCerna2079 Jun 25, 2026
786f98f
Merge remote-tracking branch 'refs/remotes/origin/dev/rubencerna/firs…
RubenCerna2079 Jun 25, 2026
f96fefb
Fixed how result is returned in insert
RubenCerna2079 Jun 26, 2026
8f2eb92
Changes based on comments
RubenCerna2079 Jul 1, 2026
c357a59
Fix syntax
RubenCerna2079 Jul 1, 2026
0bdbf83
Allow read request for vector data type
RubenCerna2079 Jul 2, 2026
e7bf7bb
Add support for vector data type in graphql
RubenCerna2079 Jul 9, 2026
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
4 changes: 2 additions & 2 deletions .pipelines/mssql-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ jobs:
# for the linux job above.
data-source.connection-string: Server=(localdb)\MSSQLLocalDB;Persist Security Info=False;Integrated Security=True;MultipleActiveResultSets=False;Connection Timeout=30;TrustServerCertificate=True;
InstallerUrl: https://download.microsoft.com/download/7/c/1/7c14e92e-bdcb-4f89-b7cf-93543e7112d1/SqlLocalDB.msi
SqlVersionCode: '15.0'
SqlVersionCode: '17.0'

steps:
- template: templates/mssql-test-steps.yml
Expand All @@ -200,7 +200,7 @@ jobs:
# for the linux job above.
data-source.connection-string: Server=(localdb)\MSSQLLocalDB;Persist Security Info=False;Integrated Security=True;MultipleActiveResultSets=False;Connection Timeout=30;TrustServerCertificate=True;
InstallerUrl: https://download.microsoft.com/download/7/c/1/7c14e92e-bdcb-4f89-b7cf-93543e7112d1/SqlLocalDB.msi
SqlVersionCode: '15.0'
SqlVersionCode: '17.0'

steps:
- template: templates/mssql-test-steps.yml
Expand Down
2 changes: 2 additions & 0 deletions config-generators/mssql-commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ add Broker --config "dab-config.MsSql.json" --source brokers --permissions "anon
add WebsiteUser --config "dab-config.MsSql.json" --source website_users --permissions "anonymous:create,read,delete,update"
add WebsiteUser_MM --config "dab-config.MsSql.json" --source website_users_mm --graphql "websiteuser_mm:websiteusers_mm" --permissions "anonymous:*"
add SupportedType --config "dab-config.MsSql.json" --source type_table --permissions "anonymous:create,read,delete,update"
add VectorType --config "dab-config.MsSql.json" --source vector_type_table --rest true --graphql false --permissions "anonymous:create,read,delete,update"
update VectorType --config "dab-config.MsSql.json" --permissions "authenticated:create,read,delete,update"
add stocks_price --config "dab-config.MsSql.json" --source stocks_price --permissions "authenticated:create,read,update,delete"
update stocks_price --config "dab-config.MsSql.json" --permissions "anonymous:read"
update stocks_price --config "dab-config.MsSql.json" --permissions "TestNestedFilterFieldIsNull_ColumnForbidden:read" --fields.exclude "price"
Expand Down
2 changes: 1 addition & 1 deletion scripts/start-mssql-server.bash
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ echo "forceencryption = 1" >> $CERT_DIR/mssql.conf
cat $CERT_DIR/mssql.conf

# Start mssql-server by volume mounting the cert, key and conf files.
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$DOCKER_SQL_PASS" -p 1433:1433 --name customerdb -h customerdb -v $CERT_DIR/mssql.conf:/var/opt/mssql/mssql.conf -v $CERT_DIR/mssql.pem:/var/opt/mssql/mssql.pem -v $CERT_DIR/mssql.key:/var/opt/mssql/mssql.key -d mcr.microsoft.com/mssql/server:2019-latest
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$DOCKER_SQL_PASS" -p 1433:1433 --name customerdb -h customerdb -v $CERT_DIR/mssql.conf:/var/opt/mssql/mssql.conf -v $CERT_DIR/mssql.pem:/var/opt/mssql/mssql.pem -v $CERT_DIR/mssql.key:/var/opt/mssql/mssql.key -d mcr.microsoft.com/mssql/server:2025-latest
sleep 30
docker logs customerdb

Expand Down
3 changes: 2 additions & 1 deletion src/Core/Models/SqlTypeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public static class SqlTypeConstants
{ "datetime2", true }, // SqlDbType.DateTime2
{ "datetimeoffset", true }, // SqlDbType.DateTimeOffset
{ "", false }, // SqlDbType.Udt and SqlDbType.Structured provided by SQL as empty strings (unsupported)
{ "numeric", true} // Not present in SqlDbType, however can be returned by sql functions like LAG and should map to decimal.
{ "numeric", true}, // Not present in SqlDbType, however can be returned by sql functions like LAG and should map to decimal.
{ "vector", true } // SqlDbType.Vector
};
}
6 changes: 2 additions & 4 deletions src/Core/Resolvers/MsSqlQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,7 @@ AND ty.name IN
N'hierarchyid',
N'sql_variant',
N'xml',
N'rowversion',
N'vector'
N'rowversion'
)
) THEN 1
ELSE 0
Expand Down Expand Up @@ -712,8 +711,7 @@ AND ty.name IN
N'hierarchyid',
N'sql_variant',
N'xml',
N'rowversion',
N'vector'
N'rowversion'
)
)
)
Expand Down
14 changes: 14 additions & 0 deletions src/Core/Resolvers/MsSqlQueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Azure.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlTypes;
using Microsoft.Extensions.Logging;

namespace Azure.DataApiBuilder.Core.Resolvers
Expand Down Expand Up @@ -695,6 +696,19 @@ public override SqlCommand PrepareDbCommand(
parameter.Size = parameterEntry.Value.Length.Value;
}

// if sqldbtype is vector then set the value as an SqlVector object
if (parameter.SqlDbType is SqlDbType.Vector)
{
List<float> values = new();
foreach (float val in (Array)parameter.Value)
{
values.Add(val);
}

SqlVector<float> value = new(values.ToArray());
parameter.Value = value;
}

cmd.Parameters.Add(parameter);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Resolvers/MultipleCreateOrderHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ private static RelationshipFields GetRelationshipFieldsInSourceAndTarget(
foreach (ObjectFieldNode field in fieldNodes)
{
Tuple<IValueNode?, SyntaxKind> fieldDetails = GraphQLUtils.GetFieldDetails(field.Value, context.Variables);
SyntaxKind fieldKind = fieldDetails.Item2;
SyntaxKind fieldKind = metadataProvider.TryGetUnderlyingFieldKind(entityName, field.Name.Value, out SyntaxKind arrayFieldKind) ? arrayFieldKind : fieldDetails.Item2;
if (GraphQLUtils.IsScalarField(fieldKind) && metadataProvider.TryGetBackingColumn(entityName, field.Name.Value, out string? backingColumnName))
{
backingColumnData.Add(backingColumnName, fieldDetails.Item1);
Expand Down
23 changes: 20 additions & 3 deletions src/Core/Resolvers/QueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Azure.DataApiBuilder.Core.Models;
using Azure.DataApiBuilder.Service.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.Data.SqlTypes;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Retry;
Expand Down Expand Up @@ -502,7 +503,7 @@ public async Task<DbResultSet>
{
if (!ConfigProvider.GetConfig().MaxResponseSizeLogicEnabled())
{
dbResultSetRow.Columns.Add(columnName, dbDataReader[columnName]);
dbResultSetRow.Columns.Add(columnName, GetColumnInformation(dbDataReader, columnName));
}
else
{
Expand Down Expand Up @@ -554,7 +555,7 @@ public DbResultSet
{
if (!ConfigProvider.GetConfig().MaxResponseSizeLogicEnabled())
{
dbResultSetRow.Columns.Add(columnName, dbDataReader[columnName]);
dbResultSetRow.Columns.Add(columnName, GetColumnInformation(dbDataReader, columnName));
}
else
{
Expand Down Expand Up @@ -822,7 +823,7 @@ internal int StreamDataIntoDbResultSetRow(DbDataReader dbDataReader, DbResultSet
{
dataRead = columnSize;
ValidateSize(availableBytes, dataRead);
dbResultSetRow.Columns.Add(columnName, dbDataReader[columnName]);
dbResultSetRow.Columns.Add(columnName, GetColumnInformation(dbDataReader, columnName));
}

return dataRead;
Expand Down Expand Up @@ -885,6 +886,22 @@ private void ValidateSize(long availableSizeBytes, long sizeToBeReadBytes)
}
}

/// <summary>
/// Helper function to get column information from the DbDataReader and handle special cases like SqlVector<float>.
/// </summary>
/// <param name="dbDataReader"></param>
/// <param name="columnName"></param>
/// <returns></returns>
private static object GetColumnInformation(DbDataReader dbDataReader, string columnName)
{
if (dbDataReader[columnName] is SqlVector<float> columnValue)
{
return columnValue.Memory;
}

return dbDataReader[columnName];
}

internal virtual void AddDbExecutionTimeToMiddlewareContext(long time)
{
HttpContext? httpContext = HttpContextAccessor?.HttpContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Data;
using System.Globalization;
using System.Net;
using System.Text.Json;
using Azure.DataApiBuilder.Auth;
using Azure.DataApiBuilder.Config.DatabasePrimitives;
using Azure.DataApiBuilder.Config.ObjectModel;
Expand Down Expand Up @@ -452,10 +453,50 @@ protected static object ParseParamAsSystemType(string param, Type systemType)
"Guid" => Guid.Parse(param),
"TimeOnly" => TimeOnly.Parse(param),
"TimeSpan" => TimeOnly.Parse(param),
"Single[]" => ParseArrayIntoSystemType(param, systemType),
_ => throw new NotSupportedException($"{systemType.Name} is not supported")
};
}

/// <summary>
/// Takes the array of the parameter we are going to parse and converts each element to the specified system type.
/// </summary>
/// <param name="param"></param>
/// <param name="systemType"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="FormatException"></exception>
private static object ParseArrayIntoSystemType(string param, Type systemType)
{
Type typeOfArray;
switch (systemType.Name)
{
case "Single[]":
typeOfArray = typeof(Single);
break;

default:
throw new NotSupportedException($"{systemType.Name} is not supported");
}

try
{
List<object> list = new();
object[] values = JsonSerializer.Deserialize<object[]>(param) ?? Array.Empty<object>();
for (int i = 0; i < values.Length; i++)
{
string stringValue = values[i]?.ToString() ?? string.Empty;
values[i] = ParseParamAsSystemType(stringValue, typeOfArray);
}

return values;
}
catch
{
throw new FormatException($"Expected an array for {systemType.Name} but got an unexpected value");
}
}

/// <summary>
/// Very similar to GQLArgumentToDictParams but only extracts the argument names from
/// the specified field which means that the method does not need a middleware context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// Licensed under the MIT License.

using System.Net;
using System.Text.Json;
using Azure.DataApiBuilder.Auth;
using Azure.DataApiBuilder.Config.DatabasePrimitives;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Core.Models;
using Azure.DataApiBuilder.Core.Services;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Mutations;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using Microsoft.AspNetCore.Http;

Expand Down Expand Up @@ -111,8 +113,17 @@ private void PopulateColumnsAndParams(string columnName, object? value)

if (value is not null)
{
// Array/vector columns (e.g. SQL Server 'vector') arrive as a List<IValueNode>.
// Calling ToString() on a list/array only yields the CLR type name, so instead extract
// the underlying element values and serialize them into a JSON array string (e.g. "[1.5,2.5,3.5]").
string stringifiedValue = value switch
{
IEnumerable<IValueNode> valueNodes => JsonSerializer.Serialize(valueNodes.Select(TypeHelper.GetValue)),
_ => value.ToString()!
};

paramName = MakeDbConnectionParam(
GetParamAsSystemType(value.ToString()!, columnName, GetColumnSystemType(columnName)), columnName);
GetParamAsSystemType(stringifiedValue, columnName, GetColumnSystemType(columnName)), columnName);
}
else
{
Expand Down
33 changes: 23 additions & 10 deletions src/Core/Resolvers/SqlMutationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,7 +1088,7 @@ await queryExecutor.ExecuteQueryAsync(
string rootFieldName = isMultipleInputType ? MULTIPLE_INPUT_ARGUEMENT_NAME : SINGLE_INPUT_ARGUEMENT_NAME;

// Parse the hotchocolate input parameters into .net object types
object? parsedInputParams = GQLMultipleCreateArgumentToDictParams(context, rootFieldName, mutationInputParamsFromGQLContext);
object? parsedInputParams = GQLMultipleCreateArgumentToDictParams(context, rootFieldName, mutationInputParamsFromGQLContext, sqlMetadataProvider, entityName);

if (parsedInputParams is null)
{
Expand Down Expand Up @@ -1758,14 +1758,16 @@ private static void PopulateCurrentAndLinkingEntityParams(
internal static object? GQLMultipleCreateArgumentToDictParams(
IMiddlewareContext context,
string rootFieldName,
IDictionary<string, object?> mutationParameters)
IDictionary<string, object?> mutationParameters,
ISqlMetadataProvider metadataProvider,
string entityName)
{
if (mutationParameters.TryGetValue(rootFieldName, out object? inputParameters))
{
ObjectField fieldSchema = context.Selection.Field;
IInputValueDefinition itemsArgumentSchema = fieldSchema.Arguments[rootFieldName];
InputObjectType inputObjectType = ExecutionHelper.InputObjectTypeFromIInputField(itemsArgumentSchema);
return GQLMultipleCreateArgumentToDictParamsHelper(context, inputObjectType, inputParameters);
return GQLMultipleCreateArgumentToDictParamsHelper(context, inputObjectType, inputParameters, metadataProvider, entityName);
}
else
{
Expand Down Expand Up @@ -1813,7 +1815,9 @@ private static void PopulateCurrentAndLinkingEntityParams(
internal static object? GQLMultipleCreateArgumentToDictParamsHelper(
IMiddlewareContext context,
InputObjectType inputObjectType,
object? inputParameters)
object? inputParameters,
ISqlMetadataProvider metadataProvider,
string entityName)
{
// This condition is met for input types that accept an array of values
// where the mutation input field is 'items' such as
Expand All @@ -1835,7 +1839,9 @@ private static void PopulateCurrentAndLinkingEntityParams(
object? parsedInputFieldItem = GQLMultipleCreateArgumentToDictParamsHelper(
context: context,
inputObjectType: inputObjectType,
inputParameters: inputField.Value);
inputParameters: inputField.Value,
metadataProvider: metadataProvider,
entityName: entityName);
if (parsedInputFieldItem is not null)
{
parsedInputFieldItems.Add((IDictionary<string, object?>)parsedInputFieldItem);
Expand All @@ -1862,33 +1868,40 @@ private static void PopulateCurrentAndLinkingEntityParams(
foreach (ObjectFieldNode inputFieldNode in inputFieldNodes)
{
string fieldName = inputFieldNode.Name.Value;
SyntaxKind fieldKind = metadataProvider.TryGetUnderlyingFieldKind(entityName, fieldName, out SyntaxKind arrayFieldKind)
? arrayFieldKind : inputFieldNode.Value.Kind;

// For the mutation pointMultipleCreateExample (outlined in the method summary),
// the following condition will evaluate to true for fields 'authors' and 'reviews'.
// Fields 'authors'/'reviews' can again consist of combination of scalar and relationship fields.
// So, the input object type for 'authors'/'reviews' is fetched and the same function is
// invoked with the fetched input object type again to parse the input fields of 'authors'/'reviews'.
if (inputFieldNode.Value.Kind == SyntaxKind.ListValue)
if (fieldKind == SyntaxKind.ListValue)
{
parsedInputFields.Add(
fieldName,
GQLMultipleCreateArgumentToDictParamsHelper(
context,
GetInputObjectTypeForAField(fieldName, inputObjectType.Fields),
inputFieldNode.Value.Value));
inputFieldNode.Value.Value,
metadataProvider,
entityName));
}
// For the mutation pointMultipleCreateExample (outlined in the method summary),
// the following condition will evaluate to true for fields 'publishers'.
// Field 'publishers' can again consist of combination of scalar and relationship fields.
// So, the input object type for 'publishers' is fetched and the same function is
// invoked with the fetched input object type again to parse the input fields of 'publishers'.
else if (inputFieldNode.Value.Kind == SyntaxKind.ObjectValue)
else if (fieldKind == SyntaxKind.ObjectValue)
{
parsedInputFields.Add(
fieldName,
GQLMultipleCreateArgumentToDictParamsHelper(
context,
GetInputObjectTypeForAField(fieldName, inputObjectType.Fields),
inputFieldNode.Value.Value));
inputFieldNode.Value.Value,
metadataProvider,
entityName));
}
// The flow enters this block for all scalar input fields.
else
Expand Down Expand Up @@ -2366,7 +2379,7 @@ private void ProcessObjectFieldNodesForAuthZ(
foreach (ObjectFieldNode field in fieldNodes)
{
Tuple<IValueNode?, SyntaxKind> fieldDetails = GraphQLUtils.GetFieldDetails(field.Value, context.Variables);
SyntaxKind underlyingFieldKind = fieldDetails.Item2;
SyntaxKind underlyingFieldKind = metadataProvider.TryGetUnderlyingFieldKind(entityName, field.Name.Value, out SyntaxKind arrayFieldKind) ? arrayFieldKind : fieldDetails.Item2;

// For a column field, we do not have to recurse to process fields in the value - which is required for relationship fields.
if (GraphQLUtils.IsScalarField(underlyingFieldKind) || underlyingFieldKind is SyntaxKind.NullValue)
Expand Down
Loading
Loading