-
Notifications
You must be signed in to change notification settings - Fork 887
feat: Handle pagination cases where after_id does not exist #1947
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
Changes from 1 commit
132784e
0e4a3c0
303feb1
cffcd71
be23937
1c6f546
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -385,6 +385,23 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque | |
return | ||
} | ||
|
||
if paginationParams.AfterID != uuid.Nil { | ||
// See if the record exists first. If the record does not exist, the pagination | ||
// query will not work. | ||
_, err := api.Database.GetTemplateVersionByID(r.Context(), paginationParams.AfterID) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this almost entirely solves the problem, but not 100%. It handles the case where the (Or alternatively, if the DB is using something like RDS with read-replicas, the row could appear to be deleted if the two queries hit different replicas, which would break the assumption of sequential consistency. Not sure if that's something we plan to support.) I guess even if that race happened, the consequence would just be falling back to the current behavior of returning an empty resultset, so this is still a big improvement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I was trying to do it in postgres to prevent that tiny window @dwahler, but I don't think you can raise an exception in a query. It has to be a postgres procedure, which is a headache. I think this is good enough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am dumb, @dwahler I'll put both db calls in a transaction. |
||
if err != nil && xerrors.Is(err, sql.ErrNoRows) { | ||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ | ||
Message: fmt.Sprintf("record at \"after_id\" (%q) does not exists", paginationParams.AfterID.String()), | ||
}) | ||
return | ||
} else if err != nil { | ||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ | ||
Message: fmt.Sprintf("get template version at after_id: %s", err), | ||
}) | ||
return | ||
} | ||
} | ||
|
||
apiVersion := []codersdk.TemplateVersion{} | ||
versions, err := api.Database.GetTemplateVersionsByTemplateID(r.Context(), database.GetTemplateVersionsByTemplateIDParams{ | ||
TemplateID: template.ID, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -735,6 +735,50 @@ func TestWorkspacesByUser(t *testing.T) { | |
}) | ||
} | ||
|
||
// TestSuspendedPagination is when the after_id is a suspended record. | ||
// The database query should still return the correct page, as the after_id | ||
// is in a subquery that finds the record regardless of its status. | ||
// This is mainly to confirm the db fake has the same behavior. | ||
func TestSuspendedPagination(t *testing.T) { | ||
t.Parallel() | ||
ctx := context.Background() | ||
client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1}) | ||
coderdtest.CreateFirstUser(t, client) | ||
me, err := client.User(context.Background(), codersdk.Me) | ||
require.NoError(t, err) | ||
orgID := me.OrganizationIDs[0] | ||
|
||
total := 10 | ||
users := make([]codersdk.User, 0, total) | ||
// Create users | ||
for i := 0; i < total; i++ { | ||
email := fmt.Sprintf("%[email protected]", i) | ||
username := fmt.Sprintf("user%d", i) | ||
user, err := client.CreateUser(context.Background(), codersdk.CreateUserRequest{ | ||
Email: email, | ||
Username: username, | ||
Password: "password", | ||
OrganizationID: orgID, | ||
}) | ||
require.NoError(t, err) | ||
users = append(users, user) | ||
} | ||
sortUsers(users) | ||
deletedUser := users[2] | ||
expected := users[3:8] | ||
_, err = client.UpdateUserStatus(ctx, deletedUser.ID.String(), codersdk.UserStatusSuspended) | ||
require.NoError(t, err, "suspend user") | ||
|
||
page, err := client.Users(ctx, codersdk.UsersRequest{ | ||
Pagination: codersdk.Pagination{ | ||
Limit: len(expected), | ||
AfterID: deletedUser.ID, | ||
}, | ||
}) | ||
require.NoError(t, err) | ||
require.Equal(t, expected, page, "expected page") | ||
} | ||
|
||
// TestPaginatedUsers creates a list of users, then tries to paginate through | ||
// them using different page sizes. | ||
func TestPaginatedUsers(t *testing.T) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
postgres defaults to an "active" filter.