Repository: Azure/azure-sdk-for-go
Pull request #20021 (2023-02-15 09:56:16)

changelog generator tool add `PageableFilter` and `type to any changelog print`
https://github.com/Azure/azure-sdk-for-go/issues/20017


Patch:
diff --git a/eng/common/scripts/get-codeowners.ps1 b/eng/common/scripts/get-codeowners.ps1
index 4264d472a5..5c2bb9b50a 100644
--- a/eng/common/scripts/get-codeowners.ps1
+++ b/eng/common/scripts/get-codeowners.ps1
@@ -1,25 +1,86 @@
+<#
+.SYNOPSIS
+A script that given as input $TargetPath param, returns the owners
+of that path, as determined by CODEOWNERS file passed in $CodeOwnersFileLocation
+param.
+
+.PARAMETER TargetPath
+Required*. Path to file or directory whose owners are to be determined from a 
+CODEOWNERS file. e.g. sdk/core/azure-amqp/ or sdk/core/foo.txt.
+
+*for backward compatibility, you might provide $TargetDirectory instead.
+
+.PARAMETER TargetDirectory
+Obsolete. Replaced by $TargetPath. Kept for backward-compatibility.
+If both $TargetPath and $TargetDirectory are provided, $TargetDirectory is
+ignored.
+
+.PARAMETER CodeOwnerFileLocation
+Optional. An absolute path to the CODEOWNERS file against which the $TargetPath param
+will be checked to determine its owners.
+
+.PARAMETER ToolVersion
+Optional. The NuGet package version of the package containing the "retrieve-codeowners" 
+tool, around which this script is a wrapper.
+
+.PARAMETER ToolPath
+Optional. The place to check the "retrieve-codeowners" tool existence.
+
+.PARAMETER DevOpsFeed
+Optional. The NuGet package feed from which the "retrieve-codeowners" tool is to be installed.
+
+NuGet feed:
+https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-net/NuGet/Azure.Sdk.Tools.RetrieveCodeOwners
+
+Pipeline publishing the NuGet package to the feed, "tools - code-owners-parser":
+https://dev.azure.com/azure-sdk/internal/_build?definitionId=3188
+
+.PARAMETER VsoVariable 
+Optional. If provided, the determined owners, based on $TargetPath matched against CODEOWNERS file at $CodeOwnerFileLocation, 
+will be output to Azure DevOps pipeline log as variable named $VsoVariable.
+
+Reference:
+https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch
+https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logging-command-format
+
+.PARAMETER IncludeNonUserAliases
+Optional. Whether to include in the returned owners list aliases that are team aliases, e.g. Azure/azure-sdk-team
+
+.PARAMETER Test
+Optional. Whether to run the script against hard-coded tests.
+
+#>
 param (
-  [string]$TargetDirectory = "", # Code path to code owners. e.g sdk/core/azure-amqp
-  [string]$CodeOwnerFileLocation = (Resolve-Path $PSScriptRoot/../../../.github/CODEOWNERS), # The absolute path of CODEOWNERS file. 
-  [string]$ToolVersion = "1.0.0-dev.20230108.6", 
-  [string]$ToolPath = (Join-Path ([System.IO.Path]::GetTempPath()) "codeowners-tool-path"), # The place to check the tool existence. Put temp path as default
-  [string]$DevOpsFeed = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json", # DevOp tool feeds.
-  [string]$VsoVariable = "", # Option of write code owners into devop variable
-  [switch]$IncludeNonUserAliases, # Option to filter out the team alias in code owner list. e.g. Azure/azure-sdk-team
-  [switch]$Test  #Run test functions against the script logic
+  [string]$TargetPath = "",
+  [string]$TargetDirectory = "",
+  # The path used assumes the script is located in azure-sdk-tools/eng/common/scripts/get-codeowners.ps1
+  [string]$CodeOwnerFileLocation = (Resolve-Path $PSScriptRoot/../../../.github/CODEOWNERS),
+  # The $ToolVersion 1.0.0-dev.20230214.3 includes following PR:
+  #    Use CodeownersFile.UseRegexMatcherDefault everywhere where applicable + remove obsolete tests
+  #    https://github.com/Azure/azure-sdk-tools/pull/5437
+  # 
+  # but not this one:
+  #    Remove the obsolete, prefix-based CODEOWNERS matcher & related tests
+  #    https://github.com/Azure/azure-sdk-tools/pull/5431
+  [string]$ToolVersion = "1.0.0-dev.20230214.3",
+  [string]$ToolPath = (Join-Path ([System.IO.Path]::GetTempPath()) "codeowners-tool-path"),
+  [string]$DevOpsFeed = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json",
+  [string]$VsoVariable = "",
+  [switch]$IncludeNonUserAliases,
+  [switch]$Test
 )
 
