Skip to content

feat(plugin-teradata): add Teradata Vantage support#1888

Merged
datlechin merged 24 commits into
mainfrom
feat/teradata-driver
Jul 16, 2026
Merged

feat(plugin-teradata): add Teradata Vantage support#1888
datlechin merged 24 commits into
mainfrom
feat/teradata-driver

Conversation

@datlechin

Copy link
Copy Markdown
Member

Adds Teradata Vantage support as a registry-only driver, written from scratch in native Swift. It speaks the Teradata wire protocol directly, so there is no ODBC driver or JDBC jar to install. Closes #1867.

Teradata does not publish its driver-core source, and every native Teradata client ships under a license that forbids embedding or redistribution, so the usual "pre-built .a + C bridge" pattern is legally blocked. A pure-Swift wire-protocol driver is the only native, self-contained, license-clean option.

What works

  • Connect and query over the TD2 logon mechanism (Diffie-Hellman + AES-256-GCM96, no external crypto). TDNEGO also works; LDAP/KRB5/JWT report a clear "unsupported" error instead of failing obscurely.
  • TLS over the gateway's HTTPS/WebSocket transport on port 443, with SSL modes Disabled / Preferred / Required / Verify CA / Verify Identity (cert-chain and hostname validation via SecTrust).
  • Browse databases, tables, views, columns, and indexes from DBC.*V. Databases and users are both namespaces, so there is no schema layer under them.
  • Run SQL with TOP / QUALIFY ROW_NUMBER() pagination (Teradata has no LIMIT).
  • Edit rows in the grid (INSERT / UPDATE / DELETE) and edit schema (CREATE / ALTER TABLE, indexes) with Teradata syntax (CREATE MULTISET TABLE ... PRIMARY INDEX).
  • Transaction modes DEFAULT / ANSI / TERA.

Architecture

  • Packages/TableProCore/Sources/TableProTeradataCore/: the pure-Swift protocol engine (~20 files). BigUInt Montgomery modPow, Diffie-Hellman, AES-GCM, LAN/parcel/DER codecs, the WebSocket-over-TLS transport, record decoding, DBC.*V schema queries, and column-type mapping. No external dependencies beyond Foundation / Network / Security / CryptoKit.
  • Plugins/TeradataDriverPlugin/: the DriverPlugin + PluginDatabaseDriver bundle bridging the core to TablePro (connection, schema, editing, DDL, SSL mapping). Registry-only, tag plugin-teradata-v*, no PluginKit ABI bump.
  • Wired: DatabaseType.teradata, the registry-defaults snapshot, build-plugin.yml, release-all-plugins.sh, docs, and a brand icon.

Testing

  • 55 unit tests in TableProTeradataCore cover the protocol codecs, WebSocket framing and accept-key, DH key exchange, record decoding (including DECIMAL), SQL builders, SSL-mode mapping, logon-mechanism gating, the failed-query loop, and error formatting.
  • Live-validated against a real Teradata Vantage server (ClearScape): TD2 SELECT 1, catalog browse, DECIMAL/DATE/TIMESTAMP columns, the full INSERT/UPDATE/DELETE/ALTER/DROP cycle, and TLS in Required and Verify Identity modes.
  • The branch was put through an adversarial review before this PR. It found and fixed seven real defects the happy-path testing missed: DECIMAL columns aborting a browse, a Verify-CA certificate-pin bypass, a WebSocket-ping deadlock, numeric-looking text cells losing their formatting, an out-of-range port crash, a short-reply crash, and the read loop stopping on end-of-statement instead of end-of-request.

Notes for review

  • LDAP / KRB5 / JWT logon are deliberately not implemented. The handshake is TD2-specific, and shipping guessed mechanism OIDs or a Kerberos path that cannot be tested against a live server of that type would be untested auth. They fail fast with a clear message; TD2 and TDNEGO cover the common case.
  • The driver is registry-only, so it is not bundled in the app (matching MSSQL / Oracle). It ships through the plugin registry with the next release.

datlechin added 22 commits July 16, 2026 17:53
@mintlify

mintlify Bot commented Jul 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 16, 2026, 2:50 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dec820bc0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

var frame: [UInt8] = [0x80 | opcode.rawValue]
let length = payload.count
if length < 126 {
frame.append(UInt8(length))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mask client WebSocket frames before sending

When SSL mode is enabled, TeradataTLSTransport.send wraps every LAN message with WebSocketFrame.encodeBinary. This encoder writes an unmasked frame (no MASK bit and no 4-byte masking key); RFC 6455 servers are required to close unmasked client-to-server frames, so TLS/HTTPS Teradata connections can complete the HTTP upgrade and then fail on the first protocol message. Please set the mask bit and mask payloads for client frames.

Useful? React with 👍 / 👎.

}

func executeUserQuery(query: String, rowCap: Int?, parameters: [PluginCellValue]?) async throws -> PluginQueryResult {
try await execute(query: query)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve row caps and parameters in user queries

When the app forwards a parameterized or auto-limited user query, this override discards both rowCap and parameters and sends the raw SQL. For queries where limit injection is not possible (or parameterized queries with ? placeholders), Teradata will either fetch every row without setting isTruncated or send literal placeholders instead of binding values; delegate to executeParameterized when parameters are present and trim/set isTruncated for rowCap like the default plugin implementation.

Useful? React with 👍 / 👎.

+ "RENAME \(identifier(oldColumn.name)) TO \(identifier(newColumn.name))"
}
guard let definition = generateColumnDefinitionSQL(column: newColumn) else { return nil }
return "ALTER TABLE \(qualified(table)) ADD \(definition)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use modify DDL instead of adding duplicate columns

When a column edit changes type/null/default without renaming, this returns ALTER TABLE ... ADD ... using the existing column name. The schema editor will therefore issue an add-column statement for a column that already exists, so normal edits fail with duplicate-column errors instead of applying the change.

Useful? React with 👍 / 👎.

@datlechin
datlechin merged commit 08b7fbd into main Jul 16, 2026
3 checks passed
@datlechin
datlechin deleted the feat/teradata-driver branch July 16, 2026 15:17
@mintlify

mintlify Bot commented Jul 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟡 Building Jul 16, 2026, 2:48 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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: Feature Request: Add support for Teradata DB

1 participant