Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Add pagination for list projects endpoint#1266

Draft
dttung2905 wants to merge 9 commits intolakekeeper:mainlakekeeper/lakekeeper:mainfrom
dttung2905:add-pagination-list-projectsdttung2905/lakekeeper:add-pagination-list-projectsCopy head branch name to clipboard
Draft

Add pagination for list projects endpoint#1266
dttung2905 wants to merge 9 commits intolakekeeper:mainlakekeeper/lakekeeper:mainfrom
dttung2905:add-pagination-list-projectsdttung2905/lakekeeper:add-pagination-list-projectsCopy head branch name to clipboard

Conversation

@dttung2905
Copy link
Copy Markdown
Contributor

@dttung2905 dttung2905 commented Jul 30, 2025

Hi team,

My goal is to add pagination to list projects endpoint

I have also tested out with a minimal example

Partially fixes #812

# Rebuild the image from local with the latest changes and spin up minimal infra
❯ cd examples/minimal
❯ docker compose -f docker-compose.yaml -f docker-compose-build.yaml up --build

# There should be 1 project
❯ curl -X GET http://localhost:8181/management/v1/project-list | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   124  100   124    0     0  38130      0 --:--:-- --:--:-- --:--:-- 41333
{
  "projects": [
    {
      "project-id": "00000000-0000-0000-0000-000000000000",
      "project-name": "Default Project"
    }
  ],
  "next-page-token": null
}

# Create 4 different projects
❯ curl -X POST http://localhost:8181/management/v1/project -H "Content-Type: application/json" -d '{"project-name": "test1"}'
curl -X POST http://localhost:8181/management/v1/project -H "Content-Type: application/json" -d '{"project-name": "test2"}'
curl -X POST http://localhost:8181/management/v1/project -H "Content-Type: application/json" -d '{"project-name": "test3"}'
curl -X POST http://localhost:8181/management/v1/project -H "Content-Type: application/json" -d '{"project-name": "test4"}'
{"project-id":"01985d81-1825-72e1-9f1f-014c1fbebaca"}{"project-id":"01985d81-182f-7400-962a-e87f68bca0a9"}{"project-id":"01985d81-1838-7772-bb81-73703a19a116"}{"project-id":"01985d81-1840-7e50-9758-17af4a98546a"}% 

# there should be 5 projects                                                                          
❯ curl -X GET http://localhost:8181/management/v1/project-list | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   432  100   432    0     0   162k      0 --:--:-- --:--:-- --:--:--  210k
{
  "projects": [
    {
      "project-id": "00000000-0000-0000-0000-000000000000",
      "project-name": "Default Project"
    },
    {
      "project-id": "01985d81-1825-72e1-9f1f-014c1fbebaca",
      "project-name": "test1"
    },
    {
      "project-id": "01985d81-182f-7400-962a-e87f68bca0a9",
      "project-name": "test2"
    },
    {
      "project-id": "01985d81-1838-7772-bb81-73703a19a116",
      "project-name": "test3"
    },
    {
      "project-id": "01985d81-1840-7e50-9758-17af4a98546a",
      "project-name": "test4"
    }
  ],
  "next-page-token": null
}

# First two with next token
❯ curl -X GET http://localhost:8181/management/v1/project-list\?page-size\=2 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   273  100   273    0     0   118k      0 --:--:-- --:--:-- --:--:--  133k
{
  "projects": [
    {
      "project-id": "00000000-0000-0000-0000-000000000000",
      "project-name": "Default Project"
    },
    {
      "project-id": "01985d81-1825-72e1-9f1f-014c1fbebaca",
      "project-name": "test1"
    }
  ],
  "next-page-token": "MSYxNzUzOTE1Mzk4MTgxMzkzJjAxOTg1ZDgxLTE4MjUtNzJlMS05ZjFmLTAxNGMxZmJlYmFjYQ"
}

# next two projects
❯ curl -X GET http://localhost:8181/management/v1/project-list\?page-size\=2\&page-token\=MSYxNzUzOTE1Mzk4MTgxMzkzJjAxOTg1ZDgxLTE4MjUtNzJlMS05ZjFmLTAxNGMxZmJlYmFjYQ | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   263  100   263    0     0   119k      0 --:--:-- --:--:-- --:--:--  128k
{
  "projects": [
    {
      "project-id": "01985d81-182f-7400-962a-e87f68bca0a9",
      "project-name": "test2"
    },
    {
      "project-id": "01985d81-1838-7772-bb81-73703a19a116",
      "project-name": "test3"
    }
  ],
  "next-page-token": "MSYxNzUzOTE1Mzk4MjAwMjk4JjAxOTg1ZDgxLTE4MzgtNzc3Mi1iYjgxLTczNzAzYTE5YTExNg"
}

