github-actions[bot] commented on code in PR #68510:
URL: https://github.com/apache/doris/pull/68510#discussion_r4100633124


##########
regression-test/suites/external_table_p0/adbc/test_adbc_metadata_ops.groovy:
##########
@@ -194,6 +194,15 @@ suite("test_adbc_metadata_ops", "p0,external") {
         sql """DESC ${catalogName}.${sqliteDb}.meta_a"""
         sqliteExec("ALTER TABLE meta_a ADD COLUMN added_by_refresh_table TEXT;"
                 + " UPDATE meta_a SET added_by_refresh_table = 'x';")
+
+        // The DESC above paid for this schema once. Until a REFRESH arrives 
the connector keeps serving
+        // that copy, which is the half that gives the assertion below its 
meaning -- a connector that
+        // re-read on every statement would satisfy that one while remembering 
nothing. The DATABASE and
+        // CATALOG levels below assert only the positive half: they exercise 
the same rule at a coarser key.
+        def columnsBeforeRefresh = sql("DESC 
${catalogName}.${sqliteDb}.meta_a").collect { it[0] } as Set

Review Comment:
   [P2] This check is masked by fe-core's schema cache, so it does not prove 
the ADBC metadata layer uses its own cache. The first `DESC` populates 
`ExternalMetaCacheMgr`; the second one is normally answered there without 
calling `AdbcConnectorMetadata` at all. Even if `arrowSchemaOf` stopped calling 
`cache.tableSchema(...)` and fetched remotely every time, this pre-refresh 
assertion would remain stale, and the post-refresh assertion would pass because 
REFRESH clears the FE cache. Since this PR deletes the native test that used 
fresh metadata instances sharing only `AdbcMetadataCache`, please retain a 
pure-Java connector-level test with a recording schema source (or otherwise 
bypass the FE schema cache) to cover that integration.



##########
fe/fe-connector/fe-connector-adbc/src/test/java/org/apache/doris/connector/adbc/AdbcConnectorMetadataNativeTest.java:
##########
@@ -0,0 +1,107 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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
+//
+//   http://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.
+
+package org.apache.doris.connector.adbc;
+
+import org.apache.doris.connector.spi.DorisConnectorException;
+import org.apache.doris.thrift.TTableDescriptor;
+import org.apache.doris.thrift.TTableType;
+
+import org.apache.arrow.adbc.core.AdbcStatement;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * The two metadata cases that need a real driver but never read a result: a 
missing table must not be
+ * reported as a driver gap, and the table descriptor handed to the scan path 
must be typed.
+ *
+ * <p>Both stop before any Arrow data is materialized -- the first ends in a 
thrown exception, the second
+ * never asks the source -- which is what makes them safe to run in FE UT. See 
{@link AdbcNativeTestSupport}
+ * for the rule: a test that iterates an {@code ArrowReader} also needs 
arrow-c-data's JNI shim, which is an
+ * upstream binary that cannot load on every host Doris supports, and those 
tests belong in the regression
+ * suites instead. The rest of the metadata surface -- listings, schema 
mapping, views, handles -- is
+ * asserted end to end by {@code 
regression-test/suites/external_table_p0/adbc}, which is where it can be.
+ */
+class AdbcConnectorMetadataNativeTest {
+
+    private static AdbcClient sqliteClient(Path dbFile) {
+        return new AdbcClient(AdbcNativeTestSupport.sqliteDriver(), 
"libadbc_driver_sqlite.so",
+                null, "file:" + dbFile, null, null, Map.of());
+    }
+
+    /**
+     * A fresh cache per call, so each test reads the source rather than an 
earlier test's answers.
+     */
+    private static AdbcConnectorMetadata metadataOn(AdbcClient client) {
+        return new AdbcConnectorMetadata(client, new AdbcSchemaStrategy(),
+                AdbcDialectRegistry::defaultDialect, new 
AdbcMetadataCache(Map.of()));
+    }
+
+    private static void seed(AdbcClient client) {
+        client.withConnection(connection -> {
+            for (String sql : new String[] {
+                    "CREATE TABLE IF NOT EXISTS t1 (c_int INTEGER, c_dbl REAL, 
c_txt TEXT, c_blob BLOB)",
+                    "INSERT INTO t1 VALUES (1, 1.5, 'a', x'00ff')",
+                    "CREATE TABLE IF NOT EXISTS t2 (a INTEGER)",
+                    "CREATE VIEW IF NOT EXISTS v1 AS SELECT * FROM t1"}) {
+                try (AdbcStatement statement = connection.createStatement()) {
+                    statement.setSqlQuery(sql);
+                    statement.executeUpdate();
+                }
+            }
+            return null;
+        });
+    }
+
+    @Test
+    void missingTableIsReportedAsSuchNotAsADriverGap(@TempDir Path tempDir) {
+        try (AdbcClient client = sqliteClient(tempDir.resolve("meta.db"))) {
+            seed(client);
+            AdbcConnectorMetadata metadata = metadataOn(client);
+            // Build a handle for a table that does not exist, bypassing 
getTableHandle's existence check.
+            AdbcTableHandle ghost = new AdbcTableHandle(new 
AdbcNamespace("main", ""), "no_such_table");
+
+            DorisConnectorException e = 
Assertions.assertThrows(DorisConnectorException.class,
+                    () -> metadata.getTableSchema(null, ghost));
+
+            // The fallback to executeSchema must fire only on 
NOT_IMPLEMENTED. Falling back on every error
+            // would answer a plain missing table with "this driver implements 
neither method", sending the
+            // user to look at their driver instead of their table name.
+            Assertions.assertTrue(e.getMessage().contains("no_such_table"), 
e.getMessage());
+            Assertions.assertFalse(e.getMessage().contains("implements 
neither"), e.getMessage());
+        }
+    }
+
+    @Test
+    void tableDescriptorIsTypedForTheScanPath(@TempDir Path tempDir) {
+        try (AdbcClient client = sqliteClient(tempDir.resolve("meta.db"))) {

Review Comment:
   [P2] Keep this descriptor assertion independent of the native libraries. 
`buildTableDescriptor` only constructs the Thrift descriptor from its arguments 
and never touches `client`, but entering this block calls `sqliteClient()` (and 
then `seed()`); `sqliteClient()` reaches `requireNativeLibraryDir()`, so an FE 
UT host without the optional ADBC `.so` files skips this otherwise pure 
contract test. Please construct metadata with a non-opening client (or move 
this case to a pure metadata test) and omit the seed so the 
`HIVE_TABLE`/`hiveTable` guarantee runs everywhere.



##########
regression-test/suites/external_table_p0/adbc/test_adbc_catalog_scan.groovy:
##########
@@ -80,10 +80,22 @@ suite("test_adbc_catalog_scan", "p0,external") {
 
     String catalogName = "test_adbc_catalog_scan_catalog"
     String dbName = "test_adbc_catalog_scan_db"
+    // A second database in the SOURCE, so the listing has a namespace it must 
not mix into the other one.
+    // Created up front, so nothing below depends on a database appearing 
behind Doris's back.

Review Comment:
   [P2] Please preserve a check that `listDatabaseNames` reads the source 
rather than the connector cache. Both source databases are created before this 
catalog and its first database lookup, and no surviving test mutates the source 
namespaces after that cache is populated. Consequently, changing 
`listDatabaseNames` from `reloadNamespaces(...)` to `namespaces(...)` leaves 
these additions green, while the deleted native test explicitly caught a 
stale/ghost cached namespace. A direct connector-level repeated listing with a 
recording source, or an FE lookup/query of a newly created remote database that 
forces the name-cache miss reload, would retain that contract.



##########
fe/fe-connector/fe-connector-adbc/src/test/java/org/apache/doris/connector/adbc/AdbcMetadataCacheNativeTest.java:
##########
@@ -1,184 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you 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
-//
-//   http://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.
-
-package org.apache.doris.connector.adbc;
-
-import org.apache.doris.connector.spi.ConnectorColumn;
-import org.apache.doris.connector.spi.ConnectorTableSchema;
-import org.apache.doris.connector.spi.handle.ConnectorTableHandle;
-
-import org.apache.arrow.adbc.core.AdbcStatement;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * The metadata path with a catalog-level cache in front of it, against the 
real SQLite driver.
- *
- * <p>Each test changes the source behind Doris's back and then asks what 
Doris sees. That is the only
- * evidence that says whether an answer came from memory or from the driver, 
and unlike a call counter it
- * cannot be satisfied by a cache that stores things it never reads.
- *
- * <p>Every {@code metadata()} call stands for one statement: the engine 
builds a fresh
- * {@link AdbcConnectorMetadata} per statement, and the cache is what they 
share.
- *
- * <p>Skips loudly when thirdparty's native libraries are absent -- see {@link 
AdbcNativeTestSupport}.
- */
-class AdbcMetadataCacheNativeTest {
-
-    private final AdbcMetadataCache cache = new AdbcMetadataCache(Map.of());
-
-    private static AdbcClient sqliteClient(Path dbFile) {
-        return new AdbcClient(AdbcNativeTestSupport.sqliteDriver(), 
"libadbc_driver_sqlite.so",
-                null, "file:" + dbFile, null, null, Map.of());
-    }
-
-    /** One statement's view of the catalog. Separate objects, one shared 
cache -- as in production. */
-    private AdbcConnectorMetadata metadata(AdbcClient client) {
-        return new AdbcConnectorMetadata(client, new AdbcSchemaStrategy(),
-                AdbcDialectRegistry::defaultDialect, cache);
-    }
-
-    private static void execute(AdbcClient client, String... statements) {
-        client.withConnection(connection -> {
-            for (String sql : statements) {
-                try (AdbcStatement statement = connection.createStatement()) {
-                    statement.setSqlQuery(sql);
-                    statement.executeUpdate();
-                }
-            }
-            return null;
-        });
-    }
-
-    /** SQLite derives its Arrow types from the values present, so a row is 
needed for the types to be real. */
-    private static void seed(AdbcClient client) {
-        execute(client,
-                "CREATE TABLE t1 (c_int INTEGER, c_txt TEXT)",
-                "INSERT INTO t1 VALUES (1, 'a')");
-    }
-
-    private static List<String> columnNames(ConnectorTableSchema schema) {
-        List<String> names = new ArrayList<>();
-        for (ConnectorColumn column : schema.getColumns()) {
-            names.add(column.getName());
-        }
-        return names;
-    }
-
-    private List<String> columnsOf(AdbcClient client, String table) {
-        ConnectorTableHandle handle = metadata(client).getTableHandle(null, 
"main", table).orElseThrow();
-        return columnNames(metadata(client).getTableSchema(null, handle));
-    }
-
-    @Test
-    void theNextStatementReadsTheSchemaTheLastOneAlreadyPaidFor(@TempDir Path 
tempDir) {
-        try (AdbcClient client = sqliteClient(tempDir.resolve("cache.db"))) {
-            seed(client);
-            Assertions.assertEquals(List.of("c_int", "c_txt"), 
columnsOf(client, "t1"));
-
-            execute(client, "ALTER TABLE t1 ADD COLUMN c_added INTEGER");
-
-            // The column really is there now -- the source changed and Doris 
was not told. Serving the
-            // remembered shape is the whole point; noticing the change here 
would mean nothing was cached.
-            Assertions.assertEquals(List.of("c_int", "c_txt"), 
columnsOf(client, "t1"));
-        }
-    }
-
-    @Test
-    void refreshTableIsWhatMakesTheAlteredColumnsVisible(@TempDir Path 
tempDir) {
-        try (AdbcClient client = sqliteClient(tempDir.resolve("cache.db"))) {
-            seed(client);
-            columnsOf(client, "t1");
-            execute(client, "ALTER TABLE t1 ADD COLUMN c_added INTEGER");
-
-            cache.invalidateTable("main", "t1");
-
-            Assertions.assertEquals(List.of("c_int", "c_txt", "c_added"), 
columnsOf(client, "t1"));
-        }
-    }
-
-    /**
-     * Decision C. Reading the listing from memory is fine; concluding from 
memory that a name does not exist
-     * is not. A user who just created a table and is told it is not there has 
no way to tell that from a
-     * typo, and no reason to suspect a cache.
-     */
-    @Test
-    void tableCreatedAfterTheListingWasCachedIsStillFound(@TempDir Path 
tempDir) {
-        try (AdbcClient client = sqliteClient(tempDir.resolve("cache.db"))) {
-            seed(client);
-            metadata(client).listTableNames(null, "main");
-
-            execute(client, "CREATE TABLE t_new (a INTEGER)", "INSERT INTO 
t_new VALUES (1)");
-
-            Optional<ConnectorTableHandle> handle = 
metadata(client).getTableHandle(null, "main", "t_new");

Review Comment:
   [P2] Please retain a portable test for `getTableHandle`'s own last-chance 
reload before deleting this case. The created-later regression queries do not 
reach that branch: fe-core first refreshes `ExternalDatabase`'s missing name 
through `AdbcConnectorMetadata.listTableNames`, which calls 
`cache.reloadTableNames`; by the time `getTableHandle` runs, its initial 
`cache.tableNames` lookup already contains the new table. Removing the fallback 
reload in `tableExists` would therefore leave those regressions green. A 
pure-Java recording source can seed a stale connector listing, expose a new 
table, call `getTableHandle` directly, and assert the source is reread.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to