This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new ea1aa7c229 [jdbc] Implement real paging and pattern filtering for 
listViewsPaged. (#8817)
ea1aa7c229 is described below

commit ea1aa7c229db2628968e32ad1a2b4302146354fb
Author: Wenchao Wu <[email protected]>
AuthorDate: Thu Jul 23 18:32:05 2026 +0800

    [jdbc] Implement real paging and pattern filtering for listViewsPaged. 
(#8817)
---
 .../java/org/apache/paimon/catalog/Catalog.java    |  11 +-
 .../java/org/apache/paimon/jdbc/JdbcCatalog.java   |  58 ++++++-
 .../java/org/apache/paimon/jdbc/JdbcUtils.java     |  32 ++++
 .../org/apache/paimon/jdbc/JdbcCatalogTest.java    | 167 +++++++++++++++++++++
 4 files changed, 262 insertions(+), 6 deletions(-)

diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java 
b/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java
index fd477ffcf1..eb650dc95e 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java
@@ -525,7 +525,8 @@ public interface Catalog extends AutoCloseable {
      * @param pageToken Optional parameter indicating the next page token 
allows list to be start
      *     from a specific point.
      * @param viewNamePattern A sql LIKE pattern (%) for view names. All views 
will be returned if
-     *     not set or empty. Currently, only prefix matching is supported.
+     *     not set or empty. Whether full LIKE semantics or only prefix 
matching is supported is
+     *     catalog-specific; the default implementation ignores the pattern.
      * @return a list of the names of views with provided page size in this 
database and next page
      *     token, or a list of the names of all views in this database if the 
catalog does not
      *     {@link #supportsListObjectsPaged()}.
@@ -552,7 +553,8 @@ public interface Catalog extends AutoCloseable {
      * @param pageToken Optional parameter indicating the next page token 
allows list to be start
      *     from a specific point.
      * @param viewNamePattern A sql LIKE pattern (%) for view names. All view 
details will be
-     *     returned if not set or empty. Currently, only prefix matching is 
supported.
+     *     returned if not set or empty. Whether full LIKE semantics or only 
prefix matching is
+     *     supported is catalog-specific; the default implementation ignores 
the pattern.
      * @return a list of the view details with provided page size (@param 
maxResults) in this
      *     database and next page token, or a list of the details of all views 
in this database if
      *     the catalog does not {@link #supportsListObjectsPaged()}.
@@ -678,7 +680,10 @@ public interface Catalog extends AutoCloseable {
 
     /**
      * Whether this catalog supports name pattern filter when list objects 
paged. If not,
-     * corresponding methods will throw exception if name pattern provided.
+     * corresponding methods will throw exception if name pattern provided. 
This flag is a
+     * catalog-wide default consulted by the base implementations; a specific 
list method may still
+     * honor a pattern by overriding the method directly (in which case it 
need not rely on this
+     * flag).
      *
      * <ul>
      *   <li>{@link #listDatabasesPaged(Integer, String, String)}.
diff --git a/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java 
b/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java
index 79687c9368..c2fc853866 100644
--- a/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java
@@ -65,6 +65,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.AbstractMap;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -1174,13 +1175,64 @@ public class JdbcCatalog extends AbstractCatalog {
                 databaseName);
     }
 
-    // TODO: Implement actual paging and pattern filtering
     @Override
     public PagedList<String> listViewsPaged(
             String databaseName, Integer maxResults, String pageToken, String 
viewNamePattern)
             throws DatabaseNotExistException {
-        CatalogUtils.validateNamePattern(this, viewNamePattern);
-        return new PagedList<>(listViews(databaseName), null);
+        if (CatalogUtils.isSystemDatabase(databaseName)) {
+            return new PagedList<>(Collections.emptyList(), null);
+        }
+
+        // Check if database exists
+        if (!JdbcUtils.databaseExists(connections, catalogKey, databaseName)) {
+            throw new DatabaseNotExistException(databaseName);
+        }
+
+        // CatalogUtils.validateNamePattern is intentionally NOT called here: 
this method supports
+        // pattern filtering directly via SQL LIKE. The catalog-wide 
supportsListByPattern flag
+        // remains false until other list methods (tables/databases) also gain 
pattern support, so
+        // that callers of those methods still get an explicit 
UnsupportedOperationException rather
+        // than silently unfiltered results. Unlike the default 
implementation, this override honors
+        // the pattern even though supportsListByPattern() is false.
+
+        // Per the Catalog contract, a null OR empty pattern means "no 
pattern": return all views.
+        boolean hasPattern = viewNamePattern != null && 
!viewNamePattern.isEmpty();
+        // pageToken is the last view name returned by the previous page 
(opaque to callers). The
+        // empty lower bound returns every view ordered by name.
+        String cursor = pageToken == null ? "" : pageToken;
+
+        String sql;
+        String[] args;
+        if (hasPattern) {
+            sql = JdbcUtils.LIST_VIEWS_PAGED_WITH_PATTERN_SQL;
+            args = new String[] {catalogKey, databaseName, viewNamePattern, 
cursor};
+        } else {
+            sql = JdbcUtils.LIST_VIEWS_PAGED_SQL;
+            args = new String[] {catalogKey, databaseName, cursor};
+        }
+
+        // Per the Catalog contract, maxResults == null OR 0 means "no 
paging": return all matching
+        // views, ordered, with no next page.
+        if (maxResults == null || maxResults == 0) {
+            List<String> views = fetch(row -> 
row.getString(JdbcUtils.VIEW_NAME), sql, args);
+            return new PagedList<>(views, null);
+        }
+
+        Preconditions.checkArgument(maxResults > 0, "maxResults must be 
positive when provided");
+        // Fetch one extra row to detect whether another page follows, without 
relying on a count.
+        List<String> views =
+                fetch(
+                        row -> row.getString(JdbcUtils.VIEW_NAME),
+                        sql + " LIMIT " + (maxResults + 1),
+                        args);
+        String nextPageToken = null;
+        if (views.size() > maxResults) {
+            // More pages remain. Drop the lookahead row and use the last 
returned name as the
+            // cursor for the next page.
+            views = new ArrayList<>(views.subList(0, maxResults));
+            nextPageToken = views.get(maxResults - 1);
+        }
+        return new PagedList<>(views, nextPageToken);
     }
 
     @Override
diff --git a/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcUtils.java 
b/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcUtils.java
index b2e00eb4b3..79ca0db1b2 100644
--- a/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcUtils.java
+++ b/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcUtils.java
@@ -411,6 +411,38 @@ public class JdbcUtils {
                     + VIEW_DATABASE
                     + " = ?";
 
+    // Ordered view listing with an exclusive cursor (view_name > ?). A {@code 
LIMIT n} clause is
+    // appended by the caller when paging is requested.
+    static final String LIST_VIEWS_PAGED_SQL =
+            "SELECT "
+                    + VIEW_NAME
+                    + " FROM "
+                    + VIEW_TABLE_NAME
+                    + " WHERE "
+                    + CATALOG_KEY
+                    + " = ? AND "
+                    + VIEW_DATABASE
+                    + " = ? AND "
+                    + VIEW_NAME
+                    + " > ? ORDER BY "
+                    + VIEW_NAME;
+
+    static final String LIST_VIEWS_PAGED_WITH_PATTERN_SQL =
+            "SELECT "
+                    + VIEW_NAME
+                    + " FROM "
+                    + VIEW_TABLE_NAME
+                    + " WHERE "
+                    + CATALOG_KEY
+                    + " = ? AND "
+                    + VIEW_DATABASE
+                    + " = ? AND "
+                    + VIEW_NAME
+                    + " LIKE ? AND "
+                    + VIEW_NAME
+                    + " > ? ORDER BY "
+                    + VIEW_NAME;
+
     static final String INSERT_VIEW_SQL =
             "INSERT INTO "
                     + VIEW_TABLE_NAME
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java 
b/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java
index 7f985f938e..55808e1e42 100644
--- a/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java
@@ -19,6 +19,7 @@
 package org.apache.paimon.jdbc;
 
 import org.apache.paimon.CoreOptions;
+import org.apache.paimon.PagedList;
 import org.apache.paimon.TableType;
 import org.apache.paimon.catalog.Catalog;
 import org.apache.paimon.catalog.CatalogContext;
@@ -50,6 +51,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLIntegrityConstraintViolationException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -60,6 +62,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.stream.Collectors;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -978,6 +981,170 @@ public class JdbcCatalogTest extends CatalogTestBase {
                 .isEmpty();
     }
 
+    @Override
+    @Test
+    public void testListViewsPaged() throws Exception {
+        String databaseName = "views_paged_db";
+        catalog.createDatabase(databaseName, false);
+
+        // Empty database returns an empty page with no next token.
+        PagedList<String> pagedViews = catalog.listViewsPaged(databaseName, 
null, null, null);
+        assertThat(pagedViews.getElements()).isEmpty();
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        View view = buildView(databaseName);
+        String[] viewNames = {"view1", "view2", "view3", "abd", "def", "opr"};
+        String[] sortedViewNames = 
Arrays.stream(viewNames).sorted().toArray(String[]::new);
+        for (String viewName : viewNames) {
+            catalog.createView(Identifier.create(databaseName, viewName), 
view, false);
+        }
+
+        // maxResults == null: return all views ordered by name, no next page.
+        pagedViews = catalog.listViewsPaged(databaseName, null, null, null);
+        assertThat(pagedViews.getElements()).containsExactly(sortedViewNames);
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // maxResults == 0 is treated as "no paging" per the Catalog contract.
+        pagedViews = catalog.listViewsPaged(databaseName, 0, null, null);
+        assertThat(pagedViews.getElements()).containsExactly(sortedViewNames);
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // An empty pattern means "no pattern": all views are returned.
+        pagedViews = catalog.listViewsPaged(databaseName, null, null, "");
+        assertThat(pagedViews.getElements()).containsExactly(sortedViewNames);
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // Page through with maxResults = 2. nextPageToken is the last name of 
the current page.
+        int maxResults = 2;
+        pagedViews = catalog.listViewsPaged(databaseName, maxResults, null, 
null);
+        assertThat(pagedViews.getElements()).containsExactly("abd", "def");
+        assertThat(pagedViews.getNextPageToken()).isEqualTo("def");
+
+        pagedViews =
+                catalog.listViewsPaged(
+                        databaseName, maxResults, 
pagedViews.getNextPageToken(), null);
+        assertThat(pagedViews.getElements()).containsExactly("opr", "view1");
+        assertThat(pagedViews.getNextPageToken()).isEqualTo("view1");
+
+        pagedViews =
+                catalog.listViewsPaged(
+                        databaseName, maxResults, 
pagedViews.getNextPageToken(), null);
+        assertThat(pagedViews.getElements()).containsExactly("view2", "view3");
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // maxResults larger than the full set returns everything in one page.
+        maxResults = 8;
+        pagedViews = catalog.listViewsPaged(databaseName, maxResults, null, 
null);
+        assertThat(pagedViews.getElements()).containsExactly(sortedViewNames);
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // pageToken resumes strictly after the token value.
+        pagedViews = catalog.listViewsPaged(databaseName, maxResults, "view1", 
null);
+        assertThat(pagedViews.getElements()).containsExactly("view2", "view3");
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // A negative maxResults is rejected.
+        assertThatThrownBy(() -> catalog.listViewsPaged(databaseName, -1, 
null, null))
+                .isInstanceOf(IllegalArgumentException.class);
+
+        // DatabaseNotExistException when the database does not exist.
+        final int finalMaxResults = maxResults;
+        assertThatThrownBy(
+                        () ->
+                                catalog.listViewsPaged(
+                                        "non_existing_db", finalMaxResults, 
"view1", null))
+                .isInstanceOf(Catalog.DatabaseNotExistException.class);
+
+        // Pattern filtering uses standard SQL LIKE: '%' matches any sequence, 
'_' a single char.
+        pagedViews = catalog.listViewsPaged(databaseName, null, null, "view%");
+        assertThat(pagedViews.getElements()).containsExactly("view1", "view2", 
"view3");
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        pagedViews = catalog.listViewsPaged(databaseName, null, null, "view_");
+        assertThat(pagedViews.getElements()).containsExactly("view1", "view2", 
"view3");
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        pagedViews = catalog.listViewsPaged(databaseName, null, null, "_bd");
+        assertThat(pagedViews.getElements()).containsExactly("abd");
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        pagedViews = catalog.listViewsPaged(databaseName, null, null, "zzz%");
+        assertThat(pagedViews.getElements()).isEmpty();
+        assertThat(pagedViews.getNextPageToken()).isNull();
+
+        // Pattern combined with paging.
+        pagedViews = catalog.listViewsPaged(databaseName, 2, null, "view%");
+        assertThat(pagedViews.getElements()).containsExactly("view1", "view2");
+        assertThat(pagedViews.getNextPageToken()).isEqualTo("view2");
+
+        pagedViews =
+                catalog.listViewsPaged(databaseName, 2, 
pagedViews.getNextPageToken(), "view%");
+        assertThat(pagedViews.getElements()).containsExactly("view3");
+        assertThat(pagedViews.getNextPageToken()).isNull();
+    }
+
+    @Override
+    @Test
+    public void testListViewDetailsPaged() throws Exception {
+        String databaseName = "view_details_paged_db";
+        catalog.createDatabase(databaseName, false);
+
+        PagedList<View> pagedViewDetails =
+                catalog.listViewDetailsPaged(databaseName, null, null, null);
+        assertThat(pagedViewDetails.getElements()).isEmpty();
+        assertThat(pagedViewDetails.getNextPageToken()).isNull();
+
+        View view = buildView(databaseName);
+        String[] viewNames = {"view1", "view2", "view3", "abd", "def", "opr"};
+        String[] sortedViewNames = 
Arrays.stream(viewNames).sorted().toArray(String[]::new);
+        for (String viewName : viewNames) {
+            catalog.createView(Identifier.create(databaseName, viewName), 
view, false);
+        }
+
+        pagedViewDetails = catalog.listViewDetailsPaged(databaseName, null, 
null, null);
+        
assertThat(viewNames(pagedViewDetails)).containsExactly(sortedViewNames);
+        assertThat(pagedViewDetails.getNextPageToken()).isNull();
+
+        // maxResults == 0 and an empty pattern both mean "no filtering / no 
paging".
+        pagedViewDetails = catalog.listViewDetailsPaged(databaseName, 0, null, 
"");
+        
assertThat(viewNames(pagedViewDetails)).containsExactly(sortedViewNames);
+        assertThat(pagedViewDetails.getNextPageToken()).isNull();
+
+        int maxResults = 2;
+        pagedViewDetails = catalog.listViewDetailsPaged(databaseName, 
maxResults, null, null);
+        assertThat(viewNames(pagedViewDetails)).containsExactly("abd", "def");
+        assertThat(pagedViewDetails.getNextPageToken()).isEqualTo("def");
+
+        pagedViewDetails =
+                catalog.listViewDetailsPaged(
+                        databaseName, maxResults, 
pagedViewDetails.getNextPageToken(), null);
+        assertThat(viewNames(pagedViewDetails)).containsExactly("opr", 
"view1");
+        assertThat(pagedViewDetails.getNextPageToken()).isEqualTo("view1");
+
+        pagedViewDetails =
+                catalog.listViewDetailsPaged(
+                        databaseName, maxResults, 
pagedViewDetails.getNextPageToken(), null);
+        assertThat(viewNames(pagedViewDetails)).containsExactly("view2", 
"view3");
+        assertThat(pagedViewDetails.getNextPageToken()).isNull();
+
+        // DatabaseNotExistException when the database does not exist.
+        final int finalMaxResults = maxResults;
+        assertThatThrownBy(
+                        () ->
+                                catalog.listViewDetailsPaged(
+                                        "non_existing_db", finalMaxResults, 
null, null))
+                .isInstanceOf(Catalog.DatabaseNotExistException.class);
+
+        // Pattern filtering.
+        pagedViewDetails = catalog.listViewDetailsPaged(databaseName, null, 
null, "view%");
+        assertThat(viewNames(pagedViewDetails)).containsExactly("view1", 
"view2", "view3");
+        assertThat(pagedViewDetails.getNextPageToken()).isNull();
+    }
+
+    private static List<String> viewNames(PagedList<View> views) {
+        return 
views.getElements().stream().map(View::name).collect(Collectors.toList());
+    }
+
     @Test
     public void testConcurrentCreateViewOnlyCreatesOneView() throws Exception {
         String databaseName = "concurrent_view_db";

Reply via email to