sarutak commented on PR #53902:
URL: https://github.com/apache/spark/pull/53902#issuecomment-4848067904

   > Design / architecture (1)
   MySQLDialect.scala:41 / PostgresDialect.scala:45: jdbc:aws-wrapper: 
hardcodes a single vendor's library URL scheme into core — see inline
   jdbc:aws-wrapper: is not a JDBC standard; it is the URL scheme of one vendor 
product (AWS aws-advanced-jdbc-wrapper, for Aurora). The wrapper deliberately 
speaks standard MySQL/PostgreSQL underneath, so — as the PR description notes — 
this adds no dialect behavior; it only bakes a third-party library's URL prefix 
into core matching. The Snowflake/Databricks dialects aren't an analogous 
precedent (those are genuinely distinct SQL backends), and accepting a vendor 
prefix here has no principled stopping point: every other connection 
wrapper/proxy (p6spy, other clouds' Aurora-style wrappers) gains an equal claim.
   >
   > The genuine friction this PR works around is that 
MySQLDialect/PostgresDialect are private case classes, so a wrapper user can't 
subclass them to override canHandle. The Spark-idiomatic fix for that is to 
expose the builtin dialects as undocumented advanced APIs — drop private and 
annotate @DeveloperApi (the base JdbcDialect and JdbcDialects.registerDialect 
are already @DeveloperApi). A user can then write class AwsMySQLDialect extends 
MySQLDialect, override canHandle to match only jdbc:aws-wrapper:mysql, and 
register it via JdbcDialects.registerDialect (user dialects are tried first). 
The wrapper URL resolves to their subclass — which inherits all real dialect 
behavior — jdbc:mysql stays on the builtin, there's no AggregatedDialect 
overlap, and no vendor string enters core. Would you be open to that shape 
instead?
   
   I've explored the proposed approach further and have some concerns:
   
   **1. case class inheritance is problematic**
   
   All existing Dialects are `private case class`. Inheriting from a case class 
is deprecated in Scala 2 and prohibited in Scala 3. This pattern doesn't exist 
anywhere in the Spark codebase.
   
   **2. Converting to regular classes has a large blast radius**
   
   To make subclassing viable, we'd need to convert case classes to regular 
classes, which requires manual `equals`/`hashCode` implementations (used by 
`registerDialect`/`unregisterDialect`), test modifications (`=== 
MySQLDialect()` assertions), and potentially affects all 10 Dialect classes for 
consistency. This is a significant refactoring for a 2-line fix.
   
   **3. The DeveloperApi approach doesn't actually serve the broader need, and 
could introduce problems**
   
   I considered the use case of users wanting to extend MySQLDialect for 
databases like TiDB or CockroachDB (which are MySQL/PostgreSQL-compatible but 
have dialect differences). However, since these databases use the same URL 
prefix (`jdbc:mysql://`, `jdbc:postgresql://`), a subclass's `canHandle` cannot 
distinguish them from the original. Both match, resulting in 
`AggregatedDialect` rather than the intended custom Dialect being selected 
alone. Worse, the custom Dialect would also match **other** connections using 
the same prefix, potentially breaking unrelated MySQL/PostgreSQL connections 
with incorrect type mappings.
   
   So the DeveloperApi approach:
   - Only works for the case where the URL prefix is **different** (like 
aws-wrapper) but that's better solved with a simpler `canHandle` fix
   - Does **not** work for the case where the URL prefix is the **same** (like 
TiDB/CockroachDB) which is where Dialect extension is actually needed
   - Could lead users to create subclasses that inadvertently break other 
connections via AggregatedDialect
   
   We should avoid exposing a problematic API to solve a small problem. The 
discussion about making Dialects properly extensible (e.g., explicit dialect 
option, post-connection detection) is valuable but is a separate concern that 
would need a different mechanism. It shouldn't block this PR.
   
   ### Compromise proposals
   
   If the concern is specifically about vendor-specific strings in 
`MySQLDialect`/`PostgresDialect`, here are two alternatives:
   
   **Option A: Regex matching (preferred)**
   
   Instead of hardcoding `"jdbc:aws-wrapper:mysql"`, use a pattern that 
generically accepts any wrapper prefix between `jdbc:` and the database name:
   
   ```scala
   override def canHandle(url: String): Boolean =
     url.toLowerCase(Locale.ROOT).matches("jdbc:(.*:)?mysql.*")
   ```
   
   This:
   - Avoids any vendor-specific string in core
   - Covers all known wrapper patterns (aws-wrapper, p6spy, log4jdbc) without 
enumerating them
   - Eliminates the "no principled stopping point" concern — it's one generic 
rule, not an open-ended list
   - Known URL patterns for MySQL (`jdbc:mysql:`, `jdbc:mysql+srv:`, 
`jdbc:mysql:replication:`, `jdbc:mysql:loadbalance:`, `jdbc:mysqlx:`, 
`jdbc:aws-wrapper:mysql:`, `jdbc:p6spy:mysql:`) are all covered
   - For other Dialects (Oracle, DB2, etc.), no wrapper usage has been 
reported, so we don't propose changes to those in this PR
   
   **Option B: Trait extraction**
   
   Extract the shared logic into a trait and create a separate 
`AuroraMySQLDialect` class:
   
   ```scala
   trait MySQLDialectBase extends JdbcDialect with SQLConfHelper with 
NoLegacyJDBCError {
     // all existing logic
   }
   
   private case class MySQLDialect() extends MySQLDialectBase {
     override def canHandle(url: String): Boolean =
       url.toLowerCase(Locale.ROOT).startsWith("jdbc:mysql")
   }
   
   private case class AuroraMySQLDialect() extends MySQLDialectBase {
     override def canHandle(url: String): Boolean =
       url.toLowerCase(Locale.ROOT).startsWith("jdbc:aws-wrapper:mysql")
   }
   ```
   
   This keeps vendor strings in a dedicated class (similar to how 
`DatabricksDialect` contains `"jdbc:databricks"`), though admittedly it creates 
a class with no dialect differences. Only a `canHandle` override.
   
   I'd lean toward Option A as it's simpler and more principled, but either 
approach addresses the core concern of keeping vendor strings out of 
`MySQLDialect`.


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