Issue #8138 : Do not send a path option when writing with file_format=jdbc - #8176
Conversation
…format=jdbc SparkFileIoSupport.writeDataset always called DataFrameWriter.save(path). That one-argument overload stores its argument as the "path" writer option before saving, and Spark's JDBC provider forwards every option it does not recognise to the driver as a connection property. A JDBC sink has no path, so the driver receives one it never asked for. Postgres and MySQL ignore properties they do not know, so nothing surfaces there. Teradata validates them and fails the write outright with "Invalid connection parameter name path". - Writing a pathless format now calls the no-argument save(), so the path never enters the writer options. Only jdbc is pathless today; the set is named so other option-addressed sinks can join it. - The error message for those formats no longer quotes a path that played no part in the write. - SparkFileOutput no longer demands a file path when the format is pathless. It previously rejected the transform before it ran, forcing users to invent a path that was then sent to the driver as the property causing the failure. The lake table writer shares writeDataset but only ever passes delta or iceberg, both path-based, so it is unaffected. Tests capture the connection properties Spark builds via a delegating driver and assert "path" is absent, rather than depending on a driver strict enough to reject it - H2, like Postgres and MySQL, ignores it.
|
Confirmed against a real Teradata Vantage Express instance 20.00.28.81 Thanks! Testing methodRather than a full build, I patched just the two changed files ( SparkFileIoSupport. java, SparkFileOutputHandler. java ), compiled them with javac against the classpath already present in a apache/hop: 2.19.0 -based container, and injected the two resulting .class files into the running hop-engines-spark-2.19.0. jar with jar -u I verified with:
The 3 scenarios work Log Information2026/08/29 18:34:02 - General - Handled Spark Lake Table Input : Read Category Sales Summary from Iceberg format=iceberg mode=TABLE table=nessie.retail.category_sales_summary timeTravel=NONE columns=[product_category, total_revenue, transaction_count, avg_sale_value, total_units_sold] |
Fixes #8138
The defect
SparkFileIoSupport.writeDatasetalways called the one-argumentDataFrameWriter.save(path):That overload stores its argument as the
pathwriter option before saving. Spark's JDBC provider forwards every option it does not recognise to the driver as a connection property, so a JDBC sink — which has no path — receives one anyway.Postgres and MySQL ignore connection properties they do not know, so this has been invisible against them. Teradata validates them and fails the write:
SparkFileOutputHandlercompounded it: it rejected the transform outright when no file path was configured, so the user had to invent a path for a sink that has none — and that invented value is precisely what got sent to the driver.The fix
isPathless(format)names the formats whose sink is addressed entirely through options rather than a path. Writing one of those now calls the no-argumentsave(), so nothing enters the writer options:Only
jdbcis in that set today. It is a named set rather than anequalscheck so other option-addressed sinks can join it, but I deliberately did not addkafka/console/noopspeculatively —jdbcis the one with a reported failure.Also: the error message for a pathless format no longer quotes a path that played no part in the write, and
SparkFileOutputno longer demands a file path when the format is pathless.SparkLakeTableSupportshareswriteDatasetbut only ever passesdeltaoriceberg, both path-based, so it is unaffected.Tests
The obvious test — write to an embedded database and see whether it complains — does not work, and it is worth saying why. I tried it with H2 first and it passed with the fix reverted: H2, like Postgres and MySQL, silently ignores connection properties it does not recognise. Only a strict driver such as Teradata's surfaces this, and depending on one in a unit test is not an option.
So the tests assert the thing that is actually wrong — what Spark hands the driver.
CapturingJdbcDriverrecords thePropertiesof every connection and delegates to H2, and the test assertspathis not among them. Reverting the fix fails it with exactly the right message:CapturingJdbcDriveris top-level rather than nested on purpose: Spark'sDriverRegistrymatches a registered driver by canonical name, which for a nested class does not equal the binary name it is loaded by, and the lookup fails with an internal error.H2 is added in test scope only, with the version imported from
hop-libs-jdbcthe same wayplugins/actions/waitforsqldoes it.Verified locally on JDK 21:
hop-engines-spark189 tests, 0 failures.spotless:checkandapache-rat:checkclean.Note on scope
I could not reproduce against Teradata itself — no access to that driver or a Vantage instance. What is verified here is that Hop no longer sends
path, which is the cause identified in the report; @tmortada, confirmation against your Teradata setup would be worth having before this is merged.