Skip to content

feat(plugin-mysql): add OceanBase MySQL-mode connection type - #2741

Open
J2TeamNNL wants to merge 15 commits into
TableProApp:mainfrom
J2TeamNNL:cursor/oceanbase-mysql-flavor-c3a9
Open

feat(plugin-mysql): add OceanBase MySQL-mode connection type#2741
J2TeamNNL wants to merge 15 commits into
TableProApp:mainfrom
J2TeamNNL:cursor/oceanbase-mysql-flavor-c3a9

Conversation

@J2TeamNNL

@J2TeamNNL J2TeamNNL commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #1748

Summary

Adds OceanBase as a connection type on the bundled MySQL plugin (libmariadb), same family as TiDB and MariaDB. Type id OceanBase, default port 2881, scheme oceanbase://. Oracle compatibility mode is out of scope.

This is not a cloned MySQL snapshot. The flavor is resolved from @@version_comment when the connection type is OceanBase. The MySQL handshake reports 5.7.25 (or 5.6.25 through OBProxy) and does not name the engine, so a server opened as MySQL stays on the MySQL path. Choose OceanBase for the OceanBase sidebar, EXPLAIN, Stop, and timeout.

Grid save

OceanBase keeps a hidden __pk_increment column on a table declared without a primary key. The catalog never reports it and SELECT * never returns it, so TablePro edits such a table the way it edits one on MySQL: the UPDATE or DELETE matches every column of the row, and a save that would touch more than one row is rolled back.

A keyless save on FLOAT, DOUBLE or JSON compares through CONCAT(), same as MySQL, MariaDB and TiDB after the measurement on OceanBase CE 4.4.2.1 that landed on main.

Flavor

  • MySQLServerFlavor.oceanbase(version:) from @@version_comment: OceanBase_CE 4.4.2.1, OceanBase 3.1.3, OceanBase_CE-v4.3.5.
  • OceanBase-type connection whose comment is silent stays OceanBase. A comment from another engine is refused.
  • Databend opened as MySQL is still refused.
  • Stop uses KILL QUERY with the handshake thread id. Query timeout sets max_execution_time and ob_query_timeout together, because OceanBase's own 10 second default still fires on statements that are not read-only.
  • System databases: information_schema, mysql, oceanbase. User database test stays listed.
  • Maintenance: ANALYZE TABLE only, on the same PluginMaintenanceOperation descriptors as TiDB and Databend.
  • CHECK and GENERATION_EXPRESSION are gated on OceanBase 4.0+, not on the 5.7 banner.

Curated snapshot

Port 2881, oceanbase://, EXPLAIN as plain text (no FORMAT=JSON, no ANALYZE), no Spatial types, no Server Dashboard, no native dump, Compare only OceanBase-to-OceanBase, no SET FOREIGN_KEY_CHECKS, no Users & Roles connection limit, triggers browse without edit, routines yes. Idle-release field only; no AWS IAM or Unix socket.

iOS

Type routing only, the same shape as TiDB. The iOS MySQL driver has no flavor path. Databend stays Mac-only.

iOS compiles the shared MySQL maintenance descriptors and the PostgreSQL catalog quoting helper (PostgreSQLObjectQueries), so the flavor switch and the quoting change from #2777 build on iPhone. The iOS PostgreSQL driver passes the raw schema and table names and lets that helper quote them.

Verification

This environment has no Docker and no Xcode. Live OceanBase was not measured here; the FLOAT/DOUBLE/JSON row-match prefixes follow the measurement already on main. Unit tests cover version comments, flavor/SQL/Stop paths, registry count 36, allKnownTypes count 30, oceanbase://, Docker Compose oceanbase/oceanbase-ce as root@sys, iOS foreign-key quoting including a backslash, and OceanBase in the MySQL-family text row-match set.

Fork CI on 81597db was green (13 checks). This revision merges current main (theme rewrite, keyless FLOAT/JSON match) so the PR is no longer conflicted.

Docs house style and check-docs-against-source.py pass. swiftlint --strict and xcodebuild could not run here.