# last batch where next page token is null
❯ curl -X GET http://localhost:8181/management/v1/project-list\?page-size\=2\&page-token\=MSYxNzUzOTE1Mzk4MjAwMjk4JjAxOTg1ZDgxLTE4MzgtNzc3Mi1iYjgxLTczNzAzYTE5YTExNg | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   114  100   114    0     0  53926      0 --:--:-- --:--:-- --:--:-- 57000
{
  "projects": [
    {
      "project-id": "01985d81-1840-7e50-9758-17af4a98546a",
      "project-name": "test4"
    }
  ],
  "next-page-token": null
}


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Server-side pagination for listing projects: supports pageToken and pageSize, returns nextPageToken, and provides stable ordering by createdAt then projectId.
* **API Changes**
  * Project field renamed to project_name for API consistency; list_projects now returns a paginated response structure.
* **Documentation**
  * OpenAPI updated to include optional next-page-token and document pagination behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

pub struct ListProjectsRequest {
/// Next page token for pagination
#[serde(default)]
pub page_token: Option<String>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use page_token: PageToken for consistency with other paginated list endpoints?

pub enum PageToken {
/// The value is present and not ""
Present(String),
/// The value is not present
NotSpecified,
/// Specified but empty
Empty,
}

This might be easier when it's a Query rather than Request, see for instance ListTablesQuery.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing it out. Let me fix it

transaction: <Self::Transaction as Transaction<Self::State>>::Transaction<'_>,
) -> Result<Vec<GetProjectResponse>> {
list_projects(project_ids, &mut **transaction).await
) -> Result<crate::api::management::v1::project::ListProjectsResponse> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) -> Result<crate::api::management::v1::project::ListProjectsResponse> {
) -> Result<ListProjectsResponse> {

Please import at the top instead of using such long fully qualified syntax.

pagination: PaginationQuery,
connection: E,
) -> Result<Vec<GetProjectResponse>> {
) -> Result<crate::api::management::v1::project::ListProjectsResponse> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) -> Result<crate::api::management::v1::project::ListProjectsResponse> {
) -> Result<ListProjectsResponse> {

.map(|project| GetProjectResponse {
project_id: ProjectId::from_db_unchecked(project.project_id),
name: project.project_name,
let has_more = projects.len() > usize::try_from(page_size).unwrap_or(usize::MAX);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather do something like the following as .len() > usize::MAX cannot be true.

let page_as_usize: usize = page_size
.try_into()
.expect("should be running on at least 32 bit architecture");

Comment thread crates/lakekeeper/src/implementations/postgres/warehouse.rs
}

#[sqlx::test]
async fn test_list_projects_pagination(pool: sqlx::PgPool) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to also test pagination when Some(HashSet) is passed to list_projects.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. That would be next in my todo list

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 11, 2025

Walkthrough

Adds server-side pagination for listing projects across API, service, and Postgres layers: new ListProjectsQuery, PaginationQuery plumbing, ListProjectsResponse.next_page_token, updated trait and handler signatures, cursor-based SQL (created_at, project_id, LIMIT N+1), updated SQLx descriptor, and OpenAPI schema change.

Changes

Cohort / File(s) Summary
API handler
crates/lakekeeper/src/api/management/mod.rs
list_projects handler now extracts Query<ListProjectsQuery> and forwards it to ApiServer::list_projects(request, api_context, metadata).
API v1 models & service API
crates/lakekeeper/src/api/management/v1/project.rs
Adds ListProjectsQuery (page_token, page_size) and Into<PaginationQuery>; adds next_page_token to ListProjectsResponse; updates Service::list_projects signature to accept ListProjectsQuery and return ListProjectsResponse.
Service trait
crates/lakekeeper/src/service/catalog.rs
Catalog::list_projects signature changed to accept PaginationQuery and return ListProjectsResponse.
Postgres catalog impl
crates/lakekeeper/src/implementations/postgres/catalog.rs
Catalog impl updated: list_projects now takes PaginationQuery, returns ListProjectsResponse, and forwards pagination to warehouse layer.
Postgres warehouse & tests
crates/lakekeeper/src/implementations/postgres/warehouse.rs, tests
Implements cursor-based pagination using (created_at, project_id), selects created_at, uses LIMIT page_size+1 to detect more results, maps rows to API responses, emits next_page_token; tests updated/added for pagination behavior.
SQLx descriptors
.sqlx/query-04c6fd4a...7780.json (deleted), .sqlx/query-ac39...fe16.json (added)
Removed old unpaginated SQLx descriptor; added new descriptor including created_at, ordering by (created_at, project_id), and pagination parameters.
OpenAPI
docs/docs/api/management-open-api.yaml
Adds optional next-page-token property to ListProjectsResponse schema (string or null).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTP as HTTP Handler (list_projects)
  participant Api as ApiServer
  participant Cat as Catalog
  participant WH as Postgres Warehouse
  participant DB as Postgres

  Client->>HTTP: GET /projects?pageToken=&pageSize=
  HTTP->>Api: list_projects(ListProjectsQuery, context, metadata)
  Api->>Cat: list_projects(project_ids?, PaginationQuery, tx)
  Cat->>WH: list_projects(project_ids?, PaginationQuery, conn)
  WH->>DB: SELECT ... WHERE cursor ... ORDER BY created_at, project_id LIMIT N+1
  DB-->>WH: rows
  WH-->>Cat: ListProjectsResponse { projects, next_page_token? }
  Cat-->>Api: ListProjectsResponse
  Api-->>HTTP: ListProjectsResponse
  HTTP-->>Client: 200 OK { projects, next_page_token? }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Add pagination to list-projects (#812)
Add pagination to list-warehouses (#812) Warehouse listing endpoints and service/implementation for warehouses were not modified.

Poem

I hop through rows with token bright,
nibble pages in the night,
created_at first, id close behind,
I tuck a crumb — your next cursor — kind.
🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
docs/docs/api/management-open-api.yaml (1)

1022-1041: Document pagination request parameters on list-projects

The endpoint advertises a paginated response but does not declare pageToken/pageSize query parameters. Please add them for parity with list-users/list-roles.

Apply this diff under /management/v1/project-list -> get:

   /management/v1/project-list:
     get:
       tags:
         - project
       summary: List Projects
       description: Lists all projects that the requesting user has access to.
       operationId: list_projects
+      parameters:
+        - name: pageToken
+          in: query
+          description: Next page token
+          required: false
+          schema:
+            type:
+              - string
+              - 'null'
+        - name: pageSize
+          in: query
+          description: |
+            Signals an upper bound of the number of results that a client will receive.
+            Default: 100
+          required: false
+          schema:
+            type: integer
+            format: int64
       responses:
         '200':
           description: List of projects
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ListProjectsResponse'
♻️ Duplicate comments (2)
crates/lakekeeper/src/api/management/v1/project.rs (1)

50-61: Consistent pagination query type (PageToken) — good

Using PageToken with serde/utoipa annotations aligns with other paginated endpoints. Defaulting page_size via management::v1::default_page_size is consistent.

crates/lakekeeper/src/implementations/postgres/catalog.rs (1)

442-444: Import ListProjectsResponse to shorten the return type

Same as earlier feedback: prefer importing the type over fully qualifying.

-    ) -> Result<crate::api::management::v1::project::ListProjectsResponse> {
+    ) -> Result<ListProjectsResponse> {
         list_projects(project_ids, pagination, &mut **transaction).await
     }

Add this import near the top:

use crate::api::management::v1::project::ListProjectsResponse;
🧹 Nitpick comments (5)
.sqlx/query-ac395ee12ac90d563f6ed2b5afe52c2b2e8f6f0bce9792aae0a253a729d3fe16.json (1)

3-3: Keyset pagination query looks correct

The WHERE clause and ORDER BY match the cursor fields; semantics are good for stable keyset pagination and first-page behavior with null token. Two small recommendations:

  • Explicitly use ORDER BY created_at ASC, project_id ASC for readability.
  • Ensure an index exists to support this cursor: e.g. CREATE INDEX ON project (created_at ASC, project_id ASC).
crates/lakekeeper/src/service/catalog.rs (1)

598-602: Shorten the return type by importing ListProjectsResponse

The long fully qualified path is harder to scan. Import the type and use it directly.

-    ) -> Result<crate::api::management::v1::project::ListProjectsResponse>;
+    ) -> Result<ListProjectsResponse>;

Add this import near the other use lines:

use crate::api::management::v1::project::ListProjectsResponse;
crates/lakekeeper/src/implementations/postgres/warehouse.rs (3)

455-457: Consider more robust architecture handling.

While the expect message is descriptive, consider using a more graceful approach:

-    let page_as_usize: usize = page_size
-        .try_into()
-        .expect("should be running on at least 32 bit architecture");
+    let page_as_usize: usize = page_size
+        .try_into()
+        .map_err(|_| ErrorModel::internal(
+            "Page size exceeds platform limits",
+            "PageSizeConversionError",
+            None,
+        ))?;

416-416: Fix formatting issues.

The static analysis tool detected formatting inconsistencies.

Run cargo fmt to fix the formatting issues at lines 416 and 475.

Also applies to: 475-475


1283-1353: Comprehensive pagination test coverage!

The test thoroughly validates:

  • Multi-page pagination with page_size=2
  • Correct ordering of results across pages (addressing previous review feedback)
  • Next page token generation and consumption
  • Completeness of paginated results

Note: Fix the formatting issue at line 1335 by running cargo fmt.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfaad28 and a247b35.

📒 Files selected for processing (8)
  • .sqlx/query-04c6fd4a25005469a8342fe751db0302f8ff81b7046efaa73c0d2433e1867780.json (0 hunks)
  • .sqlx/query-ac395ee12ac90d563f6ed2b5afe52c2b2e8f6f0bce9792aae0a253a729d3fe16.json (1 hunks)
  • crates/lakekeeper/src/api/management/mod.rs (1 hunks)
  • crates/lakekeeper/src/api/management/v1/project.rs (4 hunks)
  • crates/lakekeeper/src/implementations/postgres/catalog.rs (2 hunks)
  • crates/lakekeeper/src/implementations/postgres/warehouse.rs (6 hunks)
  • crates/lakekeeper/src/service/catalog.rs (1 hunks)
  • docs/docs/api/management-open-api.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • .sqlx/query-04c6fd4a25005469a8342fe751db0302f8ff81b7046efaa73c0d2433e1867780.json
🧰 Additional context used
🧬 Code Graph Analysis (4)
crates/lakekeeper/src/api/management/mod.rs (3)
crates/lakekeeper/src/api/management/v1/project.rs (1)
  • list_projects (232-256)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (438-444)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)
  • list_projects (414-487)
