fix(backend): select the storage dialect by driver instead of always MySQL - #13993
fix(backend): select the storage dialect by driver instead of always MySQL#13993hsinhoyeh wants to merge 1 commit into
Conversation
…MySQL
initDBClient ended with:
return storage.NewDB(newdb, storage.NewMySQLDialect())
so every install received MySQLDialect regardless of driverName. On a
PostgreSQL install the stores therefore emitted MySQL-only SQL:
- GroupConcat produced GROUP_CONCAT(... SEPARATOR "x"), which PostgreSQL
does not have; it spells this STRING_AGG.
- Upsert produced ON DUPLICATE KEY UPDATE ... VALUES(col) rather than
ON CONFLICT ... DO UPDATE SET col = EXCLUDED.col.
- UpdateWithJointOrFrom produced UPDATE ... INNER JOIN rather than
UPDATE ... FROM.
- IsDuplicateError type-asserted *mysql.MySQLError, which a pgx error can
never satisfy, so duplicate-key detection silently returned false for
every conflict and callers took the "not a duplicate" branch.
Add PostgreSQLDialect implementing the SQLDialect interface, and select the
dialect from driverName in a new newStorageDialect helper. The helper rejects
unknown drivers with the same message initDBDriver uses, rather than silently
defaulting to one engine.
Notes on the implementation:
- STRING_AGG requires an explicit separator, unlike GROUP_CONCAT which
defaults to ",". An empty separator therefore falls back to "," so the
generated SQL keeps parsing and keeps MySQL's behaviour.
- Separators are single-quoted. PostgreSQL reads a double-quoted token as an
identifier, not a string literal, so MySQLDialect's CONCAT(a,"x",b) form
would resolve "x" as a column name.
- IsDuplicateError matches SQLSTATE 23505 via errors.As, because the pgx
stdlib driver wraps *pgconn.PgError before it surfaces through
database/sql. The SQLSTATE is a local constant rather than a new
dependency on jackc/pgerrcode for a single value.
This does not by itself make the PostgreSQL overlay work. The placeholder
dialect (item 1) and the identifier casing (item 2) in kubeflow#13956 are unchanged,
and the Concat call sites in job_store.go and run_store.go still pass
double-quoted SQL string literals that PostgreSQL will read as identifiers.
Those are tracked separately. This commit removes one layer: the stores now at
least receive a dialect that matches the engine they are talking to.
Part of kubeflow#13956
Signed-off-by: hsinhoyeh <yhh92u@gmail.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @hsinhoyeh. Thanks for your PR. I'm waiting for a kubeflow member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Part of #13956. This is a bug I found while scoping items 1 and 2 that the issue does not mention.
initDBClientends with:The dialect is hardcoded, so every install gets
MySQLDialectregardless ofdriverName— including PostgreSQL installs, which reach this line through thepgxbranch a few lines above.What that breaks
storage.SQLDialectexists specifically to paper over engine differences, and onlyMySQLDialectandSQLiteDialectimplement it — there is no PostgreSQL implementation at all. So on a PostgreSQL install the stores emit MySQL-only SQL:GroupConcatGROUP_CONCAT(x SEPARATOR "y")STRING_AGG(x::text, 'y')ConcatCONCAT(a,"y",b)CONCAT(a,'y',b)— double quotes are an identifier in PGUpsertON DUPLICATE KEY UPDATE c=VALUES(c)ON CONFLICT(k) DO UPDATE SET c=EXCLUDED.cUpdateWithJointOrFromUPDATE t INNER JOIN u ...UPDATE t SET ... FROM u ...IsDuplicateError*mysql.MySQLError*pgconn.PgError, SQLSTATE23505IsDuplicateErroris the quiet one. A pgx error can never satisfyerr.(*mysql.MySQLError), so it returnsfalsefor every conflict and callers take the "not a duplicate" branch — no error, just wrong behaviour.Change
PostgreSQLDialectimplementingSQLDialect, plusNewPostgreSQLDialect().newStorageDialect(driverName), which rejects unknown drivers with the same messageinitDBDriveralready uses rather than silently defaulting to one engine.Implementation notes:
STRING_AGGrequires an explicit separator, unlikeGROUP_CONCATwhich defaults to,. An empty separator falls back to,so the SQL keeps parsing and keeps MySQL's behaviour. A single-argumentSTRING_AGGdoes not parse; there is a test for this.IsDuplicateErroruseserrors.Asbecause the pgx stdlib driver wraps*pgconn.PgErrorbefore it surfaces throughdatabase/sql. Tested with wrapped, bare, unrelated-SQLSTATE, non-PG, and nil errors.pgUniqueViolationconstant rather than adding a dependency onjackc/pgerrcodefor one value.What this does not fix
This does not make the PostgreSQL overlay work, and it is not intended to. Still outstanding from #13956:
?placeholders instead of the$1form the dialect inclient_manager/dialect.goalready defines.Concatcall sites injob_store.go:220andrun_store.go:270,283,298pass double-quoted SQL string literals (`"["`,`"]"`) which PostgreSQL reads as identifiers. That is caller-side and left alone here deliberately, rather than writing MySQL-only syntax into a new PostgreSQL dialect.Those need the design decision discussed in #13956 (comment). This PR removes one independent layer: the stores now receive a dialect matching the engine they are talking to.
Testing
10 new tests in
db_test.gocovering everyPostgreSQLDialectmethod, both separator branches, bothUpsertoverwrite branches, and fiveIsDuplicateErrorcases. Plus a regression test assertingMySQLDialect.IsDuplicateErrorreturnsfalsefor a PostgreSQL error, which is the bug being fixed.Note for reproducing locally: the
storagepackage needsCGO_ENABLED=1, sinceSQLiteDialect.IsDuplicateErrorusesmattn/go-sqlite3, which only exposessqlite3.Errorandsqlite3.ErrConstraintunder cgo.I have not run this against a live PostgreSQL instance — the remaining items above still prevent
ml-pipelinefrom reaching Ready, so there is nothing end-to-end to verify against yet.