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
138 changes: 138 additions & 0 deletions .github/workflows/auto-approve-codeflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: Auto-approve dependency-only codeflow PRs

on:
pull_request_target:
types: [opened, synchronize]

permissions:
pull-requests: write

jobs:
auto-approve-codeflow:
if: github.event.pull_request.user.login == 'dotnet-maestro[bot]'
runs-on: ubuntu-latest
steps:
- name: Validate and auto-approve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail

REPO="${GITHUB_REPOSITORY}"
PR="${PR_NUMBER}"

echo "Validating codeflow PR #${PR}..."

# Get all changed files with their patches
files_json=$(gh api "repos/${REPO}/pulls/${PR}/files" --paginate | jq -s 'add')
file_count=$(echo "$files_json" | jq 'length')
echo "PR modifies ${file_count} file(s)"

if [[ "$file_count" -eq 0 ]]; then
echo "::error::PR has no changed files"
exit 1
fi

for ((i = 0; i < file_count; i++)); do
filename=$(echo "$files_json" | jq -r ".[$i].filename")
status=$(echo "$files_json" | jq -r ".[$i].status")
patch=$(echo "$files_json" | jq -r ".[$i].patch // \"\"")

echo "::group::Validating ${filename}"

# 1. File must be in the allowed set
case "$filename" in
NuGet.config|eng/Version.Details.xml|eng/Version.Details.props|global.json)
;;
*)
echo "::error::File '${filename}' is not in the allowed set for dependency-only codeflow PRs"
exit 1
;;
esac

# 2. Only modified files are expected
if [[ "$status" != "modified" ]]; then
echo "::error::File '${filename}' has status '${status}' (expected 'modified')"
exit 1
fi

# 3. Patch data must be present
if [[ -z "$patch" ]]; then
echo "::error::File '${filename}' has no patch data"
exit 1
fi

# 4. Validate each changed line in the diff
while IFS= read -r line; do
[[ -z "$line" ]] && continue

# Only check added (+) and removed (-) lines
if [[ "$line" != +* && "$line" != -* ]]; then
continue
fi

# Get the content (strip the +/- prefix)
content="${line:1}"

# Skip empty/whitespace-only lines
if [[ "$content" =~ ^[[:space:]]*$ ]]; then
continue
fi