crates/lakekeeper/src/implementations/postgres/catalog.rs (4)
crates/lakekeeper/src/api/management/v1/project.rs (2)
  • get_project (170-202)
  • list_projects (232-256)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (2)
  • get_project (238-270)
  • list_projects (414-487)
crates/lakekeeper/src/service/catalog.rs (3)
  • get_project (589-592)
  • transaction (70-70)
  • list_projects (597-601)
crates/lakekeeper/src/api/management/mod.rs (1)
  • list_projects (565-571)
crates/lakekeeper/src/api/management/v1/project.rs (4)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (438-444)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)
  • list_projects (414-487)
crates/lakekeeper/src/service/catalog.rs (1)
  • list_projects (597-601)
crates/lakekeeper/src/api/management/mod.rs (1)
  • list_projects (565-571)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (4)
crates/lakekeeper/src/service/mod.rs (1)
  • from_db_unchecked (203-205)
crates/lakekeeper/src/api/management/v1/project.rs (1)
  • list_projects (232-256)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (438-444)
crates/lakekeeper/src/service/catalog.rs (2)
  • list_projects (597-601)
  • begin_read (64-64)
🪛 GitHub Check: check-format
crates/lakekeeper/src/implementations/postgres/catalog.rs

[warning] 435-435:
Diff in /home/runner/work/lakekeeper/lakekeeper/crates/lakekeeper/src/implementations/postgres/catalog.rs

