morningman opened a new pull request, #70372:
URL: https://github.com/apache/doris/pull/70372

   ### Background
   
   Connector plugins learn which filters they may push down from a 
`ConnectorExpression` tree that fe-core's `ExprToConnectorExpressionConverter` 
builds from a scan's conjuncts. A function call arrives as a 
`ConnectorFunctionCall` with the function's name, return type and arguments, 
and connectors that translate calls match it by name: JDBC writes it into the 
remote SQL when `enable_ext_func_pred_pushdown` is on (the default) and the 
name passes the database's rules (for MySQL, a blocklist of three names), and 
Elasticsearch runs `esquery` as a native query.
   
   ### What problem does this PR solve?
   
   Issue Number: None
   Related PR: #70369 (changes the same converter and SPI package; 
independent), #68702 (also changes the connector SPI; whichever merges second 
takes the next plugin API major)
   PR Type: Bugfix
   
   Problem Summary:
   
   A filter that calls a Java or Python UDF on a JDBC table is sent to the 
remote database as a call of the same name. On MySQL, `WHERE mydb.my_udf(id) = 
113` fails with `FUNCTION doris_test.my_udf does not exist`, and a UDF named 
like a MySQL function, `mydb.abs(id) = 113`, is computed by MySQL instead, 
which silently drops rows. The converter hands connectors only the function's 
name, so no connector can tell a UDF from a builtin. This PR adds 
`ConnectorFunctionCall#isBuiltin()`, set from the catalog function's binary 
type; JDBC and Elasticsearch no longer translate a call that is not a builtin, 
and Doris evaluates it.
   
   ### Details
   
   - **Change map:** +373 −26 in 13 files, of which tests are +303: a 
regression suite +89 with its `.out` +10, and unit tests +204. The production 
code is +69 −23:
     1. SPI (+42 −13): the flag on `ConnectorFunctionCall`, and Rule 6 of 
`pushdown/package-info.java` rewritten; plus the plugin API major 30.0 → 31.0 
in `fe/fe-connector/pom.xml`.
     2. fe-core (+12 −4): `ExprToConnectorExpressionConverter` sets the flag.
     3. Connectors (+15 −6): the checks in `JdbcQueryBuilder` and 
`EsQueryDslBuilder`.
   - **Frequency:** Always, with function pushdown on: any filter that calls a 
UDF on a JDBC catalog over MySQL, and over ClickHouse or Oracle when the UDF's 
name is on their allowlist or in `function_rules`. On Elasticsearch, a UDF 
named `esquery`.
   - **Root cause:** `ExprToConnectorExpressionConverter.convertFunctionCall` 
keeps only `getFnName().getFunction()`, the return type and the arguments, and 
drops `Function.BinaryType`. `JdbcQueryBuilder.functionCallToSql` then renders 
any call that `JdbcFunctionPushdownConfig.canPushDown` accepts by name, and 
`EsQueryDslBuilder` takes any call named `esquery` for the builtin. The SPI 
documentation said calls with real arguments "are safe to match by name".
   - **Why existing tests missed it:** `JdbcQueryBuilderTest` and 
`EsScanPlanProviderTest` build calls by hand, all of them builtins, and no unit 
test converts a UDF. The only regression suite that uses a UDF on a JDBC table, 
`test_mysql_varbinary_with_udf`, calls it in a projection; no suite filters 
with one.
   - **Fix:** The converter sets the flag where the binary type is still known, 
and each connector that matches calls by name checks it. JDBC refuses a 
non-builtin call before rendering anything, so a fallback (an expression the 
converter does not model, sent as SQL text) that calls a UDF stays local too; a 
fallback is a builtin only when it calls no UDF. Elasticsearch requires the 
builtin for `esquery`, and the converter's LIKE / REGEXP shortcut applies only 
to builtins. The alternative, the engine keeping every conjunct with a UDF away 
from all connectors as it does for CASTs, was not chosen: the flag leaves the 
decision to each connector. Changing the SPI surface moves the plugin API major 
to 31.0.
   - **Other paths and branches:** Iceberg drops every function call, and Hive, 
