From babf1b06acbf2cbe550e3d6c6fd6b3fb1cf7be31 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 28 Jan 2026 12:30:33 -0800 Subject: [PATCH] s3tables: implement token-based pagination for namespace listing --- weed/s3api/s3tables/handler_namespace.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/weed/s3api/s3tables/handler_namespace.go b/weed/s3api/s3tables/handler_namespace.go index d45641195..87d4ee3ba 100644 --- a/weed/s3api/s3tables/handler_namespace.go +++ b/weed/s3api/s3tables/handler_namespace.go @@ -220,14 +220,14 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re bucketPath := getTableBucketPath(bucketName) var namespaces []NamespaceSummary - var lastFileName string + lastFileName := req.ContinuationToken err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { for len(namespaces) < maxNamespaces { resp, err := client.ListEntries(r.Context(), &filer_pb.ListEntriesRequest{ Directory: bucketPath, Limit: uint32(maxNamespaces * 2), StartFromFileName: lastFileName, - InclusiveStartFrom: lastFileName == "", + InclusiveStartFrom: lastFileName == "" || lastFileName == req.ContinuationToken, }) if err != nil { return err @@ -245,6 +245,12 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re if entry.Entry == nil { continue } + + // Skip the start item if it was included in the previous page + if len(namespaces) == 0 && req.ContinuationToken != "" && entry.Entry.Name == req.ContinuationToken { + continue + } + hasMore = true lastFileName = entry.Entry.Name @@ -300,8 +306,14 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re } } + paginationToken := "" + if len(namespaces) >= maxNamespaces { + paginationToken = lastFileName + } + resp := &ListNamespacesResponse{ - Namespaces: namespaces, + Namespaces: namespaces, + ContinuationToken: paginationToken, } h.writeJSON(w, http.StatusOK, resp)