crates/lakekeeper/src/implementations/postgres/warehouse.rs

[warning] 475-475:
Diff in /home/runner/work/lakekeeper/lakekeeper/crates/lakekeeper/src/implementations/postgres/warehouse.rs


[warning] 416-416:
Diff in /home/runner/work/lakekeeper/lakekeeper/crates/lakekeeper/src/implementations/postgres/warehouse.rs


[warning] 1335-1335:
Diff in /home/runner/work/lakekeeper/lakekeeper/crates/lakekeeper/src/implementations/postgres/warehouse.rs

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: docker / docker
  • GitHub Check: test
  • GitHub Check: sqlx-check
  • GitHub Check: docker / docker
  • GitHub Check: clippy
  • GitHub Check: check-generated-openapi-matches
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
🔇 Additional comments (9)
crates/lakekeeper/src/api/management/v1/project.rs (3)

62-70: Helper to convert to PaginationQuery is clean

pagination_query() correctly preserves token and page_size. This keeps service-layer code tidy.


76-79: next_page_token on ListProjectsResponse

Adding next_page_token (kebab-case) makes the response shape match documented behavior.


232-256: Service wiring passes AuthZ filter and pagination through

The handler propagates AuthZ-scoped project_ids and a PaginationQuery to the catalog, returning the catalog’s paginated response as-is. Looks good.

docs/docs/api/management-open-api.yaml (1)

3260-3264: Schema addition for next-page-token is correct

ListProjectsResponse now exposes next-page-token; matches the Rust response (kebab-case).

crates/lakekeeper/src/implementations/postgres/catalog.rs (1)

430-435: Correct mapping to service-layer GetProjectResponse

Mapping project_name -> name resolves the field rename between API-layer and service-layer types.

crates/lakekeeper/src/api/management/mod.rs (1)

568-570: LGTM! Pagination parameters correctly integrated.

The changes properly wire up the pagination query parameters through the axum Query extractor and pass them to the service layer, following the established pattern for paginated endpoints.

crates/lakekeeper/src/implementations/postgres/warehouse.rs (3)

414-418: Pagination implementation looks good!

The function signature correctly accepts pagination parameters and returns the paginated response type.


420-450: Well-implemented cursor-based pagination logic.

The implementation correctly:

  • Enforces page size limits via CONFIG
  • Parses pagination tokens with proper error handling
  • Uses a stable cursor with (created_at, project_id) for consistent ordering
  • Fetches an extra record to determine if more pages exist

889-952: Test updates correctly adapted to pagination API.