Hudi, Paimon, MaxCompute and Trino translate none by name, so they are not 
affected. On branch-4.1 and branch-4.2 the legacy `JdbcScanNode` has the same 
gap (`JdbcFunctionPushDownRule` checks names only); they have no connector SPI, 
so this PR does not apply there.
   - **Behavior change:** Such a filter no longer reaches the remote database, 
so the remote query returns what the other conjuncts select, without LIMIT, and 
Doris filters the rest. Connector plugins built against plugin API 30 are 
refused by this FE until they are rebuilt.
   
   ### Testing
   
   - New regression suite `external_table_p0/jdbc/test_jdbc_mysql_udf_filter` 
(a Java UDF registered as `udf_add_one` and as `abs`, on MySQL 5.7): on the 
unfixed engine its first check fails, MySQL receiving `WHERE (udf_add_one(`id`) 
= 113)`; by hand on that engine, the query fails with MySQL error 1305, and 
`mydb.abs(id) = 113` returns no row instead of 112. With the fix both return 
112 with no WHERE in the remote query, and the builtin `abs` is still pushed. 
Run locally against a standalone `mysql:5.7.36` container; the External 
pipeline runs it (`enableJdbcTest`).
   - Unit tests, locally: new `ExprToConnectorFunctionCallTest` (fe-core) and 
`ConnectorFunctionCallTest` (SPI), and new cases in `JdbcQueryBuilderTest` and 
`EsQueryDslBuilderTest`. All of fe-connector-spi (154), fe-connector-jdbc 
(275), fe-connector-es (164), and in fe-core the converter tests with 
`PluginDrivenScanNode*Test` (282), pass.
   - Seven mutations, each failing a new test: the converter taking every call 
for a builtin, the LIKE shortcut or the fallback ignoring the flag, `equals` 
ignoring it, the 3-argument constructor meaning a UDF, and each connector check 
removed.
   
   ### Notes for Reviewers
   
   - The 3-argument `ConnectorFunctionCall` constructor still means a builtin; 
its callers are the converter's arithmetic operators and connector tests.
   - Rule 6's old example, `my_udf(a, b) = 1` arriving as one fallback, never 
happened: a UDF call converts to a named call with arguments. The new example 
is a CASE expression, which the converter does not model.
   - `getFn()` is not null-checked: plugin scan conjuncts come from Nereids' 
`ExpressionTranslator`, which always sets the catalog function.
   - The Lance connector being ported from branch-4.2 (not yet opened) will 
check the flag for `starts_with`, `ends_with`, `array_contains` and 
`arrays_overlap`, as 4.2 checks the binary type.
   
   ### Release note
   
   JDBC catalogs over MySQL, ClickHouse or Oracle no longer send a filter that 
calls a Java or Python UDF to the remote database, where it failed with 
"FUNCTION ... does not exist" or was computed by a remote function of the same 
name; Doris evaluates it. On Elasticsearch catalogs, a UDF named esquery is no 
longer run as an Elasticsearch query.
   
   ### Check List (For Author)
   
   - Behavior changed: Yes (see Details)
   - Does this need documentation: No
   - Pre-submission status: Partially verified: the regression suite ran 
against MySQL 5.7 only, not ClickHouse or Oracle; the other JDBC and 
Elasticsearch suites were not run locally
   
   ### AI Disclosure
   
   - Tool: Claude Code (Claude Opus 5.5)
   - Generated: code, tests, this description
   - Human review: The author reviewed part of the changes.
   - Prompts: While porting the Lance pushdown from branch-4.2, the agent 
reported that connectors cannot tell a Java UDF from a builtin of the same 
name; the author chose to fix it at the SPI boundary in its own PR, with a flag 
on the function call that each connector checks, and approved an end-to-end run 
against a MySQL container.
   


-- 
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