-function Get-CodeOwnersTool()
+function Get-CodeownersTool()
 {
-  $command = Join-Path $ToolPath "retrieve-codeowners"
-  # Check if the retrieve-codeowners tool exsits or not.
-  if (Get-Command $command -errorAction SilentlyContinue) {
-    return $command
+  $codeownersToolCommand = Join-Path $ToolPath "retrieve-codeowners"
+  # Check if the retrieve-codeowners tool exists or not.
+  if (Get-Command $codeownersToolCommand -errorAction SilentlyContinue) {
+    return $codeownersToolCommand
   }
   if (!(Test-Path $ToolPath)) {
     New-Item -ItemType Directory -Path $ToolPath | Out-Null
   }
-  Write-Host "Installing the retrieve-codeowners tool under $ToolPath... "
+  Write-Host "Installing the retrieve-codeowners tool under tool path: $ToolPath ..."
 
   # Run command under tool path to avoid dotnet tool install command checking .csproj files. 
   # This is a bug for dotnet tool command. Issue: https://github.com/dotnet/sdk/issues/9623
@@ -27,46 +88,64 @@ function Get-CodeOwnersTool()
   dotnet tool install --tool-path $ToolPath --add-source $DevOpsFeed --version $ToolVersion "Azure.Sdk.Tools.RetrieveCodeOwners" | Out-Null
   Pop-Location
   # Test to see if the tool properly installed.
-  if (!(Get-Command $command -errorAction SilentlyContinue)) {
-    Write-Error "The retrieve-codeowners tool is not properly installed. Please check your tool path. $ToolPath"
+  if (!(Get-Command $codeownersToolCommand -errorAction SilentlyContinue)) {
+    Write-Error "The retrieve-codeowners tool is not properly installed. Please check your tool path: $ToolPath"
     return 
   }
-  return $command
+  return $codeownersToolCommand
 }
 
-function Get-CodeOwners ([string]$targetDirectory, [string]$codeOwnerFileLocation, [bool]$includeNonUserAliases = $false)
+function Get-Codeowners(
+  [string]$targetPath,
+  [string]$targetDirectory,
+  [string]$codeownersFileLocation,
+  [bool]$includeNonUserAliases = $false)
 {
-  $command = Get-CodeOwnersTool
-  # Filter out the non user alias from code owner list.
-  if($includeNonUserAliases) {
-    $codeOwnersString = & $command --target-directory $targetDirectory --code-owner-file-path $codeOwnerFileLocation 2>&1
-  }
-  else {
-    $codeOwnersString = & $command --target-directory $targetDirectory --code-owner-file-path $codeOwnerFileLocation --filter-out-non-user-aliases 2>&1
+  # Backward compaitiblity: if $targetPath is not provided, fall-back to the legacy $targetDirectory
+  if ([string]::IsNullOrWhiteSpace($targetPath)) {
+    $targetPath = $targetDirectory
   }
-  # Failed at the command of fetching code owners.
-  if ($LASTEXITCODE -ne 0) {
-    Write-Host $codeOwnersString
+  if ([string]::IsNullOrWhiteSpace($targetPath)) {
+    Write-Error "TargetPath (or TargetDirectory) parameter must be neither null nor whitespace."
     return ,@()
   }
-  
-  $codeOwnersJson = $codeOwnersString | ConvertFrom-Json
-  if (!$codeOwnersJson) {
-    Write-Host "No code owners returned from the path: $targetDirectory"
+
+  $codeownersToolCommand = Get-CodeownersTool
+  Write-Host "Executing: & $codeownersToolCommand --target-path $targetPath --codeowners-file-path-or-url $codeownersFileLocation --exclude-non-user-aliases:$(!$includeNonUserAliases)"
+  $commandOutput = & $codeownersToolCommand `
+      --target-path $targetPath `
+      --codeowners-file-path-or-url $codeownersFileLocation `
+      --exclude-non-user-aliases:$(!$includeNonUserAliases) `
+      2>&1
+
+  if ($LASTEXITCODE -ne 0) {
+    Write-Host "Command $codeownersToolCommand execution failed (exit code = $LASTEXITCODE). Output string: $commandOutput"
     return ,@()
+  } else
+  {
+    Write-Host "Command $codeownersToolCommand executed successfully (exit code = 0). Output string length: $($commandOutput.length)"
   }
+
+# Assert: $commandOutput is a valid JSON representing:
+# - a single CodeownersEntry, if the $targetPath was a single path
+# - or a dictionary of CodeownerEntries, keyes by each path resolved from a $targetPath glob path.
+#
+# For implementation details, see Azure.Sdk.Tools.RetrieveCodeOwners.Program.Main
+
+$codeownersJson = $commandOutput | ConvertFrom-Json
   
   if ($VsoVariable) {
-    $codeOwners = $codeOwnersJson.Owners -join ","
-    Write-Host "##vso[task.setvariable variable=$VsoVariable;]$codeOwners"
+    $codeowners = $codeownersJson.Owners -join ","
+    Write-Host "##vso[task.setvariable variable=$VsoVariable;]$codeowners"
   }
 
-  return ,@($codeOwnersJson.Owners)
+  return ,@($codeownersJson.Owners)
 }
 
-function TestGetCodeOwner([string]$targetDirectory, [string]$codeOwnerFileLocation, [bool]$includeNonUserAliases = $false, [string[]]$expectReturn) {
-  Write-Host "Testing on $targetDirectory..."
-  $actualReturn = Get-CodeOwners -targetDirectory $targetDirectory -codeOwnerFileLocation $codeOwnerFileLocation -includeNonUserAliases $IncludeNonUserAliases
+function TestGetCodeowners([string]$targetPath, [string]$codeownersFileLocation, [bool]$includeNonUserAliases = $false, [string[]]$expectReturn) {
+  Write-Host "Test: find owners matching '$targetPath' ..."
+  
+  $actualReturn = Get-Codeowners -targetPath $targetPath -codeownersFileLocation $codeownersFileLocation -includeNonUserAliases $IncludeNonUserAliases
 
   if ($actualReturn.Count -ne $expectReturn.Count) {
     Write-Error "The length of actual result is not as expected. Expected length: $($expectReturn.Count), Actual length: $($actualReturn.Count)."
@@ -80,21 +159,19 @@ function TestGetCodeOwner([string]$targetDirectory, [string]$codeOwnerFileLocati
   }
 }
 
-if($Test) {
-  # These tests have been removed; now instead we should run tests from RetrieveCodeOwnersProgramTests, and in a way as explained in:
+if ($Test) {
+  # Most of tests here have been removed; now instead we should run tests from RetrieveCodeOwnersProgramTests, and in a way as explained in:
   # https://github.com/Azure/azure-sdk-tools/issues/5434
   # https://github.com/Azure/azure-sdk-tools/pull/5103#discussion_r1068680818
-  Write-Host "These tests have been removed. Please see https://github.com/Azure/azure-sdk-tools/issues/5434 for more."
-  #
-  # $testFile = (Resolve-Path $PSScriptRoot/../../../tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/simple_path_CODEOWNERS)
-  # TestGetCodeOwner -targetDirectory "sdk" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @("person1", "person2")
-  # TestGetCodeOwner -targetDirectory "sdk/noPath" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @("person1", "person2")
-  # TestGetCodeOwner -targetDirectory "/sdk/azconfig" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @("person3", "person4")
-  # TestGetCodeOwner -targetDirectory "/sdk/azconfig/package" -codeOwnerFileLocation $testFile -includeNonUserAliases $true  $testFile -expectReturn @("person3", "person4")
-  # TestGetCodeOwner -targetDirectory "/sd" -codeOwnerFileLocation $testFile -includeNonUserAliases $true  -expectReturn @()
-  # TestGetCodeOwner -targetDirectory "/sdk/testUser/" -codeOwnerFileLocation $testFile -expectReturn @("azure-sdk") 
+  Write-Host "Running reduced test suite at `$PSScriptRoot of $PSSCriptRoot. Please see https://github.com/Azure/azure-sdk-tools/issues/5434 for more."
+
+  $azSdkToolsCodeowners = (Resolve-Path "$PSScriptRoot/../../../.github/CODEOWNERS")
+  TestGetCodeowners -targetPath "eng/common/scripts/get-codeowners.ps1" -codeownersFileLocation $azSdkToolsCodeowners -includeNonUserAliases $true -expectReturn @("konrad-jamrozik", "weshaggard", "benbp")
+  
+  $testCodeowners = (Resolve-Path "$PSScriptRoot/../../../tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/glob_path_CODEOWNERS")
+  TestGetCodeowners -targetPath "tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/InputDir/a.txt" -codeownersFileLocation $testCodeowners -includeNonUserAliases $true -expectReturn @("2star")
   exit 0
 }
 else {
-  return Get-CodeOwners -targetDirectory $TargetDirectory -codeOwnerFileLocation $CodeOwnerFileLocation -includeNonUserAliases $IncludeNonUserAliases
+  return Get-Codeowners -targetPath $TargetPath -targetDirectory $TargetDirectory -codeownersFileLocation $CodeOwnerFileLocation -includeNonUserAliases $IncludeNonUserAliases
 }
diff --git a/eng/tools/generator/autorest/model/changelog.go b/eng/tools/generator/autorest/model/changelog.go
index 06f0ad3da9..6b6e0cfb91 100644
--- a/eng/tools/generator/autorest/model/changelog.go
+++ b/eng/tools/generator/autorest/model/changelog.go
@@ -94,9 +94,13 @@ func writeChangelogForPackage(r *report.Package) string {
 	}
 
 	// write additional changes
+	toAny := typeToAny(r.BreakingChanges, true)
 	additives := getNewContents(r.AdditiveChanges)
-	if len(additives) > 0 {
+	if len(additives) > 0 || len(toAny) > 0 {
 		md.WriteHeader("Features Added")
+		for _, item := range toAny {
+			md.WriteListItem(item)
+		}
 		for _, item := range additives {
 			md.WriteListItem(item)
 		}
@@ -261,15 +265,8 @@ func getSignatureChangeItems(b *report.BreakingChanges) []string {
 		}
 	}
 	// write struct changes
-	if len(b.Structs) > 0 {
-		for _, k := range sortChangeItem(b.Structs) {
-			v := b.Structs[k]
-			for f, d := range v.Fields {
-				line := fmt.Sprintf("Type of `%s.%s` has been changed from `%s` to `%s`", k, f, d.From, d.To)
-				items = append(items, line)
-			}
-		}
-	}
+	items = append(items, typeToAny(b, false)...)
+
 	// interfaces are skipped, which are identical to some of the functions
 
 	return items
@@ -316,22 +313,33 @@ func getRemovedContent(removed *delta.Content) []string {
 	// write functions
 	if len(removed.Funcs) > 0 {
 		var lroItem []string
+		var paginationItem []string
 		for _, k := range sortFuncItem(removed.Funcs) {
 			v := removed.Funcs[k]
 			if v.ReplacedBy != nil {
 				var line string
-				if !strings.Contains(k, "Begin") {
-					line = fmt.Sprintf("Operation `%s` has been changed to LRO, use `%s` instead.", k, *v.ReplacedBy)
-				} else {
-					line = fmt.Sprintf("Operation `%s` has been changed to non-LRO, use `%s` instead.", k, *v.ReplacedBy)
+				if strings.Contains(k, "Begin") || strings.Contains(*v.ReplacedBy, "Begin") {
+					if !strings.Contains(k, "Begin") {
+						line = fmt.Sprintf("Operation `%s` has been changed to LRO, use `%s` instead.", k, *v.ReplacedBy)
+					} else {
+						line = fmt.Sprintf("Operation `%s` has been changed to non-LRO, use `%s` instead.", k, *v.ReplacedBy)
+					}
+					lroItem = append(lroItem, line)
+				} else if strings.Contains(k, "Pager") || strings.Contains(*v.ReplacedBy, "Pager") {
+					if !strings.Contains(k, "Pager") {
+						line = fmt.Sprintf("Operation `%s` has supported pagination, use `%s` instead.", k, *v.ReplacedBy)
+					} else {
+						line = fmt.Sprintf("Operation `%s` does not support pagination anymore, use `%s` instead.", k, *v.ReplacedBy)
+					}
+					paginationItem = append(paginationItem, line)
 				}
-				lroItem = append(lroItem, line)
 				continue
 			}
 			line := fmt.Sprintf("Function `%s` has been removed", k)
 			items = append(items, line)
 		}
 		items = append(items, lroItem...)
+		items = append(items, paginationItem...)
 	}
 	// write complete struct removal
 	if len(removed.CompleteStructs) > 0 {
@@ -364,6 +372,10 @@ type sortItem interface {
 }
 
 func sortChangeItem[T sortItem](change map[string]T) []string {
+	if len(change) == 0 {
+		return nil
+	}
+
 	s := make([]string, 0, len(change))
 	for k := range change {
 		s = append(s, k)
@@ -419,3 +431,25 @@ func removePattern(funcName string, returnValue string) string {
 
 	return fmt.Sprintf("%s.%s", before, after)
 }
+
+func typeToAny(b *report.BreakingChanges, flag bool) []string {
+	if b.IsEmpty() {
+		return nil
+	}
+
+	var items []string
+	if len(b.Structs) > 0 {
+		for _, k := range sortChangeItem(b.Structs) {
+			v := b.Structs[k]
+			for _, f := range sortChangeItem(v.Fields) {
+				d := v.Fields[f]
+				if flag == (d.To == "any") {
+					line := fmt.Sprintf("Type of `%s.%s` has been changed from `%s` to `%s`", k, f, d.From, d.To)
+					items = append(items, line)
+				}
+			}
+		}
+	}
+
+	return items
+}
diff --git a/eng/tools/generator/cmd/v2/common/changelogProcessor.go b/eng/tools/generator/cmd/v2/common/changelogProcessor.go
index d81a162879..060d21ded6 100644
--- a/eng/tools/generator/cmd/v2/common/changelogProcessor.go
+++ b/eng/tools/generator/cmd/v2/common/changelogProcessor.go
@@ -352,6 +352,30 @@ func LROFilter(changelog *model.Changelog) {
 	}
 }
 
+// PageableFilter PageableFilter after OperationFilter
+func PageableFilter(changelog *model.Changelog) {
+	if changelog.Modified.HasBreakingChanges() && changelog.Modified.HasAdditiveChanges() && changelog.Modified.BreakingChanges.Removed != nil && changelog.Modified.BreakingChanges.Removed.Funcs != nil {
+		removedContent := changelog.Modified.BreakingChanges.Removed
+		for bFunc, v := range removedContent.Funcs {
+			var pagination string
+			clientFunc := strings.Split(bFunc, ".")
+			if len(clientFunc) == 2 {
+				if strings.Contains(clientFunc[1], "New") && strings.Contains(clientFunc[1], "Pager") {
+					clientFunc[1] = strings.TrimPrefix(strings.TrimSuffix(clientFunc[1], "Pager"), "New")
+					pagination = fmt.Sprintf("%s.%s", clientFunc[0], clientFunc[1])
+				} else {
+					pagination = fmt.Sprintf("%s.New%sPager", clientFunc[0], clientFunc[1])
+				}
+				if _, ok := changelog.Modified.AdditiveChanges.Funcs[pagination]; ok {
+					delete(changelog.Modified.AdditiveChanges.Funcs, pagination)
+					v.ReplacedBy = &pagination
+					removedContent.Funcs[bFunc] = v
+				}
+			}
+		}
+	}
+}
+
 func InterfaceToAnyFilter(changelog *model.Changelog) {
 	if changelog.HasBreakingChanges() {
 		for structName, s := range changelog.Modified.BreakingChanges.Structs {
diff --git a/eng/tools/generator/cmd/v2/common/generation.go b/eng/tools/generator/cmd/v2/common/generation.go
index b5fdbf9faa..d8de768cbd 100644
--- a/eng/tools/generator/cmd/v2/common/generation.go
+++ b/eng/tools/generator/cmd/v2/common/generation.go
@@ -211,7 +211,7 @@ func (ctx *GenerateContext) GenerateForSingleRPNamespace(generateParam *Generate
 	}
 
 	log.Printf("filter changelog...")
-	FilterChangelog(changelog, MarshalUnmarshalFilter, EnumFilter, FuncFilter, LROFilter, InterfaceToAnyFilter)
+	FilterChangelog(changelog, MarshalUnmarshalFilter, EnumFilter, FuncFilter, LROFilter, PageableFilter, InterfaceToAnyFilter)
 
 	if onBoard {
 		log.Printf("Replace {{NewClientName}} placeholder in the README.md ")
diff --git a/sdk/data/azcosmos/CHANGELOG.md b/sdk/data/azcosmos/CHANGELOG.md
index e110a9000a..b95809c018 100644
--- a/sdk/data/azcosmos/CHANGELOG.md
+++ b/sdk/data/azcosmos/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## 0.3.4 (Unreleased)
 
 ### Features Added
+* Added `NullPartitionKey` variable to create and query documents with null partition key in CosmosDB
 
 ### Breaking Changes
 
diff --git a/sdk/data/azcosmos/partition_key.go b/sdk/data/azcosmos/partition_key.go
index da0ac27763..ca61aec87a 100644
--- a/sdk/data/azcosmos/partition_key.go
+++ b/sdk/data/azcosmos/partition_key.go
@@ -14,6 +14,11 @@ type PartitionKey struct {
 	values []interface{}
 }
 
+// NullPartitionKey represents a partition key with a null value.
+var NullPartitionKey PartitionKey = PartitionKey{
+	values: []interface{}{nil},
+}
+
 // NewPartitionKeyString creates a partition key with a string value.
 func NewPartitionKeyString(value string) PartitionKey {
 	components := []interface{}{value}
diff --git a/sdk/messaging/azeventhubs/internal/eh/stress/Dockerfile b/sdk/messaging/azeventhubs/internal/eh/stress/Dockerfile
index 39f0df1368..10cf2a3ef8 100644
--- a/sdk/messaging/azeventhubs/internal/eh/stress/Dockerfile
+++ b/sdk/messaging/azeventhubs/internal/eh/stress/Dockerfile
@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/oss/go/microsoft/golang:1.18 as build
+FROM mcr.microsoft.com/oss/go/microsoft/golang:1.20 as build
 # you'll need to run this build from the root of the azeventhubs module
 ENV GOOS=linux 
 ENV GOARCH=amd64 
diff --git a/sdk/messaging/azeventhubs/internal/eh/stress/stress.go b/sdk/messaging/azeventhubs/internal/eh/stress/stress.go
index eab8fda442..81ab35a939 100644
--- a/sdk/messaging/azeventhubs/internal/eh/stress/stress.go
+++ b/sdk/messaging/azeventhubs/internal/eh/stress/stress.go
@@ -5,10 +5,8 @@ package main
 import (
 	"context"
 	"fmt"
-	"math/rand"
 	"os"
 	"sort"
-	"time"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/eh/stress/tests"
 )
@@ -42,8 +40,6 @@ func main() {
 
 	for _, test := range tests {
 		if test.name == testName {
-			rand.Seed(time.Now().UnixNano())
-
 			if err := test.fn(context.Background()); err != nil {
 				fmt.Printf("ERROR: %s\n", err)
 				os.Exit(1)
diff --git a/sdk/messaging/azeventhubs/partition_client.go b/sdk/messaging/azeventhubs/partition_client.go
index eec90d5615..8640a2a781 100644
--- a/sdk/messaging/azeventhubs/partition_client.go
+++ b/sdk/messaging/azeventhubs/partition_client.go
@@ -21,7 +21,11 @@ import (
 const DefaultConsumerGroup = "$Default"
 
 const defaultPrefetchSize = uint32(300)
-const defaultMaxCreditSize = uint32(2048)
+
+// defaultLinkRxBuffer is the maximum number of transfer frames we can handle
+// on the Receiver. This matches the current default window size that go-amqp
+// uses for sessions.
+const defaultMaxCreditSize = uint32(5000)
 
 // StartPosition indicates the position to start receiving events within a partition.
 // The default position is Latest.
@@ -100,6 +104,14 @@ func (pc *PartitionClient) ReceiveEvents(ctx context.Context, count int, options
 
 	prefetchDisabled := pc.prefetch < 0
 
+	if count <= 0 {
+		return nil, internal.NewErrNonRetriable("count should be greater than 0")
+	}
+
+	if prefetchDisabled && count > int(defaultMaxCreditSize) {
+		return nil, internal.NewErrNonRetriable(fmt.Sprintf("count cannot exceed %d", defaultMaxCreditSize))
+	}
+
 	err := pc.links.Retry(ctx, EventConsumer, "ReceiveEvents", pc.partitionID, pc.retryOptions, func(ctx context.Context, lwid internal.LinkWithID[amqpwrap.AMQPReceiverCloser]) error {
 		events = nil
 
@@ -259,6 +271,11 @@ func newPartitionClient(args partitionClientArgs, options *PartitionClientOption
 		return nil, err
 	}
 
+	if options.Prefetch > int32(defaultMaxCreditSize) {
+		// don't allow them to set the prefetch above the session window size.
+		return nil, internal.NewErrNonRetriable(fmt.Sprintf("options.Prefetch cannot exceed %d", defaultMaxCreditSize))
+	}
+
 	client := &PartitionClient{
 		eventHub:         args.eventHub,
 		partitionID:      args.partitionID,
diff --git a/sdk/storage/azqueue/CHANGELOG.md b/sdk/storage/azqueue/CHANGELOG.md
index e540248798..81127f99b8 100644
--- a/sdk/storage/azqueue/CHANGELOG.md
+++ b/sdk/storage/azqueue/CHANGELOG.md
@@ -1,4 +1,16 @@
-## 0.1.0 (2023-02-15)
+## Release History
+
+### 0.1.1 (Unreleased)
+
+#### Features Added
+
+#### Breaking Changes
+
+#### Bugs Fixed
+
+#### Other Changes
+
+### 0.1.0 (2023-02-15)
 
 ### Features Added
 
diff --git a/sdk/storage/azqueue/internal/exported/version.go b/sdk/storage/azqueue/internal/exported/version.go
index cfbc93f4ea..9ad8cee51f 100644
--- a/sdk/storage/azqueue/internal/exported/version.go
+++ b/sdk/storage/azqueue/internal/exported/version.go
@@ -8,5 +8,5 @@ package exported
 
 const (
 	ModuleName    = "azqueue"
-	ModuleVersion = "v0.1.0"
+	ModuleVersion = "v0.1.1"
 )


Test patch:
diff --git a/eng/tools/generator/cmd/v2/common/changelogProcessor_test.go b/eng/tools/generator/cmd/v2/common/changelogProcessor_test.go
index 676ffb5ae7..8f4bf7428a 100644
--- a/eng/tools/generator/cmd/v2/common/changelogProcessor_test.go
+++ b/eng/tools/generator/cmd/v2/common/changelogProcessor_test.go
@@ -79,6 +79,28 @@ func TestLROFilter(t *testing.T) {
 	assert.Equal(t, excepted, changelog.ToCompactMarkdown())
 }
 
+func TestPageableFilter(t *testing.T) {
+	oldExport, err := exports.Get("./testdata/old/page")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	newExport, err := exports.Get("./testdata/new/page")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	changelog, err := autorest.GetChangelogForPackage(&oldExport, &newExport)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	common.FilterChangelog(changelog, common.FuncFilter, common.PageableFilter)
+
+	excepted := fmt.Sprint("### Breaking Changes\n\n- Operation `*Client.GetLog` has supported pagination, use `*Client.NewGetLogPager` instead.\n- Operation `*Client.NewListPager` does not support pagination anymore, use `*Client.List` instead.\n")
+	assert.Equal(t, excepted, changelog.ToCompactMarkdown())
+}
+
 func TestInterfaceToAnyFilter(t *testing.T) {
 	oldExport, err := exports.Get("./testdata/old/interfacetoany")
 	if err != nil {
@@ -100,3 +122,23 @@ func TestInterfaceToAnyFilter(t *testing.T) {
 	excepted := fmt.Sprint("### Breaking Changes\n\n- Type of `Interface2Any.NewType` has been changed from `interface{}` to `string`\n")
 	assert.Equal(t, excepted, changelog.ToCompactMarkdown())
 }
+
+func TestTypeToAny(t *testing.T) {
+	oldExport, err := exports.Get("./testdata/old/toany")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	newExport, err := exports.Get("./testdata/new/toany")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	changelog, err := autorest.GetChangelogForPackage(&oldExport, &newExport)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	excepted := fmt.Sprint("### Breaking Changes\n\n- Type of `Client.M` has been changed from `map[string]string` to `map[string]any`\n\n### Features Added\n\n- Type of `Client.A` has been changed from `*int` to `any`\n")
+	assert.Equal(t, excepted, changelog.ToCompactMarkdown())
+}
diff --git a/eng/tools/generator/cmd/v2/common/testdata/new/page/page.go b/eng/tools/generator/cmd/v2/common/testdata/new/page/page.go
new file mode 100644
index 0000000000..e23254484a
--- /dev/null
+++ b/eng/tools/generator/cmd/v2/common/testdata/new/page/page.go
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+package page
+
+type Client struct{}
+
+func (client *Client) NewGetLogPager(resourceGroupName string, options *ClientNewGetLogPagerOptions) (ClientNewGetLogPagerResponse, error) {
+
+	return ClientNewGetLogPagerResponse{}, nil
+}
+
+type ClientNewGetLogPagerOptions struct{}
+
+type ClientNewGetLogPagerResponse struct{}
+
+func (client *Client) List(resourceGroupName string, options *ClientListOptions) (ClientListResponse, error) {
+
+	return ClientListResponse{}, nil
+}
+
+type ClientListOptions struct{}
+
+type ClientListResponse struct{}
diff --git a/eng/tools/generator/cmd/v2/common/testdata/new/toany/toany.go b/eng/tools/generator/cmd/v2/common/testdata/new/toany/toany.go
new file mode 100644
index 0000000000..c7cc527e19
--- /dev/null
+++ b/eng/tools/generator/cmd/v2/common/testdata/new/toany/toany.go
@@ -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.
+
+package toany
+
+type Client struct {
+	A any
+	M map[string]any
+}
diff --git a/eng/tools/generator/cmd/v2/common/testdata/old/page/page.go b/eng/tools/generator/cmd/v2/common/testdata/old/page/page.go
new file mode 100644
index 0000000000..a0e99019be
--- /dev/null
+++ b/eng/tools/generator/cmd/v2/common/testdata/old/page/page.go
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+package page
+
+type Client struct{}
+
+func (client *Client) GetLog(resourceGroupName string, options *ClientGetLogOptions) (ClientGetLogResponse, error) {
+
+	return ClientGetLogResponse{}, nil
+}
+
+type ClientGetLogOptions struct{}
+
+type ClientGetLogResponse struct{}
+
+func (client *Client) NewListPager(resourceGroupName string, options *ClientNewListPagerOptions) (ClientNewListPagerResponse, error) {
+
+	return ClientNewListPagerResponse{}, nil
+}
+
+type ClientNewListPagerOptions struct{}
+
+type ClientNewListPagerResponse struct{}
diff --git a/eng/tools/generator/cmd/v2/common/testdata/old/toany/toany.go b/eng/tools/generator/cmd/v2/common/testdata/old/toany/toany.go
new file mode 100644
index 0000000000..ae73cc1d69
--- /dev/null
+++ b/eng/tools/generator/cmd/v2/common/testdata/old/toany/toany.go
@@ -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.
+
+package toany
+
+type Client struct {
+	A *int
+	M map[string]string
+}
diff --git a/sdk/data/azcosmos/emulator_cosmos_item_test.go b/sdk/data/azcosmos/emulator_cosmos_item_test.go
index 3a402efc0c..1086f62d73 100644
--- a/sdk/data/azcosmos/emulator_cosmos_item_test.go
+++ b/sdk/data/azcosmos/emulator_cosmos_item_test.go
@@ -188,6 +188,181 @@ func TestItemCRUD(t *testing.T) {
 	}
 }
 
+func TestItemCRUDforNullPartitionKey(t *testing.T) {
+	emulatorTests := newEmulatorTests(t)
+	client := emulatorTests.getClient(t)
+
+	database := emulatorTests.createDatabase(t, context.TODO(), client, "itemCRUD")
+	defer emulatorTests.deleteDatabase(t, context.TODO(), database)
+	properties := ContainerProperties{
+		ID: "aContainer",
+		PartitionKeyDefinition: PartitionKeyDefinition{
+			Paths: []string{"/partitionKey"},
+		},
+	}
+
+	_, err := database.CreateContainer(context.TODO(), properties, nil)
+	if err != nil {
+		t.Fatalf("Failed to create container: %v", err)
+	}
+
+	item := map[string]interface{}{
+		"partitionKey": nil,
+		"id":           "1",
+		"value":        "2",
+		"count":        3,
+		"description":  "4",
+	}
+
+	container, _ := database.NewContainer("aContainer")
+	pk := NullPartitionKey
+
+	marshalled, err := json.Marshal(item)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	itemResponse, err := container.CreateItem(context.TODO(), pk, marshalled, nil)
+	if err != nil {
+		t.Fatalf("Failed to create item: %v", err)
+	}
+
+	if itemResponse.SessionToken == "" {
+		t.Fatalf("Session token is empty")
+	}
+
+	// No content on write by default
+	if len(itemResponse.Value) != 0 {
+		t.Fatalf("Expected empty response, got %v", itemResponse.Value)
+	}
+
+	itemResponse, err = container.ReadItem(context.TODO(), pk, "1", nil)
+	if err != nil {
+		t.Fatalf("Failed to read item: %v", err)
+	}
+
+	if len(itemResponse.Value) == 0 {
+		t.Fatalf("Expected non-empty response, got %v", itemResponse.Value)
+	}
+
+	var itemResponseBody map[string]interface{}
+	err = json.Unmarshal(itemResponse.Value, &itemResponseBody)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal item response: %v", err)
+	}
+	if itemResponseBody["id"] != "1" {
+		t.Fatalf("Expected id to be 1, got %v", itemResponseBody["id"])
+	}
+	if itemResponseBody["value"] != "2" {
+		t.Fatalf("Expected value to be 2, got %v", itemResponseBody["value"])
+	}
+
+	item["value"] = "3"
+	marshalled, err = json.Marshal(item)
+	if err != nil {
+		t.Fatal(err)
+	}
+	itemResponse, err = container.ReplaceItem(context.TODO(), pk, "1", marshalled, &ItemOptions{EnableContentResponseOnWrite: true})
+	if err != nil {
+		t.Fatalf("Failed to replace item: %v", err)
+	}
+
+	// Explicitly requesting body on write
+	if len(itemResponse.Value) == 0 {
+		t.Fatalf("Expected non-empty response, got %v", itemResponse.Value)
+	}
+
+	err = json.Unmarshal(itemResponse.Value, &itemResponseBody)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal item response: %v", err)
+	}
+	if itemResponseBody["id"] != "1" {
+		t.Fatalf("Expected id to be 1, got %v", itemResponseBody["id"])
+	}
+	if itemResponseBody["value"] != "3" {
+		t.Fatalf("Expected value to be 3, got %v", itemResponseBody["value"])
+	}
+
+	item["value"] = "4"
+	marshalled, err = json.Marshal(item)
+	if err != nil {
+		t.Fatal(err)
+	}
+	itemResponse, err = container.UpsertItem(context.TODO(), pk, marshalled, &ItemOptions{EnableContentResponseOnWrite: true})
+	if err != nil {
+		t.Fatalf("Failed to upsert item: %v", err)
+	}
+
+	// Explicitly requesting body on write
+	if len(itemResponse.Value) == 0 {
+		t.Fatalf("Expected non-empty response, got %v", itemResponse.Value)
+	}
+
+	err = json.Unmarshal(itemResponse.Value, &itemResponseBody)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal item response: %v", err)
+	}
+	if itemResponseBody["id"] != "1" {
+		t.Fatalf("Expected id to be 1, got %v", itemResponseBody["id"])
+	}
+	if itemResponseBody["value"] != "4" {
+		t.Fatalf("Expected value to be 4, got %v", itemResponseBody["value"])
+	}
+
+	patchItem := PatchOperations{}
+	patchItem.AppendReplace("/value", "5")
+	patchItem.AppendSet("/hello", "world")
+	patchItem.AppendAdd("/foo", "bar")
+	patchItem.AppendRemove("/description")
+	patchItem.AppendIncrement("/count", 1)
+
+	itemResponse, err = container.PatchItem(context.TODO(), pk, "1", patchItem, nil)
+	if err != nil {
+		t.Fatalf("Failed to patch item: %v", err)
+	}
+
+	// No content on write by default
+	if len(itemResponse.Value) != 0 {
+		t.Fatalf("Expected empty response, got %v", itemResponse.Value)
+	}
+
+	itemResponse, _ = container.ReadItem(context.TODO(), pk, "1", nil)
+
+	err = json.Unmarshal(itemResponse.Value, &itemResponseBody)
+	if err != nil {
+		t.Fatalf("Failed to unmarshal item response: %v", err)
+	}
+
+	if itemResponseBody["value"] != "5" {
+		t.Fatalf("Expected value to be 5, got %v", itemResponseBody["id"])
+	}
+
+	if itemResponseBody["hello"] != "world" {
+		t.Fatalf("Expected hello to be world, got %v", itemResponseBody["hello"])
+	}
+
+	if itemResponseBody["foo"] != "bar" {
+		t.Fatalf("Expected foo to be bar, got %v", itemResponseBody["foo"])
+	}
+
+	if itemResponseBody["count"].(float64) != float64(4) {
+		t.Fatalf("Expected count to be 4, got %v", itemResponseBody["count"])
+	}
+
+	if itemResponseBody["toremove"] != nil {
+		t.Fatalf("Expected toremove to be nil, got %v", itemResponseBody)
+	}
+
+	itemResponse, err = container.DeleteItem(context.TODO(), pk, "1", nil)
+	if err != nil {
+		t.Fatalf("Failed to replace item: %v", err)
+	}
+
+	if len(itemResponse.Value) != 0 {
+		t.Fatalf("Expected empty response, got %v", itemResponse.Value)
+	}
+}
+
 func TestItemIdEncodingRoutingGW(t *testing.T) {
 	emulatorTests := newEmulatorTests(t)
 	client := emulatorTests.getClient(t)
diff --git a/sdk/data/azcosmos/partition_key_test.go b/sdk/data/azcosmos/partition_key_test.go
index 44096f5444..bd7e651aba 100644
--- a/sdk/data/azcosmos/partition_key_test.go
+++ b/sdk/data/azcosmos/partition_key_test.go
@@ -15,6 +15,7 @@ func TestSerialization(t *testing.T) {
 		"[\"some string\"]": NewPartitionKeyString("some string"),
 		"[true]":            NewPartitionKeyBool(true),
 		"[false]":           NewPartitionKeyBool(false),
+		"[null]":            NullPartitionKey,
 	}
 
 	for expectedSerialization, pk := range validTypes {
@@ -68,4 +69,10 @@ func TestPartitionKeyEquality(t *testing.T) {
 	if !reflect.DeepEqual(pk, pk2) {
 		t.Errorf("Expected %v to equal %v", pk, pk2)
 	}
+
+	pk = NullPartitionKey
+	pk2 = NullPartitionKey
+	if !reflect.DeepEqual(pk, pk2) {
+		t.Errorf("Expected %v to equal %v", pk, pk2)
+	}
 }
diff --git a/sdk/messaging/azeventhubs/partition_client_unit_test.go b/sdk/messaging/azeventhubs/partition_client_unit_test.go
index bf18e3e57f..e03eb2d5eb 100644
--- a/sdk/messaging/azeventhubs/partition_client_unit_test.go
+++ b/sdk/messaging/azeventhubs/partition_client_unit_test.go
@@ -5,11 +5,13 @@ package azeventhubs
 
 import (
 	"context"
+	"fmt"
 	"testing"
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal"
 	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/go-amqp"
+	"github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/internal/test"
 	"github.com/stretchr/testify/require"
 )
 
@@ -41,6 +43,43 @@ func TestUnit_PartitionClient_PrefetchOff(t *testing.T) {
 	require.True(t, ns.Receiver.ManualCreditsSetFromOptions)
 }
 
+func TestUnit_PartitionClient_PrefetchOff_CreditLimits(t *testing.T) {
+	ns := &internal.FakeNSForPartClient{
+		Receiver: &internal.FakeAMQPReceiver{
+			Messages: fakeMessages(int(defaultMaxCreditSize)),
+		},
+	}
+
+	client, err := newPartitionClient(partitionClientArgs{
+		namespace: ns,
+	}, &PartitionClientOptions{
+		Prefetch: -1,
+	})
+	require.NoError(t, err)
+
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+
+	// can't request over the max
+	events, err := client.ReceiveEvents(ctx, int(defaultMaxCreditSize+1), nil)
+	require.EqualError(t, err, "count cannot exceed 5000")
+	require.Empty(t, events)
+
+	// can't request negative/0 credits
+	events, err = client.ReceiveEvents(ctx, 0, nil)
+	require.EqualError(t, err, "count should be greater than 0")
+	require.Empty(t, events)
+
+	events, err = client.ReceiveEvents(ctx, -1, nil)
+	require.EqualError(t, err, "count should be greater than 0")
+	require.Empty(t, events)
+
+	// can request the max
+	events, err = client.ReceiveEvents(ctx, int(defaultMaxCreditSize), nil)
+	require.NoError(t, err)
+	require.NotEmpty(t, events)
+}
+
 func TestUnit_PartitionClient_PrefetchOffOnlyBackfillsCredits(t *testing.T) {
 	testData := []struct {
 		Name    string
@@ -116,3 +155,77 @@ func TestUnit_PartitionClient_PrefetchOn(t *testing.T) {
 		require.Equal(t, uint32(td.initialCredits-3), ns.Receiver.ActiveCredits, "All messages should have been received")
 	}
 }
+
+func TestUnit_PartitionClient_PrefetchLimit(t *testing.T) {
+	newPartitionClient := func(prefetch int32) (*PartitionClient, error) {
+		ns := &internal.FakeNSForPartClient{
+			Receiver: &internal.FakeAMQPReceiver{
+				Messages: fakeMessages(int(defaultMaxCreditSize) + 1),
+			},
+		}
+
+		client, err := newPartitionClient(partitionClientArgs{namespace: ns}, &PartitionClientOptions{
+			Prefetch: prefetch,
+		})
+
+		return client, err
+	}
+
+	t.Run("max allowed credits is defaultMaxCreditSize", func(t *testing.T) {
+		client, err := newPartitionClient(int32(defaultMaxCreditSize))
+		require.NoError(t, err)
+		require.NotNil(t, client)
+
+		test.RequireClose(t, client)
+	})
+
+	t.Run("can't request zero or negative credits", func(t *testing.T) {
+		client, err := newPartitionClient(int32(defaultMaxCreditSize))
+		require.NoError(t, err)
+		require.NotNil(t, client)
+
+		events, err := client.ReceiveEvents(context.Background(), 0, nil)
+		require.EqualError(t, err, "count should be greater than 0")
+		require.Empty(t, events)
+
+		events, err = client.ReceiveEvents(context.Background(), -1, nil)
+		require.EqualError(t, err, "count should be greater than 0")
+		require.Empty(t, events)
+
+		test.RequireClose(t, client)
+	})
+
+	t.Run("can receive more than defaultMaxCreditSize in prefetch mode", func(t *testing.T) {
+		client, err := newPartitionClient(0)
+		require.NoError(t, err)
+		require.NotNil(t, client)
+
+		// if you're using prefetch it's fine to ask for more than the `defaultMaxCreditSize`
+		// since it doesn't actually cause to request more credits than is allowed.
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+
+		events, err := client.ReceiveEvents(ctx, int(defaultMaxCreditSize+1), nil)
+		require.NoError(t, err)
+		require.NotEmpty(t, events)
+
+		test.RequireClose(t, client)
+	})
+
+	t.Run("can't set a option.Prefetch > defaultMaxCreditSize", func(t *testing.T) {
+		// and you can't create a PartitionClient that uses more credits than allowed.
+		client, err := newPartitionClient(int32(defaultMaxCreditSize) + 1)
+		require.EqualError(t, err, fmt.Sprintf("options.Prefetch cannot exceed %d", defaultMaxCreditSize))
+		require.Nil(t, client)
+	})
+}
+
+func fakeMessages(count int) []*amqp.Message {
+	var messages []*amqp.Message
+
+	for i := 0; i < count; i++ {
+		messages = append(messages, &amqp.Message{})
+	}
+
+	return messages
+}