All test modifications properly handle the new paginated response structure while maintaining the same test coverage and assertions.

@@ -1,5 +1,5 @@
use std::collections::{HashMap, HashSet};

use axum::routing::options;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused import (may break builds with deny(warnings))

use axum::routing::options; is unused in this module. Please remove it.

-use axum::routing::options;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use axum::routing::options;
🤖 Prompt for AI Agents
In crates/lakekeeper/src/implementations/postgres/catalog.rs around line 2, the
import use axum::routing::options; is unused and will cause a warning/failure
under deny(warnings); remove that import line from the file; if the symbol is
needed later, replace with a used import or reference it where required,
otherwise delete the unused use statement so the module compiles without
warnings.

Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
@dttung2905 dttung2905 force-pushed the add-pagination-list-projects branch from a247b35 to 8e9b72d Compare August 12, 2025 20:10
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)

441-444: Import the return type; avoid fully-qualified path in signature

Prefer importing the type at the top for readability and consistency (echoing earlier feedback).

Apply this diff to the signature:

-    ) -> Result<crate::api::management::v1::project::ListProjectsResponse> {
+    ) -> Result<ListProjectsResponse> {

And add this import near the other crate::api imports:

use crate::api::management::v1::project::ListProjectsResponse;
crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)

454-457: Simplify has_more calculation and avoid lossy conversion

No need to convert page_size to usize. Compare in i64 space to avoid the expect and make intent clearer.

Apply this diff:

-    let page_as_usize: usize = page_size
-        .try_into()
-        .expect("should be running on at least 32 bit architecture");
-    let has_more = projects.len() > page_as_usize;
+    let has_more = (projects.len() as i64) > page_size;
🧹 Nitpick comments (3)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (3)

435-441: Add composite index to support the pagination ORDER BY

To keep this query efficient at scale, ensure there is a composite index on (created_at, project_id) on the project table. Without it, cursor scans will degrade to sequential scans under load.

Would you like me to open a follow-up issue or migration snippet to add this index?


886-896: LGTM: tests updated for paginated return shape

Tests now unwrap the ListProjectsResponse correctly and validate membership. Consider extracting a small helper for constructing a default PaginationQuery to reduce repetition.

Also applies to: 910-920, 932-942


1281-1362: Add tests for filtered pagination and uneven last page

Great coverage for the unfiltered, even-split case. Two additions would harden this:

  • Filtered pagination: pass Some(HashSet) and paginate to verify the WHERE clause and ordering behave with a filter.
  • Uneven page: e.g., 5 projects with page-size=2 to validate three pages (2,2,1) and a final None token.

I can draft these tests if helpful.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a247b35 and 8e9b72d.

📒 Files selected for processing (8)
  • .sqlx/query-04c6fd4a25005469a8342fe751db0302f8ff81b7046efaa73c0d2433e1867780.json (0 hunks)
  • .sqlx/query-ac395ee12ac90d563f6ed2b5afe52c2b2e8f6f0bce9792aae0a253a729d3fe16.json (1 hunks)
  • crates/lakekeeper/src/api/management/mod.rs (1 hunks)
  • crates/lakekeeper/src/api/management/v1/project.rs (4 hunks)
  • crates/lakekeeper/src/implementations/postgres/catalog.rs (1 hunks)
  • crates/lakekeeper/src/implementations/postgres/warehouse.rs (6 hunks)
  • crates/lakekeeper/src/service/catalog.rs (1 hunks)
  • docs/docs/api/management-open-api.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • .sqlx/query-04c6fd4a25005469a8342fe751db0302f8ff81b7046efaa73c0d2433e1867780.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • .sqlx/query-ac395ee12ac90d563f6ed2b5afe52c2b2e8f6f0bce9792aae0a253a729d3fe16.json
  • docs/docs/api/management-open-api.yaml
  • crates/lakekeeper/src/service/catalog.rs
  • crates/lakekeeper/src/api/management/mod.rs
🧰 Additional context used
🧬 Code Graph Analysis (3)
crates/lakekeeper/src/implementations/postgres/catalog.rs (4)
crates/lakekeeper/src/api/management/v1/project.rs (2)
  • get_project (169-201)
  • list_projects (231-255)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (2)
  • get_project (238-270)
  • list_projects (414-484)
crates/lakekeeper/src/service/catalog.rs (3)
  • get_project (589-592)
  • transaction (70-70)
  • list_projects (597-601)
crates/lakekeeper/src/api/management/mod.rs (1)
  • list_projects (565-571)
crates/lakekeeper/src/api/management/v1/project.rs (4)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)
  • list_projects (414-484)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (438-444)
crates/lakekeeper/src/service/catalog.rs (1)
  • list_projects (597-601)
crates/lakekeeper/src/api/management/mod.rs (1)
  • list_projects (565-571)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (4)
crates/lakekeeper/src/service/mod.rs (1)
  • from_db_unchecked (203-205)