case "$filename" in
"eng/Version.Details.xml")
# Allowed: <Source> element with Sha/BarId, <Sha> elements, <Dependency> opening tags with Version
if [[ "$content" =~ ^[[:space:]]*\<Source[[:space:]]+Uri=\"https://github\.com/dotnet/dotnet\"[[:space:]]+Mapping=\"efcore\"[[:space:]]+Sha=\"[0-9a-f]+\"[[:space:]]+BarId=\"[0-9]+\"[[:space:]]*/\> ]]; then
:
elif [[ "$content" =~ ^[[:space:]]*\<Sha\>[0-9a-f]+\</Sha\> ]]; then
:
elif [[ "$content" =~ ^[[:space:]]*\<Dependency[[:space:]]+Name=\"[^\"]+\"[[:space:]]+Version=\"[^\"]+\" ]]; then
:
else
echo "::error::Unexpected change in ${filename}: ${content}"
exit 1
fi
;;

"eng/Version.Details.props")
# Allowed: package version property elements like <SomethingVersion>value</SomethingVersion>
if [[ "$content" =~ ^[[:space:]]*\<[A-Za-z0-9]+Version\>[^<]+\</[A-Za-z0-9]+Version\> ]]; then
:
else
echo "::error::Unexpected change in ${filename}: ${content}"
exit 1
fi
;;

"global.json")
# Allowed: msbuild-sdks version entries only
if [[ "$content" =~ ^[[:space:]]*\"Microsoft\.DotNet\.(Arcade|Helix)\.Sdk\":[[:space:]]*\"[^\"]+\" ]]; then
:
else
echo "::error::Unexpected change in ${filename}: ${content}"
exit 1
fi
;;

"NuGet.config")
# Allowed: darc-* package source entries pointing to Azure DevOps NuGet feeds
if [[ "$content" =~ ^[[:space:]]*\<add[[:space:]]+key=\"darc-[^\"]+\"[[:space:]]+value=\"https://pkgs\.dev\.azure\.com/dnceng/(public|internal)/_packaging/darc-[^\"]+/nuget/v3/index\.json\"[[:space:]]*/\> ]]; then
:
else
echo "::error::Unexpected change in ${filename}: ${content}"
exit 1
fi
;;
esac
done <<< "$patch"

echo " ✅ Validated"
echo "::endgroup::"
done

echo ""
echo "All files contain only expected dependency update changes."
echo "Approving PR..."
gh pr review "$PR" --repo "$REPO" --approve \
--body "Auto-approved: this codeflow PR contains only dependency version updates."
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ protected override Expression VisitExtension(Expression expression)
var navigation = collectionResultExpression.StructuralProperty switch
{
INavigationBase n => n,
null => null,
null or IComplexProperty => null,
_ => throw new UnreachableException()
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ FROM root c

#region Subquery

public override Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
=> AssertTranslationFailed(() => base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior));

public override Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
=> AssertTranslationFailed(() => base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ public override async Task Select_associate_and_target_to_index_based_binding_vi

#region Subquery

public override async Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
{
if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll)
{
await AssertTranslationFailed(() => base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior));
}
}

public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
{
if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,18 @@ public virtual Task Select_subquery_optional_related_FirstOrDefault(QueryTrackin
.FirstOrDefault()!.RequiredNestedAssociate),
queryTrackingBehavior: queryTrackingBehavior);

[ConditionalTheory, MemberData(nameof(TrackingData))]
public virtual Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
=> AssertQuery(
ss => ss.Set<RootEntity>()
.OrderBy(x => x.Id)
.Select(x => ss.Set<RootEntity>()
.OrderBy(e => e.Id)
.FirstOrDefault()!.AssociateCollection),
assertOrder: true,
elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: r => r.Id),
queryTrackingBehavior: queryTrackingBehavior);

// [ConditionalTheory]
// [MemberData(nameof(TrackingData))]
// public virtual Task Select_subquery_root_set_trunk_FirstOrDefault_collection(QueryTrackingBehavior queryTrackingBehavior)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ public override Task Select_associate_and_target_to_index_based_binding_via_clos

#region Subquery

public override Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
=> AssertOwnedTrackingQuery(
queryTrackingBehavior, () => base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior));

public override Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
=> AssertOwnedTrackingQuery(
queryTrackingBehavior, () => base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,23 @@ FROM [RootEntity] AS [r]

#region Subquery

public override async Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior);

AssertSql(
"""
SELECT [r1].[c], [r1].[c0]
FROM [RootEntity] AS [r]
OUTER APPLY (
SELECT TOP(1) [r0].[AssociateCollection] AS [c], 1 AS [c0]
FROM [RootEntity] AS [r0]
ORDER BY [r0].[Id]
) AS [r1]
ORDER BY [r].[Id]
""");
}

public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,23 @@ FROM [RootEntity] AS [r]

#region Subquery

public override async Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior);

