Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: AIP-165 – Criteria-based delete #5

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
131 changes: 131 additions & 0 deletions aip/general/0165/aip.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Criteria-based delete

Occasionally, an API may need to provide a mechanism to delete a large number
of resources based on some set of filter parameters, rather than requiring the
individual resource name of the resources to be deleted.

This is a rare case, reserved for situations where users need to delete
thousands or more resources at once, in which case the normal Batch Delete
pattern (AIP-235) becomes unwieldy and inconvenient.

## Guidance

**Important:** Most APIs **should** use only Delete (AIP-135) or Batch Delete
(AIP-235) for deleting resources, and **should not** implement deleting based
on criteria. This is because deleting is generally irreversible and this type
of operation makes it easy for a user to accidentally lose significant amounts
of data.

An API **may** implement a Purge operation to permit deleting a large number of
resources based on a filter string; however, this **should** only be done if
the Batch Delete (AIP-235) pattern is insufficient to accomplish the desired
goal:

### Requests

Purge operations **must** be made by sending a `POST` request to the collection
with a `:purge` suffix:

```http
POST /v1/publishers/{publisher}/books:purge HTTP/2
Host: library.googleapis.com
Accept: application/json

{
"filter": "author=vhugo",
"force": false
}
```

- The HTTP verb **must** be `POST`.
- The POST body **should** include a `filter` field, for specifying arbitrary
purge criteria. The `filter` field **should** be required, and **must**
follow the same semantics as in List operations (see AIP-160).
- The POST body **must** include a `force` field.
- If the `force` key is not provided or is set to `false`, the service
**must** return a count of the resources that would be deleted as well as
a sample of those resources, without actually performing the deletion.
- If the `force` key is set to `true`, the purge is performed.
- If the service wishes to support deletion across multiple parents, it
**should** accept the `-` character, as described in AIP-159.

### Responses

Purge method responses **should** conform to the following interface:

```typescript
interface PurgeBooksResponse {
// The number of books that this request deleted (or, if `force` is false,
// the number of books that will be deleted).
purge_count: number;

// A sample of the resource names of books that will be deleted.
// Only populated if `force` is set to false.
purge_sample: string[];
}
```

- The `purge_count` field **should** be included, and provide the number of
resources that were deleted (or would be deleted). This count **may** be an
estimate similar to `total_size` in AIP-158 (but the service **should**
document this if so).
- The `purge_sample` field **should** be included: If `force` is `false`, it
**should** provide a sample of resource names that will be deleted. If
`force` is true, this field **should not** be populated.
- The sample **should** be a sufficient size to catch clearly obvious
mistakes: A good rule of thumb is 100. The API **should** document the
size, and **should** document that it is a maximum (it is possible to send
fewer).
- The sample **may** be random or **may** be deterministic (such as the first
matched resource names). The API **should** document which approach is
used.

**Note:** Even if `purge_count` and `purge_sample` are not included, the
`force` field **must** still be included in the request.

### Errors

If the user does not have permission to purge resources on the collection,
regardless of whether or not the collection exists, the service **must** reply
with an HTTP 403 error. A single permission **should** be checked for the whole
collection, and permission **must** be checked prior to checking if the
collection exists.

If a user with proper permissions requests to purge from a collection that does
not exist, the service **must** reply with an HTTP 404 error.

If a user with proper permissions provides an invalid filter, the service
**must** reply with an HTTP 400 error. If the user does not provide a filter at
all, the service **should** reply with an HTTP 400 error.

If a user with proper permissions provides a filter that matches zero
resources, the request **must** succeed and indicate this.

## Interface Definitions

{% tab proto -%}

Purge operations are specified using the following pattern:

{% sample 'purge.proto', 'rpc PurgeBooks' %}

- The HTTP verb **must** be `POST`, and the `body` **must** be `"*"`.
- The `parent` field **should** be included in the URI.
- The response type **must** be a `google.longrunning.Operation` (see AIP-151).

Purge operation requests are specified using the following pattern:

{% sample 'purge.proto', 'message PurgeBooksRequest' %}

- The `parent` **must** be marked as required.
- The `parent` field corresponds to the `parent` value in the URI, and
**should** include a reference annotation to the parent resource.
- The `filter` field **should** be marked as required.
- The `force` field **must not** be marked as required (which would imply that
the boolean needs to be `true` for the request to succeed).

Purge operation responses are specified using the following pattern:

{% sample 'purge.proto', 'message PurgeBooksResponse' %}

{% endtabs %}
7 changes: 7 additions & 0 deletions aip/general/0165/aip.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
id: 165
state: approved
created: 2019-12-18
placement:
category: design-patterns
order: 100
62 changes: 62 additions & 0 deletions aip/general/0165/purge.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

import "google/api/http.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/longrunning/operations.proto";

service Library {
// Remove a large number of books, based on the provided criteria.
rpc PurgeBooks(PurgeBooksRequest) returns (google.longrunning.Operation) {
option (google.api.http) = {
post: "/v1/{parent=publishers/*}/books:purge"
body: "*"
};
option (google.longrunning.operation_info) = {
response_type: "PurgeBooksResponse"
metadata_type: "PurgeBooksMetadata"
};
}
}

message PurgeBooksRequest {
// The publisher to purge books from.
// To purge books across publishers, send "publishers/-".
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
child_type: "library-example.googleapis.com/Book"
}];

// A filter matching the books to be purged.
string filter = 2 [(google.api.field_behavior) = REQUIRED];

// Actually perform the purge.
// If `force` is set to false, the operation will return a sample of
// resource names that would be deleted.
bool force = 3;
}

message PurgeBooksResponse {
// The number of books that this request deleted (or, if `force` is false,
// the number of books that will be deleted).
int32 purge_count = 1;

// A sample of the resource names of books that will be deleted.
// Only populated if `force` is set to false.
repeated string purge_sample = 2;
}