Azure / Azure/data-api-builder

⭐ [Enhancement]: Stored Procedure Parameters

Abierto
#2,365 5 comentarios 1 reacción 1 asignado Asignado a @Aniruddh25 Ver en GitHub
2.0 cri enhancement has-pr
Lenguaje dominante
C#
Estrellas
1.5k
Forks
370
Merge medio
3 d 22 h
PR fusionados (30 d)
9

Descripción

## 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

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.