AssertSql(
"""
SELECT [r1].[Id], [r1].[Name], [r1].[OptionalAssociate_Id], [r1].[OptionalAssociate_Int], [r1].[OptionalAssociate_Ints], [r1].[OptionalAssociate_Name], [r1].[OptionalAssociate_String], [r1].[OptionalAssociate_OptionalNestedAssociate_Id], [r1].[OptionalAssociate_OptionalNestedAssociate_Int], [r1].[OptionalAssociate_OptionalNestedAssociate_Ints], [r1].[OptionalAssociate_OptionalNestedAssociate_Name], [r1].[OptionalAssociate_OptionalNestedAssociate_String], [r1].[OptionalAssociate_RequiredNestedAssociate_Id], [r1].[OptionalAssociate_RequiredNestedAssociate_Int], [r1].[OptionalAssociate_RequiredNestedAssociate_Ints], [r1].[OptionalAssociate_RequiredNestedAssociate_Name], [r1].[OptionalAssociate_RequiredNestedAssociate_String], [r1].[RequiredAssociate_Id], [r1].[RequiredAssociate_Int], [r1].[RequiredAssociate_Ints], [r1].[RequiredAssociate_Name], [r1].[RequiredAssociate_String], [r1].[RequiredAssociate_OptionalNestedAssociate_Id], [r1].[RequiredAssociate_OptionalNestedAssociate_Int], [r1].[RequiredAssociate_OptionalNestedAssociate_Ints], [r1].[RequiredAssociate_OptionalNestedAssociate_Name], [r1].[RequiredAssociate_OptionalNestedAssociate_String], [r1].[RequiredAssociate_RequiredNestedAssociate_Id], [r1].[RequiredAssociate_RequiredNestedAssociate_Int], [r1].[RequiredAssociate_RequiredNestedAssociate_Ints], [r1].[RequiredAssociate_RequiredNestedAssociate_Name], [r1].[RequiredAssociate_RequiredNestedAssociate_String], [r1].[c]
FROM [RootEntity] AS [r]
OUTER APPLY (
SELECT TOP(1) [r0].[Id], [r0].[Name], [r0].[OptionalAssociate_Id], [r0].[OptionalAssociate_Int], [r0].[OptionalAssociate_Ints], [r0].[OptionalAssociate_Name], [r0].[OptionalAssociate_String], [r0].[OptionalAssociate_OptionalNestedAssociate_Id], [r0].[OptionalAssociate_OptionalNestedAssociate_Int], [r0].[OptionalAssociate_OptionalNestedAssociate_Ints], [r0].[OptionalAssociate_OptionalNestedAssociate_Name], [r0].[OptionalAssociate_OptionalNestedAssociate_String], [r0].[OptionalAssociate_RequiredNestedAssociate_Id], [r0].[OptionalAssociate_RequiredNestedAssociate_Int], [r0].[OptionalAssociate_RequiredNestedAssociate_Ints], [r0].[OptionalAssociate_RequiredNestedAssociate_Name], [r0].[OptionalAssociate_RequiredNestedAssociate_String], [r0].[RequiredAssociate_Id], [r0].[RequiredAssociate_Int], [r0].[RequiredAssociate_Ints], [r0].[RequiredAssociate_Name], [r0].[RequiredAssociate_String], [r0].[RequiredAssociate_OptionalNestedAssociate_Id], [r0].[RequiredAssociate_OptionalNestedAssociate_Int], [r0].[RequiredAssociate_OptionalNestedAssociate_Ints], [r0].[RequiredAssociate_OptionalNestedAssociate_Name], [r0].[RequiredAssociate_OptionalNestedAssociate_String], [r0].[RequiredAssociate_RequiredNestedAssociate_Id], [r0].[RequiredAssociate_RequiredNestedAssociate_Int], [r0].[RequiredAssociate_RequiredNestedAssociate_Ints], [r0].[RequiredAssociate_RequiredNestedAssociate_Name], [r0].[RequiredAssociate_RequiredNestedAssociate_String], 1 AS [c]
FROM [RootEntity] AS [r0]
ORDER BY [r0].[Id]
) AS [r1]
ORDER BY [r].[Id]
""");
}

public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,30 @@ FROM [RootEntity] AS [r]

#region Subquery

public override async Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior);

AssertSql(
"""
SELECT [r].[Id], [r1].[Id], [s].[Id], [s].[CollectionRootId], [s].[Int], [s].[Ints], [s].[Name], [s].[OptionalNestedAssociateId], [s].[RequiredNestedAssociateId], [s].[String], [s].[Id0], [s].[Id1], [s].[Id2], [s].[CollectionAssociateId], [s].[Int0], [s].[Ints0], [s].[Name0], [s].[String0], [s].[CollectionAssociateId0], [s].[Int1], [s].[Ints1], [s].[Name1], [s].[String1], [s].[CollectionAssociateId1], [s].[Int2], [s].[Ints2], [s].[Name2], [s].[String2], [r1].[c]
FROM [RootEntity] AS [r]
OUTER APPLY (
SELECT TOP(1) 1 AS [c], [r0].[Id]
FROM [RootEntity] AS [r0]
ORDER BY [r0].[Id]
) AS [r1]
LEFT JOIN (
SELECT [a].[Id], [a].[CollectionRootId], [a].[Int], [a].[Ints], [a].[Name], [a].[OptionalNestedAssociateId], [a].[RequiredNestedAssociateId], [a].[String], [n].[Id] AS [Id0], [n0].[Id] AS [Id1], [n1].[Id] AS [Id2], [n1].[CollectionAssociateId], [n1].[Int] AS [Int0], [n1].[Ints] AS [Ints0], [n1].[Name] AS [Name0], [n1].[String] AS [String0], [n].[CollectionAssociateId] AS [CollectionAssociateId0], [n].[Int] AS [Int1], [n].[Ints] AS [Ints1], [n].[Name] AS [Name1], [n].[String] AS [String1], [n0].[CollectionAssociateId] AS [CollectionAssociateId1], [n0].[Int] AS [Int2], [n0].[Ints] AS [Ints2], [n0].[Name] AS [Name2], [n0].[String] AS [String2]
FROM [AssociateType] AS [a]
LEFT JOIN [NestedAssociateType] AS [n] ON [a].[OptionalNestedAssociateId] = [n].[Id]
INNER JOIN [NestedAssociateType] AS [n0] ON [a].[RequiredNestedAssociateId] = [n0].[Id]
LEFT JOIN [NestedAssociateType] AS [n1] ON [a].[Id] = [n1].[CollectionAssociateId]
) AS [s] ON [r1].[Id] = [s].[CollectionRootId]
ORDER BY [r].[Id], [r1].[Id], [s].[Id], [s].[Id0], [s].[Id1]
""");
}