crates/lakekeeper/src/api/management/v1/project.rs (1)
  • list_projects (231-255)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (438-444)
crates/lakekeeper/src/service/catalog.rs (2)
  • list_projects (597-601)
  • begin_read (64-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: check-generated-openapi-matches
  • GitHub Check: test
  • GitHub Check: sqlx-check
🔇 Additional comments (8)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)

430-435: AI summary mismatch: this maps to service::GetProjectResponse (name), not API GetProjectResponse (project_name)

Code maps warehouse::get_project (API shape with project_name) into service::GetProjectResponse { name: ... }. The AI summary claims the opposite. If this mapping is intentional to keep the Catalog trait stable, this is fine. Otherwise, update the return type/imports accordingly.

crates/lakekeeper/src/api/management/v1/project.rs (4)

15-19: LGTM: standardized pagination types

Switch to PageToken and PaginationQuery is consistent with other endpoints and addresses prior feedback.


49-69: LGTM: ListProjectsQuery shape and helper

Query params and the pagination_query() helper look correct and consistent.


76-78: LGTM: next_page_token included in response

Response now carries the cursor; naming aligns with kebab-case conventions.


231-255: LGTM: endpoint plumbing for paginated list-projects

AuthZ filter + direct pass-through to Catalog with PaginationQuery is clean and correct.

crates/lakekeeper/src/implementations/postgres/warehouse.rs (3)

10-11: LGTM: import relocation

Using API-layer GetProjectResponse and new ListProjectsResponse here is appropriate for this module.


265-266: LGTM: field rename alignment

Mapping to project_name matches the updated API struct.


458-473: LGTM: correct cursor emission (uses last included row after trimming)

Pop extra row and derive the token from the last included row ensures no gaps/duplicates across pages.

Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)

443-447: Nit: align param naming with other paginated methods

Other methods in this impl (e.g., list_tables, list_views, list_tabulars) use pagination_query for the parameter name. Consider the same here for consistency.

Apply this diff:

