Skip to content

feat(plugin-dynamodb): rewrite the DynamoDB driver with index-aware reads, typed edits and table management - #3077

Merged
datlechin merged 4 commits into
mainfrom
feat/dynamodb-driver-rewrite
Sep 23, 2026
Merged

datlechin merged 4 commits into
mainfrom
feat/dynamodb-driver-rewrite

Conversation

@datlechin

Copy link
Copy Markdown
Member

Summary

A rewrite of the DynamoDB driver. The old one read every table with a Scan, typed every edit as a String, dropped attributes it had not seen on the first page and paged by re-reading everything before the page. This one plans each read, keeps types, and adds table management.

Reading

  • A table read is planned from the grid's filters: a Query on the table, on a global index or on a local index, a BatchGetItem for a list of keys, or a Scan with a FilterExpression. The status bar names the plan, the items returned and read, and the RCU charged.
  • A global index is chosen only when it projects every attribute and every one of its sort keys carries a filter, since an item missing a key attribute is not in the index.
  • Pages resume from the last key read instead of re-reading from the start. Positions are keyed by the access path and forgotten after any write.
  • Sorting on the sort key of a single partition goes to DynamoDB; any other sort is applied only when the whole result is held, otherwise the status bar says the rows are in DynamoDB order.
  • Streaming exports spool to a temp file so an attribute first seen late still gets a column, with no row cap. The spool is read with plain chunked reads: URL.lines goes through Foundation's single serial AsyncBytes queue, which the Copilot LSP reader holds in a blocking pipe read, and exports hung behind it.
  • Count Exactly runs the same plan with Select: COUNT; the automatic full count is off for DynamoDB (exactRowCountIsBilledScan), and a failed count now shows its error on the tab and clears it on the next success.

Editing

  • One PartiQL statement per changed row, every value a ?, typed when it runs from the key schema and a consistent GetItem of the current item, so a Number stays a Number and a String Set stays a set.
  • An UPDATE carries the old value of each attribute it changes as a guard, so it fails instead of overwriting a concurrent change or recreating a deleted item. Set NULL removes the attribute. Key changes are refused with a way forward.

Editor

  • PartiQL, plus any allowed DynamoDB action as <Action> {request JSON}. BatchWriteItem resends unprocessed items and reports what is left after 10 attempts; BatchExecuteStatement reports per-statement failures.
  • Throttling and safe-to-repeat failures retry with the SDK standard-mode backoff. Stop throws CancellationError, so the app reads it as a stop.

Tables

  • New Table opens a form the driver describes (keys, capacity, class, deletion protection, up to 20 global and 5 local indexes) with a preview of the CreateTable request. This is a new PluginKit hook pair, createTableFormSpec(schema:) and createTableStatements(for:schema:), with defaults that keep the column grid.
  • Structure tab: add or drop a global index (UpdateTable, key types completed from the table and its items, refused rather than guessed when unknown). Editing an index in place and dropping the primary key or a local index are refused up front through two new PluginSchemaOperation cases, modifyIndex and dropIndex. Before this, an edit was saved as drop then add, and DynamoDB cannot create an index while the old one is still deleting, so the index was lost.
  • Maintenance: point-in-time recovery, deletion protection, stream, table class, on-demand capacity, TTL off. DDL shows the CreateTable request plus TTL and PITR.

Connections

  • Auth methods: access keys, AWS profile, AWS SSO (expired sessions reach the sign-in prompt), and a new DynamoDB Local method. Region falls back to the profile's region. The endpoint follows the region's partition (China, GovCloud, European Sovereign Cloud).
  • Security: the region is validated before it becomes part of the host name. A region such as evil.example# from an imported connection used to move every signed request, session token included, to another host. Plain HTTP is allowed only for a loopback custom endpoint, and redirects are refused.
  • The connection importer for another app now carries the AWS region.

Statement gates

DynamoDB requests are classified per action and body for Safe Mode, MCP and the AI assistant: reads are safe, writes are writes, and requests that take data or a safeguard away (DeleteTable, dropping an index or replica, deletion protection or PITR off, enabling TTL, deleting items in a batch or transaction) are destructive. A body the classifier cannot read, or an action it does not know, is treated as the worst case. Its JSON depth limit matches the driver's, and a test keeps the two action lists equal.

PluginKit ABI

scripts/check-pluginkit-abi.sh against the merge base reports 99 added lines and none removed: two protocol requirements with defaults, new public form types, and two cases on the non-frozen PluginSchemaOperation. Kit 33 is still unreleased (v0.75.0 shipped 32), so it covers this; every plugin Info.plist is already 33 and the minimum is unchanged.

Testing

  • 1,368 unit tests pass across the DynamoDB suites and the classifier, gate, schema, form, count and importer suites, plus the SQL grammar package tests. New suites run the driver over a scripted fake transport (streaming, paging, batch retries, count paging, write errors, request completion, ORDER BY placement, timeout and Stop) and cover the table management statements.
  • A regression test parks its own reader on a quiet pipe and fails in 10 seconds instead of hanging if the export goes back to AsyncBytes; it was confirmed red against the old code.
  • App build, AllPlugins, strict SwiftLint on every changed file, and the docs checks pass.
  • DynamoDBLocalIntegrationTests (with scripts/dynamodb-test-local.sh) did not run: Docker on the build machine was unresponsive. They skip themselves when nothing answers on 127.0.0.1:18000, so CI is unaffected, but nothing in this PR was exercised against a real DynamoDB after the final round of changes.
  • No UI automation: the flows need a DynamoDB endpoint, and the plugin is registry-only, so UI tests cannot load it.

Review

The Codex review did not complete (it ran out of quota mid-run). A security review and a code review from separate agents read the diff; every finding was fixed except one, noted below.

Screenshots

Not captured. The plugin needs a DynamoDB endpoint and Docker was unavailable, so docs/images/dynamodb-create-table*.png are 1560x960 placeholders that need a real capture, and dynamodb-connection-form*.png should be re-captured to show the new auth method.

Known limitations and follow-ups

  • An UPDATE's guard is typed by the item as it is now, because a grid cell carries text without a type, so a concurrent change from the String "5" to the Number 5 is not detected.
  • A read through a global index is eventually consistent; key reads are now ConsistentRead.
  • Not DynamoDB, found on the way: LSPTransport and MCPStdioMessageTransport read their pipes with FileHandle.bytes, which shares that one serial queue, so they can block each other and any other AsyncBytes reader in the app.

@mintlify

mintlify Bot commented Sep 23, 2026

Copy link
Copy Markdown

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

Project Status Preview Updated
TablePro 🟢 Ready View Preview Sep 23, 2026, 7:35 PM

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

Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
…rewrite

# Conflicts:
#	CHANGELOG.md
#	TablePro/Core/Plugins/PluginManager.swift
#	TablePro/Resources/Localizable.xcstrings
#	TablePro/Views/Structure/CreateTableView.swift
@datlechin
datlechin merged commit 6198cda into main Sep 23, 2026
7 of 8 checks passed
@datlechin
datlechin deleted the feat/dynamodb-driver-rewrite branch September 23, 2026 19:37

This branch was successfully deployed

1 active deployment
staging - docs c43579d8 Deployed Sep 23, 2026 by mintlify[bot]
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.

1 participant