Not in this PR

  • Oracle compatibility mode
  • Cluster tenant admin UI
  • Rewriting saved MySQL connections after a comment probe
  • PluginKit bump (follows main, currently 29)
  • Weaviate (Database request: Weaviate #1724), already on main

Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/oceanbase-mysql-flavor-c3a9 branch from e3e5e51 to e163916 Compare September 11, 2026 15:43
J2TeamNNL and others added 4 commits September 11, 2026 16:48
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
# Conflicts:
#	Plugins/MySQLDriverPlugin/MySQLServerFlavor.swift
@datlechin

Copy link
Copy Markdown
Member

Thanks for this. I reviewed it, then measured the hidden-primary-key mechanism against a live OceanBase CE 4.4.2.1 (Docker, oceanbase/oceanbase-ce) using this branch's own driver through a swiftc harness. The measurements contradict what it is built on, so I pushed two commits: the flavor, Stop, snapshot and iOS work stays, the hidden-PK machinery is gone, and tables without a primary key are edited the way they are on MySQL.

What the server actually does

Claim Measured on 4.4.2.1
SHOW INDEX lists the hidden PK It does not. SHOW INDEX on a keyless table returns zero rows. Only SHOW EXTENDED INDEX shows it
__pk_cluster_column exists on some clusters It exists in no version. ob_define.h defines one hidden name, __pk_increment
The hint makes SELECT * include the hidden key It does not. Star expansion passes include_hidden=false, so SELECT /*+ ... */ * returns the visible columns only
The hint is needed for the save It is not. UPDATE/DELETE ... WHERE __pk_increment = ? work with no hint. INSERT naming the column fails even with it
__pk_increment identifies one row Not on a partitioned table. It is a per-tablet sequence: UPDATE ... WHERE __pk_increment = 1 on a 2-partition table changed 2 rows
A MySQL connection to OceanBase is detected from the banner It is not. The handshake banner is 5.7.25 (5.6.25 through OBProxy) and names no engine. Only @@version_comment does

Two more defects showed up in the branch as written. After the sidebar's bulk column load (fetchAllColumns, which autocomplete calls on every connect) the cache held [] for every table, so the grid's SELECT * stopped being projected while the metadata still named __pk_increment as the primary key: generateUpdateSQL then returned nil and the save silently did nothing, which is the bug the PR exists to fix. And the rewrite ran on every SELECT * FROM <table> the driver executed, so CSV/JSON/XLSX/SQL exports, the MCP export tool and the foreign-key preview all gained a __pk_increment column; the SQL export emitted an INSERT naming it, which the server rejects on restore.

What changed

Removed the hidden-PK machinery (MySQLPluginDriver+OceanBase.swift, OceanBaseSQL.swift, OceanBaseHiddenPrimaryKey.swift, the cache, the SELECT * rewrite and the hint injection). A keyless OceanBase table now edits like a keyless MySQL one: the WHERE matches every column, and a save that would touch more than one row is rolled back. That is what OceanBase's own client ODC, DBeaver, Navicat and TablePlus do; none of them use __pk_increment. It also removes 12 of the 15 review findings by deletion.

Identity. An OceanBase connection probes @@version_comment. A server that answers with another engine is refused with a notOceanBase mismatch error, the way the Databend arm already worked (verified against a plain MySQL 8.4). A probe that does not answer at all is not treated as evidence, so a transient failure on an automatic reconnect cannot lock the user out of a connection that worked a minute ago.

Stop uses the handshake thread id, as on MySQL. On a direct connection it equals CONNECTION_ID() and is not truncated; KILL QUERY with it interrupts the query (errno 1317, verified through the driver's cancel path). The previous CONNECTION_ID() route fell back to a raw thread id when the probe failed, which on OceanBase can name another live session.

Query timeout. max_execution_time governs read-only statements only: an UPDATE under a 30 s setting still failed at 10 s with error 4012, OceanBase's own ob_query_timeout default. The statement now sets both, so a 60 s timeout runs an 11 s UPDATE to completion, and the "no limit" setting that exports rely on lifts both.

Docker Compose import. oceanbase as a substring matched ocp-ce, obagent and miniob, none of which speak the MySQL protocol. It now matches the repository, puts OBProxy on 2883, and reads OB_TENANT_NAME, OB_TENANT_PASSWORD, OB_SYS_PASSWORD, OB_DATABASE and OB_CLUSTER_NAME instead of hardcoding an empty root@sys.

Also: one shared mysqlColumnTypesWithoutSpatial helper for TiDB and OceanBase, the iOS and Shortcuts docs now list OceanBase (the picker already offered it), and the swiftlint --strict failure on the snapshot addition is fixed, which would have blocked the lint job.

Verified

Build, swiftlint --strict on the changed paths, both docs checks, and 102 tests across the flavor, variant, registry, compose and type suites. Live: connect as OceanBase and as MySQL, the mismatch against plain MySQL 8.4, fetchColumns on keyless, partitioned, keyed, view and generated/CHECK tables, a keyless save, Stop, and the DML timeout.

datlechin and others added 8 commits September 12, 2026 09:30
# Conflicts:
#	CHANGELOG.md
#	TablePro/Core/Plugins/ImportTypeMapper.swift
#	TableProTests/Core/Plugins/PluginMetadataRegistryTypeCountTests.swift
#	docs/snippets/driver-counts.mdx
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
…ion dialog

Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
…d chrome

Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
Co-authored-by: Nguyễn Nam Long <J2TeamNNL@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Database request: Oceanbase

3 participants