-    async fn list_projects(
-        project_ids: Option<HashSet<ProjectId>>,
-        pagination: PaginationQuery,
+    async fn list_projects(
+        project_ids: Option<HashSet<ProjectId>>,
+        pagination_query: PaginationQuery,
         transaction: <Self::Transaction as Transaction<Self::State>>::Transaction<'_>,
     ) -> Result<ListProjectsResponse> {
-        list_projects(project_ids, pagination, &mut **transaction).await
+        list_projects(project_ids, pagination_query, &mut **transaction).await
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e9b72d and 087cbbf.

📒 Files selected for processing (1)
  • crates/lakekeeper/src/implementations/postgres/catalog.rs (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
crates/lakekeeper/src/implementations/postgres/catalog.rs (5)
crates/lakekeeper/src/service/catalog.rs (3)
  • get_project (589-592)
  • transaction (70-70)
  • list_projects (597-601)
crates/lakekeeper/src/api/management/v1/project.rs (2)
  • get_project (169-201)
  • list_projects (231-255)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (2)
  • get_project (238-270)
  • list_projects (414-484)
crates/lakekeeper/src/service/authz/mod.rs (2)
  • list_projects (845-847)
  • ListProjectsResponse (147-152)
crates/lakekeeper/src/service/mod.rs (2)
  • ProjectId (146-146)
  • ProjectId (164-206)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: docker / docker
  • GitHub Check: test
  • GitHub Check: sqlx-check
  • GitHub Check: check-generated-openapi-matches
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
🔇 Additional comments (2)
crates/lakekeeper/src/implementations/postgres/catalog.rs (2)

34-37: Importing ListProjectsResponse here is the right cleanup

Brings the type into scope and avoids FQNs. Also looks like the previously flagged unused axum import is gone—thanks for addressing that.


433-438: Field rename mapping verified

  • The service’s GetProjectResponse (src/service/catalog.rs:149) defines name, not project_name.
  • The Postgres query returns a row with project_name, and catalog.rs correctly maps response.project_name into GetProjectResponse.name.
  • A repository-wide search shows no remaining initializations using the legacy project_name field on GetProjectResponse.

All aligned—no further changes needed.

@dttung2905
Copy link
Copy Markdown
Contributor Author

@mooori Could you help to review it again pls 🙏 ?

Comment on lines +58 to +70
#[serde(default = "crate::api::management::v1::default_page_size")]
pub page_size: i64,
}
impl ListProjectsQuery {
#[must_use]
pub fn pagination_query(&self) -> PaginationQuery {
PaginationQuery {
page_token: self.page_token.clone(),
page_size: Some(self.page_size),
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other List*Querys have page_size: Option<i64> and then implement From<ListXQuery> for PaginationQuery. I think ListProjectsQuery should handle that in the same way. Example

Query(request): Query<crate::api::management::v1::project::ListProjectsQuery>,
) -> Result<ListProjectsResponse> {
ApiServer::<C, A, S>::list_projects(api_context, metadata).await
ApiServer::<C, A, S>::list_projects(request, api_context, metadata).await
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ApiServer::<C, A, S>::list_projects(request, api_context, metadata).await
ApiServer::<C, A, S>::list_projects(query, api_context, metadata).await

async fn list_projects<C: Catalog, A: Authorizer + Clone, S: SecretStore>(
AxumState(api_context): AxumState<ApiContext<State<A, C, S>>>,
Extension(metadata): Extension<RequestMetadata>,
Query(request): Query<crate::api::management::v1::project::ListProjectsQuery>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: import ListProjectsQuery at the top of the file

Comment on lines +433 to +438
get_project(project_id, transaction).await.map(|opt| {
opt.map(|response| GetProjectResponse {
project_id: response.project_id,
name: response.project_name,
})
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it now returning a different GetProjectResponse such that the map is needed?

api::{
iceberg::v1::PaginationQuery,
management::v1::{
project::{GetProjectResponse, ListProjectsResponse},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think importing GetProjectResponse here from api instead of service causes get_project to return the wrong GetProjectResponse.

None,
PaginationQuery {
page_size: None,
page_token: crate::api::iceberg::v1::PageToken::NotSpecified,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
page_token: crate::api::iceberg::v1::PageToken::NotSpecified,
page_token: PageToken::NotSpecified,

Some(HashSet::from_iter(vec![project_id_1.clone()])),
PaginationQuery {
page_size: None,
page_token: crate::api::iceberg::v1::PageToken::NotSpecified,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
page_token: crate::api::iceberg::v1::PageToken::NotSpecified,
page_token: PageToken::NotSpecified,

let page_as_usize: usize = page_size
.try_into()
.expect("should be running on at least 32 bit architecture");
let has_more = projects.len() > page_as_usize;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_warehouse_stats: returns a next page token even if there are no more items [ref]

list_projects returns a next page token only if there is a next page.

@c-thiel We probably want both to behave the same?

pagination: PaginationQuery,
transaction: <Self::Transaction as Transaction<Self::State>>::Transaction<'_>,
) -> Result<Vec<GetProjectResponse>>;
) -> Result<crate::api::management::v1::project::ListProjectsResponse>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) -> Result<crate::api::management::v1::project::ListProjectsResponse>;
) -> Result<ListProjectsResponse>;

assert!(page2.next_page_token.is_none());

// Paginated results are return in the expected order
let all_projects = list_projects(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering for paginated project is (created_at, project_id). So the expected order corresponds to project_ids (ids are pushed there after creation).

So I would remove all_projects and just check something like

assert_eq!(page1.projects[0], project_ids[0]);
...

Signed-off-by: dttung2905 <ttdao.2015@accountancy.smu.edu.sg>
@github-actions
Copy link
Copy Markdown

🚨 PR Title Needs Formatting

The title of this PR needs to be formatted correctly.
Please update the title to match the conventional commits format. Examples:

  • feat: feature description for a new feature
  • fix: what was fixed for a bug fix
  • docs: documentation for documentation changes
  • chore(deps): update dependencies other changes

We only use types: feat, fix, docs and chore.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
crates/lakekeeper/src/api/management/mod.rs (1)

26-28: Use the imported type instead of a fully-qualified path in the handler.

You already import ListProjectsQuery; use it directly for consistency/nit.

-        Query(query): Query<crate::api::management::v1::project::ListProjectsQuery>,
+        Query(query): Query<ListProjectsQuery>,

Also applies to: 568-571

crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)

454-457: Avoid lossy i64→usize conversion; compare in i64 directly.

Converting page_size: i64 to usize is unnecessary and adds an infallibility assumption. Compare in i64 to keep types aligned.

-    let page_as_usize: usize = page_size
-        .try_into()
-        .expect("should be running on at least 32 bit architecture");
-    let has_more = projects.len() > page_as_usize;
+    let has_more = (projects.len() as i64) > page_size;
🧹 Nitpick comments (4)
crates/lakekeeper/src/api/management/mod.rs (1)

553-564: Document query params in OpenAPI for list_projects.

The endpoint is now query-driven but the OpenAPI annotation doesn’t declare params. Add params(ListProjectsQuery) to expose pageToken/pageSize in the spec (consistent with other list endpoints like users/roles).

 #[utoipa::path(
     get,
     tag = "project",
     path = ManagementV1Endpoint::ListProjects.path(),
+    params(ListProjectsQuery),
     responses(
         (status = 200, description = "List of projects", body = ListProjectsResponse),
         (status = "4XX", body = IcebergErrorResponse),
     )
 )]
crates/lakekeeper/src/api/management/v1/project.rs (1)

49-59: Harden query deserialization and keep it consistent with other endpoints.

  • Consider defaulting page_token to avoid deserialization errors when omitted.
  • Optionally skip serializing page_size when None to match other list queries.
 #[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
 #[serde(rename_all = "camelCase")]
 pub struct ListProjectsQuery {
     /// Next page token for pagination
-    #[serde(skip_serializing_if = "PageToken::skip_serialize")]
+    #[serde(skip_serializing_if = "PageToken::skip_serialize")]
+    #[serde(default)]
     #[param(value_type=String)]
     pub page_token: PageToken,
     /// Signal an upper bound of the number of results that a client will receive
     /// Default: 100
-    pub page_size: Option<i64>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub page_size: Option<i64>,
 }
crates/lakekeeper/src/implementations/postgres/warehouse.rs (2)

442-449: Optional: bind UUIDs directly instead of strings for ANY($1).

You currently pass Vec<String> of UUIDs. Binding as Vec<uuid::Uuid> (and optionally casting ANY($1::uuid[])) avoids string conversions and can help the planner. Only do this if ProjectId exposes the inner UUID cheaply.


1279-1325: Tests cover happy-path pagination and ordering; slight flakiness risk remains.

Order is (created_at, project_id). If two rows share the same created_at at DB precision, ordering falls back to project_id which may not match insertion order. This is unlikely but possible. If this ever flakes, consider ensuring distinct created_at (e.g., tiny sleep) or relaxing the exact index-based assertions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 087cbbf and 1768671.

📒 Files selected for processing (5)
  • crates/lakekeeper/src/api/management/mod.rs (2 hunks)
  • crates/lakekeeper/src/api/management/v1/project.rs (4 hunks)
  • crates/lakekeeper/src/implementations/postgres/catalog.rs (2 hunks)
  • crates/lakekeeper/src/implementations/postgres/warehouse.rs (5 hunks)
  • crates/lakekeeper/src/service/catalog.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/lakekeeper/src/implementations/postgres/catalog.rs
🧰 Additional context used
🧬 Code graph analysis (3)
crates/lakekeeper/src/api/management/mod.rs (4)
crates/lakekeeper/src/api/management/v1/project.rs (1)
  • list_projects (229-249)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (436-442)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)
  • list_projects (414-483)
crates/lakekeeper/src/service/catalog.rs (1)
  • list_projects (600-604)
crates/lakekeeper/src/api/management/v1/project.rs (4)
crates/lakekeeper/src/api/management/mod.rs (2)
  • from (1548-1553)
  • list_projects (565-571)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (2)
  • from (687-692)
  • list_projects (414-483)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (436-442)
crates/lakekeeper/src/service/catalog.rs (1)
  • list_projects (600-604)
crates/lakekeeper/src/implementations/postgres/warehouse.rs (3)
crates/lakekeeper/src/api/management/v1/project.rs (2)
  • list_projects (229-249)
  • from (61-66)
crates/lakekeeper/src/implementations/postgres/catalog.rs (1)
  • list_projects (436-442)
crates/lakekeeper/src/service/catalog.rs (2)
  • list_projects (600-604)
  • begin_read (67-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: docker / docker
  • GitHub Check: test
  • GitHub Check: clippy
  • GitHub Check: sqlx-check
  • GitHub Check: check-generated-openapi-matches
🔇 Additional comments (5)
crates/lakekeeper/src/api/management/mod.rs (1)

570-571: Forwarding query to ApiServer looks correct.

The new call signature ApiServer::<C, A, S>::list_projects(query, api_context, metadata) aligns with the trait change. LGTM.

crates/lakekeeper/src/service/catalog.rs (1)

28-32: Catalog list_projects signature and usage verified

  • Confirmed the Catalog trait in service/catalog.rs defines async fn list_projects(project_ids: Option<HashSet<ProjectId>>, pagination: PaginationQuery, …) and that the Postgres implementation in implementations/postgres/catalog.rs matches this updated signature.
  • Inspected all direct invocations of C::list_projects (e.g., in src/api/management/v1/project.rs) and PostgresCatalog::list_projects (in implementations/postgres/warehouse.rs) to ensure the new pagination parameter (and transaction argument where required) are passed.
  • No other Catalog implementors or mocks were found in the codebase; alternate backends or in-memory mocks are not present.

All backends and call sites have been updated correctly.

crates/lakekeeper/src/api/management/v1/project.rs (2)

74-76: Response shape with next_page_token is clear and matches the SQL-layer behavior.

No issues spotted.


245-249: Plumbing pagination through to Catalog and returning ListProjectsResponse is correct.

Transaction handling and authZ filter look good.

crates/lakekeeper/src/implementations/postgres/warehouse.rs (1)

414-483: Cursor-based pagination implementation looks solid.

  • Correct keyset condition on (created_at, project_id) with LIMIT page_size + 1.
  • Properly trims the sentinel and constructs the next-page token from the last visible row.

@c-thiel c-thiel marked this pull request as draft September 26, 2025 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add pagination to warehouse endpoints

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.