public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,26 @@ FROM [RootEntity] AS [r]

#region Subquery

public override async Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior);

if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll)
{
AssertSql(
"""
SELECT [r1].[c], [r1].[Id], [r1].[c0]
FROM [RootEntity] AS [r]
OUTER APPLY (
SELECT TOP(1) [r0].[AssociateCollection] AS [c], [r0].[Id], 1 AS [c0]
FROM [RootEntity] AS [r0]
ORDER BY [r0].[Id]
) AS [r1]
ORDER BY [r].[Id]
""");
}
}

public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,33 @@ FROM [RootEntity] AS [r]

#region Subquery

public override async Task Select_subquery_FirstOrDefault_complex_collection(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_FirstOrDefault_complex_collection(queryTrackingBehavior);

if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll)
{
AssertSql(
"""
SELECT [r].[Id], [r5].[Id], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Ints], [s].[Name], [s].[String], [s].[AssociateTypeRootEntityId], [s].[AssociateTypeId], [s].[AssociateTypeRootEntityId0], [s].[AssociateTypeId0], [s].[AssociateTypeRootEntityId1], [s].[AssociateTypeId1], [s].[Id0], [s].[Int0], [s].[Ints0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Ints1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Ints2], [s].[Name2], [s].[String2], [r5].[c]
FROM [RootEntity] AS [r]
OUTER APPLY (
SELECT TOP(1) 1 AS [c], [r0].[Id]
FROM [RootEntity] AS [r0]
ORDER BY [r0].[Id]
) AS [r5]
LEFT JOIN (
SELECT [r1].[RootEntityId], [r1].[Id], [r1].[Int], [r1].[Ints], [r1].[Name], [r1].[String], [r2].[AssociateTypeRootEntityId], [r2].[AssociateTypeId], [r3].[AssociateTypeRootEntityId] AS [AssociateTypeRootEntityId0], [r3].[AssociateTypeId] AS [AssociateTypeId0], [r4].[AssociateTypeRootEntityId] AS [AssociateTypeRootEntityId1], [r4].[AssociateTypeId] AS [AssociateTypeId1], [r4].[Id] AS [Id0], [r4].[Int] AS [Int0], [r4].[Ints] AS [Ints0], [r4].[Name] AS [Name0], [r4].[String] AS [String0], [r2].[Id] AS [Id1], [r2].[Int] AS [Int1], [r2].[Ints] AS [Ints1], [r2].[Name] AS [Name1], [r2].[String] AS [String1], [r3].[Id] AS [Id2], [r3].[Int] AS [Int2], [r3].[Ints] AS [Ints2], [r3].[Name] AS [Name2], [r3].[String] AS [String2]
FROM [RelatedCollection] AS [r1]
LEFT JOIN [RelatedCollection_OptionalNested] AS [r2] ON [r1].[RootEntityId] = [r2].[AssociateTypeRootEntityId] AND [r1].[Id] = [r2].[AssociateTypeId]
LEFT JOIN [RelatedCollection_RequiredNested] AS [r3] ON [r1].[RootEntityId] = [r3].[AssociateTypeRootEntityId] AND [r1].[Id] = [r3].[AssociateTypeId]
LEFT JOIN [RelatedCollection_NestedCollection] AS [r4] ON [r1].[RootEntityId] = [r4].[AssociateTypeRootEntityId] AND [r1].[Id] = [r4].[AssociateTypeId]
) AS [s] ON [r5].[Id] = [s].[RootEntityId]
ORDER BY [r].[Id], [r5].[Id], [s].[RootEntityId], [s].[Id], [s].[AssociateTypeRootEntityId], [s].[AssociateTypeId], [s].[AssociateTypeRootEntityId0], [s].[AssociateTypeId0], [s].[AssociateTypeRootEntityId1], [s].[AssociateTypeId1]
""");
}
}

public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior)
{
await base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior);
Expand Down
Loading
Loading