Azure / Azure/data-api-builder

⭐ [Enhancement]: Stored Procedure Parameters

Open
#2,365 5 comments 1 reaction 1 assignee Assigned to @Aniruddh25 View on GitHub
2.0 cri enhancement has-pr
Dominant language
C#
Stars
1.5k
Forks
370
Avg merge
3d 17h
Merged PRs (30d)
8

Description

## What is it?

* Support proc/param metadata.
* Support optional parameters.
* Support output parameters.
* Support error-provider parameters.

### Output scenario

This demonstrates an output parameter.

```tsql
CREATE PROCEDURE [dbo].[GetBooks]
@count INT OUTPUT
AS
SELECT *, @count = COUNT(*) OVER()
FROM dbo.Books;
```

> Note how the result set from the `SELECT` statement is combined with the output parameter itself.

### Current Configuration

Current configuration supports parameters and their default values - only.

```json
{
"entities": [
{
"": {
...
"source": {
...
"parameters": {
"": "",
"": "",
"": ""
}
...
```

### Future Configuration

New object type provides more flexibility. This is an additive/alternative change.

```json
{
"entities": [
{
"": {
...
"metadata": {
"description": "" (optional default: null)
},
"source": {
...
"parameters": [ // current hierarchy position
{
"name": "",
"alias": "" (optional default: ),
"description": "" (optional default: null),
"data-type": "" (optional default: ),
"required": "true | false" (optional default: true),
"default": "" (optional default: null)
"kind": "input | output | input-output | error-provider", (optional default: input)
}
...
```

## Description

Metadata `Description` is used to enhance both the `OpenAPI` and `GraphQL Schema`.

> Maybe this means views and tables get description. We will have to see.

## CLI impact

Though this may change to align with ongoing work, this is pretty close.

* `dab update --metadata.description "value"`
* `dab update --parameters "" --name "value"`
* `dab update --parameters "" --alias "value"`
* `dab update --parameters "" --description "value"`
* `dab update --parameters "" --data-type "value"`
* `dab update --parameters "" --required "value"`
* `dab update --parameters "" --default "value"`
* `dab update --parameters "" --kind "value"`

How do we delete a parameter from the CLI?

* `dab update --parameters "" --delete`

## Output parameter

This supports both SELECT and OUTPUT params at the same time.

### Example (page count)

```sql
CREATE PROCEDURE [dbo].[GetBooksPaginated]
@PageSize INT,
@PageNumber INT,
@PageCount INT OUTPUT
AS
BEGIN

DECLARE @TotalCount INT;
SELECT @TotalCount = COUNT(*) FROM dbo.Books;
SET @PageCount = CEILING(@TotalCount * 1.0 / @PageSize);

SELECT * FROM dbo.Books ORDER BY Id
OFFSET (@PageNumber - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;

END;
```

### Output payload (GraphQL)

TBD

### Output payload (REST)

```json
{
"value": [
{
"Id": 1,
"Name": "William Shatner",
"BirthYear": 1931
}
],
"output-params": [
{
"Name": "PageCount",
"Value": 123
}
],
"errors": [] // see below
}
```

### Database parity

| Database | Supports Output Params |
|-----------------|-------------------------------|
| SQL Server | Yes |
| MySQL | Yes |
| PostgreSQL | Yes |
| Cosmos DB (SQL) | No (Return values through JavaScript) |

## Error-Provider

Today, stored procedures cannot raise an error in a controlled way.

### Today's error format (REST)

```json
{
"error": {
"code": "DatabaseOperationFailed",
"message": "Invalid column name 'Id'.\r\nInvalid column name 'Id'.",
"status": 500
}
}
```

### Today's error format (GQL)

```json
{
"errors": [
{
"message": "Invalid column name 'Id'.\r\nInvalid column name 'Id'.",
"extensions": {
"code": "DatabaseOperationFailed"
}
}
]
}
```

### Future error format (REST)

The original `error` remains - it will return the `First()` from the new `errors` array.

```json
{
"value": [],
"output-params": [],
"errors": [
{
"code": "DatabaseOperationFailed", // optional
"message": "Invalid column name 'Id'.\r\nInvalid column name 'Id'.", // required
"status": 500 // optional
}
]
}
```

### Future error format (GQL)

No change.

## Raising errors versus returning errors

This section shows how returning errors is better than raising them. Reasons:

1. Allows multiple errors.
2. Keeps execution flow.
3. Consistent format for APIs.
4. Customizable messages.

### Raising errors

```tsql
CREATE PROCEDURE [dbo].[GetBooks]
@count INT OUTPUT
AS
BEGIN
SELECT *, @count = COUNT(*) OVER()
FROM dbo.Books;

IF @count < 10
RAISERROR('The number of books is less than 10. Operation aborted.', 16, 1);
END;
```

### (versus) Returning errors

Effectively, an `error-provider` is an output parameter with a JSON payload of errors.

```tsql
CREATE PROCEDURE [dbo].[GetBooks]
@count INT OUTPUT,
@ErrorMessage NVARCHAR(MAX) OUTPUT
AS
BEGIN

IF @count < 10
BEGIN

-- error
DECLARE @ErrorTable TABLE (code NVARCHAR(50), message NVARCHAR(255), status INT);

INSERT INTO @ErrorTable (code, message, status)
VALUES ('DatabaseOperationFailed', 'The number of books is less than 10. Operation aborted.', 500);

SELECT @ErrorMessage = (SELECT * FROM @ErrorTable FOR JSON PATH, WITHOUT_ARRAY_WRAPPER);

RETURN; -- Abort

END

SELECT *, @count = COUNT(*) OVER()
FROM dbo.Books;

END;
```

#### Error result

```json
{
"errors": [
{
"code": "DatabaseOperationFailed",
"message": "The number of books is less than 10. Operation aborted.",
"status": 500
}
]
}
```

### Questions

1. Can errors be returned WITH data? Yes.
2. Should `status` in an error impact the returned HTTP status? Return 200 (REST & GQL)
3. Let's make sure we log the non-generic error and return the generic error.
4. Custom errors are not censored.

## Related Issues to Close

* #1841
* #1826
* #1843
* #2346
* #2222
* #1748
* #1844

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.