diff --git a/.claude/rules/ai-mcp-security.md b/.claude/rules/ai-mcp-security.md index 4c16d18095..cd3720f03f 100644 --- a/.claude/rules/ai-mcp-security.md +++ b/.claude/rules/ai-mcp-security.md @@ -2,10 +2,6 @@ paths: - "TablePro/Core/AI/**/*" - "TablePro/Core/MCP/**/*" - - "docs/developers/**/*" - - "docs/integrations/mcp-clients.mdx" - - "docs/integrations/raycast.mdx" - - "docs/features/ai-assistant.mdx" --- # AI and MCP @@ -15,4 +11,4 @@ This path decides what a model, a tool call or a paired client may do with the u - **Preserve** provider disclosures, tool authorization, Safe Mode and confirmation state, token scope, connection allowlists, query limits, timeouts and audit logging. Read-only stays the default, and a destructive operation stays behind an explicit confirmation. - **Widening any of them is the change itself**, never a side effect: state in the PR what a user or a paired client can now do that it could not before. - **Run a security review of the diff** before committing any change to authorization, scope, allowlists or a destructive operation. -- **Update the matching page under `docs/developers/` in the same change**; external clients are written against it. +- **Update the matching page under `../tablepro-docs/developers/` in a paired pull request**; external clients are written against it. diff --git a/.claude/rules/docs-authoring.md b/.claude/rules/docs-authoring.md deleted file mode 100644 index 638a57580d..0000000000 --- a/.claude/rules/docs-authoring.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -paths: - - "docs/**/*.mdx" - - "docs/docs.json" - - "docs/snippets/**/*" - - "docs/images/**/*" ---- - -# Docs - -- **Read `docs/STYLE.md` before writing.** It is the spec for every page. -- **Run `verify.sh docs`**, which runs the three checks CI runs on `docs/` (writing style, claims against source, links). -- **Write the docs last, or re-check every claim against the source right before committing.** Capability tables, supported-engine lists and default values go stale when a later commit in the same branch changes the code. -- **What the scripts cannot catch:** a first sentence that repeats the frontmatter `description`; `alt` text identical to the `` caption; design rationale aimed at the reader; the product as the subject of a sentence instead of the reader's task; "you can"; a bold label used as a heading; prose that repeats the table above it; a callout the page is wrong without. -- **A page that shows UI carries a light and dark screenshot pair** (`docs/images/.png` and `-dark.png`) in a ``. -- **No competitor is named in `docs/`.** diff --git a/.claude/rules/plugin-system.md b/.claude/rules/plugin-system.md index c4e980e137..0c88f1bd88 100644 --- a/.claude/rules/plugin-system.md +++ b/.claude/rules/plugin-system.md @@ -23,6 +23,6 @@ TableProPluginKit is built with Library Evolution, so an already-built plugin ke Building and structure: - **The app scheme builds only the bundled plugins**; run `verify.sh plugins` (the `AllPlugins` aggregate) whenever `Plugins/` changes. -- **A new driver** needs its `project.yml` target and an entry in the `AllPlugins` list, a `DatabaseType` constant, an entry in `.github/plugin-registry.json` keyed by its tag slug, a curated snapshot in `PluginMetadataRegistry` (without it the type never appears in the New Connection picker), a row in `docs/databases/index.mdx`, and a CHANGELOG entry. +- **A new driver** needs its `project.yml` target and an entry in the `AllPlugins` list, a `DatabaseType` constant, an entry in `.github/plugin-registry.json` keyed by its tag slug, a curated snapshot in `PluginMetadataRegistry` (without it the type never appears in the New Connection picker), a row in `../tablepro-docs/databases/index.mdx` (a paired docs pull request), and a CHANGELOG entry. - **A `Plugins/` file that `TableProMobile/project.yml` also compiles** marks every top-level declaration `nonisolated` and depends on no plugin-only code, because the iOS target defaults to `MainActor` (`scripts/ci/check-ios-shared-isolation.py` checks it). - **Plugin tags** are `plugin--v`, where the slug is a key in `.github/plugin-registry.json`; check `git tag -l "plugin-*"` before creating one. diff --git a/.claude/skills/fix-issue/SKILL.md b/.claude/skills/fix-issue/SKILL.md index 11b8759fd2..deff5b9744 100644 --- a/.claude/skills/fix-issue/SKILL.md +++ b/.claude/skills/fix-issue/SKILL.md @@ -98,7 +98,8 @@ Run every step through the tree's wrapper, written out in full each time (the va | `abi ` | `TableProPluginKit` changed (commit first) | | `lint ...` | every changed Swift file | | `l10n` | strings or plugin messages changed | -| `docs`, `agent-docs` | `docs/`, or `AGENTS.md` and `.claude/` changed | +| `docs []` | a paired docs change, or a change to a menu, shortcut, driver list or docs.tablepro.app link | +| `agent-docs` | `AGENTS.md` or `.claude/` changed | - Check the `root:` line names your tree. The verdict is `PASS`, `FAIL` or `INCONCLUSIVE` (exit 0, 1, 2), also written to `.verdict`. - **Verify in batches**: build once after a coherent set of edits, then re-run only a failed step. @@ -118,7 +119,7 @@ Fix what is real and inside the scope boundary without asking; a real finding ab 2. **Run `scripts/check-banned-words.sh --staged`** and the same check over the PR body file. 3. **Commit, then push, in separate calls**, after `git -C branch --show-current` in its own call. The PR title is the squash commit subject: Conventional Commits, at most 72 characters. Never override the git identity. If SSH fails: `git -c credential.helper='!gh auth git-credential' push https://github.com/TableProApp/TablePro.git `. 4. **Open the PR, ready for review**: `gh pr create --repo TableProApp/TablePro --base main --head --title "" --body-file `, the body following `.github/pull_request_template.md` and readable in under a minute. It is not a log: no investigation story, no review history, no list of every test run. -5. **Screenshots**, only when the change is visible: from a Debug build launched with `TABLEPRO_UI_TEST_SANDBOX` set to a scratch directory, before and after, light and dark. Look at each before using it. Attach with `gh pr create --attach './after.png#After'` and reference it as `![After](./after.png)`. Reuse them in `docs/images/` when a page shows that surface. A registry-only plugin cannot load in a sandboxed Debug build; say so when that blocks a shot. +5. **Screenshots**, only when the change is visible: from a Debug build launched with `TABLEPRO_UI_TEST_SANDBOX` set to a scratch directory, before and after, light and dark. Look at each before using it. Attach with `gh pr create --attach './after.png#After'` and reference it as `![After](./after.png)`. Reuse them in `../tablepro-docs/images/` when a page shows that surface. A registry-only plugin cannot load in a sandboxed Debug build; say so when that blocks a shot. ## Phase 7: Land diff --git a/.claude/skills/fix-issue/references/research-sources.md b/.claude/skills/fix-issue/references/research-sources.md index 4fd76adb58..6a9386404b 100644 --- a/.claude/skills/fix-issue/references/research-sources.md +++ b/.claude/skills/fix-issue/references/research-sources.md @@ -10,7 +10,7 @@ Where to find the correct behavior, and what counts as evidence for it. - **Human Interface Guidelines**: `https://developer.apple.com/design/human-interface-guidelines`. For a database client the macOS sections that matter most are windows, panels, sheets, toolbars, sidebars, menus, tables and lists, and selection. Quote the rule; "the HIG says so" without a quote is not evidence. - **AppKit** (`https://developer.apple.com/documentation/appkit`) and **SwiftUI** (`https://developer.apple.com/documentation/swiftui`). Check whether a SwiftUI modifier already does the job before dropping to AppKit, and check the reverse too: several TablePro views are AppKit because the SwiftUI version misbehaves, and `CLAUDE.md` records why. - **Availability**: the deployment target is macOS 13. An API from macOS 14, 15 or 26 needs an `if #available` branch, and the plan names the fallback. Name the modern API, and when the only option is deprecated, name its replacement. -- **TablePro's own docs** in `docs/`: a fix must not contradict what users have been told. +- **TablePro's own docs** in TableProApp/docs (`../tablepro-docs`): a fix must not contradict what users have been told. ## Dependencies diff --git a/.claude/skills/fix-issue/scripts/verify.sh b/.claude/skills/fix-issue/scripts/verify.sh index d89d4ed98b..e1c3845215 100755 --- a/.claude/skills/fix-issue/scripts/verify.sh +++ b/.claude/skills/fix-issue/scripts/verify.sh @@ -17,7 +17,7 @@ # verify.sh [options] ios [Suite…] # TableProMobileTests on a simulator # verify.sh [options] abi # verify.sh [options] lint [path…] -# verify.sh [options] docs # docs/ house style + claims against source +# verify.sh [options] docs [docs-checkout] # TableProApp/docs checks against this tree (default ../tablepro-docs) # verify.sh [options] l10n # plugin and package strings in the app catalog, managed manually # verify.sh [options] agent-docs # CLAUDE.md, .claude/rules and this skill against the tree # verify.sh tail [lines] # re-read a stored log without rerunning @@ -588,7 +588,7 @@ case "$STEP" in agent-docs) # CLAUDE.md, .claude/rules and this skill, checked for paths, symbols and skills that no - # longer exist. It never reads docs/, and it used to run inside every `lint`, which made + # longer exist. It used to run inside every `lint`, which made # every code lint red whenever main carried one stale reference nobody on the branch wrote. log="$(new_log agent-docs)" run_logged "$log" "$REPO_ROOT/scripts/check-doc-symbols.sh" @@ -622,29 +622,26 @@ case "$STEP" in ;; docs) - # The three checks that actually read docs/. None runs anywhere else in this script, and - # CI runs them in the "Validate docs" job, so a local run is the only way to see a failure - # before the push. check-links.py was missing here, so a link to a heading that does not - # exist reached main on a green local run (#2988). + # The docs live in TableProApp/docs, whose CI checks them against the app's main only. Run + # here with --app pointed at this tree, the same checks say whether a paired docs change + # passes once this branch merges, and whether this branch breaks a page that is already live. log="$(new_log docs)" : > "$log" code=0 - for check in "check-writing-style.sh" "check-docs-against-source.py" "check-links.py"; do - script="$REPO_ROOT/docs/scripts/$check" - if [ ! -f "$script" ]; then - note "missing: docs/scripts/$check" - STATUS=INCONCLUSIVE - continue - fi - case "$check" in - *.sh) (cd "$REPO_ROOT/docs" && bash "scripts/$check") >> "$log" 2>&1 || code=1 ;; - *.py) (cd "$REPO_ROOT/docs" && python3 "scripts/$check") >> "$log" 2>&1 || code=1 ;; - esac - done - if [ "$STATUS" != "INCONCLUSIVE" ]; then + main_checkout="$(dirname "$(git -C "$REPO_ROOT" rev-parse --path-format=absolute --git-common-dir)")" + docs_root="${1:-$(dirname "$main_checkout")/tablepro-docs}" + if [ ! -f "$docs_root/docs.json" ]; then + STATUS=INCONCLUSIVE + note "no TableProApp/docs checkout at $docs_root: clone https://github.com/TableProApp/docs there, or pass its path" + else + { + (cd "$docs_root" && bash scripts/check-writing-style.sh "$REPO_ROOT") || code=1 + (cd "$docs_root" && python3 scripts/check-docs-against-source.py --app "$REPO_ROOT") || code=1 + (cd "$docs_root" && python3 scripts/check-links.py --app "$REPO_ROOT") || code=1 + } >> "$log" 2>&1 if [ $code -eq 0 ]; then STATUS=PASS - note "docs/: house style, source claims and links all agree" + note "$docs_root: house style, source claims and links all agree with this tree" else STATUS=FAIL # The scripts print one line per check, most of them "ok". Show the failing check @@ -652,8 +649,8 @@ case "$STEP" in note "$(grep -A 2 -E '^ *FAIL' "$log" 2> /dev/null | sed 's/^/ /' | head -15)" note "$(grep -E 'contradict|house style' "$log" 2> /dev/null | sed 's/^/ /' | head -3)" fi + note "STYLE.md rules no script enforces: see AGENTS.md in $docs_root" fi - note "STYLE.md rules no script enforces: see .claude/rules/docs-authoring.md" emit "$log" $code ;; diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index f773027346..be94bc7450 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -122,8 +122,9 @@ rise. Touch no other version: plugins and PluginKit pin `1.0` in `project.yml`, ### Docs changelog -Add an `` block at the top of `docs/changelog.mdx`, right after the frontmatter. The docs are -English only, so there is no translated copy. +Add an `` block at the top of `changelog.mdx` in the docs repository (`../tablepro-docs`, +TableProApp/docs; confirm the path and `git pull --ff-only` first), right after the frontmatter. The +docs are English only, so there is no translated copy. ```mdx @@ -161,7 +162,7 @@ release with the flag. ### Commit, tag, push ```bash -git add Configs/Version.xcconfig CHANGELOG.md docs/changelog.mdx .github/release-flags.json \ +git add Configs/Version.xcconfig CHANGELOG.md .github/release-flags.json \ TablePro/Resources/WhatsNew.md scripts/check-banned-words.sh --staged git commit -m "release: v" @@ -170,6 +171,18 @@ git push origin main git push origin v ``` +Then publish the docs changelog. It goes straight to the docs `main` under the admin bypass, and +Mintlify deploys it: + +```bash +git -C ../tablepro-docs add changelog.mdx +git -C ../tablepro-docs commit -m "docs(changelog): v" +git -C ../tablepro-docs push origin main +``` + +The docs CI fails while the app's `CHANGELOG.md` carries a version the page does not, so push it in +the same sitting as the tag. + - Keep the subject exactly `release: v`: `scripts/ci/detect-changed-paths.sh` matches it to skip the duplicate test run on `main`, since the tag's run is the one that gates. - Always `-a -m`: the owner's git has `tag.gpgSign=true`, and a plain `git tag ` fails with @@ -255,7 +268,8 @@ To get one driver fix to users who have not updated, build it against their rele 1. **Blog post**, by `references/blog-post.md`: one file in `resources/blog/` of the marketing site repo (`../tablepro-web` by default; confirm the path). It goes live first, because the newsletter and the X posts link to it. Once it is live, link it from the version's `` block in - `docs/changelog.mdx`, on its own line before the first `###`: + `changelog.mdx` of the docs repository, on its own line before the first `###`, and push it as + `docs(changelog): link the announcement`: `[Read the announcement](https://tablepro.app/blog/)`. 2. **Newsletter**, by `references/newsletter.md`: 150 to 250 words that name the headline items and link the post. diff --git a/.claude/skills/release/references/fact-checks.md b/.claude/skills/release/references/fact-checks.md index f8ec6296ee..c16d163f09 100644 --- a/.claude/skills/release/references/fact-checks.md +++ b/.claude/skills/release/references/fact-checks.md @@ -34,12 +34,13 @@ the plugin with the release or cut the section. ## 4. Images are real and current -A `docs/images/*.png` at exactly 1560x960 is a "Screenshot coming soon" card. A real file can -still be stale, so compare its date with the commits that changed the UI it shows: +An `images/*.png` in the docs repository (`../tablepro-docs`) at exactly 1560x960 is a "Screenshot +coming soon" card. A real file can still be stale, so compare its date with the commits that changed +the UI it shows: ```bash -sips -g pixelWidth -g pixelHeight docs/images/.png -git log -1 --format='%ad %s' --date=short -- docs/images/.png +sips -g pixelWidth -g pixelHeight ../tablepro-docs/images/.png +git -C ../tablepro-docs log -1 --format='%ad %s' --date=short -- images/.png ``` Never embed a placeholder or a stale shot. Mark the gap in the draft and list it as a blocker. @@ -67,11 +68,11 @@ every constant in the model file, including the default range a comparison runs ## 7. Every URL resolves ```bash -ls docs/features/.mdx docs/databases/.mdx -grep -n "^## " docs/databases/.mdx +ls ../tablepro-docs/features/.mdx ../tablepro-docs/databases/.mdx +grep -n "^## " ../tablepro-docs/databases/.mdx ``` -`docs/changelog.mdx` carries the version only after the release-day docs deploy, so that link +The docs `changelog.mdx` carries the version only after the release-day docs deploy, so that link is a blocker until then, not a broken link. ## 8. Counts and references @@ -98,6 +99,6 @@ Report these apart from any draft. Nothing goes out until every one holds. `shortVersionString` is an element, not an attribute. - Every plugin an announcement names is tagged, built and in the registry (check 3). - Screenshots are captured, light and dark, on a build that contains the change. -- `docs/changelog.mdx` is deployed with the version. +- The docs `changelog.mdx` is deployed with the version. - The fix count is recounted after `[Unreleased]` closed. - The blog post is live. Fetch the URL rather than trusting that the file was written. diff --git a/.claude/skills/release/references/newsletter.md b/.claude/skills/release/references/newsletter.md index a46b17e5ef..4de44fd347 100644 --- a/.claude/skills/release/references/newsletter.md +++ b/.claude/skills/release/references/newsletter.md @@ -91,8 +91,8 @@ Each rule is a yes or no against the draft, and a no is a rewrite. 9. Backticks on every shortcut, SQL keyword, type name and literal (`Cmd+F`, `EXPLAIN ANALYZE`), at most about eight in a sentence. Bold for menu paths and the first mention of a new proper noun, not for run-in headlines. -10. US spelling (color, behavior), the same as the docs, whose `docs/scripts/check-writing-style.sh` - rejects British spelling, and as the app's own strings. +10. US spelling (color, behavior), the same as the docs, whose writing check rejects British + spelling, and as the app's own strings. Run the lint before reading for facts: diff --git a/.github/release-flags.json b/.github/release-flags.json index b6e56db3b1..8a9b9f0b81 100644 --- a/.github/release-flags.json +++ b/.github/release-flags.json @@ -1,4 +1,4 @@ { - "$comment": "Per-release flags read by .github/workflows/build.yml. criticalUpdate marks the release as one Sparkle may interrupt a user about, which bypasses phased rollout, hides Skip and Remind Me Later, and reaches a user who has already downloaded it sooner. The bar for setting it is written in .claude/skills/release/SKILL.md and docs/development/releasing.mdx: data loss, an exposed security issue, a crash or hang on a common path, or a regression with no workaround. Set it in the release commit and set it back in the next one. The release job fails if it is true and this file did not change since the previous tag, which is what a leftover flag looks like.", + "$comment": "Per-release flags read by .github/workflows/build.yml. criticalUpdate marks the release as one Sparkle may interrupt a user about, which bypasses phased rollout, hides Skip and Remind Me Later, and reaches a user who has already downloaded it sooner. The bar for setting it is written in .claude/skills/release/SKILL.md and RELEASING.md: data loss, an exposed security issue, a crash or hang on a common path, or a regression with no workaround. Set it in the release commit and set it back in the next one. The release job fails if it is true and this file did not change since the previous tag, which is what a leftover flag looks like.", "criticalUpdate": false } diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 4f35610b12..0000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Docs - -# No paths filter. As a required status check, a filtered workflow never starts on a PR -# that misses the filter, so the check sits pending and blocks the merge forever. It runs -# in about a minute, which is cheaper than the exception handling. -on: - pull_request: - push: - branches: [main] - workflow_dispatch: - -concurrency: - group: docs-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - docs: - name: Validate docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: '22' - - # Pinned because the CLI self-upgrades inside its own command handlers, so an - # unpinned install lets a release land mid-run and change what the gate checks. - - name: Install the Mintlify CLI - run: npm install --global mint@4.2.816 - - - name: Validate the build - working-directory: docs - run: mint validate - - - name: Check links, anchors and redirects - working-directory: docs - run: mint broken-links --check-anchors --check-redirects - - - name: Check accessibility - working-directory: docs - run: mint a11y - - # The three claims that rot fastest, each checked against the source that - # defines it. All three were wrong at once in August 2026. - - name: Check docs against the source - run: python3 docs/scripts/check-docs-against-source.py - - - name: Check writing style - run: bash docs/scripts/check-writing-style.sh - - # The Mintlify CLI needs a working native toolchain, so it is the step most likely - # to break on an environment change. This one is plain Python, and it also answers - # what the CLI does not: a page in no navigation group, which nothing links to. - - name: Check links and navigation coverage - run: python3 docs/scripts/check-links.py diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index 5befff6ed1..4f8f10c585 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -3,7 +3,7 @@ name: macOS Tests on: # No paths filter here on purpose. A required status check has to report on every pull # request, and a workflow skipped by a paths filter reports nothing at all, which leaves - # the check pending forever and blocks docs-only work. The filtering moved into the + # the check pending forever and blocks a PR that touches no code. The filtering moved into the # changes job below, so the gate job always runs and always reports. pull_request: push: @@ -528,8 +528,8 @@ jobs: if-no-files-found: ignore retention-days: 7 - # The one check to mark required on main. It reports on every pull request, so a docs-only - # change passes on skipped suites instead of hanging, and a red suite cannot be merged past. + # The one check to mark required on main. It reports on every pull request, so a change + # outside the code passes on skipped suites instead of hanging, and a red suite cannot be merged past. gate: name: macOS Tests Gate if: always() diff --git a/AGENTS.md b/AGENTS.md index 0c47caeeaf..6b77ae600b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ This file is the project guide for every agent. Claude Code loads it through `CL - `Packages/`: local SwiftPM packages (`TableProCore`, `TableProOracle`, `TableProEditor`, `TableProGrammars`). Each sets its own Swift language mode in its manifest; `SWIFT_VERSION` in the xcconfig does not reach them, and passing `SWIFT_VERSION=` to `xcodebuild` reaches every package and reports their errors as yours. `Packages/TableProCore/Sources/TableProPluginKit` is a symlink: edit `Plugins/TableProPluginKit/` only. - `Native/`: first-party Rust and Go bridges (see `.claude/rules/native-bridges.md`). - `Libs/`: prebuilt static libraries and iOS xcframeworks, downloaded by `scripts/download-libs.sh`, not in git. +- The docs site (docs.tablepro.app) is its own repository, [TableProApp/docs](https://github.com/TableProApp/docs), cloned beside this one as `../tablepro-docs`. Its `AGENTS.md` and `STYLE.md` govern its pages. - `TablePro.xcodeproj` and `TableProMobile/TableProMobile.xcodeproj` are generated from `project.yml` and `TableProMobile/project.yml` by XcodeGen. They are gitignored, except each one's tracked `Package.resolved`. Never hand-edit them. Signing overrides and secrets go in `Configs/Secrets.xcconfig` (template: `Secrets.xcconfig.example`). ## Commands @@ -94,7 +95,7 @@ swiftlint lint --strict 1. **Tests.** Unit tests for testable behavior; `TableProUITests` automation for a user flow that runs deterministically, or the reason in the PR. UI suites subclass `UITestCase`. Details: `.claude/rules/tests.md`. 2. **CHANGELOG.md.** A user-visible change gets one fragment under `[Unreleased]`, in the existing canonical section. Format: `.claude/rules/changelog.md`. 3. **Localization.** `String(localized:)` for user-facing strings outside SwiftUI literals, never with interpolation (use `String(format: String(localized: "Preview %@"), name)`). Do not localize technical terms. Plugin and package messages must be in the app catalog, managed manually: `python3 scripts/localization.py plugins --add`. Never hand-edit a `.xcstrings`; translations go through `xcodebuild -exportLocalizations` and `-importLocalizations`. -4. **Docs.** A new shortcut, UI or settings change, or driver change updates `docs/` (Mintlify). Follow `docs/STYLE.md`. +4. **Docs.** A new shortcut, UI or settings change, or driver change gets a paired draft pull request in TableProApp/docs that names this one. Its CI checks the app's `main`, so it merges after this one does; `verify.sh docs` runs the same checks against this tree first. 5. **Lint** the changed Swift files with `swiftlint lint --strict`. 6. **Atomic API changes.** A rename or signature change updates every caller and test in the same commit. 7. **Commits and pull requests.** Squash merges use the PR title as the commit subject, so the title is the record: Conventional Commits (`(): `, `!` for breaking), at most 72 characters, a canonical scope when one fits. CI checks it. Branch commits follow the same format; put the explanation in the PR description, which follows `.github/pull_request_template.md`. @@ -107,7 +108,7 @@ For everything: docs, commits, CHANGELOG, UI strings, errors, PR descriptions. S ## CI -- **PRs:** `macos-tests.yml`, `ios-tests.yml`, `docs.yml`, `swiftlint.yml` (pinned, `--strict`), `pr-title.yml` (Conventional Commits, at most 72 characters) and `repo-hygiene.yml` (actionlint, shellcheck and the repo's source-scanning checks). +- **PRs:** `macos-tests.yml`, `ios-tests.yml`, `swiftlint.yml` (pinned, `--strict`), `pr-title.yml` (Conventional Commits, at most 72 characters) and `repo-hygiene.yml` (actionlint, shellcheck and the repo's source-scanning checks). - **Releases:** `build.yml` runs on `v*` tags and produces the DMG, the ZIP and the Sparkle feed, with notes taken from `CHANGELOG.md`. - **Plugins:** `build-plugin.yml` runs on `plugin--v*` tags, where the slug is a key in `.github/plugin-registry.json`. @@ -115,12 +116,11 @@ For everything: docs, commits, CHANGELOG, UI strings, errors, PR descriptions. S | Rule | Covers | | --- | --- | -| `ai-mcp-security.md` | `TablePro/Core/AI`, `TablePro/Core/MCP`, the developer docs (`docs/developers`) | +| `ai-mcp-security.md` | `TablePro/Core/AI`, `TablePro/Core/MCP` | | `changelog.md` | `CHANGELOG.md` | | `connection-window.md` | connection windows, workspaces, sessions, `DatabaseManager` | | `data-grid.md` | the data grid under `TablePro/Views/Results` | | `data-sync-storage.md` | storage, CloudKit sync, the database core | -| `docs-authoring.md` | `docs/` | | `editor.md` | `Packages/TableProEditor`, `TablePro/Theme`, editor views | | `libs.md` | `Libs/` and the libs scripts | | `mongodb-driver.md`, `mysql-driver.md`, `redis-driver.md` | those drivers | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bae031ebd..5eae5f26cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,73 +1,176 @@ # Contributing to TablePro +[`AGENTS.md`](AGENTS.md) holds the architecture invariants and the rules every change has to meet; where this file and it disagree, it wins. The docs site lives in [TableProApp/docs](https://github.com/TableProApp/docs), and releasing is in [`RELEASING.md`](RELEASING.md). + ## Setup -Requirements: Xcode 26.0+, [XcodeGen](https://github.com/yonaskolb/XcodeGen). Optional: SwiftLint, GitHub CLI (`gh`). +| Software | Version | Notes | +|----------|---------|-------| +| Xcode | 26.0+ | The app calls macOS 26 SDK symbols behind `#available`. The deployment target stays macOS 13.0. Every CI job pins 26.4.1 through `.github/actions/setup-xcode` | +| XcodeGen | 2.46.0 exactly | Generated `pbxproj` output differs between versions, so `scripts/generate-project.sh` warns on any other | +| SwiftLint | any | The pull request gate runs `swiftlint lint --strict` | +| SwiftFormat | any | Not run in CI | +| GitHub CLI | any | Optional: `scripts/download-libs.sh` falls back to `curl` | +| Go | 1.27.1 | SAP HANA plugin only: run `scripts/build-hana.sh` with no argument before building `HanaDriver` or `AllPlugins`. The script fetches 1.27.1 when the installed `go` is another release | +| Rust | 1.91.1 | Dameng plugin only, through [rustup](https://rustup.rs): run `scripts/build-dameng.sh arm64` before building `DamengDriver` or `AllPlugins` | Fork the repo on GitHub, then: ```bash git clone https://github.com//TablePro.git && cd TablePro -brew install xcodegen swiftlint +brew install xcodegen swiftlint swiftformat scripts/download-libs.sh +cp Configs/Secrets.xcconfig.example Configs/Secrets.xcconfig scripts/generate-project.sh +open TablePro.xcodeproj ``` -`TablePro.xcodeproj` is generated from `project.yml` and is not in git. Re-run -`scripts/generate-project.sh` whenever you change `project.yml` or `Configs/`, and whenever you -add, move, or delete a source file. Never hand-edit the generated project: the next generate -throws the edit away. +Select the **TablePro** scheme and **My Mac**, then press `Cmd+R`. -### Building with a personal Apple team +### Static libraries -Copy the template and fill in your own team. `Configs/Secrets.xcconfig` is gitignored, so your -signing settings can never reach a commit and they survive regenerating the project. +`scripts/download-libs.sh` fetches two archives from the `libs-v1` GitHub Release: the macOS `.a` files (libmariadb, libpq, libduckdb, libmongoc and the rest) and the iOS xcframeworks, 257 MB downloaded and 621 MB on disk. None of it is in git, so a build that skips this step fails at link time. Every run, including one that downloads nothing, verifies `Libs/` against the checksums committed at `HEAD`. -```bash -cp Configs/Secrets.xcconfig.example Configs/Secrets.xcconfig -``` +### Signing with your own team + +Edit `Configs/Secrets.xcconfig`. The template ships the literal `YOUR_TEAM_ID`, which fails to sign the same way a missing file does: ``` TABLEPRO_DEVELOPMENT_TEAM = YOUR_TEAM_ID TABLEPRO_APP_BUNDLE_IDENTIFIER = com..TablePro ``` -The Debug configuration already uses `TablePro/TablePro.Debug.entitlements`, which drops iCloud -because free teams don't support it. Sync auto-disables at runtime. +A free personal team works. Debug builds sign against `TablePro/TablePro.Debug.entitlements`, which drops iCloud, so sync turns itself off at runtime. Plugin bundles and the MCP helper sign ad-hoc. The file is gitignored, so these settings never reach a commit and survive regenerating the project. Signing changed in the Xcode UI does not last: the next `scripts/generate-project.sh` discards it. + +To check it works, save a connection password, relaunch, and reopen the connection. The password should still be there. + +### The generated project -Don't change signing in the Xcode UI: the project is generated, so the next -`scripts/generate-project.sh` discards it. +`scripts/generate-project.sh` writes `TablePro.xcodeproj` and `TableProMobile/TableProMobile.xcodeproj` from their `project.yml` specs; pass `macos` or `ios` for one. Neither project is in git. Run it again after editing a `project.yml` or anything in `Configs/`, and after adding, moving or deleting a source file: XcodeGen collects sources at generation time. Never hand-edit a generated project. -To verify: save a connection password, relaunch, reopen. The password should still be there. +The Swift language mode comes from `SWIFT_VERSION` in `Configs/Base.xcconfig`, and each package under `Packages/` sets its own. Never pass `SWIFT_VERSION=` to `xcodebuild`: it reaches the packages too and reports their errors as yours. -Build: +## Building ```bash xcodebuild -project TablePro.xcodeproj -scheme TablePro -configuration Debug build -skipPackagePluginValidation ``` -Tests: +`-skipPackagePluginValidation` belongs on every `xcodebuild` invocation. Xcode writes the app to DerivedData; the command line writes it to `build/Debug/` inside the repo. + +Debug builds sign with the team from `TABLEPRO_DEVELOPMENT_TEAM`, and that matters locally too: a plugin in the user plugins directory has to carry the same team as the running app. + +### Every plugin + +The app scheme compiles only the plugins the app embeds. The `AllPlugins` aggregate compiles all of them, and is the check to run after changing anything under `Plugins/TableProPluginKit/`: ```bash -xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation +xcodebuild build -project TablePro.xcodeproj -scheme AllPlugins -destination 'platform=macOS' -skipPackagePluginValidation CODE_SIGNING_ALLOWED=NO ``` -## Code Style +`scripts/build-plugin.sh ` builds one signed, packaged bundle. Target names are the keys under `targets:` in `project.yml`, such as `OracleDriver`. + +### Clean builds + +| Reach for | When | +|---|---| +| `Cmd+Shift+K` in Xcode | The usual case | +| `xcodebuild -project TablePro.xcodeproj -scheme TablePro clean` | Same, from a terminal | +| `rm -rf ~/Library/Developer/Xcode/DerivedData` | Package resolution is stuck or a stale module keeps coming back. Clears every project on the machine | + +## Tests + +```bash +xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation -only-testing:TableProTests/ +``` -`.swiftlint.yml` is the source of truth, and CI runs it on every pull request. The short version: +Tests are Swift Testing. `-only-testing` matches the suite's Swift type name, or `/()` for one case. A filter that matches nothing still reports success, so check how many tests ran. `-only-testing:TableProUITests` runs the UI automation, whose suites subclass `UITestCase`. Run the suites that own the types you changed; a full run is not a gate. -- 4-space indent, 120-char lines -- Explicit access control (`private`, `internal`, `public`) -- No force unwraps (`!`) or force casts (`as!`) -- `String(localized:)` for user-facing strings -- OSLog only, no `print()` +## Code style -Before committing: +`.swiftlint.yml` is the source of truth, and CI runs it on every pull request: ```bash swiftlint lint --strict ``` +`--strict` turns warnings into errors, and several rules are warnings on purpose, so a plain `swiftlint lint` passes code CI rejects. Nothing lints during an Xcode build. The scope is `included: [TablePro]`, and that key beats a path on the command line: `swiftlint lint Plugins/` lints `TablePro/` and exits 0. Do not run `swiftlint --fix` unreviewed; check its diff. + +| Rule | Value | +|---|---| +| Indentation | 4 spaces; `case` not indented inside `switch` | +| Line length | Wrap at 120; SwiftLint warns at 180 and errors at 300 | +| Braces | K&R, `else` on the closing brace line | +| Wrapped arguments | One per line | +| Trailing commas, semicolons | None | +| `self.` | Only where the compiler needs it | +| Booleans | `is`, `has`, `can` or `should` prefix | +| Factory methods | `make` prefix | +| Acronyms | All caps, as in Apple's APIs: `SQLStatementGenerator`, `MCPAuditLogger` | +| Imports | One alphabetical block, no blank lines inside it | + +Beyond the linter: + +- Explicit access control, declared on the extension rather than on each member. +- No force unwraps (`!`) or force casts (`as!`). `force_try` is disabled in SwiftLint; the rule still applies. +- OSLog, never `print()`. +- Comments say why, never what. +- Storage goes through `AppStorageEnvironment.shared`: two custom rules fail on `applicationSupportDirectory` and `UserDefaults.standard` anywhere else, because a UI test would otherwise write into your own store. +- A type nearing a size limit splits into `TypeName+Category.swift` files under an `Extensions/` folder beside it, grouped by domain. `MainContentCoordinator` is the worked example. +- User-facing strings use `String(localized:)`, never with interpolation: `String(localized: "Preview \(name)")` builds a new key on every call and ships untranslated. Use `String(format: String(localized: "Preview %@"), name)`. Font names, database types and SQL keywords stay unlocalized. + +## Architecture + +Every database driver is a bundle the app loads at runtime, so nothing switches on database type and `DatabaseType` is a string-backed struct, not an enum. The UI is SwiftUI on an AppKit lifecycle, async work is Swift concurrency, and database connectivity is native C libraries linked from `Libs/`. + +| Component | Location | Role | +|-----------|----------|------| +| TableProPluginKit | `Plugins/TableProPluginKit/` | The plugin protocols and transfer types, ABI-resilient | +| PluginManager | `TablePro/Core/Plugins/` | Discovers, validates and loads plugin bundles | +| PluginDriverAdapter | `TablePro/Core/Plugins/` | Bridges a plugin driver to the app's `DatabaseDriver` | +| DatabaseManager | `TablePro/Core/Database/` | Owns connections and sessions; what views and coordinators talk to | +| MainContentCoordinator | `TablePro/Views/Main/` | One per connection session, split across `Views/Main/Extensions/` | +| DataChangeManager, SQLStatementGenerator | `TablePro/Core/ChangeTracking/` | Pending cell edits, and the statements Save turns them into | +| CompletionEngine | `TablePro/Core/Autocomplete/` | Ranks completions; `QueryCompletionAdapter` in `Views/Editor/` bridges it to the editor | +| MCP server | `TablePro/Core/MCP/` | Loopback-only HTTP; its contract is documented under [developers](https://docs.tablepro.app/developers/mcp-protocol) | + +| Store | Holds | +|---|---| +| Keychain | Connection passwords | +| UserDefaults | Preferences, favorites, recents | +| SQLite FTS5 | Query history | +| JSON files | Tab state, per-table filters | + +| Dependency | Source | Purpose | +|---|---|---| +| TableProEditor | `Packages/`, a fork of CodeEditSourceEditor and CodeEditTextView | The SQL editor | +| TableProGrammars | `Packages/` | Tree-sitter grammars | +| TableProCore | `Packages/` | Modules the macOS and iOS apps share | +| TableProOracle | `Packages/`, wraps a pinned fork of OracleNIO | Oracle wire protocol | +| Sparkle | SPM | Updates, EdDSA-signed | +| swift-certificates | SPM | X.509 types | +| Yams | SPM | YAML for project folder import | + +`Plugins/DamengDriverPlugin` links a Rust bridge from `Native/DamengBridge`. `Plugins/HanaDriverPlugin` runs `tablepro-hana-helper`, a Go executable from `Native/HanaBridge`, as one process per session and talks JSON over stdin and stdout. + +## Project layout + +``` +project.yml Xcode project definition (XcodeGen); the .xcodeproj is generated, not in git +Configs/ Shared build settings (.xcconfig), app version, signing template +TablePro/ App source: Core/, Views/, Models/, ViewModels/, Extensions/, Theme/ +Plugins/ .tableplugin bundles and the TableProPluginKit framework +Packages/ Local SwiftPM packages +Native/ Rust and Go bridges for the Dameng and SAP HANA plugins +TableProMobile/ iOS app, widget extension, and its own project.yml +Libs/ Prebuilt static libraries, downloaded by script, not in git +TableProTests/ Unit tests +TableProUITests/ UI automation +scripts/ Build, release and library scripts +``` + +`Packages/TableProCore/Sources/TableProPluginKit` is a symlink to `Plugins/TableProPluginKit`. Edit the files under `Plugins/` only. + ## Commits [Conventional Commits](https://www.conventionalcommits.org/). Pull requests are squash-merged with the PR title as the commit subject, so the title is what lands on `main`: at most 72 characters, checked by CI. Explain the change in the PR description, not in commit bodies. @@ -75,52 +178,34 @@ swiftlint lint --strict ``` feat: add CSV export for query results fix: prevent crash on empty query result -docs: update keyboard shortcuts page ``` -## Branch Naming +## Branch naming Branch off `main`: - `feat/add-cassandra-support` - `fix/query-editor-crash` -- `docs/update-keyboard-shortcuts` -## Pull Requests +## Pull requests One logical change per PR, described with the template the PR form opens with. Before you open it: - Tests added or updated. - `CHANGELOG.md` updated under `[Unreleased]` (skip for a fix to something still unreleased). Leave the credit off: the release adds `(#123 by @you)` to every entry from your pull request. -- Docs updated in `docs/` if the change affects user-facing behavior. +- User-facing behavior documented in a paired pull request in [TableProApp/docs](https://github.com/TableProApp/docs) that links this one. It merges after this one does, because its CI checks the app's `main`. - User-facing strings localized. - SwiftLint clean. -## Project Layout +On every pull request, `macos-tests.yml` builds every plugin, loads each built bundle, then runs the package tests, `TableProTests` and the UI tests in three shards; its first job skips the suites when nothing relevant changed. `ios-tests.yml` covers the iOS app, `swiftlint.yml` the linter, `pr-title.yml` the title, and `repo-hygiene.yml` runs actionlint, shellcheck and the repo's source-scanning checks. -``` -project.yml Xcode project definition (XcodeGen); .xcodeproj is generated, not in git -Configs/ Shared build settings (.xcconfig), app version, secrets template -TablePro/ App source (Core/, Views/, Models/, ViewModels/, Extensions/, Theme/) -Plugins/ .tableplugin bundles + TableProPluginKit framework -TableProMobile/ iOS app, widget extension, and its own project.yml -Libs/ Pre-built static libraries (downloaded via script, not in git) -TableProTests/ Tests -docs/ Mintlify docs site -scripts/ Build and release scripts -``` - -## Adding a Database Driver +## Adding a database driver -Drivers are `.tableplugin` bundles loaded at runtime. Create a new bundle under `Plugins/`, implement `DriverPlugin` + `PluginDatabaseDriver` from `TableProPluginKit`, and add the target to `project.yml`. - -Full guide: [docs/development/plugin-registry](https://docs.tablepro.app/development/plugin-registry) +Drivers are `.tableplugin` bundles loaded at runtime. Create a new bundle under `Plugins/`, implement `DriverPlugin` and `PluginDatabaseDriver` from `TableProPluginKit`, and add the target to `project.yml`. Guides: [Plugin Development](https://docs.tablepro.app/development/plugin-development) and [Plugin Registry](https://docs.tablepro.app/development/plugin-registry). ## Translating -Translate through Xcode's export and import, one XLIFF file per language. Never edit -`Localizable.xcstrings` by hand: Xcode is the only tool that writes it, so a translation diff holds -your language and nothing else. +Translate through Xcode's export and import, one XLIFF file per language. Never edit `Localizable.xcstrings` by hand: Xcode is the only tool that writes it, so a translation diff holds your language and nothing else. ```bash xcodebuild -exportLocalizations -project TablePro.xcodeproj -scheme TablePro \ @@ -130,16 +215,24 @@ xcodebuild -importLocalizations -project TablePro.xcodeproj -localizationPath Lo scripts/localization.py status # what is translated, per language ``` -The export builds the app first, so run `scripts/download-libs.sh` and -`scripts/generate-project.sh` once before it. Pass an existing language code to fill gaps, or a new -one to start a language. Commit the catalog, not the export: `Localization/` is ignored. The iPhone -and iPad app has a catalog of its own: run the same commands on -`TableProMobile/TableProMobile.xcodeproj` with the `TableProMobile` scheme. +The export builds the app first, so run `scripts/download-libs.sh` and `scripts/generate-project.sh` once before it. Pass an existing language code to fill gaps, or a new one to start a language. Commit the catalog, not the export: `Localization/` is ignored. The iPhone and iPad app has a catalog of its own: run the same commands on `TableProMobile/TableProMobile.xcodeproj` with the `TableProMobile` scheme. + +Strings from plugins and from packages under `Packages/` live in the app's catalog too, marked "Managed Manually" so that Xcode keeps them. `scripts/localization.py plugins --add` adds new ones. + +## Troubleshooting -Strings from plugins and from packages under `Packages/` live in the app's catalog too, marked -"Managed Manually" so that Xcode keeps them. `scripts/localization.py plugins --add` adds new ones. +| Symptom | Fix | +|---|---| +| `xcodegen: command not found` | `brew install xcodegen`, then check `xcodegen --version` reports 2.46.0 | +| `TablePro.xcodeproj` does not exist | Run `scripts/generate-project.sh` | +| A new source file does not compile | Run `scripts/generate-project.sh` | +| `Undefined symbols for architecture arm64` | `Libs/` is empty or stale: `scripts/download-libs.sh`, or `--force` to overwrite | +| `Libs does not match the checksums committed in git.` | Re-run `scripts/download-libs.sh --force`. A library rebuilt on purpose is published as [`RELEASING.md`](RELEASING.md#static-libraries) describes | +| `Build input file cannot be found: '…/tablepro-hana-helper'`, or the helper has no slice for your architecture | Run `scripts/build-hana.sh` with no argument, which builds both architectures | +| Signing errors after a fresh clone | `Configs/Base.xcconfig` defaults to the maintainer's team and includes `Secrets.xcconfig` optionally, so a build without it still runs and fails to sign. Create the file with your team | +| Package resolution fails | `Cmd+Shift+K` and reopen Xcode; then `rm -rf ~/Library/Developer/Xcode/DerivedData` | -## Reporting Bugs +## Reporting bugs Open a [GitHub issue](https://github.com/TableProApp/TablePro/issues) with: diff --git a/docs/development/releasing.mdx b/RELEASING.md similarity index 94% rename from docs/development/releasing.mdx rename to RELEASING.md index d7d5adb315..a355731198 100644 --- a/docs/development/releasing.mdx +++ b/RELEASING.md @@ -1,14 +1,12 @@ ---- -title: Releasing -description: Release builds, notarization, DMG packaging, static libraries, and the release tag ---- +# Releasing `scripts/build-release.sh` is the whole release build. It signs every binary in the bundle itself, each plugin, helper and framework first and the app bundle last, so signing again by hand afterwards undoes its work. -Everything on this page needs credentials the maintainer holds. A contributor building from source -wants [Building](/development/building) instead. +Everything here needs credentials the maintainer holds. A contributor building from source wants +[CONTRIBUTING.md](CONTRIBUTING.md#building) instead. The `release` skill in `.claude/skills/release` +runs a whole release, from the version bump to the announcement, on top of what this file describes. ## Release builds @@ -160,5 +158,5 @@ build is reached by shipping the fix, not by the withdrawal. ## Plugins Driver plugins release on their own `plugin-*-v*` tags through a separate workflow, and the registry -decides which binary a user gets. [Plugin Registry](/development/plugin-registry) covers the +decides which binary a user gets. [Plugin Registry](https://docs.tablepro.app/development/plugin-registry) covers the manifest, the tag names, and the bulk re-release after a PluginKit ABI change. diff --git a/TableProTests/Core/Plugins/PluginMetadataRegistryTypeCountTests.swift b/TableProTests/Core/Plugins/PluginMetadataRegistryTypeCountTests.swift index 50254cdda6..6255a8ead0 100644 --- a/TableProTests/Core/Plugins/PluginMetadataRegistryTypeCountTests.swift +++ b/TableProTests/Core/Plugins/PluginMetadataRegistryTypeCountTests.swift @@ -8,7 +8,7 @@ import Foundation import Testing /// The number of engines TablePro offers is published in three places that cannot see each other: -/// this registry, `docs/snippets/driver-counts.mdx`, and the marketing site. Nothing at runtime +/// this registry, `snippets/driver-counts.mdx` in TableProApp/docs, and the marketing site. Nothing at runtime /// reconciles them, and by August 2026 they read 28, 27 and 25 at once. /// /// The answer is 37, and the reason it once read 28 is worth keeping. Turso is served by @@ -19,9 +19,9 @@ import Testing /// being asserted on top of it. Do not "correct" it downwards by deleting that entry. /// /// A failure here means a driver was added or removed, which is a deliberate act. Update -/// `expectedTypeIds`, then `docs/snippets/driver-counts.mdx`, the `and N more` count in the -/// `docs/index.mdx` frontmatter, and the engine count on tablepro.app in the same change. -/// `docs/scripts/check-docs-against-source.py` reads the registry and holds the docs half. +/// `expectedTypeIds`, then, in TableProApp/docs, `snippets/driver-counts.mdx` and the `and N more` +/// count in the `index.mdx` frontmatter, and the engine count on tablepro.app. The docs repository's +/// `scripts/check-docs-against-source.py` reads this registry and holds the docs half. /// /// The count is taken from the built-in defaults rather than from `allRegisteredTypeIds()`. /// Both answer 37 under XCTest, where no plugin bundle ever loads, but the registry is a diff --git a/TableProTests/Views/Settings/SettingsPaneURLIdentifierTests.swift b/TableProTests/Views/Settings/SettingsPaneURLIdentifierTests.swift index 2a2d8f541e..35a63a187c 100644 --- a/TableProTests/Views/Settings/SettingsPaneURLIdentifierTests.swift +++ b/TableProTests/Views/Settings/SettingsPaneURLIdentifierTests.swift @@ -7,7 +7,7 @@ import Testing struct SettingsPaneURLIdentifierTests { - // Published in docs/developers/url-scheme.mdx. Changing a value breaks links in other apps. + // Published at docs.tablepro.app/developers/url-scheme. Changing a value breaks links in other apps. private static let published: [SettingsPane: String] = [ .general: "general", .appearance: "appearance", diff --git a/docs/.mintignore b/docs/.mintignore deleted file mode 100644 index 154b3e31f6..0000000000 --- a/docs/.mintignore +++ /dev/null @@ -1,10 +0,0 @@ -# Mintlify automatically ignores these files and directories: -# .git, .github, .claude, .agents, .idea, node_modules, -# README.md, LICENSE.md, CHANGELOG.md, CONTRIBUTING.md - -# Draft content -drafts/ -*.draft.mdx - -# Contributor style guide, not a docs page -STYLE.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 430476fbac..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# TablePro Documentation - -Source files for the [TablePro documentation site](https://docs.tablepro.app), powered by [Mintlify](https://mintlify.com). - -## Structure - -``` -docs/ -├── index.mdx # Introduction -├── quickstart.mdx # Getting started guide -├── installation.mdx # Installation instructions -├── changelog.mdx # Release changelog -├── databases/ # Database connection guides -├── features/ # Feature documentation -├── customization/ # Settings and customization -├── integrations/ # Using TablePro with other tools -├── developers/ # Build an integration, API reference, directory listing -└── development/ # Contributing -``` - -## Local Development - -Install the [Mintlify CLI](https://www.npmjs.com/package/mint) and start the dev server: - -```bash -npm i -g mint -mint dev -``` - -Preview at `http://localhost:3000`. - -## Deployment - -Changes pushed to the default branch are deployed automatically via the [Mintlify GitHub app](https://dashboard.mintlify.com/settings/organization/github-app). - -## License - -The pages in this directory are covered by the repository's [AGPLv3 license](../LICENSE), like the rest of TablePro. The TablePro name and logo, including `logo/` and `favicon.png`, are trademarks of Ngo Quoc Dat and are not licensed under it. Screenshots of other products belong to their owners. diff --git a/docs/STYLE.md b/docs/STYLE.md deleted file mode 100644 index e0e3296250..0000000000 --- a/docs/STYLE.md +++ /dev/null @@ -1,351 +0,0 @@ -# How the TablePro docs are written - -The rules a page has to meet. `docs/scripts/check-writing-style.sh` and -`docs/scripts/check-docs-against-source.py` enforce the mechanical half in CI; the rest is here -because no grep can settle it. - -Most of this was learned the hard way, from defects that shipped. Where a rule has a scar, the scar -is written down with it. That is deliberate: a rule with a reason survives, and a rule without one -gets argued away. - ---- - -## 1. What the docs are for - -A reader arrives with a task and leaves when they can do it. Everything else is cost. - -The single most useful test on any sentence: **would the reader do something different if this -sentence were deleted?** If not, delete it. - -### The corpus has one origin defect, and it explains most of the rest - -These docs grew one feature PR at a time. Each PR appended a paragraph to a page in the same voice -it used for its CHANGELOG entry, and no commit ever rewrote a page whole. `features/tabs.mdx` and -the v0.67.0 release notes still share the same `app.orders` / `staging.orders` sentence. - -Release notes answer *what is new*. Docs answer *how do I do this*. They are different genres, and -116 releases regrouped by topic produce exactly the exhaustive, product-centred, focus-free prose -this corpus is being rewritten out of. - -**So: when you touch a page, rewrite the section for someone doing a task. Never append a paragraph -describing your change, and never copy a sentence out of the changelog.** - ---- - -## 2. Voice - -Write like the developer who built the feature explaining it to the developer about to use it. - -- **Second person, imperative.** "Click **Test Connection**." Never "we", never "let's", never - "users can". -- **Present tense.** "The driver reconnects", not "will reconnect". -- **The product is not the subject.** 409 sentences in this corpus opened `TablePro `, and 26 - of 33 database pages opened with the same five words. Write from the reader's side, or from the - thing itself. "Indices are tables, documents are rows" beats "TablePro connects to Elasticsearch". -- **No design rationale.** 464 clauses in this corpus explain *why the app is built this way* to a - reader who only needs to know what to click. - - > The prefix test: if the clause still reads correctly after you put "we did it this way because" - > in front of it, cut it. - - ``` - no With a single tab there is no strip, so a window that behaves the way it - always did gains no chrome. - yes One tab, no strip. - ``` - - Consequence is not rationale. "Value filters run on the rows already loaded, so they cover the - current page only" is the point of the sentence; keep it. - -- **No internals on user-facing pages.** No class names, bundle identifiers, or activity types under - `features/`, `databases/`, `connections/`, `customization/`. A page once opened with - `NSUserActivity`. They are fine under `development/` and `developers/`; an `integrations/` page - may show a bundle ID only inside a command the reader runs. -- **Repair a hedge with a measurement.** Not "large results may take some time". Say the number, or - name the condition. - -### Banned - -On top of the CLAUDE.md list (seamless, robust, comprehensive, leverage, and the rest): - -`simply` · `just` (meaning only) · `easy` · `in order to` · `please note` · `and/or` · `e.g.` · -`i.e.` · `above` / `below` as document position · `allows you to` · `enables you to` · `lets you` · -`helps you` · **`you can`** - -`you can` is the same sentence with the subject moved. "You can right-click a column header and -choose **Filter with column**" is "Right-click a column header and choose **Filter with column**". - -**No em dashes. Anywhere.** - ---- - -## 3. Rhythm, and the trap on the other side of it - -Sentence length in this corpus is already healthy: mean 11.8 words, a third under seven. Rhythm is -not the problem and does not need fixing. - -**The problem is that every page makes the same move.** That is what "reads like AI" means here, and -it is why the first rewrite pass failed: - -> It killed the old formula completely. Zero of 46 pages still opened with the product name. It -> replaced it with a new one: 13 of 46 opened with a numeral, four of those on consecutive database -> pages; six ended on a variant of "there is nothing to install"; three reached for the same -> aphoristic predicate ("is the floor", "is the whole life of the token", "is the way in"); and two -> pages had become the same paragraph with different nouns. - -Every one of those collisions was invisible from inside the file being edited and obvious from three -pages away. - -> **Check variety across the reading order, not per page.** Before shipping a batch, read its -> openings consecutively, in sidebar order. If two rhyme, rewrite one. - -The pass after that one failed the same way in a third costume. Told not to end on "nothing to -install", it moved the negation to the front instead: a third of the batch opened on *no*, *not*, -*never*, *nothing*, or *cannot*. Two adjacent transport pages came out as the same page with -different nouns, down to a byte-identical closing sentence, because two agents wrote them -independently and neither could see the other. - -So the rule is not "avoid last time's tic". It is this: - -> **If a sibling page already carries the sentence you are about to write, you do not need a -> different wording. You need a snippet.** Rewording a duplicated fact eleven ways is how the corpus -> got eleven templates for "this driver downloads on first use". `docs/snippets/` is where a shared -> fact lives; `registry-plugin.mdx` and `helper-port.mdx` are the two that exist. - -Vary the shape deliberately: an imperative, a plain declarative, a constraint, a number, a full -complex sentence. **At most one page in five may open on a sentence fragment.** - ---- - -## 4. Opening a page - -Mintlify prints the frontmatter `description` directly under the H1, so the first body sentence must -not restate it. The description is read by someone deciding whether to open the page. The first -sentence is read by someone who already did. **They carry different facts.** - -Lead with the entry point, the constraint, or the fact that changes a decision. - -``` -no Browse and edit columns, indexes, foreign keys, triggers, and DDL for any table. - (the description, minus four words) -yes This is a DDL editor with a grid in front of it. Rename a column, add an index, - change a key, and what you get is a pending ALTER TABLE you can read before it runs. -``` - -The model in this corpus is `switching.mdx`: *"Most of a migration is one dialog."* Six words, does -not restate the title, has a point of view. - ---- - -## 5. Structure - -| Content | Use | -|---|---| -| Two or more actions in sequence | ``. Never a bare numbered list, never numbered headings | -| Parallel items, no order | Bullets, 40 words maximum each | -| Items with three or more properties | A table | -| One item with properties | A sentence | -| A chain of reasoning | Prose. Bulleting an argument hides the causal link | -| Six or more failure modes | H3 per error string | -| Same content, different variant | `` or `` | - -**A `**Label**:` paragraph is never structure.** 454 lines in this corpus open that way. A label plus -a body is an H3, a table row, or a ``. `databases/postgresql.mdx` had a `## Features` -section built from eight of them, one 620 characters long, and none appeared in the table of -contents, so a reader could not find array editing from the sidebar. - -**Never restate a table in prose.** After a table, write only what the table cannot carry. -`features/safe-mode.mdx` had a six-row table followed by six subsections saying "Same as X, but Y". - -**Name the default.** Whenever a page enumerates three or more options, one sentence says which one a -normal reader should pick and who should pick differently. Exhaustiveness is not neutrality. - -### Headings - -Sentence case. Capitals only for acronyms, names with an interior capital (`MySQL`, `PGlite`), and -headings that are literally a control or menu item in the app. `docs/scripts/proper-nouns.txt` holds -the product names whose second word is an ordinary English word: Google **Cloud** SQL, DynamoDB -**Local**. - -Stop at H3; Mintlify's table of contents ignores H4. Never write an H1, the frontmatter `title` is -the H1. Never `## Features`: it fitted 17 pages, so it is not a heading. - ---- - -## 6. Page shapes - -### A database page - -Fixed order for the sections that exist. **Engine-specific H2s are allowed** between -`## Connection URL` and `## Limitations`, and this matters: an earlier draft of this guide froze the -section list, which would have destroyed `beancount.mdx` (Source locations, Includes, BQL) and -`etcd.mdx`, the two best pages in that directory. - -``` -Quick setup · Connection settings · Connection URL · Authentication · · -SSL/TLS · Limitations · Troubleshooting · Related -``` - -Every database page answers all of these, even when the answer is "none". A page missing one fails -review however well it reads. - -1. Minimum server version, or "any" -2. Where the driver comes from: bundled, or the registry, with the plugin's exact name -3. Default port, or "no port" and what replaces it -4. Whether the Database field is required to connect -5. Default SSL mode and what it falls back to -6. What a tab bound to a second database does -7. At least one limitation. If an engine genuinely has none, say "No driver-side limits" in one - line. Never omit the heading - -### A limitation - -Three slots, fixed order: **what you cannot do, what happens instead, what to do about it.** Never -why. - -``` -no Arrays of bytea or composite types keep the plain text editor, since their - quoting cannot round-trip through a per-element list. -yes The list editor covers arrays of simple types and of JSON. bytea[], composite and - multi-dimensional arrays open the text editor instead: edit the {…} literal directly. -``` - -### An error entry - -The heading is the verbatim string the server or the app produced, casing and punctuation preserved, -variable parts collapsed to `…` inside the original quoting. The body is three lines, in order: what -it means, what to check with the file or setting named, what to change. - -Never open with "This error occurs when". The heading is the name. - -`**Auth failed**` is not searchable, because PostgreSQL has never emitted that string. -`FATAL: password authentication failed for user "…"` is what the reader has in their clipboard. - -### A capability that varies by engine - -Asserted in exactly one place: the feature page, as a table, one row per engine, no prose gloss. A -database page states what the capability does on **that** engine and links. It never lists which -other engines have it, because nobody will remember to edit it when the 27th engine gains the -feature. - ---- - -## 7. Naming what is on screen - -The shipped `String(localized:)` is the name. One term per element. - -| Element | Term | Source | -|---|---|---| -| Filter row area above the grid | filter bar | `ViewMenuBuilder.swift` "Show Filter Bar" | -| Narrow strip on the leading edge | connections strip | `ViewMenuBuilder.swift` "Show Connections" | -| Object list on the left | sidebar | | -| Right pane | inspector | | -| Launch window | welcome window | never "welcome screen" | -| Disabled control | dimmed | never "greyed out" | -| Pointer | pointer | "cursor" is the text insertion point only | - -Apple's nouns, not Microsoft's: **pane**, **dialog**, **sidebar**, **inspector**, **menu bar**, -**System Settings**. - -Verbs: **click** a button, **choose** a menu item, **select** a row or a checkbox, **press** a key, -**deselect** a checkbox. Never "click on", never "uncheck". - -**Menu paths** are one bold run with ` > `: `**File > Import from Other App…**`, never -`**File** > **X**`. Verify the leaf against `TablePro/Core/Menu/*MenuBuilder.swift` every time. Eight -paths were wrong at once because the Database menu shipped and nobody re-checked. - -**Shortcuts** in backticks, modifiers spelled out, joined with `+`: `` `Cmd+Option+F` ``. Never bold, -never a glyph. Apple's `Command-K` is deliberately rejected: the app's own menus render `Cmd`. - -**Labels** are bold and never in backticks. Drop a trailing ellipsis when instructing -(`Choose **Save as**`), keep it when the label alone is ambiguous (`**New Connection…**`). - ---- - -## 8. Callouts - -By consequence, never by tone. A reader who learns the yellow box and the blue box mean the same -thing stops reading both. - -| | Means | -|---|---| -| `` | Irreversible. Data loss, dropped objects, corrupted files | -| `` | This will fail, cost money, or silently produce wrong output | -| `` | A support fact: which engines, which versions, which tier | -| `` | An aside the reader can skip without breaking anything | -| `` | An optional shortcut that makes the task faster | -| `` | What success looks like at the end of a step | - -**A callout is never load-bearing.** If deleting it leaves the page wrong, it is not a callout. - -Before the reclassification, a `` carried silent MongoDB round-trip corruption while a -`` carried which builds accepted a URL format in 0.38. - ---- - -## 9. Numbers, and the rule that keeps them true - -**A number that exists in the source code is owned by exactly one page. Every other page links to -it.** If linking reads badly, the sentence is wrong, not the rule. - -Every count contradiction this corpus has shipped came from one fact typed on two pages: the driver -count, the database count, the AI provider count, the Settings tab count, the PluginKit ABI version. - -Three claims rot faster than anything else and are checked in CI against the source that defines -them: menu paths, keyboard shortcuts, and the PluginKit ABI. All three were wrong at once in -August 2026. - -**Verify before you write, and verify again before you commit.** A rewrite pass that trades a prose -problem for an accuracy problem is strictly worse than doing nothing. - -The second half of that rule is about *ordering*, and it is the half that gets skipped. A page -written early in a task states what the code did that morning. A review later in the same branch -changes the code. Nobody re-reads the page, and the branch ships a table that was true when it was -typed. That is how a capability table claimed Redshift, CockroachDB and PGlite had stored -procedures: they are separate driver subclasses, a later commit removed their capability flags, and -the page still carried the row. **Write the page last, or re-check every claim against the source -immediately before you commit.** Capability tables, supported-engine lists and default values rot -fastest. One turned "A filled yellow star marks a favorite" into "Starred -tables turn yellow"; the star turns yellow, not the table. Another asserted the MCP server grants -anonymous read access on stock defaults, which is the exact opposite of what -`MCPCompositeAuthenticator.swift` does, on the page whose entire job is to be trusted. - ---- - -## 10. Images - -Alt describes the picture. The caption adds what the prose does not carry. **They may never be -equal**; 21 pairs once were. If you cannot write an alt that differs from the caption, delete the -caption. - -Every `` holds either zero images or a light and dark pair at identical dimensions: - -```mdx - - What is actually visible - What is actually visible - -``` - ---- - -## 11. Before you open the pull request - -```bash -cd docs -bash scripts/check-writing-style.sh -python3 scripts/check-docs-against-source.py -mint validate -mint broken-links --check-anchors --check-redirects -mint a11y -``` - -Then the two things no script can do: - -1. **Read your batch's openings consecutively.** If two rhyme, rewrite one. -2. **Check every fact you wrote against the source**, not against the page you were editing. The - page you were editing is where the error came from. - -And when you add a check to CI, **test it by reintroducing the bug it exists to catch.** The first -version of the shortcut check joined table rows by title, and the docs word one row differently from -the action name, so it passed green on a live bug. Two of the first three checks were silent. diff --git a/docs/changelog.mdx b/docs/changelog.mdx deleted file mode 100644 index 5ea4aef2c7..0000000000 --- a/docs/changelog.mdx +++ /dev/null @@ -1,3735 +0,0 @@ ---- -title: "Changelog" -description: "Product updates and announcements for TablePro" -rss: true ---- - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-80) - - ### New Features - - - **Google Cloud IAM sign-in**: Sign in to MySQL and PostgreSQL on Cloud SQL with Application Default Credentials or a service account key, without the Cloud SQL Auth Proxy, by [@i87ce](https://github.com/i87ce) - - **MySQL and MariaDB Unix socket**: Connect through a local Unix socket in place of a host and port - - **Socket in URLs**: A `socket` parameter in `mysql://` and `mariadb://` URLs and `tablepro://import` links - - **Map preview**: A geometry value opens on a map in the row inspector, with **Text** and **Map** segments, **Fit to Geometry** and **Open in Window** - - **Selection statistics**: Sum, Average and Count of the selected cells in the status bar. Click it for Minimum, Maximum and **Copy** - - **Select Column**: In the column header menu - - **Selection in the JSON and PHP viewers**: Select rows and text in the viewer trees. `Cmd+C` copies the selected values - - **Links in JSON**: http, https and mailto values are clickable in the JSON and PHP viewer trees and the inspector's JSON tab - - **Color swatches**: Beside hex, `rgb()` and `hsl()` values in the JSON and PHP viewer trees and the inspector's JSON tab - - **Move Tab to Connection…**: `Ctrl+Option+Cmd+C`, in the **Database** menu and the tab menu. Takes a query tab to another connection - - **Sidebar + button**: At the foot of the sidebar, for **New Table**, **New View**, **New Schema**, **New Database** and **New Folder** - - **Favorites + button**: At the foot of the Favorites sidebar, for **New Query**, **New Favorite**, **New Folder** and **Add Linked SQL Folder** - - **New Folder**: In the **Database** menu - - **Comment in Create Table**: A Comment field for the new table - - **Table comment in Structure**: Edit the table comment in the **Structure** tab. It saves with the other structure changes - - **Edit Comment…**: For MySQL, MariaDB, ClickHouse, CockroachDB, Redshift, SQL Server, Oracle, DuckDB and other engines with table comments - - **Custom icons**: Pick an icon for a connection or a group from a searchable set of symbols, on Mac and iPhone. Icons sync through iCloud - - **Edit Group…**: In the welcome window, with name, icon, color and parent group in one sheet - - **Saved queries in exports**: Connection exports carry saved queries with their folders and keywords, with an option to add global saved queries - - **Saved queries on import**: Imported with connections from TablePro files, TablePlus, Sequel Ace, DBeaver, DataGrip and Beekeeper Studio - - **Filter Schemas…**: In the **View** menu. Choose the schemas listed in the sidebar, its search, Open Quickly, export and the column type picker - - **Settings links**: `tablepro://settings` and `tablepro://settings/` open Settings on a pane - - **Help > Integrations**: Opens the integrations directory on tablepro.app - - **MCP connection details**: Group, tags and color in the MCP connection list and on the AppleScript connection class - - **Pairing links**: Take `state` and `response_mode`, and send a standard `code` to any redirect - - **`connections:display` scope**: A pairing scope, off by default, that lets a launcher list connections hidden from AI - - ### Improvements - - - **Settings > MCP** in place of **Settings > Integrations**, and **MCP Activity…** in place of **TablePro > Integrations…** - - **Settings > Sync** names what stops iCloud Sync, such as full iCloud storage or a signed-out account, with the action that clears it - - Value filters stay through sorting, paging and the filter bar, and clear when the tab shows another table or query - - **Save**, **Refresh** and sorting keep the selected rows, matched by primary key - - Connection files keep the full path of nested groups, and import rebuilds that path - - Connection files written by this version, Team Catalog files included, open only in this version or later - - **Keep Existing, Add Queries** in place of **Skip** for a duplicate connection on import - - Importing connections asks before adding their startup SQL, the same way it asks about tunnel commands - - Deprecated: pairing links without `response_mode`, which wrap the code in `context` for `raycast://` redirects - - ### Security - - - Duplicating a connection set to **Blocked** for external access making the copy readable over MCP - - MCP completions offering a connection for up to 15 seconds after it was set to **Never** or **Blocked** - - Generated SQL quoting broken by a combining mark after a quote in DuckDB names and a backslash in ClickHouse names - - A database link's SQL filter shown cut to 300 characters, and connecting to a trusted database before asking about it - - A database link through an SSH tunnel to `127.0.0.1` reusing a local database's **Always Allow** for any SSH server - - Pairing accepting a redirect that opens TablePro, a web browser, a database URL or another remote resource - - A refused or unrecognized deep link writing its query values, SQL included, to the system log - - AI chat on an open connection ignoring a change to its AI policy until reconnect - - ### Bug Fixes - - - PostgreSQL slow to connect, browse, search, commit and switch schema on databases with many schemas - - Export dialog and column type picker slow to open on PostgreSQL databases with many schemas - - Database switcher slow to open on servers with many or large databases - - Table sizes read on every table switch while the inspector was closed - - Primary keys missing for PostgreSQL roles that can only read a table - - Materialized views and foreign tables listed in PostgreSQL schemas the role cannot read - - **Table Info** showing -1 rows for a PostgreSQL table that was never analyzed - - MCP database statistics reporting 0 tables for another PostgreSQL database, or failing when one refuses CONNECT - - Opening a PostgreSQL table on iPhone and iPad slow on databases with many tables - - Value filter cleared by **Save**, **Refresh**, **Restore Previous Values** and hiding a column - - Value filter lost on a background tab whose rows were freed, or by choosing the result already shown - - **Save** in a query tab running the statement at the caret instead of reading its result again, and losing the sort - - Sort arrow left on a query tab's columns after **Run** - - Header sort on rows returned by an `INSERT`, `UPDATE` or `DELETE` running that statement again - - Header sort on a pinned result, or on one of several results, dropping the other results - - **New View…** offered on Redis and other engines without views - - Column comments typed in **Create Table** dropped on PostgreSQL, SQL Server, Oracle, DuckDB and Teradata - - DuckDB **Create Table** failing on an auto-increment column - - Oracle **Create Table** ignoring **Auto Increment** - - DuckDB errors shown as "Unknown DuckDB error" instead of the message DuckDB returned - - **Create Table** and structure saves failing on Teradata when they run more than one statement - - BigQuery table comments showing partition, labels and timestamps instead of the description - - Teradata **Table Info** counting every row of the table each time it opened - - **Create Table** keeping a draft that could only fail again once the table existed and a later index or comment failed - - An AWS IAM sign-in to MySQL failing to reconnect after an idle release, and **Stop** failing, once its token expired, by [@i87ce](https://github.com/i87ce) - - MySQL and MariaDB connections with SSL Disabled still using TLS when a client certificate or key was saved - - SQL Server empty strings and binaries shown as NULL, reverting edits on **Save** in a table without a primary key - - Saves to a SQL Server or Oracle table without a primary key reporting success when no row matched - - SQL Server `datetimeoffset` values shown, copied and exported without their offset - - SQL Server `geography`, `geometry` and `hierarchyid` values shown as NULL - - A possible crash when a result holds a SQL Server `sql_variant` value - - iCloud Sync errors shown as raw CloudKit text with record IDs, on Mac and iPhone - - Every pending change resent on each edit and app switch while iCloud storage is full or iCloud is signed out - - Synced and a new Last Synced time after a sync whose download failed - - A restricted or not yet ready iCloud account told to sign in - - Signing in to another iCloud account on a Mac reusing the previous account's sync state - - TablePro data deleted in iCloud settings uploaded again without asking - - Sync not resuming on its own when the network comes back - - iPhone and iPad downloading the same changes again on every sync while uploads failed - - **Apply** and Return in **Filter Values…** keeping every value after a search narrowed the list - - Return in **Filter Values…** hiding every row when no value was selected - - Clicks on the **Select All** label in **Filter Values…** doing nothing - - "Values from 1 loaded rows" in **Filter Values…** - - The connection form, Acknowledgements and MCP Activity sidebars dragged shut with no way to bring them back - - The **Users & Roles** list and the server dashboard's slow queries staying hidden for good once dragged shut - - A JSON cell edit made in the popover lost on **Save** after **Open in Window** - - **Copy Value** on a JSON object or array copying only the rows a tree filter left on screen - - The JSON tree filter matching array positions such as `[1]`, counts such as `{2 keys}` and the `…` row - - "JSON Too Large" in Tree mode for a minified value under the 100,000 character cap - - Shift-click or Cmd-click after clicking a cell selecting only the cell clicked last - - Cmd-click on a cell inside a selected block not deselecting it - - MySQL and MariaDB DECIMAL columns in results treated as text in charts, sorting and JSON copy - - **Column Statistics** showing very small values as 0 and rounding long integers - - Copying more than 50,000 rows of selected cells cutting the text without saying so - - Disclosure arrows in the inspector's JSON tab doing nothing under a filter, then collapsing rows once it was cleared - - **Copy Key Path** not quoting keys such as `a.b` and `content-type`, giving a wrong or unparseable path - - "Invalid JSON" in Tree mode for a NULL or empty JSON cell - - `{1 keys}` and `[1 items]` for a one-member object or array in the JSON and PHP viewer trees - - A broken character before the `…` of a long string in the JSON and PHP viewer trees - - Binary geometry values shown as garbled text in the row inspector, where typing replaced the bytes with text - - Binary values saved as text after **Set NULL** or **Set DEFAULT** was cleared in the row inspector - - Crash when a spatial or JSON column held a value with thousands of nested brackets - - Parts of a multi-part geometry missing from the map with nothing saying so - - Geographic SRIDs such as 4258, 4283 and 7844 refused by the map as projected coordinate systems - - GeoJSON with a lowercase type name such as `point` not drawn on the map - - A column holding only curved geometry losing the Map view, and `CIRCULARSTRINGM` called unreadable instead of named - - Closing the only tab of a connection first opened from a link or the query history deleting its saved tabs - - A reopened tab disappearing when its connection's saved tabs finished loading - - Picking a connection that was still connecting restarting its connect - - A `.sql` file opening on the connection that connected last instead of the one on screen - - Opening an already open `.sql` file, linked favorite, query link or database file not showing its connection and tab - - Picking a database in the query editor rerunning the statement at the caret, writes included, without asking - - "Connection Failed: Cancelled by user." after declining a connect prompt from **Switch Connection** - - A database link with a filter replacing the filters of a tab already showing that table - - A cleared group, tag, AI rule, startup command or SSH profile, or a group's old parent, returning after iCloud sync - - Exported connections taking the color of another group with the same name - - **Copy TablePro Link** dropping the connect and query timeouts, and new tags after the first - - The toolbar keeping a connection's old name after it was renamed while connected - - iOS widget icons differing from the app for Dameng, Snowflake, Beancount, SurrealDB and Kafka connections - - No icon for a connection to a database type no installed plugin describes - - MCP clients on the Mac locked out for 5 minutes after five requests sent without a token - - An expired MCP token answered as unknown, without the documented `-33008` code and `token expired` challenge - - A `%2B` in a pairing redirect's own query arriving at the callback as `+` - - Connections exported from iPhone, or with an SSL mode TablePro did not recognize, imported with SSL off - - Saving or importing connections while the connection library could not be read replacing every saved connection - - Import counting connections it could not save and storing their passwords in the Keychain - - Import creating the groups, tags and credential profiles of connections left unselected - - An unpredictable password when two imported rows replaced the same connection - - TablePlus import reading the default folder instead of the data location set in TablePlus - - - - ### New Features - - - **Tunnels for Weaviate, Typesense and Elasticsearch**: SSH tunnel, Cloudflare Tunnel, SOCKS proxy and Tunnel Command. Reach a server that only answers from inside a private network - - **Elasticsearch node list**: Several nodes per connection. When one stops answering, the connection moves to the next - - ### Improvements - - - Starter or Team named on license gates, badges and the welcome window, in place of "Pro" - - **One-time** in place of **Lifetime** for a license with no expiry in **Settings > License** - - **Share usage data**, with the report's fields listed, in place of **Share anonymous usage data** in **Settings > General** - - ### Security - - - Elasticsearch console paths starting with `//` sending the Authorization header to the host they named - - MongoDB writes hidden behind computed method names, escaped keys or a leading `--` passing as reads - - Elasticsearch writes passing Read-Only when the index name held `_search` or a GET carried a body - - Redis XAUTOCLAIM passing Read-Only and read-only MCP clients as a read - - SAP HANA helper built with Go 1.27.2, which fixes the crypto/tls flaw GO-2026-6607 - - MongoDB reads writing through a shell function an earlier statement redefined - - A MongoDB raw filter row that failed to parse running as JavaScript in a table tab, even at Read-Only - - ### Bug Fixes - - - Kafka connections dialing a hidden localhost:9092 before the brokers listed in the form - - One unreachable Kafka bootstrap server using up the whole connect timeout - - A blank row in MongoDB **Hosts** saved as localhost:27017 - - Elasticsearch connections to an IPv6 host failing with "Invalid host" - - Cloudflare Tunnel offered for BigQuery, DuckDB and other drivers that cannot use it, until their plugin was installed - - Oracle NCHAR, NVARCHAR2 and NCLOB columns failing to load on databases with the UTF8 national character set - - Oracle grid saves storing NCHAR, NVARCHAR2 and NCLOB text as ¿ or editing the wrong row on a non-Unicode database - - MySQL 4.1 tables not listing, and opening with no columns, indexes or DDL - - Table Info, row counts, routines, triggers, foreign keys and the server dashboard failing on MySQL 4.1 to 5.1 - - MySQL before 5.5 showing Korean, Japanese, Russian and other non-English query errors as garbled text - - **Stop** and the query timeout not stopping a query on MySQL 4.1 - - MongoDB, Redis, etcd and SAP HANA reads confirmed at the Alert and Safe Mode levels and blocked at Read-Only. See [Safe Mode](/features/safe-mode#what-counts-as-a-read) - - MongoDB reads with `$nin`, `$mergeObjects`, `.limit()` or `getCollection` refused for read-only MCP clients - - MongoDB `remove()` and `drop ()` running without the dangerous query warning, and a JavaScript `delete` raising it - - `seq.NEXTVAL` and `NEXT VALUE FOR seq`, which advance a sequence, running at Read-Only and unconfirmed at Alert - - MongoDB `deleteMany()`, `updateMany()` and other writes treating a missing filter as `{}` and touching every document - - MongoDB `count()` on an aggregate stopping at 5,000,000 and counting only the documents not yet read - - - - ### New Features - - - **French**: TablePro in French. Pick **Français** in **Settings > General**, by [@exaland](https://github.com/exaland) - - **Manage Macs**: The activation limit error links to the tablepro.app page that removes a Mac from your license - - **Oracle column types**: BOOLEAN, JSON and VECTOR in the Oracle column type picker - - ### Improvements - - - PostgreSQL, Redshift and CockroachDB hold one connection per database, with TCP keepalives on idle ones - - **Manage Account** replaces **Manage Billing** in **Settings > License** - - ### Bug Fixes - - - Switching databases on a PostgreSQL connection reconnecting and dropping the open transaction and temp tables - - Switching back to a PostgreSQL, Redshift or CockroachDB database reloading its object list from empty - - A tab left on a previous database running on a shared connection that closed after 10 minutes idle - - **Import** and **Copy To** committing a transaction left open on the target connection - - The health check reconnecting a PostgreSQL session that was sitting in a failed transaction - - Each switch between connections in the connections strip reloading that connection's schema - - Opening a connection fetching every column of its schema twice - - Unsaved grid edits not kept with their tab after jumping to a tab of a background connection - - **Users & Roles** discarding staged changes and Undo after switching tab or connection - - Object source, ClickHouse parts, chart and map reloading after switching connection and back, and the map losing zoom - - The History drawer dropping loaded pages and the selected entry after switching connection and back - - The assistant transcript, agent session highlight, inspector edits and Redis key filter reset by a connection switch - - The Structure tab stuck on a cancellation error after switching connection while it loaded - - A file handle leaked on each switch to a SQLite or Beancount connection - - A blank results pane instead of the data grid while a table loads, or after its load error is dismissed - - Edits saved against the wrong rows after a tab kept the previous table's primary key or row count - - The status bar stuck on Loading after cancelling a **Safe Mode (Full)** confirmation for a table - - Approving a **Safe Mode (Full)** confirmation running that table's query in a tab opened from a link meanwhile - - A restored table tab that never loads when another tab was opened before its window came forward - - Save writing a table's query into the SQL file whose tab the table was opened over - - Closing a database entry or connection, or disconnecting, rolling back an open transaction without asking - - Oracle column types shown wrong, such as NVARCHAR2(100) as nvarchar2(200) and TIMESTAMP as timestamp(6)(11) - - Oracle DDL and SQL exports failing to restore a column with a default and NOT NULL, or a virtual column - - Oracle DATE values losing the time of day, and TIMESTAMP fractions cut to milliseconds or misread after a leading 0 - - Oracle NCHAR and JSON values shown as ``, BFILE values shown as NULL, and long decimals shown rounded - - Oracle UROWID and REF columns, and index-organized table ROWIDs, failing to load and resetting the connection - - A crash reading an Oracle time zone west of UTC, a negative INTERVAL DAY TO SECOND, or an integer of 20 or more digits - - Oracle edits and deletes failing for DATE, TIMESTAMP, LOB or RAW values, or saving nothing for NCHAR or JSON - - Oracle tables whose first column is a CLOB or BLOB failing to open - - Oracle BLOB columns in query results treated as text, and an empty result listing every column of the first table - - Copying an Oracle table to another database turning NUMBER(10) keys into decimals and BOOLEAN or JSON into text - - Importing into a new Oracle table proposing TEXT columns, which Oracle rejects. Update the Oracle driver in **Settings > Plugins** to get the Oracle fixes - - - - ### New Features - - - **Requesty**: An OpenAI-compatible router in **Add Provider**, with its Base URL filled in, by [@Thibaultjaigu](https://github.com/Thibaultjaigu) - - ### Improvements - - - A new AI provider with several models and no default starts with no model picked, by [@Thibaultjaigu](https://github.com/Thibaultjaigu) - - Toolbar **Save** is a plain checkmark, and the **Safe Mode** icon is filled only at the two Full levels ([#3250](https://github.com/TableProApp/TablePro/issues/3250)) - - **Actions**, filter and **Disconnect** toolbar icons lose their circle on macOS 26 and later, and **Actions** its chevron ([#3250](https://github.com/TableProApp/TablePro/issues/3250)) - - ### Bug Fixes - - - The reasoning setting ignored on OpenRouter, OpenCode Zen, llama.cpp, MLX and custom AI providers, and a reasoning picker shown for Gemini and Ollama, where it changed nothing, by [@Thibaultjaigu](https://github.com/Thibaultjaigu) - - Image attachments offered for OpenRouter models that take text only, and max output tokens ignored and image attachments rejected on Ollama, by [@Thibaultjaigu](https://github.com/Thibaultjaigu) - - An AI provider failing with "Invalid endpoint" until relaunch after its Base URL was cleared, by [@Thibaultjaigu](https://github.com/Thibaultjaigu) - - **Export** in the Structure and object source views showing the Import icon ([#3250](https://github.com/TableProApp/TablePro/issues/3250)) - - VoiceOver reading the welcome window and Integrations filter menus as "chevron.pulldown" ([#3250](https://github.com/TableProApp/TablePro/issues/3250)) - - The database icon filled in the database switcher and query editor but outlined in the toolbar and sidebar, and status bar buttons a point or two apart in height depending on their icon ([#3250](https://github.com/TableProApp/TablePro/issues/3250)) - - The highlighted row in the database and connection switchers drawn as white text on a grey fill ([#3249](https://github.com/TableProApp/TablePro/issues/3249)) - - A second "#" column beside the row numbers when the data grid bounces past its left edge, and the last column's divider sitting on the grid's right edge, where it could not be dragged - - Cells keeping their old width or order until the mouse is released while a column is resized or dragged, and the resize cursor showing a few points from a divider, where a drag moved the column instead - - Elasticsearch, Typesense and Weaviate request bodies underlined as syntax errors although they run, and wildcard index paths such as `GET /_cat/indices/*` underlined as an unterminated comment - - Brackets in Redis and etcd command arguments underlined as unmatched - - Colons in Elasticsearch URLs, Redis keys and SurrealDB record IDs read as query parameters, holding the run - - **Format Query** joining the body onto the request line on Elasticsearch, Typesense and Weaviate, and changing Redis keys, etcd paths and SurrealDB record IDs such as `user:1` - - **Show All** in the sidebar running a MongoDB command on Elasticsearch, Typesense and Weaviate, and a Redis command on etcd - - The change preview on Elasticsearch, Typesense and Weaviate rewriting a stored `{"$oid": …}` as `ObjectId(…)` - - **Preview SQL** and other wrapped text splitting a quoted name such as `"public"."reviews"` across two lines - - Counts reading "3 table to export" in the **Export** dialog, and "1 rows" in the result status bar and query plan - - **Copy To** into PostgreSQL, SQLite, DuckDB or Oracle failing on an index name repeated in the copy or already in the target - - **Copy To** review breaking a long ENUM type mid-word, and not naming the table each type change belongs to - - The Safe Mode Touch ID prompt reading "TablePro is trying to Authenticate to execute database operations." - - VoiceOver reading no name for pickers and fields such as the AI provider pop-up, the CSV NULL text field, **Copy To** and **Create Table** - - A crash when VoiceOver reads **Settings > License** while a license appears in it - - Shortcuts **Add Row** and **Add Rows** on iPhone and iPad listing no tables until a database or schema is picked, which SQLite never offers - - The Shortcuts table and database pickers on iPhone and iPad showing an empty list instead of why the connection failed - - **Server Dashboard** metrics staying on a spinner, and slow queries never refreshing - - **Users & Roles** privilege checkboxes, **Review & Apply** and "Modified" not updating after a click - - The **Users & Roles** object tree keeping its old rows after a search or a switch to **Granted**, and search results showing a disclosure triangle that opened onto nothing - - **Count Exactly** on a Redis database leaving the estimate in place - - A Redis tab narrowed by its key pattern or type showing the whole database's key count as its "~" total - - The status bar truncating the row count and **Count Exactly** instead of dropping to a narrower layout - - The query editor bar cut off, with the editor's text over it, when the editor pane is at its smallest - - Long column names drawn over their type in the ER diagram - - Beancount BQL results showing a position as raw JSON, tags and links as a JSON list, and booleans as 1 or 0, and a commodity held in several lots listed once per lot on `rledger`. Update the Beancount driver in **Settings > Plugins** - - Beancount BQL results, tables, directive metadata and transaction tags coming back empty with `rledger` 0.23 and later. Update the Beancount driver in **Settings > Plugins** - - - - ### New Features - - - **New database file**: **New…** beside **Browse…** for SQLite, DuckDB and libSQL names a database file that does not exist yet. TablePro creates the file when the connection opens - - ### Improvements - - - `$1` in a regular-expression **Replace** in the SQL editor inserts the capture group, as it does in data files - - ### Bug Fixes - - - **Replace** in the find panel editing the wrong text after the first replacement, and crashing when a match ran past the end - - **Replace All** crashing on a space in an indent or after **Clear Query**, wrapping quotes and brackets in pairs, and taking minutes on a few thousand matches - - Two **Replace** clicks undoing as one step, and a collapsed fold hiding the wrong text after **Replace All** - - **Replace** and **All** offered in read-only editors, clearing the matches without changing the text - - Regular-expression **Replace** in data files inserting the wrong capture text when a replacement changes length - - **Import into Table** from a data file naming the sheet, table, error reports and history after a temporary copy - - The query editor staying put when Cmd+Up, Cmd+Down or a long paste moves the caret out of view ([#3239](https://github.com/TableProApp/TablePro/issues/3239)) - - **Clear Query**, a favorite, a history entry or an AI fix sometimes not reaching the editor, or typing bringing the old query back - - **Run**, **Explain** and AI actions using the caret from before a find or a jump to a result's statement - - **Use Selection for Find** reverting to the previous search term once the caret moves - - Connections strip entries cutting off the database or schema line and crowding the icon against the highlight, and the strip resting partway through an entry after the sidebar was resized ([#3244](https://github.com/TableProApp/TablePro/issues/3244)) - - Oracle closing TablePro, or failing with "unexpected message", while loading rows with `SDO_GEOMETRY` or `BLOB` columns, or when a query is stopped or the connection closed while rows load. Update the Oracle driver in **Settings > Plugins** ([#3241](https://github.com/TableProApp/TablePro/issues/3241)) - - Oracle object columns such as `SDO_GEOMETRY` labelled "unknown" and showing a placeholder instead of NULL ([#3241](https://github.com/TableProApp/TablePro/issues/3241)) - - DuckDB **Browse…** offering any file, and SQLite **Browse…** dimming databases saved without a known extension - - **File > Open File…** dimming folders reached through a symlink or a Finder alias - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-77) - - ### New Features - - - **SAP HANA**: Connect to SAP HANA Cloud and on-premises HANA 2.0, with TLS on by default, schema browsing, catalog DDL and query plans. Install the driver from **Settings > Plugins** ([#1966](https://github.com/TableProApp/TablePro/issues/1966)), by [@J2TeamNNL](https://github.com/J2TeamNNL) - - **Sidebar folders**: Group the tables and views of a database or schema into folders, kept in step between Macs over iCloud. Right-click a table and choose **Move to > New Folder**, or drag tables onto a folder. Folders live in TablePro only and change nothing on the server ([#3167](https://github.com/TableProApp/TablePro/issues/3167)) - - **Saved import mappings**: CSV, JSON and Excel imports remember each table's column mapping, and **Match by Name** and **Match by Position** fill every row at once ([#3172](https://github.com/TableProApp/TablePro/issues/3172)) - - **More import encodings**: Shift JIS, EUC-JP, GB 18030, Big5, EUC-KR and UTF-16 for CSV and SQL import - - **Timeouts per connection**: **Connect timeout** and a query timeout override under a connection's **Options**. Leave a field empty to use the default ([#2525](https://github.com/TableProApp/TablePro/issues/2525)) - - ### Improvements - - - The clipboard URL banner appears for every scheme **Import from URL** accepts, `+ssh` URLs included - - ### Security - - - A query tab opened from **Users & Roles** with **Open in Query Editor** saved its script with the passwords in plain text - - Redis **Verify Identity** accepted a server certificate issued for another host - - TablePro links, connection exports and the Team Library carried the pre-connect script and other settings meant to stay on one Mac - - ### Bug Fixes - - - **Copy as UPDATE** and **Copy as INSERT** writing identity, computed and generated columns, which SQL Server refuses, leaving out the table's schema, and matching a composite key on its first column only or on unsaved edits ([#3219](https://github.com/TableProApp/TablePro/issues/3219)) - - SQL Server and Oracle identity, computed, virtual, `rowversion` and system-versioned period columns offered for editing and written on save, including edits staged before the table's structure loaded, and **Add Row** failing on a table that has one - - SQL Server saves failing on a table without a primary key that has an `ntext`, `text`, `xml` or `image` column, identity columns missed when a table or schema name contains a dot, and a new row's value dropped when a same-named table in another schema has an identity column of that name - - **Set Value > Default** storing the text `__DEFAULT__` on SQL Server and Oracle, and new Snowflake, Teradata and Trino rows storing it in columns left to their default - - **Restore Previous Values** bringing a deleted SQL Server or PostgreSQL row back under a new identity key - - Undo and Redo in a tab with unsaved edits replaying another tab's changes against the wrong rows - - Column header sort on Redis, etcd and Kafka running the last command again with `ORDER BY` appended - - **Explain Analyze** running a write on Read-Only connections and skipping the Alert and Safe Mode confirmation, `EXPLAIN ANALYSE` treated as a plain `EXPLAIN`, and `pg_terminate_backend`, `nextval` and other state-changing calls treated as reads by Safe Mode and external clients - - Safe Mode levels: **Alert (Full)**, **Safe Mode** and **Safe Mode (Full)** dropped to **Alert** by an edit on iPhone or iPad, a level lost when a connection file moves between Mac and iOS, and an unknown level from iCloud read as **Off** on iPhone and iPad - - iCloud sync resetting a connection's **External Clients** level and its Cloudflare, Cloud SQL, SOCKS and Tunnel Command settings, and applying remote deletions of connections, groups, tags, SSH profiles and table favorites with their sync category off - - **Local only** connections taking edits and deletions made on another device, and syncing their table favorites, saved queries and column layouts to iCloud - - Favorite tables: two whose names contain `|` mixed up by iCloud sync, one starred again during a sync disappearing when it finished, a save and a deletion both sent for one starred again or renamed back before a sync ran, database favorites and column layouts removed on another Mac staying on this one, and the app pausing while renaming or dropping a database or schema that holds many of them - - The **Favorites** tab missing tables starred in other schemas, and all of them on Oracle, Snowflake, BigQuery and Trino - - A table dropped or renamed with SQL from a query tab or an MCP client keeping its saved filters, column layout, favorite and Recent entry - - Import sheet: an option change ignored until the next edit and then resetting the column mapping, another table's columns shown after switching tables while the first was loading, new-table column edits lost on an option change or **Try Again**, and rows written to another database after a database switch in another window - - CSV and Excel imports reading a column under the wrong header when headers repeat or a blank one comes first, and CSV and JSON imports failing on every row, or writing a skipped field, when two fields differ only by case - - CSV import writing bytes it could not read as Latin-1 text, and merging rows at a double quote inside a field when the quote character is a single quote - - JSON import leaving out fields first seen after row 200, JSON Lines files with CRLF line endings, `U+2028`, `U+2029` or `U+0085` in a string failing to import, invalid UTF-8 imported as `U+FFFD`, and JSON Lines import stalling while GitHub Copilot is on - - **Table Transfer** emptying a destination table and then failing when two source columns map to one column - - Japanese, Chinese and Korean text in CSV, TSV and SQL files opening garbled, and Latin-1 and Windows-1252 SQL dumps importing double-encoded text through their own `SET NAMES` and changing the bytes of binary values - - Data Files window: every row of a UTF-16 file rewritten on save, an error that stayed after reloading in the wrong encoding, the encoding chosen in **File Properties** lost when another app changes the file, and another app's in-place writes missed on HFS+ and exFAT volumes - - Data Files window: no columns found for a JSON Lines file over 256 KB with non-ASCII text, a wrong row number and blank-looking characters in the save error for text the encoding cannot store, the Yen sign failing to save in Shift JIS and EUC-JP, and VoiceOver reading the status bar's delimiter and encoding as only "File format" - - Shortcuts rejecting CSV and JSON files that are not UTF-8, and **Add Row** and **Add Rows** rejecting CSV with CRLF or CR line endings - - CSV, XLSX and MQL exports ignoring a table's row filter, row limit and column choice, the export dialog offering a SQL row scope on MongoDB, Redis and other engines without SQL, and a row limit on **Copy To** and scoped exports failing with a syntax error on SQL Server, Oracle and Teradata - - MQL exports rounding 64-bit integers past 2^53 and restoring whole doubles and small 64-bit integers as 32-bit ones, and XLSX exports writing duplicate sheet names that Excel opens only after a repair - - Parquet exports rounding or emptying Oracle, Snowflake and Dameng `NUMBER`, losing `DECIMAL` digits, writing Oracle `BINARY_FLOAT` and `BINARY_DOUBLE` as text and PostgreSQL `money` as null, and leaving files behind when a multi-table export is stopped - - MongoDB exports dropping fields first seen after document 200, Elasticsearch, Typesense and SurrealDB exports cutting arrays and objects over 10,000 characters into broken JSON, and Weaviate exports failing after 10,000 objects - - **Copy To** on a foreign table, sequence, system table or external table preselecting every object in the schema, and refusing to copy between two schemas of one DuckDB or PGlite connection - - MongoDB, Elasticsearch, Typesense and SurrealDB saving a long array or object shortened for display as the cut text, and MongoDB refusing text such as `[DRAFT] Chapter one...` as a shortened value - - Filter-bar **between** refused on Typesense and Weaviate and given the wrong lower bound on BigQuery, SurrealDB **between**, **matches regex**, **is empty** and raw filters run as equality checks with **is not empty** showing only empty rows, and the Weaviate raw filter row sent as a property named `__RAW__` - - SurrealDB edits to `in` and `out` dropped when the same row had another edit - - etcd **Delete** and **Truncate** on `(root)` erasing the whole Key Prefix Root, or sibling keys when it has no trailing `/`, Value edits and key renames detaching the key's lease, and commands and saved edits reaching a different key when the key starts with a combining mark - - etcd `lease revoke`, `auth disable` and user or role deletes running without confirmation, list commands gated as writes, and commands with a flag before the key, such as `del --prefix /app`, failing with "requires a key" - - etcd filters failing with "Unknown command: select" on columns other than Key, Key filters ignoring most operators and OR, and **contains** and **starts with** on Key also matching values - - etcd TLS: an **SSL Mode** the driver ignored, URLs that lost or faked TLS on import and **Copy as URL**, a blank **TLS Mode** on new connections, an untranslated **Disabled** option, and a Verify CA error naming Advanced fields - - Redis Cluster through a tunnel failing to connect with advice to set **Connection Mode** to Cluster, and a `SCAN` typed in a query tab showing one page of keys with no cursor to continue from - - ClickHouse: the query timeout never reaching the server, capped `SELECT` queries failing at 60 seconds, **Stop** leaving the query running, connect errors such as a wrong password shown as "Failed to establish connection", and the client certificate never sent to servers that require mutual TLS - - PostgreSQL 18 virtual generated columns written without their expression in **Show DDL**, **Copy DDL** and SQL export, and **REINDEX VERBOSE** offered on PostgreSQL 9.1 to 9.4, where it fails, and ignored when reindexing a whole database - - CockroachDB showing an empty **Check Constraints** tab and no check constraints in MCP `describe_table`, and **Use ~/.pgpass** named **Use Password File** on Redshift and CockroachDB - - PGlite reporting server errors such as a missing database as an unreachable socket server, and saying it cannot change the order of a table's columns - - Remote libSQL crashing on `inf` or `nan`, storing text like `007` as a number and blobs as text, and local libSQL writes reporting success after a lock wait reached the query timeout - - Remote SQLite refreshes mixing a new WAL or journal with the previous cached database, and snapshots left on the server after a connection timeout - - Connection checks interrupting a query, import or export still running after five minutes, timeout errors naming the database when an SSH bastion stalled, connection attempts in flight restoring an edited connection's previous server, name, tags or Safe Mode, and query timeout overrides dropped by Mac sync and connection exports ([#2525](https://github.com/TableProApp/TablePro/issues/2525)) - - Save disabled for Kafka connections set to **Verify Identity** without a CA file - - SSH, Cloudflare, SOCKS, Tunnel Command and Cloud SQL tunnels dropping the client certificate and key, so mutual TLS never worked through one - - The SOCKS proxy dialing the hidden Host instead of the first host-list entry, the tunnel command preview showing port 0 or the wrong host, "SSH password rejected" for a Password-auth tunnel with no saved password, and a host-list warning naming replica set failover for Redis and Kafka and implying Sentinel works through a tunnel - - Literal backticks in cloudflared, cloud-sql-proxy, SSH config, remote command and dump tool install messages - - The clipboard URL banner turning `sslmode=verify-full` or `verify-ca` into **Required** and ignoring `sslmode=disable`, and **Import from URL** ignoring `ssl=1`, `ssl=require` and `ssl=0` - - MongoDB restore into a database with a different name restoring nothing and reporting success, **Backup Dump** and **Restore** ignoring the connection's Auth Database, Hosts list, SRV and TLS options, and the restore confirmation claiming existing objects are overwritten on PostgreSQL, MongoDB, SQLite, SQL Server and DuckDB - - Server dashboard: **Slow Queries** failing on SQL Server, sessions, slow queries and query counts failing on Redshift, no **Terminate** button on ClickHouse sessions, SQL Server and ClickHouse listing their own monitoring session, SQLite **Cache Size** shown as "-2000 pages", and an untranslatable "% used" on Typesense - - MCP `describe_table` results failing the tool's own output schema, `export_data` writing numbers and booleans as strings and binary values as base64 text, `focus_query_tab` reporting a tab focused while the window stayed elsewhere, and `browse_table` ignoring a sort on a column the table does not have - - MCP `stop_server_session` blaming the engine for a mistyped process id or for cancel on SQL Server and ClickHouse, the `question_to_sql` prompt describing only the first six tables, and a Cassandra filter error pointing MCP clients to a **Match All** control they do not have - - Compare missing DuckDB rows keyed by a binary column and writing DuckDB binary values as text in sync scripts - - Linked Folder and Team Library connections never asking for a password, **Publish to Team Library…** sharing connections with the whole team without asking, an empty Team Library after joining a team until the next relaunch, an old Team Catalog entry left beside a renamed connection, and a folder panel pointing to a Settings pane that does not exist - - iPhone and iPad: the row editor saving a long text or binary value's placeholder over the full value and writing SQL Server identity and computed columns, a DuckDB connection's Info tab showing a server at 127.0.0.1:3306, and the `SELECT * FROM` template writing `LIMIT 100` on SQL Server, Oracle and Redis without the selected schema - - iPhone and iPad keeping the query history of deleted connections and the passwords of connections deleted on another device, and listing a repeated query twice until the connection is reopened - - Registry plugins refused as needing a newer TablePro on releases the registry still publishes binaries for - - Release highlights in the update dialog run together into one paragraph, and Removed and Deprecated listed after Fixed in GitHub release notes - - - - ### Improvements - - - Cassandra and ScyllaDB tables open without header sorting, **Match any** or an automatic row count, and CQL editor results are read-only, by [@yordis](https://github.com/yordis) - - ### Removed - - - **Verify CA** in the SSL mode picker for new Oracle connections on iPhone and iPad, which have no field for a CA certificate. Choose **Verify Identity** instead - - ### Security - - - Trino sent the password or access token in plain text when **SSL Mode** was **Disabled**, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - **Verify CA** without a CA certificate accepted a certificate issued for any host on Trino, ClickHouse and etcd, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - **Verify CA** without a CA certificate accepted a certificate issued for any host on Kafka, Oracle and Teradata. Choose the CA certificate that signed the server's certificate, or use **Verify Identity** - - ### Bug Fixes - - - Cassandra and ScyllaDB: syntax errors opening, paging or filtering a table, Save dropping edited numbers, dates, UUIDs and booleans while reporting success, **Add Row** replacing a row with the same primary key, and multi-row or composite-key deletes failing, by [@yordis](https://github.com/yordis) - - Cassandra and ScyllaDB values: `decimal` and `varint` shown as hex, `time` cut to milliseconds, and user-defined types, tuples, `duration` and `vector` shown garbled or empty, by [@yordis](https://github.com/yordis) - - Cassandra and ScyllaDB: query results stopping at 100,000 rows without saying so, Structure edits that failed at Save, missing functions, aggregates and triggers on ScyllaDB, trigger and function source that failed when run, and an SSL/TLS hint recommending settings for DataStax Astra, which does not connect, by [@yordis](https://github.com/yordis) - - CQL `BATCH` statements split at their inner semicolons, and a column named `case` running the next statement with its `CREATE TABLE`, by [@yordis](https://github.com/yordis) - - Endless reconnect attempts after a server started requiring TLS, stopped accepting it, or rejected the client certificate, with the certificate error shown in place of "The connection stopped responding", and ClickHouse host lookup failures reported as a TLS hostname mismatch - - Trino and ClickHouse on port 443, and ClickHouse on 8443, sending plain HTTP to an HTTPS port ([#3166](https://github.com/TableProApp/TablePro/issues/3166)), by [@J2TeamNNL](https://github.com/J2TeamNNL) - - Trino certificate failures shown as "Query was cancelled", raw HTML error pages in connection errors, PEM CA files rejected, proxy redirects followed to another address or over plain HTTP, and no warning under **Preferred** that Trino does not fall back to plain connections, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - Trino and etcd mutual TLS crashing on an `RSA PRIVATE KEY` file, rejecting other keys, and copying them to the login keychain, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - ClickHouse **Verify Identity** ignoring the chosen CA certificate, and certificate failures reported as a generic connection failure, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - Verify modes refusing to save without a CA file on SQL Server, and **Verify Identity** on Trino and ClickHouse, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - etcd rejecting a PEM CA certificate, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - Teradata falling back to the system trust store when the CA file could not be read - - **Import from URL** rejecting `jdbc:` URLs and ignoring their `user` and `password`, and imported Trino and ClickHouse URLs with `SSL=true` skipping certificate checks, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - Links opened from a browser or `open` ignoring the driver's default SSL mode, and copied connection URLs losing **SSL Disabled**, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - A result on a plugin-browsed engine taking the selected tab's table when it finished in another tab, and plugin saves running changes out of order after an undo, by [@yordis](https://github.com/yordis) - - The welcome window toolbar growing and captioning its search field after choosing **Icon and Text**, by [@shuvroroy](https://github.com/shuvroroy) - - Generic icons for ScyllaDB, libSQL, Teradata, Trino and Cloudflare R2 SQL in the iOS widget, and for ScyllaDB in the iOS app, by [@yordis](https://github.com/yordis) - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-76) - - ### New Features - - - **Agent mode**: One AI session that works with the whole connection window, with sessions to start and delete and a record of what each one ran. Turn it on from **View > Mode > Toggle Agent Mode** (`Cmd+Option+Shift+A`) or **Open in Agent Mode** on a connection in the welcome window. The connection is held at Safe Mode **Alert** while Agent mode is on and goes back to your level after, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - **Outside MCP servers**: Add an MCP server you run in **Settings > Integrations** and let an AI session call its tools, with a per-connection allowlist and its token kept in the Keychain, by [@J2TeamNNL](https://github.com/J2TeamNNL) - - **Data Files window**: CSV, TSV, pipe-separated, `.txt` and `.dat` files open as a table, replacing the CSV inspector. JSON and JSON Lines files open and edit as a table too, and Excel `.xlsx` workbooks and compressed files such as `.csv.gz` open read-only - - **Working in a data file**: Find & Replace across the whole file, **Search All Columns**, the table grid's filter bar, column statistics whose top values filter the file, a Row Details pane (`Cmd+Option+I`), cleanup under **Edit > Data**, export to every bundled format, **Import into Table**, and a text encoding choice in Save As. Large files open, filter and sort in the background - - **MongoDB documents**: **Insert Document…** and **Edit Document…** take a whole document as Extended JSON, **Remove Field** removes a field from a cell, and **No Field** marks a field a document does not have ([#3132](https://github.com/TableProApp/TablePro/issues/3132)) - - **MongoDB fields in Structure**: Rename or remove a field across a collection from the Structure tab. The collection's validator is carried along, and a change that would break an index, a view or a document is refused before anything is written ([#3132](https://github.com/TableProApp/TablePro/issues/3132)) - - **DynamoDB**: Reads that query the table or an index when the grid's filters allow it, a Create Table form, API requests such as `CreateTable {…}` in the editor, global secondary indexes from the Structure tab, recovery, protection and billing under **Maintenance**, nested attribute paths in filters, and a **DynamoDB Local (no credentials)** auth method - - **iPad layouts**: The table list and the table browser side by side, with row previews and the editor sized to the display, on iPad and on iPhone Duo's inner display - - **iPhone and iPad**: A welcome sheet that asks once about iCloud sync and usage data, a sample database, What's New after an update, and tips in the connection list - - **Review with AI**: From the query editor bar, the **Query** menu, the editor's context menu and the `/review` command - - **Saved queries**: Version history with **Restore This Version**, Git status, history and **Discard Changes…** for files in a linked SQL folder, and **Global** folders that every connection shows ([#2505](https://github.com/TableProApp/TablePro/issues/2505)) - - **SQLite extensions**: Load sqlite-vec, SpatiaLite and other libraries on connect, for SQLite and local libSQL connections ([#2502](https://github.com/TableProApp/TablePro/issues/2502)) - - **Recent tabs**: `Control+Tab` goes back to the tab you used last, with a list of the window's tabs while Control is held ([#2524](https://github.com/TableProApp/TablePro/issues/2524)) - - **Filter by a cell**: **Filter** in a data grid cell's context menu narrows the table to rows sharing that value ([#3066](https://github.com/TableProApp/TablePro/issues/3066)) - - **Saved filters**: **Filter Settings** chooses whether a table's filter comes back without running, or is not saved at all, and **Always Show Filter Bar** is an option of its own ([#3006](https://github.com/TableProApp/TablePro/issues/3006)) - - **Every schema in Open Quickly**: Tables from every schema in Open Quickly and the sidebar filter, and `schema.table` searches in both ([#3048](https://github.com/TableProApp/TablePro/issues/3048)) - - **SQL Server scripts**: `GO` batch separators, a result tab for every result set a batch returns, and `PRINT` output in the **Output** view ([#3078](https://github.com/TableProApp/TablePro/issues/3078)) - - **Oracle**: `DBMS_OUTPUT` lines shown with the statement that printed them and in the **Output** view, and transactions opened with `SET TRANSACTION`, `SAVEPOINT` or `LOCK TABLE` held until `COMMIT` or `ROLLBACK` - - **Actions menu**: A toolbar menu with the commands for the tab you are on and every import format, alongside **File > Import > Import Data From** and **File > Session** - - **Recent connections**: **Show recent connections** and **Clear Recent** in **Settings > General > Connections**, by [@shuvroroy](https://github.com/shuvroroy) - - **Foreign key picker**: Several label columns beside the key, for parent rows only a combination tells apart ([#2996](https://github.com/TableProApp/TablePro/issues/2996)) - - **Redis**: Numbered databases on Valkey 9 clusters, and `DB ` to run one command on another database - - **PostgreSQL indexes**: Invalid indexes named on the **Indexes** tab, expression keys such as `lower(email)`, and whether a materialized view can be refreshed concurrently ([#2522](https://github.com/TableProApp/TablePro/issues/2522)) - - ### Improvements - - - The connection window's toolbar holds eight controls that follow the tab and the mode, with the rest in **Customize Toolbar**, and the **Tables** and **Favorites** chooser sits at the top of the sidebar - - The Inspector and the Assistant share one header, with a picker between them - - Assistant conversations belong to one connection and outlive the window that opened them - - AI query actions send the columns, indexes, foreign keys and row counts of the tables the query uses, and **Explain with AI** and **Optimize with AI** send the selection or the statement at the cursor rather than the whole tab - - **Base URL** replaces **Endpoint** in an AI provider's settings, with the URL requests resolve to shown under it ([#3040](https://github.com/TableProApp/TablePro/issues/3040)) - - 537 driver and import/export strings are offered for translation, where they had only ever shown in English - - iCloud sync and usage data on iPhone and iPad stay off until you turn them on - - SQLite and libSQL connections run SQLite 3.53.4, built into the driver, in place of the macOS copy - - SQL Server scripts run outside a transaction TablePro opens, as in SQL Server Management Studio - - DynamoDB maps, lists and sets show as JSON, column types use the AWS console's names, and the region comes from the AWS profile when the connection names none - - The MongoDB shell sends `$regex` and `$options` objects as operator documents and stores whole numbers past 2^53 as doubles, as mongosh does - - A column type change from the Structure tab asks for confirmation under Safe Mode, like a dropped column - - ClickHouse materialized views are read-only in the data grid, as on every other engine - - ### Removed - - - The CSV Inspector plugin, replaced by the Data Files window - - **Refresh from iCloud**, **Sync Now** and the toolbar sync button on iPhone and iPad - - The toolbar's throughput readout, and Back, Forward, New Tab, Open Quickly, Add Row and Restore Previous Values from the default toolbar - - ### Security - - - Statements hidden behind a backslash, a nested comment, a bracketed identifier, dollar quotes or an engine's own literal forms skipped Safe Mode on Mac, iPhone and iPad, and passed the one-statement check on MCP and AI chat queries - - A quoted Redis command such as `"FLUSHALL"` skipped Safe Mode, and so did SQL Server statements written after another without a semicolon - - SQL Server connections set to **Required (skip verify)** were not encrypted past the login - - Inline suggestions sent the query and table columns to the AI provider on connections set to Ask Each Time or Never - - Stored MongoDB values, and collection, view and index names, could run as shell code when a row was edited, duplicated or restored, or when DDL was run - - Code in a plugin bundle, and its resource envelope, were not verified before the bundle was loaded - - The system log held query text, table names, file paths and driver errors, which can include row values - - Oracle and Dameng metadata reads could be captured by an object shadowing a `SYS` name in the current schema - - On iPhone and iPad, SSH private keys were saved in plain text, **Require Face ID** could be turned off without authenticating, and a locked launch connected before Face ID was answered - - ### Bug Fixes - - - MongoDB statements that failed, including writes the server rejected, reported as successful with an empty result, and the **Write Concern** setting ignored by every write except inserts - - MongoDB edits that deleted a field on **Set NULL**, retyped nested dates and numbers, or stored dates and ObjectIds as text, and collections that could not be created from **New Table…** ([#3131](https://github.com/TableProApp/TablePro/issues/3131)) - - Saved queries and folders deleted at launch when their connection had not arrived from iCloud yet, and edits made during an iCloud sync reverted by it - - iCloud sync stopping for good once 250 records were waiting to upload - - Every window's front tab reloading, and asking to discard edits, after an import, a new table or a structure change, and other tabs showing old rows, columns, DDL or triggers afterwards - - Structure saves that could lose data asking for confirmation twice and showing an error after Cancel, and the Structure tab refusing saves on a SQLite table with an untyped column, on a renamed primary key column, or on an index named like one deleted in the same save - - A batch committing or aborting a transaction already open on the connection, and a query in one tab cancelled when another tab on the connection started or stopped one - - SQL Server variables lost after a script's first statement, later result sets shown under the first one's columns or crashing the app, and a connection that stopped answering after Stop ([#3078](https://github.com/TableProApp/TablePro/issues/3078)) - - Oracle PL/SQL blocks split at their inner semicolons, procedures stored INVALID while the run reported success, and a crash when a query was cancelled while its rows loaded ([#2984](https://github.com/TableProApp/TablePro/issues/2984)) - - Kafka commands failing on a cluster of more than one broker, and `CONSUME ... FROM NEWEST` returning the oldest messages ([#2993](https://github.com/TableProApp/TablePro/issues/2993)) - - etcd connections failing once authentication is enabled ([#2994](https://github.com/TableProApp/TablePro/issues/2994)) - - The Redis database list failing on AWS ElastiCache and Azure Cache for Redis, Redis Cluster writes reported as complete when a shard refused them, and Redis errors on iPhone and iPad shown as success ([#3036](https://github.com/TableProApp/TablePro/issues/3036)) - - DynamoDB edits saving numbers, booleans, maps and sets as strings, filters matching nothing, and **Count Exactly** never finishing - - MySQL and MariaDB column defaults: NULL shown as Empty, string defaults misread on MariaDB, and errors editing a column whose default is an expression ([#3058](https://github.com/TableProApp/TablePro/issues/3058)) - - Backup Dump failing with MariaDB's `mysqldump`, or on Verify CA and Verify Identity connections ([#3046](https://github.com/TableProApp/TablePro/issues/3046)) - - **Highlight Rules** pull-downs snapping back, which left every rule on **equals** and the column it was created with ([#3015](https://github.com/TableProApp/TablePro/issues/3015)) - - Filter rows typed but never applied counted as applied, and lost on closing the tab ([#3006](https://github.com/TableProApp/TablePro/issues/3006)) - - A dropped table's saved filters, layout, highlight rules and value formats applied to a new table of the same name - - A wrong AI provider Base URL reported as **Model not found**, a doubled version segment in the URL, and **Connection successful** for a URL answering 404 - - SSH tunnels on iPhone and iPad stalling part-way through a result, and every later connect hanging once a tunnel dropped - - Icon-only buttons silent to VoiceOver across the app, and the data grid's inline editor and cell viewer unreachable by VoiceOver - - `Cmd+W` closing the whole connection instead of the current tab until something in the window was clicked - - CSV rows merged when a file did not end with a line break, UTF-16 files read as garbage, and edits under a filter landing on a different row - - - - ### New Features - - - **macOS 13 Ventura**: TablePro now runs on macOS 13.0 and later - - **Credential profiles**: Save a username and password once and link any number of connections to it. Profiles have their own pane in Settings, a **Credentials** picker on the connection form with **Save These as a Profile…**, and a toggle in Settings > Sync ([#2853](https://github.com/TableProApp/TablePro/issues/2853)) - - **Import from AWS**: **File > Import > Import from AWS…** finds RDS instances and Aurora clusters in the regions you choose and imports them as connections, including profiles signed in through IAM Identity Center ([#2852](https://github.com/TableProApp/TablePro/issues/2852)) - - **Updates in the background**: Updates download on their own and install when you quit. Turn it off with **Download and install updates automatically** in Settings > General, which also shows when TablePro last checked - - **What's New**: A window from Help > What's New with the highlights of the version you updated to, and a line on the welcome window naming the version you came from - - **Welcome window**: Favorites and Recent sections, sorting, drag and drop into groups, nested groups, inline rename and tag search tokens, with **File > New Group…**, **File > Rename** and **View > Sort Connections By**. The iOS connection list gets Favorites, Recent, nested groups, sorting and tag search too - - **Partitions in the sidebar**: Partitioned tables list their partitions underneath, with each partition's bound and a count on the parent, on PostgreSQL, MySQL, MariaDB and Oracle. Turn it off with **Partitions** in View Options ([#2523](https://github.com/TableProApp/TablePro/issues/2523)) - - **System databases and schemas**: Show or hide them in the sidebar tree from View Options and Settings > General ([#2832](https://github.com/TableProApp/TablePro/issues/2832)) - - **Find and Replace**: **Edit > Find > Find and Replace…** (`Cmd+Option+F`) and **Use Selection for Find** (`Cmd+E`) in the SQL editor - - **Run button**: A split button in the query editor with Run All Statements, Run Without Limit, Clear Query and Clear Results on its menu, **Stop** while a query runs, and a reason on any command that cannot run yet - - **Result chooser**: The status bar names the result on screen and offers Pin, Unpin, Close and Close Others - - **View > Focus**: Move the keyboard to the Object List, Editor, Results, Inspector or Assistant from one submenu ([#2904](https://github.com/TableProApp/TablePro/issues/2904)) - - **Keyword case**: Completed keywords and functions follow the case you type, set in Settings > Editor ([#2833](https://github.com/TableProApp/TablePro/issues/2833)) - - **OceanBase**: A MySQL-mode connection type on the MySQL driver ([#1748](https://github.com/TableProApp/TablePro/issues/1748)), by [@J2TeamNNL](https://github.com/J2TeamNNL) - - **SQLite on an SSH server, in place**: The Server mode for a Remote Database File runs statements on the server, so the database is edited where it lives ([#2831](https://github.com/TableProApp/TablePro/issues/2831)) - - **PostgreSQL schemas**: **New Schema…** and **Edit Schema…** with owner, comment, `USAGE` and `CREATE` privileges and a statement preview ([#2908](https://github.com/TableProApp/TablePro/issues/2908)) - - **PostgreSQL arrays**: Formatted JSON and per-element editing for `jsonb[]` and `json[]`, and an element editor for arrays in the row inspector ([#2897](https://github.com/TableProApp/TablePro/issues/2897)) - - **Data Compare & Sync**: A per-table row filter and row limit, and a row grid that marks each differing value ([#2537](https://github.com/TableProApp/TablePro/issues/2537)) - - **Oracle Network Encryption**: On the connection form, matching `SQLNET.ENCRYPTION_CLIENT` ([#2919](https://github.com/TableProApp/TablePro/issues/2919)) - - **SQL Server types**: Alias, table and CLR types in the sidebar's **Types** section, each with its `CREATE TYPE` statement - - **Delete for Kafka topics and DynamoDB tables**, and `DROP TOPIC` in the Kafka query editor - - **No Database Selected**: The sidebar says so for a MySQL, MariaDB, TiDB or OceanBase connection opened without a database - - **Favorite databases sync**: The databases you star in the sidebar reach your other Macs, alongside credential profiles - - ### Improvements - - - **View > Zoom In** and **Zoom Out** (`Cmd+=`, `Cmd+-`) zoom a focused ER or query plan diagram, and change the editor's text size everywhere else - - Toggle Filters moved to `Cmd+Shift+F`, leaving `Cmd+Option+F` to Find and Replace - - New versions reach everyone over 36 hours, and a waiting update renames **Check for Updates…** to **Update Available…** rather than interrupting - - The data grid keeps its top row across a refresh, and starts from the first row after a sort, filter or page change - - Duplicate Connection keeps a linked credential profile instead of copying its password - - The connection switcher lists Favorites, Recent and groups at every depth, and connection rows drop their colored dots - - Approval cards in the assistant name the statement and the connection, and **Cancel** is now **Reject** - - SQL Server sessions open with the ANSI settings the server expects - - A script that opens its own `BEGIN` or `START TRANSACTION` runs without a second transaction around it - - ### Removed - - - The result tab strip above query results and the "Query" heading above the editor, replaced by the result chooser - - **Auto-uppercase keywords** in Settings > Editor, replaced by **Keyword case** - - ### Security - - - The AI assistant is held to the connection it is attached to: it could write to another connection with that connection's Safe Mode skipped, and read any connection's schema by id, including one with AI turned off - - The AI assistant refuses statements that read or write files or run code on the server, such as `ATTACH`, `LOAD` and `VACUUM INTO`, matching the MCP server ([#2831](https://github.com/TableProApp/TablePro/issues/2831)) - - Oracle login continuing in clear text when the server picks an encryption algorithm without sending key material ([#2919](https://github.com/TableProApp/TablePro/issues/2919)) - - Pairing approval never showed where the one-time code is sent, and a pairing link with an unreadable connection list asked for every connection ([#2930](https://github.com/TableProApp/TablePro/issues/2930)) - - Sparkle 2.10.0, with installer fixes for a symlink attack and a privilege escalation - - SQLite denies `fts3_tokenizer`, which a crafted query could use to crash the app - - Remote `VACUUM INTO` snapshots and the cached AWS SSO token were created world-readable - - ### Bug Fixes - - - `Tab` reaching no further than the sidebar, leaving the editor, data grid, inspector and assistant unreachable from the keyboard ([#2904](https://github.com/TableProApp/TablePro/issues/2904)) - - `Cmd+Return` inserting the highlighted completion instead of running the query while the autocomplete list was open - - Autocomplete that stopped opening after a request came back empty, opened by itself after being dismissed, or skipped a quoted identifier or a PostgreSQL `::` cast ([#2915](https://github.com/TableProApp/TablePro/issues/2915)) - - `Return` in the filter field accepting a suggestion nobody picked, by [@filipac](https://github.com/filipac) - - Nested Elasticsearch fields showing as null in the grid, and filters on them matching nothing, by [@digows](https://github.com/digows) - - `Esc` not leaving Vim's Insert mode in the SQL editor ([#2914](https://github.com/TableProApp/TablePro/issues/2914)) - - Drop Schema and Drop Database skipping Safe Mode's confirmation and Touch ID, and Drop Schema offered on engines with nothing behind it - - Drop Table, Truncate Table and exports generating SQL for Elasticsearch, Kafka, Weaviate and etcd, which have none ([#2884](https://github.com/TableProApp/TablePro/issues/2884)) - - The sidebar, completion and other windows not updating after a table, view, routine, trigger, type, schema or database changed ([#2819](https://github.com/TableProApp/TablePro/issues/2819)) - - A table tab showing another database's rows, or opening empty, while the connection switched database - - Row inspector edits missing from the grid, writing NULL into every selected row, or landing on another record after a sort ([#2851](https://github.com/TableProApp/TablePro/issues/2851)) - - Data sync scripts missing every UPDATE and DELETE, writing to the source schema, pairing rows on a key that is not unique, and failing on identity columns ([#2537](https://github.com/TableProApp/TablePro/issues/2537)) - - Empty Procedures and Functions lists on every SQL Server connection, and rows that could not be saved on a table with a filtered index - - SSH profile edits and deletes not reaching the connections that use them, on this Mac or another - - Backups and restores running with no password when it comes from a password source or `~/.pgpass` - - Saved passwords deleted from iCloud Keychain when TablePro for iPhone launched before its connections loaded - - SSH settings dropped from a Mac connection after it synced from the iPhone app - - Remote database file connections hanging for minutes when SSH dropped silently, and reaching the old server after the host changed - - Oracle login hanging when the server declined network encryption, and crashing on some login packets ([#2919](https://github.com/TableProApp/TablePro/issues/2919)) - - Crash on launch when a pairing link opened the approval sheet ([#2930](https://github.com/TableProApp/TablePro/issues/2930)) - - No automatic update checks on a fresh install, and update preferences overwritten at every launch - - Clicks and drags landing on the wrong table or plan step once a diagram was zoomed, and `Cmd+scroll` not zooming diagrams - - AWS profiles backed by IAM Identity Center failing to authenticate, and AWS unreachable in the China and GovCloud partitions - - Stutter and slow typing in the SQL editor on very long lines, with word wrap on, or while Find highlights many matches - - `START TRANSACTION READ WRITE` failing on MySQL and MariaDB 5.5 when saving, importing or running several statements, and a batch whose transaction never started reported as a failed commit of its first statement - - No tables listed for a MySQL server whose `information_schema` answers with nothing or an error, the previous database's tables left on screen when a newly opened database fails to load, and indexes listed in a different order each time a table's structure loads - - Copy To losing column length, precision, scale, fractional seconds and collation, failing on an Oracle `NUMBER` with a negative scale, on accented `CHAR` text into ClickHouse, on a default that passes a sequence to a function, and on a key or index too wide for MySQL, SQL Server or Oracle - - PostgreSQL expression indexes missing from the Indexes tab, `INCLUDE` columns shown as key columns, and operator classes, collation, sort order and storage parameters lost by Copy To and index renames - - SP-GiST, HNSW, IVFFlat and BLOOM indexes shown as BTREE and rebuilt as B-tree by an index edit, SQL Server clustered indexes recreated as nonclustered, and ClickHouse data-skipping indexes dropped by an index edit - - Compare & Sync rewriting a column's collation while collation differences are ignored, treating two indexes that differ only in type as the same, and refusing structure sync scripts for tables that had not changed since they were compared - - A data race on the cached server version of a PostgreSQL, Redshift or CockroachDB connection while it closes - - Filters cleared on a table coming back the next time that table is read - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-74) - - ### New Features - - - **Google Cloud Spanner driver**: A registry plugin over the REST API. Install it from Settings > Plugins ([#1226](https://github.com/TableProApp/TablePro/issues/1226), [#2480](https://github.com/TableProApp/TablePro/issues/2480)) - - **Weaviate driver**: A registry REST plugin that browses collections as tables ([#1724](https://github.com/TableProApp/TablePro/issues/1724)) - - **Cloudflare R2 SQL driver**: Read-only, for Iceberg tables in an R2 Data Catalog ([#2030](https://github.com/TableProApp/TablePro/issues/2030)) - - **TiDB and Databend**: Both are connection types on the MySQL driver, with the server differences handled for you ([#1066](https://github.com/TableProApp/TablePro/issues/1066), [#2514](https://github.com/TableProApp/TablePro/issues/2514)) - - **Map view**: A result holding a geometry column can be drawn on a map, with MapKit over Apple's own tiles ([#2532](https://github.com/TableProApp/TablePro/issues/2532)) - - **Highlight rules**: Color data grid rows or cells by the value in them ([#2723](https://github.com/TableProApp/TablePro/issues/2723)) - - **Invisible characters in the SQL editor**: Control characters, zero-width spaces, bidi controls and special spaces are shown, with warnings for full-width punctuation, curly quotes and non-ASCII spaces. **Remove Invisible Characters** is in the Query menu, and **Show invisible characters** in Settings > Editor ([#2717](https://github.com/TableProApp/TablePro/issues/2717)) - - **Encoding for MySQL and MariaDB connections**: Including **UTF-8 via Latin 1**, for a database written through a Latin 1 client ([#2725](https://github.com/TableProApp/TablePro/issues/2725)) - - **Refresh Materialized View…**: On PostgreSQL, with a concurrent refresh where the view qualifies ([#2726](https://github.com/TableProApp/TablePro/issues/2726)) - - **Show DDL and Copy DDL**: For views and materialized views ([#2726](https://github.com/TableProApp/TablePro/issues/2726)) - - **Edit Comment…**: For PostgreSQL tables, views, materialized views and foreign tables ([#2726](https://github.com/TableProApp/TablePro/issues/2726)) - - **Check connections**: In Settings > General, with Only when I use the connection for a server you would rather not have pinged ([#2700](https://github.com/TableProApp/TablePro/issues/2700)) - - **Max INSERT size for SQL export**: 1 MB by default, with the largest INSERT written reported in the summary ([#2533](https://github.com/TableProApp/TablePro/issues/2533)) - - **More SQL import encodings**: UTF-16 LE, UTF-16 BE and Windows-1252 - - **Pending edits at a glance**: A row queued for deletion is struck through, and a new row or an edited value is underlined - - **Recovery actions for a plugin that cannot load**: **Enable Plugin**, **Open Plugin Settings** and **Edit Connection…** on the connection itself - - **What's New**: In the Help menu and in Software Update settings, opening the full changelog - - **Tips**: For preview tabs, Open Quickly and query history - - **Empty state in the inspector and the assistant**: For a connection that is not up - - ### Improvements - - - The query confirmation shows the statement syntax highlighted and scrollable, with **Copy All** and the name of whatever asked to run it ([#2759](https://github.com/TableProApp/TablePro/issues/2759)) - - The app bundle is 5 MB smaller and the DMG download 7 MB smaller - - Connect progress reads as a labelled bar, and names a step only where the app is waiting on something outside itself - - BigQuery Google sign-in runs from a **Sign In** prompt, and keeps its refresh token in the keychain - - First launch opens on the welcome window, whose empty list offers the sample database and import from another app. The first-launch tour is now a one-page welcome sheet, available again from Help > Getting Started - - New Connection… and Import are on the welcome window, named as in the File menu, and Open Project Folder… is in File > Import - - The structure editor leaves out options the connected server does not support, and is read-only on a view, a materialized view, a foreign table or a system table outside PostgreSQL ([#2726](https://github.com/TableProApp/TablePro/issues/2726)) - - MySQL sessions run on the server's default `utf8mb4` collation - - Beancount connections are held at Safe Mode Read-Only ([#2030](https://github.com/TableProApp/TablePro/issues/2030)) - - ### Removed - - - Focus Border color in the theme editor - - ### Bug Fixes - - - Commands and metadata reads acting on the database the sidebar moved to rather than the one the tab names. This covered New Table, New Trigger, Drop Trigger, Show All Tables, sidebar Refresh, Ref Table and Ref Columns in the structure editor, imports, export row totals, foreign key jumps, and `describe_table` over MCP ([#2769](https://github.com/TableProApp/TablePro/issues/2769)) - - Table lists, foreign keys, triggers, check constraints, types, grants, SQL dumps, Create Database, reindex, query plans, Server Dashboard, and backup and restore failing on PostgreSQL 9.1 through 13 ([#2734](https://github.com/TableProApp/TablePro/issues/2734)) - - Garbled non-Latin text saved to MySQL and MariaDB servers that force a Latin 1 session, on the Mac, iPhone and iPad, and when restoring a SQL export through a Latin 1 client ([#2725](https://github.com/TableProApp/TablePro/issues/2725)) - - Garbled non-ASCII text on PostgreSQL databases not encoded in UTF-8, on the Mac and on iOS, and when restoring a SQL export into one - - Garbled ClickHouse text whenever another value in the same result held binary data, and edits and deletes matching no row on a table with a binary value in it - - Timestamps losing their UTC offset, running an hour late and losing their time in a daylight-saving gap, missing sub-second precision in the column filter, and still reading in the old time zone after the Mac's changed ([#2702](https://github.com/TableProApp/TablePro/issues/2702)) - - Idle metadata connections held open for the life of the app, up to six per connection, and a health check quietly reconnecting over startup commands, the query timeout and the selected database ([#2700](https://github.com/TableProApp/TablePro/issues/2700)) - - `bytea` written as a bit string, a gzipped export emptying the file it was overwriting, parts left on disk unnamed after a failure, `SET IDENTITY_INSERT` missing after the first part of a split SQL Server dump, and minutes spent rendering hex literals ([#2533](https://github.com/TableProApp/TablePro/issues/2533)) - - The start of every line hidden in the SQL editor after a long line was removed, the editor jumping sideways on each keystroke in a long line, and the cursor left off screen after pasting one ([#2709](https://github.com/TableProApp/TablePro/issues/2709)) - - BigQuery statements broken by an apostrophe or a backslash, grid inserts writing `__DEFAULT__` into columns, edits matching more rows than the edited one, and six filter operators ignored - - Connections imported from TablePlus and Beekeeper Studio arriving unusable: five engines typed so they never connect, DuckDB files dropped, certificate paths read the wrong way round, **Prompt for password** lost, and wrong default ports on Redshift and CockroachDB - - A silently lost edit on a table with no primary key holding a `FLOAT`, `DOUBLE` or `JSON` column, and a save on such a table reporting success when it matched no row - - SQL Server and Oracle reporting zero rows affected for every `INSERT`, `UPDATE` and `DELETE` - - Row numbers painted over the find bar, the filter panel and the result tab bar, showing the scrolled columns through them in dark mode, and taking no clicks below the last row - - The wrong row deleted or updated after an edit took a row out of a column value filter - - The data grid ignoring the theme's background, text, alternate row, NULL, boolean and row number colors - - A statement holding a NUL character running only up to it on SQLite and PostgreSQL, dropping its `WHERE` clause ([#2717](https://github.com/TableProApp/TablePro/issues/2717)) - - A blank welcome window list when a search matched nothing, a tag filter stuck on a tag no connection carries any more, and collapsed groups undone at the next launch - - The data grid and its bottom bar cut off at both edges in a narrow window, with rows and pagination buttons out of reach - - A PostgreSQL statement silently replayed on a new connection after a drop, a lost `COMMIT` reported as saved included - - The object browser reading "No Tables" for a database with procedures but no tables, and procedures, functions and triggers missing on Oracle, Snowflake, BigQuery and Dameng - - ### Security - - - TablePlus connections set to VERIFY-IDENTITY or VERIFY-FULL imported with TLS turned off - - BigQuery Google sign-in accepting an authorization response without PKCE or a state check - - PostgreSQL sessions inheriting `standard_conforming_strings = off`, which let a backslash break out of any quoted literal - - PostgreSQL catalog, comment and password literals escaped by quote doubling alone, which a backslash can break out of ([#2726](https://github.com/TableProApp/TablePro/issues/2726)) - - An MCP token without Full Access approving its own write and skipping TablePro's confirmation dialog - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-73) - - ### New Features - - - **Connection editor rebuilt**: Four sections in a sidebar, General, Network, Options and Appearance, in place of up to eleven panes. One Connect via picker replaces five Enable switches, and Save, Cancel and Test Connection sit on a bottom action bar - - **Toolbar rebuilt**: Five icon-only groups, with the connection and container as one centred control that switches either. Query duration and Stop moved to the results status bar - - **Typesense driver**: Collection browsing, document editing, a REST request console, API keys in Users & Roles, and Server Dashboard metrics from `/metrics.json` and `/stats.json`. Install it from Settings > Plugins ([#2629](https://github.com/TableProApp/TablePro/issues/2629)) - - **Copy To across engines**: Copy a table into a connection on a different database engine, with every type approximation listed before the copy runs. Each table takes its own `WHERE` and row limit, and a copy inside one connection runs server-side as `INSERT … SELECT` ([#1491](https://github.com/TableProApp/TablePro/issues/1491)) - - **SQLite structure editing**: Add, remove and edit foreign keys, and change a column's type, nullability and default, on SQLite, libSQL and Cloudflare D1. Each is applied as a reviewed table rebuild, checked against dependent views and triggers first - - **Default value menu**: The Structure tab's Default cell offers the engine's own values, No default, NULL, Empty string and a Custom editor ([#2688](https://github.com/TableProApp/TablePro/issues/2688)) - - **Tunnel Command transport**: Reach a database through any command that opens a local port, with presets for `kubectl port-forward` and `aws ssm start-session` ([#2520](https://github.com/TableProApp/TablePro/issues/2520)) - - **DuckDB backup and restore**: Write a database as one `.duckdb` file or a folder of Parquet. Backup Dump gained a table picker and can write several databases into one folder, and Back Up… is on a database in the connection tree ([#2485](https://github.com/TableProApp/TablePro/issues/2485)) - - **DuckDB file lock control**: Release File Lock on the Database menu and the connections strip, an Open the File Read-Only option so several processes share one file, and per-connection idle release for DuckDB and MySQL ([#2518](https://github.com/TableProApp/TablePro/issues/2518)) - - **Assistant as its own pane**: View > Show Assistant, or `Cmd+Option+A`. The inspector shows the selected row as Fields or JSON, with the column name and type on one line and the value at full width below, field search, an edited-fields-only filter, Tab between fields, and a value menu with `Ctrl+Option+N` for NULL and `Ctrl+Option+D` for DEFAULT - - **EXPLAIN bar chart**: A bar column in the plan tree, with a Metric menu for self cost, self time and row counts ([#2633](https://github.com/TableProApp/TablePro/issues/2633)) - - **Image preview in cells**: A cell holding SVG or a raster image shows a preview beside its source ([#2535](https://github.com/TableProApp/TablePro/issues/2535)) - - **Sort direction setting**: Sets the default row sort and which way the first click on a column header sorts ([#2665](https://github.com/TableProApp/TablePro/issues/2665)) - - **Row selection in the grid**: The row-number gutter holds at the left edge when the table scrolls sideways, and `Shift+Space` widens the selection to every row it touches ([#2664](https://github.com/TableProApp/TablePro/issues/2664)) - - **CSV export encoding**: Pick the encoding and whether to write a byte order mark, with a warning naming what the encoding dropped ([#2534](https://github.com/TableProApp/TablePro/issues/2534)) - - **Indexes in SQL export**: Written after the data, with a materialized view's indexes written once the view exists ([#2492](https://github.com/TableProApp/TablePro/issues/2492)) - - **MCP access approval**: A setting for connection access, with the list of approved connections and a Forget action ([#2640](https://github.com/TableProApp/TablePro/issues/2640)) - - **Transport activity**: Live throughput for an SSH tunnel or SOCKS proxy in the toolbar, with bytes carried shown in the connection switcher - - ### Improvements - - - Both diagrams zoom on a ladder from 5 to 300 per cent, and Fit to Window uses whatever scale the diagram needs instead of stopping at 25% ([#2692](https://github.com/TableProApp/TablePro/issues/2692)) - - Database type can be changed from inside the connection editor, and the reason Save is unavailable is shown next to the button - - Backup Dump is one sheet carrying scope, format and destination, in place of a picker with a save panel over it ([#2485](https://github.com/TableProApp/TablePro/issues/2485)) - - The object tree's context menu is regrouped into four groups by intent, with connection-wide commands off object rows and keyboard shortcuts shown where a menu bar equivalent exists - - View Options moved into the sidebar's filter row, in place of an entry on every context menu - - Back is available with unsaved edits and asks to discard them, instead of standing down until they were saved - - Structured inspector values expand in place instead of replacing the whole pane - - An import proposes the table name from the file name, and warns when the name is taken - - Safe Mode, Session Context and Schema have submenus in the Database menu, and the editor tab whose query is running carries an indicator - - ### Bug Fixes - - - Column defaults were quoted into string literals, from `gen_random_uuid()` to `NOW()` to `nextval(...)`, and SQL Server dropped a column's `DEFAULT` constraint on a type change without re-adding it ([#2688](https://github.com/TableProApp/TablePro/issues/2688)) - - `~/.ssh/config` was misread in eleven ways: `%h` and the other tokens arrived as literal text, `Include` lines naming more than one file read none of them, negated `Match` criteria matched every host, and `Match final` overrode blocks that had already set a value ([#2687](https://github.com/TableProApp/TablePro/issues/2687)) - - A jump host's own `ProxyJump` was not followed, leaving a chained bastion unreachable ([#2687](https://github.com/TableProApp/TablePro/issues/2687)) - - Half the ER diagram was left unpainted at its fit-to-window zoom, a table's relationship with itself was drawn underneath the table, and a table dragged past the top-left corner disappeared with the position saved ([#2692](https://github.com/TableProApp/TablePro/issues/2692)) - - Unsaved cell edits were discarded without asking when a column was hidden, shown or reset, and grid row and cell selection was lost on switching editor tabs or moving a tab to a new window ([#2667](https://github.com/TableProApp/TablePro/issues/2667)) - - Cell selection, copy and paste used columns the pointer never crossed once a column had been reordered or hidden, and a whole-result copy after Select All ignored the 50,000-row clipboard limit ([#2667](https://github.com/TableProApp/TablePro/issues/2667)) - - Indexes were missing from an SQL export on SQLite, libSQL, Cloudflare D1, SQL Server, Oracle, Dameng and Cassandra, and unique constraints were lost on SQL Server, Oracle and Dameng ([#2492](https://github.com/TableProApp/TablePro/issues/2492)) - - A PostgreSQL materialized view exported as a `CREATE TABLE`, and Oracle and ClickHouse views exported as a bare `SELECT` with no `CREATE VIEW` ([#2492](https://github.com/TableProApp/TablePro/issues/2492)) - - Backup Dump and Restore Dump were unusable on SQLite, a libSQL backup wrote an empty 52-byte file and reported success, and Restore Dump asked every engine for a file `pg_dump` wrote ([#2485](https://github.com/TableProApp/TablePro/issues/2485)) - - A connection with two transports enabled reached the database directly, with neither transport applied - - A MySQL statement was replayed outside the transaction it was run in after the server dropped the connection, and the query timeout was lost after a MySQL reconnect - - Redis and Valkey ACL users without permission to run `PING` were refused at connect and dropped by the health check, and a failed reconnect left the connection marked live - - The Query Live Activity kept counting on the iOS Lock Screen after the app was quit mid-query, and Stop left an iOS MySQL or Redis query running behind a stuck spinner - - The iOS connection screen was stuck on Connecting for good after a cancelled connect, and edited host, port or credentials were ignored until relaunch - - `is_connected` was reported as true over MCP for a connection that had stopped answering, and the MCP access prompt returned every 30 minutes ([#2640](https://github.com/TableProApp/TablePro/issues/2640)) - - `IN`, `AND`, `OR`, `NOT`, `BY` and `ON` were drawn in the editor's plain text colour, built-in type names and function calls were unhighlighted, and the MongoDB and Elasticsearch query editors had no syntax highlighting at all ([#2634](https://github.com/TableProApp/TablePro/issues/2634)) - - Delete Connection had been missing from the connection editor since 0.39.0 - - ### Security - - - Redis and Valkey connections signed in as the default user when a username was typed with an empty password - - Redis connections on iOS were reported as successful without a single command reaching the server - - - This release moves TablePro's plugin ABI to version 25. The change is additive, so plugins you already have keep working. Reinstall the SQLite, libSQL, Cloudflare D1, MSSQL, Oracle, Dameng, Cassandra and DuckDB drivers from Settings > Plugins to pick up the index and constraint export fixes. - - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-72) - - ### New Features - - - **Backup and restore**: Back up a database with its own engine's tools and restore it again. TablePro drives `pg_dump` and `pg_restore` on PostgreSQL and Redshift, `mysqldump` on MySQL and MariaDB, `mongodump` on MongoDB, `sqlite3` on SQLite and libSQL, and `sqlpackage` on SQL Server ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - **Export any object, not just tables**: The export tree lists views, routines, triggers, user-defined types, privileges, MySQL events and PostgreSQL sequences, grouped by kind. Each table can carry its own `WHERE`, row limit and column subset ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - **Transfer To**: Copy table rows straight into another open connection, with no file in between ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - **Markdown, HTML, XML and Parquet export**: Parquet installs from Settings > Plugins. JSON export can also write NDJSON, one row per line ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - **XLSX import**: Read the first worksheet of a workbook ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - **Server-Side Export**: Oracle, Snowflake and BigQuery unload to a server directory, a stage or a bucket instead of streaming through your Mac ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - **PostgreSQL types**: A Types section in the sidebar for enums, composites, domains and ranges, with a definition tab, enum label editing, and the types offered in the structure editor's type picker ([#2484](https://github.com/TableProApp/TablePro/issues/2484)) - - **Foreign key value picker**: Pick a value on a foreign key cell from the rows of the referenced table, with a label beside the key ([#2511](https://github.com/TableProApp/TablePro/issues/2511)) - - **AppleScript dictionary**: Script connections, tabs, results and the grid selection. The history drawer filters on AppleScript source and carries its own notification toggle ([#2512](https://github.com/TableProApp/TablePro/issues/2512)) - - **Connection groups in Switch Connection**: `Cmd`-click a saved connection to open it in a new window ([#1311](https://github.com/TableProApp/TablePro/issues/1311)) - - **Jump to Column**: Fuzzy search over a result's columns, with each column's type and position ([#2495](https://github.com/TableProApp/TablePro/issues/2495)) - - **Query time breakdown**: The toolbar's duration readout splits into server, first row and transfer ([#2503](https://github.com/TableProApp/TablePro/issues/2503)) - - **Saved export selections**: Reapply a selection from the export tree's bookmark menu ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - `list_types` MCP tool - - ### Improvements - - - SQL exports can skip, replace or update rows that already exist, split into numbered parts at a size you choose, and read every table at one snapshot ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - Exclude the AUTO_INCREMENT counter and Exclude DEFINER clauses in the SQL export, both on by default ([#2516](https://github.com/TableProApp/TablePro/issues/2516)) - - Save Report on an import that skipped rows, listing each one's line and error as CSV ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - Query Insights ranks on the time the database spent rather than on elapsed time ([#2503](https://github.com/TableProApp/TablePro/issues/2503)) - - The export summary reports the warnings an export produced, instead of a bare "Export completed" ([#2517](https://github.com/TableProApp/TablePro/issues/2517)) - - The Export, Transfer and Server-Side Export sheets resize, and the object tree takes the extra room. Cancel sits beside the action button in all three footers ([#2618](https://github.com/TableProApp/TablePro/issues/2618)) - - ### Bug Fixes - - - A SQL export of query results used PostgreSQL's identifier quoting whatever engine the results came from ([#2630](https://github.com/TableProApp/TablePro/issues/2630)) - - Backslashes were left unescaped in a SQL export of query results, silently altering the values, and `DROP ... CASCADE` was written for engines that reject it, SQLite and SQL Server among them - - Snowflake string literals were escaped without their backslashes, in the editor and in exports - - Restore Dump overwrote a database with no confirmation, and a failed restore left a partly-restored database without saying so - - Stopping an import applied at once, while stopping an export or a backup asked first - - A streaming query export showed a progress bar stuck at zero and an "N/0 rows" label for its whole run - - Checkboxes, pickers and text fields across the import and export sheets were unnamed to VoiceOver - - Oracle server-side export was reported as written when the Data Pump job had only been started - - An import stuck on a file that will not parse showed the parser's message as placeholder text - - DuckDB aggregate, JSON and Parquet functions failed on a Mac that cannot reach `extensions.duckdb.org` ([#2626](https://github.com/TableProApp/TablePro/issues/2626)) - - Structure and trigger edits committed or rolled back a transaction left open in a query tab on the same connection - - Composite, range and extension-typed PostgreSQL columns were labelled `ENUM(…)` in the structure editor, and columns of an enum created during the session showed as text until the next reconnect - - The sidebar showed routines, triggers and types from the previous database after switching while it was still loading - - Foreign keys were declared twice in a SQL export, or written as an `ALTER TABLE` the engine rejects, and were missing from Redshift's reconstructed `CREATE TABLE` ([#2517](https://github.com/TableProApp/TablePro/issues/2517)) - - A Contains, Starts with, Ends with or Regex filter on a PostgreSQL uuid, enum, number, date or json column failed with "Operator does not exist", and an ignore-case filter on a non-text column with "Function lower does not exist" - - A MongoDB collection named like a `db` method, such as `stats` or `version`, failed to open, save or export - - Connections, groups and tags from another device were dropped for good when the store could not be written ([#1311](https://github.com/TableProApp/TablePro/issues/1311)) - - Every saved group or tag was lost when one unreadable entry stopped the list decoding, and opening the connection list crashed after two Macs moved two groups inside each other ([#1311](https://github.com/TableProApp/TablePro/issues/1311)) - - Two Macs re-uploaded the whole group and tag lists to each other after a single change ([#1311](https://github.com/TableProApp/TablePro/issues/1311)) - - - This release moves TablePro's plugin ABI to version 21. The change is additive, so plugins you already have keep working. Reinstall the DuckDB, MSSQL, Snowflake, libSQL or Cloudflare D1 driver from Settings > Plugins to pick up the foreign key export fix. - - - - - ### New Features - - - **Saved comparisons**: Save Comparison… in the Compare & Sync toolbar, and under Database > Compare, keeps a pair of endpoints with the mode and options you set. The Comparisons button lists every one you have saved, and loading one sets both sides for you. Compare & Sync also reopens on the source, target, mode and options it last held - - **Text for binary columns**: Display As offers Text on a binary column, and TablePro detects a column holding UTF-8 on its own. Find covers those columns too, matching the text you see rather than the bytes underneath ([#2599](https://github.com/TableProApp/TablePro/issues/2599)) - - **Identity File on SSH Agent connections**: Point an agent connection at one key instead of every key the agent holds. TablePro also reads `IdentitiesOnly` out of your `~/.ssh/config` ([#2601](https://github.com/TableProApp/TablePro/issues/2601)) - - **Middle-click a tab to close it** ([#2595](https://github.com/TableProApp/TablePro/issues/2595)) - - Select > All and Select > None in the structure results pane - - ### Improvements - - - Cold launch to a usable window went from 470ms to 260ms. Plugin signature checks, 13ms each, now run after the first window instead of on the launch thread - - A connecting, schema or query progress indicator waits half a second before it appears, and stays a minimum time once it does. Window chrome no longer collapses and reopens around a connect that finishes inside that half second - - Compare & Sync reads a whole schema in a few queries instead of one per table, and reads both sides at once. Data mode lists the tables both sides share as soon as you pick a pair, and Apply… builds the script when there is not one yet - - Table collation is read on MySQL, which it never was before - - Stale `cloudflared` and `cloud-sql-proxy` processes are given time to exit before a new connection reuses their port - - ### Bug Fixes - - - Generated column expressions were missing from MySQL's whole-schema column read, and generated columns were missing entirely from SQLite's - - PostgreSQL index reads matched a table name in every schema rather than the one asked for - - Compare & Sync could publish one pair's results after the pickers had moved to another, and its toolbar kept naming the old pair after a saved comparison was loaded from Options - - A failed whole-schema trigger read counted as a schema with no triggers - - Clicks and hovers in the scrolled tab strip's edge padding landed on a tab clipped off the edge - - SSH Agent auth offered every key the agent holds, exhausting the server's `MaxAuthTries` ([#2601](https://github.com/TableProApp/TablePro/issues/2601)) - - - This release moves TablePro's plugin ABI to version 20, so every plugin installed from the registry needs its rebuilt binary. If a plugin stops loading after you update, reinstall it from Settings > Plugins. - - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-70) - - ### New Features - - - **Row as JSON**: A JSON tab in the inspector shows the selected row as JSON, reachable from Show Row as JSON on a row's right-click menu. Click a foreign key to fetch the row it points at, five levels deep, and turn on Always Expand Foreign Keys to have them open on arrival. The filter field takes plain text or a regular expression in slashes - - **JavaScript shell for MongoDB**: Write queries the way you write them in mongosh, with the `db` API, cursors, variables, functions and `print`. Each connection keeps its own shell state, so a variable or function you define survives to the next statement, and autocomplete offers the cursor methods after `find()` and `aggregate()` - - **Copy To and Duplicate Database**: Copy a database's structure, its data or both to any other connection, from the sidebar or the Database menu. The connection, database and schema picker it shares with Compare & Sync now has a search field ([#2487](https://github.com/TableProApp/TablePro/issues/2487)) - - **Column reorder**: Drag a column into place on ClickHouse and Oracle, or use Move Column Up and Move Column Down on its right-click menu. PostgreSQL, SQLite, libSQL, Turso and Cloudflare D1 have no `MOVE COLUMN`, so TablePro rebuilds the table instead and shows you the statements before anything runs. Where the engine cannot do it at all, the row number carries the reason ([#2479](https://github.com/TableProApp/TablePro/issues/2479)) - - **Open a database file from Finder**: TablePro recognises SQLite and DuckDB databases by their contents rather than their name, reads `.parquet` files through DuckDB, and offers to install the driver a file needs before opening it. File > Open File… opens anything TablePro reads ([#2476](https://github.com/TableProApp/TablePro/issues/2476)) - - **Tab rows**: Settings > General > Tabs can wrap the tab strip onto several rows instead of scrolling it. Drag a tab out of the strip, or use Move Tab to New Window on its right-click menu, to move it to a window of its own ([#2438](https://github.com/TableProApp/TablePro/issues/2438)) - - Korean, Turkish, Vietnamese and Chinese translations for 742 strings that had been shipping in English, and for the document kinds Finder shows - - ### Improvements - - - The connections strip leads with the connection name and puts the database or schema on a second line ([#2550](https://github.com/TableProApp/TablePro/issues/2550)) - - `Up` and `Down` while editing a cell move the editor to the same column of the row above or below ([#2569](https://github.com/TableProApp/TablePro/issues/2569)) - - Dragging a tab autoscrolls the strip, so a tab can be moved past the run currently on screen, and AppKit handles editor tab presses instead of SwiftUI gestures ([#2438](https://github.com/TableProApp/TablePro/issues/2438)) - - Copy on a column's right-click menu in Structure copies the cell under the pointer - - MongoDB statements are split as JavaScript rather than at every semicolon, and the editor's diagnostics report JavaScript syntax errors rather than unsupported method names - - ### Bug Fixes - - **Columns the server fills in** - - - Add Row pre-filled NULL into an identity column, which failed the insert on PostgreSQL, and a cell's Set Value menu offered no "Default" for one ([#2588](https://github.com/TableProApp/TablePro/issues/2588)) - - Paste, Fill Column and the row inspector staged edits to generated and identity columns that the save then discarded, and Duplicate Row copied an identity column that is not the primary key ([#2588](https://github.com/TableProApp/TablePro/issues/2588)) - - A new row of nothing but server-assigned columns was dropped from the save without a word - - Generated and identity columns became editable again after a tab switch or a refresh that reused cached metadata - - Add Row and Duplicate Row were offered before a table's schema had loaded, and stayed inert for good on a result switched away from mid-load ([#2588](https://github.com/TableProApp/TablePro/issues/2588)) - - The row inspector offered an editor for a column the driver marks immutable, such as MongoDB's `_id` - - **The cell editor** - - - `Tab` saved half-composed input method text and left it behind, opened the editor off screen when it wrapped onto a row below the visible ones, and left the cell cursor on the old column - - One `Tab` press switched every data grid to its accessibility layout with no assistive app attached - - **Tabs** - - - About one tab drag in seven did nothing, and a drag released on a neighbour's exact centre left the order unchanged ([#2438](https://github.com/TableProApp/TablePro/issues/2438)) - - **MongoDB** - - - Any filter written in shell syntax, such as `db.orders.find({status: 1})`, failed to parse, and `.sort()` and `.projection()` were ignored when written with unquoted keys - - **Connections** - - - Resizing a column on a database with a long file path crashed the app on every launch afterwards, with iCloud sync on ([#2575](https://github.com/TableProApp/TablePro/issues/2575)) - - SSH Agent auth asked for a private key passphrase instead of reporting that the agent was never reached ([#2583](https://github.com/TableProApp/TablePro/issues/2583)) - - An SSH connection with no password reported "SSH password rejected" when the server offered no keyboard-interactive - - **Export and structure** - - - A SQL export of a SQL Server database was missing `SET IDENTITY_INSERT` around a table's rows, so the dump could not be restored, and carried `OVERRIDING SYSTEM VALUE` and `setval` from PostgreSQL ([#2588](https://github.com/TableProApp/TablePro/issues/2588)) - - Compare & Sync could not drop an overloaded PostgreSQL routine, or any trigger - - PostgreSQL sequence DDL named the schema it was read from, in SQL export and the structure editor - - PGlite `$$` bodies split at their inner semicolons - - The row inspector lagged on hover, on a tab switch and on every keystroke, from re-parsing each field's value - - Plugin download reported no progress at all when a connection or a file needed a driver installed - - A rerun answered from cache adopted another pinned result's column metadata ([#2588](https://github.com/TableProApp/TablePro/issues/2588)) - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-69) - - ### New Features - - - **Restore Previous Values**: Take back a save that already committed. TablePro keeps the previous values of rows you changed, so an edit, a delete or a paste can be put back from the Edit menu or the toolbar's Table Actions group after the transaction closed. Settings > Data & Results has an off switch and Clear Saved Changes. This is a Starter feature ([#2107](https://github.com/TableProApp/TablePro/issues/2107)) - - **Check constraints and generated columns**: A Constraints tab in the structure editor lists check constraints with their expression, and the column row gains Generated and Expression fields with a stored or virtual choice. MCP clients see them as `check_constraints` and `generation_expression` on `describe_table` ([#2478](https://github.com/TableProApp/TablePro/issues/2478)) - - **SQLite over SSH**: A Remote File pane opens a read-only copy of a SQLite database that lives on an SSH server ([#2474](https://github.com/TableProApp/TablePro/issues/2474)) - - **Rename from the sidebar**: Rename on a table's right-click menu edits the row's label in place, and databases and schemas rename the same way where the engine supports it ([#2482](https://github.com/TableProApp/TablePro/issues/2482)) - - **Turso**: Its own engine in the New Connection picker, offered before the libSQL plugin installs - - A rebindable Find shortcut in Settings > Keyboard, for giving `Cmd+F` to the filter bar instead of the editor - - ### Improvements - - - Editor tabs are drawn as a segmented tab picker on every macOS version, so the active tab reads clearly in light appearance, in a background window and on macOS 27 ([#2439](https://github.com/TableProApp/TablePro/issues/2439)) - - A connection opens full screen on iPhone and iPad, with its four sections in a sidebar on iPad ([#2544](https://github.com/TableProApp/TablePro/issues/2544)) - - Size All Columns to Fit no longer slows down with the square of the column count - - A capped MySQL or MariaDB query returns its first rows without fetching the whole result set ([#2427](https://github.com/TableProApp/TablePro/issues/2427)) - - ### Bug Fixes - - **Saving and editing** - - - An edit or a delete on a table with no primary key changed every identical row ([#2107](https://github.com/TableProApp/TablePro/issues/2107)) - - Pressing Cmd+S again during a slow save committed the same statements twice, and switching tabs mid-save cleared the edits of the tab switched to - - Deleting one row of a pasted batch dropped the other rows or saved them with another row's values, and double-clicking a cell after such a delete edited the wrong row - - Undoing a cell edit reverted the wrong row while a column filter was active, and undoing a deleted new row put its values in the wrong columns - - Save reported the number of statements it ran as the number of rows it changed - - A save that deletes a row and reuses its unique value failed on the constraint - - "Ignore foreign key checks" did nothing on SQLite, libSQL and Cloudflare D1 - - A staged drop or truncate ran against the database in front at Save time rather than the one it was staged in, and `DROP TABLE` went out unqualified on Oracle, Dameng, Trino, Snowflake and BigQuery - - **Connections** - - - Background connections were stuck on the session preparation screen after connecting ([#2545](https://github.com/TableProApp/TablePro/issues/2545)) - - Rows, tabs and an enabled toolbar stayed on screen over a connection the connections strip already showed as failed, and Reconnect did nothing once automatic reconnect had given up - - The connections strip was hidden, with its switch commands disabled, whenever the connection on screen was disconnected - - The icon was cut off the entry at the top of a scrolled connections strip, and the strip did not scroll to the entry you switched to ([#2452](https://github.com/TableProApp/TablePro/issues/2452)) - - SSH Test Connection leaked a socket every run, and an SSH tunnel that died from sleep or a dropped network leaked its listening port, socket and session - - **SQL editor** - - - Selection highlight went missing from part of a long selection after scrolling, covered only the first line of a multi-line match, and was painted in the accent colour in an inactive window - - Shift+Arrow extended the wrong end of a selection made by dragging, Shift+double-click and Shift+triple-click did nothing, and double-clicking `=`, `<` or `>` selected nothing - - Drag-select scrolled faster on a mouse than on a trackpad and stalled mid-drag, and a quick drag started a few characters from the press point - - The arrow keys and Shift+Arrow did not work in the JSON, DDL and SQL preview editors - - VoiceOver was told the selection covered only its first line, with no announcement when it moved - - An abandoned editor tab drag reordered the strip anyway and kept that order across relaunch - - **Data grid** - - - Grid cells stayed at their old column positions until the next click after any column geometry change ([#2449](https://github.com/TableProApp/TablePro/issues/2449), [#2446](https://github.com/TableProApp/TablePro/issues/2446)) - - The row-number column could be dragged out of first place, which walked it to the far right on the next refresh - - A reordered column snapped back on the next refresh in the Structure tab and in query results - - A time entered into a date cell was discarded when the stored value carried no time, a timestamp with an offset showed one day in the grid and another in its picker, and confirming a picker without changing anything rewrote the cell - - Keep Open for a preview tab, by double-clicking it in the tab strip or from its contextual menu ([#2436](https://github.com/TableProApp/TablePro/issues/2436)) - - **Keyboard shortcuts** - - - A rebound shortcut silently killed Quit, Minimize, Hide, Settings, Show Toolbar or Enter Full Screen - - A data grid shortcut bound to `Cmd+G` took the key from Find Next with no conflict warning, and `Ctrl+Cmd+J` was accepted while the SQL editor kept it for Jump to Definition - - A shortcut recorded with only Option or Shift beeped with no reason given, against docs that said it would work - - **Databases** - - - MySQL and MariaDB: column charset and collation were lost when reordering a column by dragging its row; KILL went to a different server when the host was spelled `localhost`; Stop froze the app for up to five seconds against an unresponsive server; and a read was re-run after a dropped connection, advancing a sequence or taking a lock twice - - Snowflake: `DATE`, `TIME` and `TIMESTAMP_*` cells showed the raw epoch number, `BINARY` cells showed the hex of their hex, an unreadable date cell opened a picker set to today and overwrote the value on OK, `UPDATE` and `MERGE` reported no rows changed, two saved connections to one account shared a session, and Stop cancelled every other query on the connection ([#2454](https://github.com/TableProApp/TablePro/issues/2454)) - - Computed SQL Server columns were offered as editable and written into the `INSERT`, on both Mac and iOS - - Kafka driver plugin: topics in the grid, consumer group lag, and KafkaQL for seeking and producing ([#2419](https://github.com/TableProApp/TablePro/issues/2419)) - - The structure editor showed an empty Indexes, Foreign Keys or DDL tab with no error when the read failed, offered free-text cells for foreign key actions and index types in Create Table, and compared a generated column equal to a plain one in Compare & Sync - - **Export** - - - A CSV export wrote a real NULL and the text `NULL` identically - - A JSON export emitted a leading-zero string such as `01234` as an unquoted number, which no JSON parser accepts - - A SQL `INSERT` export quoted identifiers for ANSI on every engine, which MySQL and MariaDB reject outright - - **iPhone and iPad** - - - Insert Row wrote every column, which blocked a `NOT NULL` column with a default, wrote generated columns, offered NULL on `NOT NULL` columns, left both halves of a composite integer primary key out, discarded values typed before the column list loaded, and was offered on Redis where it can only fail ([#2543](https://github.com/TableProApp/TablePro/issues/2543)) - - PostgreSQL and Redshift read another schema's table of the same name for columns, indexes and foreign keys, and SQL Server used the login's default schema rather than the one the toolbar showed, Truncate and Drop included - - `tablepro://` deep links were ignored, from the widget, a Live Activity or a shortcut - - A connection's Tables tab had no search field, and a search from one connection or table still filtered another's list ([#2544](https://github.com/TableProApp/TablePro/issues/2544)) - - ### Security - - - SQL injection through a Snowflake schema or routine name containing a backslash - - Safe Mode on iPhone and iPad let a write run when it followed a comment or a CTE - - - - ### Improvements - - - **Row limits stop the fetch**: A query result stops pulling rows once it hits the limit on MySQL, PostgreSQL, SQLite, MSSQL, Oracle, Cassandra, Redis, DynamoDB, BigQuery, Snowflake, Trino and ClickHouse, instead of reading the whole result and throwing the rest away ([#2427](https://github.com/TableProApp/TablePro/issues/2427)) - - ClickHouse reads a capped result in one request, without the separate `LIMIT 0` round trip for the columns - - Structure editor undo granularity is set by the operation you performed rather than by run loop timing - - ### Bug Fixes - - - Row limit ignored for a parenthesised `SELECT`, a `TABLE` statement or a `VALUES` list ([#2427](https://github.com/TableProApp/TablePro/issues/2427)) - - Cancelling an export or a schema compare left the database still sending rows on MySQL, PostgreSQL, MSSQL, Oracle, Cassandra and Trino - - An aborted PostgreSQL read charged its leftover rows to the next query on that connection - - Syntax error when sorting a query result whose SQL ends in `LIMIT`, and your own `LIMIT` dropped when sorting replaced an existing `ORDER BY` - - Fetch All sending the previous run's parameters after a query that had none - - A SQLite `UPDATE` or `DELETE` alone in a query tab reporting success instead of the rows affected - - Stall on switching between two loaded table tabs, lost scroll position on the way back, and a table statistics command on every switch ([#2424](https://github.com/TableProApp/TablePro/issues/2424)) - - Another table's size and row count in Table Info when its statistics arrive late ([#2424](https://github.com/TableProApp/TablePro/issues/2424)) - - Active editor tab barely distinguishable from the inactive ones in light appearance, no selected tab at all in a background window on macOS 14 and 15, and the tab strip ignoring Increase Contrast and Reduce Transparency ([#2428](https://github.com/TableProApp/TablePro/issues/2428)) - - Crash when a file changes in a linked SQL folder or a linked connection folder, and on quit with one configured ([#2432](https://github.com/TableProApp/TablePro/issues/2432)) - - iOS: crash the first time the system reports memory pressure - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-68) - - ### New Features - - - **Compare & Sync**: Compare two databases side by side, over tables, views, procedures, functions and triggers, or over row data, and apply what differs. This is a Starter feature ([#721](https://github.com/TableProApp/TablePro/issues/721)) - - **Procedures, functions and triggers**: Triggers get their own sidebar section beside Procedures and Functions, and all three now list on MSSQL, Oracle, SQLite, ClickHouse, DuckDB, Snowflake, BigQuery, Cassandra, LibSQL, Cloudflare D1, Teradata and Dameng. Each opens in a read-only source viewer with Copy, Export and Open in Editor, and all three are reachable from the quick switcher ([#2383](https://github.com/TableProApp/TablePro/issues/2383)) - - **Device and team management**: **Settings > License** replaces Settings > Account. Release a seat on another Mac, read your team roster with seats used against seats bought, and see each activated Mac's last use and macOS version. Copy Key copies the real key and keeps it out of clipboard history - - **EXPLAIN Compare**: A fourth plan mode that reports what changed against an earlier run of the same query, and pinning a saved plan keeps it through history cleanup ([#2380](https://github.com/TableProApp/TablePro/issues/2380)) - - **Beancount directives**: Read-only tables for `directives`, pads, named queries and custom directives, carrying account booking, note tags and links, and balance assertion details ([#2399](https://github.com/TableProApp/TablePro/issues/2399), [#2413](https://github.com/TableProApp/TablePro/issues/2413), [#2415](https://github.com/TableProApp/TablePro/issues/2415)) - - **Table load history**: TablePro keeps 7 days of local load timings in Application Support so you can see which tables are slow. Nothing leaves the Mac ([#2395](https://github.com/TableProApp/TablePro/issues/2395)) - - MCP clients gain a schema-wide `list_triggers`, plus `return_type` and `language` on `list_routines` - - **Support TablePro** in the Help menu, the welcome window and the sidebar - - ### Improvements - - - The data grid draws its own cells and column separators instead of building a view for each one, so a result with hundreds of columns opens at once and holds a fraction of the memory ([#2381](https://github.com/TableProApp/TablePro/issues/2381)) - - The inline cell editor scrolls a long line instead of wrapping it ([#2381](https://github.com/TableProApp/TablePro/issues/2381)) - - Row inspector fields, cell popovers and the Compare row diff follow the Data Grid Font setting, so one value reads the same in the grid, its editor and the inspector ([#2393](https://github.com/TableProApp/TablePro/issues/2393)) - - The license pane states an expired, suspended or unverified license inline, with one action and a purchase link, instead of a banner floating over it. Sync Paused is a separate state for a license the app could not check, and one Refresh replaces the old Refresh and Check Status pair - - The license key is masked and not selectable, so a screen share cannot carry the whole credential - - iCloud Sync has its own Settings pane, and Linked Folders moved to General - - The PRO badge beside a gated control names what the feature does and links to the pricing page - - A database stays in the connections strip until you close its entry, and Close ends the connection only on the last one - - The connection color shows as a badge behind the connection name and a dot on the workspace rail icon, with deeper fills on tag badges ([#2398](https://github.com/TableProApp/TablePro/issues/2398)) - - Autocomplete and the query editor's database picker follow the tab's own database and schema - - Beancount postings keep their own flag, price, and resolved lot date and label; connections report the active `rledger` or Python Beancount version; and third-party ledger plugins are skipped unless you turn on Run Ledger Plugins - - Turkish, Vietnamese, Simplified Chinese and Traditional Chinese cover every string that was still in English - - ### Bug Fixes - - - A table with 500 columns pinned a core for 20 seconds and took a gigabyte to open. Scrolling a wide result sideways flickered, left blank columns and an unpainted gap, and opening the inline editor cost a second ([#2381](https://github.com/TableProApp/TablePro/issues/2381)) - - Find, the arrow keys, the inline editor and Size All Columns to Fit could not reach a column scrolled off the side of a wide result - - `Return` opened no editor on a row selected with the arrow keys, and `Tab` or `Shift+Tab` out of a row's end did nothing - - Opening a table in a window with no tabs showed an empty grid, one table click ran its query twice, and a closed tab left its query counted as running ([#2342](https://github.com/TableProApp/TablePro/issues/2342)) - - Exporting two same-named tables from different schemas to SQL crashed, wrote one schema's rows into the other's table, and dropped columns and foreign keys after the first schema ([#1968](https://github.com/TableProApp/TablePro/issues/1968)) - - One of two PostgreSQL function overloads was missing from the sidebar and Show DDL opened an arbitrary one, while MySQL Show DDL read the session database instead of the one being browsed ([#2383](https://github.com/TableProApp/TablePro/issues/2383)) - - Routine tooltips, VoiceOver labels and Copy with Signature showed a return type in place of the argument list, and duplicate routine rows in the flat sidebar took the selection back to the first ([#2383](https://github.com/TableProApp/TablePro/issues/2383)) - - MySQL triggers lost their definer, `WHEN` clause and ordering in the Structure tab, and Oracle triggers showed a header with no body - - Turning on iCloud Sync with many saved column layouts froze the app, and the sync record cache filled the preferences file past the 4 MB the system allows, which failed every settings write - - iCloud Sync called an unverified license missing, reported a live sync after the license was removed from that Mac, and returned to Synced when sync was turned off mid-sync - - The welcome window offered Activate License to a Mac that already held one and had just been offline - - The license server was never contacted again after removing and re-adding a license in one session - - The device list error survived a later successful refresh, and a renewal warning counted zero days above a status that already read Expired - - A previous team's shared connections and saved queries survived deactivation - - The connections strip disappeared when you clicked the entry you came from, an entry in another window raised that window without showing the connection, a disconnected connection's entries dropped to the bottom or all but one left, and a connection opened from a file or URL lost its entries when its session ended - - Bulk tab close saved the tab on screen instead of the tabs being closed, and asked about another database's unsaved work - - A connection's color and name reached the toolbar and workspace rail only after the next reconnect, the connection dot was invisible in the query history drawer and the compare strip, and a failed connection's warning icon wore the engine color ([#2398](https://github.com/TableProApp/TablePro/issues/2398)) - - The JSON viewer kept its old font after a font, theme or text-size change, and hex dumps wrapped mid-line instead of keeping their columns aligned ([#2393](https://github.com/TableProApp/TablePro/issues/2393)) - - AI inline suggestions were written without the connection's schema, and columns were missing for any table named with a capital letter - - Case-insensitive filters and completion on Redshift fell back to PostgreSQL's ASCII-only ILIKE - - The PGlite connection form offered SSH, SSL, Cloudflare Tunnel and SOCKS panes, and a password field - - Redshift and CockroachDB system databases were listed in the sidebar as ordinary user databases - - - - ### New Features - - - **Favorite databases**: Tag a favorite database with the environment it belongs to, filter the Favorites sidebar by that tag, open one directly, and keep the whole set in sync across your Macs through iCloud ([#1553](https://github.com/TableProApp/TablePro/issues/1553)) - - **Open in Window**: Any text field in the row inspector can be opened in its own window, for reading or editing a long value on a bigger surface - - ### Improvements - - - A large text value in the row inspector now scrolls in a resizable text view instead of being clipped, and stays selectable and copyable when the row is read-only - - The inspector picks its multi-line editor from the value itself, so a large `VARCHAR(MAX)`, `NCLOB` or ClickHouse `Nullable(String)` value is no longer stuck on one line - - Right-clicking a read-only inspector field now offers Copy Value instead of an empty menu - - Background tabs release their row data more carefully, keeping query results, pinned results, unsaved edits and anything still loading - - ### Bug Fixes - - - A background tab whose column metadata arrived after its rows were released showed an empty grid and never reloaded. It now reloads when you return to it - - The result display cache could exceed its memory budget when a cached row's formatted values grew - - Undo, redo, a theme change and a display-format change left the data grid reformatting every cell as you scrolled - - Autocomplete no longer bulk-loads every column of a schema too large to cache. On those databases column suggestions arrive per table as you reference one - - A saved connection whose SSH settings were written before the agent socket field existed failed to load and disappeared from the list - - Switch Connection and Open Database did nothing on a narrow window, or once you removed their toolbar button. They now open either way - - **Editor crashes on stale text**: The editor no longer crashes when layout, syntax highlighting, accessibility, an input method, dictation or Look Up reads text that a newer edit had already removed ([#2338](https://github.com/TableProApp/TablePro/issues/2338), [#2339](https://github.com/TableProApp/TablePro/issues/2339), [#2340](https://github.com/TableProApp/TablePro/issues/2340)) - - A search highlight or diagnostic underline now disappears with the text it marks, instead of staying put or jumping elsewhere in the editor ([#2341](https://github.com/TableProApp/TablePro/issues/2341)) - - The toolbar no longer sits on "Executing…" after a query ends, so Stop is not the only way back, and the session context buttons no longer empty out for the length of every query ([#2342](https://github.com/TableProApp/TablePro/issues/2342)) - - A SQLite query that stops early, because the database is locked or the volume returns an error, now says so instead of showing an empty table ([#2355](https://github.com/TableProApp/TablePro/issues/2355)) - - Stop now ends a SQLite query that is waiting on a database another program has locked, instead of leaving it to wait out the query timeout ([#2363](https://github.com/TableProApp/TablePro/issues/2363)) - - The XLSX, MQL and SQL Import plugins linked to a documentation page that did not exist. They now point at Import & Export - - - - [Read the announcement](https://tablepro.app/blog/tablepro-0-67) - - ### New Features - - - **Code folding**: Fold statements, table bodies, CTEs, subqueries, `BEGIN` blocks and multi-line comments from the editor gutter. A collapsed region becomes a chip you can hover to peek at, folds are remembered when a tab is reopened, and Fold All, Unfold All and Toggle Fold are in the Query menu. It also works in the DDL and trigger views, the SQL import preview, the AI review sheet, chat code blocks and the JSON cell viewer - - **Run one statement at a time**: The statement your cursor is in gets a faint background, and a run button appears in the gutter beside every statement. Both can be turned off in **Settings > Editor** ([#2278](https://github.com/TableProApp/TablePro/issues/2278)) - - **Statement navigation**: `Ctrl+Cmd+Left` and `Ctrl+Cmd+Right` step between statements, `Ctrl+Cmd+Enter` runs the current one and moves to the next, and `Option+Shift+Up` and `Option+Shift+Down` extend the selection a statement at a time ([#2279](https://github.com/TableProApp/TablePro/issues/2279)) - - **Named result tabs**: Each result is named after the statement that produced it, using a leading comment when you wrote one. Picking a result moves the editor cursor to the statement behind it ([#2280](https://github.com/TableProApp/TablePro/issues/2280)) - - **Find in results**: `Cmd+F` on a table tab opens a find bar over the grid. The counter says what it searched, so "3 of 12 on this page" is never mistaken for an answer about the whole table, and Search All Rows turns the term into a server-side filter - - **Charts**: Draw query results as native bar, line, area and scatter charts, with a real time axis for date and timestamp columns. The chart keeps its type and axes across a page turn, a sort and a re-run. This is a Starter feature - - **Back and Forward**: Step back through the tables you followed a foreign key into, from the toolbar or the View menu on `Ctrl+Cmd+[` and `Ctrl+Cmd+]`. Back restores the filters, sort, page and selected row you left behind, and stands down while a tab holds unsaved edits ([#2316](https://github.com/TableProApp/TablePro/issues/2316)) - - **Background notifications**: TablePro tells you when a slow query, import, export, backup or row save finishes while you are in another app, and clicking the notification selects the tab that ran it. Configure it in **Settings > Notifications** ([#2265](https://github.com/TableProApp/TablePro/issues/2265)) - - **Redis Sentinel and Redis Cluster**: Pick a Connection Mode in the connection form. Sentinel follows a failover to the new primary; Cluster reads the shard map, routes each command to the shard that owns its key, and follows MOVED and ASK redirects ([#1021](https://github.com/TableProApp/TablePro/issues/1021)) - - **MongoDB filters on nested fields**: Filter on paths like `customer.country` or `items.sku`, with **any element** and **same element** matching for fields inside an array, and values compared as the type the field actually holds ([#2315](https://github.com/TableProApp/TablePro/issues/2315)) - - **DuckDB over data files**: Point a DuckDB connection at a Parquet, CSV, TSV, JSON or NDJSON file. It opens read-only and never writes back - - **MCP overhaul**: The server grew from 19 tools to 46, gained prompt templates built from the live schema, argument completion from real table and column names, and change notifications. It speaks the 2026-07-28 protocol revision, with the two previous revisions still supported - - **Korean**: Full Korean localization for macOS, iPhone and iPad, with a 한국어 option in General settings (#2219, #2264) - - **Third-party plugins**: A plugin signed with an Apple Developer ID installs after you agree to trust that developer by name. Unsigned and ad-hoc signed bundles are still refused - - **Execution log**: Every database operation TablePro authorizes is recorded locally, statements stored as digests rather than text, and hash chained so an edited or removed entry can be detected. Nothing leaves the Mac - - **Managed Safe Mode**: An administrator can set a minimum Safe Mode level for every connection through a macOS configuration profile - - Editor tabs can be dragged to reorder them, with **Move Tab Left** and **Move Tab Right** on each tab's menu - - Swipe left on a saved connection in the welcome window to reveal Edit and Delete ([#2310](https://github.com/TableProApp/TablePro/issues/2310)) - - Double-click a table, or press `Return` on it, to keep its tab instead of reusing the preview tab ([#2235](https://github.com/TableProApp/TablePro/issues/2235)) - - Execute All Statements is on the Execute button's menu, alongside `Cmd+Shift+Enter` ([#2230](https://github.com/TableProApp/TablePro/issues/2230)) - - **View > Result View** switches the results pane between Data, Structure, JSON and Chart from the menu bar - - **Help > Acknowledgements** lists the 38 open source libraries TablePro ships, with the exact version, license and full license text of each - - ### Improvements - - - The results status bar was rebuilt on one fixed height. The row count sits on the left, the controls keep their place and dim while a table loads instead of vanishing and returning, and Add Row moved to the toolbar - - Row counts and number grouping read correctly in every language. One sentence used to be assembled from seven phrases, so English said "1 rows" - - An estimated row total is now marked as an estimate, and Last and All rows wait until you run Count Exactly - - `Cmd+F` on a table tab opens the find bar. The filter panel keeps `Cmd+Option+F` and its funnel button - - Picking a database in the connections strip returns you to the tab you last used in it ([#2217](https://github.com/TableProApp/TablePro/issues/2217)) - - Two tabs showing same-named objects from different databases carry the database in their names, so `orders` reads as `app.orders` and `staging.orders` ([#2217](https://github.com/TableProApp/TablePro/issues/2217)) - - Save in the "Do you want to save changes?" prompt now closes what you asked to close once the save lands - - A column value filter survives switching result mode and switching tabs ([#2251](https://github.com/TableProApp/TablePro/issues/2251)) - - The JSON and PHP tree filters both ignore accents, so `cafe` finds `café` ([#2204](https://github.com/TableProApp/TablePro/issues/2204)) - - The connection form's Browse button offers the file kinds the driver actually opens instead of every file on disk - - TablePro is built in the Swift 6 language mode. The Mac app, the iPhone and iPad app, all 31 plugins and the shared packages are now checked for data races by the compiler - - ### Bug Fixes - - - Forward slashes in a nested value are no longer escaped as `\/`, so a path or MQTT topic like `device/state/up/122` reads the way it is stored ([#2322](https://github.com/TableProApp/TablePro/issues/2322)) - - A MongoDB filter TablePro could not translate quietly matched the whole collection; it now matches nothing. The raw filter row works, and Match Case applies to text fields only ([#2315](https://github.com/TableProApp/TablePro/issues/2315)) - - **Preview Referenced Row** reads the cell you focused instead of counting columns from the left, and works on SQL Server. On iPhone and iPad it works on SQL Server and Oracle - - Import and export result alerts stay on screen instead of closing with the progress sheet behind them ([#2314](https://github.com/TableProApp/TablePro/issues/2314)) - - A failed import reports its line number, statement and the database's own message instead of only the rollback error ([#2314](https://github.com/TableProApp/TablePro/issues/2314)) - - An import no longer counts rows it dropped, and Skip and Continue keeps going after an unreadable line in a JSON file - - Importing into a new table can be retried after a failure, reusing and clearing the table the failed attempt created - - TablePro stays out of the Dock and the app switcher when an MCP client starts it - - Deleting a connection you published to the Team Library no longer deletes your own local copy instead, taking its saved passwords with it - - Deleting a connection with saved queries always warns that they go with it ([#2310](https://github.com/TableProApp/TablePro/issues/2310)) - - Connections in a linked folder or the Team Library open on double-click and on `Return`, instead of doing nothing at all - - Editing a connection keeps its favorite mark, its place in the list, and the AI tools you had always allowed for it - - A column whose cells carry an action button no longer cuts its value short, and Size to Fit measures the loaded page instead of about 30 rows of it ([#2303](https://github.com/TableProApp/TablePro/issues/2303)) - - 235 Turkish strings that were marked as translated while still holding English text now read in Turkish - - Opening an Oracle connection for the first time no longer leaves the object list spinning forever ([#2294](https://github.com/TableProApp/TablePro/issues/2294)) - - An object list that cannot load says so and offers Retry, instead of retrying silently every 15 seconds with nothing on screen ([#2294](https://github.com/TableProApp/TablePro/issues/2294)) - - A database type is drawn in one colour everywhere. Two colour tables had drifted apart on 12 of the 23 types they both named - - Answering **Save** on the unsaved-changes prompt applies the staged structure edits it was asking about, from any view, and leaves the tab open when the save is refused or rejected - - Opening one table in two tabs gives each tab its own structure editor, so an edit staged in one no longer lands in the other - - Clicking another table no longer silently discards staged structure edits - - A `BEGIN ... END` body runs whole from the editor instead of being split at every semicolon inside it, and a semicolon inside a MySQL `#` comment is no longer read as a statement boundary ([#2278](https://github.com/TableProApp/TablePro/issues/2278)) - - A failed refresh no longer empties the stored procedure and function lists - - Switching to a PostgreSQL database you cannot open now reports the failure and leaves the connection pointing at the database it came from - - Plugins installed from the registry are notarized, so Gatekeeper stops blocking them. Installing from a ZIP clears the quarantine flag from the whole bundle - - DuckDB lists its databases, schemas, tables and columns on a Mac that cannot reach the internet. Every metadata query used to depend on a function fetched from the extension registry ([#2285](https://github.com/TableProApp/TablePro/issues/2285)) - - A DuckDB UUID, ENUM, list, struct, map or time zone aware timestamp column no longer crashes TablePro - - A DuckDB view opens as a statement you can run, a failed connection releases its file instead of locking it until quit, a mistyped path says the file is missing, and a new DuckDB connection can be saved - - Opening a table on Dameng, Oracle, BigQuery, Elasticsearch and etcd no longer puts up a "Switch Failed" alert ([#2262](https://github.com/TableProApp/TablePro/issues/2262)) - - Switching schema on Dameng, Oracle, BigQuery, Snowflake and Trino re-reads only the routines instead of the whole object list ([#2262](https://github.com/TableProApp/TablePro/issues/2262)) - - Dameng results are complete instead of stopping at the server's first 32KB batch. A 20,000 row table used to read back as 662 rows with nothing saying anything was missing ([#2262](https://github.com/TableProApp/TablePro/issues/2262)) - - Dameng tables with a `CLOB` or `BLOB` column read the right columns, rows and values, column comments show up, creating a table with an index or comment works, and connections recover after a query is stopped ([#2262](https://github.com/TableProApp/TablePro/issues/2262)) - - Redis reports what the server actually answered instead of clearing a refused write from the grid as if it had been saved - - `SET key value KEEPTTL` keeps the expiry it asks to keep, and a command whose reply timed out is only re-sent when it provably never reached the server - - Redis Cluster routes `OBJECT`, `MEMORY`, `SORT`, `GEORADIUS`, `ZUNIONSTORE` and `ZINTERSTORE` to the right shard on Redis 6 - - Floating point values show the shortest digits that identify the stored number exactly, so a stored `0.1` no longer reads as `0.10000000000000001` - - Editing a MongoDB row keeps each field's stored type, so a decimal is not written back as a double and a 64-bit integer is not narrowed to 32 - - A nested value too long to show in full is no longer written back or exported as a fragment - - Query Insights keeps each aggregate's query text and error inside the selected connection, source and date range ([#2268](https://github.com/TableProApp/TablePro/issues/2268)) - - A plugin's own connection fields reach the connection form, so Redis gets its Key Separator field and its ElastiCache IAM section back ([#1021](https://github.com/TableProApp/TablePro/issues/1021)) - - Rows past a driver's row-count estimate are reachable, All rows loads all of them, and changing rows-per-page on any page but the first no longer strands you - - Turning a page while Count Exactly is running no longer leaves a spinner that never stops - - Editing a cell in a result you clicked back to writes to that result's own table. A tab can hold several results, and an edit on an earlier one used to be saved against the newest - - A result whose table TablePro cannot identify no longer looks editable, and running several statements no longer carries the previous run's primary key over to the new table - - Rows loaded by Fetch All stay with the result they were loaded for, and switching between results asks before discarding unsaved edits - - JSON result mode shows the rows the grid is showing, in the same order, and resolves a selection to that same row. Rows marked for deletion no longer appear there ([#2251](https://github.com/TableProApp/TablePro/issues/2251)) - - `Cmd+F`, the Filters button and `Cmd+Option+F` behave correctly in JSON result mode, and Add Row, Duplicate Row, Paste and Delete no longer collapse the view to the row they touched ([#2244](https://github.com/TableProApp/TablePro/issues/2244)) - - Every restored tab keeps the columns you hid, its caret position, its sort and its page number, not just the tab that was in front when you quit - - A restored tab reopens on the rows it was showing. The page number used to be saved without the page size it was counted in - - Scrolling a result with hundreds of columns is no longer slow. A 500-column table carried about 18,000 live cells and laid all of them out on each frame ([#1219](https://github.com/TableProApp/TablePro/issues/1219)) - - A column you hid keeps its place in the column order instead of returning at the far right - - The date picker on an empty date or timestamp cell starts at your own clock and writes your own time ([#2241](https://github.com/TableProApp/TablePro/issues/2241)) - - Timestamps written with a space before the offset, or with fractional seconds, follow your chosen date format instead of showing as raw text - - **Open in New Tab** opens a second tab for a table that is already open, so you can browse it under two different filters ([#2235](https://github.com/TableProApp/TablePro/issues/2235)) - - Following a foreign key into a new tab no longer overwrites the filters on a tab already showing that table - - Opening a table from the sidebar while the object list is still loading opens it, and opening one from Favorites gives it a tab of its own - - Sorting a column keeps working after you hide or show columns, and **Hide All** hides every column of a MongoDB collection ([#2234](https://github.com/TableProApp/TablePro/issues/2234)) - - MongoDB, Cassandra and Trino connections can name a database, keyspace or catalog in the connection form ([#2234](https://github.com/TableProApp/TablePro/issues/2234)) - - Opening a `.sql` file that is already open shows its tab instead of replacing what you have typed in it - - Table maintenance from the object browser runs against the database the table you picked lives in - - A table that exists in two databases can be opened in both, and Server Dashboard, Users & Roles, Query Insights, ER Diagram and a linked SQL file select the tab they already opened ([#2217](https://github.com/TableProApp/TablePro/issues/2217)) - - Table favorites and saved queries sync through iCloud. Their record types had never been created, so every one was rejected on upload and retried forever - - A connection carries its favorite mark, every tag, and its AI rules and always-allowed tools to your other Macs, and an SSH profile with a jump host syncs - - Closing a tab holding unsaved cell edits, `.sql` changes, staged structure changes or staged user and role changes asks before discarding them. Cell edits were the worst case, because nothing brought them back - - Staged structure changes and a table definition in progress survive switching tabs - - Switching connection no longer empties the SQL editor or kills its undo, syntax highlighting, shortcuts and Vim mode for the rest of the tab's life ([#2236](https://github.com/TableProApp/TablePro/issues/2236)) - - `Cmd+W` closes the Settings and Integrations windows, the JSON and PHP viewers, the connection form and the column inspector - - The Settings window is titled after the pane you are on, and refuses to be resized smaller than a pane can draw - - The empty Favorites sidebar fits its width on macOS 15 instead of running past both edges - - Open Quickly no longer draws a faint rectangular outline around its Liquid Glass surface on macOS 26 and later ([#2321](https://github.com/TableProApp/TablePro/issues/2321)) - - iOS: a value containing a backslash, a newline or a carriage return is stored as you typed it on PostgreSQL, Redshift, SQL Server, SQLite, DuckDB and Oracle - - iOS: Add Row to Table and Add Rows to Table write to the database or schema you picked in Shortcuts, and all three actions are found by searching "TablePro" - - ### Removed - - - MCP remote access. The server binds to this Mac only. Its certificate named only localhost, so no other device could ever have verified it - - ### Security - - - A read-only MCP token can no longer run a statement that writes. A statement TablePro does not recognize is treated as a write, and the ones that reach the filesystem or run a program are refused - - A token limited to some connections no longer sees the names, hosts, ports and usernames of the others - - Safe Mode asks again for MCP, where every path used to pre-clear the confirmation - - With authentication turned off, a local caller is read-only and cannot administer anything - - Secrets belonging to separately distributed drivers are stored in the Keychain and stripped from exported connections. Existing values move to the Keychain on the next save - - The activity log records which token made each call, stores statements as digests, is hash chained, and is no longer readable by other accounts on the Mac - - Repeated bad tokens are rate limited, including requests that carry no credentials at all - - A pairing link is checked before it is used and its destination shown before you approve it, and an MCP session identifier is no longer usable as a credential - - - - ### New Features - - - **Tree sidebar layout**: Groups each database or schema's tables, views, materialized views, foreign tables, procedures and functions into collapsible folders ([#1590](https://github.com/TableProApp/TablePro/issues/1590)) - - **Dameng DM8**: A downloadable plugin with schema browsing, table editing, metadata, DDL, transactions, EXPLAIN, query cancellation and a query timeout. Stopping a query closes its connection, and TablePro reconnects on the next one (#1671, #2003, #2010) - - **Microsoft Entra ID for SQL Server**: Sign in through your browser on Azure SQL Database, Azure SQL Managed Instance and SQL Server 2022, with multifactor authentication and Conditional Access honoured. Set it up on the Mac; iPhone and iPad prompt to sign in once the connection syncs over - - **Query Insights**: A Starter feature in **Database > Query Insights** that summarizes the query history on your Mac: which queries you run most, which are slowest, which got slower and which fail. Queries that differ only by their values count as one, and nothing leaves the Mac ([#2107](https://github.com/TableProApp/TablePro/issues/2107)) - - ### Improvements - - - The Quick Switcher is now **Open Quickly**, in the **File** menu under Open File. `Cmd+Shift+O` is unchanged and still rebindable in **Settings > Keyboard** ([#2185](https://github.com/TableProApp/TablePro/issues/2185)) - - `Cmd+Return` opens the selected item in a new tab in Open Quickly, alongside `Option+Return` ([#2185](https://github.com/TableProApp/TablePro/issues/2185)) - - Open Quickly is drawn on the system Liquid Glass material on macOS 26 and later, and keeps the blurred popover material on macOS 14 and 15 - - Open Quickly's scope filters are a segmented control inside the panel, instead of buttons that vanished as soon as anything was listed, which left four of the five scopes unpickable with the mouse - - Open Quickly has a footer for `Return`, `Cmd+Return`, `Cmd+1` to `Cmd+5` and `Escape`. The `Escape` hint reads **Clear** while the field has text and **Close** when it is empty - - A connection that fails because a Microsoft Entra ID or AWS SSO sign-in expired now offers to sign in again and reconnects for you, both when you open it and from the connection form's Test button - - The editor tab bar moved into the window chrome below the toolbar and lines up with the content pane, with its tabs in a filled track and the selected tab raised out of it. It still appears only once a connection has a second tab - - Tree layout opens a connection with its current database and schema expanded, unless the connection already remembers what you left open - - Turkish, Vietnamese, Simplified Chinese and Traditional Chinese cover 298 strings that were still showing in English, including the Query Insights tab, the Window and Help menus, the query plan viewer, the connection progress steps and the Microsoft Entra ID sign-in prompts - - Five on-screen strings dropped their em dash, including the JSON and PHP viewer titles and the expired-license banner in **Settings > Account** - - DuckDB connections now report a database name, so their saved column widths, per-table filters, favorites and recent tables reset once - - ### Bug Fixes - - - Right-clicking a database and choosing Export now exports from the database you picked, with everything in it selected, instead of listing another database's tables. On DuckDB and PGlite, where a second connection cannot reach another database, Export is offered only for the active one - - On Oracle, Dameng and BigQuery, "Close Tabs for Other Databases" closed every query tab; it now closes only tabs in a different schema - - A `tablepro://` link applies the database first and the schema second, instead of applying a database name as a schema on connections that have schemas - - On connections with schemas, the toolbar shows the database and the schema side by side, each opening its own list, and the schema picker is back at the foot of the object list ([#2196](https://github.com/TableProApp/TablePro/issues/2196)) - - Data-grid columns reserve space for their trailing editor and foreign-key actions, so the foreign-key arrow and the enum dropdown chevron no longer widen a column after the rows are drawn. A column you resized keeps the width you gave it - - Open Quickly reuses the object list it already built instead of re-querying the database and schema lists and 200 rows of query history each time it opens, which over an SSH tunnel is the whole time the panel sits empty - - Open Quickly's Recent section now includes materialized views, foreign tables, system tables, partitioned tables and external tables, ranks them higher the more you open them, and tells apart two objects with the same name in different schemas - - Opening a table from Open Quickly reuses the tab that is already open, instead of opening a second tab for a same-named table in another schema - - Open Quickly says it is loading instead of "No results", both while the object list is being fetched and while a search is being ranked - - Open Quickly's `Cmd+Return`, `Cmd+1` to `Cmd+5`, `Ctrl+J`, `Ctrl+K`, `Ctrl+N` and `Ctrl+P` work with Caps Lock on, and the numeric keypad's Enter works with `Cmd` - - Toolbar buttons show the shortcut you bound in **Settings > Keyboard** in their tooltip and overflow menu, and the Database and Preview buttons keep their shortcut hint after you switch connection ([#2185](https://github.com/TableProApp/TablePro/issues/2185)) - - The column value filter, date and time picker, array editor, type picker, binary viewer and sidebar database filter now open against the control they belong to, instead of above and to the left of it - - The array, JSON, hex, date and type editors and the column value filter close when the rows change, so an editor left open while sorting, refreshing or filtering can no longer save onto a different row - - JSON results, Copy as JSON and JSON export no longer crash on unsigned or wider-than-64-bit integers, and keep every digit of large integers and high-precision decimals - - Pasting a large block of text into the query editor no longer crashes the app, and the pasted text keeps its syntax highlighting (#2158, #2172) - - The flat sidebar keeps its Materialized Views, Foreign Tables, Procedures and Functions sections while a connection reconnects after a dropped SSH or proxy tunnel ([#2173](https://github.com/TableProApp/TablePro/issues/2173)) - - `Cmd+C` in the query editor with nothing selected copies the current line instead of emptying the clipboard, and paste accepts RTF and file-path clipboards ([#2172](https://github.com/TableProApp/TablePro/issues/2172)) - - Sixteen-byte SQL binary columns respect their Display As setting in the grid, the row inspector, VoiceOver and single-cell copy, and keep it across a refresh ([#2157](https://github.com/TableProApp/TablePro/issues/2157)) - - A very long single line no longer stalls the app or eats memory, in the JSON value viewer, chat code blocks, the SQL review sheet, and the SQL editor with Word Wrap on - - The data grid picks up a theme or appearance change made while it is on screen, including an open cell editor, deleted and newly added rows, and the foreign-key arrow and dropdown chevron - - Format Query no longer crashes on an unclosed string literal ending in a backslash, and leaves an unclosed `/*` comment alone - - Clicking the right edge of the SQL editor, a strip as wide as 140 points, puts the caret on that line, and text selection works there in the trigger editor, the JSON view, the structure DDL, the SQL review sheet, the import preview and chat code blocks ([#2156](https://github.com/TableProApp/TablePro/issues/2156)) - - Query results are editable again when the `SELECT` aliases its table, when the query spans several lines, starts with a comment, or ends in `FOR UPDATE` ([#2150](https://github.com/TableProApp/TablePro/issues/2150)) - - `UNION`, `EXCEPT` and `INTERSECT` results are read-only now instead of writing an edit into one branch, as are results from a join, a subquery, a CTE, a `FOR SYSTEM_TIME` read, and `FROM ONLY` - - Results stay editable when you write the schema in front of the table, as long as it is the schema the connection is browsing - - The connection window no longer draws a rule under its toolbar, which divided two areas of the same colour - - Empty Structure tabs and structured-value errors keep their toolbar at the top and center the message in the content area below it - - The sidebar star updates as soon as you add or remove a favorite table, and Save Changes and Preview update as soon as data-grid changes are added, undone, saved or discarded - - Content stays readable on the blue selection fill in the connection switcher, the database switcher and the query history drawer ([#2140](https://github.com/TableProApp/TablePro/issues/2140)) - - Refreshing the database switcher keeps the list on screen instead of showing a spinner, and a failed refresh no longer hides the databases you were already looking at - - The database switcher keeps your highlighted database when its details finish loading, and no longer highlights a database the search filter hides - - Right-clicking a day heading in the query history drawer no longer clears the selected entry, and right-clicking a row in the connection switcher shows its menu - - The query history drawer stops re-reading history once you close it, and a drawer that starts closed does not load until you open it - - Query History's "Load in Editor" and "Run in New Tab" work again, from the buttons, the right-click menu, and `Return` or a double-click on a row - - "Run in New Tab" runs the query it opens, and asks first when that query writes, even with safe mode set to Silent - - A history entry opens in the window of the connection it was recorded against, in a tab bound to its own database - - Clearing query history from the drawer says what it will actually delete, instead of promising the connection's whole history when the source filter spares table browsing, row edits, imports and AI queries - - When TablePro cannot open the query history store, the drawer says so and that your history is still on disk, instead of showing "No Query History" - - DuckDB `TIMESTAMP_S`, `TIMESTAMP_MS` and `TIMESTAMP_NS` columns show their stored value with sub-second digits, not `1970-01-01 00:00:00` on every row ([#2130](https://github.com/TableProApp/TablePro/issues/2130)) - - DuckDB `UUID`, `ENUM`, `BIT`, `LIST`, `STRUCT`, `MAP`, `ARRAY` and `UNION` columns show their value instead of an empty cell ([#2130](https://github.com/TableProApp/TablePro/issues/2130)) - - Querying two DuckDB tables that share a column name no longer repeats one table's value in both columns ([#2130](https://github.com/TableProApp/TablePro/issues/2130)) - - DuckDB tables outside the `main` schema are visible again, with every schema listed in the sidebar and `ATTACH`ed databases shown alongside the one you opened ([#2131](https://github.com/TableProApp/TablePro/issues/2131)) - - An `ATTACH`ed DuckDB file's tables stay out of the database you opened when the two share a schema name, and a DuckDB table in the default schema is titled `orders` again rather than `main.orders` - - **File > Save As...** on a table, structure or diagram tab, and **File > Export > Export Results...** with no rows to export, are dimmed now instead of doing nothing when chosen - - A license the server reports as suspended, expired or no longer activated on this Mac pauses Pro features at that check, instead of running on for up to a month. **Check Status** in **Settings > Account** says what the server replied - - A license bought with an email address containing a slash can be activated now, instead of telling you to update the app - - ### Removed - - - **Settings > Data** no longer lists saved table layouts and filters. TablePro still remembers both; reset columns from the table's Columns popover and filters from the filter panel - - ### Security - - - Paid features are read from the signed license, so an edited or copied local copy cannot enable them. TablePro honours a machine binding when the server sends one ([#2181](https://github.com/TableProApp/TablePro/issues/2181)) - - - - ### New Features - - - **Query History drawer**: History opens as a resizable drawer per connection, groups entries by day and loads in pages, so queries older than the most recent few hundred are reachable for the first time. It remembers its height, its filters and whether it was open - - **Query History filters**: Filter by where a query came from (the editor, EXPLAIN runs, table browsing, grid edits, structure changes, imports, AI and MCP clients), by failed queries only, and by time range. Scope it to one connection or search across all of them - - **Pause Query History**: Nothing is recorded from any source while it is paused, including row edits, imports and AI clients - - **Query plans as result tabs**: A plan opens beside your results instead of replacing them, and can be pinned. MySQL `EXPLAIN FORMAT=TREE` and `EXPLAIN ANALYZE`, plus PGlite, Cloudflare D1, libSQL and Turso, render as a diagram and tree - - **Plan diagram controls**: Pinch, two-finger double tap, or Cmd and scroll to zoom, fit to window, and copy or export as PNG. The plan tree has sortable columns, arrow-key navigation and a resizable detail panel. The ER diagram gained the same zoom and scrolling - - **PostgreSQL array editor**: Array columns of a simple type, including enum arrays, get a list editor in the grid with one row per element, reordering, and NULL per element ([#2095](https://github.com/TableProApp/TablePro/issues/2095)) - - **MongoDB autocomplete**: `db.` lists collections, `db.users.` lists driver methods, and inside a query you get field names, nested paths and the operators valid in that spot ([#2095](https://github.com/TableProApp/TablePro/issues/2095)) - - **PostgreSQL autocomplete**: Enum values are suggested when you compare against an enum column, alongside about 400 built-in functions, the operator set, and multi-word syntax such as `ON CONFLICT DO UPDATE SET` ([#2095](https://github.com/TableProApp/TablePro/issues/2095)) - - **Syntax problems as you type**: The editor underlines structural mistakes such as an unclosed bracket or comment. A half-written statement is never flagged ([#2095](https://github.com/TableProApp/TablePro/issues/2095)) - - **Legacy UUID Encoding for MongoDB**: Binary UUIDs written by the Java, C# or Python drivers read as UUIDs instead of hex, and filters, edits and exports write the same bytes back ([#2086](https://github.com/TableProApp/TablePro/issues/2086)) - - **Multi-select in the sidebar**: Select several databases or schemas and drop, refresh, copy names or export them at once - - **Quick Switcher across connections**: Search tables, views, saved queries and recent queries across every open connection - - **SSL settings on mobile**: A mode picker plus CA, client certificate and client key for MySQL, PostgreSQL and Redis, from a PEM file, pasted text, or PKCS#12 ([#2083](https://github.com/TableProApp/TablePro/issues/2083)) - - ### Improvements - - - Every open connection lives in one window. Picking a connection in the strip switches that window to it, and each connection keeps its scroll position, cursor and half-filled sheets while it waits its turn - - Opening a table or query on a connection you already have open adds a tab instead of opening another window. A tab strip appears once a connection holds more than one tab - - Closing the last tab leaves the connection open on its empty state, so Close Tab again closes the connection - - Clicking a table in the sidebar opens it right away instead of waiting out the double-click interval. Open in New Tab gives you a second copy - - Opening a table from the sidebar leaves the keyboard in the sidebar, so you can keep arrowing through tables and watch each one load - - The bar at the bottom of the sidebar is gone. New Table and New View are in the Database menu and the right-click menu, switching schema is Database > Schema, and the database filter is View > Filter Databases - - The sidebar follows Sidebar icon size in System Settings, with a Row Size override when you want to fit more objects on screen - - Window tabs follow your "Prefer tabs when opening documents" setting instead of always forcing tabs - - Typing a name in the database tree jumps to the matching object - - Dropping a SQL file on a connection window opens it ([#2104](https://github.com/TableProApp/TablePro/issues/2104)) - - The Settings window can be resized - - ### Bug Fixes - - - The Quick Switcher opens what you searched for. Typing a new search kept the previously highlighted row selected whenever it still matched, so `Return` could open something you had stopped searching for - - A query that fails shows the database's error again and is recorded in history. A syntax error left the results area blank with no message at all ([#2120](https://github.com/TableProApp/TablePro/issues/2120)) - - Query History no longer mixes every connection's queries together, and searching it matches as you type instead of only whole adjacent words - - Deleting a connection deletes its query history, drawer filters and saved queries with it - - Closing a connection removes it from the strip, ends its session and closes its tunnel, and closing a window disconnects every connection it was showing - - Refreshing a database or schema from the sidebar no longer empties it while the refresh runs - - Holding an arrow key in the sidebar no longer opens a tab and runs a query for every object it passes - - The sidebar object list is a native outline in every layout, so selection, arrow keys and type-to-find work. Favorites is native too, and Team Library entries can be selected for the first time - - Cmd-clicking or Shift-arrowing to select several tables no longer opens the table it added - - Sidebar and inspector widths are remembered per window again - - Switching connection resizes the toolbar to the connection you switched to instead of holding the previous one's width - - The drop, truncate, delete and external-link confirmations are standard alerts that default to Cancel, so `Return` no longer drops a table ([#2104](https://github.com/TableProApp/TablePro/issues/2104)) - - Selected rows and results use the system foreground colour, so labels stay readable under any accent colour and with Increase Contrast on - - VoiceOver follows the data grid cell cursor and reports the selected range, and Reduce Motion suppresses the remaining animations - - A chained method on a MongoDB aggregation no longer goes missing, and MQL export writes real `ObjectId`, `ISODate` and `BinData` values (#2095, #2086) - - Tables you create in an in-memory DuckDB database show up in the sidebar ([#2108](https://github.com/TableProApp/TablePro/issues/2108)) - - Editing a connection on mobile no longer wipes its SSL settings and per-database options ([#2083](https://github.com/TableProApp/TablePro/issues/2083)) - - Redis and Valkey ACL users can sign in on mobile - - ### Security - - - Mobile checks the SSH server's host key before sending any credential, and asks you the first time it sees a server. It never checked at all, so anyone intercepting the connection received the SSH password - - SQL Server Verify CA and Verify Identity check the certificate for real on Mac, and ClickHouse Verify CA refuses to connect when the CA file cannot be read - - libssh2 is patched against CVE-2026-55199, where a malicious SSH server could pin a CPU core before authentication - - An import link can no longer make TablePro fetch an AWS credential and send it to the link author's server, and team library connections no longer carry startup SQL or credential-resolution options - - A connection's password source no longer runs if the connections file was edited outside TablePro - - MySQL and MariaDB connections refuse a server's request to read a local file - - An MCP client can no longer reach the history or schema of a connection whose external access was turned off by naming it directly, and the server refuses requests from an Origin that is not allow-listed - - Query parameter values are no longer written to the history database, and the history database is protected at rest - - - - ### New Features - - - **Workspace rail**: A rail on the leading edge of the sidebar lists every connection and database you have open, so one click switches to it. It has its own shortcuts and keyboard control ([#1282](https://github.com/TableProApp/TablePro/issues/1282)) - - **Match Case**: Filter operators can match case on every database that can express it, including MongoDB, Elasticsearch, DynamoDB, and etcd ([#2048](https://github.com/TableProApp/TablePro/issues/2048)) - - **Disconnect and Reconnect**: End a session without closing its window, from the Database menu, the workspace rail, or the connection list - - **Oracle administrative logons**: Connect as SYSDBA or SYSOPER, on Mac and mobile ([#2039](https://github.com/TableProApp/TablePro/issues/2039)) - - **Oracle on TablePro Mobile**: Browse schemas and tables, run queries, and edit rows, with a Service Name or SID picker ([#2033](https://github.com/TableProApp/TablePro/issues/2033)) - - **Database menu**: Holds the database and table commands, so each can take a shortcut. Minimize, Zoom, Move Tab to New Window, Show Toolbar, and Customize Toolbar were added too - - **Show Object Icons**: Turn sidebar icons off for a plain list of names - - **Redshift external schemas**: Spectrum, federated query, cross-database, and datashare schemas now list their tables, marked in the sidebar and opened read-only - - ### Improvements - - - The menu bar is rebuilt on native macOS menus, so every command enables and disables from the state of the window you are using - - `Cmd+F` finds in whatever is in front of you: rows in a table tab, text in the SQL editor, the CSV inspector - - Connecting fills the window and names the step it is on, and a connection that fails shows the database's own error - - Running a query or opening a table replaces whatever that tab was already running, instead of waiting for it to finish - - Reopening the last session connects the window you land on first, and the rest when you switch to them - - A saved pre-connect script no longer runs when the app reopens a session on its own - - Settings opens in a standard preferences window with a toolbar instead of a tab strip - - A tab's subtitle names the database it is bound to, and its toolbar repoints only that tab ([#2026](https://github.com/TableProApp/TablePro/issues/2026)) - - MCP and AI table tools take a `database` argument, so a table in another database can be inspected without changing the app ([#2026](https://github.com/TableProApp/TablePro/issues/2026)) - - The "Group all connections in one window" setting is gone. Each connection has its own window and tab bar ([#1282](https://github.com/TableProApp/TablePro/issues/1282)) - - ### Bug Fixes - - - A tab keeps running against the database it was opened on, so changing database no longer breaks it with a "table doesn't exist" error or writes to the wrong place ([#2026](https://github.com/TableProApp/TablePro/issues/2026)) - - Menu commands act on the selected row, column, and editor, and stay disabled when they would do nothing - - A reconnect brings every window's tabs back, each in the window it was in - - Window and tab names follow what the window is showing, in every language, and a name you chose yourself is kept - - Tabs are saved before a session ends, and a window that never loaded any tabs no longer deletes them - - A cancelled or failed connect leaves the window usable, with Connect and Manage Connections - - A connection that could not be reached at launch is reopened next time, and reconnects once the server is up - - Opening a connection that already has a window reuses it, and a window opened by an MCP client connects - - Row counts refine to an exact count, and stop when you close the window or leave the table ([#2059](https://github.com/TableProApp/TablePro/issues/2059)) - - Clicking through the sidebar no longer keeps loading tables you have left, or shows the previous table's rows ([#2058](https://github.com/TableProApp/TablePro/issues/2058)) - - Stop cancels reads only, so it can no longer cut off a save, a discard, or a schema change - - A brief SSH tunnel reconnect no longer closes your tabs, and a dead tunnel reports why and offers to retry - - The JSON view follows the grid's sort, filters, and hidden columns, and updates with every query - - The SQL editor restores your cursor and selection, and keeps autocomplete after a reconnect - - AI replies stream smoothly, render Markdown as they arrive, and no longer slow the panel down - - Claude, Gemini, and local models no longer fail on thinking and image settings ([#2031](https://github.com/TableProApp/TablePro/issues/2031)) - - Filters compare text columns as text, match NULL and TRUE literally, and no longer break IS EMPTY ([#2029](https://github.com/TableProApp/TablePro/issues/2029)) - - Alerts, focus, calendars, number formats, and Reduce Motion follow system conventions - - Oracle connections turn on TCP keepalive, so an idle session is less likely to be dropped ([#2038](https://github.com/TableProApp/TablePro/issues/2038)) - - iPhone and iPad ask for Local Network access only when it is needed ([#2040](https://github.com/TableProApp/TablePro/issues/2040)) - - Requests to TablePro's own servers require TLS again - - Updated Sparkle to 2.9.5, patching CVE-2026-47121 and CVE-2026-47122 in the updater - - - - ### Improvements - - - **Translations**: Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese now cover the strings that still showed in English - - **Claude Agent tradeoffs**: The provider sheet lists what running chat on a Claude subscription costs you, including Anthropic's terms, the limits you share with your other Claude work, and the API key it ignores - - ### Bug Fixes - - - `Cmd+Delete` and `Option+Delete` in the SQL editor delete to the start of the line and the previous word again ([#2022](https://github.com/TableProApp/TablePro/issues/2022)) - - Refreshing a table no longer fails with "Query cancelled" on every second click, and stopping a query no longer shows a red error, records a failure in history, or cancels the next query you run on MySQL, MariaDB, and Redis ([#2021](https://github.com/TableProApp/TablePro/issues/2021)) - - Generated columns: SQL export leaves them out of the INSERT statements so the dump imports cleanly, adding or duplicating a row saves, and the data grid shows them as read-only. Covers MySQL, MariaDB, and ClickHouse ([#2023](https://github.com/TableProApp/TablePro/issues/2023)) - - Data grid header: the bottom line no longer cuts through a column comment, a sorted column no longer draws an extra line and divider, and selecting a column highlights the full header height - - The New Connection window opens in front of the Welcome window on the first try, and importing a connection URL while one is open no longer discards the pasted URL - - Clicking a foreign key arrow in a query tab opens the referenced table in its own tab instead of replacing the query, and keeps the filters and hidden columns you saved for each table - - Saving a table structure change with several connections open now runs against the connection, database, and schema the edited table belongs to - - Saving on a server that starts sessions read-only no longer fails on MySQL, MariaDB, PostgreSQL, CockroachDB, and Redshift - - Changing Safe Mode applies to an open connection instead of waiting for a reconnect, and a read-only error says whether the server or Safe Mode refused the write - - The MCP `export_data` tool honours the configured row limits and query timeout, works on SQL Server, Oracle, and Teradata, and shows up in query history - - Setting the MCP row limits or query timeout to zero no longer crashes the app, and AI chat no longer crashes on a limit larger than TablePro can count to - - - - ### New Features - - - **Claude Agent provider**: Run AI chat through the Claude Code command line tool and use a Claude subscription instead of a metered API key. It answers from your schema when the MCP server is on - - **Count exactly**: Sits next to an estimated row total and replaces the estimate with a real count when you want one - - **Reorder filter rows**: Drag the grip on the left of a row, or use Move Up and Move Down in its right-click menu - - **On Update column**: Set a MySQL or MariaDB timestamp column to update itself from the Structure tab, without writing the SQL by hand ([#2005](https://github.com/TableProApp/TablePro/issues/2005)) - - **Length column for Redis**: Shows the size Redis reports for a key, so you can tell how much a preview leaves out. Bytes for a string, element count for a hash, list, set, sorted set, or stream - - ### Improvements - - - The Redis key tree in the sidebar lists keys with `SCAN` instead of `KEYS`, and no longer reads every key's value just to draw the tree. `KEYS` blocks the server for the whole scan - - Redis command arguments follow the same quoting rules as `redis-cli`, so `\xHH` writes a raw byte and a command you paste from `redis-cli` behaves the same way here. A command with unbalanced quotes is rejected instead of guessed at - - SQL editor tabs are saved about a second after you stop typing instead of waiting up to 30 seconds ([#1997](https://github.com/TableProApp/TablePro/issues/1997)) - - ### Bug Fixes - - - A Redis string key shows its whole value in the grid. Values were cut at 1,000 characters, and because that was the only copy the app held, the same cut value reached the JSON tab, Copy JSON, the row inspector, and exports. Editing one of those cells and saving wrote the cut value back and lost the rest - - A Redis value that is not text, such as a gzip or MessagePack payload, is no longer shown as base64 and rewritten as base64 when you save. It displays as binary, opens in the hex editor, and is written back byte for byte. The query editor keeps binary arguments intact too - - Redis hash, list, set, sorted set, and stream previews are valid JSON. They were cut mid-token, so a preview could end in the middle of a key or an escape sequence - - A Redis key that expires between listing and reading shows an empty cell instead of the text `(nil)` - - Opening a SQL Server table or view outside the default schema no longer fails with "Invalid object name", and switching database no longer leaves the sidebar and the query on different schemas ([#2004](https://github.com/TableProApp/TablePro/issues/2004)) - - Editing a MySQL or MariaDB column no longer drops its `ON UPDATE CURRENT_TIMESTAMP`. Saving a change to any other part of the column, even just its comment, silently removed the clause ([#2005](https://github.com/TableProApp/TablePro/issues/2005)) - - A MySQL or MariaDB timestamp column that keeps fractional seconds saves again ([#2005](https://github.com/TableProApp/TablePro/issues/2005)) - - Quitting no longer loses unsaved SQL editor tabs. A window still waiting on its connection could erase the saved tabs for it, and a query over 500KB is now kept in full rather than restored empty ([#1997](https://github.com/TableProApp/TablePro/issues/1997)) - - Opening a large MongoDB collection no longer hangs, Stop cancels a running query on the server, and a query that runs out of time says so and points at the missing index that usually causes it - - Running a MongoDB query with no limit no longer pulls the whole collection into memory first, and exporting a query with a skip or limit exports that range instead of the whole collection - - A row count estimate of zero from a table that was never analyzed no longer displays as an empty table - - - - ### New Features - - - **Connect from a project folder**: Pick a folder from the welcome screen or File > Open Project Folder, and TablePro reads the database settings it finds in `.env`, `wp-config.php`, `prisma/schema.prisma`, `config/database.yml`, `docker-compose.yml`, `application.properties`, `application.yml`, or `appsettings.json`. Review what it found and the connection form opens filled in ([#1959](https://github.com/TableProApp/TablePro/issues/1959)) - - **AI walkthroughs**: Explain, Optimize, and Fix Error now answer with numbered steps and a before/after SQL diff you can apply to the editor ([#1945](https://github.com/TableProApp/TablePro/issues/1945)) - - **Close tabs in groups**: Close All Tabs, Close Other Tabs, and Close Tabs for Other Databases in the File menu. None has a shortcut out of the box; bind them in Settings > Keyboard ([#1972](https://github.com/TableProApp/TablePro/issues/1972)) - - **Structure editing in the inspector**: Select a column, index, or foreign key in the Structure tab and edit it in the inspector, with the same type list and YES/NO menus the grid offers - - **AI tool call limit**: A limit you can raise or turn off in Settings > AI, now 25 per reply instead of a fixed 10. Reaching it pauses the reply and keeps what the AI found, with Continue to carry on ([#1987](https://github.com/TableProApp/TablePro/issues/1987)) - - **Pin button on result tabs**: Pinning was already there, but only in a right-click menu and the View menu ([#1982](https://github.com/TableProApp/TablePro/issues/1982)) - - ### Improvements - - - The welcome screen groups Import from Other App, Open Project Folder, Import Connections, and Import from URL into one Add from Existing menu - - A failed iCloud sync on iPhone and iPad names the cause: out of storage, no network, or a rejected change ([#1990](https://github.com/TableProApp/TablePro/issues/1990)) - - An SSH tunnel that cannot reach the database reports whether the connection was refused or timed out, instead of leaving the driver to report a handshake timeout. The usual cause is Host holding the server's public address when the database only listens on `127.0.0.1` ([#1981](https://github.com/TableProApp/TablePro/issues/1981)) - - The macOS local network prompt now says why TablePro needs access, so it is no longer a generic prompt that is easy to deny - - ### Bug Fixes - - - iCloud Sync now sends only the fields that changed, so editing a connection on two devices no longer overwrites the field the other one changed. It also reports a failed sync instead of always reporting success, downloads every page when catching up, and retries the changes iCloud rejected instead of dropping them ([#643](https://github.com/TableProApp/TablePro/issues/643)) - - Connection changes made on the Mac reach the iPhone and iPad app again, and a synced connection keeps its safe mode setting and its colour tag ([#643](https://github.com/TableProApp/TablePro/issues/643)) - - Primary Key, Nullable, Auto Inc, and the index Unique flag now change when you pick a value, and no longer offer Set NULL ([#1991](https://github.com/TableProApp/TablePro/issues/1991)) - - A PostgreSQL partitioned table is listed once, with its partitions under it, so hundreds of partitions no longer bury the rest of the schema or fill up autocomplete ([#1984](https://github.com/TableProApp/TablePro/issues/1984)) - - The inspector shows the Structure row you selected, not the data row in the same position - - An SSH tunnel no longer drops under heavy traffic, and a `ProxyCommand` line in `~/.ssh/config` is logged as skipped instead of ignored without a word - - MongoDB authenticates against the database set on the connection, not the one you are browsing, and creating a database now asks for its first collection so it is kept ([#1970](https://github.com/TableProApp/TablePro/issues/1970)) - - Exporting a MongoDB collection works again, includes fields that first appear after the opening document, and `db.getCollection("name")` is accepted in the query editor ([#1971](https://github.com/TableProApp/TablePro/issues/1971)) - - `postgres`, CockroachDB's `defaultdb`, and Redshift's `dev` are no longer hidden as system databases, and the database a connection is using always shows in the sidebar and switchers ([#1967](https://github.com/TableProApp/TablePro/issues/1967)) - - File > New Connection, ⌘N, and the other File menu connection commands work once a connection is open ([#1975](https://github.com/TableProApp/TablePro/issues/1975)) - - The license activation sheet opens when you click Activate License - - Right-clicking below the SQL editor opens the menu for the result tabs, data grid, or status bar instead of the editor's, and View > Pin Result is enabled only when there is a result tab to pin ([#1982](https://github.com/TableProApp/TablePro/issues/1982)) - - Closing a window asks about unsaved changes in every tab, not just the one on screen ([#1972](https://github.com/TableProApp/TablePro/issues/1972)) - - The AI chat panel stays inside the right panel at narrow widths, and a message sent right after launch is no longer replaced by the restored conversation ([#1956](https://github.com/TableProApp/TablePro/issues/1956)) - - BigQuery `REPEATED` columns no longer show every element as `null`, and `STRUCT` cells show nested structs and arrays as JSON instead of escaped text ([#1963](https://github.com/TableProApp/TablePro/issues/1963)) - - - - ### New Features - - - **RDS Endpoint**: AWS IAM connections have an RDS Endpoint field, for when the connection points at a port forward you run yourself and TablePro cannot tell which database is behind it ([#1432](https://github.com/TableProApp/TablePro/issues/1432)) - - ### Improvements - - - The AWS profile field on MySQL and PostgreSQL connections now lists the profiles found on disk, like MariaDB already did ([#1432](https://github.com/TableProApp/TablePro/issues/1432)) - - ### Bug Fixes - - - AWS IAM authentication now works through a tunnel. The token was signed for the local forward instead of the database endpoint, so RDS rejected every connection made over SSH, Cloudflare, Cloud SQL Auth Proxy, or SOCKS. A tunneled connection also no longer needs the region filled in by hand ([#1432](https://github.com/TableProApp/TablePro/issues/1432)) - - - - ### New Features - - - **PGlite**: Connect to PGlite through its socket server (`pglite-server`), with schema browsing, SQL editing, and row editing like PostgreSQL ([#1911](https://github.com/TableProApp/TablePro/issues/1911)) - - **CSV structure editing**: In the CSV editor, rename, insert, delete, or retype columns, insert or delete rows, and split or merge columns from the right-click menus and the Edit menu. Each split or merge is a single undo step, and deleting columns or rows that hold data asks you to confirm first (#1469, #1913) - - **CSV file properties**: In the CSV editor, set the delimiter, quote character, escape character, encoding, and line ending from Edit > Set CSV Properties…, then Reload to re-read the file. Switch the first row between header and data with `Cmd+Shift+H` ([#1469](https://github.com/TableProApp/TablePro/issues/1469)) - - **Local AI providers**: Add llama.cpp and MLX as local AI providers, each pointing at the server's default local endpoint with no API key ([#1777](https://github.com/TableProApp/TablePro/issues/1777)) - - ### Improvements - - - SQL Server Windows Authentication (Kerberos) now connects to servers whose Kerberos realm differs from your Mac's default realm, read from the `[domain_realm]` mapping in your Kerberos configuration ([#1947](https://github.com/TableProApp/TablePro/issues/1947)) - - Switching schemas reloads the table list once per connection instead of once per window, so the sidebar no longer flashes its loading spinner once per open tab ([#1946](https://github.com/TableProApp/TablePro/issues/1946)) - - ### Bug Fixes - - - The object list no longer blanks out and shows a spinner while a schema change refreshes it. A spinner now only appears for a first load or a refresh that runs long ([#1916](https://github.com/TableProApp/TablePro/issues/1916)) - - Saving a change in the Structure tab no longer strips and re-adds the Columns, Indexes, and Foreign Keys counts, so the picker stops shifting on every save ([#1916](https://github.com/TableProApp/TablePro/issues/1916)) - - SSH tunnels using the None auth method now work on iPhone and iPad, including connections synced from the Mac ([#1912](https://github.com/TableProApp/TablePro/issues/1912)) - - Browsing a Trino table no longer fails with a syntax error ([#1936](https://github.com/TableProApp/TablePro/issues/1936)) - - The Authentication method selector no longer shifts position in the connection form when you switch between methods that show or hide the username and password ([#1947](https://github.com/TableProApp/TablePro/issues/1947)) - - - - ### New Features - - - **Trino**: Trino as a downloadable native Swift driver. Connect with a username and password or a JWT token, optionally over TLS with a custom CA or client certificate, browse catalogs, schemas, tables, and columns, run SQL, edit rows, and create or alter tables ([#1906](https://github.com/TableProApp/TablePro/issues/1906)) - - **SSH auth: None**: SSH tunnels support None as an auth method, for hosts that handle SSH auth themselves like Tailscale SSH ([#1907](https://github.com/TableProApp/TablePro/issues/1907)) - - **SSH keyboard-interactive 2FA**: SSH tunnels support keyboard-interactive verification, so a server that needs a private key plus a one-time code now connects. TablePro shows the prompt and you type the code ([#1920](https://github.com/TableProApp/TablePro/issues/1920)) - - ### Improvements - - - AI Chat renders Markdown while the assistant reply streams, including open code blocks, instead of waiting for the reply to finish - - ### Bug Fixes - - - Copy as INSERT, UPDATE, JSON, CSV, or Markdown copies only the cells you selected, not the whole row. Copy as UPDATE still keys its WHERE clause on the primary key ([#1929](https://github.com/TableProApp/TablePro/issues/1929)) - - Windows Authentication (Kerberos) to SQL Server now works on macOS. The bundled driver was built without Kerberos support, so every Windows-auth connect failed ([#1918](https://github.com/TableProApp/TablePro/issues/1918)) - - MySQL and MariaDB queries no longer fail after about a minute when a longer query timeout is set. The client read timeout now follows the query timeout ([#1921](https://github.com/TableProApp/TablePro/issues/1921)) - - A dropped MySQL or MariaDB connection no longer re-runs a statement that changes data, so a lost connection can't run an insert or update twice ([#1921](https://github.com/TableProApp/TablePro/issues/1921)) - - The MongoDB, Oracle, Cassandra, and Elasticsearch plugins install again instead of failing with "Bundle failed to load executable" ([#1917](https://github.com/TableProApp/TablePro/issues/1917)) - - Plugin install and load failures now name the real cause (wrong architecture, missing dependency, or version incompatibility) instead of a generic error ([#1915](https://github.com/TableProApp/TablePro/issues/1915)) - - - - ### New Features - - - **SOCKS5 proxy**: Connect through a SOCKS5 proxy from the connection form's new SOCKS Proxy pane. The proxy resolves the database hostname, so names that only resolve behind it work ([#1882](https://github.com/TableProApp/TablePro/issues/1882)) - - **Windows Authentication for SQL Server**: Sign in to SQL Server with Kerberos on macOS, using the `kinit` ticket you already have or a Kerberos principal and password. Connect by hostname, not IP address ([#1879](https://github.com/TableProApp/TablePro/issues/1879)) - - **Teradata**: Teradata as a downloadable native Swift driver. Connect over TD2 or TDNEGO, optionally with TLS, browse databases, tables, and columns, run SQL, edit rows, and create or alter tables ([#1867](https://github.com/TableProApp/TablePro/issues/1867)) - - **Saved Customizations**: A new Settings section lists the tables where you set column layouts or filters, and lets you reset any or all of them - - **iCloud sync for column layouts**: Per-table column layouts (widths, order, and hidden columns) sync across your Macs with iCloud when Settings sync is on - - ### Improvements - - - The sidebar database tree remembers which databases and schemas you expanded, per connection, so reopening a window keeps them open - - The query result row cap no longer adds a LIMIT to your SQL. The query runs as written and TablePro stops reading at the cap, so a query with ORDER BY no longer returns different rows than you typed ([#1884](https://github.com/TableProApp/TablePro/issues/1884)) - - Settings groups the data grid, pagination, result formatting, JSON viewer, query history, and saved customizations under a new Data tab - - ### Bug Fixes - - - The split dividers in Users and Roles, Structure, Server Dashboard, and the SQL editor now show the resize cursor on hover ([#1905](https://github.com/TableProApp/TablePro/issues/1905)) - - A query with a comment after the closing semicolon, such as `SELECT 1; -- note`, no longer runs as two statements, and a comment-only query does nothing instead of erroring ([#1895](https://github.com/TableProApp/TablePro/issues/1895)) - - Duplicating a connection no longer drops its Cloudflare Tunnel and Cloud SQL Auth Proxy settings and stored secrets - - The tunnel panes now list every conflicting connection method, each with a button to turn it off - - Copy as, Delete, and the Edit menu's copy and delete now act on the whole cell-range or column selection, not just the active row ([#1898](https://github.com/TableProApp/TablePro/issues/1898)) - - Copy as JSON copies the right rows when the grid is sorted or filtered ([#1898](https://github.com/TableProApp/TablePro/issues/1898)) - - Tables picked in the sidebar are now checked and exported to SQL or MQL instead of being silently skipped ([#1897](https://github.com/TableProApp/TablePro/issues/1897)) - - ClickHouse queries that return rows now show the grid, including empty results and queries with their own FORMAT clause ([#1886](https://github.com/TableProApp/TablePro/issues/1886)) - - ClickHouse INSERT statements report the right number of rows affected ([#1886](https://github.com/TableProApp/TablePro/issues/1886)) - - ClickHouse query exports no longer write an empty file when the query starts with a comment ([#1886](https://github.com/TableProApp/TablePro/issues/1886)) - - A restored window tab no longer shows a blank name until you switch to it ([#1894](https://github.com/TableProApp/TablePro/issues/1894)) - - The window title updates right away after saving a query to a file or opening a favorite into the current tab ([#1894](https://github.com/TableProApp/TablePro/issues/1894)) - - The SQL editor in a new tab keeps keyboard focus instead of losing it after the first few characters ([#1885](https://github.com/TableProApp/TablePro/issues/1885)) - - Cancelling a SQL Server connection stops right away, and a connection gives up after the login timeout with a clear message that points at the usual Kerberos causes for Windows Authentication ([#1889](https://github.com/TableProApp/TablePro/issues/1889)) - - MySQL and MariaDB queries ending in ORDER BY now show a server error instead of an empty result ([#1884](https://github.com/TableProApp/TablePro/issues/1884)) - - The query result row cap no longer returns a single row when set to unlimited ([#1884](https://github.com/TableProApp/TablePro/issues/1884)) - - SSH tunnels that accept a connection then go quiet now give up after 10 seconds and log the reason ([#1883](https://github.com/TableProApp/TablePro/issues/1883)) - - Connections are no longer reset when a single session opens several at once through one SSH tunnel ([#1883](https://github.com/TableProApp/TablePro/issues/1883)) - - The connection form's SSH tunnel Socket Path hint shows the right default per database type, such as `/var/run/mysqld/mysqld.sock` for MySQL and MariaDB ([#1902](https://github.com/TableProApp/TablePro/issues/1902)) - - Per-column display formats (Display As) are kept per table, so tables with the same name in different databases or schemas no longer share formatting - - Reopening a table restores a saved filter's AND/OR match mode instead of resetting it to AND - - - - ### New Features - - - **SSH to a Unix socket**: SSH tunnels can forward to a Unix socket on the remote server, for databases that only listen on a socket. Set the socket path on the General pane and TablePro forwards to it through your jump hosts ([#1871](https://github.com/TableProApp/TablePro/issues/1871)) - - ### Bug Fixes - - - Fixed a crash that could quit the app when opening or sorting a table whose columns have comments (#1869, #1880) - - The right sidebar resizes again while a Users & Roles tab is open, and the user list collapses on its own when the tab gets too narrow ([#1872](https://github.com/TableProApp/TablePro/issues/1872)) - - Fixed dividers that could not be dragged at all in Users & Roles, the Structure trigger list, and the Server Dashboard metrics and slow query panes ([#1872](https://github.com/TableProApp/TablePro/issues/1872)) - - Cancel now stops a connection attempt right away, drops the cancelled connection from the last session, and no longer interrupts a later successful connection ([#1358](https://github.com/TableProApp/TablePro/issues/1358)) - - MCP tools keep working after the server sits idle for 15 minutes or is restarted from Settings, so agents like Claude Code and Cursor no longer need to be turned off and on again ([#1881](https://github.com/TableProApp/TablePro/issues/1881)) - - ### Security - - - Patched CVE-2026-55200 in libssh2, a critical out-of-bounds write that let a malicious SSH server corrupt memory and run code before authentication finished. It affected every SSH tunnel and jump host - - - - ### New Features - - - **SurrealDB**: SurrealDB 2.x and 3.x as a downloadable driver. Browse namespaces and databases, run SurrealQL, and edit records in the grid ([#1862](https://github.com/TableProApp/TablePro/issues/1862)) - - **Beancount**: Beancount ledgers as a downloadable, read-only driver. Transactions, postings, accounts, prices, and balances read as SQL tables, and BQL runs with a `BQL:` prefix ([#1474](https://github.com/TableProApp/TablePro/issues/1474)) - - **Users & Roles**: Manage database users, roles, and privileges on MySQL and PostgreSQL from View > Users & Roles. Pick any object from the server down to a single column, see where each privilege comes from, and grant or revoke it. Changes are staged, undoable, and shown as SQL before they run ([#1413](https://github.com/TableProApp/TablePro/issues/1413)) - - **Cloud SQL Auth Proxy**: Connect to Google Cloud SQL without starting the proxy yourself. Turn it on for a MySQL, PostgreSQL, or SQL Server connection, set the instance connection name, and TablePro starts and stops the proxy for you ([#1728](https://github.com/TableProApp/TablePro/issues/1728)) - - **xAI (Grok)**: Grok as an AI provider. Paste a key from the xAI Console, or sign in with a SuperGrok or X Premium+ subscription and use no key at all - - **Reopen Closed Tab**: `Cmd+Shift+T` brings back the tab you just closed, and File > Recently Closed lists the last 20, kept for 30 days ([#1854](https://github.com/TableProApp/TablePro/issues/1854)) - - **Open from the terminal**: Install the `tablepro` command from Settings > General, then run `ddev tablepro` in a DDEV project. Links to a database on your own Mac can be trusted with Always Allow and reviewed in Settings > General ([#1486](https://github.com/TableProApp/TablePro/issues/1486)) - - New Query in the Favorites sidebar + menu, which opens an empty SQL query tab - - ### Improvements - - - Query results always open in a result tab, so a single result can be pinned before the next query replaces it. Pin from the tab's context menu or with `Cmd+Option+P` ([#1855](https://github.com/TableProApp/TablePro/issues/1855)) - - Closing a query tab keeps its SQL in Recently Closed instead of throwing it away, and the close button shows the unsaved dot for a query you typed into ([#1854](https://github.com/TableProApp/TablePro/issues/1854)) - - ### Bug Fixes - - - Fixed a crash when several tables on the same connection ran queries at the same time - - Column comments now show in the data grid header as soon as the table opens, and the header grows to fit them ([#1861](https://github.com/TableProApp/TablePro/issues/1861)) - - Size to Fit no longer stretches a column of long text past the window. Fitted columns stop at half the visible grid, and every column has a maximum width - - The username is now optional. Leaving it empty no longer fills in `root`, and lets the database pick its own default the way `psql` and `mysql` do - - A failed or cancelled connection that uses a Cloudflare tunnel no longer leaves the `cloudflared` process running in the background - - Pinned results are no longer discarded by Clear Results, and a tab holding one is no longer reused to browse a different table ([#1855](https://github.com/TableProApp/TablePro/issues/1855)) - - Quitting now warns about unsaved changes in any tab, not just the visible one ([#1854](https://github.com/TableProApp/TablePro/issues/1854)) - - - - ### New Features - - - **Resizable JSON field**: Drag the handle below a JSON value in the row details inspector to make it taller and read more in place. The height is remembered across rows and app restarts ([#1849](https://github.com/TableProApp/TablePro/issues/1849)) - - ### Improvements - - - Column comments now appear directly in data grid column headers when object comments are enabled, instead of only in the header tooltip ([#1789](https://github.com/TableProApp/TablePro/issues/1789)) - - ### Bug Fixes - - - MySQL and MariaDB connections that prompt for a password now ask for a fresh one after an authentication failure on reconnect, instead of looping in Connecting with the expired password - - Pressing Escape to dismiss the SQL autocomplete popup no longer moves focus out of the editor, so you can keep typing ([#1845](https://github.com/TableProApp/TablePro/issues/1845)) - - - - ### Bug Fixes - - - Oracle now connects to servers that require native network encryption (`SQLNET.ENCRYPTION_SERVER = REQUIRED`), negotiating AES with a SHA checksum automatically like SQL Developer and DBeaver. The separate encryption option is gone; encryption is offered to every server and used only when the server asks for it ([#483](https://github.com/TableProApp/TablePro/issues/483)) - - Reading a connection password from a command, 1Password, Vault, or AWS Secrets Manager no longer occasionally returns corrupted output from a race while reading the output ([#1841](https://github.com/TableProApp/TablePro/issues/1841)) - - MongoDB filters and generated edit statements now quote numeric-looking values that would not round-trip, such as `.5`, `1.`, `+7`, `01`, and very large integers, instead of emitting invalid or lossy JSON. Update the MongoDB plugin to get the fix ([#1813](https://github.com/TableProApp/TablePro/issues/1813)) - - The Structure tab filter and column sort now update the grid instead of leaving stale rows on screen - - The row details inspector now shows the selected row's values, including JSON, when a column value filter is active, and an open value follows the selected row as you move between rows ([#1837](https://github.com/TableProApp/TablePro/issues/1837)) - - Copying, duplicating, and deleting rows now act on the rows you selected when a column value filter is active, instead of the rows sitting at those positions in the unfiltered result ([#1837](https://github.com/TableProApp/TablePro/issues/1837)) - - - - ### New Features - - - **Secret manager passwords**: A connection can pull its password from 1Password, HashiCorp Vault, or AWS Secrets Manager at connect time, so the secret is never stored in TablePro. - - **Team Library**: Share connections and saved queries with your team through your account. Right-click a connection or use the Favorites sidebar to Publish to Team Library, and teammates see them in their own list and sidebar. Passwords are never included. - - **Team Catalog**: On the Team plan, publish connections to a shared folder your team reads from, without passwords. Teammates add the folder under Settings > Linked Folders. - - **Join a team with an invite code**: Paste your invite code where a license key goes, in Settings > Account. Owners manage members and seats on tablepro.app. - - **Saved queries and slash commands sync**: Saved SQL queries and their folders, and custom AI slash commands, now sync across your Macs with iCloud Sync. - - **Recent tables**: A Recent section at the top of the sidebar lists the last 10 tables you opened per connection and database, and remembers them between launches. Off by default; turn it on in Settings > General > Sidebar ([#1352](https://github.com/TableProApp/TablePro/issues/1352)) - - **Delete confirmation**: Saving now asks you to confirm before it permanently deletes rows ([#1823](https://github.com/TableProApp/TablePro/issues/1823)) - - ### Improvements - - - The sidebar filter matches table and view names by substring instead of fuzzy matching, and sorts names that start with what you typed to the top ([#1822](https://github.com/TableProApp/TablePro/issues/1822)) - - ### Bug Fixes - - - Typing in the SQL editor no longer crashes the app, and the autocomplete popup appears again as you type ([#1835](https://github.com/TableProApp/TablePro/issues/1835)) - - Deleting many rows at once no longer freezes the app; multi-row deletes run as one batched statement instead of one query per row ([#1823](https://github.com/TableProApp/TablePro/issues/1823)) - - Deleting rows from a table without a primary key now matches on every column, so it won't remove other rows that share the first column's value ([#1823](https://github.com/TableProApp/TablePro/issues/1823)) - - Sorting a table column keeps your rows-per-page setting and returns to page 1 instead of loading the whole table ([#1826](https://github.com/TableProApp/TablePro/issues/1826)) - - Sorting a column no longer fails with a SQL error on tables whose name contains a word like limit or offset ([#1825](https://github.com/TableProApp/TablePro/issues/1825)) - - Elasticsearch connections using Username & Password now show a Password field, so you can set up basic auth on a secured cluster. Update the Elasticsearch plugin to get the fix ([#1816](https://github.com/TableProApp/TablePro/issues/1816)) - - Switching schemas on an Oracle connection no longer hangs on an infinite spinner. Oracle switches by schema, loads each schema's tables on expand, respects the query timeout with automatic reconnect, and shows an error with a Retry button when a schema fails to load ([#1807](https://github.com/TableProApp/TablePro/issues/1807)) - - Resizing a data grid column no longer triggers a sort. Dragging a column edge only resizes it; clicking the header still sorts ([#1815](https://github.com/TableProApp/TablePro/issues/1815)) - - Hidden columns stay hidden, and resizing a column no longer brings them back. Column widths, order, and visibility are remembered per table, kept separately for each connection, database, and schema. A new Reset Columns button in the Columns popover restores defaults ([#1815](https://github.com/TableProApp/TablePro/issues/1815)) - - Query and filter errors now appear in a banner you can select and copy, with a Fix with AI button, instead of a dialog that cut the message off ([#1815](https://github.com/TableProApp/TablePro/issues/1815)) - - The editor autocomplete popup drops below the line you're typing instead of covering it, no longer overlaps the Columns and Add buttons, and dismisses when you click anywhere on it that isn't a suggestion (#1815, #1831) - - - - ### New Features - - - **Add rows from the Shortcuts app (iOS)**: Two new shortcuts, Add Row to Table and Add Rows to Table, pick a saved connection, database or schema, and table, then insert rows from JSON or CSV. They run without opening the app ([#1788](https://github.com/TableProApp/TablePro/issues/1788)) - - **Row cap as a real LIMIT**: SELECT queries without their own LIMIT now run with the row cap applied to the statement sent to the server, so forgetting a LIMIT no longer pulls millions of rows. A query with its own LIMIT is sent as written, and Execute Query Without Limit (Option+Cmd+Enter) skips the cap for one run ([#1794](https://github.com/TableProApp/TablePro/issues/1794)) - - **Cursor marker in SQL Favorites**: Put ;; in a saved query to set where the cursor lands after keyword expansion in the editor ([#1795](https://github.com/TableProApp/TablePro/issues/1795)) - - ### Improvements - - - Settings > Plugins > Browse has a Refresh button to reload the plugin list on demand ([#1799](https://github.com/TableProApp/TablePro/issues/1799)) - - Restored table tabs no longer reload all at once on launch. Only the frontmost tab loads immediately; other tabs load when you switch to them, and a load failure shows inline in the tab instead of a dialog ([#1796](https://github.com/TableProApp/TablePro/issues/1796)) - - ### Bug Fixes - - - The New/Edit Favorite and linked file metadata dialogs now use the standard macOS grouped form layout instead of opening with a large empty area and crushed fields ([#1809](https://github.com/TableProApp/TablePro/issues/1809)) - - Tables and views outside the connection's default schema now open and save correctly when reached through the Quick Switcher, a restored tab, or the MCP table-open tool ([#1774](https://github.com/TableProApp/TablePro/issues/1774)) - - Quick Switcher opens again on macOS Sequoia; since 0.51.0 the panel came up invisible on macOS 15 ([#1806](https://github.com/TableProApp/TablePro/issues/1806)) - - Quick Switcher keyboard navigation (Ctrl-J/K and arrows) no longer goes dead after the switcher has been opened and closed repeatedly ([#1806](https://github.com/TableProApp/TablePro/issues/1806)) - - Execute All Statements now applies the row cap to each SELECT in the batch, and each result tab tracks its own truncation state so Fetch All loads the rest of the result you are viewing ([#1794](https://github.com/TableProApp/TablePro/issues/1794)) - - Queries starting with a comment or with a newline right after the first keyword are classified correctly: the row cap applies to them, and Safe Mode recognizes such writes and destructive statements - - Enum and set value pickers now populate when the driver reports the values only inside the column type - - The plugin list no longer goes stale. The app checks the plugin registry for changes at launch, when the plugin browser opens, and before reporting a plugin as missing. An unreachable registry reports a connection problem instead of "Plugin not found" ([#1799](https://github.com/TableProApp/TablePro/issues/1799)) - - Dropping a materialized view or foreign table from the sidebar now generates the correct DROP statement, and drop and truncate statements are schema-qualified. ClickHouse lists materialized views as their own sidebar section ([#1800](https://github.com/TableProApp/TablePro/issues/1800)) - - Scrolling no longer goes dead across the app after opening an ER diagram and switching tabs while the pointer rests over the canvas ([#1805](https://github.com/TableProApp/TablePro/issues/1805)) - - - - ### New Features - - - **Per-tab database picker**: Each SQL tab can target its own database from the editor toolbar, without clearing other tabs. - - **Table and column comments**: Comments from the database now show in the sidebar, the column header tooltip, and the table inspector. Toggle from View > Show Object Comments. Available for MySQL and PostgreSQL ([#1771](https://github.com/TableProApp/TablePro/issues/1771)) - - ### Improvements - - - Single-click a table in the sidebar to open it in the current tab; double-click to open it in a new tab. - - Switching the active database keeps your open tabs instead of closing them. - - The toolbar, quick switcher, and query editor database pickers follow the sidebar database filter. - - Switching table tabs is faster. - - ### Bug Fixes - - - Safe mode keeps the confirmation on the tab you ran the query from instead of jumping to the first tab ([#1781](https://github.com/TableProApp/TablePro/issues/1781)) - - Switching between sidebar tables no longer leaves blank space above the list ([#1675](https://github.com/TableProApp/TablePro/issues/1675)) - - SSH tunnels no longer pin a CPU core after the connection drops ([#1769](https://github.com/TableProApp/TablePro/issues/1769)) - - Restored table tabs load with the current page size from this session. - - SQL Server: large nvarchar(max) and text values no longer truncate when copied or viewed ([#1783](https://github.com/TableProApp/TablePro/issues/1783)) - - Oracle: connections with Native network encryption turned on no longer hang for about a minute against servers that do not complete it, such as Oracle 11g. The login now stops after 30 seconds and explains how to turn the option off ([#1746](https://github.com/TableProApp/TablePro/issues/1746)) - - - - ### New Features - - - **Multiple tags per connection**: Assign several tags in the connection form and filter the welcome list by tag with Match Any or Match All ([#744](https://github.com/TableProApp/TablePro/issues/744)) - - **Per-column value filter**: Click the funnel icon on a column header to pick which loaded values to show, across several columns at once. It filters loaded rows without re-querying ([#1454](https://github.com/TableProApp/TablePro/issues/1454)) - - **Elasticsearch support**: Connect to 7.x and 8.x, browse indices, run Query DSL in a console, and edit documents in the data grid. Install from Settings > Plugins ([#1529](https://github.com/TableProApp/TablePro/issues/1529)) - - **ER diagram cardinality**: Relationships show one-to-one, one-to-many, and optional variants in crow's foot notation, read from primary keys and unique indexes. Junction tables collapse into a single many-to-many link, with a toolbar toggle to expand them ([#1335](https://github.com/TableProApp/TablePro/issues/1335)) - - **Export ER diagram to SQL**: A toolbar button opens a query tab with CREATE TABLE and foreign key statements for the current schema ([#1335](https://github.com/TableProApp/TablePro/issues/1335)) - - **Oracle native network encryption**: An option in the connection form, off by default, for servers that require encrypted traffic ([#1746](https://github.com/TableProApp/TablePro/issues/1746)) - - ### Improvements - - - The connection switcher and welcome list now show each connection's tags and group ([#1323](https://github.com/TableProApp/TablePro/issues/1323)) - - The ER diagram uses a more compact layout, keeps foreign-key-linked tables together, and tints each connected group with its own header color ([#1755](https://github.com/TableProApp/TablePro/issues/1755)) - - When an Oracle server drops the connection during login, the error dialog shows which handshake phase it stopped at, which helps diagnose Oracle 11g ([#1746](https://github.com/TableProApp/TablePro/issues/1746)) - - ### Bug Fixes - - - Opening a new query tab now puts keyboard focus in the SQL editor instead of the sidebar filter, so you can type right away ([#1765](https://github.com/TableProApp/TablePro/issues/1765)) - - Raw filters in the data grid now work on document and key-value databases; the typed text was dropped before reaching the driver ([#1529](https://github.com/TableProApp/TablePro/issues/1529)) - - Connecting to Oracle no longer crashes on certain server values during the handshake; a bad packet now fails the connection with an error ([#1746](https://github.com/TableProApp/TablePro/issues/1746)) - - Connecting to Oracle no longer hangs when the server permits but does not require native network encryption; TablePro connects in clear text by default, like Oracle's own clients ([#1746](https://github.com/TableProApp/TablePro/issues/1746)) - - Following a foreign key into another schema now opens the correct table on SQL Server and Oracle, instead of the default schema ([#1754](https://github.com/TableProApp/TablePro/issues/1754)) - - Browsing or editing a SQL Server or Oracle table outside the default schema no longer fails with "Invalid object name" or writes to the wrong table ([#1754](https://github.com/TableProApp/TablePro/issues/1754)) - - - - ### New Features - - - **Import and export connections on iPhone**: Open a .tablepro file from Files or AirDrop, or use Import Connections in the list menu. Export from the list menu. Passwords are left out by default; include them by setting a passphrase that encrypts the file - - Encrypted files prompt for the passphrase on import, and you choose how to handle duplicates - - ### Improvements - - - Drag-selecting many columns in a wide result set scrolls smoothly instead of lagging - - Scrolling large result sets uses less CPU - - Typing in the sidebar table search stays responsive on databases with thousands of tables - - The welcome sidebar stays responsive with many connections and nested groups - - Autocomplete stays snappy on wide SELECT clauses with hundreds of columns - - The connection Export Options dialog keeps a steady size when you turn on Include Credentials, and saves through the standard macOS save dialog - - ### Bug Fixes - - - SQL autocomplete no longer stops appearing until the app is relaunched; it stays available after switching query tabs and windows ([#1731](https://github.com/TableProApp/TablePro/issues/1731)) - - Oracle connections no longer crash the app when the server sends a message the driver cannot decode; the query fails with a clear error and reconnects ([#483](https://github.com/TableProApp/TablePro/issues/483)) - - MongoDB TLS handshake failures now report the actual cause instead of always blaming a cipher or protocol mismatch ([#1418](https://github.com/TableProApp/TablePro/issues/1418)) - - The External Clients access level no longer reverts to Read Only after saving and reopening a connection, so MCP clients keep the write access you granted ([#1730](https://github.com/TableProApp/TablePro/issues/1730)) - - Typing fast with the autocomplete window open no longer stalls each keystroke - - - - ### New Features - - - **Triggers**: The table structure view has a Triggers tab for MySQL, MariaDB, PostgreSQL, SQLite, SQL Server, Oracle, libSQL, and Cloudflare D1, with a filter field, sortable columns, and a read-only view of each trigger's definition. You can create, edit, and drop triggers from the tab ([#1695](https://github.com/TableProApp/TablePro/issues/1695)) - - **Traditional Chinese**: Added 繁體中文 in Settings > General - - **Add row button**: The table status bar has an Add button that inserts a new row at the end of the grid and starts editing it - - **DuckDB remote (experimental)**: Connect to a remote DuckDB server over the Quack protocol. Pick Remote (Quack) in the connection form and enter the host, port, and token. Needs DuckDB 1.5.3 or later ([#1716](https://github.com/TableProApp/TablePro/issues/1716)) - - ### Improvements - - - Selecting a Redis namespace in the sidebar now filters the open view to that prefix with paging, instead of opening a separate tab ([#1701](https://github.com/TableProApp/TablePro/issues/1701)) - - ### Bug Fixes - - - SQL autocomplete now suggests columns for a derived-table or CTE alias ([#1697](https://github.com/TableProApp/TablePro/issues/1697)) - - Redis entries no longer disappear after the connection sits idle ([#1701](https://github.com/TableProApp/TablePro/issues/1701)) - - Redis key browsing now lists and pages through every key in a database or namespace, instead of only the first batch ([#1701](https://github.com/TableProApp/TablePro/issues/1701)) - - A dropped Redis connection now reconnects on the next command and replays auth and the selected database ([#1701](https://github.com/TableProApp/TablePro/issues/1701)) - - DuckDB VARIANT columns now show their value as text instead of an empty cell - - A new database group now appears in the connection list right away instead of only after restarting the app ([#1704](https://github.com/TableProApp/TablePro/issues/1704)) - - The SQL formatter keeps nested indentation for UNION, INTERSECT, and EXCEPT inside a subquery, and puts the closing parenthesis on its own line ([#1698](https://github.com/TableProApp/TablePro/issues/1698)) - - Toolbar button tooltips now show each action's real keyboard shortcut and follow your custom bindings ([#1694](https://github.com/TableProApp/TablePro/issues/1694)) - - Deleting a table from the sidebar now removes it from the tree right away on multi-database servers like MySQL and PostgreSQL ([#1714](https://github.com/TableProApp/TablePro/issues/1714)) - - The row detail panel no longer stays blank when a table is opened in a second tab - - The sidebar filter now stays put when you open another tab - - Fixed a crash when browsing the database tree on servers with many schemas, such as PostgreSQL - - Reopening the app no longer shows a "Not connected to database" error when it restores a table tab on a connection that is still reconnecting, such as one over SSH - - - - ### New Features - - - **Database filter in the sidebar**: Filter the tree to only the databases you pick, saved per connection ([#1667](https://github.com/TableProApp/TablePro/issues/1667)) - - **Unsaved SQL recovery**: Closing a query tab no longer loses unsaved SQL. The next blank query tab for the same connection restores the last closed draft ([#1686](https://github.com/TableProApp/TablePro/issues/1686)) - - **Toggle all filters**: A checkbox in the filter panel header turns every filter row on or off at once - - ### Improvements - - - The filter panel's "Unset" button is now "Clear"; it keeps your filter rows and only drops the applied state - - A filter row's right-click menu now has "Apply Only This Filter", replacing the inline per-row Apply button - - The sidebar and inspector keep their width per connection, the sidebar its collapsed state, and the inspector its selected tab, across quit and reopen - - ### Bug Fixes - - - Shift+Arrow in the data grid starts and extends a cell selection from the focused cell, and Cmd+Shift+Arrow extends to the row or column edge - - Delete key removes all rows covered by a cell-range selection - - Right-clicking inside a multi-row or cell-range selection no longer collapses the selection first - - Oracle connections no longer crash during connect when the server sends a short or unexpected handshake packet ([#1683](https://github.com/TableProApp/TablePro/issues/1683)) - - MongoDB filters on `_id` and other ObjectId fields now match a 24-character hex value ([#1682](https://github.com/TableProApp/TablePro/issues/1682)) - - - - ### New Features - - - **BigQuery datasets**: Switch, create, and drop datasets from the toolbar, Cmd+K switcher, and File menu ([#509](https://github.com/TableProApp/TablePro/issues/509)) - - **Quick Switcher upgrades**: Search saved queries, scope results with Cmd+1 to Cmd+4 and a recent-items view, open a table in a new tab with Option+Return, and right-click a result to open its structure or copy the name or query - - **Session restore on launch**: TablePro reopens your last session by default, bringing back each tab's sort, page, cursor position, and column widths plus the active database and schema. Tabs autosave every 30 seconds and recover after a crash ([#1673](https://github.com/TableProApp/TablePro/issues/1673)) - - **`.psql` and `.pgsql` files**: Open in the SQL editor like `.sql` ([#1641](https://github.com/TableProApp/TablePro/issues/1641)) - - ### Improvements - - - The Quick Switcher opens as a Spotlight-style floating panel (Liquid Glass on macOS 26), highlights matched characters, and ranks the items you use most - - The sidebar filter, database switcher, and connection switcher use the same fuzzy matching, so `upv` finds `user_profile_view` - - Redis connections filter with a key-pattern search and a key-type scope, matched server-side across the whole keyspace - - Container names match each database: Dataset for BigQuery, Keyspace for Cassandra and ScyllaDB ([#509](https://github.com/TableProApp/TablePro/issues/509)) - - Refresh (Cmd+R) acts only on the focused window's connection, and holding it no longer queues a backlog of refreshes - - The inspector panel resizes freely by dragging its divider - - Switching PostgreSQL schemas sets the search path to just the selected schema ([#1662](https://github.com/TableProApp/TablePro/issues/1662)) - - ### Bug Fixes - - - PostgreSQL and Redshift autocomplete now completes tables and columns from schemas other than the selected one ([#1668](https://github.com/TableProApp/TablePro/issues/1668)) - - Favorite keywords work again; deleting a connection also removes its saved queries, folders, and per-table filters - - Keyword and SQL keyword autocomplete work in editors without a connection - - PostgreSQL databases without a "public" schema load tables from the first available schema ([#1662](https://github.com/TableProApp/TablePro/issues/1662)) - - Switching schemas no longer closes open tabs or discards unsaved SQL ([#1669](https://github.com/TableProApp/TablePro/issues/1669)) - - Creating a table turns the Create Table tab into the new table's tab and updates the sidebar, and Cmd+S creates the table ([#1664](https://github.com/TableProApp/TablePro/issues/1664)) - - Format Query can be undone with Cmd+Z and formats only the selection when one is active (#1645, #1656) - - Foreign key jump arrows survive sorting, filtering, and paginating, and PostgreSQL foreign keys are read from the system catalogs - - Sorting a query result no longer overwrites the SQL editor or an open `.sql` file ([#1645](https://github.com/TableProApp/TablePro/issues/1645)) - - iCloud Sync between the iPhone and Mac apps uses the Production CloudKit environment - - Exports no longer fail mid-table on servers with a statement time limit ([#1633](https://github.com/TableProApp/TablePro/issues/1633)) - - Refreshing a table reloads its data even when a previous load is still running ([#1637](https://github.com/TableProApp/TablePro/issues/1637)) - - Cmd+R on a table reloads its rows instead of failing with a query error - - SQL autocomplete suggests tables after JOIN across multi-join and multi-clause queries ([#1646](https://github.com/TableProApp/TablePro/issues/1646)) - - Large SQL scripts no longer freeze the editor; above 2 MB it suspends syntax highlighting and inline AI so typing and scrolling stay responsive ([#1652](https://github.com/TableProApp/TablePro/issues/1652)) - - Opening a query from history in the Quick Switcher loads the full query instead of a 100-character preview - - ### Security - - - Imported connections from a deep link or shared file can no longer carry a pre-connect script that runs a shell command - - External database links ask for confirmation before connecting, and a password in the link is never saved to the Keychain - - MCP tools enforce each connection's access level, AI policy, and token scope, and the MCP server requires a paired token by default - - An installed plugin's code signature is re-checked right before it loads, so the binary cannot be swapped after the first check - - MongoDB filter values in the Contains, Not Contains, Starts With, Ends With, and Regex operators can no longer inject query operators - - On iOS, TLS certificates are validated for verify SSL modes, copied values stay on the device, and the home screen widget no longer stores database host and port on disk - - - - ### New Features - - - **Cursor as an AI provider**: Use a Cursor API key or sign in with the Cursor CLI to run AI chat and inline suggestions ([#1624](https://github.com/TableProApp/TablePro/issues/1624)) - - **Sign in with ChatGPT**: Run AI chat and inline suggestions without an API key; existing Codex CLI logins can be imported ([#1617](https://github.com/TableProApp/TablePro/issues/1617)) - - **libSQL / Turso local files**: Open a local libSQL database file and work with it offline, transactions included ([#1607](https://github.com/TableProApp/TablePro/issues/1607)) - - ### Bug Fixes - - - Default row sort now applies to the first table opened after launch ([#1603](https://github.com/TableProApp/TablePro/issues/1603)) - - The query editor no longer erases characters or drops focus while typing, most visible on macOS 15 ([#1608](https://github.com/TableProApp/TablePro/issues/1608)) - - The autocomplete popup filters in place instead of closing and reopening on each keystroke ([#1608](https://github.com/TableProApp/TablePro/issues/1608)) - - Syntax highlighting no longer disappears after formatting a query ([#1612](https://github.com/TableProApp/TablePro/issues/1612)) - - Clicking an already-open table switches to its tab instead of opening a duplicate ([#1613](https://github.com/TableProApp/TablePro/issues/1613)) - - MongoDB connects over an SSH or Cloudflare tunnel instead of failing with connection refused ([#1621](https://github.com/TableProApp/TablePro/issues/1621)) - - Oracle connections with native network encryption no longer crash on a server error; the real ORA error is shown and the connection keeps working ([#483](https://github.com/TableProApp/TablePro/issues/483)) - - DBeaver connections import from any edition, based on your DBeaver data rather than which app is installed ([#1628](https://github.com/TableProApp/TablePro/issues/1628)) - - Cancelling a SQLite query no longer races a disconnect ([#1610](https://github.com/TableProApp/TablePro/issues/1610)) - - The GitHub Copilot provider no longer shows a Max output tokens field it ignores or leaves a stray model ID field behind - - A plugin updated in Settings stays marked Installed instead of showing the Update button again - - - - ### Bug Fixes - - - Plugins from the registry install and load again; 0.49.0 rejected plugins built before the update with an invalid plugin bundle error - - Default row sort by primary key works again for PostgreSQL and other databases, and rows arrive already sorted on the first load ([#1603](https://github.com/TableProApp/TablePro/issues/1603)) - - - - ### New Features - - - **Snowflake**: Connect with username & password (MFA included), key-pair, browser SSO, or an access token. Browse and edit data, import CSV and JSON, edit table structure, run scripts, and switch warehouse and role from the toolbar. Snowflake CLI connections can be reused by name ([#1420](https://github.com/TableProApp/TablePro/issues/1420)) - - **CSV and TSV Import**: Import CSV and TSV files into a table. Map columns to an existing table or create a new one, with delimiter, quote, encoding, header, and NULL options ([#1568](https://github.com/TableProApp/TablePro/issues/1568)) - - **Per-Row Filters**: Each filter row can be switched on or off and applied on its own; disabled rows stay in the panel ([#1561](https://github.com/TableProApp/TablePro/issues/1561)) - - **AWS Authentication**: Connections can assume an IAM role through STS, including chained source profiles. Redis connects to ElastiCache with IAM auth, Cassandra connects to Amazon Keyspaces with SigV4, and expired AWS SSO sessions prompt a browser sign-in ([#1567](https://github.com/TableProApp/TablePro/issues/1567)) - - **Oracle Encryption and Redirects**: Oracle connections negotiate Native Network Encryption and follow listener redirects, so RAC SCAN, shared server, and load-balanced setups connect ([#483](https://github.com/TableProApp/TablePro/issues/483)) - - SQL autocomplete completes each segment of qualified names, loads tables of unopened schemas on demand, and suggests the connection's dialect functions - - Importing connections from other apps detects duplicates and lets you replace, add a copy, or skip each one - - Export and import dialogs remember the last-used format, options, and encoding, with a Reset to Defaults button ([#1591](https://github.com/TableProApp/TablePro/issues/1591)) - - ### Improvements - - - Custom keyboard shortcuts work on non-US layouts, can use function keys, and shifted symbols like Cmd+[ record correctly - - The Keyboard settings list is grouped by where shortcuts act, with a reset button per changed shortcut, and conflict detection checks live macOS system shortcuts - - Show Tables and Show Favorites moved to Cmd+Option+1 and Cmd+Option+2; Cmd+N opens a new connection - - AWS connections list the profiles found in your AWS config files ([#1567](https://github.com/TableProApp/TablePro/issues/1567)) - - The results status bar has a divider, balanced margins, and clear VoiceOver labels ([#1569](https://github.com/TableProApp/TablePro/issues/1569)) - - ### Bug Fixes - - - Accepting an autocomplete suggestion replaces the whole typed word instead of leaving part of it behind ([#1601](https://github.com/TableProApp/TablePro/issues/1601)) - - MongoDB: connecting to Atlas no longer fails with TLS internal error (-9838) ([#1599](https://github.com/TableProApp/TablePro/issues/1599)) - - DuckDB: the plugin runs DuckDB 1.5.2 again after a rollback to 1.5.0 - - Query results appear as soon as rows return; metadata loads in the background, removing a multi-second wait on slow remote databases ([#1574](https://github.com/TableProApp/TablePro/issues/1574)) - - JSON import: failed imports restore deleted rows, skip-and-continue no longer duplicates rows, and "Stop and Commit" keeps rows inserted before the error - - JSON file import works again; it failed to load in 0.48.0 - - The connection and database switcher focuses its search field even while a filter input is being edited ([#1575](https://github.com/TableProApp/TablePro/issues/1575)) - - TablePro no longer shows its icon for .sql, .sqlite, and .duckdb files when it is not their default app ([#1594](https://github.com/TableProApp/TablePro/issues/1594)) - - The JSON results view shows row data right away and follows the row selection ([#1576](https://github.com/TableProApp/TablePro/issues/1576)) - - Query result columns follow the SELECT order ([#1565](https://github.com/TableProApp/TablePro/issues/1565)) - - SQL Server: logins restricted to one database, such as Azure SQL contained users, now connect - - PostgreSQL scripts with dollar-quoted blocks no longer fail with an unterminated string error ([#1559](https://github.com/TableProApp/TablePro/issues/1559)) - - AWS IAM connections no longer ask for a password on connect - - Oracle connection failures show the listener's actual reason instead of a generic message ([#483](https://github.com/TableProApp/TablePro/issues/483)) - - Custom Copy, Cut, Delete, and Find shortcuts take effect in the editor and data grid, and pagination buttons no longer fire twice - - - - ### New Features - - - **JSON Import**: Import a JSON file into a table. Pick SQL or JSON from the Import menu; the JSON flow accepts an array of objects, newline-delimited JSON, or TablePro's own JSON export, and maps each field to a new or existing table ([#1536](https://github.com/TableProApp/TablePro/issues/1536)) - - **Table Name in the Title Bar**: The window title bar shows the open table's name, with its database and schema below ([#1475](https://github.com/TableProApp/TablePro/issues/1475)) - - **iOS DuckDB**: Open DuckDB database files and in-memory DuckDB databases on iOS, matching the Mac app ([#1526](https://github.com/TableProApp/TablePro/issues/1526)) - - **Save Query as Favorite**: Save the current query as a favorite from a star button in the SQL editor toolbar - - ### Improvements - - - Field names and types in the row Details panel can now be selected and copied - - Plugins stay binary-compatible across app updates that add plugin capabilities, so installed plugins no longer need a rebuild for additive changes - - Connection list rows show the database name after the host, so look-alike connections are easier to tell apart ([#1535](https://github.com/TableProApp/TablePro/issues/1535)) - - Editor toolbar buttons show their keyboard shortcut in the tooltip, updated when you rebind it - - The window toolbar groups the connection and database selectors on the left and moves Refresh and Save to the right - - ### Bug Fixes - - - Save as Favorite uses Cmd+D again; the Cmd+Control+D set in 0.47.0 was reserved by macOS for Look Up, so it never fired - - PostgreSQL keeps the selected schema applied after an automatic reconnect, so unqualified table names keep resolving against it ([#1540](https://github.com/TableProApp/TablePro/issues/1540)) - - Import now finds the Setapp edition of TablePlus and reads its connections ([#1528](https://github.com/TableProApp/TablePro/issues/1528)) - - Favorite keyword suggestions now appear in editor autocomplete instead of being dropped before the popup - - Editor autocomplete refreshes when you switch schema, suggesting the new schema's tables and columns - - - - ### New Features - - - **Favorite Tables**: Star a table in the sidebar to pin it to the top of its section and the Favorites tab. Favorites are scoped to the connection, database, and schema, and sync through iCloud - - **Database Tree Sidebar**: Show every database on the server as an expandable tree. Switch a connection between the flat list and the tree from the View menu, and set the default for new connections in Settings ([#139](https://github.com/TableProApp/TablePro/issues/139)) - - **Import from Navicat**: Export a connections file from Navicat, then pick it under Import from Other App. SSH tunnel and SSL settings carry over, and saved passwords are decrypted during import ([#1485](https://github.com/TableProApp/TablePro/issues/1485)) - - **PostGIS Rendering**: PostgreSQL `geometry` and `geography` columns render as WKT with SRID instead of raw hex ([#1458](https://github.com/TableProApp/TablePro/issues/1458)) - - **External Password Sources**: A connection can read its password from a file, environment variable, or command at connect time instead of the Keychain ([#1254](https://github.com/TableProApp/TablePro/issues/1254)) - - **Create Tables from the Sidebar**: A plus button in the Tables sidebar footer creates a new table or view without right-clicking - - **Sidebar Keyboard Control**: Focus the filter field (Cmd+Option+F) and switch between the Tables and Favorites sidebars (Ctrl+1 and Ctrl+2), all rebindable in Settings ([#1490](https://github.com/TableProApp/TablePro/issues/1490)) - - **Result Pager in the Query Menu**: Previous, Next, First, and Last Page are now in the Query menu, with Previous and Next on Cmd+[ and Cmd+] ([#1490](https://github.com/TableProApp/TablePro/issues/1490)) - - ### Improvements - - - **Safe Mode Everywhere**: Table structure changes, creation, maintenance, column reorder, and saved grid edits now follow the connection's Safe Mode and read-only setting, and AI assistant and MCP queries follow the same rules ([#1481](https://github.com/TableProApp/TablePro/issues/1481)) - - **Always Confirm Destructive Queries**: DROP, TRUNCATE, and DELETE without WHERE always ask for confirmation, even with Safe Mode off ([#1481](https://github.com/TableProApp/TablePro/issues/1481)) - - **Native Sidebar Footer**: The Tables sidebar footer uses native macOS styling, and switching schemas stays in sync with the toolbar - - **Window Sizing**: The window minimum width adjusts to the visible panes, so opening the inspector on a small window no longer pushes content off-screen - - **Save as Favorite Shortcut**: Moved to Cmd+Control+D, leaving Cmd+D free for the system "Don't Save" action ([#1490](https://github.com/TableProApp/TablePro/issues/1490)) - - **iOS Button Roles**: Sheet close, cancel, and confirm buttons use the native iOS 26 button roles ([#1524](https://github.com/TableProApp/TablePro/issues/1524)) - - ### Bug Fixes - - - **Keyboard and VoiceOver Accessibility**: Tab moves focus between panes and out of the AI rules field, opening a table or query tab focuses the right control, Escape dismisses search sheets, filtering keeps you on the keyboard, and VoiceOver labels and announcements were added across the grid, inspector, tabs, and sheets. Copy with Headers gains a default Cmd+Option+C shortcut ([#1490](https://github.com/TableProApp/TablePro/issues/1490)) - - **Details Pane on Click**: The Details pane updates when you select a row by clicking, not only with the arrow keys ([#1496](https://github.com/TableProApp/TablePro/issues/1496)) - - **EXPLAIN Plan Viewer**: Running EXPLAIN or EXPLAIN ANALYZE in the editor opens the plan viewer instead of one truncated grid cell ([#1480](https://github.com/TableProApp/TablePro/issues/1480)) - - **Faster Table Open**: Opening a table on a connection with many tables no longer stalls while autocomplete and metadata load ([#1483](https://github.com/TableProApp/TablePro/issues/1483)) - - **Connection Group Sync**: Moving a connection into or out of a group syncs across devices - - **Cassandra Encrypted Keys**: Cassandra SSL connections with a client certificate gain a Key Passphrase field and clearer error messages ([#1487](https://github.com/TableProApp/TablePro/issues/1487)) - - - - ### New Features - - - **Rectangular Cell Selection**: Click and drag to select a range in the data grid. Shift and Cmd modifiers extend or add cells, and Cmd+C copies the selection as TSV ([#1446](https://github.com/TableProApp/TablePro/issues/1446)) - - **Favorite Connections**: Star a connection from the welcome screen and it appears in a Favorites section at the top of the list ([#1302](https://github.com/TableProApp/TablePro/issues/1302)) - - **Foreign Key in New Tab**: Cmd-click a foreign key arrow, or pick Open in New Tab from the right-click menu, to open the referenced table without replacing the current one ([#1421](https://github.com/TableProApp/TablePro/issues/1421)) - - **Add and Remove in Table Structure**: The structure editor gains add and remove buttons for columns, indexes, and foreign keys, with Cmd+Shift+N and Cmd+Delete shortcuts ([#1319](https://github.com/TableProApp/TablePro/issues/1319)) - - **OpenCode Zen AI Provider**: A new AI provider with free models when no key is set ([#1400](https://github.com/TableProApp/TablePro/issues/1400)) - - **Auto JSON and PHP Cell Viewer**: Text cells holding JSON or PHP serialized values open in the structured viewer automatically - - **BigQuery Dataset Tree**: BigQuery datasets show as expandable nodes in the sidebar instead of one at a time behind a picker - - **Oracle 11g and SID Connections**: Oracle Database 11g (11.1 and 11.2) now connects, and Oracle connections can use a SID instead of a service name ([#1425](https://github.com/TableProApp/TablePro/issues/1425)) - - ### Improvements - - - **Clear Query Results**: The query trash button clears results too, and a Clear Results menu item clears results alone ([#1256](https://github.com/TableProApp/TablePro/issues/1256)) - - **AI SQL Into New Tab**: Inserting SQL from AI Chat opens a new query tab, or fills an empty editor in place ([#1257](https://github.com/TableProApp/TablePro/issues/1257)) - - ### Bug Fixes - - - **Toolbar Customizations Persist**: Toolbar customizations stick across closing and reopening a session window ([#1455](https://github.com/TableProApp/TablePro/issues/1455)) - - **MongoDB NaN and Infinity Crash**: Opening a MongoDB collection no longer crashes on documents containing NaN or infinite numbers ([#1418](https://github.com/TableProApp/TablePro/issues/1418)) - - **Paste Rows with Commas and NULL**: Pasting rows with commas in a cell keeps each value in its own column and preserves NULL vs the literal text "NULL" - - **BigQuery Table Switch**: Switching to another table in BigQuery loads its data immediately instead of leaving the grid empty - - **Custom AI Base URL ending /v1**: Custom and OpenAI-compatible AI providers work when the base URL ends in `/v1` ([#1400](https://github.com/TableProApp/TablePro/issues/1400)) - - **Plugin Update Wait**: Connecting after an app update waits for in-progress plugin updates; when no compatible plugin build exists yet, the message asks you to update TablePro ([#1380](https://github.com/TableProApp/TablePro/issues/1380)) - - **Detailed Saved Connection Errors**: Failed saved connections show the Test Connection troubleshooting dialog instead of a generic alert (#1425, #483) - - **Plain-Language Oracle Errors**: Oracle connection errors explain the cause in plain language instead of the driver's raw message ([#483](https://github.com/TableProApp/TablePro/issues/483)) - - **AWS IAM Profile Sources**: AWS IAM authentication with a named profile reads `~/.aws/config` and supports `credential_process`, so SSO, IAM Identity Center, and assume-role profiles work ([#1291](https://github.com/TableProApp/TablePro/issues/1291)) - - **Single Initial Query**: Opening a table no longer runs the initial query multiple times before data arrives - - **iOS Safe Mode Persist**: Safe Mode setting survives relaunch on iOS instead of reverting to Off after iCloud sync - - **iOS Large Result Stability**: Large query results no longer crash the app on iOS; the editor keeps the rows it loaded and suggests adding LIMIT - - **iOS Confirm Writes Prompt**: Safe Mode "Confirm Writes" prompts before grid edits and inserts on iOS, matching the query editor - - **Redshift Schema and Filters**: Schema switching, table search, and contains/starts with/ends with filters now work on Redshift ([#1439](https://github.com/TableProApp/TablePro/issues/1439)) - - **MCP Require Auth**: Turning on Require Authentication no longer hangs the first request, generates a default token if needed, and shows it once. The Generate Token sheet focuses the Token Name field on first open ([#1093](https://github.com/TableProApp/TablePro/issues/1093)) - - **Direct CSV and TSV Open**: Double-clicking a CSV or TSV file when TablePro is closed opens the file directly ([#1443](https://github.com/TableProApp/TablePro/issues/1443)) - - **Tab Named After SQL File**: Opening a `.sql` file names the tab after the file instead of "SQL Query" ([#1220](https://github.com/TableProApp/TablePro/issues/1220)) - - **Server Dashboard Slow Queries**: Server Dashboard shows the Slow Queries panel, with a draggable vertical split and remembered divider positions ([#1464](https://github.com/TableProApp/TablePro/issues/1464)) - - - - ### New Features - - - **Cloudflare Tunnel**: Connect to a database behind Cloudflare Access. TablePro starts and stops `cloudflared access tcp` per connection, the same way it manages SSH tunnels, with browser sign-in or a service token. Needs cloudflared (`brew install cloudflared`) ([#1285](https://github.com/TableProApp/TablePro/issues/1285)) - - **AWS IAM Authentication**: Authenticate PostgreSQL and MySQL connections to RDS and Aurora with AWS IAM, using an access key, named profile, or SSO. TablePro generates a fresh token on every connect and requires SSL ([#1291](https://github.com/TableProApp/TablePro/issues/1291)) - - **Fill Column**: Right-click a column header to set one value across all loaded rows. The fill is staged like a normal edit, and one undo reverts it. Not available on primary key columns ([#1304](https://github.com/TableProApp/TablePro/issues/1304)) - - **Date Picker**: Date, datetime, timestamp, and time cells show a date picker from the chevron button. Double-clicking still edits the cell as text, and the picker keeps the value's format, fractional seconds, and timezone offset ([#1405](https://github.com/TableProApp/TablePro/issues/1405)) - - **Pagination Bar**: Table tabs gain a pagination bar with a rows-per-page menu (5, 10, 20, 100, 500, 1,000, All rows, or custom) and First, Previous, Next, and Last buttons. Click the page indicator to jump to a page, and page through filtered tables even when the total row count is unknown. First Page and Last Page are assignable keyboard actions in Settings > Keyboard ([#1364](https://github.com/TableProApp/TablePro/issues/1364)) - - **Pretty JSON Cells**: JSON and JSONB cells display pretty-printed by default, keeping your key order and exact numbers. Viewing or reformatting no longer marks the row changed, and saving no longer reorders keys or rounds large integers - - ### Bug Fixes - - - **Font Size Persists**: Changing the editor or data grid font size in Appearance settings now applies immediately and persists across relaunch, with no orphan custom themes left behind ([#1381](https://github.com/TableProApp/TablePro/issues/1381)) - - **Plugin Install After Update**: Installing or updating a plugin right after updating TablePro now refetches the plugin list first, so it no longer fails against a stale cached list ([#1380](https://github.com/TableProApp/TablePro/issues/1380)) - - **Esc Keeps Fullscreen**: Pressing Esc to close the raw SQL filter suggestions, or to clear a search field, no longer also exits fullscreen ([#1403](https://github.com/TableProApp/TablePro/issues/1403)) - - **Clear MCP OAuth Error**: Connecting an OAuth-capable MCP client like Claude Code with an invalid or expired token now shows a clear error instead of "Invalid OAuth error response" ([#1409](https://github.com/TableProApp/TablePro/issues/1409)) - - - - ### New Features - - - **DataGrip Import**: Import connections and passwords from DataGrip, including SSH tunnels and SSL settings. The source app doesn't need to be running ([#1374](https://github.com/TableProApp/TablePro/issues/1374)) - - ### Improvements - - - **Active Connections Popover**: Active Connections is now a searchable toolbar popover instead of a modal dialog ([#1350](https://github.com/TableProApp/TablePro/issues/1350)) - - **Filter Suggestions Everywhere**: The raw SQL filter suggests columns and keywords at every position, including after AND and OR, instead of only the first ([#1346](https://github.com/TableProApp/TablePro/issues/1346)) - - **Quieter Plugin Updates**: Plugins left incompatible after a TablePro update now update in the background instead of showing a premature "could not be loaded" alert. You are notified only when no compatible version exists yet ([#1322](https://github.com/TableProApp/TablePro/issues/1322)) - - ### Bug Fixes - - - **Remembered Filters**: Reopening a table restores the filter you had applied, per connection. Removing or clearing a filter is remembered too, so an unfiltered table reopens with no filter ([#1347](https://github.com/TableProApp/TablePro/issues/1347)) - - **Filtered Row Counts**: Filtering a table updates the row and page counts to match the filtered result, instead of the whole-table totals - - **PostgreSQL Without pg_matviews**: Connecting to a PostgreSQL-compatible engine without the pg_matviews catalog (such as db9.ai) no longer fails to load tables ([#1383](https://github.com/TableProApp/TablePro/issues/1383)) - - **Quick Switcher Height**: The quick switcher panel fits its results instead of leaving empty space below short lists ([#1349](https://github.com/TableProApp/TablePro/issues/1349)) - - **TablePlus Passwords**: Importing connections from TablePlus brings over saved passwords again, after a recent release looked under the wrong keychain name - - **TablePlus SSH Import**: Importing an SSH connection from TablePlus no longer fills in a fake private key path when no key was selected, and skips empty TLS certificate paths - - **DBeaver Keychain Prompt**: Importing from DBeaver no longer shows an unnecessary keychain permission warning, since DBeaver stores passwords in its own file - - **Manual Plugin Install**: A plugin you download and install by hand is no longer blocked by macOS Gatekeeper once its signature is verified ([#1322](https://github.com/TableProApp/TablePro/issues/1322)) - - **Reliable Tab Reuse**: Clicking a table replaces the active tab instead of opening a new one when you have multiple tabs open. A new tab still opens for unsaved edits, an applied filter, or sorting; double-click always opens a new tab ([#1348](https://github.com/TableProApp/TablePro/issues/1348)) - - - - ### Bug Fixes - - - **Add Row Crash**: Adding a row no longer crashes when a cell editor or value viewer is open ([#1378](https://github.com/TableProApp/TablePro/issues/1378)) - - - - ### Improvements - - - **Skip Heavy Columns**: Hiding a column now also drops it from the query, so tables with one heavy column load faster. The primary key is always fetched so editing still works - - ### Removed - - - **Embedded Terminal**: The built-in database CLI terminal has been removed. Use your system terminal for command-line access to your databases. - - ### Bug Fixes - - - **Plugin Crash on Open**: Opening a connection no longer crashes when its database plugin is older than the app; outdated plugins update automatically ([#1371](https://github.com/TableProApp/TablePro/issues/1371)) - - **Safe Mode Sticks**: Safe mode stays set for the connection and no longer resets when you open another table ([#1351](https://github.com/TableProApp/TablePro/issues/1351)) - - **Shortcut Reassignment**: Reassigning the Execute Query, Execute All Statements, and Cancel Query shortcuts now takes effect and shows in the Query menu, and custom shortcuts require a modifier key ([#1357](https://github.com/TableProApp/TablePro/issues/1357)) - - **Connection Cancellation**: Cancelling a pending connection no longer lets the abandoned attempt overwrite a later successful one, and cancelling a pending SSH connection now closes its tunnel (#1358, #1369) - - **DBeaver Import**: Importing connections from DBeaver now brings over the username ([#1355](https://github.com/TableProApp/TablePro/issues/1355)) - - **Copy Visible Columns**: Copying rows includes only the visible columns, in their current order ([#1354](https://github.com/TableProApp/TablePro/issues/1354)) - - **Accurate Table Query**: The query shown in the editor when you open a table now matches the query that actually runs - - **Full Text Cells**: Large text columns are no longer truncated to 256 characters when browsing a table, so editing a cell can't save a shortened value - - - - ### New Features - - - **Copy Column Values**: Right-click a column header to copy all its loaded values ([#1325](https://github.com/TableProApp/TablePro/issues/1325)) - - **More Copy Formats**: The row "Copy as" submenu adds CSV, CSV with Headers, Markdown table, and an IN clause for `WHERE id IN (...)` lookups ([#1325](https://github.com/TableProApp/TablePro/issues/1325)) - - **Read-Only Cell Viewer**: Double-click or press Return on a read-only result cell to open a selectable text viewer. JSON columns open a viewer popover and BLOB columns open the hex viewer ([#1336](https://github.com/TableProApp/TablePro/issues/1336)) - - ### Improvements - - - **Plugins Recover on Their Own**: A plugin that fails to load no longer interrupts launch with an alert. The app posts a notification and lists rejected plugins in Settings > Plugins with Update Now and Remove buttons, and they auto-update from the registry in the background ([#1322](https://github.com/TableProApp/TablePro/issues/1322)) - - **Right Plugin Binary**: The app installs the plugin binary built for its own version, even when the registry holds binaries for other versions ([#1322](https://github.com/TableProApp/TablePro/issues/1322)) - - **Deferred Plugin Updates**: A plugin update that arrives while a connection is open installs when you close the connection or quit, instead of being blocked - - **Plugin Status at a Glance**: Settings > Plugins shows a badge with the count of rejected plugins and available updates, connections with a failed driver show a yellow warning triangle, and the connection form shows an inline Update Plugin banner - - **Copy and Filter Shortcuts**: `Cmd+C` copies the focused cell when one row is selected and a cell has focus, otherwise the selected row(s) as TSV; `Cmd+Shift+C` always copies row(s) as TSV. `Cmd+F` toggles the filter panel on a table and opens Find in the SQL editor, replacing the old `Cmd+Shift+F` filter shortcut ([#1332](https://github.com/TableProApp/TablePro/issues/1332)) - - ### Bug Fixes - - - **PluginKit Version Errors**: Fixes the recurring "Plugin was built with PluginKit version N, but version M is required" error after app updates. Rejected plugins now recover without manual steps ([#1322](https://github.com/TableProApp/TablePro/issues/1322)) - - **DuckDB Types and Schemas**: Spatial `GEOMETRY` renders as WKT, `HUGEINT` and `UHUGEINT` keep full precision and handle negatives, streaming results respect the row cap and render `TIMESTAMPTZ`/`TIMETZ`/`GEOMETRY`, ENUMs resolve in non-`main` schemas, BC years show a leading minus, and schema reads handle apostrophes and concurrent switches ([#1324](https://github.com/TableProApp/TablePro/issues/1324)) - - **Open SQLite from Finder**: `.db`, `.db3`, `.s3db`, `.sl3`, and `.sqlitedb` files open in TablePro from Finder ([#1327](https://github.com/TableProApp/TablePro/issues/1327)) - - **DynamoDB SSO**: Connections work with `sso-session` profiles right after `aws sso login`, with no extra AWS CLI command ([#1333](https://github.com/TableProApp/TablePro/issues/1333)) - - - - ### New Features - - - **Beekeeper Studio Import**: Bring connections over from Beekeeper Studio, including encrypted passwords and SSH bastion hosts - - **Schema Picker in Sidebar**: New picker at the bottom of the Tables sidebar to switch the active schema ([#1296](https://github.com/TableProApp/TablePro/issues/1296)) - - **Inline Enum Picker**: Edit ENUM and SET columns via a dropdown on MySQL, MariaDB, PostgreSQL, ClickHouse, DuckDB, and MongoDB JSON-schema enums ([#1283](https://github.com/TableProApp/TablePro/issues/1283)) - - **CSV/TSV Inspector**: Open, edit, filter, sort, and re-shape CSV files with undo/redo and auto-reload on external changes; large files stream from disk ([#1259](https://github.com/TableProApp/TablePro/issues/1259)) - - **iOS SQL Server**: Connect to SQL Server over TDS 7.4 from iOS, with schema browsing, data browser, search, filter, and transactions - - **iOS Sync Controls**: Settings > Sync now shows last sync time with Sync Now and Refresh from iCloud actions - - ### Improvements - - - **Native SSL Defaults**: New PostgreSQL, MySQL, MariaDB, SQL Server, Redshift, and CockroachDB connections default to Preferred SSL mode, matching the native client library defaults. Hosted databases like AWS RDS and Cloud SQL work out of the box - - **MySQL/MariaDB 2-Pass Connect**: Preferred mode tries TLS first and falls back to plaintext only on SSL handshake errors. Auth and network errors are not retried - - **Per-Engine SSL Guidance**: The SSL pane shows engine-specific advice and warns inline when the driver has no TLS fallback for Preferred mode - - **Structured SSL Error Messages**: Connection failures caused by SSL handshake errors now name the cause and recommend an SSL Mode to switch to. Credentials are redacted from the raw driver response - - **Filter Rows Enum Dropdown**: Filter operators `=` and `!=` show an enum dropdown on enum columns ([#1283](https://github.com/TableProApp/TablePro/issues/1283)) - - ### Bug Fixes - - - **PostgreSQL Hosted Connections**: AWS RDS, Cloud SQL, Azure, and other hosted Postgres no longer fail with "no pg_hba.conf entry for host" ([#1298](https://github.com/TableProApp/TablePro/issues/1298)) - - **Oracle SSL Respected**: SSL/TCPS settings from the SSL pane now wire through to OracleNIO; previously every Oracle connection was plain TCP - - **Cassandra SSL Respected**: SSL settings from the SSL pane now reach the driver; previously every Cassandra connection was plain TCP - - **MySQL Hosted TLS**: Cloud SQL, Azure Database, and other hosted MySQL servers that require TLS no longer fail under Preferred mode - - **ClickHouse Plain HTTP via DNS**: ClickHouse and other HTTP-based drivers can now connect to plain-HTTP servers addressed by hostname ([#1316](https://github.com/TableProApp/TablePro/issues/1316)) - - **Import App Detection**: TablePlus, Sequel Ace, and DBeaver are now picked up via LaunchServices before they have been opened ([#1305](https://github.com/TableProApp/TablePro/issues/1305)) - - **TablePlus Password Import**: Reads passwords from the keychain correctly instead of returning empty - - **Port Number Locale**: Ports no longer render with a thousand separator on locales that use a dot as a digit separator - - **New Query Tab Focus**: Cmd+T no longer jumps focus back to the previous table tab on SQLite and other file-based databases ([#1313](https://github.com/TableProApp/TablePro/issues/1313)) - - **File-Based Sidebar Flash**: SQLite and DuckDB sidebars no longer flash on window focus; external changes are picked up via the file watcher instead - - - - ### New Features - - - **CockroachDB**: Connect over the PostgreSQL wire protocol, with a Connection Options field for libpq routing such as CockroachDB Cloud cluster names ([#1226](https://github.com/TableProApp/TablePro/issues/1226)) - - **AI Chat OpenAI Responses API**: GPT-5 and Codex models now use the Responses API. Reasoning shows in a collapsible Thinking panel, with a reasoning-effort picker (Minimal to Extra High), a curated model picker, strict tool schemas by default, and image input via paste or drop (HEIC, TIFF, and BMP convert to PNG or JPEG; EXIF and GPS are stripped before sending) ([#1112](https://github.com/TableProApp/TablePro/issues/1112)) - - **AI Chat Claude Extended Thinking**: Opus 4.7, Sonnet 4.6, and Haiku 4.5 now show reasoning in the same Thinking panel ([#1112](https://github.com/TableProApp/TablePro/issues/1112)) - - **iOS SQL Server**: Connect to SQL Server over TDS 7.4. Data browser, search, filter, and pagination all use SQL Server syntax - - **iOS Sync Controls**: Settings > Sync shows last sync time, with Sync Now and Refresh from iCloud to re-download missing connections, groups, and tags - - **Default Row Sort**: Settings > Data Grid lets you open every table sorted by primary key or first column. Click any column header to override ([#1284](https://github.com/TableProApp/TablePro/issues/1284)) - - ### Improvements - - - **Database Switcher Popover**: The toolbar database chip now opens a popover with an active-row checkmark, search, Refresh, and an engine-aware New Database footer. ⌘N and ⌘R bind globally inside the popover - - **Native New / Drop Database Dialogs**: Both flows use the native sheet and confirmation dialogs instead of custom sheets - - ### Bug Fixes - - - **AI Chat Gemini and DeepSeek**: Gemini tool calls no longer fail with 400, `thoughtSignature` round-trips after tool runs, and DeepSeek V4 thinking is captured across turns - - **AI Chat GitHub Copilot**: Tool registration accepts optional fields instead of failing with "Expected string" - - **MySQL and MariaDB BIT Columns**: `BIT(N)` values display as decimal numbers instead of raw bytes that rendered as control characters in the grid ([#1272](https://github.com/TableProApp/TablePro/issues/1272)) - - **Structure Tab Refresh**: ⌘R now picks up external schema changes on the Columns, DDL, and ClickHouse Parts sub-tabs immediately ([#1281](https://github.com/TableProApp/TablePro/issues/1281)) - - **Query Editor Autocomplete**: Enter and ⌘+Enter work after accepting a suggestion; double-clicking a suggestion no longer closes the window; renamed columns appear after Refresh ([#1278](https://github.com/TableProApp/TablePro/issues/1278)) - - **HTTP Query Timeouts**: ClickHouse, BigQuery, CloudflareD1, LibSQL, Etcd, and DynamoDB long-running queries now honor Settings > Query timeout instead of cutting off at 30 seconds ([#1267](https://github.com/TableProApp/TablePro/issues/1267)) - - **MongoDB Username Field**: The connection form shows the Username field for auth-optional engines, so connections to auth-enabled servers stop failing with "requires authentication" - - **MongoDB Multi-Host Editor**: Deleting a host keeps the list interactive instead of freezing until focus is lost and regained ([#1293](https://github.com/TableProApp/TablePro/issues/1293)) - - **SQL Import**: PostgreSQL dollar-quoted function bodies parse correctly, and statements are no longer dropped when the database is slower than the parser ([#1264](https://github.com/TableProApp/TablePro/issues/1264)) - - **SQL Export**: Views, materialized views, and foreign tables emit the matching `DROP` so re-import no longer fails with "is not a table" ([#1264](https://github.com/TableProApp/TablePro/issues/1264)) - - **iOS Data Persistence**: Connections, groups, and tags survive TestFlight and App Store updates - - ### Security - - - **AI Chat Destructive Operation Confirmation**: `DROP`, `TRUNCATE`, and `ALTER...DROP` always prompt for approval. Silent mode and Always Allow no longer bypass the check - - ### Notes - - - **Report an Issue Removed**: The Help menu item now opens GitHub Issues in the browser instead of a built-in panel - - - - ### New Features - - - **Backup and Restore**: New File menu entries `Backup Dump…` and `Restore Dump…` for PostgreSQL and Redshift, with live progress, cancel, and SSH tunnel reuse ([#1211](https://github.com/TableProApp/TablePro/issues/1211)) - - ### Bug Fixes - - - **Redis SSL**: "Required (skip verify)" mode now actually skips certificate verification, so Upstash and other untrusted-CA endpoints connect ([#1247](https://github.com/TableProApp/TablePro/issues/1247)) - - **MSSQL SSL**: SSL mode setting now affects the connection. Previously every mode was silently ignored - - **MongoDB SSL**: "Required" and "Verify CA" modes now connect to self-signed and untrusted-CA servers instead of failing - - **MongoDB Crash on macOS**: Connecting no longer crashes with a `dispatch_sync` libdispatch trap ([#1249](https://github.com/TableProApp/TablePro/issues/1249)) - - **MongoDB Atlas TLS on macOS 26**: TLS handshake to Atlas no longer fails with `internal error (-9838)`. The Mongo plugin now uses OpenSSL instead of Apple Secure Transport - - **MongoDB URL Import**: Importing a connection string with no database path now works for Atlas users restricted to a single database - - **MySQL SSL**: The CA certificate is no longer loaded when the SSL mode skips verification, matching PostgreSQL - - ### Notes - - - **Plugin format updated**. Older plugin builds no longer load. Reinstall plugins from the registry after updating. - - - - ### Bug Fixes - - - **AI Chat**: Scrolling stays smooth on long conversations and stream completion no longer briefly hides the chat ([#1239](https://github.com/TableProApp/TablePro/issues/1239)) - - **AI Chat**: Starting a new conversation no longer carries context from the previous one - - **PostgreSQL**: Connecting to servers older than 9.3 no longer fails on schema load ([#1240](https://github.com/TableProApp/TablePro/issues/1240)) - - **MySQL**: EXPLAIN now offers a plain variant that works on every version - - **MSSQL**: Editing a view on SQL Server 2014 or earlier no longer fails with a syntax error - - **Cassandra**: Connecting to a 2.x server now shows a clear unsupported-version message instead of failing on sidebar load - - **MongoDB**: Connecting to servers older than 3.4 no longer fails on the database listing - - **ClickHouse**: The index sidebar no longer fails on versions older than 19.17 - - - - ### New Features - - - **Set Value SQL Presets**: Right-click a date, datetime, or timestamp cell to insert `CURRENT_DATE`, `CURRENT_TIME`, `NOW()`, or `CURRENT_TIMESTAMP`, filtered by column type - - **Import from Other App on Welcome Screen**: One-click button on the welcome left pane to import connections from TablePlus, Sequel Ace, or DBeaver - - ### Improvements - - - **Row Numbers Continue Across Pages**: The `#` column reads `1001-2000` on page 2 instead of restarting at 1, and auto-sizes so long numbers no longer clip - - **Foreign Key Preview Follows Selection**: Press Space on an FK cell, then arrow up or down. The popover slides to the next row and refreshes its content - - **Inline Editor for Date Cells**: Double-click a `DATE`, `DATETIME`, or `TIMESTAMP` cell to type a new value directly; the popover date picker is removed - - **Inline Connecting State**: The connection window shows the connecting state with a Cancel button instead of an empty sidebar - - ### Bug Fixes - - - Closing the connection window during a slow connect no longer leaves a stuck "Connecting…" window or a stray failure alert ([#1185](https://github.com/TableProApp/TablePro/issues/1185)) - - Cmd+Z while editing a cell now undoes typing in the editor; pressing it after dismissing no longer crashes - - Cmd+Z right after Add Row no longer leaves a stranded editor over the removed row - - Editing a NULL cell and dismissing without typing no longer flips the value to an empty string - - Double-clicking another cell while editing no longer drops pending changes or stalls the new editor - - Double-clicking an enum, set, or boolean cell now opens the inline text editor; the chevron still opens the picker - - Chevron-accessory cells (enum, boolean, JSON, blob) no longer truncate short values that fit the full width - - DATE columns no longer render a phantom `00:00:00` time suffix - - Adding a new row no longer renders the row on top of the auto-opened editor mid-animation - - - - ### Improvements - - - **Quick Switcher**: Matches the Open Database dialog and shows a Recent section per connection - - **Connection Switcher and SQL Preview**: Open as sheets so they work from the toolbar, overflow menu, and keyboard shortcuts - - **Toolbar**: Filters button moved out; the bottom-bar Filters control remains - - **Welcome Screen**: Drops the Import Connections button; both import flows remain in the + menu - - ### Bug Fixes - - - Toolbar overflow menu entries now fire their action when the window is narrowed - - SQL Preview no longer freezes when previewing a very large batch - - Quick Switcher crash on macOS 26 - - Registry updates for bundled drivers (ClickHouse, Redis) now persist after restart - - - - ### New Features - - - **Vim Mode**: Full standard command surface in the SQL editor. Motions, operators, text objects, named and numbered registers, macros, marks, search, dot-repeat, scroll, and `gv` all behave like vim - - **Sidebar Object Groups**: Database objects split into Tables, Views, Materialized Views, Foreign Tables, Procedures, and Functions. Routines load on connect for Postgres and MySQL; "Show DDL" opens in a new query tab ([#1038](https://github.com/TableProApp/TablePro/issues/1038)) - - **iOS Live Activity**: Running queries show preview, elapsed time, and row count on the lock screen and Dynamic Island - - **iOS iPad Multi-Window**: Drag a tab off to open a second window; each window remembers its own selected connection - - **iOS App Lock**: Face ID, Touch ID, or Optic ID with cold-launch protection and idle timeout (1, 5, 15, or 60 minutes) - - **iOS Background iCloud Sync**: Sync runs every 30 minutes while the app is closed - - **iOS Connection Info Tab**: Host, SSL, SSH tunnel, active database, and live connection status in one place - - **iOS Search Persistence**: Search text in Tables and Data Browser persists across process kill; Cmd+F focuses the search field - - **iOS Settings**: New toggles for iCloud Sync, Rows per Page (50/100/200/500), Default Safe Mode, and "Hide query in Live Activities" - - **MCP Zed Support**: MCP Setup sheet adds Zed alongside Claude Desktop, Claude Code, and Cursor - - ### Improvements - - - **Faster Wide-Table Scroll**: Max main-thread stall during scroll drops from 3.5s to about 1.3s. Display cache raised to 50,000 rows / 64 MB; date format cache to 100,000 entries - - **Quieter Cross-Window Events**: Editing a favorite, query-history entry, or linked SQL folder in one window no longer refreshes other windows on unrelated connections - - **iOS Vietnamese Localization**: 312/312 keys translated - - **iOS Accessibility**: VoiceOver reads connection and table rows as combined elements with tap hints; icon-only toolbar buttons gain labels; metadata badges cap at the first accessibility text size - - **PostgreSQL Round-Trip Export**: Foreign keys emitted via `ALTER TABLE ... ADD CONSTRAINT` after data load; sequences re-synced via `setval` so re-imported dumps round-trip cleanly ([#1114](https://github.com/TableProApp/TablePro/issues/1114)) - - **Streaming SQL Import**: Bounded streaming and run-length string append reduce memory and CPU on large files ([#1114](https://github.com/TableProApp/TablePro/issues/1114)) - - **AI Chat Refinements**: Inline-suggestion debounce configurable (default 500 ms); 30-second SSE keep-alive on MCP HTTP; Anthropic model list through Claude 4.7; per-tool access modes; native button styles and accessibility labels - - **Translucent Surfaces**: Welcome sidebar, settings banners, ER diagram toolbar, JSON editor, and Pro scrim swap to solid surfaces when Reduce Transparency or Increase Contrast is on - - **Typed Binary Cells**: Engines that expose binary types (PostgreSQL BYTEA, Oracle RAW/LONG_RAW/BLOB, MySQL BLOB, SQLite BLOB, MSSQL VARBINARY/IMAGE, DuckDB BLOB, Cassandra blob, MongoDB BSON, DynamoDB B, BigQuery BYTES) route through a typed pipeline; hex editor saves bind raw bytes through the driver's binary parameter format ([#1188](https://github.com/TableProApp/TablePro/issues/1188)) - - **Plugin Restart UX**: "Restart TablePro" banner gains a Quit & Reopen button with an alert on relaunch failure; install badges auto-clear after 3 seconds; the restart-needed flag no longer persists across launches - - ### Bug Fixes - - - **Vim Shift+J**: Joins lines as expected; gJ joins without a space, count prefixes work, J in visual mode joins every selected line. `w` and `W` no longer overshoot; Esc switches mode after typing `;` ([#1222](https://github.com/TableProApp/TablePro/issues/1222)) - - **AI Chat Agent Mode**: Copilot Agent persists across follow-up turns instead of reverting to plain chat after the first message - - **AI Chat Streaming**: No longer hangs at 100% CPU; Agent-mode text emitted before a tool call now shows above the tool card; Markdown rendering moved to a native renderer ([#1205](https://github.com/TableProApp/TablePro/issues/1205)) - - **AI Chat Mentions**: `@` mention detection handles emoji and other non-BMP characters at the cursor - - **iOS Data Browser Reload**: No longer flashes "No Data" mid-reload when search, filter, sort, or page size changes - - **iOS Row Detail Crash**: Filtered row detail pager no longer crashes (build 11 regression) - - **MySQL/MariaDB Cells**: `BINARY(N)` and `VARBINARY(N)` route through the blob editor; numeric, decimal, date, and time columns render as values instead of hex bytes; JSON column detection no longer flickers across refreshes - - **Plugin Install**: Built-in driver upgrades persist after restart ([#1192](https://github.com/TableProApp/TablePro/issues/1192)); concurrent installs no longer race past the install guard; multi-bundle ZIPs rejected with a clear error; auto-update preserves concurrent rejection entries - - **Structure Tab Tinting**: Edit, delete, undo, save, and discard repaint correctly; right-click "Undo Delete" works; dropdown columns enter inline edit; Cmd+Shift+N adds a row - - **SQL Server**: `USE ` switches databases; INSERTs skip IDENTITY columns; default schema comes from `SELECT SCHEMA_NAME()`; DATETIME edits round-trip as ISO 8601 - - **PostgreSQL SQL Round-Trip**: Tables sidebar refreshes after import; "Disable foreign key checks" sets `session_replication_role = replica`; `GENERATED ALWAYS AS IDENTITY` columns round-trip via `OVERRIDING SYSTEM VALUE`; values ending in backslash and dollar-quoted blocks parse correctly ([#1114](https://github.com/TableProApp/TablePro/issues/1114)) - - **Welcome Window**: Opens reliably on launch and Dock-icon clicks - - **Safe Mode Silent**: Holding Cmd+Return no longer stacks two confirmations or runs the query twice - - **iCloud Sync Resilience**: Decode failures for SSH, SSL, jump-host, or driver-specific fields no longer overwrite the cloud copy with empty defaults; app settings decode failures are logged instead of silently dropped - - **Keychain Errors**: Cancelled prompts, failed biometrics, and unknown OSStatus values are distinguished from "not found", preventing silent password loss - - **Smart Substitution**: Smart quotes, dashes, and text substitution no longer corrupt typed input in cell editors and filter inputs - - **Cross-Window Fetches**: Connecting one window no longer triggers a table fetch in unrelated windows - - **Foreign-Key Navigation**: From a tab with unsaved edits, the referenced table opens in a new window tab without wiping the original grid - - **Schema Picker**: PostgreSQL and Redshift schema pickers no longer hide user schemas starting with `pg` (`pgboss`, `pgcrypto`, `pgvector`, `pgaudit`) - - **Import from Other App**: Cancel button stops the keychain prompt loop instead of silently continuing ([#1134](https://github.com/TableProApp/TablePro/issues/1134)) - - - - ### Bug Fixes - - - **Launch Failure on 0.39.0**: App failed to open with "The application can't be opened" / errno 163. Production entitlements shipped a literal `$(AppIdentifierPrefix)` placeholder for the keychain access group because `codesign --entitlements` does not expand Xcode build variables. Reverted to the hardcoded team prefix ([#1104](https://github.com/TableProApp/TablePro/issues/1104)) - - - - ### New Features - - - **AI Chat Tool Calling**: The assistant can look up your database on demand. Read-only tools (`list_tables`, `describe_table`, `get_table_ddl`, etc.) auto-run; write and destructive ops show inline approval pills (Run / Always for this connection / Cancel). Works with Anthropic, OpenAI, OpenRouter, Gemini, Ollama, GitHub Copilot, and custom OpenAI-compatible endpoints. Ask / Edit / Agent mode picker controls which tools the model sees. Safe mode still gates execution - - **AI Chat `@` Mentions**: Attach Schema, a Table, the Current Query, recent Query Results, or a saved SQL query as chips. Type `@` in the composer or pick from the menu - - **AI Chat Slash Commands**: Built-in `/explain`, `/optimize`, `/fix`, `/help`, plus user-defined commands in Settings -> AI -> Custom Slash Commands with `{{query}}`, `{{schema}}`, `{{database}}`, `{{body}}` placeholders - - **AI Chat Per-Connection Rules**: Add custom guidance about table conventions, PII columns, and naming in the connection's AI Rules tab; the assistant sees it on every chat turn - - **AI Chat Panel Redesign**: Composer-focused chat tab with inline model picker and per-turn model attribution - - **Linked SQL Folders**: Point TablePro at a folder of `.sql` files for two-way sync with Favorites. Edit in TablePro or your editor; changes flow both ways. Frontmatter sets `@name`, `@keyword`, and `@description`; save-time conflicts show a side-by-side diff. Free - - **Database Type Chooser**: New Connection opens a categorised, searchable sheet listing every supported driver (Relational, Document, Key-Value, Analytical, Wide-Column, Cloud Native, Coordination & Config) - - **Free XLSX Export**: Excel export no longer requires Pro - - **Free Safe Mode**: Touch ID, Full, and Read Only Safe Mode levels no longer require Pro - - ### Improvements - - - **iOS Streaming Data Layer**: Large tables and queries stream rows instead of buffering the full result set. Memory pressure shrinks the row window automatically; CSV / JSON / SQL export stream too - - **Distinguishable Toolbar**: Multiple windows on the same database (e.g. `prod-safe`, `prod-unsafe`, `staging`) show a tinted engine icon plus connection name instead of duplicate "PostgreSQL 16.x" text ([#1044](https://github.com/TableProApp/TablePro/issues/1044)) - - **Connection-Scoped Favorites**: Opening a second tab on the same connection no longer reloads the favorites tree or flashes a spinner. Selection persists across windows - - **Connection Form Redesign**: Rebuilt around macOS HIG sidebar navigation (General, SSH Tunnel, SSL/TLS, Customization, Advanced). Cancel, Save, and Save & Connect live in the native window toolbar; Test Connection inline - - **Connection URL Import**: Moved into the database type chooser; paste a URL there instead of inside the form - - **AI Chat Opt-In Context**: New installs no longer auto-include schema, current query, and query results in every prompt; attach via `@` when you want them. Existing users keep their current settings - - **HIG Polish**: Hero icons scale with system text size, search fields use native `NSSearchField`, validation banners use the standard warning convention, form sheets switch to grouped Form layouts - - **Native Windows**: Welcome, Connection Form, and Integrations Activity now use SwiftUI scenes, fixing an assertion crash and restoring Integrations Activity at next launch - - **ER Diagram Accessibility**: Diagram nodes scale with system text size - - **Terminology**: "Read-Only" / "Read-Write" renamed to "Read Only" / "Read & Write" to match macOS HIG - - ### Bug Fixes - - - **Schema Switching on SQL Server / Oracle**: Cmd+K Quick Switcher schema selection no longer silently ignored - - **iOS MySQL Crash**: Fixed `EXC_BREAKPOINT` when opening some MySQL tables - - **iOS Local Network**: Connections to `.local` hostnames and local-network addresses (10.x, 192.168.x, link-local, IPv6 ULA) no longer time out silently - - **iOS Row List Crash**: Data browser and query result lists no longer crash with "Index out of range" when rows shrink mid-update ([#1094](https://github.com/TableProApp/TablePro/issues/1094)) - - **iOS Port Validation**: Out-of-range port for MySQL, PostgreSQL, or Redis reports a readable error instead of crashing ([#1094](https://github.com/TableProApp/TablePro/issues/1094)) - - **MariaDB Install Prompt**: New connection chooser no longer falsely prompts to install built-in lazy drivers - - **IME Editor Jump**: SQL editor no longer jumps to the end after committing Chinese, Japanese, or Korean words like "测试" ([#1012](https://github.com/TableProApp/TablePro/issues/1012)) - - **MongoDB SRV Port**: Connection strings strip the port from the host per the URI spec ([#1101](https://github.com/TableProApp/TablePro/issues/1101)) - - **Cmd+T Focus Flash**: New tabs no longer flash focus back to the previous tab - - **Cmd+X No Selection**: Cuts the current line, matching VS Code, Sublime, and Xcode ([#1075](https://github.com/TableProApp/TablePro/issues/1075)) - - **Cmd+A Trailing Newline**: Selecting all on a query ending with a newline highlights every line ([#1075](https://github.com/TableProApp/TablePro/issues/1075)) - - **Editor Window State**: Windows remember size, position, and zoom across launches - - **Personal Team Builds**: External-contributor builds on personal Apple Developer teams now work without an iCloud entitlement ([#1020](https://github.com/TableProApp/TablePro/issues/1020)) - - **SSH Auth Errors**: Auth-failure alerts now point at the actual cause (wrong password, wrong verification code, rejected key) ([#1005](https://github.com/TableProApp/TablePro/issues/1005)) - - **TOTP Rotation**: Codes that crossed a 30-second rotation boundary are no longer rejected - - **SSH Password vs Keyboard-Interactive**: Auth Method = Password now works against servers that only advertise keyboard-interactive (typical `pam_google_authenticator` setups) ([#1005](https://github.com/TableProApp/TablePro/issues/1005)) - - **SSH + Google Authenticator**: Connections with Password + TOTP no longer fail ([#1005](https://github.com/TableProApp/TablePro/issues/1005)) - - **Editor Caret Edge Cases**: Up arrow on the first line, Down on the last line, and Cmd+Left/Right at end-of-line with no trailing newline now work correctly ([#1007](https://github.com/TableProApp/TablePro/issues/1007)) - - **Caret Gutter Color**: Caret at end of query keeps its line-number color in the gutter - - **Multi-Window Tab Persistence**: All windows now persist on relaunch instead of all-but-one being dropped - - **Filter Autocomplete Focus**: Popover no longer steals keyboard focus from the text field on Full Keyboard Access - - **Toolbar Database Name**: No longer empty after relaunch when the last-used database is restored - - **Cmd+K Database Switch**: Switching databases no longer reverts in Cmd+T, query history, AI prompts, or several tab-creation paths ([#1043](https://github.com/TableProApp/TablePro/issues/1043)) - - **AI Provider Test**: No longer shows `unsupported URL` while editing a draft endpoint - - **Connection Form Coordinator**: No longer rebuilds on every parent re-render ([#1102](https://github.com/TableProApp/TablePro/issues/1102)) - - **AI Chat Composer**: IME safety, visible scroll bar, and Shift+Return for newline ([#1100](https://github.com/TableProApp/TablePro/issues/1100)) - - **AI Chat Tool Limit**: Roundtrip limit raised from 5 to 10 ([#1096](https://github.com/TableProApp/TablePro/issues/1096)) - - **AI Chat Rules Persistence**: Per-connection rules persist through `StoredConnection` and CloudKit sync ([#1098](https://github.com/TableProApp/TablePro/issues/1098)) - - **AI Chat Retry Button**: Hidden for non-recoverable errors (auth failure, deprecated model, invalid endpoint) - - **AI Chat Code Blocks**: Render with syntax highlighting and Insert even when the language tag is missing - - **AI Chat Insert**: Stays enabled when the chat panel has focus instead of the editor - - **MCP Errors**: Surface a readable message via `LocalizedError` ([#1095](https://github.com/TableProApp/TablePro/issues/1095)) - - **Data Grid Header Inset**: Column headers align with result-cell horizontal inset - - **Toolbar Status Inset**: Connection status keeps its left inset when no connection tag is shown - - - - ### New Features - - - **Check for Updates in Welcome Window**: New link next to the version number triggers Sparkle without leaving the screen - - **Integrations Activity Window**: Dedicated, resizable window for the MCP activity log and connected clients (Window menu). Sidebar split between Activity Log and Connected Clients, with native search, filter, refresh, and export. Position and size remembered across launches - - **Sample Database**: Chinook SQLite database bundled with the app. Open from the welcome screen with one click; reset via File menu - - **Connection String Detection**: Paste a `postgres://`, `mysql://`, `redis://`, or `mongodb://` URL into the connection form to auto-fill host, port, user, password, and database - - **SSH Config Aliases**: SSH tunnel resolves host aliases from `~/.ssh/config`. Type an alias like `aia-bastion` in the SSH host field and it works the same as `ssh aia-bastion` in a terminal. Supports glob patterns, `Match` directives, `ProxyJump`, hostname canonicalization, and `Include`. Live: edit `~/.ssh/config` and the next connection picks it up - - **Oracle 10G Authentication**: Accounts whose `password_versions` includes a 10G hash now connect successfully, matching DBeaver/JDBC/sqlplus - - **Oracle Test Connection Diagnostics**: Failed auth opens a focused diagnostic sheet with copy-able info, suggested actions, and a link to file an issue - - **MCP Protocol 2025-11-25**: Server now supports protocol versions `2025-06-18` and `2025-11-25`. Clients on the latest spec no longer downgrade. Includes structured tool output (`structuredContent`), tool annotations, completions capability, and streaming progress notifications - - ### Improvements - - - **Welcome Window Polished to macOS HIG**: Subtle drop shadow on the app icon, dynamic text styles, "Sponsor" button removed, "Create connection" uses the bordered control style, toolbar icons (+ / new group) gain a hover background and proper hit targets, window background uses native vibrancy - - **Settings > Integrations Flattened**: Now a flat preferences pane per macOS HIG. Configuration only (server toggle, status, port, row limits, query timeout, tokens, network options); activity moved to the new Integrations Activity window, setup snippets to a "Connect a Client…" sheet - - **MCP Idle Timeout Raised**: From 5 to 15 minutes, fewer reconnects during slow workflows - - **Sync Passwords Toggle**: Now shows a caption explaining the toggle only affects new saves. Existing passwords keep their current sync state until you re-save them - - **Keychain Hardening**: Non-syncing keychain items use `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`, keeping local-only secrets out of unencrypted device backups - - ### Bug Fixes - - - **Welcome / Connection / Feedback Window Position**: Windows now remember position and size across launches. Previously they always reopened centered because frame autosave was never set on the underlying NSWindow - - **Saved Passwords Disappearing**: Saved connection passwords no longer disappear after quitting and relaunching the app. Removed the destructive legacy-keychain migration that was deleting credentials on sandboxed macOS configurations - - **Cmd+Z Cell Background**: Undo after editing a cell no longer leaves the yellow "modified" highlight in place - - **Tab Switching Burst Lag**: Rapid Cmd+Number presses no longer queue tail animations after key release. AppKit tab switches are now applied synchronously - - **Oracle Type Coverage**: TIMESTAMP variants, INTERVAL DAY TO SECOND, INTERVAL YEAR TO MONTH, DATE, RAW, and BLOB columns render through typed decoders instead of garbled text. INTERVAL YEAR TO MONTH and BFILE columns no longer crash on row fetch. Unknown types display `` instead of crashing - - **Oracle 23ai Handshake**: Connections to 23ai cloud and containerized deployments no longer fail with `uncleanShutdown` mid-handshake - - **Plugin Re-install Prompt**: Connecting to a downloadable database type after the plugin is disabled or uninstalled now reopens the install prompt instead of failing - - **Redshift Schemas for Non-Admin Users**: Schema switcher no longer shows an empty list. Now reads from `pg_namespace` filtered by `has_schema_privilege`, matching what the user can actually use - - **MCP SSE Stream**: GET `/mcp` now opens a real Server-Sent Events stream. Previously the connection was closed immediately and `notifications/progress` events were dropped - - **MCP Concurrent Tool Calls**: No longer serialize at the dispatcher loop, each exchange dispatches in its own child task - - **MCP Session Recovery**: Stale `Mcp-Session-Id` after idle timeout returns a proper JSON-RPC `Session not found` envelope with HTTP 404, letting clients re-initialize cleanly instead of hanging until a 4-minute timeout - - ### Removed - - - **`useSSHConfig` toggle**: Per-connection toggle removed. `~/.ssh/config` is always consulted at connection time; explicit form values still take precedence over ssh config defaults - - - - ### New Features - - - **External API**: Connect Raycast, Cursor, Claude Desktop, and other MCP clients. Token-based pairing, per-connection access control, 90-day activity log - - **New MCP Tools**: `list_recent_tabs`, `search_query_history`, `open_connection_window`, `open_table_tab`, `focus_query_tab` - - **PostgreSQL ICU Collation**: Provider picker in Create Database for PG 15+ - - **Single-Click Cell Editing**: Click a focused cell to start editing, no double-click needed - - **Multi-Cell Paste**: Paste TSV data from the clipboard, grouped as one undo - - **Rich Copy**: Copy rows in TSV, HTML, and plain text for spreadsheet apps - - ### Improvements - - - Result safety cap is enforced after the query runs, not by rewriting your SQL. Status bar shows "Showing N rows (truncated)" with a Fetch All button when the cap kicks in - - Settings tab renamed from "MCP" to "Integrations" with sections for clients, activity log, and pairing - - MCP server lazy-starts on first request, no more manual enable - - Connection URL parsing supports SSH user:password@host, multi-host, MongoDB auth, Redis database index - - SSH Private Key auth auto-resolves keys from ~/.ssh/config and default locations - - Shift+Tab navigates to the previous cell; Cmd+Z is unified across editor and grid - - VoiceOver: column headers announce sort direction; cells expose row and column index ranges - - Activity log: Export to CSV, search across all fields - - AI provider settings allow manually entering a model name - - OpenSSL shared as dylib across app and plugins, ~15MB smaller bundle - - ### Bug Fixes - - - SELECT queries with `LIMIT N` now return N rows. The engine no longer strips your LIMIT and uses its own cap. Affected SQLite, DuckDB, LibSQL, ClickHouse, Redshift, Cloudflare D1, and the MCP query path. MSSQL and Oracle no longer silently inject ORDER BY either ([#956](https://github.com/TableProApp/TablePro/issues/956)) - - Crash on macOS 26 when opening SQL Preview - - File associations for .sql, .sqlite, .duckdb now appear in Finder's Open With menu - - New tab from the empty state replaces the placeholder instead of opening a separate window-tab - - PostgreSQL Create Database collation errors on glibc-initialized servers ([#927](https://github.com/TableProApp/TablePro/issues/927)) - - SSH agent paths now expand ~ for 1Password and similar agents - - Pairing approval: 5-minute countdown, searchable connection list, requires explicit Approve click - - Group and connection deletions persist before sync, fixing a race that could re-upload deleted records to iCloud - - ### Breaking Changes - - - Old name-based deep links (`tablepro://connect/{name}/...`) removed. Use UUID-keyed paths from "Copy Connection Deep Link" - - MCP server data directory moved to `~/Library/Application Support/TablePro/`. Re-pair external clients after upgrading - - Separately distributed plugins (Oracle, DuckDB, MSSQL, MongoDB, BigQuery, LibSQL, Cassandra, Etcd, Cloudflare D1, DynamoDB) require update. PluginKit ABI bumped to 9 - - Settings renamed: `enforceQueryResultLimit` to `truncateQueryResults`, `queryResultLimit` to `queryResultRowCap`. Custom values revert to default on first launch - - - - ### New Features - - - **GitHub Copilot**: Inline code suggestions, chat, OAuth sign-in, and schema-aware context - - **Query Parameters**: Use `:name` placeholders in SQL, fill values in an inline panel, execute with native prepared statements - - **Plugin Auto-Update**: Outdated plugins update silently at launch; one-click update in Settings - - **Connection Sharing**: Copy Connection String, Copy TablePro Link, and Copy as JSON via Share menu - - **MCP Server Security**: Token auth with three permission tiers, TLS, remote access, rate limiting, and one-click setup for Claude Code, Desktop, and Cursor - - ### Improvements - - - AI settings rewritten as a single tab with one active provider and per-provider config sheets - - Native search fields in keyboard shortcuts, database switcher, and quick switcher - - Filter value field uses native SwiftUI suggestion dropdown - - MCP bridge pins TLS certificate fingerprint - - ### Bug Fixes - - - Plugin ABI mismatch no longer crashes on launch - - IME input (Chinese, Japanese, Korean) works correctly in filter value field - - SQL parameter escaping handles control characters and edge-case formats - - Foreign app import SSL/SSH parsing for TablePlus, DBeaver, and Sequel Ace - - Export race conditions, missing confirmation dialogs, and empty states - - Window position restore, connection error display, localization gaps - - - - ### New Features - - - **MongoDB Replica Sets**: Multi-host connections for replica set clusters - - **JSON Results View**: Data/Structure/JSON toggle in the status bar - - **JSON Window**: Pop out JSON viewer into a resizable, fullscreen-capable window - - **Feedback Form**: Bug reports and feature requests via Help > Report an Issue - - **Local Only Connections**: Exclude individual connections from iCloud sync - - **MCP Query Safety**: Server-side confirmation for write and destructive queries - - ### Improvements - - - Import URL supports libSQL, D1, Oracle, ClickHouse, etcd with dynamic placeholders and clipboard auto-paste - - Filter operator picker shows SQL symbols alongside names - - SQL autocomplete suggests columns before FROM using cached schema - - Native macOS UI across cell editors, alerts, search, and toolbar - - ### Bug Fixes - - - Connection form overflow with SSH jump hosts and TOTP fields - - Missing confirmation on group deletion - - Crash when scrolling AI Chat during streaming on macOS 15.x - - Connection failure on PostgreSQL-compatible databases without `SET statement_timeout` - - Schema-qualified table names resolve correctly in autocomplete - - Alert dialogs use sheet attachment instead of bare modal - - - - ### New Features - - - **libSQL / Turso**: New database plugin for libSQL and Turso via Hrana HTTP protocol - - **JSON Viewer**: Text/tree toggle for viewing and editing JSON data in cells - - **MCP Server**: Built-in Model Context Protocol server with client management and status menu - - **Connection Import**: Import connections from TablePlus, Sequel Ace, and DBeaver - - **Database CLI Terminal**: Embedded terminal for mysql, psql, redis-cli, etc. (`Ctrl+Cmd+\``) - - **Structure Editing**: Alter existing table columns, indexes, foreign keys, and primary keys - - ### Improvements - - - Sidebar toggle uses Xcode-style navigator buttons next to traffic lights - - Sidebar and inspector panels use native macOS split view controls - - Theme colors adapt to system appearance and accent color automatically - - ### Bug Fixes - - - SQL formatter now preserves original case, UNION and parentheses spacing - - - - ### New Features - - - **Cancel Query**: Stop a running query from the toolbar or with `Cmd+.` - - **Execute All**: Run all statements in the editor with `Cmd+Shift+Enter` - - **Drop Database**: Drop databases from the switcher via context menu, toolbar, or Delete key - - **Query Result Limit**: Configurable row limit in Data Grid preferences - - **Structure Tab**: Search, sort, count badges, PK column, DDL view with highlighting, Copy As (CSV/JSON/SQL), dropdown pickers, destructive change confirmation - - **Structure Tab**: Charset/collation (MySQL), index prefix length, partial indexes (PostgreSQL), cross-schema FK, schema changes in query history - - **ClickHouse Parts**: Optimize table, drop/detach partition actions - - **Streaming Export**: Export query results directly from database with no memory limit - - **Import Error Handling**: Stop and Rollback, Stop and Commit, or Skip and Continue - - **Handoff**: Resume work across devices via NSUserActivity - - ### Improvements - - - Query tabs now load rows progressively (default 10,000) with Load More and Fetch All - - Main editor window rewritten on AppKit for faster tab opens and correct lifecycle - - Toolbar layout follows Apple HIG (sidebar left, connection center, view actions right) - - Export engine rewritten with streaming row fetch, macOS system progress, and atomic file writes - - SQL import parser rewritten with DELIMITER support, MySQL conditional/hash comments, and async decompression - - ### Bug Fixes - - - Fix selection highlight not covering the last line on Cmd+A - - Fix Cmd+W closing the connection window instead of clearing to empty state - - Fix ER Diagram and Server Dashboard replacing the current tab instead of opening a new one - - Fix welcome window stealing focus on connect - - Fix toolbar empty on second tab, menu shortcuts disabled after toolbar click - - Fix AI chat freeze with large queries or results in the system prompt - - Fix AI chat panel not updating when switching connections - - Fix schema not restored on reconnect for PostgreSQL, Redshift, and BigQuery - - Fix database lost after auto-reconnect - - Fix database switch closing windows before confirming success - - Fix Redis database selection not persisted across sessions - - Fix SSH jumphost lost after disconnect or app restart - - Fix password appearing missing when Keychain is locked after reboot - - Fix import rollback reporting and FK checks not restored after failure - - Fix JSON export coercing leading-zero strings to integers - - Fix XLSX export not splitting tables exceeding 1,048,576 rows - - Fix CSV formula injection guard to OWASP-standard prefixes - - Fix MQL export not validating JSON values - - Fix SQL export gzip compression blocking and not cancellable - - Fix export progress bar not reaching 100% - - - - ### Improvements - - - Revert in-app tab bar refactor to restore native macOS window tabs (stability) - - - - ### Improvements - - - OpenSSL updated to 3.4.3 (CVE-2025-9230, CVE-2025-9231) - - Memory pressure monitoring now reactive via DispatchSource - - ### Bug Fixes - - - Fix raw SQL injection via external URL scheme deeplinks: now requires user confirmation - - Fix MySQL prepared statements silently truncating columns larger than 64KB - - Fix MSSQL error messages misattributed when multiple connections open simultaneously - - Fix BigQuery filter injection via unescaped column names and unvalidated operators - - Fix app quitting without warning when tabs have unsaved edits - - Fix connection list corruption risk from non-atomic UserDefaults writes - - Fix stale user-installed plugins silently rejected with no UI feedback - - Fix SSL mode picker showing misleading "Required" instead of "Required (skip verify)" - - Fix plugin load blocking main thread on first connection after launch - - ### Security - - - SHA-256 checksum verification added to FreeTDS, Cassandra, and DuckDB build scripts - - - - ### Improvements - - - **Keyboard Shortcuts**: Follow macOS HIG. Quick Switcher remapped to ⌘⇧O, Format Query to ⌘⇧L, AI Explain (⌘L), Optimize (⌘⌥L), and Toggle Sidebar (⌘0) now wired to menu bar - - ### Bug Fixes - - - **AI Chat**: Fix app hanging during AI streaming, schema fetch, and conversation loading - - **SSH Tunnel**: Agent auth falls back to key file from `~/.ssh/config` when agent has no loaded identities - - **SSH Tunnel**: Fix connections failing to reconnect after idle or sleep - - **Data Grid**: Fix composite primary key tables editing/deleting wrong rows - - **Structure View**: Prevent saves from bypassing Safe Mode on read-only connections - - - - ### New Features - - - **iOS Database Icons**: Brand icons for MySQL, PostgreSQL, MongoDB, Redis, etc. instead of generic SF Symbols - - ### Bug Fixes - - - Fixed native tab bar "+" button always creating "Query 1" ([#727](https://github.com/TableProApp/TablePro/issues/727)) - - Fixed sidebar gap shifting when switching tabs ([#728](https://github.com/TableProApp/TablePro/issues/728)) - - Fixed SSH Agent auth failing on apps launched from Finder ([#729](https://github.com/TableProApp/TablePro/issues/729)) - - Fixed iOS SSH private key import not working during test connection ([#730](https://github.com/TableProApp/TablePro/issues/730)) - - Fixed iOS SQLite file picker not updating after file selection ([#732](https://github.com/TableProApp/TablePro/issues/732)) - - Fixed toggle inspector shortcut mismatch ([#726](https://github.com/TableProApp/TablePro/issues/726)) - - - - ### New Features - - - **Restore Sessions**: All open connections and tabs are restored after quitting the app ([#703](https://github.com/TableProApp/TablePro/issues/703)) - - **SQLite Auto-Refresh**: Schema changes from external tools (migrations, CLI) are detected automatically ([#704](https://github.com/TableProApp/TablePro/issues/704)) - - **Query Menu**: New dedicated menu for Execute, Explain, Format, and Preview SQL actions - - ### Improvements - - - **SQL Formatter**: Complete rewrite with token-based architecture, with proper handling of JOINs, subqueries, CASE expressions, CTEs, window functions, and 15+ SQL constructs ([#705](https://github.com/TableProApp/TablePro/issues/705)) - - **Keyboard Shortcuts**: Updated to follow macOS HIG: `⌘F` is Find, `⌘⇧F` for filters, `⌘⌥I` for inspector, `⌘0` for sidebar. Format Query and Pagination now customizable. - - **Menu Bar**: Restructured per macOS HIG: `⌘N` opens connection list ([#722](https://github.com/TableProApp/TablePro/issues/722)), Help search restored, duplicates removed - - **UI Colors**: Standardized to macOS semantic colors across all error/success/warning indicators - - ### Bug Fixes - - - Fixed auto-selection of first item failing with fast input in Database Switcher ([#714](https://github.com/TableProApp/TablePro/issues/714)) - - Fixed Ollama model selection and error messages in AI settings ([#712](https://github.com/TableProApp/TablePro/issues/712)) - - Fixed filter logic: `= NULL` auto-converts to `IS NULL`, BETWEEN works on all drivers, IN/NOT IN handles NULL values ([#706](https://github.com/TableProApp/TablePro/issues/706)) - - Fixed UI layout breaks when toggling menus, panels, and inspectors ([#702](https://github.com/TableProApp/TablePro/issues/702)) - - Fixed `⌘W` accidentally closing connection window instead of tab - - Fixed tabs not saved before database switch, rollback failures now logged, filter validation improved ([#707](https://github.com/TableProApp/TablePro/issues/707)) - - Fixed Ollama AI chat streaming: responses were silently discarded - - - - ### Bug Fixes - - - Fixed query tabs always named "Query 1" instead of incrementing ([#695](https://github.com/TableProApp/TablePro/issues/695)) - - Fixed sidebar empty in new or restored window tabs ([#694](https://github.com/TableProApp/TablePro/issues/694)) - - Fixed tab titles, order, and persistence lost on quit/restore - - Fixed PostgreSQL version display for v10+ ([#698](https://github.com/TableProApp/TablePro/issues/698)) - - Fixed license activation metadata and deactivation error handling - - - - ### Bug Fixes - - - Fixed iCloud Sync not working between Mac and iPhone/iPad TestFlight builds - - - - ### New Features - - - **Server Dashboard**: Monitor active sessions, server metrics, and slow queries with configurable auto-refresh (PostgreSQL, MySQL, MSSQL, ClickHouse, DuckDB, SQLite) - - **Handoff**: Continue database work across Mac and iPhone/iPad - - **Create Database Options**: Database-specific charset/encoding options (encoding + LC_COLLATE for PostgreSQL, charset + collation for MySQL, name-only for others) - - ### Improvements - - - Sidebar table loading refactored for faster, race-free database switching - - iOS: full-text search in data browser, state restoration across app lifecycle, iPad keyboard shortcuts - - ### Bug Fixes - - - Fixed SSH tunnel failing with `~/.ssh/config` profiles: added `Include` directive support, token expansion, multi-word `Host` filtering ([#672](https://github.com/TableProApp/TablePro/issues/672)) - - Fixed Create Database dialog showing MySQL options for all database types - - - - ### New Features - - - **Auto-Uppercase Keywords**: SQL keywords are automatically uppercased as you type ([#660](https://github.com/TableProApp/TablePro/issues/660)) - - **Unified Cell Editors**: Boolean, enum, date, JSON, and blob columns now show a chevron button for quick editing ([#665](https://github.com/TableProApp/TablePro/issues/665)) - - ### Bug Fixes - - - Fixed MSSQL connection failing on Docker and fresh SQL Server instances ([#661](https://github.com/TableProApp/TablePro/issues/661)) - - Fixed context menu Format SQL not working ([#659](https://github.com/TableProApp/TablePro/issues/659)) - - - - ### New Features - - - **ER Diagram**: Interactive entity-relationship diagrams with crow's foot notation, drag-to-rearrange layout, and PNG export ([#186](https://github.com/TableProApp/TablePro/issues/186)) - - **FK Preview Toggle**: Press Space key to toggle foreign key preview popover ([#648](https://github.com/TableProApp/TablePro/issues/648)) - - **iOS Connection Reorder**: Drag-to-reorder connections in the iOS app with iCloud sync ([#652](https://github.com/TableProApp/TablePro/issues/652)) - - ### Bug Fixes - - - Fixed export dialog doing nothing on macOS Tahoe due to incorrect window reference for save panel ([#654](https://github.com/TableProApp/TablePro/issues/654)) - - Fixed column visibility popover and hex editor alignment per macOS HIG ([#653](https://github.com/TableProApp/TablePro/issues/653)) - - Accept SQLAlchemy-style connection URLs with driver hints ([#642](https://github.com/TableProApp/TablePro/issues/642)) - - - - ### New Features - - - **Maintenance Tools**: VACUUM, ANALYZE, OPTIMIZE, REINDEX, CHECK TABLE, and more via table context menu - - **EXPLAIN Visualization**: Diagram, tree, and raw views for query execution plans (PostgreSQL, MySQL) - - ### Bug Fixes - - - Fixed cross-schema foreign key preview, edit, and navigation for PostgreSQL and MySQL ([#644](https://github.com/TableProApp/TablePro/issues/644)) - - Fixed macOS HIG compliance: system colors, accessibility labels, theme tokens, localization - - Fixed idle ping spin loop caused by exhausted AsyncStream iterator ([#618](https://github.com/TableProApp/TablePro/issues/618)) - - Skip exact row count for large tables: use database statistics estimate ([#519](https://github.com/TableProApp/TablePro/issues/519)) - - ### Improvements - - - Theme font pickers now list all installed monospaced fonts dynamically - - - - ### New Features - - - **Smart Value Detection**: Auto-render UUIDs in BINARY(16) columns and timestamps in integer columns - - **Display As Override**: Per-column format override via column header context menu - - **iOS Safe Mode**: Off, Confirm Writes, or Read-Only per connection - - **iOS FK Navigation**: Tap to preview referenced row from foreign key columns - - **iOS Syntax Highlighting**: SQL keywords, strings, numbers, comments colored in query editor - - ### Bug Fixes - - - Fixed excessive idle ping traffic from orphaned monitor tasks - - Fixed Cmd+W save not persisting data grid changes - - Fixed window sizing, selection highlight, and connection switcher errors - - Moved file loading off main thread, replaced timing hacks with signals - - - - ### New Features - - - **iOS App**: Groups, tags, filter, sort, pagination, query history, export to clipboard, Spotlight search, Siri Shortcuts, Home Screen widget - - ### Bug Fixes - - - Fixed crashes in SSH tunnel, export dialog, and jump host removal - - Fixed data races in storage layers with MainActor isolation - - Native sheet presentation for all dialogs and file pickers - - Replaced event monitors and timing hacks with native SwiftUI APIs - - ### Improvements - - - Migrated undo system to NSUndoManager - - - - ### New Features - - - **Cloudflare D1 Batch Queries**: Execute multi-statement SQL via REST API - - **Cloudflare D1 Schema Editing**: CREATE TABLE, ADD/DROP COLUMN, CREATE/DROP INDEX - - ### Bug Fixes - - - Fixed multi-statement SQL execution failing on Cloudflare D1, ClickHouse, and other drivers without transaction support - - ### Improvements - - - Switched to Apple-standard `xcodebuild archive` + `exportArchive` build pipeline with dSYM collection - - - - ### New Features - - - **Foreign Key Preview**: Cmd+Enter or right-click on FK cells to preview referenced rows - - **Structure Tab Context Menu**: Copy Name, Copy Definition, Duplicate, and Delete for columns, indexes, and foreign keys - - **Column Header Context Menu**: Sort and show/hide columns from header right-click - - ### Bug Fixes - - - Fixed Oracle crash when opening views - - - - ### New Features - - - **Group Connection Tabs**: Option to group all connection tabs in one window instead of separate windows per connection - - ### Improvements - - - Separate preferred themes for Light and Dark appearance modes, with automatic switching in Auto mode - - - - ### Bug Fixes - - - Fixed table queries being incorrectly prefixed with the connection username as a schema name on non-schema databases (MySQL, MariaDB, ClickHouse, Redis, etc.), causing "Table 'username.table' doesn't exist" errors when opening a second table tab - - - - ### New Features - - - **Password Prompt on Connect**: Option to prompt for database password on every connection instead of saving to Keychain - - **Visual Create Table UI**: Design tables visually with multi-database support (sidebar "Create New Table...") - - **Collapsible Results Panel**: Toggle with `Cmd+Opt+R`, multiple result tabs for multi-statement queries, and result pinning - - **Auto-fit Column Width**: Double-click column divider or right-click "Size to Fit" to auto-size columns - - **Filter Field Autocompletion**: Column names and SQL keywords suggested as you type in filter fields - - **Multi-line Raw SQL Filter**: Use Option+Enter for newlines in the Raw SQL filter field - - **Database-aware SQL Functions**: Field menu now shows SQL functions specific to your database engine - - **Inline Error Banner**: Query errors displayed inline instead of modal dialogs - - ### Improvements - - - JSON syntax highlighting and brace matching in Details sidebar and JSON editor popover - - Replaced GCD dispatch patterns with Swift structured concurrency - - Refactored Details sidebar into modular field editor architecture - - ### Bug Fixes - - - PostgreSQL schema name lost after app restart, causing "relation does not exist" errors for non-public schemas - - Error dialog OK button not dismissing when a SwiftUI sheet is active - - SQL Server Unicode characters (Thai, CJK, etc.) displaying as question marks in nvarchar/nchar/ntext columns - - Globe+F (fn+F) fullscreen shortcut not working - - - - ### New Features - - - **SQL File Management**: Open, save, and save-as for SQL files with native macOS title bar integration - - **BigQuery Support**: Google BigQuery analytics database via REST API (plugin) - - **AI Kill Switch**: Global toggle to disable all AI features (Settings > AI) - - **Column Reordering**: Drag to reorder columns in the Structure tab (MySQL/MariaDB) - - **Nested Groups**: Hierarchical connection groups up to 3 levels deep - - **Safety Dialogs**: Confirmation prompts for deep link queries, connection imports, and pre-connect scripts - - ### Improvements - - - JSON fields in Row Details sidebar now display in a scrollable monospaced text area - - Removed query history sync from iCloud Sync (history stays local-only) - - ### Bug Fixes - - - SQL editor not auto-focused on new tab and cursor missing after tab switch - - Long lines not scrollable horizontally in the SQL editor - - Home and End keys not moving cursor in the SQL editor - - SSH profile lost after app restart when iCloud Sync enabled - - MariaDB JSON columns showing as hex dumps instead of JSON text - - MongoDB Atlas TLS certificate verification failure - - ENUM/SET dropdown chevron buttons not showing on first table open - - - - ### New Features - - - **Connection Sharing**: Export and import connections as `.tablepro` files. Includes import preview with duplicate detection, status badges, and per-item resolution ([#466](https://github.com/TableProApp/TablePro/issues/466)) - - **Encrypted Export** (Pro): Include passwords in exported files, protected by AES-256-GCM encryption with a passphrase - - **Linked Folders** (Pro): Watch a shared directory for `.tablepro` files. Connections appear read-only in the sidebar with per-user passwords - - **Environment Variables** (Pro): Use `$VAR` and `${VAR}` in `.tablepro` connection files, resolved at connection time - - - - ### New Features - - - **Enum/Set Picker**: Edit enum and set columns with a dropdown picker for PostgreSQL custom enums, ClickHouse Enum8/Enum16, and DuckDB ENUM types - - **Boolean Picker**: MSSQL BIT columns and MySQL TINYINT(1) columns now use a boolean toggle instead of manual text entry - - ### Improvements - - - Correct type classification for ClickHouse Nullable()/LowCardinality() wrappers, MSSQL MONEY/IMAGE/DATETIME2, DuckDB unsigned integers, and parameterized MySQL integer types - - ### Bug Fixes - - - XLSX export producing corrupted files that Excel cannot open - - Deep link cold launch missing toolbar and duplicate windows - - - - ### Bug Fixes - - - Keyboard shortcut hints in the welcome window footer no longer overflow and truncate when space is limited - - - - ### New Features - - - **Multi-select Connections**: Select multiple connections in the Welcome window with Cmd+Click and Shift+Click, then bulk delete (⌘⌫), move to group, or multi-connect - - **Reorder Connections**: Drag to reorder connections within groups and reorder groups in the Welcome window - - **Built-in Plugins**: ClickHouse, MSSQL, Redis, XLSX Export, MQL Export, and SQL Import now ship as built-in plugins (no separate installation needed) - - ### Improvements - - - Large document safety caps for syntax highlighting (skip files >5MB, throttle >50KB) - - Lazy-load full values for LONGTEXT/MEDIUMTEXT/CLOB columns in the detail pane sidebar - - ### Bug Fixes - - - SSH profile connections displaying incorrect host/username on the Welcome window home screen - - Saved connections disappearing after normal app quit (Cmd+Q) while persisting after force quit - - Crash when disconnecting an etcd connection while requests are in-flight - - Detail pane showing truncated values for LONGTEXT/MEDIUMTEXT/CLOB columns, preventing correct editing - - Redis hash/list/set/zset/stream views showing empty or misaligned rows when values contained binary, null, or integer types - - - - ### Bug Fixes - - - MongoDB Atlas connections failing to authenticate ([#438](https://github.com/TableProApp/TablePro/issues/438)) - - MongoDB TLS certificate verification skipped for SRV connections - - Active tab data no longer refreshes when switching back to the app window - - Undo history preserved when switching between database tables - - Health monitor now detects stuck queries beyond the configured timeout - - SSH tunnel closure and schema restore errors during reconnect now logged instead of silently discarded - - Memory not released after closing tabs - - New tabs opening as separate windows instead of joining the connection tab group - - Clicking tables in sidebar not opening table tabs - - - - ### New Features - - - **SSH Test Connection**: Test SSH connectivity directly from the SSH profile editor before saving - - ### Improvements - - - Faster type-aware sorting and lower memory usage with adaptive tab eviction - - - - ### New Features - - - **Redis Key Tree**: Browse Redis keys grouped by namespace with collapsible tree view in the sidebar ([#418](https://github.com/TableProApp/TablePro/issues/418)) - - **Keyboard Focus Navigation**: Navigate connection list, quick switcher, and database switcher using Tab, Ctrl+J/K/N/P, and arrow keys - - **MongoDB SRV Support**: Connect using `mongodb+srv://` URIs with SRV toggle, Auth Mechanism dropdown, and Replica Set field ([#419](https://github.com/TableProApp/TablePro/issues/419)) - - **Database Type Badges**: Connection form shows all available database types with install status badges ([#418](https://github.com/TableProApp/TablePro/issues/418)) - - ### Improvements - - - MongoDB `authSource` now defaults to the database name per MongoDB URI spec instead of always "admin" - - ### Bug Fixes - - - Fixed DuckDB TIMESTAMPTZ, TIMETZ, and other temporal columns displaying as null ([#424](https://github.com/TableProApp/TablePro/issues/424)) - - Fixed onboarding "Get Started" button not rendering on macOS 15 until window loses focus ([#420](https://github.com/TableProApp/TablePro/issues/420)) - - Faster MongoDB sidebar loading with `estimatedDocumentCount` and smaller schema sample - - - - ### New Features - - - **Row Numbers Column**: Show or hide row numbers in the data grid via Settings > Editor - - **Persistent Column Layout**: Column widths and order are saved per table across tab switches, view toggles, and app restarts - - ### Bug Fixes - - - Fixed incorrect version displayed for installed registry plugins ([#410](https://github.com/TableProApp/TablePro/issues/410)) - - Fixed dangling pointer in release builds due to incorrect buffer handling - - Fixed AI provider connection test error handling ([#407](https://github.com/TableProApp/TablePro/issues/407)) - - Fixed use-after-free crash in Redis plugin - - - - ### New Features - - - **Export Query Results**: Export the results of any SQL query directly to CSV, JSON, SQL, XLSX, or MQL files via right-click context menu or File > Export Results - - **Amazon DynamoDB Support**: Connect to DynamoDB with PartiQL queries, AWS IAM/Profile/SSO authentication, GSI/LSI browsing, table scanning, capacity display, and DynamoDB Local support - - **SSH Tunnel Profiles**: Save SSH configurations once and reuse them across multiple connections - - **Ctrl+HJKL Navigation**: Arrow key alternative for keyboards without dedicated arrow keys - - **Pro License Gating**: Safe Mode (Touch ID) and XLSX export now require a Pro license - - ### Bug Fixes - - - Fixed high CPU usage (79%+) and energy consumption when idle ([#394](https://github.com/TableProApp/TablePro/issues/394)) - - Fixed etcd connection failing with 404 when gRPC gateway uses a different API prefix - - Fixed data grid editing not working in query tabs ([#383](https://github.com/TableProApp/TablePro/issues/383)) - - - - ### New Features - - - **Cloudflare D1 Support**: Connect to Cloudflare D1 databases directly from TablePro - - **Autocomplete Match Highlighting**: Matched characters in autocomplete suggestions are now shown in bold - - **Autocomplete Loading Indicator**: Loading spinner while fetching column metadata - - ### Improvements - - - Refactored autocomplete popup to native SwiftUI with visible selection highlight, native accent color, and scroll-to-selection - - Autocomplete now suppresses noisy empty-prefix suggestions in non-browseable contexts (e.g., after SELECT, WHERE) - - Autocomplete ranking stays consistent as you type with unified fuzzy scoring - - Increased autocomplete suggestion limit from 20 to 40 for schema-heavy contexts - - - - ### Improvements - - - Improved performance for foreign key fetching, query history, tab persistence, and sidebar rendering - - ### Bug Fixes - - - Fixed SQL syntax error when editing columns with reserved keyword names (e.g., `database`, `table`, `order`) in MySQL, PostgreSQL, and SQLite - - Fixed high CPU usage and memory leaks at idle - - Fixed architecture-specific update delivery - - - - ### New Features - - - **iCloud Keychain Sync**: Optionally sync connection passwords via iCloud Keychain across your devices - - ### Bug Fixes - - - Fixed `Use ~/.pgpass` setting not persisting when saving a PostgreSQL connection - - - - ### Bug Fixes - - - Fixed Safe Mode badge not displaying for silent level - - Fixed Safe Mode level not reflecting live toolbar changes - - Fixed `~/.pgpass` password lookup using SSH tunnel host instead of the original host - - - - ### Bug Fixes - - - Fixed plugin registry compatibility with PluginKit version 2 - - - - ### New Features - - - **Turkish Language**: Added Turkish (Türkçe) as a new language option in Settings > General - - **etcd v3 Support**: New database plugin with prefix-tree key browsing, etcdctl syntax editor, lease management, watch, mTLS, auth, and cluster info - - **Save Changes Button**: New toolbar button for committing pending data edits - - **Connection Delete Confirmation**: Confirmation dialog before deleting a connection - - **Unsaved Edit Protection**: Confirmation dialog before sort, pagination, filter, or search discards unsaved edits - - ### Bug Fixes - - - Fixed SSH tunnel crashes caused by concurrent libssh2 calls on the same session - - Fixed unsaved cell edits lost when switching tabs, sorting, paginating, filtering, or switching apps - - Fixed auto-reconnect and health monitor silently discarding unsaved changes - - Fixed SSH tunnel recovery failing after tunnel death due to stale driver state - - Fixed health monitor ping interfering with active user queries - - Fixed connection test not cleaning up SSH tunnel on completion - - Fixed test connection success indicator not resetting after field changes - - Fixed SSH port field accepting invalid values - - Fixed DROP TABLE and TRUNCATE TABLE sidebar operations producing no SQL for plugin-based drivers - - Fixed foreign key navigation arrows not appearing after switching databases with Cmd+K on MySQL - - Fixed sidebar not refreshing after creating or dropping tables - - Fixed dropping a table disconnecting the database when the dropped table's tab was active - - - - ### Bug Fixes - - - Fixed SSH tunnel connections timing out when connecting through SSH - - Fixed plugin metadata dispatch failing for externally installed plugins - - Improved SSH public key authentication error messages with detailed failure reasons - - - - ### New Features - - - **iCloud Sync**: Sync connections, groups, tags, settings, and query history across Macs with per-category toggles, conflict resolution, and real-time status indicator (requires Pro license) - - **SQL Favorites**: Save frequently used queries with optional keyword bindings for autocomplete expansion - - **Copy as JSON**: Copy selected rows as JSON from context menu and Edit menu - - **Help Menu**: Quick links to website, documentation, GitHub, and sponsor page - - **BLOB Hex Display**: View BLOB data as hex dump in the detail view sidebar - - ### Bug Fixes - - - Fixed SSH agent connections failing when socket path contains `~` (e.g., 1Password agent) - - Fixed Keychain authorization prompt appearing on every table open - - - - ### Bug Fixes - - - Fixed plugin download counts resetting to zero when a new plugin version is released - - - - ### New Features - - - **Theme Engine**: 4 built-in themes (Default Light/Dark, Dracula, Nord) with full color and font customization. Import/export themes as JSON - - **Theme Registry**: Browse, install, and update community themes from the plugin registry - - **Cassandra & ScyllaDB Support**: Connect to Cassandra and ScyllaDB databases via a downloadable plugin - - **SSH Two-Factor Authentication**: TOTP support with auto-generate and prompt modes for SSH connections - - **SSH Host Key Verification**: Fingerprint confirmation dialog for new and changed host keys - - **Keyboard Interactive SSH**: Support for keyboard-interactive authentication method - - **Column Visibility**: Toggle columns on/off via status bar button or header context menu - - **Copy as SQL**: Copy selected rows as INSERT or UPDATE statements from the data grid context menu - - **PostgreSQL `.pgpass` Support**: Automatic password lookup from `~/.pgpass` for PostgreSQL and Redshift connections - - **Pre-connect Script**: Run a shell command before each connection via Connection > Advanced - - **Custom Plugin Registry URL**: Configure a private/enterprise registry URL for plugin distribution - - ### Improvements - - - MSSQL, MongoDB, Redis, XLSX export, MQL export, and SQL import extracted into downloadable plugins. MySQL, PostgreSQL, SQLite, CSV, JSON, and SQL export remain built-in - - Redesigned Plugins settings with master-detail layout and download counts - - All database-specific behavior now driven by plugin metadata instead of hardcoded switches, enabling third-party database plugins - - Connection form fields, sidebar labels, and SQL dialect features are now fully plugin-driven - - App-level appearance mode (Light, Dark, Auto) independent of theme selection - - MSSQL query cancellation and lock timeout support - - ### Bug Fixes - - - Fixed plugin icon rendering not supporting custom asset images alongside SF Symbols - - - - ### New Features - - - **DuckDB Support**: Connect to `.duckdb` files, query CSV/Parquet/JSON files via SQL, schema navigation, and extension management - - **MongoDB Auth Database**: Configure `authSource` to authenticate against any database instead of the default `admin` - - **Safe Mode Levels**: 6 per-connection levels (Silent, Alert, Alert Full, Safe Mode, Safe Mode Full, Read-Only) replacing the boolean read-only toggle, with confirmation dialogs and Touch ID/password authentication - - **Preview Tabs**: Single-click opens a temporary preview tab, double-click or editing promotes it to a permanent tab - - **Import Plugin System**: SQL import extracted into a `.tableplugin` bundle with support for `.sql` and `.gz` compressed files - - **Open SQLite from Finder**: Double-click `.sqlite`, `.sqlite3`, `.db3`, `.s3db`, `.sl3`, and `.sqlitedb` files to open them directly - - **Plugin Install Prompt**: Automatic prompt to install missing driver plugins when connecting to an unsupported database type - - ### Improvements - - - Oracle and ClickHouse shipped as downloadable plugins, reducing app bundle size - - SQLite driver extracted from built-in bundle to downloadable plugin - - Export plugin options (CSV, XLSX, JSON, SQL, MQL) now persist across app restarts - - Plugins can declare settings views rendered in Settings > Plugins - - True prepared statements for MSSQL and ClickHouse, eliminating string interpolation - - Batch query operations for MSSQL, Oracle, ClickHouse, and SQLite, eliminating N+1 patterns - - Unified error formatting and localized error messages across all database drivers - - Standardized parameter binding with type-aware numeric handling and NULL literal support - - ### Bug Fixes - - - Fixed MongoDB Read Preference, Write Concern, and Redis Database not persisting across app restarts - - Fixed DELETE and UPDATE queries using all columns in WHERE clause instead of primary key for PostgreSQL, Redshift, MSSQL, and ClickHouse - - Fixed SSL/TLS always being enabled for MongoDB, Redis, and ClickHouse connections - - Fixed Redis sidebar click showing data briefly then going empty - - Fixed MongoDB showing "Invalid database name" when connecting without a database name - - Fixed result truncation at 100K rows being silently discarded instead of reported to UI - - - - ### Bug Fixes - - - Fixed stale filter causing repeated errors when restoring tabs after switching database or schema - - Fixed sidebar showing old tables during database/schema switch instead of a loading indicator - - Fixed sidebar search field disappearing when no tables match filter on macOS 15 and earlier - - Fixed disabled plugin database types still appearing in connection form picker - - Fixed main window not closing before reopening welcome screen on connection failure - - - - ### New Features - - - **Plugin System**: All 8 database drivers and 5 export formats extracted into `.tableplugin` bundles loaded at runtime. Enables third-party plugins - - **Plugin Marketplace**: Browse, search, and install plugins from the GitHub-hosted registry with checksum verification - - **Settings > Plugins**: Manage installed plugins: enable/disable, install from file or marketplace, view details - - **ClickHouse Support**: Query ClickHouse databases with progress tracking, EXPLAIN variants, TLS/HTTPS, server-side cancellation, and Parts view - - **Startup Commands**: Run custom SQL after connecting (e.g., `SET time_zone`) via Connection > Advanced tab - - **Chinese Simplified Localization**: Full zh-Hans translation for the entire app UI - - ### Improvements - - - Reduced memory by ~80-130 MB per connection: eliminated dedicated ping driver, lazy plugin loading, RowBuffer deduplication, metadata driver consolidation - - Consolidated per-editor NSEvent monitors into shared singleton (O(n) to O(1) per event) - - Reorganized project into domain-specific subdirectories - - ### Bug Fixes - - - Fixed inspector separator bleeding into toolbar area with default connection color - - Fixed inspector toggle lagging due to synchronous UserDefaults writes during animation - - - - ### New Features - - - **Oracle Database Support**: Connect to Oracle databases via OCI (Oracle Call Interface) - - **Database URL Scheme**: Open connections directly from terminal with `open "mysql://user@host/db" -a TablePro`. Supports MySQL, PostgreSQL, SQLite, MongoDB, Redis, MSSQL, and Oracle - - **SSH Agent Authentication**: Use SSH Agent for tunnel authentication, compatible with 1Password SSH Agent, Secretive, and ssh-agent - - **Multi-Jump SSH**: Chain multiple SSH hops (ProxyJump) to reach databases through bastion hosts. Configure jump hosts in the connection form or import from `~/.ssh/config` - - ### Improvements - - - Reduced app binary size by ~55% by replacing CodeEditLanguages xcframework (38 grammars) with a local package compiling only SQL, Bash, and JavaScript - - ### Bug Fixes - - - Fixed memory leak where session state objects were recreated on every tab open, causing 785MB usage at 5 tabs - - Fixed per-cell field editor allocation in DataGrid creating 180+ NSTextView instances instead of sharing one - - Fixed NSEvent monitor not removed on all popover dismissal paths in connection switcher - - Fixed race condition in FreeTDS `disconnect()` where `dbproc` was set to nil without holding the lock - - Fixed JSON encoding and file I/O blocking the main thread in TabStateStorage - - Fixed MySQL/MariaDB getting `BEGIN` instead of `START TRANSACTION` in table operations - - Fixed port resetting to default value when editing a connection with a custom port - - Fixed data races in `MainContentCoordinator`, `LibPQConnection`, and `VimKeyInterceptor` - - Fixed SSH askpass script written with world-readable permissions - - Fixed welcome screen showing blank panel when connections have orphaned group IDs - - Fixed multiple tabs auto-executing queries simultaneously on connection restore - - Fixed unescaped identifiers in MySQL `SHOW CREATE TABLE` queries allowing SQL injection via table names - - Fixed `QueryResultRow` equality ignoring cell values, preventing SwiftUI from re-rendering updated rows - - Fixed `Cmd+Delete` in sidebar search clearing the query editor - - - - ### New Features - - - **PostgreSQL Database & Schema Switching**: Switch databases and schemas for PostgreSQL connections via ⌘K - - - - ### New Features - - - **Microsoft SQL Server Support**: Connect to SQL Server 2017+ databases via FreeTDS with schema browsing, table structure, indexes, foreign keys, and paginated queries - - **Edit and Delete Without Primary Key**: Edit and delete rows in tables that don't have a primary key - - ### Bug Fixes - - - Fixed MSSQL connection losing selected database after disconnect and reconnect when no default database is configured - - Fixed DELETE operations on tables without a primary key being silently dropped when row data is missing - - Fixed high CPU and RAM usage on app launch from blocking storage init, unsynchronized health monitors, and excessive retry loops - - Fixed slow database switcher loading by replacing N+1 metadata queries with single batched queries - - Fixed slow Redis key browsing by pipelining TYPE and TTL commands in a single round trip - - Fixed slow SQL export startup by batching COUNT(*) queries and dependent lookups - - Fixed slow AI Chat schema loading by fetching all foreign keys in a single bulk query - - Fixed O(n) string operations causing high CPU in geometry parsing, Redis driver, and autocomplete scoring - - - - ### New Features - - - **Redis Support**: Connect to Redis databases with key-value browsing, database-level sidebar (db0-db15), TTL management, and interactive CLI - - **TablePlus-compatible URLs**: Open databases via command line with `open -a TablePro "postgresql://user@host/db"`, supporting schema switching, table opening, filters, color, and environment tags - - ### Bug Fixes - - - Fixed sidebar search field and main content area background colors not blending with macOS vibrancy - - Fixed POINT and geometry columns showing blank values in MySQL and wrong type label in sidebar - - - - ### New Features - - - **Amazon Redshift Support**: Connect to Amazon Redshift data warehouses - - **Deep Links**: Open connections, tables, queries, and import connections via `tablepro://` URLs - - **Copy as URL**: Right-click a connection to copy its details as a connection string (e.g., `mysql://user:pass@host/db`) - - **Auto-show Inspector**: Automatically open the right sidebar when selecting a row (Settings > Editor) - - **Homebrew Cask**: Install via `brew install --cask tablepro` - - ### Improvements - - - ENUM and SET columns now open their picker on single click with a chevron indicator, matching boolean column behavior - - ### Bug Fixes - - - Fixed "Table not found" error when switching databases within the same connection (Cmd+K) while a table tab is open - - Fixed right sidebar state not persisting across native window-tabs - - - - ### Bug Fixes - - - Fixed MySQL second tab showing empty rows when macOS merges tab groups - - Fixed MongoDB tab name showing "MQL Query" instead of collection name when using bracket notation - - - - ### New Features - - - **Environment Color Indicator**: Subtle toolbar tint based on connection color for at-a-glance environment identification - - **SSH Tunnel URL Import**: Import database connections from SSH tunnel URLs (e.g., `mysql+ssh://`, `postgresql+ssh://`) - - **Connection Groups**: Organize database connections into folders with colored headers - - ### Improvements - - - Toolbar now uses native macOS overflow behavior with History/Export/Import in the secondary action menu - - Redesigned right sidebar detail pane with compact field layout and type-aware editors - - ### Bug Fixes - - - Fixed toolbar briefly showing "MySQL" and missing version info when opening a new tab - - Fixed keyboard shortcuts not working after connecting from the welcome screen until a second tab was opened - - Fixed toolbar overflow menu showing only one item when the window is narrow - - Fixed AI chat showing "SQL" label and missing syntax highlighting for MongoDB code blocks - - - - ### New Features - - - **Multiple Database Connections**: Open separate windows for different database connections, each with independent session isolation - - **MongoDB Support**: Connect to MongoDB databases with collection browsing, document viewing, and MQL export - - **Import from URL**: Import database connections directly from connection strings (e.g., `postgresql://user:pass@host:5432/db`) - - **Custom About Window**: New About window with version info and quick links to Website, GitHub, and Documentation - - ### Improvements - - - Release notes now shown in the Sparkle update window - - ### Bug Fixes - - - Fixed new row (Cmd+I) and duplicated row not appearing in data grid until manual refresh - - Fixed PostgreSQL SSH tunnel connections failing with "no encryption" due to SSL config not being preserved - - Fixed PostgreSQL SSL `sslrootcert` passed unconditionally, causing certificate verification failure in Required mode - - - - ### Bug Fixes - - - Fixed app bundle not ad-hoc signed. Signing step was unreachable when no dylibs were bundled - - - - ### Bug Fixes - - - Fixed Sparkle auto-update failing with "improperly signed" error. Release ZIPs now preserve framework symlinks and include proper ad-hoc code signatures - - - - ### New Features - - - **Vim Mode for SQL Editor**: Full Vim keybindings with Normal/Insert/Visual modes, motions (`w`, `b`, `e`, `^`, `_`), operators, and `:w`/`:q` commands. Toggle in Editor Settings - - **PostgreSQL Schema Switching**: Browse and switch between schemas (`public`, `auth`, custom schemas) via ⌘K database switcher - - ### Improvements - - - Query history operations converted to native Swift async/await for faster response - - Export and Import services consolidated to reduce UI update overhead - - App startup uses structured Task-based retry loops instead of dispatch chains - - ### Bug Fixes - - - Fixed cell edit showing modified background (yellow) but reverting to original value after pressing Enter - - Fixed undo on inserted row cell edit not syncing data correctly - - Fixed Vim Escape key not working when autocomplete popup is visible - - Fixed Copy/Cut (⌘C/⌘X) not working in SQL editor - - Fixed Vim yank/delete not syncing to system clipboard - - Fixed multiple Vim motion and visual mode selection issues - - Fixed event monitor and memory leaks in SQL editor lifecycle - - Fixed unbounded memory growth from tab registry, sorted row cache, and schema provider retention - - Fixed background tabs retaining full result data indefinitely - - Fixed crash on macOS 14.x caused by missing libpq symbol. Now uses vendored static libraries - - Fixed duplicate tabs when inserting SQL from AI Chat or History with multiple windows open - - Fixed various coordinator lifecycle issues (teardown, destroy, cancellation) - - Fixed DataGridView unnecessary column reconfiguration on every version bump - - Fixed ConnectionHealthMonitor slow failure detection. Now supports immediate health checks - - - - ### New Features - - - **Native macOS Window Tabs**: The tab bar is now rendered by macOS itself. Identical to Finder, Safari, and Xcode tabs with automatic dark/light mode support, drag-to-reorder, and "Merge All Windows" for free - - **Independent Tab Windows**: Each tab is a full independent window with its own sidebar, editor, and state. No more shared state between tabs - - ### Improvements - - - **Tab Switching Performance**: Schema is now cached per connection so new native tabs reuse the already-loaded schema instead of re-fetching from the database (saves 500ms–2s per tab) - - **Background Schema Loading**: Schema loads concurrently. Table data appears immediately while autocomplete schema loads in the background - - **Sidebar MVVM Refactor**: Sidebar table list migrated to a testable `SidebarViewModel` architecture with extracted `TableRowView` and context menu components - - Window title updates dynamically after in-place navigation (sidebar click, FK navigation) - - 10+ SwiftUI rendering optimizations to prevent O(N) view cascades across windows - - ### Bug Fixes - - - Fixed sidebar losing keyboard focus (arrow key navigation) after opening a second table tab - - Fixed sidebar active state flash when clicking a table that opens in a new native window tab - - Fixed sidebar losing active state when opening a second table in a new native window tab - - Fixed sidebar not refreshing after switching databases via Cmd+K - - Fixed Cmd+W in empty state doing nothing. Now closes the connection window and disconnects - - Fixed Cmd+K database switch flooding all windows with error alerts - - Fixed clicking a table in the sidebar replacing the current tab instead of opening a new one - - Fixed Cmd+W on any tab disconnecting the entire session - - Fixed Cmd+T from empty state creating two native tabs instead of one - - Fixed native tab title showing "SQL Query" instead of the table name - - Fixed Cmd+W on the last tab disconnecting the session instead of returning to empty state - - - - ### New Features - - - **Combined Search & Filters**: Quick search and filter rows now work together. When both are active, conditions are combined with AND for precise data discovery - - **Foreign Key Navigation**: FK columns display a clickable arrow icon in each cell. Click to jump directly to the referenced table, pre-filtered to the related row - - ### Improvements - - - **Instant Metadata Loading**: FK arrows, column info, and row counts now load on a dedicated parallel connection, eliminating the 200-300ms delay on initial table load - - **Instant Pagination**: Approximate row count from database metadata displays immediately with data; exact count refines silently in the background - - Syntax highlighting added to Import SQL file preview - - XLSX export enforces Excel's 1M row limit per sheet with reduced memory usage - - Multiline cell editing now uses a scrollable overlay editor for better navigation - - MySQL result fetching switched to streaming mode to reduce memory for large result sets - - 30+ internal performance optimizations across SQL editor, tab switching, data grid, exports, and persistence - - ### Bug Fixes - - - Fixed AND/OR filter logic mode being ignored in query execution - - Fixed filter panel state (filters, visibility, search, logic mode) not preserved when switching tabs - - Fixed FK navigation filter being cleared when switching to a new tab - - Fixed PostgreSQL and SQLite LIKE/NOT LIKE expressions missing ESCAPE clause - - Fixed SQLite regex filter silently falling back to LIKE substring match - - Fixed PostgreSQL SQL export: newline/tab escaping, missing enum types, missing DROP IF EXISTS for types and sequences - - Fixed memory management issue in PostgreSQL C connector (free vs deallocate) - - Fixed FTS5 search errors from special characters like *, OR, AND - - - - ### Bug Fixes - - - **PostgreSQL SQL Export**: Fixed DDL export failing for all PostgreSQL tables with "Failed to fetch DDL" error - - - - ### Improvements - - - Welcome window now uses native macOS frosted glass translucency - - Improved tab switching performance by caching row providers and change managers across render cycles - - ### Bug Fixes - - - **MySQL/MariaDB Timeout**: Auto-detect server type to use the correct timeout variable (`max_execution_time` for MySQL, `max_statement_time` for MariaDB) - - **DataGrid Scrolling**: Row view recycling, O(1) string length checks, cached fonts, reduced compositing overhead, deferred accessibility labels - - Eliminated selection sync feedback loop causing redundant updates during tab switch - - Reduced SwiftUI re-render cascades by batching state mutations during tab switch - - - - ### Improvements - - - Replaced generic SwiftUI colors with native macOS system colors for proper dark mode, vibrancy, and accessibility adaptation - - Use semantic label colors (`quaternaryLabelColor`, `tertiaryLabelColor`) instead of hardcoded opacity - - Use native `shadowColor` instead of `Color.black` for shadows - - Replaced iOS-style Capsule badges with RoundedRectangle for native macOS look - - - - ### Performance - - - **45 performance fixes** across the entire codebase, covering memory, CPU, data handling, network, and I/O: - - **Memory**: Reference-based row buffers, index-based sort cache, streaming XLSX export, driver-level row limits (100K cap), removed redundant string copies, weak references in schema provider, undo stack depth cap, dictionary-based pending changes, weak self in Task captures, clear cached data on disconnect, AI chat message cap - - **CPU**: Removed expensive Unicode operations in database drivers, cached 100+ regex patterns in SQL formatter, async Keychain reads, cached frequently-used regex patterns, O(1) change lookup index - - **Data**: Auto-append LIMIT for unprotected queries, row limit caps for all drivers, batch column fetching via INFORMATION_SCHEMA, index permutation sort cache, cached row provider, clipboard 50K row cap, Int-based row IDs replacing UUID - - **Network**: Phase 2 metadata cache, connect timeout for PostgreSQL, query cancellation via mysql_kill/PQcancel/sqlite3_interrupt, loading guard for sidebar, reuse cached schema for AI - - **I/O**: Throttled history cleanup, async history storage migration, consolidated onChange handlers - - - - ### New Features - - - **Inline AI Suggestions**: Ghost text completions in the SQL editor. Triggers automatically on typing pause, Tab to accept, Escape to dismiss - - **Schema-Aware Completions**: Inline suggestions use actual table and column names from your connected database (cached with 30s TTL) - - **VoiceOver Accessibility**: Added accessibility labels to data grid, filter panel, toolbar buttons, editor tab bar, and sidebar controls - - ### Improvements - - - Migrated notification observers to async sequences for modern Swift concurrency - - Migrated tab state persistence from UserDefaults to file-based storage in Application Support for faster app launch - - Refactored menu and toolbar commands to `@FocusedObject` pattern. Direct method calls instead of global notifications - - Redesigned connection form with tab-based layout (General / SSH Tunnel / SSL/TLS / Advanced) - - Revamped connection form UI to native macOS grouped form style with automatic label alignment - - SQLite connections now only show relevant tabs (General and Advanced) - - Added async/await wrapper methods to query history storage - - ### Bug Fixes - - - Fixed thread safety race condition in SQLite driver. Serialized all handle access with a dedicated actor - - Fixed SwiftUI sheet presentation reliability. Consolidated multiple `.sheet` modifiers into a single `.sheet(item:)` - - Fixed SSH tunnel setup blocking the UI. Replaced synchronous process waiting with async port probing - - Fixed potential deadlocks in MySQL and PostgreSQL connection cleanup - - SQL editor now respects the macOS accessibility text size preference with live updates - - Fixed retain cycle in update checker and leaked observer in SQL editor coordinator - - Eliminated tab switching delay. Kept NSViews alive across switches, moved I/O to background threads - - Reduced tab-switch CPU spikes from 40-60% to ~10-20% by eliminating redundant data grid reloads - - Table open now shows data instantly. Metadata loads in the background without blocking the grid - - Eliminated 20-80ms overhead when clicking an already-open table in the sidebar - - Fixed Keychain writes silently failing. Return values are now checked and logged - - Added proper service identifiers to all Keychain queries to prevent collisions with other apps - - Fixed leaked async tasks in import dialog and AI provider settings - - - - ### New Features - - - **AI Chat Panel**: Right-side panel for AI-assisted SQL queries with multi-provider support (Claude, OpenAI, OpenRouter, Ollama, custom endpoints). Includes schema-aware context, markdown rendering, and code blocks with Copy/Insert to Editor buttons - - **AI Provider Settings**: Configure multiple AI providers in Settings > AI with API key management, endpoint configuration, model selection, and connection testing - - **AI Feature Routing**: Map AI features (Chat, Explain Query, Fix Error, Inline Suggestions) to specific providers and models - - **Per-Connection AI Policy**: Control AI access per connection (Always Allow, Ask Each Time, Never) in the connection form - - **Keyboard Shortcut Customization**: Rebind any menu shortcut in Settings > Keyboard via press-to-record UI with conflict detection and "Reset to Defaults" - - **Structure View Undo/Redo**: Full undo/redo support (⌘Z / ⇧⌘Z) for all column, index, and foreign key operations in the structure editor - - **Structure View Type Picker**: Database-specific type picker popover: searchable, grouped by category, supports freeform input for parametric types like `VARCHAR(255)` - - **Structure View Dropdowns**: YES/NO dropdown menus for Nullable, Auto Inc, and Unique columns - - **Tab Reuse Setting**: Opt-in option in Settings > Tabs to reuse clean table tabs when clicking a new table in the sidebar - - **Switch Connection Shortcut**: ⌘⌥C to quickly open the connection switcher popover - - **SQL Autocomplete Enhancements**: New clause types (RETURNING, UNION, OVER/PARTITION BY), smart clause transitions, qualified column suggestions in JOINs, compound keywords, richer column metadata, keyword documentation, and expanded function/keyword coverage - - ### Improvements - - - Migrated 5 NSPopover controllers (Enum, Set, TypePicker, JSONEditor, ForeignKey) to SwiftUI with shared `PopoverPresenter` utility - - Replaced AppKit history panel (5 files) with single pure SwiftUI `HistoryPanelView` - - Replaced `ExportTableOutlineView` (757 lines) with SwiftUI `ExportTableTreeView` (~146 lines) - - Replaced `KeyEventHandler` NSViewRepresentable with native `.onKeyPress()` modifiers - - Improved layout architecture. Eliminated KVO observation hacks and recursive view tree traversal - - Structure tab grid columns now auto-size to fit content on data load - - SQL autocomplete now uses 50ms debounce and optimized fuzzy matching - - ### Bug Fixes - - - Fixed structure view undo/redo not working. Undo-delete no longer duplicates rows, and undoing deletion of unsaved items works correctly - - Fixed structure view save button remaining enabled when validation errors exist - - Fixed structure view incorrectly handling multi-column foreign keys and column renames on MySQL/MariaDB - - Fixed PostgreSQL DDL tab missing constraints and primary key detection in structure grid - - Fixed SQL injection vulnerability in driver schema queries with special characters in table/database names - - Fixed SQL editor undo/redo (⌘Z / ⇧⌘Z) being blocked by responder chain mismatch - - Fixed SQL autocomplete issues: subquery clause detection, block comment handling, database-specific types, schema suggestions after CREATE TABLE, function completion inserting incomplete parentheses - - Fixed data grid column order flashing/swapping when sorting - - Fixed "Copy Column Name" and "Filter with column" context menu copying sort indicators - - Fixed AI chat "Ask Each Time" policy silently falling through to "Always Allow" - - - - ### New Features - - - **SQL Preview**: Review all pending SQL statements before committing changes with a new toolbar button (eye icon) or shortcut (⌘⇧P) - - **Multi-Column Sorting**: Shift+click column headers to add columns to the sort list; regular click replaces with single sort. Priority indicators (1▲, 2▼) shown in headers - - **Copy with Headers**: Copy selected rows with column headers as the first TSV line via ⇧⌘C or the data grid context menu - - **Column Width Persistence**: Resized columns retain their width across pagination, sorting, and filtering reloads within a tab session - - **Dangerous Query Confirmation**: DELETE/UPDATE statements without a WHERE clause now prompt a confirmation dialog summarizing affected queries - - **SQL Editor Horizontal Scrolling**: Long lines scroll horizontally without word wrapping - - **Find Panel Scroll-to-Match**: SQL editor find panel now scrolls to each match during navigation - - ### Improvements - - - Raised minimum macOS version from 13.5 (Ventura) to 14.0 (Sonoma) - - Changed Export/Import keyboard shortcuts from ⌘E/⌘I to ⇧⌘E/⇧⌘I to avoid conflicts with standard text editing shortcuts - - URLSession now waits for network connectivity in analytics and license services - - Improved SQL statement parser to handle backslash escapes within string literals, preventing false positives in dangerous query detection - - ### Bug Fixes - - - Fixed SQL editor not updating colors when switching between light and dark mode - - Fixed sidebar retaining stale table selections and pending operations for tables removed since the last refresh - - - - ### Bug Fixes - - - Fixed launch crash on macOS 13 (Ventura) x86_64 caused by accessing `NSApp.appearance` before `NSApplication` is initialized during settings singleton setup - - - - ### Bug Fixes - - - Fixed syntax highlighting not applying after paste in SQL editor by deferring frame-change notification so the visible range recalculates after layout - - Fixed data grid not refreshing after inserting a new row - - - - ### New Features - - - **Language Setting**: Choose between System, English, or Vietnamese in Settings > General with full Vietnamese localization (637 strings) - - **ENUM/SET Column Editor**: Double-click ENUM columns for a searchable dropdown, SET columns show multi-select checkboxes with OK/Cancel buttons - - **PostgreSQL Enum Support**: User-defined enum types resolved via `pg_enum` catalog lookup - - **SQLite Pseudo-Enum Detection**: CHECK constraint-based enum detection for SQLite columns - - **Connection Health Monitoring**: Automatic 30-second health checks with exponential backoff auto-reconnect (3 retries) - - **Anonymous Usage Analytics**: Opt-out toggle available in Settings > General > Privacy - - ### Improvements - - - Migrated `Libs/*.a` static libraries to Git LFS tracking to reduce repository clone size - - Replaced `filter { }.count` with `count(where:)` across 7 files - - Replaced `print()` with `Logger` in documentation examples - - Aligned Xcode `SWIFT_VERSION` build setting from 5.0 to 5.9 - - ### Bug Fixes - - - Fixed launch crash on macOS 13 caused by missing `asyncAndWait` symbol in CodeEditSourceEditor 0.15.2 - - Fixed SQL injection vulnerability in PostgreSQL `pg_enum` lookup and SQLite `sqlite_master` queries by escaping single quotes - - Fixed ENUM column nullable detection to use actual schema metadata instead of heuristic `rawType` check - - Fixed PostgreSQL primary key modification to query actual constraint name from `pg_constraint` - - - - ### New Features - - - **SSL/TLS Connection Support**: Secure connections for MySQL/MariaDB and PostgreSQL with configurable modes (Disabled, Preferred, Required, Verify CA, Verify Identity) and custom certificate file paths - - **CSV Clipboard Paste**: RFC 4180-compliant CSV parser with auto-detection of CSV vs TSV format when pasting from clipboard - - **Explain Query**: New button in the SQL editor toolbar and menu item (⌥⌘E) for viewing query execution plans - - **Connection Switcher**: Quick-switch popover for active and saved connections directly from the toolbar - - **Date/Time Picker**: Dedicated date picker popover for editing date, datetime, timestamp, and time columns in the data grid - - **Read-Only Mode**: Connection-level read-only toggle with toolbar badge and full UI enforcement. Disables editing, row operations, and save changes - - **Query Timeout**: Configurable execution timeout in Settings > General (default 60s, 0 = no limit) with per-driver enforcement via `statement_timeout` (PostgreSQL), `max_execution_time` (MySQL), `max_statement_time` (MariaDB), and `sqlite3_busy_timeout` (SQLite) - - **Foreign Key Lookup**: Searchable dropdown for FK columns showing values from the referenced table with both ID and descriptive display column - - **JSON Column Editor**: Popover editor for JSON/JSONB columns with pretty-print formatting, compact mode, real-time validation, and explicit save/cancel buttons - - **Excel Export**: Export to `.xlsx` format with a lightweight pure-Swift OOXML writer. Supports shared strings deduplication, bold headers, numeric type detection, and multi-table export to separate worksheets - - **View Management**: Create View (opens SQL editor with template), Edit View Definition (fetches existing definition), and Drop View from sidebar context menu - - ### Bug Fixes - - - Fixed crash on launch on macOS 13 (Ventura) caused by missing Swift runtime symbol - - Fixed redo functionality in data grid (⌘⇧Z now works correctly) - - Fixed redo stack not being cleared when new changes are made - - Fixed `canRedo()` always returning false in data grid coordinator - - Wired undo/redo callbacks directly to data grid for proper responder chain validation - - Fixed MariaDB connection error 1193 "Unknown system variable 'max_execution_time'" by using the correct `max_statement_time` variable - - Query timeout errors no longer prevent database connections from being established - - ### Improvements - - - Replaced all `print()` statements with structured OSLog `Logger` across 25 files for better debugging via Console.app - - - - ### New Features - - - **CodeEditSourceEditor Migration**: SQL editor now powered by tree-sitter via CodeEditSourceEditor for improved syntax highlighting and performance - - **Multi-Statement Execution**: Execute multiple SQL statements in a single run - - **Show Structure**: Right-click any table in the sidebar to quickly view its structure - - **Improved Filter Panel**: Redesigned filter UI for a better experience - - **SwiftUI Tab Bar**: New pure SwiftUI editor tab bar replacing the AppKit implementation - - **GPL v3 License**: Project is now licensed under the GNU General Public License v3 - - **Auto-Update**: In-app updates via Sparkle 2 with EdDSA signing. Check for updates from the TablePro menu or Settings > General - - ### Bug Fixes - - - Fixed MySQL 8+ connections failing with `caching_sha2_password` plugin error by rebuilding libmariadb with the auth plugin compiled statically - - Fixed Delete key on data grid rows incorrectly marking the table as deleted - - Downgraded all APIs to support macOS 13.5 (Ventura) - - ### Maintenance - - - CI release notes now read from curated CHANGELOG.md instead of auto-generating from commits - - Removed redundant `prepare-libs` CI job, speeding up the build pipeline by ~5 minutes - - Added SPM Package.resolved for CodeEditSourceEditor dependencies - - Updated build and test commands with `-skipPackagePluginValidation` - - - - The first public release of TablePro: a native macOS database client built with SwiftUI and AppKit. - - ### New Features - - - **Multi-Database Support**: Connect to MySQL, PostgreSQL, and SQLite databases - - **SQL Editor**: Syntax highlighting, autocomplete, and line numbers - - **Data Grid**: Browse and edit table data with sorting, filtering, and pagination - - **SSH Tunneling**: Secure database connections via SSH tunnels - - **Query History**: Track and replay your SQL queries - - **Table Structure**: View and modify table schemas, indexes, and constraints - - **Import/Export**: Import SQL files and export data in CSV, JSON, and SQL formats - - **Keyboard Shortcuts**: Keyboard navigation for the grid, sidebar, and editor - - **Native Tab Bar**: AppKit-powered tab bar with drag-to-reorder support - - **Dock Menu**: Quick access to welcome window and recent connections - - **License Activation**: RSA-signed license verification with offline support - - ### Performance - - - Optimized SQL editor for large files with viewport-only syntax highlighting - - Native AppKit tab bar for lightweight tab management - - Efficient data grid rendering for large result sets - - Lightweight memory footprint with native Apple frameworks - diff --git a/docs/connections/aws-iam.mdx b/docs/connections/aws-iam.mdx deleted file mode 100644 index f274acf290..0000000000 --- a/docs/connections/aws-iam.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: AWS IAM Authentication -description: Connect to Amazon RDS and Aurora with IAM database authentication instead of a static password ---- - -Set up the AWS side first, or **Test Connection** fails against a user the database never created for IAM. - -## On the AWS side - -Enable IAM database authentication on the RDS instance or Aurora cluster. MySQL, MariaDB, and PostgreSQL all support it. Then create the database user: - - - - ```sql - CREATE USER 'app_user' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; - ``` - - - ```sql - GRANT rds_iam TO "app_user"; - ``` - - - - -A user is either password-authenticated or IAM-authenticated, never both. Connecting as a user that still has only a password fails. - - -## Import from AWS - -Choose **File > Import > Import from AWS…** and the endpoints come from the AWS API instead of the console. The profile needs `rds:DescribeDBInstances` and `rds:DescribeDBClusters`, both in the `AmazonRDSReadOnlyAccess` policy. - - - Import from AWS sheet, profile acme-prod, us-east-1 checked, Authentication set to AWS IAM - Import from AWS sheet, profile acme-prod, us-east-1 checked, Authentication set to AWS IAM - - - - - The list holds the profiles in `~/.aws/config` and `~/.aws/credentials`, and the line under it names how that one signs in. An expired IAM Identity Center session offers **Sign In** here. - - - AWS has no default region, so **Continue** stays dimmed until at least one is selected. The profile's own `region` starts selected. - - - Every instance and cluster arrives as a row with its endpoint, port, and engine. A cluster brings its writer endpoint, and its reader endpoint as a second row that starts deselected. - - - -A row matching a saved connection on host and port is marked duplicate and starts deselected. Select it to add it as a copy. **Username** stays empty, because the IAM database user is rarely the master user. - -Imported connections carry the region and the profile, and take IAM authentication on every database that has it enabled. The rest ask for a password on the first connect. - -A region that answers with an error keeps its own line and leaves the others running. Engines with no TablePro driver, and databases with no endpoint yet, are named at the top of the list and skipped. - -## Setting up - -In the connection form, set **Authentication** to one of the AWS IAM options. The **Password** field gives way to the AWS fields, and **Username** takes the IAM database user. - - - Connection form with Authentication set to AWS IAM (Profile) - Connection form with Authentication set to AWS IAM (Profile) - - -| Option | Credentials come from | -|--------|----------------------| -| **AWS IAM (Access Key)** | An access key ID, secret access key, and optional session token typed into the form | -| **AWS IAM (Profile)** | A named profile in `~/.aws/credentials` and `~/.aws/config` | -| **AWS IAM (SSO)** | A profile backed by IAM Identity Center, using the token cache in `~/.aws/sso/cache` | - -Pick **AWS IAM (Profile)** if you already use the AWS CLI here: it reads the same files the same way. - -**AWS Region** is read from the endpoint when the hostname looks like `mydb.abc123.us-east-1.rds.amazonaws.com`. Fill it in for a CNAME or any other custom endpoint, or to override what was detected. - -## Token lifetime - -Each connect signs a fresh token, valid for 15 minutes and never written to disk; automatic reconnects sign another. Nothing to paste, rotate, or refresh. - -IAM also requires encryption in transit, so an [SSL mode](/connections/ssl) of **Disabled** or **Preferred** is raised to **Required** for the connect. - -## Profiles - -**Profile Name** lists the profiles found in `~/.aws/config` and `~/.aws/credentials` and accepts a typed name. Blank means `default`. A profile resolves by what it declares, the way the AWS CLI resolves it, so **AWS IAM (Profile)** and **AWS IAM (SSO)** reach the same profiles: - -- `role_arn` assume-role profiles, resolved through STS. Base credentials come from `source_profile`, chaining up to five deep and resolving that profile by its own kind, or from `credential_source = Environment`. `mfa_serial` is not supported. -- IAM Identity Center profiles, through `sso_session` or the older inline `sso_start_url`, using the token cache in `~/.aws/sso/cache`. -- Static `aws_access_key_id` / `aws_secret_access_key` pairs. -- `credential_process` commands, so a profile can be backed by 1Password or any other credential helper. - -`web_identity_token_file` is not supported and says so rather than reporting the profile incomplete. - -A connect that fails on an expired IAM Identity Center session offers the browser sign-in; `aws sso login --profile ` does the same from the shell. - -## Tunnels and port forwards - -RDS checks the token against its own hostname and port, so the endpoint it was signed for is what matters, not the address dialed. - -A tunnel the app opens needs nothing extra: the token is signed for the **Host** and **Port** in the form, not the loopback address the driver gets. - -A forward you run yourself leaves `127.0.0.1` in the form, which names no database. Set **RDS Endpoint** to the real one: - -```text -mydb.abc123.us-east-1.rds.amazonaws.com:5432 -``` - -The port is optional, falling back to 5432 for PostgreSQL and 3306 for MySQL and MariaDB. The field also covers a CNAME or any alias AWS did not issue, and is honored only when **Host** is loopback or already the same hostname. - -## Troubleshooting - -### Could not determine an AWS region for "…" - -The hostname is not a standard RDS endpoint. Fill in **AWS Region**. - -### TablePro cannot sign an RDS token for "…" - -The connection points at a port forward the app did not open. Fill in **RDS Endpoint** with the endpoint from the AWS console. - -### PAM authentication failed - -PostgreSQL reports this and MySQL reports `Access denied` when the token was signed for the wrong endpoint. Check **RDS Endpoint** against the console, including the port. - -### Profile "…" was not found - -The profile declares no credentials at all: no `role_arn`, no `sso_session` or `sso_start_url`, no static keys, and no `credential_process`. Check the name and the contents of `~/.aws/config` and `~/.aws/credentials`. - -### AWS SSO Sign-In Required - -The cached SSO session expired. Accept the prompt, or run `aws sso login --profile `. - -## Other AWS services - -The same **Authentication** options reach Amazon ElastiCache ([Redis](/databases/redis)) and Amazon Keyspaces ([Cassandra](/databases/cassandra)), each with fields of its own on those pages. diff --git a/docs/connections/cloud-sql-proxy.mdx b/docs/connections/cloud-sql-proxy.mdx deleted file mode 100644 index 3b7e96ee2e..0000000000 --- a/docs/connections/cloud-sql-proxy.mdx +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Cloud SQL Auth Proxy -description: Connect to Google Cloud SQL by letting TablePro manage the Cloud SQL Auth Proxy ---- - -import HelperPort from "/snippets/helper-port.mdx"; - -One field carries the whole setup: the instance connection name, `project:region:instance`, which is on the instance's overview page in the Google Cloud console. The proxy itself runs as a child process, started on connect and killed on disconnect. - - - Instance connection name, authentication and local listener fields - Instance connection name, authentication and local listener fields - - -## Before you start - -The transport appears for Cloud SQL instances running MySQL, PostgreSQL, or SQL Server. The account you connect with needs the **Cloud SQL Client** role (`roles/cloudsql.client`) on the project, and for Application Default Credentials, one run of: - -```bash -gcloud auth application-default login -``` - -Install the binary, or click **Download cloud-sql-proxy…** on the Network tab, which fetches 2.23.0 and checks its SHA-256 against the value pinned for your CPU architecture. - -```bash -brew install cloud-sql-proxy -``` - -Auto-detection covers your `PATH`, `/opt/homebrew/bin`, `/usr/local/bin`, and `~/google-cloud-sdk/bin`. Anywhere else, use **Choose…**. - -## Setting up - - - - On the **Network** tab, set **Connect via** to **Cloud SQL Auth Proxy**. A connection uses one transport, so choosing this one switches off whichever was selected before. - - - Enter the **Instance connection name**, then choose **Application Default Credentials** or **Service Account Key**. - - - The proxy encrypts the leg to Cloud SQL and hands the driver plain loopback, so SSL/TLS stays off. - - - On **General**, click **Test Connection**. **Host** and **Port** there are never dialed: the instance connection name decides where the proxy lands. **Username** and **Database** work as usual. - - - -## Options - -| Option | What it does | Default | -|--------|--------------|---------| -| **Instance connection name** | `project:region:instance`. Three colon-separated parts, none empty, or the connect is refused outright. | - | -| **Credentials** | **Application Default Credentials**, or a **Service Account Key** pasted as JSON. | Application Default Credentials | -| **Use IAM database authentication** | Signs in as an IAM principal instead. Set **Username** to that principal, a user email or `name@project.iam` for a service account; the password goes unused. | Off | -| **Connect over private IP** | Reaches the instance on its private address instead of its public one. | Off | -| **Path** | The `cloud-sql-proxy` binary. Blank auto-detects. | Blank | - -A pasted key is kept in the macOS Keychain and written to a temporary file readable only by you while the proxy runs, then deleted. It never reaches the command line. - - -`GOOGLE_APPLICATION_CREDENTIALS` works too, but the proxy inherits the app's environment, and a GUI app never sees variables exported by your shell profile. - - - - -## IAM sign-in without the proxy - -When the instance is already reachable, over its private IP or an authorized network, you can skip the proxy and sign in as an IAM principal directly. This works for MySQL and PostgreSQL. - -On the **General** tab, set **Authentication** to one of: - -- **Google Cloud IAM (Application Default)**: the credentials from `gcloud auth application-default login`. -- **Google Cloud IAM (Service Account)**: a **Service Account Key**, as a file path or pasted JSON. - -Set **Host** to the instance IP and **Username** to the database user of the principal: - -| Engine | User account | Service account | -|--------|--------------|-----------------| -| PostgreSQL | `name@example.com` | `name@project.iam` | -| MySQL | `name` | `name` | - -The password field disappears. The token TablePro signs in with allows a Cloud SQL sign-in and nothing else, and lasts one hour. It is reused until five minutes before it expires, then replaced, including for a reconnect and for the extra connection MySQL opens to stop a query. SSL/TLS is raised to **Required** if it was lower. - -The principal needs the **Cloud SQL Instance User** role (`roles/cloudsql.instanceUser`), and the instance needs IAM database authentication turned on and a database user for the principal: - -```bash -gcloud sql users create name@example.com --instance=INSTANCE --type=cloud_iam_user -``` - - -`GOOGLE_APPLICATION_CREDENTIALS` is read from the app's environment, which never has the variables your shell profile exports. To use a key file, choose **Service Account** and give its path. - - -These options also work with **Connect via > Cloud SQL Auth Proxy**. SSL then stays as set, because the proxy encrypts the connection to the instance. - -## Troubleshooting - -### cloud-sql-proxy was not found - -Install it with `brew install cloud-sql-proxy`, download it from the Network tab, or set **Path**. - -### The proxy did not become ready in time - -Run it by hand to see what it says: - -```bash -cloud-sql-proxy --port 5433 --address 127.0.0.1 project:region:instance -``` - -### Permission or authentication errors - -The proxy reports these on its own output, which the failed connect shows. Usually the account is missing the **Cloud SQL Client** role, Application Default Credentials were never set up, or IAM database authentication is on with no database user for the principal. - -### The gcloud login does not allow Cloud SQL sign-in - -The login was made with `--scopes` that leave out Cloud SQL sign-in. Run `gcloud auth application-default login` again without `--scopes`. diff --git a/docs/connections/cloudflare-tunnel.mdx b/docs/connections/cloudflare-tunnel.mdx deleted file mode 100644 index 817ae23b22..0000000000 --- a/docs/connections/cloudflare-tunnel.mdx +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: Cloudflare Tunnel -description: Reach a database behind Cloudflare Access by letting TablePro manage the cloudflared process ---- - -import HelperPort from "/snippets/helper-port.mdx"; - -The Access application is yours to create, in the Cloudflare dashboard. The **Cloudflare Tunnel** transport takes the hostname of one that already fronts the database, and runs `cloudflared` against it for the life of the connection. - - - Access hostname, authentication method and local listener fields - Access hostname, authentication method and local listener fields - - -## Before you start - -Create a self-hosted application in the [Zero Trust dashboard](https://developers.cloudflare.com/cloudflare-one/applications/non-http/) routed to the database, with a policy that admits you. Its public hostname is the only Cloudflare value the form needs. - -```bash -brew install cloudflared -``` - -The form looks on your `PATH` and in `/opt/homebrew/bin` and `/usr/local/bin`, and shows what it found. Installed elsewhere, set **Path** by hand: a GUI app never sees the `PATH` your shell profile exports. - -## Setting up - - - - On the **Network** tab, set **Connect via** to **Cloudflare Tunnel**. A connection uses one transport, so choosing this one switches off whichever was selected before. - - - **Hostname** is the Access application's public hostname, `db.example.com` in the example. With **Browser Sign-In** chosen, click **Sign In with Browser…** so the first connect does not stop to ask. - - - On **General**, click **Test Connection**. **Host** and **Port** there are never dialed while the tunnel is on: the Access application decides where it lands. - - - -## How it works - -A free port is picked, `cloudflared` starts listening on it, and the driver gets it once it accepts. Run the same command yourself to see the client's own output: - -```bash -cloudflared access tcp --hostname db.example.com --url 127.0.0.1:54321 -``` - -Disconnecting or quitting stops the process. One orphaned by a crash is reaped at the next launch. - -## Options - -| Option | What it does | Default | -|--------|--------------|---------| -| **Hostname** | The Access application hostname `cloudflared` connects to. | - | -| **Expose to local network** | Binds `0.0.0.0` rather than `127.0.0.1`. | Off | - - -With **Expose to local network** on, anything that reaches the port is past Cloudflare Access on your session and faces only the database's own login. - - -### Authentication - - - - **Sign In with Browser…** runs `cloudflared access login` and caches the token under `~/.cloudflared`; later connects reuse it silently. A connect with no cached token asks you to sign in rather than appearing to hang. - - - For unattended connections, enter a service token as **Client ID** and **Client Secret**. Both stay in the macOS Keychain and reach `cloudflared` as environment variables, never on the command line. - - - The policy must use a **Service Auth** rule. Against an identity-provider-only policy, Cloudflare prompts for a browser sign-in even with a token set. - - - - - - -## Troubleshooting - -### cloudflared was not found - -Install it with `brew install cloudflared`, or set **Path** to the binary. - -### A browser opens every time you connect - -The cached Access token expired, or a service token is running against a policy that is not **Service Auth**. Sign in again, or change the policy. - -### The tunnel did not become ready in time - -An unknown hostname, a policy that is not **Service Auth**, or a fixed port already taken. diff --git a/docs/connections/connection-form.mdx b/docs/connections/connection-form.mdx deleted file mode 100644 index a40a97b358..0000000000 --- a/docs/connections/connection-form.mdx +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: Connection form -description: The four sections of the connection editor and the fields each driver adds ---- - -The editor's sidebar lists the same four sections whatever the driver. Only Network is ever absent, and only for a driver that connects directly and cannot negotiate TLS. - - - Connection editor with a General, Network, Options and Appearance sidebar, credential fields filling the pane, and Test, Cancel and Save along the bottom - Connection editor with a General, Network, Options and Appearance sidebar, credential fields filling the pane, and Test, Cancel and Save along the bottom - - -| Section | Contents | -|------|----------| -| **General** | Name, database type, host, port, database, credentials | -| **Network** | How the connection reaches the server, and how it is encrypted | -| **Options** | Timeouts, driver options, startup SQL, Safe Mode, external and AI access, iCloud | -| **Appearance** | Icon, color, tags, group | - -The bar along the bottom carries **Test Connection**, **Cancel** and **Save**, plus **Save & Connect** on a new connection and **Delete** on a saved one. When **Save** is dimmed, the reason sits to its left, prefixed with the tab that holds the empty field. - -## General - -| Field | Description | -|-------|-------------| -| **Name** | Display name in the connection list | -| **Type** | The database type. **Change…** retypes the connection and resets the fields the old type owned | -| **Connect Using** | MySQL and MariaDB. **Host and Port**, or **Socket** for a server on this Mac. See [Unix socket](/databases/mysql#unix-socket) | -| **Host** | Empty falls back to the driver's own default, usually `localhost` | -| **Port** | Pre-filled from the database type | -| **Socket** | Replaces Host and Port when **Connect Using** is **Socket**. The full path of the server's socket file, such as `/tmp/mysql.sock` | -| **Database** | Optional on drivers that browse every database from one session. PostgreSQL and Redshift do not connect without one | -| **Username** | Not pre-filled. Empty means the driver's own default | -| **Password** | Stored in the macOS Keychain | -| **Prompt for password** | Stores nothing, asks on every connect. Reads **Prompt for API token** on API-only drivers | -| **Use Password File** | PostgreSQL, Redshift, and CockroachDB. Reads `~/.pgpass`, and reports underneath whether the file exists, has `chmod 0600`, and holds a matching line | - -SQLite, DuckDB, libSQL, and Beancount replace the host fields with a file path. **Browse…** picks a file that exists. **New…** names one that does not, and the file is made when you connect; Beancount opens only an existing ledger, so it has no **New…**. - -## Network - -**Connect via** is a single choice, and the fields under it belong to whichever transport is selected. A connection uses one transport or none. - -| Choice | What it does | -|--------|--------------| -| **Direct** | Connects straight to the host and port on General | -| **SSH Tunnel** | Forwards a local port through an SSH server. See [SSH Tunneling](/connections/ssh-tunneling) | -| **Remote Database File** | SQLite only. Reaches a database file over SSH. **Open** picks **On the Server**, which runs statements there, or **As a Read-Only Copy**. See [Remote Database Files](/connections/remote-database-files) | -| **Cloudflare Tunnel** | Runs `cloudflared access tcp`. See [Cloudflare Tunnel](/connections/cloudflare-tunnel) | -| **Cloud SQL Auth Proxy** | MySQL, PostgreSQL, and SQL Server. See [Cloud SQL Auth Proxy](/connections/cloud-sql-proxy) | -| **SOCKS Proxy** | Routes through a SOCKS5 proxy, which also resolves the hostname. See [SOCKS Proxy](/connections/socks-proxy) | -| **Tunnel Command** | Holds a `kubectl port-forward`, an AWS SSM session, or a command of your own. See [Tunnel Command](/connections/tunnel-command) | - -Leave it on **Direct** unless the database is unreachable from this Mac. Switching to another transport keeps what you typed into the previous one, so switching back costs nothing. - -A connection set to **Socket** on General offers only **Direct**, since a tunnel cannot reach a socket file on this Mac. - -**Socket Path**, under **SSH Tunnel**, forwards to a unix socket instead of Host and Port, which are then unused. See [Forwarding to a unix socket](/connections/ssh-tunneling#forwarding-to-a-unix-socket). - -Encryption sits below the transport. [SSL/TLS](/connections/ssl) covers the modes and the certificate fields. - -## Options - -**Safe Mode**, **AI Policy** and **External Clients** share the **Access** section. - -| Field | Description | -|-------|-------------| -| Plugin fields | Driver-specific options, such as MongoDB's `replicaSet`, in a section named after the database type | -| **Extensions** | SQLite, and libSQL in Local File mode. Libraries loaded into the database file on every connect. See [Extensions](/databases/sqlite#extensions) | -| **Connect timeout** | Maximum seconds for the network transport and remote database to finish connecting. Empty uses 30 seconds; valid values are 1 to 600 | -| **Query timeout** | Override of **Settings > General** for this connection. Empty inherits the global value; `0` means no limit. Only drivers that support query timeouts enforce it | -| **Startup Commands** | SQL to run after every connect. See [Startup commands](#startup-commands) | -| **Pre-Connect Script** | Shell script run before connecting. A non-zero exit aborts the connect | -| **Safe Mode** | Confirms, authenticates or refuses queries before they run. See [Safe Mode](/features/safe-mode) | -| **AI Policy** | Per-connection override for the in-app AI agents. **Never** also refuses external clients. Shown while **Enable AI Features** is on | -| **External Clients** | **Blocked**, **Read Only** (the default), or **Read & Write** for MCP clients such as Raycast, Cursor, and Claude Desktop, and for [AppleScript](/developers/applescript). A token's own scope cannot raise it. See the [integration security model](/developers#security-model) | -| **AI Rules** | Guidance the AI reads on every chat turn for this connection: table conventions, columns to avoid, join hints. Shown while **Enable AI Features** is on. See [AI Assistant](/features/ai-assistant) | -| **Local only** | Keeps this connection off iCloud Sync. Shown while **Sync this Mac with iCloud** is on. See [iCloud Sync](/features/icloud-sync) | - -The connect timeout is one deadline for the whole network attempt, not a new timeout for each step. -It includes the selected SSH tunnel or proxy, TCP, TLS and database authentication. Local SQLite and -local libSQL file opens have no network deadline. If an SSH bastion stalls, the timeout error names -that server rather than the database behind it. Reconnect after changing either timeout so the open -session picks it up. - - - PostgreSQL connection Options pane with Connect timeout and Query timeout fields using their inherited defaults - PostgreSQL connection Options pane in dark appearance with Connect timeout and Query timeout fields using their inherited defaults - - -A pre-connect script never runs unprompted. A **Pre-Connect Script** alert shows the script itself and waits for **Run Script**, every time. At launch it is not prompted for at all: a restored window whose connection carries a script waits with a **Connect** button. - -## Appearance - -**Icon** replaces the database logo in the connection list, **Switch Connection**, the connections strip and the toolbar. Click it, type to search the symbols, then click one or press `Return`. **Default** brings the logo back. The database type stays in the row's tooltip. - -**Color** marks the connection in the connection list and its window. **Tags** and **Group** decide where it sits in the welcome window's tree. - -## Startup commands - -Statements split on semicolons and newlines and run in order on the connection that just opened, after every connect including an automatic reconnect. They all run on one connection, so write one dialect. - - -```sql MySQL -SET time_zone = '+00:00'; -SET NAMES utf8mb4; -``` - -```sql PostgreSQL -SET search_path TO myschema, public; -``` - - -A statement that fails is logged and skipped, and the connection still opens. - -## Connection health - -Every active connection is checked on the interval set by **Check connections** in Settings > -General, every 30 seconds by default, and the check is skipped while any query, export, import, or -other tracked operation is running. A failed check starts a reconnect at 2s, then 4s, 8s, doubling -to a 120-second ceiling, and keeps going until the connection comes back or you close it. An -authentication failure stops the retries and leaves the session in error. So does a TLS failure -that needs an edit to the connection: TLS off where the server requires it, TLS on where the server -refuses it, a client certificate the server requires, or a client key that cannot be read or -decrypted. A server certificate that fails verification or does not match the host keeps the -retries going, and the window shows that certificate error in place of a generic one. A reconnect -rebuilds the tunnel, restores the selected database and schema, and re-runs the startup commands; -the session reads as connecting throughout. - -The check is a query, so on a database that sleeps when idle or bills per query it is not free. **Only when I use the connection** stops it: TablePro sends nothing on its own, and instead checks the connection the first time you use it after five quiet minutes. Waking the Mac from sleep counts as a quiet period, so the first thing you do after opening the lid checks too. - -A Server Dashboard tab keeps its own refresh running on its own interval, which **Check connections** does not change. Close the tab or set its interval to Off. - -SQLite, DuckDB, Beancount, Snowflake, and Teradata are not checked. - -Metadata connections are the extra ones opened to read a database's object list and structure. While many reads run at once, as after a Refresh with many databases expanded, more than six can be open; as that work ends, a connection closes the ones unused longest until six are left. One left unused for 10 minutes closes and reopens on the next read. - -## Which drivers get which transports - -| Database | Default port | SSH tunnel | SSL/TLS | Cloudflare Tunnel | Cloud SQL Proxy | SOCKS Proxy | Tunnel Command | -|----------|-------------|-----------|---------|-------------------|-----------------|-------------|----------------| -| [MySQL](/databases/mysql) | 3306 | Yes | Yes | Yes | Yes | Yes | Yes | -| [MariaDB](/databases/mariadb) | 3306 | Yes | Yes | Yes | No | Yes | Yes | -| [TiDB](/databases/tidb) | 4000 | Yes | Yes | Yes | No | Yes | Yes | -| [Databend](/databases/databend) | 3307 | Yes | Yes | Yes | No | Yes | Yes | -| [OceanBase](/databases/oceanbase) | 2881 | Yes | Yes | Yes | No | Yes | Yes | -| [PostgreSQL](/databases/postgresql) | 5432 | Yes | Yes | Yes | Yes | Yes | Yes | -| [Amazon Redshift](/databases/redshift) | 5439 | Yes | Yes | Yes | No | Yes | Yes | -| [CockroachDB](/databases/cockroachdb) | 26257 | Yes | Yes | Yes | No | Yes | Yes | -| [PGlite](/databases/pglite) | 5432 | No | No | No | No | No | No | -| [Microsoft SQL Server](/databases/mssql) | 1433 | Yes | Yes | Yes | Yes | Yes | Yes | -| [Oracle](/databases/oracle) | 1521 | Yes | Yes | Yes | No | Yes | Yes | -| [Dameng DM8](/databases/dameng) | 5236 | Yes | No | Yes | No | Yes | Yes | -| [ClickHouse](/databases/clickhouse) | 8123 | Yes | Yes | Yes | No | Yes | Yes | -| [Teradata](/databases/teradata) | 1025 | Yes | Yes | Yes | No | Yes | Yes | -| [Trino](/databases/trino) | 8080 | Yes | Yes | Yes | No | Yes | Yes | -| [MongoDB](/databases/mongodb) | 27017 | Yes | Yes | Yes | No | Yes | Yes | -| [Redis](/databases/redis) | 6379 | Yes | Yes | Yes | No | Yes | Yes | -| [Cassandra / ScyllaDB](/databases/cassandra) | 9042 | Yes | Yes | Yes | No | Yes | Yes | -| [etcd](/databases/etcd) | 2379 | Yes | Yes | Yes | No | Yes | Yes | -| [SurrealDB](/databases/surrealdb) | 8000 | Yes | Yes | Yes | No | Yes | Yes | -| [Elasticsearch](/databases/elasticsearch) | 9200 | Yes | Yes | Yes | No | Yes | Yes | -| [Typesense](/databases/typesense) | 8108 | Yes | Yes | Yes | No | Yes | Yes | -| [Weaviate](/databases/weaviate) | 8080 | Yes | Yes | Yes | No | Yes | Yes | -| [SAP HANA](/databases/sap-hana) | 443 | No | Yes | No | No | No | No | -| [Snowflake](/databases/snowflake) | 443 | No | No | No | No | No | No | -| [SQLite](/databases/sqlite) | File | No | No | No | No | No | No | -| [DuckDB](/databases/duckdb) | File | No | No | No | No | No | No | -| [Beancount](/databases/beancount) | File | No | No | No | No | No | No | -| [DynamoDB](/databases/dynamodb) | AWS API | No | No | No | No | No | No | -| [BigQuery](/databases/bigquery) | Cloud API | No | No | No | No | No | No | -| [Spanner](/databases/spanner) | Cloud API | No | No | No | No | No | No | -| [Cloudflare D1](/databases/cloudflare-d1) | Cloud API | No | No | No | No | No | No | -| [Cloudflare R2 SQL](/databases/cloudflare-r2-sql) | Cloud API | No | No | No | No | No | No | -| [libSQL / Turso](/databases/libsql) | URL | No | No | No | No | No | No | - -SQLite is the one driver offering **Remote Database File**; it reaches an SSH server without forwarding a port. A driver with no SSL/TLS column is either a local file or an HTTPS API that manages its own encryption. [SSL/TLS](/connections/ssl) has the per-driver defaults. diff --git a/docs/connections/credential-profiles.mdx b/docs/connections/credential-profiles.mdx deleted file mode 100644 index a5e08c4f0d..0000000000 --- a/docs/connections/credential-profiles.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Credential Profiles -description: One username and password, shared by any number of connections ---- - -Rotating a database password used to mean opening every connection that used it. Create one profile in **Settings > Profiles**, point those connections at it, and the next rotation is a single edit. - -## Create a profile - - - Credential profiles and SSH servers, with the connections using each - Credential profiles and SSH servers, with the connections using each - - - - - - **Credential Profiles** is the first section. - - - Give it a name, a username, and pick where the password comes from. - - - Every connection's **Credentials** picker now offers it. - - - -**Save These as a Profile…** on a connection that already has credentials filled in does the same thing without retyping them. - -## Point a connection at one - - - A connection signing in through a credential profile - A connection signing in through a credential profile - - -**Credentials** sits at the top of the **Authentication** section. **Enter Below** is the default and keeps today's behavior: the username and password belong to that connection alone. Picking a profile replaces both fields with a read-only summary, and the connection signs in with whatever the profile holds at the moment it connects. - -A linked connection keeps no password of its own. Linking deletes the one it had, so an encrypted export of it carries nothing and **Duplicate** copies the link rather than the secret. - -## Where the password comes from - -| Source | What happens on connect | -|---|---| -| Saved in the Keychain | Read from the Keychain, under the profile's own id | -| Ask every time | Asked for once per profile each time TablePro runs, however many connections use it | -| `~/.pgpass` | Looked up by the host, port, database and username of whichever connection is opening | -| Read from elsewhere | Resolved from a file, an environment variable, a command's output, 1Password, Vault or AWS Secrets Manager | - -Pick **Saved in the Keychain** unless something else already owns the password. **Read from elsewhere** never leaves this Mac and never syncs. - -## Additional secrets - -Some engines sign in with more than a password: an AWS secret access key, a Snowflake private key, a service account JSON. **Additional Secrets** in the profile editor lists them per database type. A connection reads only the secrets its own type declares, so one profile can carry several engines' keys without them colliding. - -## Rename, duplicate and delete - -Right-click a row in **Settings > Profiles** for **Edit…** and **Duplicate**. The row's trailing label says how many connections use it. - -**Delete Profile** is at the bottom of the editor. Deleting hands the username and the password to each connection that used it, as that connection's own credentials, so nothing stops connecting. Two profiles cannot share a name, since every picker offers them by name alone. - -## Sharing and sync - -An exported connection carries its profile by name, alongside the name, the username and where the password comes from. Importing on a Mac that has no profile of that name creates one set to ask for the password; a profile already there is left as it is and the connection links to it. The password never travels, and a profile reading its password from a file or a command exports as one that asks. - -With [iCloud Sync](/features/icloud-sync) on, **Settings > Sync > Credential Profiles** carries profiles between your Macs: the name, the username, and where the password comes from. The password and any additional secrets stay in the Keychain. They cross only while **Passwords** is on in the same pane, and only when saved after it was turned on. A profile set to **Read from elsewhere** arrives as **Ask every time**, and a source set on the second Mac stays on that Mac. - -Which connections use a profile is kept on each Mac, so pick the profile in **Credentials** again on the second one. A profile arriving under a name already taken gets a number after it, such as `prod (2)`. Deleting a profile deletes it on your other Macs too. diff --git a/docs/connections/index.mdx b/docs/connections/index.mdx deleted file mode 100644 index 3af9c72691..0000000000 --- a/docs/connections/index.mdx +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Managing Connections -description: Create, organize, and switch database connections ---- - -Press `Cmd+N` anywhere in the app to open the connection form. **New Connection…** on the welcome window does the same, and the **Import** menu under it takes one from a URL, [another client](/features/connection-sharing), an exported file, or a [project folder](/features/project-folder-import). Saved connections fill the right side of that window. The search field in its toolbar, focused by `Cmd+F`, matches name, host, database, username, type, tag and group. The window keeps one size and remembers where you left it on screen. Until you save a connection, that side offers **Open Sample Database** instead, plus **Import from Other App…** when a client TablePro imports from is installed. - - - Welcome window with New Connection, Open File and Import on the left, and a search field over an empty connection list offering the sample database - Welcome window with New Connection, Open File and Import on the left, and a search field over an empty connection list offering the sample database - - -## Create a connection - - - - The chooser sheet groups drivers as Relational, Document, Key-Value, Analytical, Wide-Column, Cloud Native, Coordination & Config, and Other. Picking a driver badged **Not Installed** offers the download before the form opens. See [Plugins](/features/plugins). - - - Host, port, and credentials live on **General**. Everything else sits on **Network**, **Options** and **Appearance**; see [Connection form](/connections/connection-form). - - - On success the button becomes a green checkmark and reads **Connected**. On failure a **Connection Test Failed** sheet carries the driver's own message, and for errors the driver recognizes a diagnostic sheet appears instead, with suggested fixes and **Copy Diagnostic Info**. - - - The connection is saved and the window opens on it. **Save** alone keeps it without connecting. - - - - - Database type chooser - Database type chooser - - -Every driver's default port, and which of SSH tunnel, SSL/TLS, Cloudflare Tunnel, Cloud SQL Proxy, and SOCKS Proxy it accepts, is in the [connection form reference](/connections/connection-form#which-drivers-get-which-transports). - -### Import from URL - -Paste a connection string instead of typing the fields. Click **Import from URL…** in the chooser sheet footer, paste, check the parsed preview, and click **Import**. The form opens filled in, ready to save. Schemes, query parameters, and the `+ssh` form are in the [Connection URL Reference](/connections/urls). - - - Import from URL - Import from URL - - - -Special characters in passwords (`@`, `#`, `%`) need percent-encoding. `p@ssword` becomes `p%40ssword`. - - -### Open a URL without saving anything - -```bash -open "postgresql://user:pass@host:5432/dbname" -``` - -A confirmation alert names the target first. Matching a saved connection on type, host, port, database, and username reuses it; otherwise the session is temporary and never joins your connection list, and the URL's password stays in memory rather than reaching the Keychain. [Confirmation and trusted links](/connections/urls#confirmation-and-trusted-links) covers **Always Allow** and how to revoke it. - -## Organize connections - -The connection form's **Appearance** tab holds **Icon**, **Color**, **Tags** and **Group**. Give two connections to the same engine different icons, such as a flame for production, to tell them apart at a glance. The color tints the toolbar while the connection is open. - - - Color swatches, a tag field and a group picker - Color swatches, a tag field and a group picker - - - -Red for production, green for development. On production, set **Safe Mode** to **Read-Only** under **Access** on the **Options** tab to block accidental writes. See [Safe Mode](/features/safe-mode). - - -| | How it works | -|---|---| -| **Groups** | Folders in the connection list, nested up to three levels. Choose **File > New Group…**, or right-click empty space for **New Group…**. Right-click a group for **Rename**, **Edit Group…**, **New Subgroup…**, **Color**, **Move Group To**, and **Delete Group…**. **Edit Group…** changes the name, icon, color and parent group in one sheet. Deleting a group deletes its subgroups too and leaves the connections inside ungrouped rather than deleted | -| **Tags** | Several per connection, each with a name and color. Type a tag's name in the search field and pick it to filter by it, or choose tags from **Filter by Tag** in the **View Options** menu. With two or more chosen, **Match** switches between any and all of them, and **Clear Filters** removes them | -| **Favorites** | Right-click a connection and choose **Add to Favorites**, or drag it onto the **Favorites** header. Favorites gather in a section at the top while the connection keeps its place in its group | -| **Recent** | The five connections you opened last, favorites left out. Right-click one for **Remove from Recent**, or the section header for **Clear Recent**. To hide the section, turn off **Show recent connections** in [General settings](/customization/general-settings#connections) | - -A connection's icon, group, tags, and favorite sync through iCloud, and so does a group's icon, to the iPhone and iPad app as well, unless it is marked local only. See [iCloud Sync](/features/icloud-sync). - -### Arrange the list - -Drag connections and groups into a group, or onto the **Connections** header to take them out of every group. Choose the order under **View > Sort Connections By** or **Sort By** in the **View Options** menu: **Manual**, **Name**, **Database Type**, or **Last Connected**. Dragging to a spot between rows needs **Manual**; in the other orders a drop files the connection into the group and the sort places it. **Edit > Undo** puts a move back. - -To rename a connection or group, select it and choose **File > Rename**. Press `Return` to keep the new name or `Escape` to restore the old one. - -The sort order and the Recent section, shown or hidden, stay on this Mac. - -## Switch connections and databases - -Press `Ctrl+Cmd+C` for **Switch Connection**. Whatever is already open sits at the top, then your favorites and, while **Show recent connections** is on, the connections you opened last. The rest are listed under the group they belong to, a nested group carrying its full path. Arrow keys move, `Return` switches the window to that connection, and `Cmd`-click opens a saved one in a window of its own. Typing searches every group at once and collapses the matches into a single list. - -Switching leaves the query tab you were in on its own connection. To take it with you, press `Ctrl+Option+Cmd+C` for **Move Tab to Connection…** instead. See [Moving a query tab to another connection](/features/tabs#moving-a-query-tab-to-another-connection). - -A footer under that list names the transport the window's connection runs on. On an SSH tunnel or a SOCKS proxy it also carries the bytes that have crossed since the transport opened, received and sent, with the current rate beside each direction while something is moving. - -The rest show their name alone. Cloudflare Tunnel, Cloud SQL Auth Proxy and Tunnel Command each run a separate binary that holds the connection end to end, a [remote database file](/connections/remote-database-files) is fetched once and then read from disk, and a direct connection's socket belongs to the database driver. - -**Open Database** (`Cmd+K`) moves to another database on the same server. - - - Database list with the current database checked - Database list with the current database checked - - -Leaving **Database** empty on MySQL, MariaDB, MongoDB, SQL Server, and ClickHouse browses every database the user can reach. PostgreSQL and Redshift need one to connect at all: use `postgres` (Redshift: `dev`) and switch with `Cmd+K`. System databases such as `mysql`, `information_schema` and `master` sit at the end of the switcher under **System**. To hide the rest, choose **View > Filter Databases** and select the ones you want; the choice is saved per connection and never hides a system database from the switcher. **View > Filter Schemas** does the same for the schemas of the database you are in. See [Filtering schemas](/features/connection-window#filtering-schemas). - -## Edit, delete, and where it all lives - -Right-click a connection to edit or delete it, or select it and press `Delete`. Deleting asks first and removes the saved settings, nothing on the server. Pressing `Delete` on a row under **Favorites** or **Recent** only takes it out of that section. Edits apply on the next connect. - -Connections are stored in `~/Library/Application Support/TablePro/connections.json` with their passwords in the macOS Keychain, so copying that file to another Mac restores the connections without the passwords. - -Right-clicking the Dock icon lists saved connections under **Open Connection**. diff --git a/docs/connections/remote-database-files.mdx b/docs/connections/remote-database-files.mdx deleted file mode 100644 index 82e7503758..0000000000 --- a/docs/connections/remote-database-files.mdx +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Remote Database Files -description: Work with a SQLite database on an SSH server, live on the server or as a read-only copy on this Mac ---- - -A SQLite database on an SSH server can be reached two ways, and the **Open** menu beside the path is where you choose. Run your statements on the server against the live file, or fetch a read-only copy and open it here. - -The choice turns on where SQLite runs. Its file locking is only reliable for a process on the same machine as the file, so a client that wrote across the network would eventually corrupt the database. Running on the server keeps every read and write on that one machine; the copy sidesteps the question by never writing at all. - -## Set one up - - - - Create or edit a SQLite connection, open the **Network** tab, and set **Connect via** to **Remote Database File**. - - - Fill in **SSH Host**, **SSH Port**, and **SSH User**, then pick an authentication method. Password, private key, SSH agent, keyboard-interactive, jump hosts, and one-time codes all work the way they do for a tunnel, and a saved [SSH profile](/connections/ssh-profiles) supplies all of it at once. - - - Put the database's path on the server in **Path**. An absolute path is taken as written. A relative path, or one starting `~`, resolves against the SSH account's home directory. - - - Set **Open** to **On the Server** for live reads and writes, or **As a Read-Only Copy** to browse a snapshot on this Mac. New connections start on the server. - - - - -The connection list shows the origin as `deploy@prod-1:/srv/app.db` instead of a local path, so a remote database never reads like one of your own. - - -## On the Server - -Your statements run on the host, so the sidebar, the grid, the SQL editor and every structure edit act on the live database, alongside whatever else is writing it. Nothing is copied and nothing is installed: TablePro sends a small Python program over the SSH connection, and that program opens the file with the server's own SQLite. - -The server needs `python3`, version 3.6 or newer, with its standard library. Ubuntu, Debian and RHEL cloud images ship it; a stripped container may not. When it is missing, the connection fails with a message that says so, and **As a Read-Only Copy** is the fallback. - - -Statements run on the server as your SSH user, against the live database. A write commits in place. Leave [Safe Mode](/features/safe-mode) at **Read-Only** on connections you only mean to browse. - - -The server's SQLite is the one that runs the query, so its version sets what the editor and the structure tools can do. A `DROP COLUMN` needs 3.35, and editing a `CHECK` constraint through `ALTER TABLE` needs 3.53; on an older server the statement is refused with the server's own error. `SELECT version()` in the editor reports what the server runs. - -## As a Read-Only Copy - -The database is fetched over SFTP, opened from the copy on this Mac, and the original on the server is never written to. The connection runs at Safe Mode [**Read-Only**](/features/safe-mode#connections-that-are-always-read-only): editing a cell, running an `INSERT`, and altering a table are all refused, because an edit would land in the copy and disappear the next time the file was fetched. - -Where the server carries `sqlite3` 3.27 or newer, it is asked for a `VACUUM INTO` snapshot and that is what comes down. This is the case worth having: the snapshot is taken while other programs keep writing and is still internally consistent. Measured against a database taking 22,518 committed transactions during the copy, the snapshot passed `PRAGMA integrity_check`. - -Otherwise the file is copied directly, along with any `-wal` or `-journal` beside it. Those are not optional. A database in WAL mode keeps committed rows in `-wal` until a checkpoint moves them, so the main file alone is short of the most recent work. - - -A direct copy of a database another program is writing at that moment can hold a mix of old and new pages. Take one when nothing else is writing, install `sqlite3` on the server so the snapshot path is available, or open the database on the server instead. - - -What arrives is checked before anything opens it: the byte count has to match what the server reported, the file has to start like a SQLite database, and it has to pass `integrity_check`. A transfer cut short by a dropped session leaves an intact header and a plausible prefix, and `sqlite3_open` on that reports success and shows an empty database. - -## Reconnecting - -Closing a connection ends the server session, or leaves the copy on disk. Opening it again reconnects, or compares the file on the server against what was fetched and reuses the copy when neither its size, its modification time, nor its write-ahead log has moved. The health check watches a live server session and reconnects it after a dropped connection, the same way an SSH tunnel recovers. - -## Limitations - -No remote file browser. Type the path. - -SQLite only. DuckDB and libSQL open local files too, but their recovery-log naming and their local-versus-remote modes need handling this does not yet have. - -Two connections naming one remote file each open their own session or copy. Two copies of TablePro on two Macs cannot see each other; the server's own locking keeps their writes apart. - -## Related - -- [SSH Tunneling](/connections/ssh-tunneling) for reaching a database server rather than a file -- [SSH Profiles](/connections/ssh-profiles) for reusing one bastion across connections -- [SQLite](/databases/sqlite) diff --git a/docs/connections/socks-proxy.mdx b/docs/connections/socks-proxy.mdx deleted file mode 100644 index 8a64afb2c3..0000000000 --- a/docs/connections/socks-proxy.mdx +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: SOCKS Proxy -description: Route a database connection through a SOCKS5 proxy, with remote DNS (socks5h) so the proxy resolves the database hostname ---- - -Leave the database's **Host** and **Port** on the General section exactly as they are. The proxy resolves that name and dials it from its own side. A hostname that exists only inside the private network works, and no DNS query for the database leaves your Mac. - - - Proxy server host and port with a username and password below - Proxy server host and port with a username and password below - - -## How it works - -```mermaid -flowchart LR - subgraph mac ["Your Mac"] - Driver["Database driver
127.0.0.1:auto"] - Relay["TablePro relay"] - end - - subgraph proxy ["SOCKS5 Proxy"] - SOCKS["proxy:1080"] - end - - subgraph db ["Database Server"] - Database["PostgreSQL
MySQL
db:5432"] - end - - Driver -->|"loopback"| Relay -->|"SOCKS5"| SOCKS -->|"TCP"| Database -``` - -No helper binary is involved: the relay is part of the app. It listens on a free loopback port and carries each connection through the proxy. If the relay dies mid-session, the connection reconnects and rebuilds it, up to ten attempts with a widening delay. - -## Setting up - - - - On the **Network** tab, set **Connect via** to **SOCKS Proxy**. A connection uses one transport, so choosing this one switches off whichever was selected before. - - - **Host** and **Port** under **Proxy Server**, plus **Username** and **Password** if the proxy authenticates. - - - On **General**, click **Test Connection**. - - - -It is offered for the drivers that support SSH tunneling; the [transport matrix](/connections/connection-form#which-drivers-get-which-transports) says which. - -## Options - -| Field | What it is | Default | -|-------|-----------|---------| -| **Host** | The SOCKS5 proxy's address. | - | -| **Port** | The proxy's port. | 1080 | -| **Username** | Only for a proxy that requires username and password authentication. | - | -| **Password** | Stored in the macOS Keychain. Blank connects without authentication. | - | - - -An SSH dynamic port forward is a SOCKS5 proxy. Run `ssh -D 1080 user@bastion`, then set the host to `127.0.0.1` and the port to `1080`. - - -[SSL/TLS](/connections/ssl) still applies, with one unavoidable adjustment: the driver dials a loopback port that no server certificate names, so **Verify CA** and **Verify Identity** fall back to **Required** and certificate paths are dropped. - -Bytes through the proxy are counted the same way an SSH tunnel's are, and the totals and the live rate sit under the connection switcher. See [transport activity](/connections#switch-connections-and-databases). - -## Troubleshooting - -### Timed out connecting through the SOCKS proxy - -Fifteen seconds passed with no path to the database. The proxy did not answer, rejected the credentials, or could not reach the database. Check the proxy host and port, then that the database answers from the proxy's network. - -### Local network permission prompt - -On macOS 15 and later, a proxy on your local network (a `192.168.x.x` address) raises the one-time Local Network alert. Allow it, or the proxy stays unreachable. A loopback proxy such as `ssh -D` on `127.0.0.1` never triggers it. - -### The database rejects the connection - -The path works and the server refused you. The driver's own error is shown, exactly as on a direct connection: check credentials, SSL settings, and whether the database accepts connections from the proxy's address. diff --git a/docs/connections/ssh-profiles.mdx b/docs/connections/ssh-profiles.mdx deleted file mode 100644 index 647785a837..0000000000 --- a/docs/connections/ssh-profiles.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: SSH Profiles -description: Save SSH tunnel configurations as reusable profiles shared across connections ---- - -One bastion, many connections. A profile holds the server (host, port, username), the authentication method, the two-factor settings, and the jump hosts, so every connection through that bastion stops repeating them. How the tunnel itself behaves is on [SSH Tunneling](/connections/ssh-tunneling). - -## Create a profile - - - - **Create New Profile…** sits beside the **Profile** picker. - - - The same fields an inline config has, plus a name. - - - The profile now appears in the **Profile** picker of every connection. - - - - - Creating an SSH profile - Creating an SSH profile - - -**Save Current as Profile…** converts a connection that already has an inline config. Picking a profile swaps the inline fields for a read-only **Profile Details** summary; **Inline Configuration** in the same picker brings them back for a tunnel that belongs to one connection alone. - -## Test a profile - -**Test Connection** in the profile editor runs the handshake, verifies the host key, and authenticates, without touching a database. Success turns the button into a green checkmark reading **Connected**; failure marks it with a red X and prints the error beside it. An interactive prompt, a verification code for instance, appears here exactly as it would on connect. - -## Edit and delete - -**Edit Profile…** opens the selected profile, and **Delete Profile** is at the bottom of that editor. An edit reaches every connection using the profile, on this Mac and on any Mac it syncs to: the server, the authentication method, the jump hosts, the password, the key passphrase and the TOTP secret all come from the profile on the next connect. - -Deleting a profile hands its configuration and its secrets to each connection that used it, as that connection's own inline tunnel. Nothing stops connecting, and the tunnel is now editable per connection under **SSH Tunnel**. - -## iCloud Sync - -Profiles sync across Macs with iCloud Sync on and the **SSH Profiles** toggle set in **Settings > Sync**. Passwords, key passphrases, and TOTP secrets stay in the local Keychain unless the **Passwords** toggle, nested under **Connections**, is on too, and that covers newly saved credentials only: re-save an existing password to include it. diff --git a/docs/connections/ssh-tunneling.mdx b/docs/connections/ssh-tunneling.mdx deleted file mode 100644 index 42b51f3c3f..0000000000 --- a/docs/connections/ssh-tunneling.mdx +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: SSH Tunneling -description: Route database connections through an SSH tunnel to reach servers in private networks ---- - -The database **Host** on the General section is resolved from the SSH server, not from your Mac. A database on the SSH server itself is therefore `localhost`, not the server's public name, and a database elsewhere on the private network is whatever the SSH server calls it (an RDS endpoint, for instance). - -```mermaid -flowchart LR - TablePro["TablePro
localhost:60000"] -->|"Encrypted tunnel"| SSH["SSH server"] -->|"Internal network"| DB["Database
db:3306"] -``` - -## Set up a tunnel - - - - In the connection form, open the **Network** tab and set **Connect via** to **SSH Tunnel**. A connection uses one transport, so choosing this one switches off whichever was selected before. - - - Fill in **SSH Host**, **SSH Port** (22 by default), and **SSH User**. With `~/.ssh/config` entries present, a **Config Host** picker appears above the host field instead. - - - Password, Private Key, SSH Agent, Keyboard Interactive, or None. [Authentication methods](#authentication-methods) has the fields for each. - - - Back on **General**, `localhost` reaches a database on the SSH server itself. One on a unix socket needs [Socket Path](#forwarding-to-a-unix-socket) instead. - - - A wrong host, a blocked forward, or an agent nothing answered on names the real reason instead of timing out. [Troubleshooting](#troubleshooting) has the messages the SSH side reports. - - - -To share one SSH config across connections, save it with **Save Current as Profile…** or pick an existing one from the **Profile** picker; see [SSH Profiles](/connections/ssh-profiles). To fill the fields from a string instead, paste a `scheme+ssh://` URL into the [Import from URL…](/connections/urls#ssh-tunnel-format) sheet. - - - Network section with SSH Tunnel selected and a saved profile in the Profile picker - Network section with SSH Tunnel selected and a saved profile in the Profile picker - - -**SSH Tunnel** is not offered on SQLite, DuckDB, PGlite, libSQL, Turso, Beancount, BigQuery, Spanner, Cloudflare D1, Cloudflare R2 SQL, DynamoDB, or Snowflake: each is reached over a local file, a loopback socket, or a vendor HTTP API. For SAP HANA, forward the port yourself, as its [Limitations](/databases/sap-hana#limitations) show. - -## Authentication methods - -| Method | What to fill in | -|---|---| -| **Password** | The SSH password. Prefer a key on anything production | -| **Private Key** | **Key File**, with **Browse** to pick one, and **Passphrase** if the key is encrypted. Leaving **Key File** empty auto-detects from `~/.ssh/config` and the default key locations | -| **SSH Agent** | **Agent Socket**: **SSH_AUTH_SOCK** for the `ssh-agent` macOS runs, **1Password** for its socket at `~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock`, or **Custom Path** for Secretive or your own. **Identity File** takes a `.pub` and offers that key ahead of the others; empty offers every key the agent holds. Signing stays in the agent; the key is never read, and no key file is tried if the agent refuses | -| **Keyboard Interactive** | The SSH password, sent through SSH's challenge-response. Use it when the server rejects plain password auth, common with PAM | -| **None** | Nothing, for a server that authenticates the connection itself such as a [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh) host. A server that does want credentials fails the connect with a message naming the other methods | - -**Private Key** is the one to pick unless the server only ever issued you a password. - -### Verification codes and two-factor authentication - -A keyboard-interactive challenge partway through authentication, from `google-authenticator` or `duo_unix` for example, is shown as the server worded it and your answer sent back. Every method except **None** supports this, a key or agent followed by a second factor included (`AuthenticationMethods publickey,keyboard-interactive`). - -The **Two-Factor Authentication** section can answer TOTP codes instead. **None** and **Prompt at Connect** both ask when the server does; **Auto Generate** computes the code from the base32 **TOTP Secret** off your authenticator enrollment, with **Algorithm** (SHA1, SHA256, SHA512), **Digits** (6 or 8), and **Period** (30s or 60s) already set to what most servers use. - -## Host keys - -A first connection shows the server's key type and SHA-256 fingerprint, in `ssh-keygen -l` format, and waits for **Trust**. Trusted keys go to `~/Library/Application Support/TablePro/known_hosts`. - -If a trusted server's key later changes, an **SSH Host Key Changed** alert lists the previous and current fingerprints. **Connect Anyway** does not answer the Return key. Escape picks **Disconnect**, which is what to take unless you know the server was reinstalled. With jump hosts, every hop's key is verified the same way. - -## Using ~/.ssh/config - -Pick an alias from the **Config Host** picker and `HostName`, `User`, `Port`, `IdentityFile`, `IdentitiesOnly`, `IdentityAgent`, and `ProxyJump` are resolved from the config at connect time. Anything typed into the form overrides the file, which is re-read whenever it or an `Include`d file changes. - -`IdentityFile` picks which agent key is offered, not only which key file is read, and a `.pub` is enough. That is how a 1Password or Secretive key gets pinned to a host. `IdentitiesOnly yes` then drops every agent key the config did not name, which is what keeps a thirty-key agent inside a server's `MaxAuthTries`. - - - Config Host picker - Config Host picker - - -### Tokens - -Each keyword takes the tokens `ssh_config(5)` gives it, and no others. `Hostname %h` is the common one: it names the host the connection already asked for, which is what makes a catch-all `Host *.*` block work. - -| Keyword | Tokens | -|---------|--------| -| `Hostname` | `%%` `%h` | -| `ProxyJump` | `%%` `%h` `%n` `%p` `%r` | -| `IdentityFile`, `IdentityAgent`, `Match exec` | `%%` `%C` `%d` `%h` `%i` `%j` `%k` `%L` `%l` `%n` `%p` `%r` `%u` | -| `Include` | `%%` `%d` `%i` `%L` `%l` `%u` | - -`%h` is the host after any `HostName` substitution and `%n` the one the connection named; `%p`, `%r` and `%j` are the port, remote user and jump host in effect; `%d` and `%u` are your home directory and username; `%C` hashes `%l%h%p%r%j`, the way `ControlPath` does. `${VAR}` reads an environment variable in the same places, and a leading `~` or `~user` expands in any keyword naming a file. - -A token a keyword does not take stops the connection and names both, rather than reaching the server as literal text. `Include` is the one narrowing: the file is read once for every connection, before any host is known, so a token naming the target is skipped with a note in Console. - -## Jump hosts - -For a database behind more than one bastion, expand **Jump Hosts** and add each hop in order. Each hop is an SSH session tunneled through the previous one, with no `ssh` subprocess involved. - -| Field | Description | -|-------|-------------| -| **Host** | Hostname or IP of the jump host | -| **Port** | SSH port, 22 by default | -| **Username** | SSH username for this hop | -| **Auth** | **Private Key** or **SSH Agent**. Password auth is not offered for a hop | -| **Key File** | Private key path, under Private Key auth | - -With the list left empty and an SSH host that matches a config entry carrying `ProxyJump`, that directive is followed instead. - -Hops are Mac only. A connection that has them syncs to [iPhone and iPad](/ios) with every hop, its port and its key path intact, and does not open there. The tunnel's own port and auth method make the same round trip: a port you left empty stays empty, so `~/.ssh/config` still decides it. - -## Forwarding to a unix socket - -Some servers listen on a unix socket with no TCP port open at all, a PostgreSQL box set up for `local` connections in `pg_hba.conf` being the usual case. Fill in **Socket Path** under **SSH Tunnel** and the forward targets that socket, the same thing `ssh -L 5434:/var/run/postgresql/.s.PGSQL.5432 server` does by hand. **Host** and **Port** are ignored while a socket path is set. Jump hosts still apply: the hops reach the SSH server, and the socket opens from there. - -Point at the socket file, not the directory holding it: - -| Database | Typical socket path | -|----------|---------------------| -| PostgreSQL | `/var/run/postgresql/.s.PGSQL.5432` | -| MySQL / MariaDB | `/var/run/mysqld/mysqld.sock` | -| Redis | `/var/run/redis/redis.sock` | - -`peer` authentication works: the SSH server opens the socket as your SSH login user, so a `local all all peer` line authenticates you as the PostgreSQL role of that name. - -A database on a socket cannot negotiate TLS, so SSL is switched off for the connection. The SSH tunnel still encrypts the whole path. - -## If the tunnel drops - -A keep-alive goes out every 30 seconds. When one fails the session drops to connecting and the tunnel is rebuilt, retrying at 2s, 4s, 8s and doubling to a 120-second ceiling, ten attempts at most. A query in flight dies with the tunnel and is not replayed. After ten failures the session ends with **SSH tunnel disconnected. Click to reconnect.** - -If tunnels keep dropping on an idle network, the keep-alive is not the missing piece: check the server's `ClientAliveInterval` and the idle timeouts on any firewall or load balancer in between. - -## What the tunnel is carrying - -Press `Ctrl+Cmd+C` and read the footer under the connection list: bytes received and sent since the tunnel opened, and the rate beside each direction while traffic is moving. A rebuilt tunnel starts again from zero. See [transport activity](/connections#switch-connections-and-databases). - -## Troubleshooting - -### "The SSH server could not reach …" - -SSH is fine and the forward is not. Almost always the **Host** field: that address is resolved from the SSH server, so a database bound to `127.0.0.1` (the MySQL and PostgreSQL default) needs **Host** set to `localhost`. Check what it is bound to with `ss -lntp` on the server. If the host is right, check `sshd_config` for `AllowTcpForwarding yes`. - -### "The SSH server did not open a forwarding channel to …" - -The destination took the connection and never answered, which usually means a firewall or security group dropping packets rather than refusing them. Test it from the SSH server: `ssh user@server "nc -zv db-host 3306"`. - -### "The SSH server would not forward the socket …" - -The socket path does not exist on the server, or `sshd_config` sets `AllowStreamLocalForwarding no`. That is a separate setting from `AllowTcpForwarding`. - -### "No SSH agent answered on the socket from …" - -Nothing is listening where that socket points, and the message names which of the three set it: **Agent Socket** under **SSH Tunnel**, an `IdentityAgent` line for the host in `~/.ssh/config`, or `SSH_AUTH_SOCK`. Change it in the place the message names. - -`SSH_AUTH_SOCK` is the one that catches people out. An app launched from Finder gets it from launchd, which means the `ssh-agent` macOS runs, whatever a shell profile exports. 1Password and Secretive are reached by naming their own socket: switch **Agent Socket** to **1Password**, or to **Custom Path**. - -A jump host has no **Agent Socket** field of its own, so its agent comes from `IdentityAgent` or `SSH_AUTH_SOCK`. - -### "The SSH agent from … holds no keys." - -The agent answered and offered nothing. 1Password serves keys only while it is running and unlocked, and Secretive only while its agent is loaded; unlock it and add the key there. `ssh-add` loads keys into the `SSH_AUTH_SOCK` agent alone, so it is the fix only when that is the socket in the message. - -### "The SSH agent from … holds no key matching the identity file set for this host. …" - -The agent has keys, and none of them is the one `IdentityFile` or **Identity File** names. List what that agent holds with `SSH_AUTH_SOCK= ssh-add -L`, since a bare `ssh-add` reads the shell's `SSH_AUTH_SOCK` rather than the socket TablePro used. Point the setting at a key in that list, or clear it to go back to offering every key. - -### "No public key could be read from the identity file set for this host, …" - -`IdentityFile` or **Identity File** names a path, and neither it, its `.pub` sidecar, nor its `-cert.pub` parses as an OpenSSH public key. Check the path, and run `ssh-keygen -l -f ` to confirm the file itself. `ssh-keygen -y -f > .pub` writes the sidecar when only the private key exists. - -### "SSH agent did not authenticate. …" - -The agent offered keys and the server accepted none of them. Check the public key is in `~/.ssh/authorized_keys` on the server for the **SSH User** you filled in, and that the key you expect is in the agent rather than only on disk. - -### "The SSH server closed the connection while the agent's keys were being offered. …" - -The server reached `MaxAuthTries`, 6 by default, before a key it accepts came up. Count that agent's keys with `SSH_AUTH_SOCK= ssh-add -L`: past six, the right one may never be offered. Name it with `IdentityFile` and `IdentitiesOnly yes` for the host in `~/.ssh/config`, or fill in **Identity File** on the connection. - -### The tunnel connects and the database refuses the login - -The database credentials are separate from the SSH ones. Check you did not carry one set into the other. - -### A firewall prompt on connect - -The local end of the tunnel listens on a port between 60000 and 65000. Allow it. - -### SSH fails instantly against a server on your own network - -macOS 15 and later put outbound connections to local network addresses behind a Local Network permission, and a denied app fails fast with "no route to host". Check TablePro under **System Settings > Privacy & Security > Local Network**. Servers over the internet, and anything on `127.0.0.1`, are unaffected. - -### SSH itself fails - -Try the same host, user, and key in Terminal with `ssh -v user@server`. If that fails too, the problem is server-side. diff --git a/docs/connections/ssl.mdx b/docs/connections/ssl.mdx deleted file mode 100644 index 06f7c093ed..0000000000 --- a/docs/connections/ssl.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: SSL/TLS -description: Configure encrypted database connections, per-driver defaults, and certificate verification ---- - -Managed databases require TLS, every one of them, from RDS and Cloud SQL to Supabase, Neon, Atlas, and ClickHouse Cloud. Use **Verify Identity** when your provider hands you a CA certificate, or when the driver checks the system trust store on its own: ClickHouse, Trino, and SQL Server. Otherwise set **Required (skip verify)**. - -## Modes - -| Mode | Behavior | -|---|---| -| Disabled | Plain TCP, no TLS negotiation | -| Preferred | TLS first, plain if the server will not, where the driver has a fallback at all | -| Required (skip verify) | TLS or nothing. The certificate is not checked | -| Verify CA | TLS, and the certificate must chain to the CA certificate you choose. The hostname is not checked, so a CA certificate is required | -| Verify Identity | TLS, certificate validated, and the hostname must match the certificate subject | - - - SSL mode and certificate settings in the connection form - SSL mode and certificate settings in the connection form - - -## Certificate fields - -The certificate rows appear only once **SSL Mode** is anything but Disabled. Each takes a file path, with **Browse** to pick one. - -| Field | When it appears | What it takes | -|---|---|---| -| **Certificate**, under **CA Certificate** | Verify CA and Verify Identity only | The PEM or DER certificate the server's certificate chains to, for example `/path/to/ca-cert.pem`. Saving without it is blocked, except under Verify Identity on ClickHouse, Kafka, SAP HANA and Trino, which check the system trust store when it is empty | -| **Client Certificate** | Any mode but Disabled | Your certificate, for a server that enforces mutual TLS. Optional otherwise | -| **Client Key** | Any mode but Disabled | The matching private key. Required once a client certificate is set | -| **Key Passphrase** | Once **Client Key** has a path, on drivers that support it (Cassandra and ScyllaDB) | The passphrase of an encrypted key. Stored in the Keychain | - -SQL Server shows no certificate fields at all and verifies against the system trust store. See [SQL Server](/databases/mssql#limitations). Under Verify Identity, SAP HANA and Kafka verify against the system trust store when **Certificate** is left empty. See [SAP HANA](/databases/sap-hana#ssl%2Ftls). - -## Per-driver defaults - -A new connection starts on the mode that matches the driver's own default, and so does a connection URL that names no mode. The driver's own guidance prints under the picker where there is any. - -| Driver | Default | What Preferred does | -|---|---|---| -| PostgreSQL, Redshift, CockroachDB | Preferred | libpq's own `sslmode=prefer` fallback to plain TCP. Same as `psql` and DataGrip | -| MySQL, MariaDB | Preferred | Tries TLS, then retries plain on an SSL handshake error. Auth and network errors are not retried | -| SQL Server | Preferred | FreeTDS `encryption=request`: the login over TLS, the rest in plain TCP unless the server forces encryption | -| Teradata | Disabled | Opens a TLS transport, retries on a plain socket if it fails to come up | -| MongoDB, Redis, Cassandra, ClickHouse, Elasticsearch, Typesense, SurrealDB, Weaviate | Disabled | Nothing. No fallback exists, so Preferred forces TLS exactly like Required | -| etcd | Disabled | Nothing. The driver never reads these fields. Set **TLS Mode** on the Options section instead, and see [etcd](/databases/etcd) | -| Trino | Disabled | Sends every request over HTTPS with no fallback, again like Required | -| SAP HANA | Verify Identity | Nothing. No fallback exists, so Preferred forces TLS exactly like Required | -| Oracle | Disabled | Connects in plain TCP, so it behaves like Disabled. A red warning appears under the picker; use Required to enforce TCPS | -| Snowflake, BigQuery, Spanner, DynamoDB, Cloudflare D1, Cloudflare R2 SQL, libSQL / Turso | Always encrypted | These drivers are HTTPS and manage TLS themselves. No **SSL Mode** | -| SQLite, DuckDB, Beancount, PGlite | None | Local files or an in-process engine. No **SSL Mode** | - -## Ports that mean TLS - -| Driver | Ports | -|---|---| -| Trino | 443 | -| ClickHouse | 443, 8443 | - -Type one of these ports while **SSL Mode** is still at the driver's default and the mode becomes **Verify Identity**, with a note under **Port**. Type a different port and the default comes back. A mode you picked yourself never changes. - -A saved connection set to **Disabled** on one of these ports shows a warning under **Port**, with a button that switches it to **Verify Identity**. An imported URL follows the same rule; see [Connection URL Reference](/connections/urls). - -## Behind a tunnel - -Verify CA and Verify Identity do not survive a tunnel. An SSH tunnel, Cloudflare tunnel, Cloud SQL Auth Proxy, or SOCKS proxy has the driver connect to `127.0.0.1`, and no server certificate is issued for that, so the mode drops to Required for the tunneled connection and the certificate paths are cleared. TLS itself still runs the whole way to the database; only the certificate check goes. If you need the check, reach the server without a tunnel. - -Forwarding to a unix socket drops TLS altogether, because a socket cannot negotiate it. The SSH tunnel encrypts that path instead. - -## Troubleshooting - -A TLS failure is reported with the cause, a mode to switch to, and the driver's own response underneath, password redacted: - -```text -The server requires an encrypted connection but TablePro is configured to connect in plain text. - -On the connection's Network tab, set SSL Mode to Verify Identity, or to Required to skip the certificate check. - -Server response: FATAL: no pg_hba.conf entry for host "…", user "…", database "…", no encryption -``` - -### "FATAL: no pg_hba.conf entry for host … no encryption" - -The PostgreSQL server requires SSL and the connection is set to Disabled. Switch to **Preferred** or **Required (skip verify)**. - -### "Connections using insecure transport are prohibited" - -The MySQL server has `require_secure_transport=ON`. Switch to **Preferred** or **Required (skip verify)**. - -### "SSL handshake failed" / "tls handshake failed" - -Driver and server share no TLS version or cipher. Update the server, or on a development box drop to **Required (skip verify)**. - -### "certificate verify failed" / "self-signed certificate" - -The server's certificate chains to nothing in the system trust store. Set **Verify CA** and fill **Certificate** with the CA's PEM, or drop to **Required (skip verify)** to skip validation. - -### "hostname does not match certificate" - -The certificate's CN or SAN does not cover the host you typed. Change **Host** to a name the certificate covers. - -### "client certificate required" - -The server enforces mutual TLS. Fill in **Client Certificate** and **Client Key**. - -### "client private key is encrypted" / "passphrase is incorrect" - -Enter the key's passphrase in **Key Passphrase**, which appears once **Client Key** has a path. Trino and etcd have no **Key Passphrase**: decrypt the key first with `openssl pkey -in encrypted.key -out client.key` and choose the decrypted file. - -## On iPhone and iPad - -Four modes, Preferred excluded. MySQL, MariaDB, PostgreSQL, Redshift, and Redis get **CA Certificate**, **Client Certificate**, and **Client Key** rows alongside them. Oracle gets Disabled, Required, and Verify Identity with no certificate rows, so Verify Identity checks the server against the system trust store; an Oracle connection saved with Verify CA does not connect until its mode changes. SQL Server gets only Disabled and Required. - -Each row takes a file or pasted text. **Client Certificate** also accepts a PKCS#12 (`.p12` or `.pfx`) and fills in both the certificate and its key from it. iOS cannot read a `.p12` exported without a password, and rejects a private key carrying its own passphrase: strip the passphrase or export a `.p12`. - -Imported certificates never leave the device and never sync, so a connection set up on the Mac needs them imported again on each iPhone and iPad. See [TablePro for iPhone and iPad](/ios). diff --git a/docs/connections/tunnel-command.mdx b/docs/connections/tunnel-command.mdx deleted file mode 100644 index 7f9a8e2d07..0000000000 --- a/docs/connections/tunnel-command.mdx +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Tunnel Command -description: Reach a database through kubectl port-forward, an AWS SSM session, or any command that opens a local port ---- - -A `kubectl port-forward` running in a terminal is a window you cannot close for as long as you need the database, and a tab you have to notice when it dies. Move it into the connection and it starts on connect, stops on disconnect, and comes back on its own. - - - Tunnel Command selected, with the kubectl method, namespace, resource and a Will Run section - Tunnel Command selected, with the kubectl method, namespace, resource and a Will Run section - - -## Setting up - - - - On the **Network** tab, set **Connect via** to **Tunnel Command**. A connection uses one transport, so choosing this one switches off whichever was selected before. - - - **kubectl port-forward** and **AWS SSM Session** ask for the two or three things that vary. **Custom Command** takes a command line. - - - It shows the exact argument list, with `{port}` standing in for the port that gets allocated on connect. - - - On **General**, click **Test Connection**. - - - -The **Host** and **Port** on the General section stay the database's own. They are what the forward points at, so a kubectl forward reads the port from there and an SSM session forwards to that host and port from the target instance. - -## Methods - -| Method | Asks for | Runs | -|---|---|---| -| **kubectl port-forward** | Resource, namespace, context | `kubectl port-forward` against the resource, on `127.0.0.1` | -| **AWS SSM Session** | Target instance, profile, region | `aws ssm start-session` with the `AWS-StartPortForwardingSessionToRemoteHost` document | -| **Custom Command** | A command line | Whatever you wrote | - -Pick a preset if one fits. A preset takes named values rather than a command line, so nothing typed into it can turn into an extra argument, and the values travel between machines that have the same tools installed. - -### Where the tool comes from - -Both presets look the tool up on `PATH`, with `/usr/local/bin` and `/opt/homebrew/bin` added, because an app started from the Dock inherits a minimal `PATH` rather than your shell's. **Executable path** overrides that with a full path. - -An SSM session also needs the [Session Manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html), which the AWS CLI finds the same way. - -### Custom commands - -Three placeholders are substituted before the command runs: - -| Placeholder | Value | -|---|---| -| `{port}` | The loopback port allocated for this connection. Required | -| `{host}` | The **Host** field on the General section | -| `{remotePort}` | The **Port** field on the General section | - -```bash -ssh -N -L {port}:{host}:{remotePort} bastion.example.com -``` - -The command is split into arguments the way a shell would split it, quoting included, and then run directly. Nothing is expanded: `$HOME` stays four characters and `*` stays one. For environment variables, start the command with `/usr/bin/env`: - -```bash -/usr/bin/env KUBECONFIG=~/.kube/staging kubectl port-forward -n prod service/pg {port}:5432 -``` - -Substitution happens after the split, so a value carrying a space stays a single argument. - -## While the connection is open - -The process is held for the life of the connection and killed with its whole process group on disconnect, which is what takes an `aws ssm` session's helper down with it. If it exits on its own while the connection is still open, the connection reconnects and starts it again, up to ten attempts with a widening delay. That covers an SSM session hitting its idle timeout and a `kubectl port-forward` losing its pod. - -The local port shares the connection's **Connect timeout** under **Options**, which uses 30 seconds -when empty. Past that deadline the connect fails, and the error carries the last lines the command -printed to standard error, which is usually where the real reason is. - -[SSL/TLS](/connections/ssl) still applies, with one unavoidable adjustment: the driver dials a loopback port that no server certificate names, so **Verify CA** and **Verify Identity** fall back to **Required** and certificate paths are dropped. - -## Sharing a connection that has one - -A tunnel command travels under tighter rules than the rest of a connection. - -| Route | What happens | -|---|---| -| iCloud sync | Never carried. The command names tools on one Mac | -| `tablepro://` link, team library | Stripped before the connection is offered | -| `.tablepro` file | Offered, and importing prints every command and asks. **Import Without Commands** is the default | -| Duplicating a connection | Copied, like every other setting | - -Editing `connections.json` by hand is detected, and a connection whose command arrived that way refuses to run it until you open the connection in TablePro and save it again. - -## Troubleshooting - -### kubectl was not found - -The tool is not on the `PATH` an app inherits. Run `which kubectl` in a terminal and put the answer in **Executable path**. - -### The tunnel command exited before the port was open - -The command failed on its own terms and its output follows the message. A wrong namespace, an expired credential, and a resource that does not exist all land here. Run the same command in a terminal to confirm. - -### The tunnel command did not open its local port in time - -The connection's **Connect timeout** passed with the process still running and nothing listening. An -SSM session waiting on a target that is not registered with Systems Manager behaves this way, as -does a `kubectl` context pointing at an unreachable cluster. - -### The connection keeps reconnecting - -The command is exiting shortly after it starts. An SSM idle timeout is the common cause: raise it in the session preferences for the account, or expect a reconnect on the first query after a quiet period. diff --git a/docs/connections/urls.mdx b/docs/connections/urls.mdx deleted file mode 100644 index 68f7b5126c..0000000000 --- a/docs/connections/urls.mdx +++ /dev/null @@ -1,244 +0,0 @@ ---- -title: Connection URL Reference -description: Every supported database URL scheme, format, and query parameter for imports and direct connections ---- - -Two readers take these URLs, and not the same set. The **Import from URL…** sheet accepts every scheme in this reference, while `open` and browser links reach TablePro only through the ones it registers with macOS. - - - Import from URL sheet with a pasted connection URL - Import from URL sheet with a pasted connection URL - - -## URL schemes - -| Scheme | Database | `open` | -|--------|----------|:---:| -| `postgresql://` | PostgreSQL | Yes | -| `postgres://` | PostgreSQL (alias) | Yes | -| `mysql://` | MySQL | Yes | -| `mariadb://` | MariaDB | Yes | -| `tidb://` | TiDB | Yes | -| `oceanbase://` | OceanBase | Yes | -| `sqlite://` | SQLite | Yes | -| `mongodb://` | MongoDB | Yes | -| `mongodb+srv://` | MongoDB (SRV) | Yes | -| `redis://` | Redis | Yes | -| `rediss://` | Redis with TLS | Yes | -| `redshift://` | Amazon Redshift | Yes | -| `cockroachdb://` | CockroachDB | Yes | -| `cockroach://` | CockroachDB (alias) | Yes | -| `pglite://` | PGlite | No | -| `mssql://` | Microsoft SQL Server | Yes | -| `sqlserver://` | Microsoft SQL Server (alias) | Yes | -| `jdbc:sqlserver://` | Microsoft SQL Server (JDBC) | No | -| `oracle://` | Oracle Database | Yes | -| `dm://` | Dameng DM8 | Yes | -| `hdb://` | SAP HANA | No | -| `jdbc:oracle:thin://` | Oracle Database (JDBC thin) | No | -| `cassandra://` | Cassandra | Yes | -| `cql://` | Cassandra (alias) | Yes | -| `scylladb://` | ScyllaDB | Yes | -| `scylla://` | ScyllaDB (alias) | Yes | -| `clickhouse://` | ClickHouse | Yes | -| `ch://` | ClickHouse (alias) | Yes | -| `teradata://` | Teradata | No | -| `trino://` | Trino | No | -| `duckdb://` | DuckDB file | Yes | -| `quack://` | DuckDB remote (Quack protocol) | No | -| `beancount://` | Beancount ledger file | No | -| `etcd://` | etcd | Yes | -| `etcds://` | etcd with TLS | Yes | -| `d1://` | Cloudflare D1 | Yes | -| `libsql://` | libSQL / Turso | Yes | -| `surrealdb://` | SurrealDB | Yes | - -**Import from URL…** also takes a JDBC URL: `jdbc:` in front of a scheme above, such as `jdbc:trino://` or `jdbc:postgresql://`, is dropped. `user` and `password` parameters fill in the credentials when the URL has none before the host, as in `jdbc:postgresql://db.example.com/app?user=analyst`. A JDBC form with a protocol segment, such as `jdbc:ch:http://`, is not recognized. - -Append `+ssh` to any non-file scheme (so not `sqlite`, `duckdb`, or `beancount`) to carry SSH settings in the same string: - -```text -postgresql+ssh:// -mysql+ssh:// -mariadb+ssh:// -``` - -No `+ssh` scheme is registered with macOS, so `open` and browser links cannot route one. Import those through the sheet. - -## Standard format - -```text -scheme://[username[:password]@]host[:port][/database][?param=value&…] -``` - -```text -postgresql://alice:secret@db.example.com:5432/myapp -mysql://root@localhost/shop -mongodb://user:pass@mongo.host:27017/analytics?authSource=admin -redis://:password@cache.host:6379/1 -sqlite:///Users/alice/data/local.db -``` - -If the URL is already in a project's `.env` file, [Open Project Folder](/features/project-folder-import) reads it for you instead of asking you to paste it. - - -Passwords containing `@`, `#`, `%`, or `:` must be percent-encoded. `p@ss#word` becomes `p%40ss%23word`. - - -A URL the parser cannot read is reported as `Could not parse database URL: …`, with the password stripped out of the echoed string. - -## SSH tunnel format - -Both sets of credentials go in one string, SSH first: - -```text -scheme+ssh://[ssh_user@]ssh_host[:ssh_port]/[db_user[:db_pass]@]db_host[:db_port][/database][?params] -``` - -```text -postgresql+ssh://deploy@bastion.host:22/dbuser:dbpass@internal-pg:5432/mydb -mysql+ssh://ec2-user@jump.host/root:secret@10.0.0.5/shop -mariadb+ssh://admin@ssh.host/maria_user@db.internal/store?usePrivateKey=true -``` - -An omitted `db_host` defaults to `127.0.0.1`. - -## Query parameters - -Names are matched case-insensitively. Anything unrecognized is ignored, except on a MongoDB URL, where every unknown parameter is passed to the driver unchanged. - -### Connection - -| Parameter | Description | Example | -|-----------|-------------|---------| -| `name` | Connection name shown in the sidebar, and part of what a trusted link is matched on | `?name=Production+DB` | -| `safeModeLevel` | [Safe Mode](/features/safe-mode) for this connection: 0=silent, 1=alert, 2=read-only | `?safeModeLevel=2` | -| `statusColor` | Connection color as hex, matched to the nearest palette color | `?statusColor=FF3B30` | -| `env` | Assign an existing tag by name. `environment` is accepted too | `?env=production` | -| `socket` | MySQL and MariaDB. Socket file path, in place of host and port. Alias `unix_socket`. See [Unix socket](#unix-socket) | `?socket=%2Ftmp%2Fmysql.sock` | - -### Unix socket - -```text -mysql://root@localhost/shop?socket=%2Ftmp%2Fmysql.sock -mariadb://root@/shop?socket=(/tmp/mysql.sock) -``` - -- Works only with host `localhost` or no host. -- Any other host, `127.0.0.1` included, ignores `socket` and uses TCP. -- Percent-encode the path, or wrap it in parentheses. -- `+` reads as a space. Write a literal `+` as `%2B`. -- The path must start with `/` and fit in 103 bytes. -- A path with a control or invisible character refuses the URL. -- Other engines and `+ssh` URLs ignore `socket`. -- **Copy Connection String** writes this form, with no port. - -### SSL/TLS - -| Parameter | Values | Description | -|-----------|--------|-------------| -| `sslmode` | `disable`, `prefer`, `require`, `verify-ca`, `verify-full` | SSL mode under libpq's names, in any case and with `_` for `-`, so `VERIFY_FULL` works too. ClickHouse JDBC's `strict` and `none` read as Verify Identity and Required | -| `tlsmode` | `0`-`4` | The same modes as integers: 0=disable, 1=prefer, 2=require, 3=verify-ca, 4=verify-full | -| `tls` / `ssl` | `true`, `false` | `true` turns TLS on: Verify Identity for Trino and ClickHouse, Required for every other database. `false` sets Disabled, even on a port that means TLS | -| `SSLVerification` | `FULL`, `CA`, `NONE` | Trino's certificate check once TLS is on: Verify Identity, Verify CA, Required. Ignored while TLS is off. Verify CA also needs a CA certificate file | - -`sslmode` or `tlsmode` wins over `ssl`, `tls` and `SSLVerification`. With none of them, a Trino URL on port `443` and a ClickHouse URL on `443` or `8443` import as Verify Identity, as in [Ports that mean TLS](/connections/ssl#ports-that-mean-tls), and every other URL takes the driver's default from [Per-driver defaults](/connections/ssl#per-driver-defaults). **Import from URL…** and a link opened from a browser or `open` resolve the mode the same way, so `ssl=false` or `sslmode=disable` is how a link asks for plain TCP. A URL that turns TLS on and names no port opens on the driver's TLS port, `443` for Trino and `8443` for ClickHouse, and a port written in the URL is kept. - -```text -postgresql://user:pass@host/db?sslmode=require -postgresql://user:pass@host/db?tlsmode=2 -``` - -### Navigation - -| Parameter | Description | Example | -|-----------|-------------|---------| -| `table` | Open this table once connected | `?table=users` | -| `view` | Open this view once connected | `?view=active_orders` | -| `schema` | Switch here first | `?schema=reporting` | - -On PostgreSQL, Redshift, SQL Server, and Oracle, `schema` switches the schema. Everywhere else the value is read as a database name and switches the database. - -```text -postgresql://user:pass@host/mydb?schema=reporting&table=monthly_sales -``` - -### Filtering - -These open the table in a new tab with the filter applied, so a tab already showing that table keeps its own filters. A link carrying a filter always asks first, even for a trusted database, and the alert shows the whole filter. A filter longer than 51,200 characters is refused. - -| Parameter | Description | Example | -|-----------|-------------|---------| -| `column` | Column to filter on | `?column=status` | -| `operation` | Filter operator. Symbols and names both work (`=`, `LIKE`, `Equal`, `Contains`, `Is Null`); `operator` is an alias for the parameter | `?operation=LIKE` | -| `value` | Filter value | `?value=active` | -| `condition` | Raw SQL WHERE condition, which overrides `column`, `operation`, and `value`. `raw` and `query` are aliases | `?condition=status%3D'active'` | - -```text -postgresql://user:pass@host/mydb?table=orders&column=status&operation==&value=pending -postgresql://user:pass@host/mydb?table=orders&condition=total%3E1000 -``` - -### SSH tunnel - -| Parameter | Description | -|-----------|-------------| -| `usePrivateKey` | `true` for key-based SSH authentication | -| `useSSHAgent` | `true` to authenticate through an SSH agent | -| `sshNoAuth` | `true` to send no credentials at all, for servers like Tailscale SSH. See [Authentication methods](/connections/ssh-tunneling#authentication-methods) | -| `agentSocket` | Path to a non-default agent socket | - -```text -postgresql+ssh://ubuntu@bastion/dbuser@10.0.0.5/mydb?usePrivateKey=true -``` - -### MongoDB - -| Parameter | Description | -|-----------|-------------| -| `authSource` | Authentication database, `admin` by default | -| `authMechanism` | For example `SCRAM-SHA-256` | -| `replicaSet` | Replica set name | - -Comma-separated hosts work for a replica set URI. - -```text -mongodb://user:pass@host:27017/mydb?authSource=admin -mongodb://user:pass@host1:27017,host2:27017,host3:27017/mydb?replicaSet=rs0 -``` - -## What the path component means - -The part after the host is the database name on most schemes. Seven read it differently: - -| Scheme | The path is | -|---|---| -| `redis://`, `rediss://` | The database index, from 0 to 2147483646, and 0 when omitted. `db2` reads as 2, and any other path is refused | -| `cassandra://`, `scylladb://` | The default keyspace. Omit it to connect with none | -| `sqlite://`, `duckdb://`, `beancount://` | An absolute file path, so the URL carries three slashes | -| `quack://` | The alias a remote DuckDB server attaches the database under. See [DuckDB](/databases/duckdb) | -| `d1://` | The D1 database name or UUID, with the host as the Cloudflare account ID | -| `libsql://` | Nothing. The host is the database URL, and the auth token goes in the connection form's password field | - -```text -redis://:password@host:6379/2 -cassandra://user:pass@host:9042/my_keyspace -duckdb:///Users/me/data/analytics.duckdb -quack://host:9494/alias -d1://account-id/database-name -libsql://your-database.turso.io -``` - - -When another client also handles the scheme, `open -b com.TablePro "mysql://…"` forces TablePro. See [Terminal](/integrations/terminal) for the `tablepro` command and [DDEV](/integrations/ddev) for `ddev tablepro`. - - -## Confirmation and trusted links - -Every URL opened from outside the app raises an **Open External Database Connection?** alert naming the database type, host and port or socket, user, and database. **Connect** does not answer the Return key. Escape is the only keyboard answer, and it cancels. - -**Always Allow** appears only for a database on this machine (`localhost`, `127.x.x.x`, `::1`, or a socket). A trusted link is matched on database type, host, socket, database, username, and the URL's `name` parameter, never the port, so the same link on a different port still connects without asking again. Trusting `localhost` does not cover a socket link. Trusting one socket does not cover another. Remote hosts always prompt, and so does a link through an SSH tunnel, whose database is on the SSH server. - -A link with a [filter](#filtering) asks every time and offers no **Always Allow**. Read the filter in the box under the target before choosing **Connect**: it runs on the table as soon as the connection opens. - -Trusted links are listed under **Settings > General**, where **Forget** drops one and **Forget All** drops every one. diff --git a/docs/consent.js b/docs/consent.js deleted file mode 100644 index 963535941c..0000000000 --- a/docs/consent.js +++ /dev/null @@ -1,169 +0,0 @@ -// Mintlify loads every .js file here on every page. docs.json (integrations.cookies) holds all analytics -// back until localStorage has KEY = GRANTED, and reads it only on page load, so a changed answer reloads. -(() => { - const KEY = 'tablepro:analytics-consent'; - const GRANTED = 'granted'; - const DENIED = 'denied'; - // The measurement ID in docs.json without its "G-", as GA names the stream cookie. - const STREAM_COOKIE = '_ga_GQYNPKSK83'; - // Mintlify's own visitor ID, which it writes once analytics are allowed. - const MINTLIFY_ID = 'mintlify_anonymous_id'; - const PRIVACY_URL = 'https://tablepro.app/privacy#cookies'; - const BAR_ID = 'tablepro-consent'; - - function read() { - try { - const value = window.localStorage.getItem(KEY); - - return value === GRANTED || value === DENIED ? value : null; - } catch { - return null; - } - } - - function write(choice) { - try { - window.localStorage.setItem(KEY, choice); - } catch { - // Storage is blocked: analytics stay off. - } - } - - function cookieNames() { - return document.cookie - .split(';') - .map((pair) => pair.split('=')[0].trim()) - .filter((name) => name === '_ga' || name.startsWith('_ga_')); - } - - // GA cookies sit on .tablepro.app, shared with tablepro.app, whose own stream may be there with consent. - function clearAnalytics() { - const parent = window.location.hostname.split('.').slice(-2).join('.'); - const expire = (name) => { - document.cookie = `${name}=; Max-Age=0; path=/; domain=.${parent}`; - document.cookie = `${name}=; Max-Age=0; path=/`; - }; - - expire(STREAM_COOKIE); - - if (!cookieNames().some((name) => name !== '_ga')) { - expire('_ga'); - } - - try { - window.localStorage.removeItem(MINTLIFY_ID); - } catch { - // Storage is blocked: nothing was kept. - } - } - - function choose(choice) { - const previous = read(); - - write(choice); - document.getElementById(BAR_ID)?.remove(); - - if (choice === DENIED) { - clearAnalytics(); - } - - if ((choice === GRANTED) !== (previous === GRANTED)) { - window.location.reload(); - } - } - - // The assistant launcher sits bottom left, so the bar goes right, and above it on a phone. - function addStyles() { - if (document.getElementById(`${BAR_ID}-style`)) { - return; - } - - const style = document.createElement('style'); - - style.id = `${BAR_ID}-style`; - style.textContent = ` -#${BAR_ID} { position: fixed; right: 16px; bottom: 76px; left: 16px; z-index: 60; padding: 16px; border: 1px solid #d4d4d4; border-radius: 12px; background: #fff; color: #171717; font-size: 14px; line-height: 1.5; box-shadow: 0 8px 30px rgb(0 0 0 / 0.12); } -html.dark #${BAR_ID} { border-color: #3f3f46; background: #18181b; color: #ededed; } -#${BAR_ID} p { margin: 0; } -#${BAR_ID} a { color: inherit; text-decoration: underline; } -#${BAR_ID} div { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; } -#${BAR_ID} button { padding: 6px 12px; border: 1px solid #767676; border-radius: 8px; background: transparent; color: inherit; font: inherit; font-weight: 500; cursor: pointer; } -html.dark #${BAR_ID} button { border-color: #8f8f8f; } -#${BAR_ID} button:hover { background: rgb(127 127 127 / 0.12); } -[data-cookie-settings] { padding: 0; border: 0; background: none; color: inherit; font: inherit; font-size: 14px; white-space: nowrap; text-decoration: underline; cursor: pointer; opacity: 0.8; } -@media (min-width: 640px) { #${BAR_ID} { bottom: 16px; left: auto; width: 26rem; } } -@media print { #${BAR_ID} { display: none; } } -`; - document.head.appendChild(style); - } - - function showBar(focus) { - let bar = document.getElementById(BAR_ID); - - if (!bar) { - addStyles(); - - bar = document.createElement('section'); - bar.id = BAR_ID; - bar.setAttribute('aria-label', 'Analytics cookies'); - - const text = document.createElement('p'); - const link = document.createElement('a'); - - text.textContent = 'Allow Google Analytics cookies to measure visits? '; - link.href = PRIVACY_URL; - link.textContent = 'Privacy'; - text.appendChild(link); - - const actions = document.createElement('div'); - - for (const [label, choice] of [['Allow', GRANTED], ['Decline', DENIED]]) { - const button = document.createElement('button'); - - button.type = 'button'; - button.textContent = label; - button.addEventListener('click', () => choose(choice)); - actions.appendChild(button); - } - - bar.append(text, actions); - document.body.appendChild(bar); - } - - if (focus) { - bar.querySelector('button')?.focus(); - } - } - - function addSettingsControl() { - const footer = document.getElementById('footer') ?? document.querySelector('footer'); - - if (!footer || footer.querySelector('[data-cookie-settings]')) { - return; - } - - addStyles(); - - const button = document.createElement('button'); - - button.type = 'button'; - button.dataset.cookieSettings = ''; - button.textContent = 'Cookie settings'; - button.addEventListener('click', () => showBar(true)); - footer.appendChild(button); - } - - const answer = read(); - - if (answer !== GRANTED) { - clearAnalytics(); - } - - if (answer === null) { - showBar(false); - } - - // Mintlify swaps the footer with the page on navigation. - addSettingsControl(); - window.addEventListener('mintlify:navigate', addSettingsControl); -})(); diff --git a/docs/customization/appearance.mdx b/docs/customization/appearance.mdx deleted file mode 100644 index 62bfc585df..0000000000 --- a/docs/customization/appearance.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Appearance -description: Themes, per-theme fonts, color customization, and connection colors ---- - -Light and Dark each hold their own theme. The **Editing** control at the top of the tab says which of the two the theme list writes to, so setting your dark theme never means leaving Light mode; **Appearance** beside it picks the one in use, or **Auto** to follow the system. - - - Appearance settings - Appearance settings - - -## Themes - -Four ship with the app: Default Light, Default Dark, and two more dark ones, Dracula and Nord. One theme carries the editor and syntax colors, the data grid colors, the interface colors, and both fonts. - -The list groups them into Built-in, Registry, and Custom, and shows the ones that suit the slot you are editing plus whatever that slot already holds. Click a row to assign it; it applies at once if that slot is the one in use. - -The editor on the right always edits the **active** theme, not the row you highlighted. Switch **Editing** to the slot in use before you change a font or a color, or the change lands on the other theme. - -| Control | What it does | -|--------|--------| -| **+** menu | **New Theme** copies the active theme; **Import…** reads a theme JSON file | -| **-** button | Deletes the selected theme. Custom themes only | -| **Gear** menu | **Duplicate** and **Export…** act on the active theme; **Uninstall** removes the selected registry theme | - -A built-in or registry theme is read-only, so the first change forks it. Changing a font makes "Name (Custom)" and edits that; **Duplicate**, **New Theme**, and the **Duplicate Theme** button on the Colors tab make "Name (Copy)" for you to edit. - -## Fonts - -The pickers list the monospaced families installed on your Mac; the preview renders a sample query in the theme's own editor colors. - -| Setting | Options | Default | Applies to | -|---------|---------|---------|------------| -| Editor Font | Family + size, 11 to 18 pt | System Mono, 13 pt | SQL editor and the JSON viewer's Text mode | -| Data Grid Font | Family + size, 10 to 18 pt | System Mono, 13 pt | Grid cells, the inspector, the cell popovers and the Compare row diff | - -## Colors - -Custom themes get color wells here. A built-in or registry theme shows a lock and a **Duplicate Theme** button instead. - -| Group | Controls | -|-------|----------| -| Editor | Background, text, cursor, current line, selection, line number, invisibles | -| Syntax Colors | Keyword, string, number, comment, NULL, operator, function, type | -| Data Grid | Background, text, alternate row, NULL value, bool true/false, row number, modified/inserted/deleted rows, deleted text | -| Interface | Window, control, and card backgrounds, border, primary/secondary/tertiary text, selection, hover | -| Status Colors | Success, warning, error, info | -| Badges | Badge background, primary key, auto increment | -| Sidebar | Background, text, selected item, hover, section header | -| Toolbar | Secondary and tertiary text | - -Interface, Sidebar, and Toolbar colors sit on macOS system colors until you override one, which is why an untouched theme follows your system accent color. So do the grid's background, text, alternate row, NULL value, and row number. Bool true and false show in the grid's text color until you set them. An overridden well grows a reset button that puts the system color back. - -## Theme files - -A theme is one JSON file. **Export…** in the gear menu writes every color the active theme sets, which is the shortest way to start a new one; **Import…** in the **+** menu reads one back into `~/Library/Application Support/TablePro/Themes/`. Themes published to the [plugin registry](/features/plugins#themes) install from **Settings > Plugins > Browse**, Themes category. - -| Key | Holds | -|-----|-------| -| `id`, `name`, `author` | Identity. An `id` that collides with a built-in, a registry theme, or one you already have is replaced on import | -| `version` | Format version, `1` | -| `appearance` | `light`, `dark`, or `auto`. Decides which slot lists the theme; `auto` lists in both | -| `editor` | Editor colors, with syntax colors nested under `syntax` | -| `dataGrid` | Grid colors | -| `ui` | Interface colors, with `status` and `badges` nested | -| `sidebar`, `toolbar` | Sidebar and toolbar colors | -| `fonts` | `editorFontFamily`, `editorFontSize`, `dataGridFontFamily`, `dataGridFontSize` | - -```json -{ - "id": "user.midnight", - "name": "Midnight", - "version": 1, - "appearance": "dark", - "author": "You", - "editor": { - "background": "#2E3440", - "text": "#D8DEE9", - "currentStatementHighlight": "#FFFFFF0F", - "syntax": { "keyword": "#81A1C1", "string": "#A3BE8C" } - }, - "fonts": { "editorFontFamily": "System Mono", "editorFontSize": 13 } -} -``` - -Colors are sRGB hex, `#RRGGBB` or `#RRGGBBAA` for a translucent overlay such as a selection band. A value that is neither renders as the system label color, which is how a typo shows up. - -Every key is optional. A group you leave out falls back to Default Light. A color left out of `ui`, `sidebar`, or `toolbar` is the macOS system color rather than a fixed value, and so is a `dataGrid` `background`, `text`, `alternateRow`, `nullValue`, or `rowNumber`. A missing `boolTrue` or `boolFalse` uses the grid's text color. - -## Connection colors - -Assign one with **Color** on the connection form's **Appearance** tab: None, Red, Orange, Yellow, Green, Blue, Purple, Pink, or Gray. With None, no color cue appears. Connection groups take a color the same way, from their right-click menu on the welcome window. - -In the workspace rail the color is a dot on the corner of the database icon, and it stays visible on the selected row. In the welcome window list, the connection switcher and the query history drawer, it is a dot beside the name. The color's name is in the connection's tooltip and its VoiceOver label. - -The database icon keeps its engine's own color everywhere, whichever color the connection carries. - - -Use red for production databases as a visual reminder to be careful with queries. - - - - Connection colors on rail dots and welcome list rows - Connection colors on rail dots and welcome list rows - diff --git a/docs/customization/data-settings.mdx b/docs/customization/data-settings.mdx deleted file mode 100644 index 0a22b62ab0..0000000000 --- a/docs/customization/data-settings.mdx +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: Data Settings -description: Data grid, pagination, query result cap, JSON viewer, and query history ---- - -Browsing a table pages 1,000 rows at a time, and a query that returns more than 10,000 rows stops there. Both numbers, and the rest of what the grid does before you touch anything, are on **Settings > Data**. - -The tab is app-wide, so these values apply to every connection you open; filters, column widths, and the sort you clicked are remembered per table instead. Grid fonts are per-theme, on the theme's Fonts tab under [Appearance](/customization/appearance). - - - Data settings tab - Data settings tab - - -## Data Grid - -| Setting | Default | Notes | -|---------|---------|-------| -| Row height | Normal | Compact, Normal, Comfortable, Spacious | -| Date format | ISO 8601 | ISO 8601, ISO Date, US Long, US Short, EU Long, EU Short. Sub-second digits and a UTC offset stored with the value are printed wherever the format renders a time, as the server wrote them: `2024-12-31 23:59:59.123456+07`. A value stored without an offset, such as a MySQL `DATETIME`, never gains one, and a fraction of nothing but zeros is not printed | -| NULL display | `NULL` | Text shown for NULL cells, up to 20 characters | -| Show alternate row backgrounds | On | | -| Show row numbers | On | | -| Auto-show inspector on row select | Off | Opens the inspector on selecting a row, in a query result as well as a table. A pane you left on the assistant stays on it | -| Smart value detection | On | Reads id-like binary columns as UUIDs, timestamp-named integer columns as dates, and binary columns holding UTF-8 as text. Override per column with Display As, see [Cell and Row Viewers](/features/json-viewer) | -| Default row sort | No sorting (engine order) | Or Primary key, First column. Applied when a table first opens; clicking a column header overrides it | -| Sort direction | Ascending | Or Descending. Sets the default row sort's direction and which way the first click on a column header sorts | - -## Pagination - -| Setting | Default | Options | -|---------|---------|---------| -| Default page size | 1,000 rows | 100, 500, 1,000, 5,000, 10,000 | -| Count rows if estimate less than | 100,000 | 1,000; 10,000; 100,000; 1,000,000; Always count | - -Tables with more estimated rows than the threshold show an approximate count instead of running a slow `COUNT(*)`. - -## Query result row cap - -| Setting | Default | Options | -|---------|---------|---------| -| Truncate query results | On | | -| Row cap | 10,000 | 100; 1,000; 5,000; 10,000; 50,000; 100,000; 500,000 | - -The cap limits the rows read back, not the statement: your SQL reaches the server exactly as you typed it, and a query carrying its own `LIMIT` is untouched. What a capped result offers instead is in [Query Results](/features/query-results#the-row-cap). - -## JSON Viewer - -| Setting | Default | Options | -|---------|---------|---------| -| Default view | Text | Text, Tree | - -Switching modes inside the viewer writes the choice back to this setting. See [JSON Viewer](/features/json-viewer). - -## Query History - -| Setting | Default | Description | -|---------|---------|-------------| -| **Maximum entries** | 10,000 | 100 to 10,000, or Unlimited | -| **Keep entries for** | 90 days | 7 days to 1 year, or Forever | -| **Auto cleanup** | On | Applies the two limits in this table. Turn it off and nothing is pruned. | -| **Clear History…** | - | Wipe all history, for every connection, after confirmation. Also clears the saved changes below. | - -To clear only what you are looking at, use the trash button in the history drawer instead: it respects the drawer's connection scope and date range. - -## Data Rewind - -Starter license. What a row held before a save is kept here, so [Restore Previous Values](/features/change-tracking#restoring-a-save) has something to put back. - -| Setting | Default | Description | -|---------|---------|-------------| -| **Keep saved changes for restoring** | On | Off stops the capture. Saves made while it is off cannot be restored later. | -| **Clear Saved Changes…** | - | Delete everything kept, for every connection, after confirmation | - -Values are stored encrypted on this Mac, for 7 days, and never leave it. A save is dropped early when it is over 32 MB in total, or when its table already has 50 newer ones. diff --git a/docs/customization/editor-settings.mdx b/docs/customization/editor-settings.mdx deleted file mode 100644 index 8896100e26..0000000000 --- a/docs/customization/editor-settings.mdx +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Editor Settings -description: SQL editor, Vim mode, line numbers, indentation, and query parameter settings ---- - -**Settings > Editor** covers typing and how SQL is displayed. Grid, pagination, result cap and JSON viewer settings are on the [Data](/customization/data-settings) tab instead. - -Editor fonts are per-theme. Edit them on the theme's Fonts tab under [Appearance](/customization/appearance). - - - Editor settings - Editor settings - - -## SQL editor - -| Setting | Default | Notes | -|---------|---------|-------| -| Show line numbers | On | | -| Highlight current line | On | | -| Highlight current statement | On | A faint band behind the statement the cursor is in. See [Statement markers](/features/sql-editor#statement-markers) | -| Word wrap | Off | Off means long lines scroll horizontally | -| Code folding | On | Shows the fold ribbon in the gutter. See [Code Folding](/features/code-folding) | -| Run button beside each statement | On | Gutter run buttons, revealed when the pointer is over the gutter | -| Tab width | 4 spaces | 2, 4, or 8 | -| Keyword case | Match what I type, otherwise UPPERCASE | The case completed keywords and functions take, and the case **Format SQL** produces. `UPPERCASE` and `lowercase` also rewrite keywords as you type them, leaving strings, comments and quoted identifiers untouched. See [Keyword case](/features/autocomplete#keyword-case) | -| Query parameters (`:name` syntax) | On | Detects `:name` placeholders and shows the parameter panel. See [Query Parameters](/features/query-parameters) | -| Vim mode | Off | Modal editing in the SQL editor. See [Vim Mode](/features/vim-mode) | - -Autocomplete is always on; see [Autocomplete](/features/autocomplete). Editor shortcuts are listed and customized on [Keyboard Shortcuts](/features/keyboard-shortcuts). diff --git a/docs/customization/general-settings.mdx b/docs/customization/general-settings.mdx deleted file mode 100644 index a4c7e263bf..0000000000 --- a/docs/customization/general-settings.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: General Settings -description: Language, startup behavior, tabs, sidebar layout, connections, query timeout, the command line tool, updates, and reset ---- - -Most of this tab takes effect the moment you click. Language waits for a restart, and the query timeout reaches a connection only when that connection opens. - - - General settings tab - General settings tab - - -## Language - -System (the default), English, Tiếng Việt, 简体中文, 繁體中文, 한국어, Türkçe, Français. The tab tells you to restart once you pick a different one. - -## Startup - -**When TablePro starts** takes **Reopen Last Session** (the default) or **Show Welcome Screen**. What a reopened session brings back, and what it does when a server is down, is on [Tabs](/features/tabs#what-survives-a-restart). - -## Tabs and sidebar - -| Setting | Default | What it does | -|---------|---------|--------------| -| **Enable preview tabs** | On | Single-click opens a temporary tab that the next click replaces; double-clicking the table, the tab, or editing it makes it permanent | -| **Show connections** | On | The [connections strip](/features/workspace-rail) on the window's leading edge | -| **Show recent tables** | Off | A [Recent section](/features/favorites#recent-tables) at the top of the sidebar | -| **Show object icons** | On | A type icon before each object name. Off gives a plain list of names | -| **Show object comments** | On | Database object comments beside tables in the sidebar and in grid column headers | -| **Show partitions** | On | A partitioned table's partitions listed under it, and how many it holds beside its name | -| **Show system databases and schemas** | Off | `mysql`, `information_schema`, `master` and the other system databases and schemas in the sidebar tree and its database and schema filters. The database switcher lists them either way | -| **Row size** | Match System | Match System follows **Sidebar icon size** in **System Settings > Appearance**. Small, Medium, or Large overrides it to fit more objects on screen | -| **Default layout for new connections** | List | List or Tree, on servers with a database tree. Switch the connection you are in from the **View** menu | - -Icons, comments, system databases and schemas, partitions, and row size are also under **View Options**, the button beside the sidebar's filter field. Right-clicking the empty space below the object list reaches the same options. - -## Connections - -**Show recent connections** is on out of the box and puts a [Recent section](/connections#organize-connections) in the welcome window and **Switch Connection**. Turning it off hides the section and keeps the history. **Clear Recent**, on the **Recent connection history** row, forgets it, which also starts sorting by **Last Connected** over. The switch and the history both stay on this Mac. - -**Check connections** sets how often each open connection is checked for a dropped session. The choices, and what a failed check starts, are under [Connection health](/connections/connection-form#connection-health). - -## Query timeout - -How many seconds a query runs before it is cancelled. The default is 60; the picker offers 10 to 600 seconds and **No limit**. The value is read when a connection opens, so a change reaches an open connection only after you reconnect. - -A connection can override this value under **Options > Query timeout**. Leave that field empty to inherit this setting, or enter `0` for no limit. The same section has a separate connect timeout for tunnels, proxies and database sign-in; it does not change how long a query can run. - -Where the engine can enforce it, the timeout becomes a server-side setting: `statement_timeout` on PostgreSQL, `max_execution_time` on MySQL and ClickHouse, `max_statement_time` on MariaDB, `ob_query_timeout` on OceanBase. On SQLite it bounds how long a statement waits for a locked database instead. Drivers that talk HTTP (BigQuery, Spanner, Cloudflare D1, LibSQL, Etcd, DynamoDB, Elasticsearch, Typesense, and ClickHouse) bound the request at the timeout plus 30 seconds, and Oracle enforces it in the client: the connection is closed to unblock the call, and the next query reconnects and restores your current schema. SAP HANA ends the statement with the same `ALTER SYSTEM CANCEL SESSION` that **Stop** sends. - -Older servers have neither: MySQL before 5.7.8, MariaDB before 10.1.1. A statement that passes the timeout there is stopped with `KILL QUERY` from a second connection, which reaches a `SELECT` on MySQL and any statement at all on MariaDB. A server behind a proxy that reports a version it does not have still gets the right one of the two. - -**No limit** still caps an HTTP request at one hour, because the transport needs a ceiling. - -## Command line tool - -**Install** writes a `tablepro` command to `/usr/local/bin` that opens database URLs in the app. When it cannot write there, the section prints the command to run in Terminal yourself. See [Terminal](/integrations/terminal). - -## Trusted links - -External links you chose to always allow, each with **Forget**, plus **Forget All**. Only a database on this machine can be trusted; a link to a remote host asks every time. - -## Software update - -Both switches are on out of the box, and together they are the whole of the decision: the first one stops the daily check, the second stops the install. - -**Automatically check for updates** runs the check in the background once a day. Turn it off and nothing reaches the network unless you press the button. - -**Download and install updates automatically** fetches the new version in the background and swaps it in the next time TablePro quits, with no dialog. Turn it off to be asked first. With the check switched off the switch is dimmed and installs never run. - -**Last checked** sits beside **Check for Updates…**, which checks now and is also **TablePro > Check for Updates…** in the menu bar. A check you press ignores the schedule and any staged rollout, so it always offers the newest build. - -When a background check finds something, nothing jumps in front of you. The menu item and the button both change to **Update Available…** and wait until you come back. Updates arrive through [Sparkle](https://sparkle-project.org/) and are signature-checked before they install. - -**What's New** opens the highlights of the version you are running, with a button to the full changelog. It is also **Help > What's New**. - -An administrator can pin both switches and the interval across a fleet. See [Managed updates](/security/managed-updates). - -## Privacy - -**Share usage data** is on. It sends one heartbeat a day with no query text, hostname, or account in it. The full payload, field by field, is on [Privacy](/security/privacy#usage-heartbeat). - -## Reset - -**Reset All Settings to Defaults** is at the bottom of the tab. What it restores and what it leaves alone is under [Where settings live](/customization/settings#where-settings-live). diff --git a/docs/customization/notifications.mdx b/docs/customization/notifications.mdx deleted file mode 100644 index a0834c2fc6..0000000000 --- a/docs/customization/notifications.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Notifications -description: Which finished operations notify you, how long work has to run first, and what the notification carries ---- - -You only get told when the result is somewhere you are not. That means TablePro behind another window or in another Space, the window minimized, or the tab that ran the work not the one on screen. Work you cancelled yourself never notifies, and neither does anything that finished faster than the threshold. - - - Notifications settings tab - Notifications settings tab - - -**Settings > Notifications** changes any of it. - -| Setting | Default | Covers | -| --- | --- | --- | -| **Notify when long-running work finishes** | On | The master switch for every kind in this table | -| **Only after** | 20 seconds | How long work has to run to count: 5, 10, 20 or 30 seconds, or 1, 2, 5 or 10 minutes | -| **Queries** | On | Editor queries, including Explain | -| **Statement batches** | On | Execute All Statements, as one notification for the batch | -| **Row edits** | On | Saving edited rows | -| **Structure changes** | On | Applying table structure changes | -| **Imports** | On | File imports | -| **Exports** | On | Exports, including streaming exports | -| **Object copies** | On | Copy To and Duplicate Database | -| **Backups** | On | Database dumps | -| **Fetch all rows** | On | Loading every row of a truncated result | -| **AI and MCP queries** | On | Queries an AI assistant or MCP client runs | -| **AppleScript queries** | On | Queries [a script](/developers/applescript) runs | - -Keep **Only after** below the [query timeout](/customization/general-settings#query-timeout), or a query that times out finishes before its notification is due. - -## What a notification carries - -The connection name is the title, the database is the subtitle, and the body is the outcome with a duration: "1,204 rows in 3m 12s", or "Failed after 2m 45s" with the reason. A failure adds a **Copy Error** button, and anything that wrote a file adds **Show in Finder**. Clicking the notification brings TablePro forward on the tab that ran the work. - -Banners group by connection. Re-running the same kind of work in the same tab replaces that tab's earlier banner instead of stacking a second one. - -## The tab dot - -A tab whose work finished while you were looking elsewhere carries a dot beside its name until you select it. That dot is not a notification: it appears whatever the settings say, and whether or not you ever granted permission. - -## Permission - -macOS asks the first time there is actually something to tell you, not at launch. Refuse, or turn TablePro off in **System Settings > Notifications**, and this tab says so with a button that opens the right pane; TablePro bounces its Dock icon in place of the banner. Sound and alert style are per-app System Settings options, so this tab does not repeat them. diff --git a/docs/customization/settings.mdx b/docs/customization/settings.mdx deleted file mode 100644 index 528eacf169..0000000000 --- a/docs/customization/settings.mdx +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: Settings -description: Every settings tab, its controls, and their defaults ---- - -The settings window is app-wide: a value on any of these twelve tabs holds for every connection you open. `Cmd+,` opens it; what belongs to one connection is in that connection's form instead. - - - Settings window - Settings window - - - - Language, startup, tabs, sidebar, connections, query timeout, updates, reset. - Appearance mode, themes, per-theme fonts and colors. - SQL editor, Vim mode, line numbers, query parameters. - Data grid, pagination, result cap, JSON viewer, and query history. - Custom shortcuts. - Credential profiles and SSH servers, and which connections use each. - Which finished operations notify you when TablePro is in the background. - Providers, inline suggestions, context, slash commands. - MCP server, tokens, clients, activity. - Manage drivers, exporters, and themes. - iCloud sync and what it carries. - Activation, devices, team. - - -## Stock values - -What a fresh install ships with, so one glance down this column says what you changed. Each tab page explains what a setting does and which other values it takes. - -| Tab | Setting | Default | -|-----|---------|---------| -| General | Language | System | -| General | When TablePro starts | Reopen Last Session | -| General | Enable preview tabs | On | -| General | When tabs stop fitting | Scroll them | -| General | Show connections | On | -| General | Show recent tables | Off | -| General | Show object icons | On | -| General | Show object comments | On | -| General | Show partitions | On | -| General | Row size | Match System | -| General | Default layout for new connections | List | -| General | Show recent connections | On | -| General | Check connections | Every 30 seconds | -| General | Query timeout | 60 seconds | -| General | Automatically check for updates | On | -| General | Download and install updates automatically | On | -| General | Share usage data | On | -| Appearance | Appearance | Auto | -| Appearance | Light theme | Default Light | -| Appearance | Dark theme | Default Dark | -| Appearance | Editor font | System Mono, 13 pt | -| Appearance | Data grid font | System Mono, 13 pt | -| Editor | Show line numbers | On | -| Editor | Highlight current line | On | -| Editor | Highlight current statement | On | -| Editor | Word wrap | Off | -| Editor | Code folding | On | -| Editor | Run button beside each statement | On | -| Editor | Show invisible characters | On | -| Editor | Tab width | 4 spaces | -| Editor | Keyword case | Match what I type, otherwise UPPERCASE | -| Editor | Query parameters | On | -| Editor | Vim mode | Off | -| Data | Row height | Normal | -| Data | Date format | ISO 8601 | -| Data | NULL display | `NULL` | -| Data | Show alternate row backgrounds | On | -| Data | Show row numbers | On | -| Data | Auto-show inspector on row select | Off | -| Data | Smart value detection | On | -| Data | Default row sort | No sorting (engine order) | -| Data | Sort direction | Ascending | -| Data | Default page size | 1,000 rows | -| Data | Count rows if estimate less than | 100,000 | -| Data | Truncate query results | On | -| Data | Row cap | 10,000 | -| Data | JSON viewer default view | Text | -| Data | Maximum history entries | 10,000 | -| Data | Keep history entries for | 90 days | -| Data | Auto cleanup | On | -| Data | Keep saved changes for restoring | On | -| Notifications | Notify when long-running work finishes | On | -| Notifications | Only after | 20 seconds | -| Notifications | Every operation kind | On | -| AI | Enable AI Features | On | -| AI | Enable inline suggestions while typing | Off | -| AI | Debounce | 500 ms | -| AI | Limit tool calls per reply | On | -| AI | Tool call limit | 25 | -| AI | Include database schema | On | -| AI | Include current query | On | -| AI | Include query results | Off | -| AI | Max schema tables | 20 | -| AI | Connection policy | Ask Each Time | -| MCP | Enable MCP Server | Off | -| MCP | Port | 23508 | -| MCP | Default row limit | 500 | -| MCP | Maximum row limit | 10,000 | -| MCP | Query timeout | 30 seconds | -| MCP | Log MCP queries in history | On | -| MCP | Require authentication | On | -| MCP | Approval | Ask Once for Each Connection | -| MCP | Allow apps to list connections hidden from AI | Off | -| Sync | Sync this Mac with iCloud | Off | -| Sync | Passwords (keychain sync) | Off | - -Three tabs are not in the table. Stock keyboard chords are on [Keyboard Shortcuts](/features/keyboard-shortcuts), and neither the Plugins nor the License tab holds a value of its own. - -## Where settings live - -The file is `~/Library/Preferences/com.TablePro.plist`. **Reset All Settings to Defaults** on the General tab puts every tab back to its stock value after a confirmation, and that includes emptying the AI provider list. Saved connections, query history, and MCP tokens survive it. - -Two settings have no control in the window: - -| Setting | Command | -|---------|---------| -| Custom plugin registry | `defaults write com.TablePro com.TablePro.customRegistryURL ` | -| Clear custom registry | `defaults delete com.TablePro com.TablePro.customRegistryURL` | - -See [Plugin Registry](/development/plugin-registry#custom-registry-url) for what a private registry has to serve. diff --git a/docs/databases/beancount.mdx b/docs/databases/beancount.mdx deleted file mode 100644 index 0f3553517c..0000000000 --- a/docs/databases/beancount.mdx +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: Beancount -description: Backend requirements, the table layout, source locations, includes, and BQL support ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Install `rledger` or Python Beancount before you connect. The plugin shells out to one of those two and projects the answer into the read-only SQL tables below. - - - -## Backend requirements - -The compatibility fixture exercises `rledger` 0.22.0 and 0.24.0, and Python Beancount 3.2.3. Other versions may work, but are not part of that tested contract. Install `rledger` if you are choosing: it wins when both are present, it is the only one that fills `diagnostics`, and BQL runs through it and nothing else. - - -```bash rledger -# build or download rustledger, put it on PATH, then: -rledger --version -``` - -```bash Python Beancount -pip3 install beancount -python3 -c "import beancount" -``` - - -An app launched from Finder does not inherit your shell's `PATH`, so a backend that answers in Terminal can still be invisible. Name it outright, then relaunch: - -```bash -launchctl setenv TABLEPRO_RUSTLEDGER_BINARY /opt/homebrew/bin/rledger -launchctl setenv TABLEPRO_BEANCOUNT_PYTHON /opt/homebrew/bin/python3 -``` - -`TABLEPRO_BEANCOUNT_BACKEND` overrides which one wins. It takes `rledger` or `python`. - -After connecting, TablePro's database version display names the backend it selected and the version that executable reports. - -## Ledger plugins - -A `plugin "..."` directive names Python code that Beancount imports and runs while it loads the ledger. The plugins that ship with Beancount itself, everything under `beancount.`, always run. Anything else is third-party code, so the Python backend skips it unless you turn on **Run Ledger Plugins** on the connection's Options tab. - -The setting belongs to one connection, so trusting your own ledger does not trust the next ledger someone sends you. Skipped plugin names appear in `diagnostics` with phase `security`. If the ledger needs one of them to load at all, the connection fails and the error names the plugin it skipped. - -## Quick setup - - - - Click **New Connection…** and choose **Beancount**. - - - Click **Browse…** and select a `.beancount` file. No host, no port, no password, no Database field: the ledger path is the whole connection. - - - Click **Save & Connect**. - - - - - Beancount ledger open in TablePro with projected tables in the sidebar - Beancount ledger open in TablePro with projected tables in the sidebar - - -## Connection URL - -```text -beancount:///path/to/main.beancount -``` - -Three slashes: the path is absolute. See [Connection URL Reference](/connections/urls). - -## Tables - -| Table | Contents | -|-------|----------| -| `transactions` | Date, flag, payee, narration, source location. Includes transactions with no postings | -| `postings` | Account, amount, commodity, posting flag, resolved cost and price details, source location | -| `accounts` | Opened accounts, declared currencies, and booking methods | -| `prices` | Price directives | -| `balances` | Computed account balances by commodity | -| `balance_assertions` | Expected balance, explicit tolerance, and the booked difference | -| `commodities` | Commodity directives | -| `documents` | Document directives, their attached paths, tags, and links | -| `notes` | Account notes, tags, and links | -| `events` | Event type and description directives | -| `pads` | Pad directives, their target and source accounts, and source locations | -| `closes` | Close directives | -| `queries` | Named query directives and their source locations | -| `custom` | Custom directive type and source location | -| `custom_values` | Ordered, typed values belonging to each custom directive | -| `transaction_metadata`, `posting_metadata` | Metadata key/value pairs | -| `transaction_tags`, `transaction_links` | Tags and links | -| `directives` | Every dated directive that is not a transaction, with its source location | -| `directive_metadata` | User metadata on those directives | -| `diagnostics` | Validation output from `rledger check` | -| `source_files` | Parsed ledger and include files | - -`postings.transaction_id` points at `transactions.id`, and so do `transaction_metadata`, `transaction_tags`, and `transaction_links`. `posting_metadata.posting_id` points at `postings.id`, `custom_values.custom_id` points at `custom.id`, and `directive_metadata.directive_id` points at `directives.id`. The rest join by name: an account column against `accounts.name`, a `source_file` column against `source_files.path`. - -```sql -SELECT t.date, t.payee, p.account, p.amount, p.commodity -FROM postings p -JOIN transactions t ON t.id = p.transaction_id -WHERE p.account LIKE 'Expenses:%' -ORDER BY t.date DESC; -``` - -Amounts arrive booked, so thousands separators, arithmetic, cost (`{}`) and price (`@`/`@@`) annotations are resolved before they reach a cell. Metadata values are text, and booleans keep their Beancount spelling of `TRUE` and `FALSE`. - -Posting costs use `cost_number`, `cost_currency`, `cost_date`, and `cost_label`. Resolved inline prices use `price_number` and `price_currency`; `flag` is the posting's own flag, independent of the transaction flag. - -`custom_values.value_type` is `string`, `date`, `boolean`, `amount`, `account`, or `number`. `value` is the complete readable value; amounts also split into `number` and `currency`, and standalone numbers repeat in `number` so exports do not have to parse the display text. - -`balance_assertions.difference_amount` is the booked balance immediately before the assertion date minus the expected amount. Its currency is in `difference_currency`; an explicit `~` value is in `tolerance`. - -Every ledger file, glob directory, and referenced document is watched by modification date, size, and directory listing. Change one and the next query rebuilds the projection first, so an edit in your text editor lands without reconnecting. - -## Source locations - -`transactions`, `postings`, `pads`, `queries`, and `custom` carry `source_file`, `line`, and `source_location`, the last formatted `path:line` so it copies, filters, and exports as it stands. A posting points at its own line, not at its transaction's. - -`directives` carries the source location of each non-transaction directive, so metadata joined through `directive_id` reaches the line the directive is on. That is how two same-day directives of one type stay apart. Transaction and posting metadata stay in their dedicated tables. - -The backends disagree about generated entries. For the transaction a `pad` inserts, `rledger` leaves these columns empty and Python Beancount points them at the `pad`. - -A pad generated by a Beancount plugin carries a synthetic filename and no line number, so `source_file` holds that name and `line` and `source_location` are null. - -## Diagnostics - -`diagnostics` carries `source_file`, `line`, `source_location`, `column_number`, `end_line`, `end_column`, `severity`, `phase`, `code`, and `message`. A ledger opened through `rledger` is projected even when validation fails, so whatever the projection skipped is readable here. The Python backend records the ledger plugins it skipped. - -Rows the projection wrote itself carry `phase` `projection`, including any pad whose accounts it could not resolve. The rest come from `rledger check` and carry its own phase names. - -## Includes - -`include` directives are followed, both literal paths and glob patterns such as `include "imports/*.beancount"` and `include "imports/**/*.beancount"`. - -## BQL - -Prefix a query with `BQL:` to send it to `rledger` instead of the projection. - -```sql -BQL: SELECT account FROM accounts ORDER BY account -``` - -Table browsing, row counts, and pagination work on a BQL result. SQL parameters do not. - -BQL values read as Beancount writes them: an amount as `19.43 USD`, a position held at cost as `2 AAPL {150.00 USD}`, tags as `trip, food`, and a boolean as `TRUE`. The JSON that `rledger` returns carries no lot date or label, so a position shows neither. - -## Limitations - -- No writes. The connection runs at [Safe Mode Read-Only](/features/safe-mode#connections-that-are-always-read-only), and INSERT, UPDATE, DELETE, and every form of schema editing are rejected. Edit the ledger in a text editor; the next query picks the change up. -- No import, SSH, SSL, or ledger switching. One connection is one ledger file. -- BQL needs `rledger` even when the ledger opened on the Python backend. The query is refused. Install `rledger`, or drop the `BQL:` prefix and query the projected tables. -- Directives outside those tables are not projected. They stay in the source files. - -## Troubleshooting - -**Beancount needs rledger or Python Beancount**: Neither was found on `PATH`, in `/opt/homebrew/bin`, or in `/usr/local/bin`. Set `TABLEPRO_RUSTLEDGER_BINARY` or `TABLEPRO_BEANCOUNT_PYTHON` with `launchctl setenv`, then relaunch. - -**TABLEPRO_BEANCOUNT_PYTHON points to a Python executable that cannot import beancount**: Run `pip3 install beancount` with that exact interpreter, or point the variable at one that already has the package. - -**BQL queries need rledger**: Install rustledger so `rledger` lands on `PATH` or in Homebrew, or point `TABLEPRO_RUSTLEDGER_BINARY` at it. - -**File does not exist**: The ledger moved or was renamed. Re-pick it with **Browse…** in the connection form. - -**Beancount include cycle detected**: Two ledger files include each other. Break the loop in the source files. diff --git a/docs/databases/bigquery.mdx b/docs/databases/bigquery.mdx deleted file mode 100644 index 4152dd4107..0000000000 --- a/docs/databases/bigquery.mdx +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Google BigQuery -description: Connect to Google BigQuery with Service Account, ADC, or OAuth auth ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Use Application Default Credentials if `gcloud` is already on the machine. The other two methods are for machines where it is not: a service account key file, or a browser sign-in against an OAuth client you create. Everything travels over the REST API, so there is no host, port, or tunnel. - - - -## Quick setup - -Click **New Connection…**, select **BigQuery**, pick an auth method, enter your Project ID, and click **Save & Connect**. - -## Connection settings - -| Field | Required | Notes | -|-------|----------|-------| -| **Auth Method** | Yes | Service Account Key, Application Default Credentials, or Google Account (OAuth) | -| **Project ID** | Yes | The ID (`my-project-123456`), not the name or number | -| **Service Account Key** | Service account only | A `.json` key file path, or the raw JSON | -| **OAuth Client ID**, **OAuth Client Secret** | OAuth only | From GCP Console > APIs & Services > Credentials | -| **OAuth Refresh Token** | No | OAuth only. Tried before the token saved by sign-in | -| **Location** | No | Processing location (`US`, `EU`, `us-central1`), sent with every job | -| **Max Bytes Billed** | No | Options tab. A query over the cap fails instead of billing | - -There is no Database field and no URL scheme. Datasets stand in for databases: the sidebar lists them as expandable nodes, `Cmd+K` and **Database > Open Database…** move between them, and **Database > New Database…** runs `CREATE SCHEMA`. Switching does not move tabs you already have open; each stays on its own dataset. See [Tabs](/features/tabs#where-a-tab-points). - -## Authentication - -### Application default credentials - -```bash -gcloud auth application-default login --project=my-project -``` - -The driver reads `~/.config/gcloud/application_default_credentials.json`, accepting the `authorized_user`, `service_account`, and `impersonated_service_account` types. The form then needs only a Project ID. - -### Service account key - -Point **Service Account Key** at a `.json` key file from Google Cloud Console (IAM > Service Accounts > Keys), or paste the JSON straight into the field. - -### Google account (OAuth 2.0) - - - - In [Google Cloud Console](https://console.cloud.google.com/), under APIs & Services > Credentials, click Create Credentials > OAuth client ID and pick Desktop app. - - - Paste the Client ID and Client Secret into the connection form. - - - The first connect stops at **Google Sign-In Required**. Click **Sign In** and approve access in the browser; the connection retries when sign-in finishes. The browser step times out after two minutes. - - - -The refresh token from that sign-in is kept in the macOS keychain, one per OAuth client, so later connects and reconnects need no browser. A background reconnect never opens one. When Google revokes the token, the next connect asks you to sign in again. - -One OAuth client signs in one Google account, shared by every connection that uses it. To use a second account, create a second OAuth client, or paste that account's refresh token into **OAuth Refresh Token**. - -## Cost - -Every query is billed on bytes scanned, so there are three places to control it. - -- Before running, pick **Dry Run (Cost)** from the Explain dropdown. It asks BigQuery what the query would scan without executing it. -- After running, the status bar shows bytes processed, bytes billed, and an estimate at the on-demand rate of $6.25 per TB: `Processed: 1.5 MB | Billed: 10 MB | ~$0.0001`. A cache hit is marked `(cached)`. -- As a hard ceiling, set **Max Bytes Billed**: BigQuery rejects a job that would exceed it. - - - Status bar showing bytes processed, bytes billed, and estimated query cost - Status bar showing bytes processed, bytes billed, and estimated query cost - - -## Querying and editing - -Completion, quoting, and formatting follow GoogleSQL: backticks around table names, single quotes around strings. `STRUCT` and `ARRAY` columns display as JSON. Cells, inserts, and deletes go through the grid. The Structure tab is read-only, but DDL runs from the editor: `CREATE SCHEMA`, `ALTER TABLE … ADD COLUMN` and `DROP COLUMN`, `CREATE OR REPLACE VIEW`. The DDL tab reads definitions from `INFORMATION_SCHEMA.TABLES`; clustering and partitioning appear under Indexes. - -Minimum IAM roles: `roles/bigquery.user` to run queries, `roles/bigquery.dataViewer` to read, `roles/bigquery.dataEditor` to write. - -## Limitations - -- Grid UPDATE and DELETE match a row on its declared primary key. A table without one is matched on every column except `ARRAY`, `JSON` and `GEOGRAPHY` ones (and `STRUCT` columns that contain them), so identical rows change together. A table whose columns are all of those types cannot be edited in the grid. Declare `PRIMARY KEY (…) NOT ENFORCED` to match one row exactly. -- A partitioned table needs a partition filter for UPDATE and DELETE, or the statement is rejected. Write it in the editor. -- No transactions, no streaming inserts, no SSH tunnel. -- Deep pagination rescans from the start of the table. Filter rather than paging far in. -- A job is polled until the [query timeout](/customization/general-settings#query-timeout), then cancelled. With **No limit**, it is polled until it finishes. - -## Troubleshooting - -**Application default credentials not found. Run gcloud auth application-default login.**: No ADC file was found. Run the command, set `GOOGLE_APPLICATION_CREDENTIALS` to a key file, or switch the connection to a service account key. - -**Permission denied**: Authenticated but unauthorized. Grant `roles/bigquery.user` on the project and a data role on the dataset. - -**Project not found**: Enter the Project ID, not the display name and not the numeric project number. - -**No tables after connect**: Tables load when you expand the dataset node. An empty dataset stays empty; open another. diff --git a/docs/databases/cassandra.mdx b/docs/databases/cassandra.mdx deleted file mode 100644 index 79e664ba09..0000000000 --- a/docs/databases/cassandra.mdx +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Cassandra / ScyllaDB -description: Connect to Cassandra and ScyllaDB clusters, browse keyspaces, and run CQL queries ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Cassandra 3.0 is the floor here, and 2.x will not connect. The driver reads its schema from `system_schema`, which 2.x does not have. ScyllaDB is a separate entry in the database type list but the same driver, over the same CQL native protocol. - -## Quick setup - -Click **New Connection…**, select **Cassandra** or **ScyllaDB**, enter host, port, and credentials, and connect. - - - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `localhost` | CQL contact point | -| **Port** | `9042` | CQL native port | -| **Username** | - | Only if the cluster has an authenticator configured | -| **Password** | - | | - -There is no Keyspace field. Pick one in the sidebar after connecting, or put it in the URL path and import that URL. Later switches happen in place, with no reconnect, and the system keyspaces are filtered out of the list. Tabs already open are bound to their own keyspace and do not follow. See [Tabs](/features/tabs#where-a-tab-points). - -There is no consistency level field either. The driver never sets one, so every statement runs at the DataStax C driver's default of `LOCAL_ONE`. - -## Connection URLs - -```text -cassandra://user:password@host:9042/keyspace -scylladb://user:password@host:9042/keyspace -``` - -`cql://` and `scylla://` work too. See [Connection URL Reference](/connections/urls). - -## Amazon Keyspaces - -Keyspaces authenticates with SigV4, not a password, so set **Authentication** to an AWS IAM mode and leave Username and Password empty. - - - - Host `cassandra.{region}.amazonaws.com`, port `9142`. - - - Access Key, Profile, or SSO. Profiles resolve from `~/.aws/config` and `~/.aws/credentials`, including `credential_process` and assumed roles. - - - Keyspaces refuses a plaintext connection. See [AWS IAM Authentication](/connections/aws-iam). - - - -## Browsing and editing - - - Cassandra keyspace and tables in the sidebar with table data in the grid - Cassandra keyspace and tables in the sidebar with table data in the grid - - -The structure view gives each column's name, CQL type, a primary key flag set for partition key and clustering columns alike, and the table's secondary indexes. Structure editing adds and drops columns; every other schema change goes in the CQL editor. In the grid, map, set, list, and tuple render as collections, a user-defined type as `{field: value}`, a `duration` as `1d2h30m`, a float `vector` as a list of numbers, and blob as hex. - -CQL has no `OFFSET`, so a page is reached by reading forward from the first row. **Next** and **Previous** over pages already shown take one read each. Jumping straight to page 200 reads the 199 pages before it first. Rows arrive in token order, and a column header does not sort them: CQL orders rows only by clustering columns inside one partition. A result in the CQL editor sorts the rows it holds. - -Neither a table's total nor a filtered total is counted on open. **Count Exactly** runs `SELECT COUNT(*)`, which reads every partition whatever `LIMIT` it carries, so on a large table it can hit the 30 second request timeout. - -### Filters - -A filter adds `ALLOW FILTERING`, so the server reads every partition to answer it unless the filter pins the whole partition key. - -| Operator | Result | -|---|---| -| **equals**, **greater than**, **greater or equal**, **less than**, **less or equal**, **in list**, **between** | Runs on any column | -| **contains**, **starts with**, **ends with** | Sends `LIKE`. ScyllaDB takes it on any column; Cassandra only on a column with a SASI or SAI index | -| **not equals**, **not contains**, **is NULL**, **is not NULL**, **is empty**, **is not empty**, **not in list**, **matches regex** | Refused with a message naming the operator. CQL has no form for them | - -The filter bar offers **Match all** only, because CQL has no `OR`, and **Search All Rows** does not appear in the find bar. A raw row goes into the `WHERE` clause as written, joined to the others with `AND`. - -### Editing - -An edited value is sent as its column's type, so it has to read as one: a number for `int` or `decimal`, a UUID for `uuid`, `0x…` for `blob`. A `timestamp` takes the form the grid shows, the form cqlsh prints, a date alone, or milliseconds since 1970, and one with no zone is read as UTC. A value that does not parse is refused and nothing in the save is written. `list`, `set`, `map`, `tuple`, user-defined types and `duration` cannot be edited in the grid: write an `UPDATE` in the CQL editor. - -Each row in a save is written by its own statement, named by its whole primary key, so deleting several rows sends one `DELETE` each. **Add Row** writes `INSERT … IF NOT EXISTS`: a new row whose key is already taken is refused rather than written over the row that holds it. A primary key column cannot be edited in place. Delete the row and add it again with the new key. - -Results in the CQL editor are read-only. Open the table from the sidebar to edit its rows. - -## SSL/TLS - -The default is **Disabled**, and there is no opportunistic TLS here, so **Preferred** behaves exactly like **Required**. - -| Mode | Verifies | -|------|----------| -| **Preferred**, **Required** | Nothing. Encryption only | -| **Verify CA** | The chain, against the PEM file you supply | -| **Verify Identity** | The chain and the hostname | - -Both verifying modes need a **CA Certificate** path, and refuse to connect without one rather than falling back to the system roots. For mutual TLS set the client certificate and key paths, with the key's passphrase in **Key Passphrase**. See [SSL/TLS](/connections/ssl). - -## Limitations - -- A page far into a large table is slow, because every row before it is read first. Filter the table down, or query it in the CQL editor with a `WHERE` on the partition key. -- Counter columns cannot be set with a plain `UPDATE`. The grid will edit one and the server rejects the statement it generates. Use `UPDATE … SET c = c + 1` in the CQL editor. -- Materialized views and UDTs do not appear in the sidebar. Query `system_schema` in the CQL editor to see them. UDFs and UDAs are listed under Functions; see [Procedures, functions, and triggers](/features/routines-triggers). -- The DDL tab reconstructs `CREATE TABLE` from the column list, so table options (compaction, compression, `gc_grace_seconds`), clustering order, and the split between partition and clustering key are all missing from it. Read the real definition with `DESCRIBE TABLE` in `cqlsh`. -- DataStax Astra DB does not connect. The Secure Connect Bundle it requires cannot be loaded, and there is no way around it. -- The connect timeout comes from the connection's **Options** section. The CQL request timeout - remains fixed at 30 seconds; neither a per-connection query override, the app-wide query timeout, - nor a multi-DC policy reaches this driver. - -## Troubleshooting - -**Connection refused**: Check Cassandra is running with `nodetool status`, that port 9042 matches `cassandra.yaml`, and that `rpc_address` and `listen_address` are reachable from your Mac. - -**Authentication fails**: Verify the credentials and check `authenticator: PasswordAuthenticator` in `cassandra.yaml`. The default superuser is `cassandra` / `cassandra`. - -**Read timeout**: Put the full partition key in the `WHERE` clause, add a `LIMIT`, and check cluster health with `nodetool`. diff --git a/docs/databases/clickhouse.mdx b/docs/databases/clickhouse.mdx deleted file mode 100644 index 44fd7678d0..0000000000 --- a/docs/databases/clickhouse.mdx +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: ClickHouse -description: Connection settings, the URL format, and the parts and partition tools for ClickHouse ---- - -Every query travels over HTTP to port 8123. The native TCP protocol on 9000 is not used, so the server's HTTP interface has to be on. Any version connects, though data-skipping indices need 19.17 or newer to show up. The driver ships inside the app. - -## Quick setup - -Click **New Connection…**, select **ClickHouse**, fill in host, port, and credentials, then click **Save & Connect**. - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `localhost` | | -| **Port** | `8123` | HTTP port, not 9000 native TCP | -| **Username** | `default` | Left empty, `default` is sent. Set a password in production | -| **Database** | `default` | Optional. Leave empty and the server uses `default` | - -Switching database happens in place, with no reconnect, and the sidebar's choice only affects new tabs: an open tab stays on its own database. See [Tabs](/features/tabs#where-a-tab-points). For unencrypted HTTP to a remote server, use [SSH tunneling](/connections/ssh-tunneling). - -## Connection URL - -```text -clickhouse://user:password@host:8123/database -``` - -See [Connection URL Reference](/connections/urls) for all parameters. - -## Browsing - -The sidebar lists the current database's tables, views, and materialized views. Press `Cmd+K` or click the database name in the toolbar to switch. Choose **View > Sidebar as Tree** to list every database and expand them in place. - -| Tab | Contents | -|-----|----------| -| **Structure** | Columns with types, default expressions, and comments | -| **Indexes** | The sorting key, listed as `PRIMARY (sorting key)`, plus data-skipping indices (minmax, set, bloom_filter, and the rest) | -| **DDL** | The full `CREATE TABLE`, engine and settings included | -| **Parts** | Every part from `system.parts` with its partition, rows, disk size, modification time, and active flag, plus **Optimize**, **Drop Partition**, and **Detach Partition** | - - - ClickHouse Parts tab showing partitions with Optimize, Drop, and Detach actions - ClickHouse Parts tab showing partitions with Optimize, Drop, and Detach actions - - -The [Server Dashboard](/features/server-dashboard) covers ClickHouse with active sessions, server metrics, and slow queries. - -## EXPLAIN variants - -Click the Explain dropdown in the query editor to choose: - -| Variant | Description | -|---------|-------------| -| **Plan** | Logical query plan (default) | -| **Pipeline** | Physical execution pipeline with thread and port info | -| **AST** | Abstract syntax tree of the parsed query | -| **Syntax** | Query after syntax optimizations | -| **Estimate** | Estimated rows, marks, and parts to read | - -## Running queries - -Each request carries a query id and asks for progress in the HTTP headers, so a long query reports rows read as it goes. Cancelling sends `KILL QUERY` for that id, stopping the query on the server rather than only dropping the connection. The [query timeout](/customization/general-settings#query-timeout) bounds each request at the timeout plus 30 seconds. - -Grid edits go out as INSERT, `ALTER TABLE … UPDATE`, and `ALTER TABLE … DELETE`. - - -ClickHouse mutations run in the background, so the grid reports the statement as accepted rather than applied, and a large table can take a long time. Watch `SELECT * FROM system.mutations WHERE is_done = 0` for progress. - - -## SSL/TLS - -The default is **Disabled**, which sends plain HTTP. Any other mode switches the URL scheme to `https`. Port `443` or `8443` turns on **Verify Identity**; see [Ports that mean TLS](/connections/ssl#ports-that-mean-tls). - -| Mode | Verifies | Pick it for | -|------|----------|-------------| -| **Preferred**, **Required** | Nothing. Encryption only | A self-signed certificate you have no CA file for | -| **Verify CA** | The chain, against the CA file | Private PKI | -| **Verify Identity** | The chain and the hostname, against the CA file or, with none set, the system trust store | ClickHouse Cloud, on port 8443 | - -The CA file may be PEM or DER. **Verify CA** needs one, since it checks no hostname, and a connection without it is refused. If **Verify CA** or **Verify Identity** cannot read the file, the connection fails rather than falling back to the public root store. An imported URL with `ssl=true` opens as Verify Identity, and `sslmode=none` as Required. See [SSL/TLS](/connections/ssl). - -## Limitations - -- No foreign keys and no multi-statement transactions. -- A materialized view opens read-only in the data grid. Edit the table a `TO` view writes into, or run `ALTER TABLE … UPDATE` and `ALTER TABLE … DELETE` on a view that stores its own rows from the SQL editor. -- No auto-increment, and the primary key and sorting key are fixed at creation. Structure editing covers adding, modifying, and dropping columns, and dropping data-skipping indexes. Add or change a data-skipping index with `ALTER TABLE … ADD INDEX` in the SQL editor, and recreate the table for anything else. -- A `SET` does not carry to the next statement. Every statement is its own HTTP request with no session id, and the setting is gone by the next one. Put it in a `SETTINGS` clause on the query itself. -- A query with its own `FORMAT` clause, such as `SELECT 1 FORMAT JSON`, shows the server's raw output in one column instead of a parsed table. Drop the clause to get a grid. -- A column with even one value that is not valid UTF-8 shows every row as hex in the grid, and its cells do not open for inline editing. `String` and `FixedString` hold raw bytes, so a hash or a stray byte is enough. An export, an object copy and a data compare judge each value on its own, so a row holding text stays text there. Select `toValidUTF8(column)` to read it as text, with the bad bytes replaced. - -## Troubleshooting - -**Connection refused**: `curl http://localhost:8123/ping` should answer `Ok.`. The usual cause is the port: 8123 is HTTP, 8443 HTTPS, 9000 native TCP and unused here. If the HTTP interface is off, check `` in `/etc/clickhouse-server/config.xml`. - -**Authentication failed (HTTP 403)**: Check the user exists (`SELECT name, auth_type FROM system.users`) and reaches the target database (`SHOW GRANTS FOR app_user`). - -**Connection timeout**: Check host, port, firewall rules, and, on ClickHouse Cloud, that your IP is in the allowed list. diff --git a/docs/databases/cloudflare-d1.mdx b/docs/databases/cloudflare-d1.mdx deleted file mode 100644 index d8a3bd11d9..0000000000 --- a/docs/databases/cloudflare-d1.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Cloudflare D1 -description: Connect to Cloudflare D1 databases over the Cloudflare REST API ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -A D1 connection is three strings: the database name, your Cloudflare account ID, and an API token. Every query leaves as an HTTPS request to the Cloudflare API, and the dialect on the other end is SQLite. - - - -## Connection settings - -| Field | Required | Description | -|-------|----------|-------------| -| **Database** | Yes | D1 database name or UUID. A name resolves to its UUID through the API | -| **Account ID** | Yes | Your Cloudflare account ID | -| **API Token** | Yes | Token with D1 permissions, entered in the password field (labeled **API Token** here). Stored in the macOS Keychain | - -No host, no port, and no **SSL Mode**: the endpoint is fixed and always HTTPS. - - - Cloudflare D1 connection form with Database, Account ID, and API Token fields - Cloudflare D1 connection form with Database, Account ID, and API Token fields - - -Click **Test Connection** to verify, then **Save & Connect**. - -## Connection URL - -```text -d1://account-id/database-name -``` - -The token is not in the URL. See [Connection URL Reference](/connections/urls). - -## Getting your credentials - - - - It is in the right sidebar of the [Cloudflare dashboard](https://dash.cloudflare.com), or run `npx wrangler whoami`. - - - On [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens), click **Create Token**, pick the **Custom token** template, and add the permission **Account > D1 > Edit**. - - - Cloudflare shows the token once. Paste it into **API Token**. - - - - -The token reaches every D1 database in its account, not only the one you connect to. - - -## Databases and schema - -The toolbar's database switcher lists every D1 database in the account and moves between them without reconnecting. It creates and drops them too. A tab keeps the database it opened on for its whole life: see [Tabs](/features/tabs#where-a-tab-points). - -| In the structure editor | Supported | -|---|---| -| Create and drop tables, add and drop columns, create and drop indexes | Yes, as generated DDL | -| Create, edit, and drop triggers | Yes, under **Triggers** | -| Rename or retype a column, change its nullability or default | No | -| Add or edit a foreign key | No | - -Browsing, `EXPLAIN QUERY PLAN`, data editing, and export behave as they do for [SQLite](/databases/sqlite). - -## Limitations - -- Nothing survives between statements. Each query is an independent HTTP request with no session state: `PRAGMA` settings and temp tables do not carry. Put what a statement needs into that statement. -- No multi-statement transactions. Every statement auto-commits alone, with no rollback. -- A column cannot be renamed or retyped, and a foreign key cannot be edited. Recreate the table and copy the rows across. -- No bulk import through the plugin. Run `wrangler d1 execute --file=dump.sql`, which needs `wrangler` installed. -- 10 GB per database, Cloudflare's limit. Shard across databases for more. -- Cloudflare's API rate limits apply. A limited request surfaces as an error carrying the retry delay Cloudflare returned. - -## Troubleshooting - -**Authentication failed. Check your API token and Account ID.**: The token lacks **D1 Edit**, the Account ID belongs to a different account, or the token expired or was revoked. - -**Database '…' not found in account**: Confirm the name or UUID with `wrangler d1 list`, and confirm the token reaches the account that owns it. - -**Rate limited by Cloudflare. Retry after … seconds.**: Wait the stated interval. If it repeats, refresh less often and lower the page size. diff --git a/docs/databases/cloudflare-r2-sql.mdx b/docs/databases/cloudflare-r2-sql.mdx deleted file mode 100644 index a9fc721b7f..0000000000 --- a/docs/databases/cloudflare-r2-sql.mdx +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: Cloudflare R2 SQL -description: Query Apache Iceberg tables in an R2 bucket with Cloudflare's read-only SQL engine ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -One connection reads one R2 bucket that has R2 Data Catalog turned on. Queries travel as HTTPS requests to `api.sql.cloudflarestorage.com`, R2 SQL runs them against the bucket's Iceberg tables, and it bills by the bytes each query scans. - - - -## Connection settings - -| Field | Required | Description | -|-------|----------|-------------| -| **Account ID** | Yes | Your Cloudflare account ID | -| **Bucket** | Yes | The R2 bucket whose catalog holds the tables | -| **API Token** | Yes | Token with R2 SQL, R2 Data Catalog and R2 Storage access, entered in the password field (labeled **API Token** here). Stored in the macOS Keychain | - -There is no host, port, Database field, SSH tunnel or **SSL Mode**: the endpoint is fixed and always HTTPS, and the warehouse name comes from the account ID and bucket. Click **Test Connection**, then **Save & Connect**. - -## Connection URL - -R2 SQL has no connection URL. Fill in the form instead. - -## Getting your credentials - - - - In the [Cloudflare dashboard](https://dash.cloudflare.com), open the bucket under **R2 Object Storage**, then **Settings > R2 Data Catalog**, and enable it. A bucket without the catalog has no tables to query. - - - It is in the dashboard's right sidebar, or run `npx wrangler whoami`. - - - Create an [R2 API token](https://developers.cloudflare.com/r2/api/tokens/) with R2 SQL read, R2 Data Catalog read, and R2 Storage access. R2 SQL needs all three: the catalog for the table list, storage for the data files, and SQL to run the query. - - - - -The token reaches every bucket its permissions cover, not only the one this connection names. - - -## Namespaces and tables - -Iceberg groups tables into namespaces, and each namespace is a schema here: the sidebar lists the bucket's namespaces with their tables inside, and the [toolbar's centred control](/features/connection-window#the-toolbar) moves between them. The list comes from `SHOW NAMESPACES` and `SHOW TABLES IN`, and a table's columns from `DESCRIBE`. None of the three scans data. A column's Iceberg `doc` shows as its comment in the Structure tab. - -Name the namespace in a query tab: - -```sql -SELECT user_id, event, ts -FROM logs.events -WHERE ts >= '2026-01-01T00:00:00Z' -ORDER BY ts DESC -LIMIT 1000 -``` - -R2 SQL converts nothing implicitly: quote strings, leave numbers bare, and give timestamps a time zone. - -## Read-only connections - -R2 SQL runs `SELECT`, `SHOW`, `DESCRIBE` and `EXPLAIN`, and rejects every write. The connection runs at Safe Mode **Read-Only** whatever level it was given, so cell editing, row insert and delete, and import are off, and the Structure tab only reads. Export works. See [Safe Mode](/features/safe-mode#connections-that-are-always-read-only). - -## Pagination - -R2 SQL cannot skip rows and returns at most 10,000 from one query. A table tab shows its leading rows: the rows-per-page menu stops at 10,000, the page buttons are gone, and the status bar reads `Rows 1-500` until **Count Exactly** fills in the total. Filter and sort to decide which rows load, because both run in the query. See [Pages and row counts](/features/data-grid#pages-and-row-counts). - -Past the first 10,000, page by key in a query tab, carrying the last value of the previous page forward: - -```sql -SELECT * FROM logs.events -WHERE event_id > '01HQ7Z2K3M4N5P6Q7R8S9T0V' -ORDER BY event_id -LIMIT 1000 -``` - -A query with no `LIMIT` would stop at R2 SQL's own default of 500 rows, so every read you did not limit is sent with one: the [row cap](/customization/data-settings) plus one row, or 10,000 for **Fetch All** and exports. - -## Cost - -Each query is billed on the bytes it scans, with a minimum per query, and a table tab's automatic row count would be one more scan every time the tab loaded. That count only runs when you click **Count Exactly**. `SHOW`, `DESCRIBE` and `EXPLAIN` scan nothing. [R2 SQL pricing](https://developers.cloudflare.com/r2-sql/platform/pricing/) has the current rates. - -## Limitations - -- No writes, DDL, or transactions. Load tables through an Iceberg writer such as Spark, PyIceberg, or R2 Pipelines. -- 10,000 rows per query. A table export or copy stops there and names each table it cut short; export a filtered or keyed subset for the rest. -- `OFFSET` is rejected. Page by key, as in [Pagination](#pagination). -- Two output columns with the same name come back as one. Alias them in the query. -- No primary keys, foreign keys, or indexes, so rows cannot be edited and the ER diagram has no relationships. -- One bucket per connection. Add a connection for each bucket. - -## Troubleshooting - -### `Authentication error` - -The token is missing a permission or belongs to another account. Check it carries R2 SQL, R2 Data Catalog and R2 Storage access, and that **Account ID** is the account that owns the bucket. - -### No namespaces after connecting - -The bucket has R2 Data Catalog turned off, or the catalog holds no tables yet. Turn the catalog on in the bucket's settings, then refresh the sidebar. - -### `SHOW TABLES returned columns TablePro does not recognize: …` - -R2 SQL answered a catalog statement in a shape this version of the driver cannot read. Update the plugin from **Settings > Plugins**. - -### `unsupported feature: OFFSET clause is not supported` - -A query tab query uses `OFFSET`. Rewrite it with keyset paging, as in [Pagination](#pagination). diff --git a/docs/databases/cockroachdb.mdx b/docs/databases/cockroachdb.mdx deleted file mode 100644 index 6e648091dc..0000000000 --- a/docs/databases/cockroachdb.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: CockroachDB -description: Connect to CockroachDB clusters over the PostgreSQL wire protocol, including CockroachDB Cloud with cluster routing ---- - -Everything on the [PostgreSQL](/databases/postgresql) page holds here. The same libpq driver serves both, out of the PostgreSQL plugin the app already ships, and no minimum cluster version is enforced. This page is only the differences. - - - CockroachDB connection form - CockroachDB connection form - - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Port** | `26257` | Not 5432 | -| **Database** | `defaultdb` | Required. New clusters start with `defaultdb` and `postgres`, and only `system` is marked a system database | -| **Username** | - | Not pre-filled. `root` on a local insecure cluster | -| **SSL Mode** | **Preferred** | TLS first, plain text if the server refuses | - -For a node started with `cockroach start-single-node --insecure`, set SSL Mode to **Disabled** and leave the password empty. Otherwise fill in the form and click **Save & Connect**. - -## Connection URL - -```text -cockroachdb://user:password@host:26257/defaultdb -``` - -`cockroach://` also works. See [Connection URL Reference](/connections/urls). - -## CockroachDB Cloud - -A Cloud connection string carries a cluster routing parameter that the form has no field for. Copy the host, port, user, password, and database from the Cloud console, then put the routing value in **Connection Options** on the Options tab: - -```text ---cluster=my-cluster-1234 -``` - -Set SSL Mode to **Verify Identity** for Cloud. **Connection Options** goes straight to libpq, so it takes any other libpq option a self-hosted cluster needs. - -## What is different - -Table and view DDL comes from CockroachDB's own `SHOW CREATE`, so the DDL tab shows the statement the cluster itself would emit rather than a reconstruction. - -`EXPLAIN` and `EXPLAIN ANALYZE` render as a visual plan tree. CockroachDB returns text plans rather than JSON, and the text is parsed into the tree. See [EXPLAIN Visualization](/features/explain-visualization). - -As on PostgreSQL, each database browsed from the connections strip keeps a connection of its own, so moving between databases does not reconnect, and a tab runs on its database's connection. See [Cross-database tabs](/databases/postgresql#cross-database-tabs). - -## Limitations - -- No structure editing at all: no column changes, no index changes, no primary key changes. The Structure tab is read-only. Run schema changes as SQL in the editor. -- No `pg_dump` or `pg_restore`. **File > Backup Dump…** and **File > Restore Dump…** stay dimmed. Use CockroachDB's `BACKUP` and `RESTORE` statements. -- No Table Maintenance submenu. CockroachDB garbage-collects on its own and offers no `VACUUM` to run. -- Foreign key checks cannot be deferred. - -## Troubleshooting - -**Connection refused**: Check the cluster is running and port 26257 is reachable. For a local cluster, confirm `cockroach start` is still running. - -**Authentication fails**: On a local insecure cluster use `root`, no password, and SSL Mode **Disabled**. On Cloud, copy the credentials from the console and use **Verify Identity**. - -**Cloud connection hangs or is rejected**: The cluster routing value is missing. Put `--cluster=your-cluster-name` in **Connection Options**. - -**EXPLAIN shows raw text**: The plan did not match the shape the parser expects, so it fell back to the server's own text. The plan is complete; only the tree is missing. diff --git a/docs/databases/dameng.mdx b/docs/databases/dameng.mdx deleted file mode 100644 index 202d0a0df1..0000000000 --- a/docs/databases/dameng.mdx +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Dameng DM8 -description: Dameng DM8 over its native wire protocol, with no DM client stack to install ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Host, port, username, password. The plugin speaks the DM8 wire protocol itself, so no part of the DM client stack has to be on your Mac and no DM8 version is ruled out. - - -A `CLOB` or `BLOB` that DM8 stored outside the row reads as an empty cell. Nothing marks it as truncated, so it looks exactly like an empty column. Cast `CLOB` values to `VARCHAR`, or wrap binary columns in `RAWTOHEX`, when you need the contents. - - - - Dameng DM8 connection form - Dameng DM8 connection form - - - - -## Quick setup - -Click **New Connection…**, choose **Dameng DM8**, fill in host, port, and credentials, and click **Save & Connect**. - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `localhost` | DM8 server or tunnel endpoint | -| **Port** | `5236` | Default DM8 listener port | -| **Database** | - | Optional, and it picks a schema, not a database. Left empty, the driver uses your username uppercased | -| **Username** | - | DM username, such as `SYSDBA` | -| **Password** | - | Stored in the macOS Keychain | - -One DM8 instance holds one database, so there is nothing to switch between; use the schema picker instead, which switches without reconnecting. `SYS`, `SYSAUDITOR`, `SYSSSO`, `CTISYS`, `SYSJOB`, and `SYSGEO2` are system schemas: the schema picker lists them last, and the sidebar shows them only with [**Show system databases and schemas**](/customization/general-settings#tabs-and-sidebar) on, or when one is the schema in use. `SYSDBA`, the administrator's own schema, lists with your schemas, and like the system schemas it cannot be dropped. An open tab stays on the schema it started with. See [Tabs](/features/tabs#where-a-tab-points). - -## Connection URL - -```text -dm://user:password@host:5236/schema -``` - -The path is a schema, not a database. See [Connection URL Reference](/connections/urls). - -## SSL/TLS - -None. Native DM8 TLS is not implemented, the Network tab has no **SSL Mode**, and the connection is unencrypted. Reach a server outside a private network through an [SSH tunnel](/connections/ssh-tunneling), [SOCKS proxy](/connections/socks-proxy), or [Cloudflare tunnel](/connections/cloudflare-tunnel). - -## Limitations - -- Stopping a query drops its connection. The driver reconnects on your next query, back on the schema you were using: a read reruns, an open transaction is gone, and a write is reported rather than retried. Check whether the write landed before running it again. A query timeout does the same. -- The server finishes the statement you stopped. A heavy query keeps running there, and a stop can take a few seconds to show up. -- A view definition longer than 8188 bytes is refused. The DDL tab reports the length instead of showing truncated text you could save back. Read the full definition on the server. -- Username and password are the only authentication, and there is no trigger editor. Write trigger DDL in the SQL editor. - -## Troubleshooting - -**The Dameng connection is closed.**: The server, a tunnel, or the network dropped the connection. The driver rebuilds it on the next query; rerun the statement. - -**The Dameng connection was lost, so the open transaction was rolled back.**: Nothing in that transaction committed. Run its statements again. - -**Dameng did not switch to the requested schema.**: The schema does not exist or your user cannot reach it. List the schemas visible to your user with `SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_TYPE = 'SCH'`. - -**Switch away from a schema before dropping it.**: A schema cannot drop itself out from under the session. Move to another one first. - -If a server value fails to decode, include the DM8 version and the column type in a [GitHub issue](https://github.com/TableProApp/TablePro/issues). diff --git a/docs/databases/databend.mdx b/docs/databases/databend.mdx deleted file mode 100644 index 748eb2ac54..0000000000 --- a/docs/databases/databend.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Databend -description: Connect to Databend and Databend Cloud through Databend's MySQL handler on port 3307 ---- - -Choose **Databend** as the connection type. A connection saved as MySQL that reaches a Databend server fails at connect and asks for the type to be changed. Port 8000 is Databend's HTTP API and does not connect. - -## Quick setup - -A local server in Docker, with the handler on 3307: - -```bash -docker run -d -p 3307:3307 datafuselabs/databend -``` - -Connect as `root` with an empty password and database `default`. Set `QUERY_DEFAULT_USER` and `QUERY_DEFAULT_PASSWORD` on the container to change them. - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Port** | `3307` | The MySQL handler. 8000 is HTTP and does not connect | -| **Database** | empty | Optional. New servers start with `default` | -| **SSL Mode** | Preferred | TLS first, plain text if the server refuses | - -Host, user, password, SSH tunnels and `Cmd+K` database switching work as on [MySQL](/databases/mysql). No minimum Databend version is enforced. - -## Connection URL - -No URL scheme opens a Databend connection, and `databend://` URLs, which point at the HTTP API, do not open one either. Create the connection in the form. **Copy Connection String** gives a `mysql://` URL for the MySQL handler, which other MySQL clients accept. - -## Databend Cloud - -The MySQL endpoint of a Databend Cloud warehouse is off by default. Ask Databend support to turn it on, then copy the host and port from the warehouse's **Connect** dialog. - -## Editing rows - -Databend tables have no primary keys, so a row edit or delete matches on every column of the row. ARRAY, MAP, TUPLE, VARIANT, JSON, BITMAP, BINARY, GEOMETRY, GEOGRAPHY and VECTOR columns are left out of that match. A save that would change more than one row is refused and rolled back; add a column that tells the rows apart, or run the statement in the editor. - -ARRAY, MAP and TUPLE cells cannot be written from text. Change them with an `UPDATE` in the SQL editor. - -## Limitations - -- The sidebar hides `system` and `information_schema`. -- No indexes, primary keys or foreign keys. The Structure tab edits columns and CHECK constraints and renames tables, views and columns; column reordering, AUTO_INCREMENT, charset and collation are not offered. -- No Users & Roles, Server Dashboard, triggers, routines or events. **File > Backup Dump…** stays dimmed. -- Create Database takes a name only. Table Maintenance offers `ANALYZE TABLE` only. -- `EXPLAIN` and `EXPLAIN ANALYZE` show the plan as text, not as a plan tree. -- [Compare & Sync](/features/compare-sync) is not available. -- The SQL editor splits statements without backslash escapes, so write a quote inside a string as `''` rather than `\'`. -- Not available on iPhone and iPad. A synced Databend connection appears in the list there but does not open. - -## Related - -- [MySQL](/databases/mysql), for connection fields, SSL/TLS, and troubleshooting -- [SSH Tunneling](/connections/ssh-tunneling) diff --git a/docs/databases/duckdb.mdx b/docs/databases/duckdb.mdx deleted file mode 100644 index ecfd5ed90d..0000000000 --- a/docs/databases/duckdb.mdx +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: DuckDB -description: Open local DuckDB files or connect to a remote DuckDB server over Quack ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Pick a `.duckdb` file to open a database, or a `.parquet`, `.csv`, `.tsv`, `.json` or `.ndjson` file to read it where it sits. DuckDB 1.5.2 is compiled into the driver, so nothing runs as a server unless you point the connection at one. - - - -## Quick setup - - - - Click **New Connection…** or press `Cmd+N`, then choose **DuckDB**. - - - Leave **Connection Type** on **Local File** and click **Browse…**, or **New…** to name a `.duckdb` file that does not exist yet. Type `:memory:` in the field instead for a database that lasts as long as the session. - - - Click **Save & Connect**. - - - - - DuckDB connection form with Local File and Remote (Quack) connection types - DuckDB connection form with Local File and Remote (Quack) connection types - - -## What the file field accepts - -| Path | What you get | -|-------|-------------| -| `.duckdb`, `.ddb` | The database in that file. A path that does not exist yet is created | -| `.parquet`, `.csv`, `.tsv`, `.json`, `.ndjson` | An in-memory database holding two read-only views over the file, one named `file` and one named after the file. A path that does not exist is reported instead of created | -| `:memory:` | An empty database, discarded on disconnect | - -In Local File mode the path is the whole connection, and it is required; the form shows no host, port or Database field. - -Double-click a `.duckdb` or `.ddb` file in Finder to open it here. `.parquet` files list TablePro under **Open With** rather than taking the association; to make a double-click open them here, select one in Finder, press `Cmd+I`, set **Open with** to TablePro, and click **Change All…**. `.csv`, `.tsv`, `.json` and `.ndjson` open from the connection form only. - -A DuckDB database carries `DUCK` eight bytes in, so a database opens under any name: drag `warehouse.db` onto a window and it reaches this driver rather than SQLite. The data formats need their extension either way, because the reader is chosen from it. Installing the plugin is offered first when it is not already there. - -## Remote (Quack) - -Quack is DuckDB's client-server protocol, served by `quack_serve`. Set **Connection Type** to **Remote (Quack, experimental)**: - -| Field | Value | -|-------|-------| -| **Host** | Server address. Required | -| **Port** | `9494` by default | -| **Token** | The token the server was started with | -| **Database Alias** | The name the remote is attached under. Pre-filled with `remotedb` | - -**Save & Connect** attaches the remote to an in-memory DuckDB. Reach it through the alias: `SELECT * FROM remotedb.main.orders`. - - -The `quack` extension is not linked into the macOS build. DuckDB downloads it from `extensions.duckdb.org` on the first remote connect and caches it under `~/.duckdb`, so that one connect needs to reach that host. - - -## Connection URL - -```text -duckdb:///path/to/database.duckdb -quack://host:9494/alias -``` - -See [Connection URL Reference](/connections/urls) for all parameters. - -## Databases and schemas - -A connection opens one database named after the file: `analytics.duckdb` becomes `analytics`, schema `main`. `ATTACH` a second file and it joins the sidebar as a sibling database after a refresh: - -```sql -ATTACH 'warehouse.duckdb' AS warehouse; -``` - -Press `Cmd+K` to move between them, in place, with no reconnect. DuckDB does not persist attachments, so a reconnect starts again from the one file. `system` and `temp` are hidden, and with them `information_schema` and `pg_catalog`. - -Any connection can also read a file straight from SQL: - -```sql -SELECT * FROM 'data.csv'; -SELECT * FROM read_parquet('analytics.parquet'); -``` - -## Sharing the file with another process - -One process writes a DuckDB file at a time, and the lock lasts as long as the connection. While a tab is open on `sales.duckdb`, `duckdb sales.duckdb` in a terminal is refused, and so is a notebook, a dbt run, or a second TablePro window. - -Three ways out, in **Options** on the connection: - -| Setting | What happens | Pick this when | -|---|---|---| -| **Release the File Lock After** | The file is handed back once the connection has been idle that many minutes, and taken again on the next query. `0`, the default, keeps it | A script or a scheduled job needs the file while you are reading it | -| **Open the File Read-Only** | The file is opened read-only, which several processes can do at once. Writes are refused by the engine | Two or more readers, and nothing writing | -| Neither | The lock is held for the life of the connection | Nothing else touches the file | - -**Database ▸ Release File Lock** hands it back now, without ending the session. The tabs, the schema cache and the row you were on all stay; the next query takes the file again. - -A release is refused, with the reason, while the session holds anything reopening would destroy: an open transaction, a temporary table or view, an attached database, or a changed setting. Commit the transaction or drop the temporary table and the release goes through. - -Read-only is not a way to keep writing from somewhere else. A read-write connection blocks every other process, read-only ones included; a read-only connection blocks writers. Two readers work, a reader and a writer do not. - -Releasing hands the file to whoever wants it next, which may not be you. A query run after another process has taken it fails until that process lets go; reconnect once it has. - -## Limitations - -- A DuckDB file takes one writer at a time. A connect against a file another process holds fails, naming that process and its process ID. Quit the `duckdb` CLI, the Python process or the other TablePro window first, or set **Release the File Lock After**. -- A data file opened through **Browse…** is read-only, and the file on disk is left byte for byte identical. To change its contents, open a `.duckdb` database and run `CREATE TABLE t AS SELECT * FROM 'data.parquet'`. -- Remote (Quack) lists no tables in the sidebar. Write them out by name through the alias. -- No **SSL Mode** and no SSH tunnel. The engine is in-process; see [SSL/TLS](/connections/ssl). -- No creating or dropping a database. `ATTACH` is the way to reach a second file. - -## Troubleshooting - -### No file at … - -A data-file path that is not there. Only `.duckdb` and `.ddb` are created on demand. Re-pick the file with **Browse…**. - -### DuckDB opened the file but reported no catalog to browse - -The file opened and exposed nothing to list. Check that it is a DuckDB database and not a file with a borrowed extension. - -### Port must be a number between 1 and 65535 - -**Port** holds something else. The Quack default is `9494`. - -### … is using sales.duckdb - -Another process holds the file. The message names it and its process ID. Quit it, or turn on **Open the File Read-Only** when the message says read-only would get in, which it does when the other process is itself a reader. - -### The database file is no longer at … - -The file moved or was deleted while the connection was idle with its lock released. Point the connection at the new path, or restore the file, then reconnect. - -## Related - -- [TablePro for iPhone and iPad](/ios), where DuckDB opens a file picked through the Files app -- [Import & Export](/features/import-export) diff --git a/docs/databases/dynamodb.mdx b/docs/databases/dynamodb.mdx deleted file mode 100644 index cd59a7d7d4..0000000000 --- a/docs/databases/dynamodb.mdx +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: Amazon DynamoDB -description: Browse DynamoDB tables through their indexes, edit items with their types intact, and run PartiQL or DynamoDB API requests ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -What a table read costs depends on the filter bar. Pin the partition key with `=` and the grid runs a Query that reads one partition; leave it out and the grid runs a Scan, which reads the whole table and is billed for every item it reads, including the ones your filters then drop. - - - -## Quick setup - -Click **New Connection…**, select **DynamoDB**, choose an **Auth Method**, fill in its fields and the **AWS Region**, then click **Save & Connect**. - - - DynamoDB connection form with Auth Method, Access Key ID, Secret Access Key, Session Token, AWS Region and Custom Endpoint fields - DynamoDB connection form with Auth Method, Access Key ID, Secret Access Key, Session Token, AWS Region and Custom Endpoint fields - - -## Connection settings - -There is no host, port, database or connection URL. A connection is one AWS account in one region, and the sidebar lists that region's tables. Every region works, as does DynamoDB Local. - -| Field | Description | -|-------|-------------| -| **AWS Region** | Region the tables live in. Left empty, the region comes from the AWS profile, then `us-east-1` | -| **Custom Endpoint** | Replaces the regional endpoint. Leave it empty unless you run DynamoDB Local or a VPC endpoint | - -The endpoint follows the region's partition, so `cn-north-1` reaches `dynamodb.cn-north-1.amazonaws.com.cn` and the European Sovereign Cloud reaches its own domain without a custom endpoint. - -## Authentication - -| Auth Method | Fields | Credentials come from | -|-------------|--------|----------------------| -| **Access Key + Secret Key** | **Access Key ID**, **Secret Access Key**, **Session Token** for temporary credentials | What you type | -| **AWS Profile** | **Profile Name** | That profile in `~/.aws/config` and `~/.aws/credentials`, read the way the AWS CLI reads it | -| **AWS SSO** | **Profile Name** | The IAM Identity Center session for that profile. An expired session opens the sign-in prompt | -| **DynamoDB Local (no credentials)** | None | Fixed placeholder keys that DynamoDB Local accepts | - -Pick **AWS Profile** if the AWS CLI already works on this Mac. Static keys, `credential_process` helpers and `role_arn` chains all resolve; the rules are on [AWS IAM Authentication](/connections/aws-iam#profiles). Expired temporary credentials are fetched again and the request retried once. - -## DynamoDB Local - -```bash -docker run -p 8000:8000 amazon/dynamodb-local -``` - -Set **Auth Method** to **DynamoDB Local (no credentials)**. With **Custom Endpoint** empty it connects to `http://localhost:8000`; fill it in for any other port. - -## How a table is read - -Opening a table, filtering it and paging it all go through one planner. It picks the cheapest read your filters allow and names it at the start of the status bar, followed by how many items came back, how many DynamoDB read to find them, and the read capacity units it charged: `Query on index byStatus · 50 returned · 212 read · 26.5 RCU`. - -| Filters | Read | -|---------|------| -| Partition key `=` one value | **Query on the table** | -| Partition key `IN` a list, or several partition keys joined by **Match Any** | One Query per partition, or **Get by key** when the table has no sort key | -| A global index's partition key `=`, with a filter on each of its sort keys | **Query on index** | -| The partition key `=` and a key-condition filter on a local index's sort key | **Query on index** for that local index | -| Anything else | **Scan with filters** | - -A filter on the sort key of the chosen table or index joins the key condition when it is `=`, `<`, `<=`, `>`, `>=`, **BETWEEN** or **STARTS WITH**. Other filters become a FilterExpression, which DynamoDB applies after reading, so they narrow the rows without lowering the bill. **ENDS WITH**, regular expressions, matches that ignore case and a search across every column are checked on the returned items instead. **CONTAINS** means what DynamoDB's `contains` means on either side: text inside a String, or a member of a set or a list. **STARTS WITH** ignores case until you select **Match Case** in its operator menu, and only then can it run as a key condition. - -A global index is used only when it projects every attribute, and only when every one of its sort keys carries a filter, because an item missing a key attribute is not in the index at all. - -Filter on a nested attribute with a path such as `address.city` or `items[0].sku`. Autocomplete offers the map paths found in the table's first 100 items. - -### Sorting and paging - -Clicking the sort key's header on a Query that reads one partition makes DynamoDB return the items in that order, forwards or backwards. Any other sort needs every matching item. When the read holds them all, the grid sorts them. When it does not, the rows stay in DynamoDB's order and the status bar says so: `In DynamoDB order: sorting by total needs the whole result, or a Query on that sort key`. - -The next page starts from where the previous one stopped rather than reading the table again from the start. A page can take several requests: DynamoDB counts the items it examined against the request's limit before a FilterExpression drops any, so the reader keeps going until the page is full or the table has no more. - -### Row counts - -The total under the grid is DynamoDB's own item count, which it refreshes about every six hours. No count runs on its own. **Count Exactly** reads every item your filters match with `Select: COUNT`, so it is billed as a full read of those items. The query timeout does not stop it; `Cmd+.` does. - -## Items in the grid - -A table declares only its key attributes, so the columns come from the items themselves: every attribute in the table's first 100 items and in the page on screen, plus the key attributes of each index. The table's keys come first. An attribute that appears in none of those items has no column until a page returns it. Column types use the AWS console's names, taken from the most common type in the column. - -| Attribute type | In the cell | -|----------------|-------------| -| String, Number, Boolean | The value | -| Null | `NULL` | -| Binary | Hex, `0x…` | -| Map, List | Plain JSON, `{"city":"Paris"}` and `["a",1]` | -| String Set, Number Set, Binary Set | A JSON array | - -Maps, lists and sets open in the JSON editor. Numbers keep all 38 digits; nothing in the grid rounds them. - -### Saving edits - -Each changed row becomes one PartiQL statement, sent when you save, and each value keeps the type it had: an edited Number stays a Number, a String that looks like `02134` stays a String, and a String Set edited as JSON goes back as a String Set, nested Binary values included. A new attribute takes the type its column shows. **Set NULL** removes the attribute from the item. - -An update also checks each attribute it changes against the value the grid loaded, so it fails rather than overwrite a value someone else changed since, or recreate an item someone deleted. **Duplicate Row** copies every attribute but the keys; fill in the new keys before saving. - -## PartiQL and the DynamoDB API - -The editor runs two kinds of statement. PartiQL takes double quotes around names and single quotes around strings, and Amazon's [PartiQL reference](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html) has the grammar: - -```sql -SELECT * FROM "Orders" WHERE "pk" = 'customer#42' AND "sk" > 100 -``` - -A DynamoDB action name followed by its request JSON runs that action, exactly as the [API reference](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations_Amazon_DynamoDB.html) describes the request: - -```json -Query {"TableName": "Orders", "IndexName": "byStatus", - "KeyConditionExpression": "#s = :s", - "ExpressionAttributeNames": {"#s": "status"}, - "ExpressionAttributeValues": {":s": {"S": "shipped"}}} -``` - -| Kind | Actions | -|------|---------| -| Read | `Scan`, `Query`, `GetItem`, `BatchGetItem`, `TransactGetItems` | -| Write | `PutItem`, `UpdateItem`, `DeleteItem`, `BatchWriteItem`, `TransactWriteItems` | -| PartiQL | `ExecuteStatement`, `ExecuteTransaction`, `BatchExecuteStatement` | -| Describe | `ListTables`, `DescribeTable`, `DescribeTimeToLive`, `DescribeContinuousBackups`, `ListTagsOfResource`, `DescribeLimits` | -| Manage | `CreateTable`, `UpdateTable`, `DeleteTable`, `UpdateTimeToLive`, `UpdateContinuousBackups`, `TagResource`, `UntagResource` | - -Any other action name is refused. Autocomplete inserts a template for the common ones. Items come back in the grid; any other response comes back as JSON. - -- A `Scan` or `Query` request takes a trailing `ORDER BY`, `LIMIT` and `OFFSET`, which count the items returned. With `"Select": "COUNT"` it pages to the end and returns one row of `Count` and `ScannedCount`. -- A PartiQL `SELECT` takes them too. An `ORDER BY` on the sort key goes to DynamoDB when the WHERE clause fixes the partition key with `=`; any other is applied to the result. A `SELECT` that does not fix the partition key reads the whole table, and the status bar says so. -- A `BatchWriteItem` sends its unprocessed items again until they are written, and reports the ones still left after 10 attempts rather than calling the batch a success. -- A throttled request retries with backoff, and so does a failed one that is safe to send twice: a read, or a write whose repeat changes nothing. Four attempts in all. `Cmd+.` stops a running statement. - -## Creating and changing tables - -**New Table…** opens a form rather than a column grid: the partition and sort key with their types, on-demand or provisioned capacity, the table class, deletion protection, up to 20 global and 5 local secondary indexes. **Preview** shows the `CreateTable` request it will send. A local secondary index can only be defined here, when the table is created. The new table opens empty until DynamoDB finishes creating it. - - - Create Table form with Primary Key, Capacity, Settings and secondary index sections - Create Table form with Primary Key, Capacity, Settings and secondary index sections - - -In [Table Structure](/features/table-structure), **Indexes** lists the key and every index. Add a global secondary index there with its partition key as the first column and an optional sort key as the second, or drop one; either is an `UpdateTable` request, and DynamoDB builds or removes the index in the background. The key's type comes from the table's items; when no item read so far holds that attribute, the index is refused, so create it from the editor with an `UpdateTable` request that declares `AttributeDefinitions`. An index cannot be edited in place: delete it, save, and add the replacement once the old one is gone. Save one index change at a time. DynamoDB takes one index create or delete per `UpdateTable` request and refuses another while the table is updating, so a second index change in the same save fails after the first has run. The primary key and a local secondary index go only with the table. **DDL** shows the `CreateTable` request that recreates the table, plus the Time to Live and point-in-time recovery settings it carries. - -Right-click a table and choose **Maintenance** for point-in-time recovery, deletion protection, the stream, the table class, switching to on-demand capacity and turning Time to Live off. [Table Operations](/features/table-operations#maintenance) covers the sheet. - -Dropping a table from the sidebar sends `DeleteTable`. It fails while deletion protection is on. - -## IAM permissions - -Browsing, filtering, exporting and the Structure tab call these actions: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "dynamodb:ListTables", - "dynamodb:DescribeTable", - "dynamodb:DescribeTimeToLive", - "dynamodb:DescribeContinuousBackups", - "dynamodb:Scan", - "dynamodb:Query", - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:PartiQLSelect" - ], - "Resource": "*" - } - ] -} -``` - -Saving grid edits adds `dynamodb:PartiQLInsert`, `dynamodb:PartiQLUpdate` and `dynamodb:PartiQLDelete`. Creating, changing and dropping tables adds `dynamodb:CreateTable`, `dynamodb:UpdateTable`, `dynamodb:DeleteTable`, `dynamodb:UpdateTimeToLive` and `dynamodb:UpdateContinuousBackups`. A request typed in the editor needs the action it names. - -## SSL/TLS - -Every request goes to the AWS endpoint over HTTPS, signed with Signature Version 4, and there is no **SSL Mode** to set. A custom endpoint may use plain HTTP only when it is on this Mac: `localhost`, `127.0.0.1` or `::1`. - -## Limitations - -- A key attribute cannot be edited in place, and saving the change fails. Duplicate the row, give the copy its new key, save, then delete the original. -- The Structure tab cannot add, change or drop an attribute. Write the attribute to an item and it appears as a column. -- A global index that projects only its keys or chosen attributes is never used for browsing, so a filter on its key scans the table. Query that index from the editor with a `Query` request naming its `IndexName`. -- Import is not available. Write items from the editor with `BatchWriteItem` or PartiQL `INSERT`. -- Truncate is not offered, because DynamoDB can only empty a table by deleting every item. Drop the table and create it again from its DDL. -- Saving writes each changed row on its own, so there is no transaction across rows. Group writes that must succeed together in an `ExecuteTransaction` or `TransactWriteItems` request. -- A read through a global secondary index is eventually consistent, so an edit saved a moment ago can be missing from it. Refresh, or filter on the table's own partition key, which reads the table. -- DAX endpoints are not supported. Leave **Custom Endpoint** empty or point it at a DynamoDB endpoint. - -## Troubleshooting - -### Authentication failed: … - -DynamoDB rejected the credentials: an unknown key, a bad signature, or a session token that aged out. Check the key and secret, the profile name, or sign in again with `aws sso login --profile `. - -### "…" is not an AWS region - -**AWS Region**, or the region in the AWS profile, holds something other than a region name. Region names are lowercase letters, digits and hyphens. Set it to one such as `us-east-1`. - -### DynamoDB error: [AccessDeniedException] … - -The credentials work but their policy does not allow the action the message names. Add it to the policy, using the list in [IAM permissions](#iam-permissions). - -### DynamoDB error: [ResourceNotFoundException] … - -The table is not in this region. Tables are regional; set **AWS Region** to the one the table was created in. - -### DynamoDB error: [ProvisionedThroughputExceededException] … - -The table used up its provisioned read or write capacity and four attempts did not get through. Filter on the partition key so the read runs as a Query, raise the capacity, or switch the table to on-demand from **Maintenance**. - -### The item … changed after it was loaded. Refresh the table and edit it again. - -Someone changed the item between loading it and saving it, and the save was refused so their change survives. Refresh, then make the edit again. - -### Plain HTTP is only allowed for an endpoint on this Mac (localhost). Use https:// for any other host. - -**Custom Endpoint** starts with `http://` and names another machine. Use `https://`, or run DynamoDB Local on this Mac. - -## Related - -- [AWS IAM Authentication](/connections/aws-iam) -- [Filtering](/features/filtering) -- [Table Operations](/features/table-operations) -- [Import & Export](/features/import-export) diff --git a/docs/databases/elasticsearch.mdx b/docs/databases/elasticsearch.mdx deleted file mode 100644 index fce244b304..0000000000 --- a/docs/databases/elasticsearch.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: Elasticsearch -description: Connect to Elasticsearch with a Query DSL console, index browsing, and document editing ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Two things share this connection. A grid browses and edits documents by `_id`, and a console that takes a method, a path and an optional JSON body the way Kibana Dev Tools does. There is no SQL behind either. Elasticsearch 7.x and 8.x are supported, over the REST API. - - - -## Quick setup - -Click **New Connection…**, select **Elasticsearch**, enter a node under **Hosts**, pick an **Auth Method**, then click **Save & Connect**. - -The form shows no Database field: a connection reaches one cluster, and its indices are the objects. - -For a node on a private server, open the **Network** tab and set **Connect via** to **SSH Tunnel**, then enter the address Elasticsearch listens on as the SSH server sees it under **Hosts**, usually `localhost:9200`. See [SSH Tunneling](/connections/ssh-tunneling). - -## Clusters - -Add one row under **Hosts** per node of the cluster, as `host:port` or a pasted URL such as `https://10.0.0.1:9200`. **SSL Mode** decides between HTTP and HTTPS for every node, so a pasted `https://` URL needs a mode other than **Disabled**. - -The connection uses the first node that answers and stays on it: - -- A node that refuses or drops the connection sends reads to the next node at once. -- A request that changes data moves only when it never reached the node, so it is never applied twice. -- A request that times out is not sent again. The next request goes to the next node. -- Over a tunnel, only the first node is used. - -## Connection settings - -| Field | Description | -|-------|-------------| -| **Hosts** | One or more nodes of the cluster, `localhost:9200` for a local node. A host without a port uses `9200`, a URL without one uses `443` or `80` | -| **Auth Method** | Username & Password, API Key, or None | -| **API Key** | Base64-encoded key. Shown when **Auth Method** is API Key | -| **Skip TLS Verification** | Options tab. Trusts any certificate, even under **Verify CA** or **Verify Identity** | - -## Authentication - -| Auth Method | What is sent | -|-------------|--------------| -| **Username & Password** | HTTP Basic, from the standard credential fields | -| **API Key** | An `Authorization: ApiKey` header carrying the field verbatim. Paste the `encoded` value the create API key request returns, or your own base64 of `id:api_key` | -| **None** | Nothing. For a cluster with security off | - -8.x ships with security and TLS on, so **None** is a 7.x or dev-cluster setting. - -## Browsing indices - -The sidebar lists indices as tables, hiding names that begin with `.`. Browsing an alias, or any name that resolves to several indices, shows the union of their mappings. - -Columns come from the index mapping. `object` fields flatten to dotted paths such as `address.city`. A `nested` field gets one JSON column for the whole array of objects, plus a dotted column per leaf (`identifiers.type`) whose cell is a JSON array with one entry per object, `null` where an object omits the field. Array and object values render as JSON in the cell. Every document also carries `_id`, `_index` and `_score`, and `_id` is the primary key. Type an `_id` into a new row to name the document; any other edit to the three stops the save. - -Column filters translate to `term`, `range`, `wildcard`, `terms` and `exists`. Two filter rows on leaves of the same array match two different objects unless you set both rows to the same element. The **Raw SQL** filter column is the exception: its text goes over as a `query_string`, so write Lucene there, `name:Widget` or `price:>10`, matched across all fields. Sorting a `text` field targets its `.keyword` subfield when the mapping has one. A nested leaf sorts and filters; its JSON parent column does neither. - -Grid edits become REST calls keyed by `_id`: `POST /index/_update/{id}`, `PUT /index/_doc/{id}` and `DELETE /index/_doc/{id}`. Edit a nested array through its JSON parent column. A value typed into one of its leaf columns, in an existing document or a new one, cannot be written, so Save stops, names that column, and sends nothing until you undo the edit. - -Deleting an index from the sidebar sends `DELETE /`, the request the confirmation shows. - -Export reads the whole index with a point-in-time and `search_after`, so it is not capped at the 10,000-document result window. - -## Query DSL console - - - Elasticsearch Query DSL console with a search result rendered as a grid - Elasticsearch Query DSL console with a search result rendered as a grid - - -```http -GET /my-index/_search -{ - "query": { - "match": { "title": "search term" } - } -} -``` - -Search responses render as a grid, `GET /_cat/indices?format=json` and other array responses tabularize, and anything else is shown as formatted JSON. - -## SSL/TLS - -New connections start on **Disabled**, plain HTTP with no fallback to anything else. Every other mode goes over HTTPS: **Preferred** and **Required (skip verify)** accept a self-signed certificate, **Verify CA** and **Verify Identity** check it. See [SSL/TLS](/connections/ssl). - -## Limitations - -- The SQL endpoint (`_sql`) is not reachable. Use the console. -- The sidebar lists concrete indices only, and hides every name beginning with `.`. Aliases and data streams do not appear; address them by name in the console. -- Sorting on `_id`, or on a `text` field with no `.keyword` subfield, does nothing: the column is dropped from the sort and the rows keep the order they had. Add the subfield, or sort a keyword field. -- Paging past 10,000 documents switches to `search_after` over a point-in-time. That threshold is fixed, so an index with a lowered `max_result_window` errors before the switch; raise the index setting back to 10,000. -- An array or object cell is cut at 10,000 characters and ends in `...`. Saving an edit to a cut cell stores the fragment; change long values in the console. -- Truncate is not offered. The nearest thing Elasticsearch has is `_delete_by_query`, which runs asynchronously and reports conflicts per document. Run it in the console. -- No mapping or schema editing, no transactions, no import. -- OpenSearch is not supported. - -## Troubleshooting - -### Authentication failed: … - -The username and password, or the API key, were rejected. On 8.x check the connection is on HTTPS at all: security is on by default there. - -### Connection failed: … - -A TLS or network failure. For a self-signed certificate set **SSL Mode** to **Required (skip verify)**, or turn on **Skip TLS Verification**. - -## Related - -- [Import & Export](/features/import-export) -- [Filtering](/features/filtering) diff --git a/docs/databases/etcd.mdx b/docs/databases/etcd.mdx deleted file mode 100644 index c266e167cb..0000000000 --- a/docs/databases/etcd.mdx +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: etcd -description: Connect to etcd v3 clusters with prefix-based key browsing and an etcdctl-style command editor ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -etcd 3.2 and later need nothing configured beyond a host and the client port, 2379. The peer port on 2380 serves no HTTP/JSON gateway, so a connection aimed there never opens. - - - -## Quick setup - -Click **New Connection…**, select **etcd**, enter host and port, then click **Save & Connect**. etcd has no databases, so there is no Database field and nothing to switch between. - -## Connection settings - -| Field | Description | -|-------|-------------| -| **Host** | etcd server hostname or IP. `localhost` by default | -| **Port** | Client port, `2379` by default | -| **Username / Password** | The etcd user to authenticate as. Leave both empty on a server that has no authentication | - -### Options tab fields - -| Field | Description | -|-------|-------------| -| **Key Prefix Root** | Scope browsing to a subtree, `/app`. Empty browses the whole keyspace | -| **TLS Mode** | Disabled, Required (skip verify), Verify CA, or Verify Identity | -| **CA Certificate** | Path to `ca.pem`, for Verify CA and Verify Identity | -| **Client Certificate** / **Client Key** | Paths to the client certificate and private key, for mutual TLS. PEM or DER, with an unencrypted RSA or EC key. Only used with both set | - -## Authentication - -Fill in **Username** and **Password** once the server has run `auth enable`. Credentials against a server that has authentication off still connect, unauthenticated, the way `etcdctl --user` does. - -The user does not need the `root` role. A user whose roles cover part of the keyspace connects and browses what those roles allow. `endpoint status` and the `user` and `role` commands do need `root`, and answer `etcdserver: permission denied` without it. On etcd 3.6.0 through 3.6.11 that also leaves the database size blank. - -Running `auth enable` from the command editor turns authentication on for the whole cluster. A connection that already holds a username and password keeps working; one that holds neither cannot, and has to be reopened with credentials. - -## Connection URL - -```text -etcd://127.0.0.1:2379 -etcds://127.0.0.1:2379 -``` - -Both schemes import. Neither switches TLS on by itself; set **TLS Mode** on the Options tab. - -## Browsing keys - -The sidebar groups keys by their first path segment under the **Key Prefix Root**, with segment-less keys under **(root)**. The grid gives one row per key: **Key**, **Value**, **Version**, **ModRevision**, **CreateRevision** and **Lease**. Key is the primary key. The three revision columns belong to etcd: an edit to one stops the save and names the column. - -Saving edits generates commands. Changing **Value** or **Lease** re-`put`s the key. Setting **Value** to NULL stores an empty value, and setting **Lease** to NULL removes the lease. Changing **Key** is a `put` at the new name and a `del` of the old one, so the old key is deleted rather than moved. A new row whose key does not start with `/` gets the **Key Prefix Root** prepended. - -## Command editor - -The editor takes etcdctl-style commands, with autocomplete over both commands and flags. - - - etcd command editor with a get --prefix result grid - etcd command editor with a get --prefix result grid - - -```bash -get /app/config --prefix --limit=50 --keys-only -put /app/session/abc hello --lease=694d77aa1e775e08 -del /app/cache --prefix -watch /app/config --prefix --timeout=60 -lease grant 300 -compaction 1000 --physical -``` - -The whole vocabulary is `get`, `put`, `del` (or `delete`), `watch`, `lease grant|revoke|timetolive|list|keep-alive`, `member list`, `endpoint status|health`, `compaction`, `auth enable|disable`, `user add|delete|list|grant-role|revoke-role`, and `role add|delete|list`. Anything else, an etcdctl command with no entry here included, comes back as `Unknown command: …` without reaching the server. - -| Command | Flags | -|---------|-------| -| `get` | `--prefix`, `--limit=N`, `--keys-only`, `--order=ASCEND` or `DESCEND`, `--sort-by=KEY`, `VERSION`, `CREATE`, `MOD` or `VALUE`. Leave the last two out and the server's own ordering applies | -| `put` | `--lease=ID` | -| `del` | `--prefix` | -| `watch` | `--prefix`, `--timeout=SECONDS`, 30 by default. Events collect for that long, then arrive as a grid | -| `lease timetolive` | `--keys` | - -Lease IDs take decimal or hex, and hex is detected by its letters. Prefix an all-digit hex ID with `0x` (`--lease=0x69407712`) or it reads as decimal and resolves to another lease. - -## SSL/TLS - -TLS here is set by **TLS Mode** on the Options tab, not by the connection's SSL mode, and it starts **Disabled**. **Required (skip verify)** encrypts without checking the certificate, **Verify CA** checks it against the **CA Certificate** you name, and **Verify Identity** adds the hostname check. **Client Certificate** and **Client Key** cover mutual TLS under either verifying mode. See [SSL/TLS](/connections/ssl) for the concepts. - -The **CA Certificate** may be PEM or DER. **Verify CA** needs one, since it checks no hostname, and a connection without it is refused. **Verify Identity** with no **CA Certificate** checks the system trust store. A file that cannot be read fails the connection rather than falling back to the system trust store. - -## Limitations - -- No transactions. Each command, and each row you save, is its own gateway request. -- The form takes one endpoint and does not fail over to another member. Point it at a member that is up; `member list` shows the rest. -- A value that is not valid UTF-8 shows in the grid as `` wrapping its base64, and text you type is sent as UTF-8. Write binary values with `etcdctl`. -- No schema editing and no import. - -## Troubleshooting - -### No etcd v3 API found - -The gateway answered 404 on `/v3`, `/v3beta` and `/v3alpha`. Point the connection at the client port, 2379 by default, and not the peer port on 2380. - -### The server answered but is not an etcd v3 JSON gateway - -Something is listening on that port and it is not etcd. A reverse proxy that rewrites 404 into a branded page does this too. - -### This etcd server has authentication enabled - -The server wants a token and the connection carries no credentials. Add a **Username** and **Password** and connect again. - -### etcdserver: authentication failed, invalid user ID or password - -etcd rejected the credentials. Check them with `etcdctl --user=name:password endpoint health`. - -### "Verify CA needs a CA certificate" - -**TLS Mode** is Verify CA and **CA Certificate** is empty. Choose the CA certificate that signed the server's certificate, or set **TLS Mode** to **Verify Identity** to check it against the system trust store. - -### etcdserver: permission denied - -The credentials were accepted and the user's roles do not cover that key or command. `endpoint status`, `user` and `role` need the `root` role. - -## Related - -- [SSH Tunneling](/connections/ssh-tunneling), for reaching a cluster through a bastion host -- [Import & Export](/features/import-export) diff --git a/docs/databases/index.mdx b/docs/databases/index.mdx deleted file mode 100644 index 98491091d0..0000000000 --- a/docs/databases/index.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Supported Databases -description: All 37 engines TablePro connects to, their default ports, and which ones need a plugin ---- - -import DriverCounts from "/snippets/driver-counts.mdx"; - -Thirty-seven engines, and every one of them is free to use. What differs between them is where the -driver comes from, not what the license covers. - - - -## The full list - -| Database | Default port | Driver | -|---|---|---| -| [Amazon Redshift](/databases/redshift) | 5439 | Built-in | -| [Beancount](/databases/beancount) | File-based | Plugin | -| [BigQuery](/databases/bigquery) | API-based | Plugin | -| [Cassandra](/databases/cassandra) | 9042 | Plugin | -| [ClickHouse](/databases/clickhouse) | 8123 | Built-in | -| [Cloudflare D1](/databases/cloudflare-d1) | API-based | Plugin | -| [Cloudflare R2 SQL](/databases/cloudflare-r2-sql) | API-based | Plugin | -| [CockroachDB](/databases/cockroachdb) | 26257 | Built-in | -| [Dameng DM8](/databases/dameng) | 5236 | Plugin | -| [Databend](/databases/databend) | 3307 | Built-in | -| [DuckDB](/databases/duckdb) | File, or 9494 remote | Plugin | -| [DynamoDB](/databases/dynamodb) | API-based | Plugin | -| [Elasticsearch](/databases/elasticsearch) | 9200 | Plugin | -| [etcd](/databases/etcd) | 2379 | Plugin | -| [libSQL](/databases/libsql) | API-based | Plugin | -| [Kafka](/databases/kafka) | 9092 | Plugin | -| [MariaDB](/databases/mariadb) | 3306 | Built-in | -| [Microsoft SQL Server](/databases/mssql) | 1433 | Plugin | -| [MongoDB](/databases/mongodb) | 27017 | Plugin | -| [MySQL](/databases/mysql) | 3306 | Built-in | -| [OceanBase](/databases/oceanbase) | 2881 | Built-in | -| [Oracle Database](/databases/oracle) | 1521 | Plugin | -| [PGlite](/databases/pglite) | 5432 | Built-in | -| [PostgreSQL](/databases/postgresql) | 5432 | Built-in | -| [Redis](/databases/redis) | 6379 | Built-in | -| [SAP HANA](/databases/sap-hana) | 443 | Plugin | -| [ScyllaDB](/databases/cassandra) | 9042 | Plugin | -| [Snowflake](/databases/snowflake) | 443 | Plugin | -| [Spanner](/databases/spanner) | API-based | Plugin | -| [SQLite](/databases/sqlite) | File-based | Built-in | -| [SurrealDB](/databases/surrealdb) | 8000 | Plugin | -| [Teradata](/databases/teradata) | 1025 | Plugin | -| [TiDB](/databases/tidb) | 4000 | Built-in | -| [Trino](/databases/trino) | 8080 | Plugin | -| [Typesense](/databases/typesense) | 8108 | Plugin | -| [Turso](/databases/libsql) | API-based | Plugin | -| [Weaviate](/databases/weaviate) | 8080 | Plugin | - -Rows sharing a page share a driver. MariaDB reads as MySQL, ScyllaDB as Cassandra, Turso as libSQL, -and Redshift, CockroachDB and PGlite all speak the PostgreSQL wire protocol. TiDB, Databend and -OceanBase also run on the bundled MySQL driver. - -## Built-in against plugin - -A built-in driver is already in the app and connects with nothing else installed. A plugin is a -signed bundle TablePro fetches the first time you pick that engine, before the connection form -opens and without a restart. After that the two behave the same: same grid, same editor, same -export. - -The split is about size, not capability. Bundling every client library would put hundreds of -megabytes of Oracle and MongoDB code in an app most people use for one database. - -Plugins update on their own schedule, so a driver fix can reach you without waiting for an app -release. [Plugins & Themes](/features/plugins) covers installing, updating and removing them. - -## When yours is not here - -Two of these engines already stand in for others. Anything that speaks the PostgreSQL or MySQL wire -protocol will usually connect through those drivers, including managed services that never say so: -Neon, Supabase, Heroku, Aurora and Cloud SQL all work this way. Point the connection at the -host they give you. - -If it speaks neither, open a request on -[GitHub](https://github.com/TableProApp/TablePro/issues). Drivers are plugins, so a new one is a -bundle rather than a new version of the app. diff --git a/docs/databases/kafka.mdx b/docs/databases/kafka.mdx deleted file mode 100644 index cf2a795f34..0000000000 --- a/docs/databases/kafka.mdx +++ /dev/null @@ -1,203 +0,0 @@ ---- -title: Kafka -description: Browse Kafka topics and messages in the grid, check consumer group lag, and produce test messages ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -A log is not a table. Topics are tables here and a message is a row, but the log has no sort, no -`WHERE`, and no way to change a message once it is written, so the grid drops those affordances -instead of pretending to honor them. What replaces them is a start position: read the newest -hundred messages, or the oldest, or everything after an offset or a timestamp. - -## Quick setup - -Click **New Connection…**, select **Kafka**, enter a broker under **Bootstrap Servers**, and -connect. Any broker in the cluster works as the bootstrap: the rest are discovered from it. The driver speaks -Kafka's binary protocol directly and needs Kafka 0.11 or later, the release that introduced the -record format every message has used since. - - - -## Connection settings - -| Field | Description | -|-------|-------------| -| **Bootstrap Servers** | Brokers to bootstrap from, tried in order until one answers. Default port `9092` | -| **Security Protocol** | `PLAINTEXT`, `SSL`, `SASL_PLAINTEXT`, or `SASL_SSL` | -| **SASL Mechanism** | `PLAIN`, `SCRAM-SHA-256`, or `SCRAM-SHA-512`, for the two SASL protocols | -| **Username** / **Password** | The SASL credentials | -| **Broker Addresses** | Whether to dial the addresses the cluster advertises, or stay on the bootstrap. Leave this on **Use the addresses the cluster advertises** unless those addresses are unreachable from this Mac | - -There is no Database field. A connection is one cluster, and the sidebar shows its topics directly. -Set the whole attempt's **Connect timeout** under **Options**; an empty field uses 30 seconds. - -## Connection URL - -```text -kafka://user:pass@broker:9092 -``` - -The scheme is registered with macOS, so a link like that opens the connection form pre-filled. See -[Connection URL Reference](/connections/urls). - -## Authentication - -Leave **Security Protocol** on `PLAINTEXT` for a broker with no authentication, which is the usual -local Docker setup. Managed Kafka nearly always wants `SASL_SSL`. - -| Mechanism | Use it for | -|-------|-------------| -| **PLAIN** | Confluent Cloud, and any broker configured with a username and password | -| **SCRAM-SHA-256** | Self-hosted brokers with SCRAM credentials, and Amazon MSK | -| **SCRAM-SHA-512** | The same, where the cluster was set up for SHA-512 | - -Pick `SCRAM-SHA-512` when the cluster offers both. If the mechanism is wrong the broker names the -ones it accepts in the error. - -## Reading messages - -Opening a topic shows its most recent hundred messages, newest last. Columns are `partition`, -`offset`, `timestamp`, `key`, `value`, `headers`, `key_size`, and `value_size`. - -Messages from different partitions are interleaved by timestamp. Kafka orders messages only within a -partition, so that ordering is a reading convenience and not a property of the log; `partition` and -`offset` together are what identify a message. - -A `key` or `value` that is not valid UTF-8 is shown as hex. A null value is a tombstone and renders -empty but distinct from a zero-length message, which matters on a compacted topic. - -## KafkaQL - -The query editor takes a small command language. - -```text -CONSUME "orders" FROM NEWEST LIMIT 100 -CONSUME "orders" FROM OLDEST LIMIT 500 -CONSUME "orders" FROM OFFSET 1000 LIMIT 100 -CONSUME "orders" FROM TIME "2026-08-01T00:00:00Z" LIMIT 100 -CONSUME "orders" PARTITION (0,2) FROM NEWEST LIMIT 50 -``` - -`FROM TIME` takes an ISO 8601 instant or milliseconds since the epoch, and resolves to the first -message at or after it in each partition. - -```text -SHOW TOPICS -SHOW BROKERS -SHOW GROUPS -SHOW CLUSTER -DESCRIBE TOPIC "orders" -DESCRIBE GROUP "order-processor" -``` - -`DESCRIBE GROUP` is the lag report: committed offset, end offset, and the gap, per partition. A -partition the group has never committed shows an empty lag rather than a number counted from zero. -A group the cluster does not hold is reported by name instead of coming back empty. - -`SHOW GROUPS` asks every broker, because a broker knows only the groups it coordinates. When one -broker cannot be reached the list is marked truncated rather than presented as the whole cluster's. - -In `SHOW BROKERS`, **takes_admin_requests** marks the broker a `DROP TOPIC` goes to. On a KRaft -cluster any broker forwards an admin request, and the one marked here moves between runs. - -## Producing a message - -```text -PRODUCE INTO "orders" KEY "order-1" VALUE "{\"id\":1}" -PRODUCE INTO "orders" VALUE "no key" PARTITION 2 -PRODUCE INTO "orders" KEY "k" VALUE "v" HEADER "source" "tablepro" -``` - -Without `PARTITION`, a keyed message is placed by the same hash Kafka's own producer uses, so it -lands with the rest of that key's messages. A message with no key goes to partition 0. - -## Compression - -Messages are decompressed as they are read. gzip, snappy, lz4, and zstd all work, and no setting -selects them: each batch declares its own codec and brokers hand back whatever the producer stored. - -## SSL/TLS - -**Security Protocol** decides whether the connection is encrypted, and the SSL mode decides how -strictly the certificate is checked. Setting the protocol to `SSL` or `SASL_SSL` with no mode chosen -verifies the chain and the hostname. A private CA goes in the **CA Certificate** field; mutual TLS -also needs the client certificate and key. - -**Verify CA** checks the chain against the certificate in **CA Certificate** and skips the hostname, -so it needs that certificate: without one, the connection is refused before any broker is contacted. - -Setting the protocol to `PLAINTEXT` or `SASL_PLAINTEXT` turns encryption off whatever the SSL mode -says, so a mode left over from an earlier edit cannot silently encrypt a plaintext listener. - -## Limitations - -Editing a cell, deleting a row, and sorting a column are all unavailable, and the grid dims them -rather than failing at save time. Kafka has no update primitive, no per-message delete, and no -server-side sort. To change what a consumer sees, produce a new message. - -Deleting a topic is available, from the sidebar or with `DROP TOPIC `. The request goes to -the cluster controller, which accepts it and removes the log directories afterwards, so the topic -can still appear in the list for a moment. Truncate is not offered: Kafka removes records by -retention or by an offset per partition, neither of which empties a topic the way Truncate means. - -Paging jumps are unavailable. Page two continues from where page one stopped, so a topic being -written to while you read it will not repeat or skip a message. A tail browse pages backwards -through the log, one page older each time. - -Row counts are approximate. The count is the end offset minus the start offset, which overcounts -where retention has removed messages from the middle of a compacted topic. - -Schema Registry is not read. A topic whose values are Avro or Protobuf shows the raw bytes, -including the five-byte Confluent wire-format prefix. - -Creating a topic, editing topic configuration, and resetting consumer group offsets are not -available. Deleting a topic is, as the paragraph above describes. - -## Troubleshooting - -### Could not reach the Kafka cluster: the connection was refused - -Nothing is listening on that address. Check the port: `9092` is the usual plaintext listener and -`9093` is often the controller, which does not answer client requests. - -### Could not reach the Kafka cluster: the TLS handshake failed - -**Security Protocol** is `SSL` or `SASL_SSL` but the port is a plaintext listener. Brokers expose -one protocol per port, so use the port configured for TLS. - -### Kafka authentication failed: the broker does not offer PLAIN, only … - -The mechanism does not match the cluster. Set **SASL Mechanism** to one of the names in the message. - -### The broker rejected ListOffsets for partition …: this broker no longer leads the partition - -The partition moved to another broker while the request was in flight, and it moved again before -the retry. Run `DESCRIBE TOPIC "…"` to see the current leader of each partition. A cluster that is -rebalancing settles on its own; one that keeps moving has a broker leaving and rejoining. - -### Broker … advertises …, which could not be reached: … - -The address in the message is what the cluster told this Mac to use, and nothing is listening there. -Check that the broker's `advertised.listeners` names an address reachable from here, not a -container hostname or a private IP. Where it cannot be changed, set **Broker Addresses** to only use -the bootstrap address. - -### Partition … of … is led by another broker - -Only the leader of a partition can answer for it, and this connection is pinned to one broker. -Set **Broker Addresses** back to the advertised addresses, or forward every broker's port through -the tunnel so the advertised addresses resolve. - -### Requests time out, or a topic loads on a tunnel but its messages do not - -The cluster is advertising addresses that are not reachable from here. Behind an -[SSH tunnel](/connections/ssh-tunneling) this is handled: the driver is pinned to the bootstrap -address automatically. On a direct connection to brokers behind NAT or inside Docker, set **Broker -Addresses** to only use the bootstrap address. - -## Related - -- [SSH Tunneling](/connections/ssh-tunneling) -- [SSL/TLS](/connections/ssl) -- [Plugins & Themes](/features/plugins) diff --git a/docs/databases/libsql.mdx b/docs/databases/libsql.mdx deleted file mode 100644 index b6355c4f65..0000000000 --- a/docs/databases/libsql.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: libSQL / Turso -description: Connect to Turso cloud databases, self-hosted sqld, or local libSQL files ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -**Connection Mode** decides every other field on the form. Remote speaks the Hrana HTTP protocol to Turso or a self-hosted sqld; Local File opens a libSQL file with the SQLite engine the driver carries. - - - -## Quick setup - -Click **New Connection…**, select **libSQL / Turso**, pick a **Connection Mode**, fill in the fields it leaves on screen, then click **Test Connection** and **Save & Connect**. - - - libSQL connection form showing Remote (Turso) and Local File modes - libSQL connection form showing Remote (Turso) and Local File modes - - -## Connection settings - -### Remote (Turso) - -| Field | Description | -|-------|-------------| -| **Database URL** | `https://your-db-name.turso.io`, or `http://localhost:8080` for sqld. A `libsql://` URL is rewritten to `https://`, and trailing slashes are dropped | -| **API Token** | The bearer token, in the password field, labeled **API Token** for this driver. Leave it empty for a sqld started without `--auth-jwt-key-file`. Stored in the macOS Keychain | - -There is no host, no port and no Database field: the URL names one database and the connection stays on it. Any Turso database works, as does any sqld serving the Hrana v2 pipeline endpoint. - -### Local File - -| Field | Description | -|-------|-------------| -| **Database File** | Path to the file. **Browse…** picks one, **New…** names one that does not exist yet, and `~` expands. A path that does not exist is created, along with any missing folders | - -The API Token field is hidden here; a local file needs no authentication. **Options** gains an **Extensions** list in this mode, which loads SQLite extensions the way it does for [SQLite](/databases/sqlite#extensions). - - -Do not open a file that is being synced as a Turso embedded replica while the owning app is running. Concurrent access to a syncing replica can corrupt it. - - -## Connection URL - -```text -libsql://your-db-name.turso.io -https://your-db-name.turso.io -``` - -Importing either creates a Remote connection. See [Connection URL Reference](/connections/urls). - -## Getting a token - -Take the URL and a fresh token from the Turso [dashboard](https://turso.tech/app) or the CLI: - -```bash -turso db show --url -turso db tokens create -``` - -A self-hosted sqld answers on `http://localhost:8080` by default. With JWT authentication on, generate a token matching the key the server started with. - -## Schema and SQL - -libSQL takes SQLite syntax, so browsing, `EXPLAIN QUERY PLAN`, data editing and export behave as they do on [SQLite](/databases/sqlite). The structure editor creates tables, adds and drops columns, and adds and drops indexes; **Triggers** in the Structure tab creates, edits and drops those. - -## SSL/TLS - -There is no **SSL Mode**. A Remote connection is encrypted when its **Database URL** starts with `https://` and is not when it starts with `http://`, and a local file is not networked at all. See [SSL/TLS](/connections/ssl). - -## Limitations - -- Remote mode runs no transaction. Each statement is its own HTTP request and auto-commits, and `BEGIN` fails with `Transactions are not supported in this mode`. Use Local File mode for transactional work. -- Local File mode does not sync. The file opens standalone, with no embedded-replica sync to a remote Turso database. Reach the remote copy through Remote mode. -- libSQL-only SQL such as `ALTER TABLE ALTER COLUMN` does not run in Local File mode. The SQLite engine gets the statement and rejects it. Rewrite it as standard SQLite DDL. -- Remote mode loads no extensions. A Turso database runs the extensions its server was built with. -- Encrypted libSQL files do not open in Local File mode. -- The structure editor cannot rename a column, change its type, nullability or default, or edit a foreign key. Recreate the table instead. -- One connection, one database. A switch returns `Switching databases is not supported`, and there is no create or drop. -- No bulk import, no SSH tunnel, no custom TLS settings. Load data with the Turso CLI or a direct sqld import. - -## Troubleshooting - -### Authentication failed - -The full message is `Authentication failed. Check your auth token.` The server answered 401 or 403. Generate a new token with `turso db tokens create `; for sqld, check the JWT matches the configured key. Watch for whitespace picked up in the paste. - -### Server not found - -`Server not found. Check your database URL.` is a 404. The URL wants the bare origin, `https://your-db.turso.io` or `http://localhost:8080`, with no path. Confirm the database exists with `turso db list`. - -### Rate limited - -`Rate limited. Retry after … seconds.` means Turso is throttling the connection. Wait out the interval it names, then retry. - -## Related - -- [SQLite](/databases/sqlite) -- [Import & Export](/features/import-export) diff --git a/docs/databases/mariadb.mdx b/docs/databases/mariadb.mdx deleted file mode 100644 index 56846202a3..0000000000 --- a/docs/databases/mariadb.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: MariaDB -description: Connect to MariaDB 10.x and later through the bundled MySQL driver ---- - -MySQL and MariaDB load the same bundled driver, so [MySQL](/databases/mysql) carries the setup. Port `3306`, an optional Database field, `Cmd+K` to change database in place, SSL/TLS starting on **Preferred**, [AWS IAM](/connections/aws-iam), SSH tunnels, and a [Unix socket](/databases/mysql#unix-socket) for a server on this Mac. MariaDB 10.x and later are supported. The rest of this page is what differs. - -## Connection URL - -Both schemes open a connection; `mariadb://` creates a MariaDB connection, `mysql://` a MySQL one: - -```text -mariadb://user:password@host:3306/database -mariadb://user:password@localhost/database?socket=%2Ftmp%2Fmysql.sock -``` - -`socket` works as it does on [MySQL](/databases/mysql#connection-url). See [Connection URL Reference](/connections/urls). - -## Differences from MySQL - -- The default authentication plugin is `mysql_native_password`, against `caching_sha2_password` on MySQL 8. Both connect. -- MariaDB stores JSON in a LONGTEXT column. The driver reads MariaDB's extended field attributes and types those columns as JSON, so their values open in the JSON editor instead of as plain text. -- Virtual and persistent columns are detected on every version, including the bare `VIRTUAL` and `PERSISTENT` spelling that 10.1 and older report, and are left out of generated INSERT and UPDATE statements. -- A query timeout goes in as `SET SESSION max_statement_time`, counted in seconds. MySQL takes `max_execution_time` in milliseconds. -- 10.2.1 to 10.2.21 and 10.3.0 to 10.3.9 enforce check constraints without publishing an `information_schema.CHECK_CONSTRAINTS` to read them from. The [Constraints tab](/features/table-structure#constraints-tab) takes them out of `SHOW CREATE TABLE` on those releases and out of the catalog everywhere else. -- Sequences appear in the sidebar's Tables section as their own kind, read-only, with **Drop Sequence** on the menu. See [Sequences and system-versioned tables](/features/table-operations#sequences-and-system-versioned-tables). -- A table declared `WITH SYSTEM VERSIONING` is listed as an ordinary table and loses **Truncate** alone, which the server refuses with error 4137. -- A temporary table shadowing a base table of the same name is left out of the listing, so the name appears once. - - - JSON cell editor popover - JSON cell editor popover - - -## Limitations - -- Cloud SQL Auth Proxy is not offered for MariaDB. That transport appears for MySQL, PostgreSQL and SQL Server only. Reach a server that is not directly routable through an [SSH tunnel](/connections/ssh-tunneling). -- [Users & Roles](/features/users-roles) reads `mysql.user` and reports every row there as a user, never as a role. Create and grant MariaDB roles with SQL in the editor. - -## Related - -- [MySQL](/databases/mysql), for connection fields, common setups, SSL/TLS and troubleshooting -- [SSH Tunneling](/connections/ssh-tunneling) diff --git a/docs/databases/mongodb.mdx b/docs/databases/mongodb.mdx deleted file mode 100644 index b36025a345..0000000000 --- a/docs/databases/mongodb.mdx +++ /dev/null @@ -1,416 +0,0 @@ ---- -title: MongoDB -description: Connect to MongoDB and run mongosh JavaScript, browse collections, and set up Atlas SRV automatically ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -The query tab is a JavaScript shell. Write `db.orders.find({status: "new"})` the way mongosh takes it, unquoted keys and all, and keep going into variables, loops and `forEach`. Collections appear as tables in the sidebar, with top-level fields as columns and nested objects as formatted JSON. - - - -## Quick setup - - - - Click **New Connection…**, select **MongoDB**, and enter hosts and credentials - - - Click **Test Connection** to verify, then **Save & Connect** - - - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Hosts** | `localhost:27017` | Several `host:port` pairs for a replica set | -| **Username** | - | Leave empty for local dev without auth | -| **Password** | - | | -| **Database** | - | Opens straight into that database. Leave it empty to browse them all | -| **Auth Database** | - | Options tab. Where your user account lives, usually `admin` | -| **Auth Mechanism** | Default | SCRAM-SHA-1, SCRAM-SHA-256, X.509, or AWS IAM, under Authentication | - -Naming a **Database** skips listing every database on the server, which is worth doing on a cluster with hundreds. Leave it empty and the first non-system database opens instead. `Cmd+K` switches either way, on the same connection, with no reconnect. - -**Auth Database** is a separate question: it says where your account is defined, not what you browse. Left empty, it follows the **Database** field. An account defined in `admin` needs **Auth Database** set to `admin` whenever **Database** names something else, or authentication fails. SRV connections authenticate against `admin` regardless, unless told otherwise. Switching databases in the app never changes it, so browsing a database your user has no account in is fine. - -Also on the Options tab: **Read Preference**, **Write Concern**, **Use SRV Record**, **Replica Set** name, and **Legacy UUID Encoding**. The server has to be MongoDB 4.0 or later; the driver refuses to connect to anything older. - -**Write Concern** covers every insert, update and delete, from a query tab or from a save in the data grid, unless the statement passes its own `writeConcern`. Index, collection and database commands, and `db.runCommand`, go to the server as written. On a server that is not a replica set, pick **Default**, **Majority** or **1**: with **2** or **3** every write fails with `cannot use 'w' > 1 when a host is not replicated`. `journal` and `wtimeoutMS` in a pasted connection URL go out with every write too. - - - MongoDB connection form with the multi-host Hosts editor - MongoDB connection form with the multi-host Hosts editor - - -The database list is requested as authorized databases only, so an account without the `listDatabases` privilege still sees what it can read. - -## Connection URL - -```text -mongodb://user:password@host:27017/database?authSource=admin -mongodb+srv://user:password@cluster.mongodb.net/database -``` - -`mongodb+srv://` resolves hosts through DNS SRV records and takes no port; one pasted into the field is stripped before connecting. Plain `mongodb://` keeps whatever port you give it. See [Connection URL Reference](/connections/urls). - -## MongoDB Atlas (SRV) - -An Atlas connection needs the cluster hostname, a username, and a password. Atlas requires SRV and TLS, so a host ending in `.mongodb.net` gets both turned on for you, TLS only if the SSL mode was still Disabled. Elsewhere the **Use SRV Record** toggle in Advanced does the same job. Add your current IP to the cluster's access list in the Atlas console first: traffic from an address that is not on it times out rather than failing. - -## Replica sets - -The **Hosts** field takes comma-separated pairs (`host1:27017,host2:27017,host3:27017`), the primary is discovered, and writes are routed to it. Set the replica set name on the Options tab. A multi-host URI pastes in directly: - -```text -mongodb://user:pass@host1:27017,host2:27017,host3:27017/db?replicaSet=rs0 -``` - - - -Over an SSH tunnel only the first host is used and the rest of the list is dropped. Replica set discovery and failover are off for that session, with nothing on screen to say so. - - - -## Browsing collections - -Click a collection to page through its documents. The columns are the fields in the documents on screen, then any field the collection's `$jsonSchema` validator declares that none of them hold, so an empty collection still shows the fields it was created with. A top-level ObjectId renders as its hex string. A nested object or array shows as Extended JSON in the order it is stored: `{"$oid": "…"}`, `{"$date": "2024-05-01T10:00:00.123Z"}`, `{"$numberLong": "5"}`, and `3.0` for a double that holds a whole number. - -An edited cell keeps its field's type. A date stays a date, whether typed as `2024-01-02T03:04:05Z`, as `2024-01-02`, or picked in the date picker, which writes your local time. An ObjectId stays an ObjectId, an integer past 2^53 is sent as a 64-bit integer, and a field the validator declares as `string` is written as text even when it reads `123` or `true`. Where the grid has no type to go on, `123` is a number and `{"a": 1}` a document; text that only looks like JSON is written as a string. - -Editing a nested object or array writes only the paths that changed, such as `address.city` or `tags.1`, so every other value in it keeps its type and its place. Adding two keys at once, reordering keys, or changing an array's length writes the whole value instead. A binary cell keeps its BSON subtype when its bytes are edited, and a duplicated or pasted row keeps its binary fields. - -A field whose own name contains a dot, starts with `$` or is `__proto__` is written with `$setField`, which needs MongoDB 5.0 or later. On an older server the save is refused and nothing is sent. - -The filter bar's column picker lists paths inside nested objects and arrays of objects, so `customer.country` and `items.sku` filter directly; a row on an array field chooses **any element** or **same element**, which makes one array entry satisfy every row set to it. See [Filtering](/features/filtering#nested-fields). A field name containing a literal dot is left out of the picker, since MongoDB reads a dot as a path separator; reach it with `$getField` inside `$expr`. - -The Structure tab lists a collection's indexes, and [renames or removes a field](#renaming-and-removing-fields) across the whole collection. Drop an index from a query tab with `db.users.dropIndex("email_1")`. **New Database** asks for a database name and a first collection, both required. **New View** opens a query tab holding a `db.createView("view_name", "source_collection", [pipeline])` template, and editing a view pre-fills `db.runCommand({"collMod": …})`. - -### Views - -Views sit in their own **Views** group in the sidebar and open read-only, with no cell edits, **Add Row** or **Delete**, and no **Rename** or **Truncate** on the context menu. **Show DDL** writes the `db.createView(…)` that makes the view again, pipeline and collation included. **Edit View Definition** opens a `collMod` holding the view's source and pipeline: run it to change the view in place, collation kept. Both write each value in its own BSON type, so the text runs the same in a query tab and in mongosh: `NumberLong("…")` for an Int64, `Double(1.0)` for a whole Double, `ISODate("…")` for a date, `BSONRegExp("…", "…")` for a regular expression. A date before year 1 or after 9999 is written as `new Date(…)`. Export writes a view's documents; import and table transfer skip views. - -A time-series collection is listed with the other collections, and its DDL starts with a `// Time series:` line naming its time and meta fields. `system.views`, `system.profile` and the `system.buckets.*` collections behind time series are marked as system collections, with no **Rename** or **Truncate**. - -### Indexes - -The Structure tab's **Indexes** list shows each index's fields in key order, and its type from the key: - -| Key | Type | -|-----|------| -| `{a: 1}` or `{a: -1}` | **BTREE** | -| `{a: "hashed"}` | **HASH** | -| `{a: "text"}` | **FULLTEXT**, listing the fields in its weights | -| `{loc: "2dsphere"}` | **SPATIAL** | -| `{loc: "2d"}` | **2D** | -| `{"$**": 1}` or `{"tags.$**": 1}` | **WILDCARD** | - -**Show DDL** writes one `createIndex` per index with every option the server reports: TTL, partial filter, collation, text weights, wildcard projection and 2d bounds. Values are written the way a view's are, so run that text in a query tab and the indexes it builds match the originals, value types included. The collation leaves out the server's ICU `version`, so the statement also runs on a server built with a different ICU. - -### Missing fields and null - -A field a document does not have reads **No Field**; a field holding null reads NULL. **Set Value > NULL** stores `null` and keeps the field. To delete a field, right-click the cell and choose **Remove Field**, or choose it from the value menu of the field in the inspector; saving sends `$unset`. A validator that lists the field in `required` refuses the save with `Document failed validation`. - -A new row starts with every field missing, and only the cells you fill in are written, so a row saved untouched inserts a document holding only a generated `_id`. **Duplicate** and **Paste** keep which fields were missing and which held null. Pasted text carries no such distinction, so a `NULL` in it leaves the field out. Undo, discarding, and [restoring a save](/features/change-tracking#restoring-a-save) put a removed field back and take out a field that was filled in. A restore treats a field removed or added since the save as **Changed since the save**, and leaves that document alone. - -**Set Value > NULL** appears only where the validator takes null: the field's type lists `null` or is not declared, and any `enum` on it lists `null` too. A rule over the whole document, such as a top-level `anyOf` or a query operator beside `$jsonSchema`, hides it on every field. A validator set to `validationAction: "warn"` hides it on none. The filter bar's **is NULL** matches a field holding null and a missing field alike, as `{field: null}` does in `mongosh`. To find only documents that lack a field, run `db.users.find({nickname: {$exists: false}})` in a query tab. - -### Binary UUIDs - -A binary subtype 4 field renders as `UUID("8cd003eb-4a25-4324-9332-88fce2da0d1a")`. Subtype 3 is the legacy format, and its bytes do not say which driver wrote them, so it stays `BinData(3, "…")` until **Legacy UUID Encoding** on the connection is set to Java, C#, or Python. Match it to the driver that wrote the data: the wrong choice shows a valid-looking but wrong UUID. - -Once set, the value renders as `LegacyJavaUUID("…")` and reads that way everywhere, filters and MQL export included. Nothing stored is rewritten, `uuidRepresentation=javaLegacy` in a pasted URL sets the same option, and a change takes effect on the next connect. - -## Creating a collection - -Choose **Database > New Table…**. Each row of the grid is a field, and its type is one of the BSON types in the list: `objectId`, `string`, `int`, `long`, `double`, `decimal`, `date`, `bool`, `array`, `object` and the rest. **Create Table** turns the rows into a `$jsonSchema` validator and runs `db.createCollection("articles", {"validator": …})`, which **SQL Preview** shows first. - - - SQL Preview tab of Create Table showing db.createCollection for articles with a $jsonSchema listing _id, title, tags, date and schemaVersion - SQL Preview tab of Create Table showing db.createCollection for articles with a $jsonSchema listing _id, title, tags, date and schemaVersion - - -| Row | Becomes | -|-----|---------| -| **Nullable** NO | The field is in `required`: a document without it, or with `null`, is refused | -| **Nullable** YES | The field is optional and may hold `null` | -| `_id` | Always the key, typed `objectId`. Leave it out and MongoDB still adds one | -| No rows, only a name | A collection with no validator | - -MongoDB enforces the validator with its defaults, `strict` and `error`, so an insert or edit of the wrong type fails with `Document failed validation`. **Primary Key** on any field other than `_id` is refused, and so are field names that start with `$` or contain a dot. - -Each row on the **Indexes** tab runs as a `createIndex` after the collection is made, with its fields in the order listed. **BTREE** is an ascending key, **HASH** is `hashed`, **FULLTEXT** is `text`, and **SPATIAL** is `2dsphere`. A hashed index takes one field and cannot be unique, and a text index cannot be unique either. - -## Inserting documents - -A field exists only in the documents that hold it, so a collection with no documents and no validator shows `_id` alone and has no column to type a field into. Choose **Edit > Insert Document…** to write a whole document instead. It is on a row's context menu too, and on the context menu of an empty grid. - - - Insert Document sheet over the empty events collection, holding a document with a name, a channel, a date, a count and a list of tags - Insert Document sheet over the empty events collection, holding a document with a name, a channel, a date, a count and a list of tags - - -The text is Extended JSON: quote every field name, and write an ObjectId as `{"$oid": "…"}`, a date as `{"$date": "2024-05-01T10:00:00Z"}` and a decimal as `{"$numberDecimal": "1.10"}`. A whole number is stored as a 32-bit integer, or as a 64-bit one when it does not fit; `{"$numberLong": "5"}` stores a small 64-bit integer. A number with a decimal point is a double. Fields are stored in the order written. Leave out `_id` and the server generates one. - -Every field name is stored as typed, an empty name, a dotted name such as `a.b` and a top-level name starting with `$` included. MongoDB before 5.0 refuses the last two kinds. mongosh reads a document holding both `$ref` and `$id` at the top level as a DBRef, so keep that pair for references. - -## Editing documents - -A cell edits one value. To add, rename or remove a field, select one row and choose **Edit > Edit Document…**, or choose **Edit Document…** on the row's context menu. It works on a collection tab with no unsaved grid edits, not on a query tab. - -The sheet reads the document from the server again, so it shows what is stored now rather than what the grid loaded. A value reads as plain JSON when that stores the same type, and keeps its wrapper when it would not: a 64-bit integer small enough for 32 bits stays `{"$numberLong": "5"}`, and a date before 1970 or after 9999 stays `{"$date": {"$numberLong": "…"}}`. - -**Save** replaces the whole document with the text, in the order written. A field left out of the text is removed. A dotted name, or a name starting with `$` inside a subdocument, is stored as that exact name rather than read as a path. `_id` cannot change, and leaving it out keeps it. - -If anyone changed or deleted the document after the sheet opened, nothing is saved and the sheet says so. Copy your text, close the sheet and open the document again. - -## Renaming and removing fields - -In the Structure tab, edit a field's **Name** to rename it, or remove its row to remove the field, then choose **Save Changes**. Each change is one `updateMany` over the collection, and **Preview SQL** shows it: - -```javascript -db.people.updateMany({"status": {"$exists": true}, "state": {"$exists": false}}, {"$rename": {"status": "state"}}) -db.people.updateMany({"tmp": {"$exists": true}}, {"$unset": {"tmp": ""}}) -``` - -A document that already holds the new name keeps its value, and the rename skips it. When the collection's `$jsonSchema` validator declares the field in `properties`, `required` or `dependencies`, the save starts with a `db.runCommand({"collMod": …})` that renames or removes the field there as well, so a collection made with **New Table…** keeps validating. A field only the validator declares, with no document holding it, is removed from the validator. - -With **Write Concern** set on the connection, or `w`, `journal` and `wtimeoutMS` in its connection string, every statement carries it as `{"writeConcern": {"w": "majority"}}`, and **Preview SQL** shows it. `w=0` without `journal=true` goes out as `w: 1`. - -The save is refused before anything is written when: - -| Refused when | What to do | -|---|---| -| An index uses the old or the new name | Drop the index from a query tab. The message gives the `dropIndex` command | -| An Atlas Search or Vector Search index mentions the old or the new name anywhere in its definition, or the server runs `$search` but is too old to list search indexes | Change or drop the search index in Atlas first, then rename the field from a query tab | -| A view's pipeline holds the old name anywhere, even as a plain value such as `{$match: {status: "active"}}` when renaming `active` | Change the view's pipeline first | -| A view or the validator hands the whole document, `$$ROOT` or `$$CURRENT`, to anything but `$replaceWith`, `$replaceRoot` or a `$getField` of one named field, as `{$objectToArray: "$$ROOT"}` or `{$push: "$$ROOT"}` do | Change the view's pipeline, or the validator with `collMod`, first | -| The validator uses the field outside `properties`, `required` and `dependencies`, as in `$expr`, or lists whole documents naming it in `enum` | Change the validator with `collMod` first | -| A `patternProperties` pattern matches the old or the new name, or `additionalProperties` applies to one because `properties` does not declare it | Change the validator with `collMod` first. A field `properties` declares renames as usual | -| A document holds both the old and the new name | Remove one of the two from those documents | -| The validator would reject a document once it changes, or once the save moves a rule onto a name the document already holds | Fix that document or the validator. The message gives its `_id` | -| One save changes the same name twice, as a swap does | Save one change at a time | -| The field is `_id` | Leave it: MongoDB keys every document by `_id` | -| The name starts with `$`, contains a dot, or is `__proto__` | Rewrite those documents from a query tab | -| The collection is a view, a time series collection or a `system.` collection | Change the fields in the collection underneath, if there is one | -| The collection is capped and the new name takes more bytes than the old one | Choose a name no longer than the old one. A longer one makes MongoDB delete the oldest documents to stay within the cap | -| A longer name would take a document past MongoDB's 16 MB limit | Choose a shorter name, or shrink that document. The message gives its `_id` | -| The collection's validator or options changed after the save was prepared, or while its documents were checked | Check the new `collMod` in **Preview SQL**, then choose **Save Changes** again | - -The checks for a document holding both names and for a document the validator would reject run on -**Save Changes** only, never in **Preview SQL**. Under a `moderate` validator, a document the -validator already rejected before the save is left alone. Each reads every document that holds a -changed field and stops at this connection's effective query timeout, or after 10 minutes when the -timeout is **No limit**. Set an override under **Options**, or leave it empty to inherit **Settings > -General**. A check that times out changes nothing. Every check reads the primary, whatever the -connection's **Read Preference**. Once they pass, the save reads the collection's options, indexes -and views again, and stops if the options changed or an index, search index or view now uses the -field. - -An `updateMany` that stops partway, at the query timeout or on a duplicate key, keeps the documents it already changed. Another client can also write the old name while the save runs, so after the last statement the save counts the documents that still hold it, and reports how many when any do. It also reads the indexes, search indexes and views again, and reports one another client made on either name while the save ran. A `collMod` whose write concern was not met stops the save with the server's reason, after the validator was already changed. Either way the change stays queued in the Structure tab: choose **Save Changes** again and it finishes the rest, because each statement skips the documents already done. A document left holding both names stops that second save instead, with the message for that case. Removing a field asks for confirmation, as a dropped column does. - -When the save rewrote the validator, it then looks for a document holding a changed name that the new validator rejects. One another client wrote with only the new name, after the checks and before the `collMod`, passed the old validator and fails the new one, and its next update fails with `Document failed validation`. The save reports its `_id`: fix that document, then choose **Save Changes** again. - -## Writing queries - -Queries run through JavaScriptCore, so a statement is JavaScript and the whole language is -available: object literals with unquoted keys, single-quoted strings, regex literals such as -`/abc/i`, `new Date()`, arithmetic, `//` and `/* */` comments. - -`Date("2020-01-01")` without `new` gives a date rather than the string plain JavaScript would -return, so a filter written that way keeps matching. `new Date` and `instanceof Date` are the -native ones. - -`$type`, `$regex` and `$options` objects reach the server as the operator documents they are -written as, the same as in mongosh: `{tags: {$type: ["array", "null"]}}` and -`{name: {$regex: "^a", $options: "i"}}` filter in a query tab and in the filter bar's -**Raw Filter** row. Such an object is a document everywhere, so `insertOne` stores -`{$regex: "a", $options: "i"}` as an embedded document and MongoDB refuses it inside `$in`. Write -a regular expression value as a literal, `/^a/i`. - -```javascript -db.orders.find({status: "completed"}, {customerId: 1, total: 1}) - .sort({date: -1}) - .limit(20) - -db.sales.aggregate([ - {$match: {date: {$gte: ISODate("2025-01-01")}}}, - {$group: {_id: "$product", totalSales: {$sum: "$amount"}}} -]) -``` - -### Scripts - -The shell is per connection, so a variable or function defined in one statement is there for the -next, in any tab on that connection, until you disconnect. - -```javascript -var stale = []; -db.orders.find({status: "pending"}).forEach(function (order) { - if (order.updatedAt < ISODate("2025-01-01")) { stale.push(order._id); } -}); -print("stale orders: " + stale.length); -``` - -`print` and `printjson` write to the result grid, one row per line, whenever the statement itself -returns no documents. A statement that returns documents shows those instead, with the printed -lines on the status line under the grid. - -### Values - -A number written without a constructor is stored by what it holds: - -| Number | Stored as | -|---|---| -| Whole, from -2147483648 to 2147483647 | int32 | -| Whole, outside int32 up to 9007199254740991 (2^53 - 1) | int64 | -| Whole past 2^53, a fraction, `-0`, `NaN`, `Infinity` | double | - -mongosh stores every whole number outside int32 as a double; write `Double(3000000000)` for the -same result here. Past 2^53 a JavaScript number cannot hold every integer, so `9007199254740993` -is already `9007199254740992` when the statement runs. Pass an exact 64-bit integer as a string: -`Long("9007199254740993")`. Because `-0` is a double, `{$inc: {n: Math.round(-0.2)}}` turns an -int32 field into a double, the same as in mongosh. - -| Constructor | Stores | Takes | -|---|---|---| -| `Int32`, `NumberInt` | int32 | A number or numeric string, cut toward zero | -| `Long`, `NumberLong` | int64 | A string of digits, a number cut toward zero, or `Long(low, high)` from two 32-bit halves | -| `Double` | double | A number | -| `Decimal128`, `NumberDecimal` | decimal128 | A numeric string, `NaN`, `Infinity` or `-Infinity` | -| `Timestamp` | timestamp | `(t, i)`, `{t, i}` or a `Long`, each part from 0 to 4294967295 | -| `BSONRegExp` | regular expression | A pattern string, and options from `i`, `l`, `m`, `s`, `u` and `x` | - -A value out of range throws when the constructor runs. `Int32(2147483648)` and -`NumberLong("9223372036854775808")` throw where mongosh wraps them round to a negative number. - -A document read into a script holds these: - -| Stored | Reads as | Compares | -|---|---|---| -| double | a number | A stored `5.0` makes `doc.qty === 5` true | -| int32 | a number | A stored `5` makes `doc.qty === 5` true | -| int64 | a `Long` | `doc.views == 5` is true and `doc.views === 5` is false; `.toNumber()` gives a number | -| decimal128 | a `Decimal128` | `String(doc.price)` gives the stored digits | -| regular expression | a `RegExp`, or a `BSONRegExp` for the `l` and `x` options and PCRE-only syntax such as `(?i)` | `.test()` works on a `RegExp` | - -Written back, a whole double is stored by the rules for a bare number: `5.0` becomes int32, as in -mongosh, and `3000000000.0` becomes int64 where mongosh keeps a double. Wrap it in `Double()` to -keep it a double. A regular expression goes back exactly as the server sent it, options included. - -### Collection references - -`db.users`, `db["users"]`, or `db.getCollection("users")`. Use `getCollection` for names with dots -or spaces, names starting with a digit, and names that collide with a database method: `db.stats` -and `db["stats"]` both reach the method, because the shell cannot tell which you meant. - -### Cursors - -`find()` and `aggregate()` return a cursor and touch nothing until something reads it. Chain -`sort`, `skip`, `limit`, `projection`, `hint`, `collation`, `maxTimeMS`, `batchSize` and -`allowDiskUse` onto it, then read it with `forEach`, `map`, `toArray`, `hasNext`/`next`, `itcount`, -`count` or `explain`. On an aggregation, `sort`, `skip` and `limit` become `$sort`, `$skip` and -`$limit` stages appended to the pipeline. - -A modifier after the cursor has started throws, the same as mongosh. Split it into two statements, -or set the modifier before the first read. - -### Write options - -`updateOne`, `updateMany`, `replaceOne`, `findOneAndUpdate` and the delete calls take an options -document, and `upsert`, `arrayFilters`, `hint`, `collation`, `returnDocument` and `writeConcern` -reach the server. `insertOne`, `insertMany`, `insert` and `bulkWrite` take `writeConcern` too, and -`insertMany` takes `ordered: false` to go on inserting past a document that fails. -`remove(filter, true)` and `remove(filter, {justOne: true})` delete one matching document. A write -returns the object mongosh returns: `matchedCount`, `modifiedCount`, `upsertedCount` and -`upsertedId` for an update, `deletedCount` for a delete, `insertedId` for an insert. - -```javascript -db.users.updateOne({_id: 1}, {$set: {active: true}}, {upsert: true}) -db.events.insertMany(batch, {ordered: false, writeConcern: {w: "majority"}}) -``` - -`writeConcern` takes mongosh's `journal` and `wtimeoutMS` as well as `j` and `wtimeout`. Naming any -of `w`, `j` or `wtimeout` replaces the connection's **Write Concern** for that write; an empty -`writeConcern: {}` keeps it. With `w: 0` and no `j: true` the server does not report what a write -did, so the result is `{acknowledged: false}` with no counts. `findOneAndUpdate` and the other -find-and-modify calls still return the document. - -### When a write stops part-way - -MongoDB keeps whatever a write changed before it failed, timed out or was killed, and undoes -nothing. An `updateMany` that fails on its third document has already changed the first two. The -error names this under the server's message: `insertMany` and `bulkWrite` give the count, and -`updateMany` and `deleteMany` say only that some documents may have changed, as does any write sent -with `w: 0`. A statement that ran several writes counts them all, and so does an export whose query -fails after the writes before it. Check the data before you run the statement again. - -### Methods - -Collection: `find`, `findOne`, `aggregate`, `countDocuments`/`count`, `estimatedDocumentCount`, -`distinct`, `insertOne`/`insertMany`/`insert`, `updateOne`/`updateMany`/`update`, `replaceOne`, -`save`, `deleteOne`/`deleteMany`/`remove`, `findOneAndUpdate`/`findOneAndReplace`/`findOneAndDelete`, -`bulkWrite`, `createIndex`/`createIndexes`, `dropIndex`/`dropIndexes`, `getIndexes`, -`hideIndex`/`unhideIndex`, `drop`, `renameCollection`, `stats`, `dataSize`, `storageSize`, -`totalIndexSize`, `totalSize`, `isCapped`, `validate`, `explain`. - -Database: `getCollection`, `getSiblingDB`, `getCollectionNames`, `getCollectionInfos`, -`createCollection`, `createView`, `dropDatabase`, `stats`, `version`, `serverStatus`, `hostInfo`, -`currentOp`, `killOp`, `runCommand`, `adminCommand`. `use `, `show dbs` and `show collections` work as -typed. Anything with no method of its own goes through `db.runCommand({…})`. - -`Cmd+Shift+F` reformats by nesting depth. Autocomplete offers collections, collection methods, -cursor methods after `find()`, nested field paths such as `address.city`, and the `$` operators -valid at the cursor; see [Autocomplete](/features/autocomplete#mongodb). For a query plan, chain -`.explain("executionStats")` onto the cursor. - -## SSL/TLS - -New connections default to **Disabled**, and the driver has no TLS fallback: **Preferred** behaves exactly as **Required**, which is what the Network tab warns about. For an unencrypted local instance use **Disabled** or [SSH tunneling](/connections/ssh-tunneling). See [SSL/TLS](/connections/ssl). - -## Limitations - -- A row with no `_id` cannot be updated or deleted. The save is refused rather than matched on the remaining fields, and every change stays pending. Keep `_id` in the projection so every row carries one. -- A binary value keeps the subtype it was read with. Bytes typed into a new row are saved as subtype 0 where the validator declares the field `binData`. -- Other bytes refuse the save when their subtype was never read, or was read as two different ones: bytes typed into an existing document, pasted from another collection, or put back by Data Rewind after TablePro restarts. Write that value with a query. -- A field holding text in some documents and objects or arrays in others takes no value that reads as JSON from the grid, since it could be either type: the save is refused. Write it with a query. -- A new row, duplicate or paste cannot hold a field named `__proto__`, or an empty field name at any depth: the save is refused. Use **Remove Field** on that cell, save, then set it on the saved document. -- `_id` is read-only in the grid, the row inspector and **Edit Document…**, and a row added with **Add Row** is inserted without one so the server generates it. To choose your own, use **Insert Document…**. -- **Edit Document…** opens a message instead of the text for a document it cannot write back exactly: a top-level field starting with `$`, a repeated field name, a subdocument shaped like `{"$numberInt": "5"}`, more than 28 levels of nesting, or a guarded save over 16 MB, which a document made mostly of numbers reaches at about 5 MB. Change such a document with `updateOne` in a query tab. -- The server replaces a top-level `Timestamp(0, 0)` with the current time whenever a whole document is written, so **Insert Document…** stores the time there and **Edit Document…** refuses a document holding one. `$set` in a query tab keeps it. -- **Edit Document…** also refuses a document holding a NaN, double or decimal, or JavaScript code with a scope. Change such a document with `updateOne` in a query tab. -- **Edit Document…** needs MongoDB 4.0 or later, and refuses a view and a time-series collection. Edit the collection a view reads, and change time-series documents with `updateMany` in a query tab. -- In a collection whose default collation is not simple, saving a document with a string `_id` reads the whole collection to find it. On a large collection, change the document with `updateOne` in a query tab instead. -- Transactions are not exposed. Statements always run standalone, on any topology. -- A collection takes one text index. A second **FULLTEXT** row fails after the collection and the indexes before it are created: list every text field in one index instead. -- **New Table…** writes the validator with the server's own level and action. To log bad documents instead of refusing them, run `db.runCommand({collMod: "articles", validationAction: "warn"})` after creating the collection. -- Nested paths filter but do not sort. Sorting works on the grid's own columns. -- Filtering, sorting or editing a field named `""` or starting with `$` fails in the grid. A dotted name such as `a.b` reads as the path to `b` inside `a`. Use `$getField` and `$setField` in the query editor. -- **same element** covers a field one array deep. A path through an array inside another array needs nested `$elemMatch`, so those filter with dot notation only. -- GridFS buckets are not browsable, and change streams are unsupported. -- A `Code` scope holding an object that opens with `$type`, `$regex` or `$options`, such as `Code("f", {a: {$type: "binData"}})`, fails with `This is not a document MongoDB can read`. List another key of that object first. -- A script that loops without touching the database cannot be stopped: JavaScriptCore has no public way to interrupt one. `Cmd+.` stops anything that reads, writes or prints, which covers every query. A script silent for 120 seconds is abandoned and the shell restarts. -- Field names that are whole numbers up to 4294967294 (`"0"`, `"12"`) sort ahead of the rest in a document literal, which is what JavaScript does with them. A nested object with such a key after another key, or with a key named `__proto__`, refuses the save when it is duplicated or written whole. Use **Insert Document…** or a query for it. -- `Decimal128("…")` with more than 34 significant digits, or an exponent outside the decimal128 range, fails when the statement runs with "This is not a document MongoDB can read". Round a long value to 34 digits. For an exponent out of range, store the value scaled to a unit that fits, or as a double or a string. -- An MQL export of a view holds the view's documents, not the view, so restoring the file creates a collection of that name. Drop that collection and run the view's **Show DDL** text to get the view back. -- A time-series collection takes inserts and deletes from the grid, but refuses an edited cell and a rename, and the server's error is shown. Change its documents from a query tab with `updateMany` filtered on the meta field. -- A filter or validator with a regular expression under `$regex`, such as `{email: {$regex: /@/i}}`, is refused as a document MongoDB cannot read. Write `{email: /@/i}` or `{email: {$regex: "@", $options: "i"}}` instead. **Show DDL** writes such a validator the way the server holds it, which runs in mongosh but not in a query tab. -- A DBPointer, an `undefined`, or a date more than 100 million days from 1 January 1970 has no mongosh spelling, so **Show DDL** keeps its Extended JSON wrapper. A query tab runs that text; mongosh does not. -- `bulkWrite` runs its operations in order and stops at the first one that fails, even with `ordered: false`. To go on past a failed insert, use `insertMany` with `ordered: false`. -- A field rename checks indexes, search indexes, validators and views in the same database only. Other databases and application code keep the old name: update them after the rename. -- A field rename does not lock the collection. A validator another client sets in the milliseconds between the last check and the save's `collMod` is replaced by it. -- MongoDB before 4.4 cannot measure a document, so a rename to a longer name is not checked against the 16 MB limit there. Rename to a name no longer than the old one on those servers when documents are close to the limit. -- **Compare & Sync** compares MongoDB structures but writes no script for them, because a collection's fields come from a sample of its documents. - -## Troubleshooting - -**Connection refused**: check MongoDB is running (`brew services start mongodb-community`) and that the port and `bindIp` in `mongod.conf` match what you entered. - -**Authentication fails on connect**: the error names the database that was authenticated against. If your user does not live there, set **Auth Database** on the Options tab; otherwise check the username, password, and auth mechanism. The MQL editor does not parse `db.getUsers()` or `db.createUser()`; read users with `db.runCommand({"usersInfo": 1})`. - -**Timeout**: for Atlas, add your IP to the cluster's access list first. Otherwise verify host and port and check the network and firewall. - -**A collection is slow to open**: a sort or filter on an unindexed field makes MongoDB read every document, even for 20 rows. Check the Structure tab for an index on that field. `Cmd+.` stops the query on the server. - -**The row total shows `~`**: that is the instant estimate from collection metadata. The automatic count is capped at 5 seconds and keeps the estimate if the server is slower; **Count Exactly** runs a real count against your query timeout. Views and time-series collections have no metadata count, so their estimate can be missing altogether. diff --git a/docs/databases/mssql.mdx b/docs/databases/mssql.mdx deleted file mode 100644 index ebb5b038bc..0000000000 --- a/docs/databases/mssql.mdx +++ /dev/null @@ -1,196 +0,0 @@ ---- -title: Microsoft SQL Server -description: Connect to SQL Server and Azure SQL Database with the FreeTDS-based driver plugin ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Named instances are not supported. Give the host and the TCP port, `1433` by default, and let the **Authentication** dropdown decide the rest of the form: a SQL Server login, a Kerberos ticket from your domain, or a Microsoft Entra ID work account. The protocol underneath is TDS 7.4, which every server from 2012 on speaks, so Windows, Linux, Docker and Azure SQL Database are all reachable. - - - -## Quick setup - -Click **New Connection…**, select **SQL Server**, fill in the fields, then click **Save & Connect**. For Azure SQL Database, use `yourserver.database.windows.net` and an SSL mode of **Required (skip verify)** or stricter. - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `localhost` | A hostname, or an IP for anything but Kerberos | -| **Port** | `1433` | | -| **Authentication** | SQL Server Authentication | Or Windows Authentication (Kerberos), or Microsoft Entra ID | -| **Username** | - | The SQL Server login. Hidden under the other two methods | -| **Database** | - | Optional, sent during login when set. Required for a login scoped to one database, such as an Azure SQL contained user | -| **Schema** | `dbo` | Selected after connecting. Clear it to take the login's own default | - - - SQL Server connection form with host, port, credentials, database, and schema fields - SQL Server connection form with host, port, credentials, database, and schema fields - - -## Connection URL - -```text -sqlserver://user:password@host:1433/database -mssql://user:password@host:1433/database -``` - -See [Connection URL Reference](/connections/urls) for all parameters. - -## Windows Authentication (Kerberos) - -On macOS, Windows Authentication means Kerberos. Set **Authentication** to **Windows Authentication (Kerberos)**, then either leave **Kerberos Principal** and **Password** blank to use the ticket in your credential cache, or enter `user@REALM.COM` and your domain password to have one requested at connect. Get a cached ticket with `kinit user@REALM.COM`; `klist` shows it. - -Four things have to hold: - -- Connect by hostname, never an IP address. Kerberos targets the service principal name `MSSQLSvc/host.domain.com:1433`, registered against the host name. -- Write the realm in uppercase, usually the DNS domain: `CONTOSO.COM`. -- This Mac has to reach the domain's KDC, through DNS SRV records or `/etc/krb5.conf`. -- The SQL Server service account has to own that SPN. Your administrator registers it. - -For a server in a realm other than your `default_realm`, map its DNS domain and the ticket is requested there instead: - -```ini -[domain_realm] - .sql.example.com = RESOURCE.REALM.COM -``` - -A host with no `[domain_realm]` entry falls back to `default_realm`. TablePro never runs `kinit` or edits `/etc/krb5.conf`. - -## Microsoft Entra ID - -Set **Authentication** to **Microsoft Entra ID** to sign in with a work account instead of a SQL Server login: Azure SQL Database, Azure SQL Managed Instance, and SQL Server 2022 with Entra authentication enabled. - -| Field | Notes | -|-------|-------| -| **Application (Client) ID** | The application registration TablePro signs in as. Required | -| **Directory (Tenant) ID** | Your directory ID or domain. Blank uses the multi-tenant `organizations` authority | - - - - In the Azure portal, under **App registrations**, add one with platform **Mobile and desktop applications**, **Allow public client flows** set to **Yes**, and the delegated permission **Azure SQL Database → user_impersonation**. - - - ```sql - CREATE USER [you@contoso.com] FROM EXTERNAL PROVIDER; - ALTER ROLE db_datareader ADD MEMBER [you@contoso.com]; - ``` - - - Fill in the two fields and click **Test Connection**. A one-time code goes to the clipboard and your browser opens on the sign-in page. Paste it, approve, then test again. - - - -The device code flow honors multifactor authentication and Conditional Access. Tokens land in your login keychain marked device-only, so they never travel through iCloud Keychain, and the access token refreshes in the background. You sign in again only when the refresh token expires or is revoked. - -A sign-in that succeeds and is then refused by the database means no database user matches the token. Run `CREATE USER … FROM EXTERNAL PROVIDER` against the database you are opening. - -## Databases and schemas - -The sidebar nests tables under their schema, hides the built-in role schemas (`db_owner`, `guest`, and the rest), and lists `master`, `tempdb`, `model` and `msdb` only with [**Show system databases and schemas**](/customization/general-settings#tabs-and-sidebar) on. Press `Cmd+K` to change database, system databases included: the switch happens in place, with no reconnect. Opening a table queries it in the schema it is listed under, so a table outside `dbo` needs no switch first. - -INSERTs from the data grid leave IDENTITY columns out, so the server assigns them, and `TEXTSIZE` is raised at connect, so `nvarchar(max)` and `text` values arrive whole rather than cut to FreeTDS's 2048-byte default. - -A `datetimeoffset` keeps its offset, `2024-01-15T10:30:45.1234567+07:00`, so edit it in that form. A `geography`, `geometry` or `hierarchyid` value shows as its bytes in hex; select `col.ToString()` to read it as text. - -## Session options - -Every connection opens with the SET profile SQL Server requires for filtered indexes, indexes on computed columns, indexed views and XML data type methods: - -| Option | Value | -|---|---| -| `ANSI_NULLS`, `ANSI_PADDING`, `ANSI_WARNINGS` | ON | -| `ARITHABORT`, `CONCAT_NULL_YIELDS_NULL`, `QUOTED_IDENTIFIER` | ON | -| `NUMERIC_ROUNDABORT` | OFF | - -FreeTDS leaves all of them at Sybase's defaults, and a session that misses any one of them refuses every write to a table carrying a filtered index or an index on a computed column, with an error naming the SET options rather than the table. - -Two things change in the SQL editor as a result. `col = NULL` matches nothing, so compare with `col IS NULL`. And `'total: ' + @value` is NULL when `@value` is, so wrap it in `ISNULL(@value, '')` to keep the rest of the string. - -## Scripts and batches - -A script run with `Cmd+Shift+Enter`, or a selection of several statements run with `Cmd+Enter`, reaches the server one batch at a time, the way `sqlcmd` and SQL Server Management Studio send it. A line holding only `GO` ends a batch, and a script with no `GO` line is one batch. A variable, a table variable or a `TRY...CATCH` lasts until the end of its batch: - -```sql -DECLARE @sn NVARCHAR(50) = N'2404GQV000066A00105'; - -SELECT * FROM serialnew WHERE [S/N] = @sn; -SELECT * FROM serial_existed WHERE sn_code = @sn; -``` - -Every result set a batch returns opens in its own result tab. In a batch of plain queries each tab is named after its query; a procedure call, a loop or an `IF` numbers them **Result 1**, **Result 2** instead. `PRINT` output and messages up to severity 10 appear in the **Output** view. - -- Give `CREATE PROCEDURE`, `CREATE VIEW`, `CREATE FUNCTION`, `CREATE TRIGGER` and `CREATE SCHEMA` a batch of their own. The server refuses each of them after another statement in the same batch. -- `GO 5` runs the batch before it five times. -- No transaction is opened around a script. Put `BEGIN TRAN` and `COMMIT` in it when a failure has to undo what ran before it. A script that leaves a transaction open ends with a notice saying so. -- The run stops at the first batch that fails, and the error names the line in the editor: `Line 12: Invalid object name 'orders'.` An error raised inside a procedure names the procedure and the line inside it instead. -- `Cmd+Enter` with nothing selected runs only the statement at the insertion point. Select the `DECLARE` along with a statement that reads its variable. - -**File > Import** runs a `.sql` file that holds a `GO` line the same way, `GO 5` included, and never sends a `GO` line. A file with no `GO` line runs one statement at a time, split at each `;`: dumps from TablePro 0.75 and earlier have none and import this way. End such a file with a `GO` line to run it as one batch instead. A failure is reported at the line its batch or statement starts on, and its error names the line in the file: `Failed at line 3. Line 5: Invalid object name 'orders'.` A batch past 64 million characters, about half of what SQL Server takes in one request, ends at its next `;`. - -## SSL/TLS - -New connections start on **Preferred**, which encrypts the login and leaves queries and results in plain TCP unless the server forces encryption. Pick **Required (skip verify)** or stricter to encrypt the whole session. - -| Mode | Behavior | -|---|---| -| **Disabled** | The login over TLS when the server offers it, then plain TCP. A server that forces encryption gets TLS throughout | -| **Preferred** | Same as Disabled | -| **Required (skip verify)** | TLS throughout, certificate unchecked. A server without TLS is refused | -| **Verify CA** | TLS throughout, certificate chained to a trusted authority. The host name is not checked | -| **Verify Identity** | TLS throughout, chain checked, and the certificate names the host you dialled | - -Verification runs against the macOS system roots at `/etc/ssl/cert.pem`, which covers Azure SQL and anything else publicly trusted. See [SSL/TLS](/connections/ssl). - -SQL Server connections can also run through the [Cloud SQL Auth Proxy](/connections/cloud-sql-proxy), which TablePro starts and stops for you. - -## Limitations - -- Named instances are not supported. Give the host and the TCP port that instance listens on. -- A `sql_variant` value shows as NULL, because FreeTDS does not report its base type. Select `CAST(col AS nvarchar(4000))` to read it. -- NTLM is not supported: Windows Authentication on macOS is Kerberos only. -- Entra ID connections are created on the Mac only, through the device code flow only. iPhone and iPad take them over sync and prompt to sign in when you open one. -- The Network tab offers no certificate fields here, and your own `freetds.conf` is not read. A server certificate from a private CA fails Verify CA and Verify Identity: use **Required (skip verify)** for that server. -- A result set from a batch offers **Fetch All**, and sorts on the server, only when its query can run again on its own. One that reads a variable, or comes from a procedure call or a loop, stays at the row limit and sorts the rows already shown: raise **Row cap** in [Settings](/customization/settings), or run the query by itself. -- A batch keeps its first 100 result sets. The statements behind the rest still run, and the status bar says how many result sets were not kept. -- With a transaction open under `SET XACT_ABORT ON`, a query cut at the row limit reads its whole result before any rows appear. Stop ends it, and SQL Server then rolls the transaction back. -- Imported from a file with no `GO` line, a procedure, function or trigger whose body is not wrapped in `BEGIN...END` keeps only the first statement of its body, and the statements after it run on their own. Wrap the body in `BEGIN` and `END`, or give every statement of the file a `GO` line after it. - -## Troubleshooting - -### Failed to connect to host:1433: … - -The tail is FreeTDS's own message. Nothing listening means TCP/IP is off in SQL Server Configuration Manager, the service is down, or port 1433 is closed. A rejected login means the credentials, or a server that takes Windows Authentication only: `SELECT SERVERPROPERTY('IsIntegratedSecurityOnly')` returns `1` for that, and mixed mode goes on in SSMS under Server Properties > Security. A login scoped to one database, an Azure SQL contained user for instance, needs **Database** filled in; without it the server authenticates against `master` and refuses. - -### No Kerberos ticket was found. - -There is no ticket in the credential cache. Run `kinit user@REALM.COM`, or fill in **Kerberos Principal** and **Password**, then reconnect. - -### Server not found in Kerberos database - -Reported as `The SQL Server Kerberos service principal name is not registered.` The server has no SPN, or has one in a realm this Mac does not map the host to. Ask your administrator to register `MSSQLSvc/host.domain.com:1433`, connect by hostname rather than IP, and add a `[domain_realm]` entry for a cross-realm server. - -### This Mac's clock is too far out of sync with the domain controller. - -Turn on **Set time automatically** in System Settings > General > Date & Time, then reconnect. - -### Set the Microsoft Entra ID application (client) ID for this connection. - -The connection carries no client ID. Fill in **Application (Client) ID**. - -### The Microsoft Entra ID sign-in timed out before it was approved. - -The one-time code expired first. Click **Test Connection** to start a new one. - -### Must declare the scalar variable "@…". - -The statement that reads the variable ran in a different batch from its `DECLARE`. Check for a `GO` line between them, or a `Cmd+Enter` that ran only the statement at the insertion point. Select both statements, or run the whole script with `Cmd+Shift+Enter`. - -### AADSTS7000218 - -The app registration does not allow public client flows, which the device code flow needs. Turn it on in the Azure portal. - -## Related - -- [SSL/TLS](/connections/ssl) -- [SSH Tunneling](/connections/ssh-tunneling) -- [Work on Several Databases at Once](/features/table-operations#databases-and-schemas) diff --git a/docs/databases/mysql.mdx b/docs/databases/mysql.mdx deleted file mode 100644 index 7899485901..0000000000 --- a/docs/databases/mysql.mdx +++ /dev/null @@ -1,219 +0,0 @@ ---- -title: MySQL -description: Connect to MySQL 4.1 and later with the bundled MariaDB Connector/C driver ---- - -MySQL 8 accounts on `caching_sha2_password` connect on the first try, with no auth plugin to switch over. The same bundled driver covers every release from 4.1 (see [Older servers](#older-servers)), plus [MariaDB](/databases/mariadb), [TiDB](/databases/tidb), [Databend](/databases/databend), and [OceanBase](/databases/oceanbase). - -## Quick setup - - - - Click **New Connection…** on the welcome window and pick **MySQL** - - - Fill in host, port, and credentials, or pick **Socket** under **Connect Using** for a server on this Mac. Click **Test Connection**, then **Save & Connect** - - - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Connect Using** | **Host and Port** | **Socket** connects through the server's socket file on this Mac. See [Unix socket](#unix-socket) | -| **Host** | `localhost` | Always TCP, `localhost` included | -| **Port** | `3306` | | -| **Socket** | empty | With **Connect Using** set to **Socket**: the full path of the socket file | -| **Username** | empty | An empty field connects as your macOS login name | -| **Password** | empty | Stored in the macOS Keychain | -| **Database** | empty | Optional. Leave it empty and open one with `Cmd+K` | - -Every connection sets its session character set to `utf8mb4` once it logs in, over any `init_connect` -the server runs, so emoji and non-Latin text round-trip untouched. The **Connect timeout** under -**Options** bounds the whole attempt; an empty field uses 30 seconds. - -## Unix socket - -A server on this Mac listens on a socket file as well as a port, and a server started with `skip-networking` listens only there. Set **Connect Using** to **Socket** and enter the file's path. Host and Port leave the form, and **Network** offers only **Direct**: a tunnel cannot reach a file on this Mac. - -The server reports its path: - -```sql -SELECT @@socket; -``` - -| Where | Socket | -|-------|--------| -| Homebrew, MySQL installer | `/tmp/mysql.sock` | -| MAMP | `/Applications/MAMP/tmp/mysql/mysql.sock` | -| Local | `~/Library/Application Support/Local/run//mysql/mysqld.sock` | - -The note under the field says when nothing is at the path or the file there is not a socket. Save still works, so a connection can be set up before its server starts. A path starting with `~` is expanded, and macOS caps a socket path at 103 bytes. - -**SSL Mode** still applies. **Preferred** connects without TLS over a socket, as the `mysql` client does; **Required** and stricter encrypt the connection. Backup and restore reach the server through the same socket. - -## Connection URL - -```text -mysql://user:password@host:3306/database -mysql://user:password@localhost/database?socket=%2Ftmp%2Fmysql.sock -``` - -`socket` replaces host and port. It needs host `localhost` or no host. Opening a URL connects directly. See [Connection URL Reference](/connections/urls#unix-socket). - -## Common setups - -| Where | Settings | -|-------|----------| -| Homebrew | `localhost:3306` | -| Docker | `localhost` with the mapped port, password from `MYSQL_ROOT_PASSWORD` | -| MAMP Pro | `localhost:8889`, user and password `root` | -| AWS RDS / Aurora | Endpoint hostname, password or [AWS IAM](/connections/aws-iam), which signs a fresh 15-minute token on each connect | -| Google Cloud SQL | **Connect via > Cloud SQL Auth Proxy** and the instance name, or the instance IP with a Google Cloud IAM option in **Authentication**. See [Cloud SQL Auth Proxy](/connections/cloud-sql-proxy) | -| Remote production | [SSH tunnel](/connections/ssh-tunneling) | - -## Users & Roles - -**Database > Users & Roles** manages users, roles, and privileges. Changes are staged and shown as SQL before they run. See [Users & Roles](/features/users-roles). - -## Query plans - -`EXPLAIN FORMAT=JSON`, `EXPLAIN FORMAT=TREE`, and `EXPLAIN ANALYZE` render as a plan diagram or tree; plain multi-column `EXPLAIN` stays in the results grid. See [EXPLAIN Visualization](/features/explain-visualization). - - - Visual EXPLAIN plan diagram - Visual EXPLAIN plan diagram - - -## Browsing - -The sidebar lists one database's tables, or every accessible database with **View > Sidebar as Tree**. Switch databases with `Cmd+K`. A connection with an empty **Database** field opens on **No Database Selected** in the flat list: click **Open Database…** to pick one. A tab keeps the database it was opened on; MySQL switches database in place, so that tab runs on the same connection rather than a second one. - -Table and column comments show in the UI: dimmed after a table's name in the sidebar, and in the grid header tooltip. Turn that off under **View Options**, the button beside the sidebar’s filter field. - -A partitioned table shows how many partitions it holds and lists them under it. A `RANGE` partition is labelled `VALUES LESS THAN (2024)` and a `LIST` one `VALUES IN (1,2,3)`; `HASH` and `KEY` partitions state no bound, so their rows carry the position instead. Subpartitions nest under the partition they subdivide. - -A MySQL partition belongs to its table rather than standing as one, so its row offers **Copy Name**, **Copy Bound** and **Copy Table Name** and nothing that writes. Query one with `SELECT * FROM orders PARTITION (p2024)` in the editor. - -## Releasing an idle connection - -A connection holds a server thread and one slot against `max_connections` for as long as it is open, and MySQL's `wait_timeout` rarely reclaims it because the connection check counts as activity on the server. **Release the Server Connection After**, in **Options**, hands the connection back after that many minutes of no queries and takes a new one on the next query. `0`, the default, keeps it. - -To stop the check itself, set **Check connections** to **Only when I use the connection** in Settings > General. That is the connection-wide setting; this one is per connection and closes the connection rather than quietening it. - -Reconnecting costs a TCP connect, the TLS handshake and authentication: measured at 2ms against a server on the same machine and 800ms to 1.9s across the internet. That cost lands on the first query after an idle period, so leave this at `0` for a remote server unless the slot matters more than the wait. - -A release is refused, with the reason, while the session holds anything a reconnect would destroy: an open transaction, a temporary table, a prepared statement, a `GET_LOCK`, `LOCK TABLES`, `FLUSH TABLES WITH READ LOCK`, an open `HANDLER`, a user variable, a changed session setting, a database selected with `USE`, or a stored routine call, whose body is opaque. The statements a dump runs inside `/*!40101 ... */` count, since the server executes them. The transaction comes from the server itself, so one opened by `SET autocommit = 0` or by an `XA START` counts as well. - -## When the server drops the connection - -A read that fails because the server closed the connection is run once more on a new one, and only from a session holding nothing: the same list as above. Every other statement reports the error, because the second session answers from different state. Measured on MySQL 8.4.11, replaying regardless: `SELECT @total` came back `NULL` where it had come back 42, and `SELECT DATABASE()` came back as the connection's own database rather than the one `USE` had selected, neither of them raising anything. `LAST_INSERT_ID()`, `ROW_COUNT()`, `FOUND_ROWS()` and `CONNECTION_ID()` are never replayed either, whatever the session holds: their answer belongs to the connection that is gone. - -A connection with startup commands that run `SET` is holding a changed session setting from the moment it opens, so it reports the error rather than retrying. **Database > Reconnect** takes a new connection and puts back the database and the startup commands. - -Waiting has its own limit, and reaching it is a different case. The socket gives the server the [query timeout](/customization/general-settings#query-timeout) plus 30 seconds, and a statement that outlasts that is never sent again: the first copy is almost certainly still running there. It is stopped with `KILL QUERY` from a second connection, and the error comes back after that. MySQL 4.1 has no `KILL QUERY`; see [Older servers](#older-servers). - -## Older servers - -MySQL 4.1 to 5.1 lack catalogs that newer servers have, so TablePro reads those servers through `SHOW` statements instead. Tables, structure, data, indexes and foreign keys work on all of them. Objects a version cannot have are left out rather than shown as errors: routines before 5.0.2, triggers before 5.0.10, events and partition counts before 5.1.6. A routine's parameters are listed from 5.5.3. **Users & Roles** needs 5.0.2, the release that added `CREATE USER`. - -### Stopping a query on MySQL 4.1 - -Stopping a query closes its connection, because 4.1 has no `KILL QUERY`. The query timeout stops a query the same way. Closing the connection discards the session's open transaction, table locks and variables. When the session held any of those, the next statement fails with a message saying so, and TablePro reconnects before the one after it. Run the discarded statements again. - -### Error messages in other languages - -A server before 5.5 set to a non-English language, such as Korean or Japanese, sends its error messages in that language's character set. TablePro reads the language once connected and shows them correctly. An error while connecting, such as a wrong password or an unknown database, arrives before the language can be read and still shows garbled. - -### Pre-4.1 password hashes - -An account with a pre-4.1 password hash cannot sign in: the connection fails with error 5010 naming `mysql_old_password`. The hash is 16 characters long in `mysql.user`, which servers running with `old_passwords=1` write. Set a new password with `old_passwords` off to switch the account to the 4.1 hash. MySQL 4.0 and older cannot connect at all. - -## Garbled non-Latin text - -A comment or value that reads `メール` where `メール` belongs was written by a client that sent UTF-8 while telling the server it was sending Latin 1. A `mysql` command-line client without a UTF-8 locale does that, and so does a MySQL 5.7 container loading its `docker-entrypoint-initdb.d` scripts, and so does any client on a server whose `init_connect` runs `SET NAMES latin1`. The server stored the garbled form, so every UTF-8 client shows the same thing. - -To work with such a database the way that client did, set **Encoding** to **UTF-8 via Latin 1** in **Options** and reconnect. Text written through Latin 1 then reads correctly, text stored correctly still reads correctly, and whatever you save is stored the way the old client stored it, so the application that wrote the data keeps reading it. - - -With **UTF-8 via Latin 1**, a correctly stored value you edit is saved in the garbled form, and a table or column whose non-Latin name was stored correctly cannot be opened. Use it on a database written through Latin 1, never on one where applications write UTF-8. - - -An SQL export taken with **UTF-8 via Latin 1** holds the text as it reads, in UTF-8. Restoring it gives a database with the text fixed, which the old client then reads as `?`. Restore it where you are moving off that client, not as a backup of the database it still writes to. - -To fix the stored text instead, convert it in place. The `WHERE` clause skips values that were stored correctly: - -```sql -UPDATE orders -SET note = CONVERT(CAST(CONVERT(note USING latin1) AS BINARY) USING utf8mb4) -WHERE note = CONVERT(CONVERT(note USING latin1) USING utf8mb4) - AND CONVERT(CAST(CONVERT(note USING latin1) AS BINARY) USING utf8mb4) IS NOT NULL; -``` - -Table comments take one `ALTER TABLE` each. This query writes them for the current database; run the statements it returns: - -```sql -SELECT CONCAT('ALTER TABLE `', TABLE_NAME, '` COMMENT = ', - QUOTE(CONVERT(CAST(CONVERT(TABLE_COMMENT USING latin1) AS BINARY) USING utf8mb4)), ';') -FROM information_schema.TABLES -WHERE TABLE_SCHEMA = DATABASE() AND TABLE_COMMENT <> '' - AND TABLE_COMMENT = CONVERT(CONVERT(TABLE_COMMENT USING latin1) USING utf8mb4) - AND CONVERT(CAST(CONVERT(TABLE_COMMENT USING latin1) AS BINARY) USING utf8mb4) IS NOT NULL; -``` - -A column comment is part of the column's definition. Copy it from the **Structure** tab while **UTF-8 via Latin 1** is on, then switch back to **UTF-8**, reconnect, and paste it into the same column. - -## Behind a protocol proxy - -DBLE, MyCat and ShardingSphere-Proxy answer `information_schema` from their own configuration rather than from the database in front of you. A logical schema whose name is not a physical database reads back empty there, or with `ERROR 1064`. - -Point the connection at the proxy and browse as usual. Tables, columns, indexes and foreign keys come from `SHOW` statements when the catalog says nothing, and nothing changes on a direct server. The sidebar, the **Structure** tab, schema comparison and the ER diagram all fill in from that. Each whole-schema read costs one statement per table on that path, every time: a sidebar refresh, an ER diagram and a schema compare each pay it again. - -Two fields are unavailable on that path, because only `information_schema` carries them: - -- A generated column's expression. The column still shows as generated. -- A foreign key's `ON DELETE` and `ON UPDATE` where the server prints no clause for them. MySQL 8.0.11 and 8.0.12 print nothing for `RESTRICT`, which reads back as `NO ACTION`. 8.0.13 and later print it. - -## SSL/TLS - -New connections default to **Preferred**: TLS first, dropping to plain text only after an SSL handshake error. Pick **Verify CA** with the provider's certificate for strict validation. See [SSL/TLS](/connections/ssl). - -## Limitations - -- `LOAD DATA LOCAL INFILE` is refused by the driver. Load the file with **File > Import > Import Data…** instead. -- An index key over an expression, such as `lower(email)`, needs MySQL 8.0.13 or later. MariaDB has none, so index a generated column instead. The **Indexes** tab stops the save on either server before anything runs. - -## Troubleshooting - -### Connection refused - -Nothing answered on that host and port. Check the server is running (`brew services start mysql`), the port is right, and `skip-networking` is off. - -### Can't connect to local server through socket '…' (2) - -The number at the end says why the socket did not open: - -| Number | Cause | -|--------|-------| -| `2` | Nothing is at that path. Start the server, or copy the path from `SELECT @@socket;` | -| `61` | The file is there and no server is listening on it, usually left behind by a server that stopped. Start the server | -| `13` | Your macOS account cannot open the file or a folder on its path | - -### Access denied for user '…'@'…' (using password: YES) - -The password is wrong, or no account covers the host you connect from. Sign in with an account that can read the `mysql` schema and run `SHOW GRANTS FOR 'name'@'host';` with the real user name and host. Without that access, the statement fails with `SELECT command denied`. - -### Access denied for user '…'@'…' to database '…' - -The account can see the database in the list but holds no privilege in it, so the server refuses to open it. An account whose only privilege there covers routines, such as `EXECUTE`, opens the database and lists no tables. Check what the server matched and granted: - -```sql -SELECT CURRENT_USER(); -SHOW GRANTS; -``` - -`CURRENT_USER()` can name a different account from the one you typed, such as `'name'@'%'` when `'name'@'localhost'` holds the grants. A role listed in `SHOW GRANTS` grants nothing until it is active, and a new connection starts with its default roles only: ask for `SET DEFAULT ROLE ALL TO 'name'@'host';`. Otherwise ask for `SELECT` on the database. - -### Authentication plugin errors - -`caching_sha2_password` needs no configuration. For any other plugin, read the account's plugin with `SELECT user, plugin FROM mysql.user;`. diff --git a/docs/databases/oceanbase.mdx b/docs/databases/oceanbase.mdx deleted file mode 100644 index a66263a086..0000000000 --- a/docs/databases/oceanbase.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: OceanBase -description: Connect to an OceanBase MySQL-mode tenant through the bundled MySQL driver, directly or through OBProxy ---- - -The tenant goes in the user name: `root@test` on an observer's port 2881, `root@test#obcluster` through OBProxy on 2883. Any OceanBase version in MySQL mode connects. Host, password, SSH tunnels, SSL/TLS and `Cmd+K` database switching work as on [MySQL](/databases/mysql). - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Port** | `2881` | The observer's MySQL port. OBProxy listens on `2883` | -| **Username** | empty | `user@tenant` on an observer, `user@tenant#cluster` through OBProxy. The sys tenant's administrator is `root@sys` | -| **Database** | empty | Optional. Leave it empty and open one in the tenant with `Cmd+K` | -| **SSL Mode** | Preferred | TLS first, plain text if the server refuses | - -**Release the Server Connection After** is on the Options tab. AWS IAM, Cloud SQL Auth Proxy and Unix socket are not offered; reach a private cluster through an [SSH tunnel](/connections/ssh-tunneling). - -## Connection URL - -```text -oceanbase://root%40test:password@host:2881/database -oceanbase://root%40test%23obcluster:password@proxy-host:2883/database -``` - -Write the `@` in the user name as `%40` and the `#` as `%23`. `oceanbase+ssh://` opens it through an SSH tunnel. See [Connection URL Reference](/connections/urls). - -## What differs from MySQL - -- Connecting reads `@@version_comment`. A server that does not answer with `OceanBase` and a version number there is refused, so a wrong host or port fails at connect. -- The sidebar hides `information_schema`, `mysql`, `oceanbase`, `SYS`, `LBACSYS` and `ORAAUDITOR`. A tenant's `test` database stays listed. -- The [query timeout](/customization/general-settings#query-timeout) replaces OceanBase's own 10 second statement limit, and **No limit** removes it. -- CHECK constraints appear in the Structure tab on OceanBase 4.0 and later. Adding one from the Structure tab is not offered. -- Triggers appear in the Structure tab. Adding one from there is not offered. -- `EXPLAIN` shows the plan as text. `EXPLAIN FORMAT=JSON` and `EXPLAIN ANALYZE` are not offered. -- Table Maintenance offers `ANALYZE TABLE` on OceanBase 4.2.2 and later, and nothing on earlier versions. -- External tables list as external tables, and system and virtual tables as system tables. All three are read-only kinds, so their menus drop **Truncate** and **Import**. - -A connection saved as **MySQL** that reaches an OceanBase server gets none of the above, because the MySQL handshake reports `5.7.25` or `5.6.25` and names no engine. Choose **OceanBase** as the connection type. - -## Tables without a primary key - -An OceanBase table declared without a primary key is edited as it is on MySQL: the `UPDATE` or `DELETE` matches every column of the row, and a save that would change more than one row, or none, stops and reports it. See [Change tracking](/features/change-tracking). - -## Limitations - -- Oracle-mode tenants do not connect. Point the connection at a MySQL-mode tenant. -- No Server Dashboard, and **File > Backup Dump…** stays dimmed. -- [Compare & Sync](/features/compare-sync) writes no structure script between OceanBase and MySQL or MariaDB. OceanBase against OceanBase works. -- Tenant and cluster administration is not offered. Create tenants with SQL as `root@sys`. - -## Related - -- [MySQL](/databases/mysql), for connection fields, SSL/TLS, and troubleshooting -- [TiDB](/databases/tidb) -- [SSH Tunneling](/connections/ssh-tunneling) diff --git a/docs/databases/oracle.mdx b/docs/databases/oracle.mdx deleted file mode 100644 index 90b2a9bc11..0000000000 --- a/docs/databases/oracle.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: Oracle Database -description: Connection settings, SSL modes, auth compatibility, and column type support for Oracle ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -The driver speaks TNS in pure Swift, so Instant Client and the rest of Oracle's stack stay off your Mac. Logins work against Oracle Database 11.1 and later, on-premises, in Docker, or in Oracle Cloud. - - -Oracle 10g and earlier cannot connect. Their O3LOGON handshake is not implemented, and there is no setting that works around it. - - -A RAC SCAN listener, a shared server, or a load balancer in front of the database needs no configuration. Neither does Native Network Encryption: the server says in its accept packet whether it wants the negotiation, and when it does, AES with a SHA-2 checksum is offered. Set **Network Encryption** to change what TablePro offers. - - - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `localhost` | | -| **Port** | `1521` | Listener port | -| **Connection Type** | Service Name | Switch to SID for an instance that has no service | -| **Service Name** | - | **Required**. `lsnrctl services` on the server lists what the listener answers for | -| **Username** | - | Username and password only. There is no OS auth | -| **Role** | Normal | SYSDBA or SYSOPER for an administrative logon. The account needs the matching privilege, and the session runs as `SYS` rather than as the user you signed in with | -| **Network Encryption** | Accepted | Matches `SQLNET.ENCRYPTION_CLIENT`, and sets the data-integrity checksum to the same level because the driver negotiates both in one exchange. Accepted offers AES without insisting, which is what Oracle's own clients send. Rejected skips the negotiation, Requested prefers it, Required refuses to connect without it | - -There is no Database field. Each user is a schema, and `Cmd+K` or the **Schema** toolbar button switches the active one. Oracle's own schemas, such as `SYS`, `SYSTEM` and `XDB`, sit at the end of the schema switcher, and the sidebar lists them only with [**Show system databases and schemas**](/customization/general-settings#tabs-and-sidebar) on, or when one is the schema in use. A tab binds to a schema rather than a database, so nothing reconnects when you switch. - - - Oracle connection form with Connection Type and Service Name fields - Oracle connection form with Connection Type and Service Name fields - - -## Connection URL - -```text -oracle://user:password@host:1521/service_name -``` - -See [Connection URL Reference](/connections/urls) for all parameters. - -## Authentication - -Username and password are the only credentials. Every verifier in `dba_users.password_versions` works, `10G` included; external, Kerberos, and LDAP-managed accounts do not connect at all. A `10G`-only account still connects, on DES-based hashing Oracle has deprecated; rotate it with `ALTER USER … IDENTIFIED BY` and check that `password_versions` then holds `11G` or `12C`. - -```sql -CREATE USER app_user IDENTIFIED BY "Password1!"; -GRANT CREATE SESSION, SELECT ANY TABLE TO app_user; -SELECT username, password_versions FROM dba_users WHERE username = 'APP_USER'; -``` - -## Common setups - -| Where | Settings | -|-------|----------| -| Oracle XE, local | `localhost:1521`, user `system`, service `XEPDB1` | -| Docker | `gvenzl/oracle-xe:21-slim`, same as local | -| Remote | Host, port, and credentials as usual; service name from the DBA | -| Oracle Cloud ADB | One-way TLS, no wallet. See [Oracle Cloud (ADB)](#oracle-cloud-adb) | - -A pluggable database has its own service name, and it is not the container's: `XEPDB1` is a PDB inside `XE`, and connecting to the container name gets you the CDB with none of the application schemas. `lsnrctl services` lists both. - -## Oracle Cloud (ADB) - -Copy host, port, and service name (of the form `mydb_tp`) from the **TLS** connection string in the Oracle Cloud Console, not the mTLS one, then set SSL mode to **Required**, or **Verify CA** with the CA certificate. Wallet files are never read, which also makes this the way out of a wallet that has stopped working. - -The database has to accept TLS without mutual TLS: set **Mutual TLS (mTLS) authentication** to *Not Required* and give the database an access control list or a private endpoint. Oracle rejects the connection otherwise. - -## Editing and queries - -Table info carries columns, types, nullability, primary keys, indexes, foreign keys, and CREATE TABLE DDL. The structure editor writes `ALTER TABLE` with double-quoted identifiers for case-sensitive names: ADD, RENAME COLUMN, MODIFY, DROP, and CREATE INDEX. - -The editor runs SQL and PL/SQL, and grid paging is written as `OFFSET … ROWS FETCH NEXT … ROWS ONLY`. Import and export work as they do elsewhere, and a remote server reaches through an [SSH tunnel](/connections/ssh-tunneling). - -A partitioned table shows its partition count and lists the partitions under it, each with its position. `ALL_TAB_PARTITIONS.HIGH_VALUE` is a `LONG` column the driver cannot decode, so an Oracle partition row carries the position rather than the bound. Subpartitions nest under their partition. An interval-partitioned table reports no count: the server's figure there is the range it will extend into, not the partitions that exist. Query one with `SELECT * FROM orders PARTITION (p2024)`. - -## PL/SQL - -A block or a stored unit is one statement, however many semicolons are inside it. Put the cursor anywhere in it and press `Cmd+Enter`, or click its run button in the gutter. - -```sql -DECLARE - total NUMBER; -BEGIN - SELECT COUNT(*) INTO total FROM orders; - DBMS_OUTPUT.PUT_LINE(total); -END; -``` - -The semicolon after a unit's last `END` is sent with it. A plain SQL statement is sent without its semicolon. - -Scripts written for SQL*Plus or SQL Developer run unchanged: a line holding only `/` ends the statement above it and is never sent. Between units the slash is optional, except after `CREATE JAVA SOURCE`, `CREATE MLE MODULE` and a `WRAPPED` unit, which each run to the next `/` line or to the end of the script. - -A `CREATE` whose unit does not compile fails with Oracle's own errors, one per line with its position, instead of reporting success. The unit stays in the schema as INVALID until a version that compiles replaces it. - -`DBMS_OUTPUT.PUT_LINE` lines show with the result of the statement that printed them. See [Server output](/features/query-results#server-output). - -In a trigger body, `:NEW` and `:OLD` are left as written. In an anonymous block, `:name` is a [query parameter](/features/query-parameters) filled from the panel. - -An anonymous block, or a query whose `WITH` clause declares a function, runs code on the server. One that drops or truncates, including through `EXECUTE IMMEDIATE '…'`, raises the [dangerous query warning](/features/safe-mode) before it runs, and a Read-Only connection refuses it like any write. [MCP clients](/developers/mcp-tools) and the AI assistant cannot send either. - -## Transactions - -A statement commits the moment it succeeds, and another session sees the change at once. A `SELECT ... FOR UPDATE` holds its row locks until the next statement you run that is not a query. To hold changes until you decide, start with `SET TRANSACTION`, `SAVEPOINT` or `LOCK TABLE`. Everything after it waits in one transaction until a `COMMIT` or `ROLLBACK`, and `ROLLBACK TO` a savepoint keeps the rest of it open. - -```sql -SAVEPOINT before_raise; -UPDATE emp SET sal = sal * 1.1 WHERE deptno = 20; -SELECT ename, sal FROM emp WHERE deptno = 20; -ROLLBACK TO before_raise; -COMMIT; -``` - -Run the lines one at a time with `Cmd+Enter`, or all of them with `Cmd+Shift+Enter`. While the transaction is open, a grid save and a batch run inside it and leave the commit to you; see [which transaction a batch runs in](/features/sql-editor#which-transaction-the-batch-runs-in). A `CREATE`, `ALTER`, `DROP` or `TRUNCATE` commits the open transaction before it runs, so a `ROLLBACK` after one takes back only what came later. - -If the connection drops while a transaction is open, the next write fails with "The connection was lost while a transaction was open" and runs nothing. Check which of the transaction's changes were saved before running them again. - -## Column type support - -The Structure tab and the table DDL show each column's type as it was declared: `NVARCHAR2(100)`, `VARCHAR2(50 CHAR)`, `NUMBER(*,2)`, `TIMESTAMP(6) WITH TIME ZONE`, `REF "HR"."ADDRESS_T"`. `VARCHAR2` and `CHAR` always name their unit, `BYTE` or `CHAR`, so a copied definition means the same column whatever `NLS_LENGTH_SEMANTICS` the target session uses. - -NCHAR, NVARCHAR2 and NCLOB read the same under either national character set Oracle allows, `AL16UTF16` or `UTF8`. A grid save writes text that is not ASCII into them as `UNISTR('\0EA5...')`, so it keeps characters the database character set lacks. - -| Oracle type | Display | -|---|---| -| VARCHAR2, NVARCHAR2, CHAR, NCHAR, LONG, CLOB, NCLOB | Text as stored | -| JSON | JSON text, fields in stored order | -| NUMBER | Exact digits as stored | -| BINARY_FLOAT, BINARY_DOUBLE | Native Swift Float/Double | -| DATE | `YYYY-MM-DD HH:MM:SS` | -| TIMESTAMP(p) | `YYYY-MM-DD HH:MM:SS` plus exactly p fractional digits | -| TIMESTAMP WITH TIME ZONE | As TIMESTAMP, followed by the value's own offset, such as `+02:00` | -| TIMESTAMP WITH LOCAL TIME ZONE | As TIMESTAMP, in the session time zone, followed by that zone's offset | -| INTERVAL DAY TO SECOND | `D HH:MM:SS` plus fractional seconds when non-zero (up to 9 digits, trailing zeros trimmed) | -| INTERVAL YEAR TO MONTH | `Y-MM` | -| RAW, LONG RAW, BLOB | Lowercase hex, truncated to 4 KB | -| ROWID, UROWID | As Oracle prints it, including the `*` form of an index-organized table's ROWID | -| BOOLEAN | `true` / `false` | -| BFILE | `BFILENAME('DIRECTORY', 'file')` | -| REF | `` placeholder | -| Object types, such as XMLTYPE and SDO_GEOMETRY | `` placeholder; a NULL object shows as NULL | - -To read a geometry as text, select `SDO_UTIL.TO_WKTGEOMETRY(column)` instead of the column. A `TIMESTAMP WITH TIME ZONE` stored with a region name such as `Europe/Paris` shows ``; select `TO_CHAR(column, 'YYYY-MM-DD HH24:MI:SS.FF TZR')` to read it. - -Grid edits, deletes and filters spell dates and timestamps in a form that does not depend on the session's `NLS_DATE_FORMAT`, so they work whatever it is set to. A table without a primary key matches the row by every column, and an edit is refused when that match would need a LOB, LONG, BFILE, XMLTYPE, JSON, VECTOR or object value, since Oracle cannot compare those. - -Anything else renders as ``. Report those through [GitHub Issues](https://github.com/TableProApp/TablePro/issues). - -## SSL/TLS - -New connections default to **Disabled**, and there is no opportunistic upgrade: **Preferred** connects in plain TCP, which is why the Network tab flags it in red. **Required** is TCPS. **Verify CA** checks the server's certificate against the file in **CA Certificate** and skips the hostname, so it needs a CA certificate: without one, the connection is refused. **Verify Identity** checks the hostname as well. See [SSL/TLS](/connections/ssl). - -## On iPhone and iPad - -The Oracle driver is compiled into TablePro Mobile, with no plugin to install. Browsing, queries, and row editing work; creating or altering tables and triggers stays on the Mac. See [iPhone and iPad](/ios). - -The SSL mode offers no certificate rows there, so **Verify Identity** checks the server against the system trust store and **Verify CA** is not listed for a new connection. A connection saved with **Verify CA** does not connect on iPhone or iPad until its mode changes. - -## Limitations - -- No OS auth, wallets, Kerberos, or LDAP. Create a database user with a password and connect as that. -- BFILE columns show the directory and file name, never the file's contents. -- SQL*Plus commands such as `SET SERVEROUTPUT ON`, `SHOW ERRORS` and `EXEC` are not SQL and fail when sent. Delete them, and write `EXEC p` as `BEGIN p; END;`. Output needs no `SET SERVEROUTPUT ON`. -- No Users & Roles pane. Manage accounts with `CREATE USER` and `GRANT` in the editor. -- On a database whose character set is not Unicode, filters, imports, Copy To and copied SQL still send NCHAR, NVARCHAR2 and NCLOB text through that character set, so a character it lacks becomes `¿`. Write such values with `UNISTR` in the editor. - -## Troubleshooting - -**PROCEDURE … was created with compilation errors**: the unit is stored but does not compile. Each line after the first gives the line and column in the unit's source and the `PLS-` error there. Fix the source and run the `CREATE OR REPLACE` again. - -**Connection refused**: check the listener is up with `lsnrctl status`, that port 1521 is open, and, on Docker, that the container is running (`docker start oracle-xe`). - -**Invalid service name**: the listener does not answer for that name. List what it does answer for with `lsnrctl services`. - -**Unsupported Password Verifier**: the account's verifier is one the driver cannot use, usually a new one such as 23ai. Read `password_versions` for that user and file an issue with the verifier flag the dialog shows. - -**Server Version Not Supported**: the server is 10g or earlier. Nothing on this side helps; the database has to be 11.1 or later. - -**Login Handshake Timed Out**: TCP connected but the login never finished, so the attempt stops when -the connection's **Connect timeout** expires. An empty field under **Options** uses 30 seconds. The -dialog names the step the server stopped answering at. If that step is network encryption, set -**Network Encryption** to Rejected and connect again. Otherwise confirm the host and port reach the -listener, and look for a firewall, VPN, or proxy that stalls traffic once the TCP handshake is done. - -**Connection Dropped During Handshake**: the server closed the connection mid-login, and the dialog names the phase it stopped at (`advancedNegotiation`, `dataTypeNegotiation`, `authentication`). Look for a firewall, VPN, or proxy that resets traffic, and confirm the host and port reach the listener itself. - -**Native Network Encryption Not Completed**: the server requires an encryption or checksum algorithm the driver does not offer. Ask the DBA which ones are set and file an issue with the reason the dialog shows. TLS is the alternative: set an SSL mode instead. - -**Native Network Encryption Required**: the server requires the negotiation but did not offer it on this connection. Ask the DBA to enable it for this client, or set an SSL mode and use TLS instead. diff --git a/docs/databases/pglite.mdx b/docs/databases/pglite.mdx deleted file mode 100644 index 2822d0004f..0000000000 --- a/docs/databases/pglite.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: PGlite -description: Connect to a PGlite database over its socket server using the PostgreSQL wire protocol ---- - -[PGlite](https://pglite.dev) is PostgreSQL 17 compiled to WebAssembly, and it runs inside a JavaScript process instead of listening on a socket of its own. Nothing can reach it until you start `@electric-sql/pglite-socket` yourself. After that the connection is ordinary [PostgreSQL](/databases/postgresql), served by the same driver, with the same introspection, DDL, and EXPLAIN, and nothing extra to install. - - - PGlite connection form - PGlite connection form - - -## Start the socket server - -It listens on `127.0.0.1:5432` unless `--port` says otherwise. Leave it running for as long as the connection is open. - -```bash -# In-memory database, gone when the server stops -npx @electric-sql/pglite-socket - -# Persist to a data directory -npx @electric-sql/pglite-socket --db=./my-pgdata -``` - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `127.0.0.1` | The socket server binds loopback IPv4, not `localhost` | -| **Port** | `5432` | Match the server's `--port` | -| **Database** | `postgres` | The single database PGlite exposes | -| **Username** | `postgres` | Trust auth; any username connects | - -The socket server never checks a password, so the form has no password field, and it has no TLS, so SSL Mode is fixed to Disabled. Fill in the form and click **Save & Connect**. - -## Connection URL - -```text -pglite://postgres@127.0.0.1:5432/postgres -``` - -See [Connection URL Reference](/connections/urls) for all parameters. - -## What you get - -Real PostgreSQL 17, so `pg_catalog` and `information_schema` are complete: table and view definitions, indexes, foreign keys, structure editing, and the visual `EXPLAIN` and `EXPLAIN ANALYZE` plan tree all behave as they do on a server. The default schema is `public`, and `postgres` is the only entry in the `Cmd+K` database list. - -## Limitations - -- One connection at a time. A second client, or a second TablePro window on the same server, is refused: close the other one first. -- No cross-database tabs. A tab bound to a database the connection is not on refuses to run and names that database instead of guessing. -- Cancel has no effect. `Cmd+.` returns without stopping anything and the query runs to completion, so keep exploratory queries small. -- No TLS. SSL Mode is fixed to Disabled, and the form has no Network tab. - -## Troubleshooting - -**Can't reach a PGlite socket server**: nothing is listening at that address. Start it with `npx @electric-sql/pglite-socket`, and check the host is `127.0.0.1` rather than `localhost`, which can resolve to IPv6 first. - -**Too many connections**: something else is already talking to the socket server. Close that client and connect again. diff --git a/docs/databases/postgresql.mdx b/docs/databases/postgresql.mdx deleted file mode 100644 index 3dc5a9d271..0000000000 --- a/docs/databases/postgresql.mdx +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: PostgreSQL -description: Connect to PostgreSQL 9.1 and later with the libpq driver, including PostGIS display, AWS and Google Cloud IAM auth, and pg_dump backups ---- - -import DeclaredColumnType from "/snippets/declared-column-type.mdx"; - -Unlike MySQL, PostgreSQL will not connect without a **Database**, and a connection to the server serves that one database. Everything else on the form is ordinary. The libpq driver ships inside the app and also serves [Amazon Redshift](/databases/redshift), [CockroachDB](/databases/cockroachdb), and [PGlite](/databases/pglite). - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | `localhost` | | -| **Port** | `5432` | | -| **Username** | - | Not pre-filled; most servers use `postgres` | -| **Database** | - | **Required** | - -Fill in the form and click **Save & Connect**. For remote servers use [SSH tunneling](/connections/ssh-tunneling); Google Cloud SQL instances can connect through the [Cloud SQL Auth Proxy](/connections/cloud-sql-proxy). - -Any server from 9.1 on works. A feature the server predates stays out of the sidebar and the structure editor: - -| Feature | Needs | -|---|---| -| Materialized views | 9.3 | -| BRIN indexes, the **Bypass row level security** role attribute | 9.5 | -| Declarative partitioning, identity columns, renaming an enum label | 10 | -| Generated columns | 12 | - - - PostgreSQL connection form - PostgreSQL connection form - - -## Connection URL - -```text -postgresql://user:password@host:5432/database -``` - -`postgres://` also works. Opening a URL connects directly. See [Connection URL Reference](/connections/urls). - -## Authentication - -### AWS IAM - -Set **Authentication** to an AWS IAM option and set **Username** to a database role granted `rds_iam`. See [AWS IAM Authentication](/connections/aws-iam). - -### Google Cloud IAM - -For a Cloud SQL instance you reach directly, set **Authentication** to a Google Cloud IAM option and set **Username** to the IAM database user. See [IAM sign-in without the proxy](/connections/cloud-sql-proxy#iam-sign-in-without-the-proxy). - -### Password file - -Turn on **Use Password File** to read the password from `~/.pgpass` instead of typing one. Entries are `hostname:port:database:username:password`, `*` matches any field, first match wins. The form reports what it found, including `~/.pgpass has incorrect permissions (needs chmod 0600)`, the one that silently blocks a connect. - -## Databases and schemas - -Every database on the server is listed, `postgres` included; `template0` and `template1` are not. The sidebar shows every schema you have access to, and in each one the tables, views, materialized views and foreign tables you own or hold any privilege on, a column privilege included. To list fewer schemas, choose **View > Filter Schemas**; see [Filtering schemas](/features/connection-window#filtering-schemas). The toolbar's centred control names the active database: click it, or press `Cmd+K`, for the list. The active schema has a picker at the foot of the object list in the flat layout, and **Database > Schema** everywhere. - -Right-click a schema to drop it. The statement is `DROP SCHEMA … CASCADE`, so views and functions in *other* schemas that depend on it go too; the confirmation says so before it runs. - -### Creating and editing a schema - -**New Schema…** and **Edit Schema…** on a schema row build `CREATE SCHEMA … AUTHORIZATION`, `ALTER SCHEMA … OWNER TO`, `COMMENT ON SCHEMA`, and the `GRANT` and `REVOKE` for `USAGE` and `CREATE`. The sheet shows them before it runs them. See [Creating and editing a schema](/features/table-operations#creating-and-editing-a-schema). - -**Owner** lists the roles on the server; leave it at **Database default** to let the connecting role own it. Changing it needs you to be a member of the new owning role, and the server's own refusal appears under the form. - -A grant to `PUBLIC` is a grant to every user on the server. A role literally named `public` is a different grantee and the grid keeps the two apart. - -Rename from the same sheet, or in place: select the row and press Return. - -`pg_catalog`, `information_schema` and `pg_toast` are refused for rename, edit and drop. - -A partitioned table is listed once, with how many partitions it holds beside its name. Expand it for the partitions, each labelled with its bound: `FROM ('2024-01-01') TO ('2024-02-01')`, `IN ('de', 'fr')`, `WITH (modulus 4, remainder 0)`, or `DEFAULT`. Expand a partition again if it is subpartitioned. - -A partition is a relation, so opening, truncating, renaming and dropping one work as they do on any table. A partition declared in another schema, `CREATE TABLE archive.orders_2023 PARTITION OF public.orders`, is reached under its parent and carries `archive` into every statement built from it. - -Tables using the older `INHERITS` inheritance are listed normally, each child on its own. Turn partitions off with **Partitions** under **View Options**. - -## Column types - - - -That is what the [Structure tab](/features/table-structure) shows, what the [MCP tools](/developers/mcp-tools) report as `data_type`, and what the AI context and autocomplete carry. An enum column keeps its value picker, and a domain column keeps the editor, formatting and sorting of the type underneath it. - -`jsonb` renders as formatted JSON, and `uuid`, `inet`, `timestamp with time zone`, `interval` and `bytea` display natively. PostGIS `geometry` and `geography` render as EWKT with the SRID kept, `SRID=4326;POINT(-73 40.7237)`, rather than raw EWKB hex; a value that fails to convert stays hex. - -An array column opens one of three editors, decided by its element type: - -| Element type | Editor | -|---|---| -| `text[]`, `integer[]`, `numeric[]`, `uuid[]`, `boolean[]`, `timestamptz[]`, enum arrays such as `mood[]` | A list, one row per element | -| `jsonb[]` and `json[]` | The same list, with the selected element in the [JSON viewer](/features/json-viewer) | -| `bytea[]`, composite arrays, and any multi-dimensional array | The plain text editor over the `{…}` literal | - -In the list editor, reorder rows with the arrows, add and remove elements, and set a single element to NULL; an empty array and a NULL column stay distinct. Enum elements pick from the labels the type declares, and a label the type no longer lists stays selectable and is flagged. **Edit as Text** switches to the raw literal at any time. - -A `jsonb[]` element gets the Text and Tree modes a `jsonb` cell gets, so the column reads the same browsing a table as it does through `to_jsonb(col)` in a query. Two things that query cannot show you: a SQL NULL element stays apart from a JSON `null` element, and reading an element does not rewrite it, so a `json[]` cell you open and save keeps the exact text it stored. - -A value the list cannot represent opens the text editor instead: a multi-dimensional value such as `{{1,2},{3,4}}`, or a literal carrying explicit bounds such as `[0:2]={a,b,c}`. Edit the `{…}` literal directly. - -`box[]` is the one column whose elements are not separated by a comma. PostgreSQL uses a semicolon there, because a box's own text contains commas, and the list reads it that way. - -## User-defined types - -Enums, composites, domains and ranges are listed under **Types** in each schema, the `CREATE` statement rebuilt from `pg_type`. An enum's labels are edited in place with `ALTER TYPE … ADD VALUE` and, from PostgreSQL 10, `RENAME VALUE`; PostgreSQL has no statement that drops or reorders a label. The structure editor's type picker offers the schema's types under **User-Defined**. See [User-Defined Types](/features/user-defined-types). - -## Views and comments - -A view's definition is rebuilt to run anywhere. Every table it reads is schema-qualified, and `WITH (security_barrier)`, `WITH (security_invoker)` and `WITH CASCADED CHECK OPTION` are written back, so executing the statement elsewhere keeps the restrictions the original carried. Read it with **Show DDL**, copy it with **Copy DDL**, or edit it with **Edit View Definition**. A materialized view's statement carries its access method, storage parameters and tablespace, and the DDL tab adds its indexes. - -`REFRESH MATERIALIZED VIEW` runs from **Refresh Materialized View…**. A plain refresh holds an `ACCESS EXCLUSIVE` lock, so nothing reads the view until it finishes. **Refresh concurrently** takes `EXCLUSIVE` instead and leaves readers working, and PostgreSQL accepts it only for a populated view with a valid unique index on plain columns, no `WHERE` clause and no expressions; a partial or expression index does not qualify. The refresh runs on its own connection, outside any transaction a query editor holds open. Refreshing needs ownership of the view, or the `MAINTAIN` privilege from PostgreSQL 17. - -**Edit Comment…** writes `COMMENT ON`, picking `TABLE`, `VIEW`, `MATERIALIZED VIEW` or `FOREIGN TABLE` to match the object. Column comments are written from the Columns tab as `COMMENT ON COLUMN`. Clearing the field writes `IS NULL`, which removes the comment. Only the object's owner may comment on it. - -An SQL export writes those statements back for tables, views and materialized views, so a dump and a restore keep their comments. See [Import & Export](/features/import-export). - -The Structure tab of a view, a materialized view or a foreign table offers only the edits PostgreSQL takes on that kind of object, which is fewer than a table's and different for each. See [Table Structure](/features/table-structure#what-each-object-accepts). - -PostgreSQL refuses `INSERT`, `UPDATE` and `DELETE` on a materialized view, so its rows open read-only in the data grid. Edit the tables it reads from, then choose **Refresh Materialized View…**. - -## Invalid indexes - -A `CREATE INDEX CONCURRENTLY` or `REINDEX CONCURRENTLY` that fails or is cancelled leaves an invalid index on the table. The **Indexes** tab names it, and exports leave it out as `pg_dump` does. [Table Structure](/features/table-structure#invalid-indexes) covers dropping or rebuilding it. - -## Cross-database tabs - -PostgreSQL has no in-place `USE`, so every database browsed from the [connections strip](/features/workspace-rail) gets a server connection of its own. Moving between two database entries switches between those connections without reconnecting, and each keeps its open transaction, temp tables, and `SET` values until its entry is closed or the connection ends. Closing the entry or the connection, or disconnecting, asks first when that rolls back an open transaction. A tab runs on the connection of the database it is bound to. - -A tab bound to a database that has no entry runs on a [metadata connection](/connections/connection-form#connection-health) instead. That one is shared with the Structure tab and closes after 10 minutes idle, so open the database from the strip before starting a transaction or a `CREATE TEMP TABLE` there. A database connection that died while idle is reopened on its next use; if it held a transaction, that statement is refused once with a message saying the server rolled it back. Binding itself is on [Tabs](/features/tabs#where-a-tab-points). - -## Sessions on the server - -In `pg_stat_activity`, each database's own connection reports `application_name` as `TablePro`, one per database entry in the strip. The [metadata connections](/connections/connection-form#connection-health) that read the sidebar and the Structure tab, and the ones a tab on a database without an entry runs on, report `TablePro Metadata`. To name them yourself, put `-c application_name=reporting` in **Connection Options**: every connection then uses that name. - -## Text encoding - -Sessions run in `UTF8` whatever encoding the database was created with, and the server converts text both ways. Japanese in an `EUC_JP` database or accented names in a `LATIN1` one read and save intact, and stay that way after `RESET ALL` or `DISCARD ALL` in a query tab. A `-c client_encoding=…` in **Connection Options** is overridden; other settings there still apply. - -## Tools - -`EXPLAIN` and `EXPLAIN ANALYZE` run with `FORMAT JSON` and render as a plan diagram or tree. See [EXPLAIN Visualization](/features/explain-visualization). **Database > Users & Roles** shows where each privilege comes from before you grant or revoke it. **Backup Dump** and **Restore Dump** shell out to your local `pg_dump` and `pg_restore`; see [Backup & Restore](/features/backup-restore). - -## SSL/TLS - -New connections default to **Preferred** (libpq `sslmode=prefer`): TLS first, plain text if the server refuses it. Pick **Verify CA** to validate the server certificate. See [SSL/TLS](/connections/ssl). - -## Limitations - -- Columns cannot be reordered. The structure editor adds, renames, retypes, and drops; changing the order of existing columns means recreating the table. -- Session state belongs to one database. A temp table or an open transaction made on `app` is not visible from a tab on `logs`, and closing the `app` entry ends it, after asking first when a transaction is open. -- Backup and restore need `pg_dump` and `pg_restore` on your Mac. Neither is bundled; install them with Homebrew. `pg_dump` 15 and later refuse PostgreSQL 9.1, so back one up with `pg_dump` 14 or earlier. -- Before PostgreSQL 12, the [`run_maintenance`](/developers/mcp-tools) tool's `REINDEX` with no `table` runs without `CONCURRENTLY` and rebuilds the system catalog indexes as well. While an index rebuilds, writes to its table wait, and so do reads that use that index. Run it when the database is quiet. - -## Troubleshooting - -**Connection refused**: check the server is running, that `listen_addresses` in `postgresql.conf` covers remote connections, and that the firewall allows port 5432. - -**FATAL: password authentication failed for user "…"**: the role and password are checked against `pg_hba.conf`. Confirm the method on the matching line (`scram-sha-256` or `md5` for passwords, `trust` for local dev), and that the line matches the host you are connecting from. - -**ERROR: invalid byte sequence for encoding "UTF8": 0x…**: the database's encoding is `SQL_ASCII`, which stores bytes without checking them, and a value in the result is not UTF-8. Run `SHOW server_encoding` to confirm. Move the data to a `UTF8` database: dump it with `pg_dump --encoding=` and the encoding the text was written in, such as `EUC_JP` or `WIN1252`, then restore that dump into a database created with `ENCODING 'UTF8'`. - -**A Postgres-compatible engine loads no tables**: wire-compatible engines connect under the PostgreSQL type, and the catalogs they omit are probed for rather than assumed. An engine without `pg_matviews` still lists its tables. One missing a catalog, function or column the listing reads loses table comments first, then falls back to `information_schema.tables`: tables and views appear, each partition as a table of its own, and materialized views and foreign tables do not. diff --git a/docs/databases/redis.mdx b/docs/databases/redis.mdx deleted file mode 100644 index 28484f14ab..0000000000 --- a/docs/databases/redis.mdx +++ /dev/null @@ -1,213 +0,0 @@ ---- -title: Redis -description: Browse keys by namespace, manage TTLs, and run Redis commands directly from the editor ---- - -The sidebar splits keys into folders at every `:`, and each key gets a grid row with its type, TTL, length, and value. The editor takes Redis commands, one per statement, and passes them through as written. Redis needs no plugin: its hiredis-based driver is part of the app, and it reaches standalone servers, Sentinel-managed primaries, and Redis Cluster. - -## Quick setup - - - - Click **New Connection…**, select **Redis**, and enter host, port, password, and database index - - - Click **Test Connection**, then **Save & Connect** - - - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Connection Mode** | `Standalone` | `Sentinel` for a Sentinel-managed primary, `Cluster` for a sharded cluster | -| **Host** | `localhost` | Standalone only | -| **Port** | `6379` | Standalone only | -| **Username** | - | Redis 6 ACL user, sent as `AUTH username password`. Empty signs in as `default` | -| **Password** | - | Empty for a server with no `requirepass`, and for a `nopass` ACL user | -| **Database Index** | `0` | Options tab. Type the index or use the stepper; connecting fails with the server's error if it has no such database | -| **Key Separator** | `:` | Options tab. What the sidebar splits key names on | - -No minimum server version. ACL users need Redis 6, a Sentinel ACL user needs 6.2, and Cluster mode reads routing tips from Redis 7 where they exist. - -The sidebar then lists one entry per database, `db0` upward, counted from the server's own `CONFIG GET databases`. ElastiCache, Azure Cache for Redis and Memorystore refuse `CONFIG`, and so do ACL users without `config|get`; those servers are listed as 16 databases, or up to the highest one `INFO keyspace` names as holding keys. A key count left blank means the server would not answer `INFO` for your user. Click a database to browse it in place, which is also how the database index changes after connecting. - -## Connection URL - -```text -redis://:password@host:6379/0 -rediss://:password@host:6380/0 -``` - -`rediss://` connects with TLS. See [Connection URL Reference](/connections/urls). - -## Authentication - -The Username field selects a Redis 6 ACL user. Empty signs in as `default`, the identity a bare `requirepass` password belongs to. - -A username is sent as `AUTH username password` even when Password is empty, which is the form a `nopass` ACL user needs. Every other user rejects that form, so the connection either holds the identity typed into the form or fails. - -A `nopass` user accepts any password, so a leftover value in Password still signs in as that user. - -## Connection modes - -### Standalone - -The default. One server, named by Host and Port. - -### Sentinel - -For a deployment fronted by [Redis Sentinel](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/). The quorum says which node is primary and the data connection opens there; a failover is read from the quorum on the next health check and the connection moves itself. A demoted primary keeps answering `role:master` and keeps accepting writes for several seconds, and those writes are lost, so the quorum is the only signal worth trusting. - -| Field | Notes | -|-------|-------| -| **Sentinel Nodes** | One or more `host:port` entries. Port defaults to `26379` | -| **Primary Group Name** | The name after `sentinel monitor` in `sentinel.conf`, usually `mymaster` | -| **Sentinel Username** | Under Authentication. Only when Sentinel has its own ACL user | -| **Sentinel Password** | Under Authentication. Only when Sentinel has its own password. Kept in the Keychain | - -Host and Port are ignored here. Username and Password under Authentication belong to the primary; Sentinel is a separate plane with its own. The rest of the quorum is discovered at connect and remembered, so a lookup still works once the node you listed goes down. If none can be reached, or none monitors the group you named, the error lists the addresses tried and the groups they do monitor. - -### Cluster - -For a sharded [Redis Cluster](https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/). The shard map is read with `CLUSTER SHARDS`, or `CLUSTER SLOTS` on Redis 6; a connection opens to every primary and each command goes to the shard that owns its key. - -| Field | Notes | -|-------|-------| -| **Cluster Seed Nodes** | One or more `host:port` entries. Any reachable member is enough, the rest are discovered. Port defaults to `6379` | - -`DBSIZE` is summed across shards, browsing merges their keys into one tree, and `MGET`, `MSET`, `DEL`, `EXISTS`, `TOUCH` and `UNLINK` are split per shard and recombined. When one shard refuses its part, or cannot answer because it is loading or busy running a script, the command reports that shard's error instead of a partial total. Redis cannot undo the part of a split write that already ran, so the error lists the keys it changed, or for `FLUSHDB` the nodes. A shard that denies `DBSIZE` leaves the sidebar key count blank, and on a cluster with more than one database each count is `INFO keyspace` summed across primaries. A `MOVED` re-points the slot and retries, an `ASK` retries against the importing node, and one command follows at most five redirects. What Cluster mode cannot do is under [Limitations](#limitations). - -The wrong mode is caught at connect, on Valkey as on Redis: Standalone against a cluster member, or any data mode against a Sentinel port, names the field to change. - -## Amazon ElastiCache (IAM) - -Set **Authentication** to an AWS IAM mode (Access Key, Profile, or SSO). A short-lived IAM token becomes the Redis password and the username is your IAM-enabled Redis user. Enter the AWS region and the cache name (replication group ID), and turn TLS on, which ElastiCache IAM requires. Profiles resolve from `~/.aws/config` and `~/.aws/credentials`, `credential_process`, SSO, and assumed roles included. - -## Browsing keys - -`user:1` and `user:2` sit under a `user` folder, nested as deep as the key goes (`app:cache:session:1`). Change the separator on the Options tab. When the tree cannot load, the server's error takes its place; right-click **Keys** and choose **Refresh** to try again. - - - Redis keys grouped by namespace in the sidebar with values in the data grid - Redis keys grouped by namespace in the sidebar with values in the data grid - - -The grid columns are **Key**, **Type**, **TTL**, **Length**, and **Value**. Value is a preview for everything but a string; Length is what Redis reports, so the two together say how much is missing. - -| Type | Value column | Length column | -|---|---|---| -| String | The entire value, however long | `STRLEN`, in bytes | -| Hash | First 100 fields, as a JSON object | `HLEN` | -| List | First 100 elements, as a JSON array | `LLEN` | -| Set | First 100 members, as a JSON array | `SCARD` | -| Sorted set | First 100 `[member, score]` pairs | `ZCARD` | -| Stream | Newest 5 entries, as `[id, fields]` | `XLEN` | - -A value that is not valid UTF-8, such as a gzip or MessagePack payload, opens in the hex editor instead of as text. - -A **Type**, **TTL**, **Length** or **Value** cell is NULL when the server will not say for your user. ACL key patterns do not filter `SCAN`, so a user limited to `~app:*` still sees every key name, and a **Value** edit on a key whose **Type** is NULL stops the save. - -### Editing - -Editing a **Key** cell runs `RENAME`. Editing a **TTL** cell runs `EXPIRE`, or `PERSIST` when you set it to `-1` or NULL; in that column `-1` means no expiry and `-2` means the key is gone. Editing a **Value** cell runs `SET`, and only on a string, since a preview of a hash or list is not the whole structure. - -Save sends nothing, names the reason and keeps your edits when a row holds an edit these commands cannot make: a **Value** edit on any type but a string, a NULL **Value**, a **Type** edit, or a **TTL** that is not a whole number of seconds above 0. - -Change the other types with a command: `HSET myhash field1 "value1"` rewrites one field and leaves the rest alone. Adding a row does follow the type you pick, generating `HSET`, `RPUSH`, `SADD`, `ZADD`, or `SET`; any other type stops the save. - -### Filtering - -Toggle the filter bar to search keys by pattern. Patterns are Redis glob (`*` any sequence, `?` one character, `[ae]` a character set) and case-sensitive, as in `redis-cli`. The type scope narrows to String, Hash, List, Set, Sorted Set, or Stream. Matching runs server-side with `SCAN MATCH` and `SCAN TYPE`. - -## Redis CLI - -Each statement is one command; separate several with `;`. Commands the driver does not recognize go through too, and only the result formatting is type-aware. A recognized command with arguments its result view does not model, such as `FLUSHDB ASYNC` or `CONFIG SET a 1 b 2`, is also sent exactly as typed, so the server answers it as `redis-cli` would. Arguments are quoted the way `redis-cli` quotes them, so `"` and `'` both work and `\xHH` writes a raw byte; unbalanced quotes are rejected rather than guessed at. Redis has no comment syntax and none is stripped, so a `--` or `#` line is sent to the server and fails. - -```redis -SET mykey "hello" EX 60 -HGETALL myhash; LRANGE mylist 0 -1 -SCAN 0 MATCH user:* COUNT 100 -``` - -`SELECT` moves the editor's session only. The commands after it run on that database, while the sidebar, the key tree and the database tab stay on the database you clicked. Clicking a database moves the editor to that database. - -`DB 3 GET key` runs one command on database 3 and leaves the session where it was. A key opened from the tree is read this way, and so are grid saves on a cluster. - -### MULTI blocks - -A command sent after `MULTI` answers `QUEUED` instead of its own reply, and nothing runs until `EXEC`. `EXEC` then returns every reply in order, errors included; an error element reads `(error) WRONGTYPE …`, the way `redis-cli` prints one. - -The rest of the app reads the same session, so while the block is open it sends nothing of its own on it. The sidebar, the key browser, the key tree and a database click say the block is open instead, and the connection check skips its `PING`. Run `EXEC` or `DISCARD` to get them back. Saving grid edits is refused while keys are watched; run `EXEC`, `DISCARD` or `UNWATCH` first. - -If the connection drops while a block is open or keys are watched, the server discards both. The next command reports the lost block or `WATCH` instead of running on the new session, and an `EXEC` the connection dropped under reports that whether the block ran is unknown. - -## SSL/TLS - -Set **SSL Mode** on the **Network** tab. Upstash, Redis Cloud and the like require TLS; `rediss://` turns it on when importing a URL. - -| Mode | hiredis behavior | Use for | -|---|---|---| -| **Disabled** | Plain TCP | Local dev | -| **Preferred** | TLS, do not verify peer | Same as Required here; there is no fallback | -| **Required (skip verify)** | TLS, do not verify peer | Self-signed certs, Upstash, untrusted CAs | -| **Verify CA** | TLS, verify peer with your CA cert | Private PKI; supply the CA path | -| **Verify Identity** | Same as Verify CA | Redis does no separate hostname check | - -New connections default to **Disabled**. SNI is sent in every TLS mode. - -## Limitations - -- A command that fails while `EXEC` is running cannot be taken back. Standalone and Sentinel wrap a grid save in `MULTI`/`EXEC`, so the rest of the block stays applied and the error names the one that failed; check the keys it touched. A command refused before it runs, for a missing ACL permission, a full `maxmemory` or wrong arity, aborts the block and writes nothing. -- No transactions in Cluster mode. Grid saves run their commands one at a time, so a failure leaves the earlier ones applied. Deleting grid rows sends one `DEL` per hash slot, and **Save Incomplete** counts how many ran. Group keys under one hash tag if they must move together. -- A `SELECT` typed inside a `MULTI` block waits for `EXEC`, and never happens at all after a `DISCARD`. Clicking a database in the sidebar while a block is open is refused; close the block first. -- A service with one database, such as Upstash or Redis Cloud, refuses `CONFIG`, so the sidebar lists 16. Every database but `db0` shows the server's error when you click it. -- Redis Cluster serves database 0 only, and so does Valkey unless `cluster-databases` is above 1, in which case the sidebar lists each database. A cluster whose primaries refuse `CONFIG` shows `db0` alone. The Database Index field is hidden in Cluster mode, and a cluster connection always opens on `db0`. -- A command whose keys span hash slots, such as `RENAME`, `SMOVE`, or the `*STORE` commands, is refused in Cluster mode before it is sent. Give the keys a shared hash tag, like `{user}:1` and `{user}:2`. -- A key that is not valid UTF-8 never appears in the grid or the tree. Reach it from the CLI; values have no such limit. -- Pub/Sub has no grid support. `PUBLISH` runs in the CLI, and there is no subscriber view. -- The sidebar tree loads at most 50,000 keys and a filter scans at most 10,000 matches. Narrow the pattern, or `SCAN` from the CLI. -- Values and TTLs cannot be filtered on. Redis has no server-side primitive for either; filter by key pattern and type only. -- No import. Export works as it does elsewhere. - -## Troubleshooting - -### NOPERM … has no permissions to run the 'scan' command - -The ACL user cannot run `SCAN`, which the key tree and the key browser read keys with, and key types need `TYPE`. Grant both with `ACL SETUSER +scan +type`, then right-click **Keys** and choose **Refresh**. - -On iPhone and iPad the key list reads `SCAN` and `TYPE` too, and opening a key reads it by type: `GET`, `LLEN` and `LRANGE`, `HLEN` and `HSCAN`, `SCARD` and `SSCAN`, `ZCARD` and `ZRANGE … WITHSCORES`, or `XLEN` and `XRANGE`. A refusal there reads **Permission Denied**. - -### SELECT … failed: ERR DB index is out of range - -The server has no database with that index. A server that refuses `CONFIG` is listed as 16 databases whatever it holds, so check `databases` in `redis.conf` or the service's own limit, and browse a database the server has. - -### Connection refused - -The server is not listening where the connection points. Check Redis is running (`brew services start redis`), the port matches `redis.conf`, and the `bind` directive covers the address you are using. - -### WRONGPASS invalid username-password pair or user is disabled. - -The server rejected the pair. With Username empty, `AUTH password` is checked against the `default` user, and an ACL user's password fails that: fill in Username to authenticate as that user. With Username filled and Password empty, the named user has a password and needs it, or the name does not exist on this server. - -One ACL trap produces the same error: `>password` sets a password, `#hash` sets a SHA-256 hash, and a 64-character hex string is valid for both, so `ACL SETUSER myuser on #<64-hex>` is accepted where `>` was meant. `ACL LIST` prints passwords as hashes, so a value copied from there is a hash, not a password. - -### This server requires authentication. - -The server answered `NOAUTH` to the first command, so the connection carries no identity. Check `requirepass` in `redis.conf` and whether the `default` user is `off` in `ACL LIST`. Fill in Password, and Username as well when the account is an ACL user. - -### ERR wrong number of arguments for 'auth' command - -The server predates Redis 6 and takes a password alone. Check its version with `INFO server`. Clear Username. - -### Timeout - -Nothing answered in time. Verify host and port, check the network and firewall, and allow your IP on a cloud-hosted Redis. - -### Sentinel or cluster mode over SSH - -A tunnel forwards one local port to one remote address, and the addresses Sentinel and Cluster hand back are the server's own. Over a tunnel only the first host in the list is used, as a standalone node, so failover and shard routing are unavailable. - -### Slow key list - -`KEYS` blocks the server on a large keyspace. Browse by namespace instead, and use `SCAN` in the CLI when you need a pattern. Check memory pressure with `INFO memory`. diff --git a/docs/databases/redshift.mdx b/docs/databases/redshift.mdx deleted file mode 100644 index b576159304..0000000000 --- a/docs/databases/redshift.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Amazon Redshift -description: Connect to Redshift clusters and Serverless over the PostgreSQL wire protocol, with DISTKEY and SORTKEY metadata display ---- - -Redshift speaks the PostgreSQL wire protocol and uses the same bundled libpq driver, so the [PostgreSQL](/databases/postgresql) page covers the form and the URL. This page covers where a columnar warehouse differs: distribution style and sort keys in table info, external schemas, and a read-only structure tab. - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | - | The cluster endpoint, `my-cluster.abc123xyz.us-east-1.redshift.amazonaws.com` in shape, or the workgroup endpoint on Serverless | -| **Port** | `5439` | Same for a provisioned cluster and for Serverless | -| **Database** | `dev` | **Required**; every cluster is created with a `dev` database | - -Fill in the form and click **Save & Connect**. New connections default to SSL mode **Preferred**, and there is no version gate: any cluster AWS still runs will connect. - - -AWS IAM authentication is not available for Redshift connections, unlike MySQL and PostgreSQL. Use a database username and password. For a cluster that is not publicly reachable, put it behind an [SSH tunnel](/connections/ssh-tunneling) on a bastion in the VPC. - - -## Connection URL - -```text -redshift://user:password@my-cluster.abc123xyz.us-east-1.redshift.amazonaws.com:5439/dev -``` - -See [Connection URL Reference](/connections/urls). - -## Schemas and metadata - -The default schema is `public`, and table metadata comes from `svv_table_info`: distribution style, sort keys, and table size. `dev` is listed like any other database; only `padb_harvest` is marked as a system database. A tab bound to a database other than the connection's active one opens a second connection for it, exactly as on [PostgreSQL](/databases/postgresql#cross-database-tabs). - -Table DDL includes DISTKEY, SORTKEY, DISTSTYLE, and ENCODE directives. Foreign keys are shown but not enforced by the server. - - - Redshift table info showing distribution style and sort keys - Redshift table info showing distribution style and sort keys - - -## External schemas - -Schemas created with `CREATE EXTERNAL SCHEMA` are listed beside regular ones and marked **External**, with an external icon on their tables. Spectrum over S3, federated query, cross-database references, and datashare consumers all land here. - -Their tables are read-only: Redshift takes no `UPDATE` or `DELETE` on them and they carry no primary key, so cell editing, adding rows, and deleting rows are off. Browsing, filtering, sorting, and export work as usual. - -## Backup - -**Backup Dump** and **Restore Dump** run `pg_dump` and `pg_restore` against Redshift, the only engine besides PostgreSQL where they are offered. See [Backup & Restore](/features/backup-restore). - -## Limitations - -- No structure editing. The structure tab is read-only for Redshift; run `ALTER TABLE` in the SQL editor. -- No indexes, enums, sequences, or triggers. DISTKEY and SORTKEY do the work indexes would. -- Foreign keys are informational only. Nothing stops a row that violates one; validate in the load job. -- No Maintenance menu. Run `VACUUM` and `ANALYZE` from the SQL editor. -- External tables show no row count, and their column types come through as the external catalog reports them, such as `varchar(65535)` or `array`. - -## Troubleshooting - -**Connection refused**: check the security group allows port 5439, the cluster is publicly accessible or reachable through a tunnel, and it is not paused. - -**FATAL: password authentication failed for user "…"**: the cluster rejected the credentials. Verify the master username and password in the AWS Console, and that the user exists in this database. - -**Slow queries**: check distribution with `SELECT * FROM svv_table_info WHERE "table" = 'your_table'`, run `ANALYZE` to refresh stats, and match SORTKEY to the predicates you filter and join on. diff --git a/docs/databases/sap-hana.mdx b/docs/databases/sap-hana.mdx deleted file mode 100644 index a4f1e52582..0000000000 --- a/docs/databases/sap-hana.mdx +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: SAP HANA -description: SAP HANA Cloud and on-premises HANA 2.0, with TLS, schema browsing, catalog DDL and plans ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Give a HANA Cloud instance its SQL endpoint host, a user and a password, and leave the rest: port `443` and **Verify Identity**, checked against the certificates your Mac already trusts, are the defaults. An on-premises server needs its own port, listed under [Connection settings](#connection-settings). The driver speaks HANA's SQL protocol through SAP's open source [go-hdb](https://github.com/SAP/go-hdb) library, so the SAP HANA client, ODBC and SQLDBC stay off your Mac. - - - -## Quick setup - -Click **New Connection…**, choose **SAP HANA**, enter the host, username and password, and click **Save & Connect**. SAP HANA Cloud and SAP HANA 2.0 servers connect, HANA Express included. - -In SAP HANA Cloud Central, the instance's **SQL Endpoint** reads `host:443`. The part before `:443` goes in **Host**. - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Host** | - | SQL endpoint or server hostname | -| **Port** | `443` | The HANA Cloud port. On-premises ports are in the next table | -| **Schema** | - | Optional. The schema the session starts in. Empty uses your user's default schema | -| **Username** | - | Database user | -| **Password** | - | Stored in the macOS Keychain | -| **SSL Mode** | Verify Identity | On the **Network** tab. There is no fallback to plain TCP | -| **TLS Server Name** | Empty | On the **Options** tab. The name **Verify Identity** checks the certificate against. Empty uses the host | - -Schema names are case-sensitive. HANA stores an unquoted name in uppercase, so a schema created as `app` is `APP`, and **Schema** has to say `APP`. - -On premises, each database has its own SQL port, built from the two-digit instance number `NN`: - -| Database | Port | -|---|---| -| System database | `3NN13` | -| First tenant database, or a single-container system | `3NN15` | -| Further tenant databases | From `3NN40` on, so `3NN41` for the next one | -| HANA Express system database | `39013` | -| HANA Express tenant `HXE` | `39015` | - -`SELECT DATABASE_NAME, SQL_PORT FROM SYS_DATABASES.M_SERVICES WHERE SQL_PORT <> 0`, run on the system database, lists every tenant's port. - -One connection reaches one database. A tenant and the system database are two connections, so there is no database to switch between; the schema picker switches schema instead, on the same session and without reconnecting. `SYS`, `SYS_DATABASES` and HANA's own `_SYS_` schemas are system schemas: the schema picker lists them last, and the sidebar shows them only with [**Show system databases and schemas**](/customization/general-settings#tabs-and-sidebar) on, or when one is the schema in use. An open tab stays on the schema it started with. See [Tabs](/features/tabs#where-a-tab-points). - -## Connection URL - -```text -hdb://user:password@host:443/SCHEMA -``` - -The path is the starting schema. See [Connection URL Reference](/connections/urls). - -## Authentication - -Username and password, over HANA's SCRAM-PBKDF2-SHA256 exchange, so the password itself never crosses the network. X.509 certificate, JWT, SAML, Kerberos and LDAP logons do not connect: log on as a database user with a password. - -**Client Certificate** and **Client Key** on the **Network** tab are for a server that requires mutual TLS, and the password is still asked for. Both take PEM files, and the key must not be encrypted. - -## Running and stopping queries - -**Stop** sends `ALTER SYSTEM CANCEL SESSION` for your session from a second session under the same user. The statement ends with its changes rolled back, and the connection stays. - -The connection closes instead when the server refuses that statement or takes more than 10 seconds to answer it, and when the statement is still running 30 seconds after **Stop**. If the driver itself has not answered 40 seconds after **Stop**, it is shut down. Each case reconnects on the schema you were using, without your temporary tables and session variables. [Troubleshooting](#stop-reconnects-instead-of-cancelling) says what to change. - -The [query timeout](/customization/general-settings#query-timeout) sends the same cancel and closes the connection in the same cases, short of shutting the driver down. When the cancel works, the error says the timeout was reached. - -### Explain - -**Query > Explain Query** runs `EXPLAIN PLAN` for the statement, reads the operators HANA stored in `SYS.EXPLAIN_PLAN_TABLE`, and deletes them again. The statement itself does not run. Each line of the plan is one operator, indented under its parent, with its table, estimated output rows and subtree cost. On HANA Cloud and HANA 2.0 SPS07 or later, the user needs the `OPTIMIZER ADMIN` privilege. See [EXPLAIN Visualization](/features/explain-visualization). - -## Values - -| HANA type | Shown as | -|---|---| -| `DATE` | `2024-01-31` | -| `TIME` | `13:45:00` | -| `SECONDDATE` | `2024-01-31 13:45:00` | -| `TIMESTAMP` | `2024-01-31 13:45:00.1234567`, always seven fraction digits | -| `DECIMAL`, `SMALLDECIMAL` | Exact, with the column's scale kept: `1.500`. A floating `DECIMAL` with an exponent past 64 in either direction uses E notation | -| `REAL`, `DOUBLE` | The fewest digits that read back as the same number. Plain notation from `1e-7` up to `1e21`, so `1000000` rather than `1e+06`, and exponent notation outside that range | -| `BOOLEAN` | `TRUE`, `FALSE` | -| `NCLOB`, `CLOB`, `TEXT`, `BINTEXT` | Text, up to 64 MiB | -| `BLOB`, `VARBINARY`, `BINARY` | Binary, up to 64 MiB for a `BLOB` | -| `ST_GEOMETRY`, `ST_POINT` | Hex WKB. `shape.ST_AsWKT()` in the query returns WKT text instead | - -Dates and times are the stored wall-clock value, with no time zone added. HANA's empty date is a value rather than `NULL` and shows as `0000-00-00`, with `00:00:00` and the fraction after it on the longer types. - -A value longer than 64 MiB is cut at 64 MiB, and the result's message counts the cut cells. - -Query parameters and values typed into the grid go to the server as bound parameters, never pasted into the SQL. Dates and times take the forms in the table, the empty date included, with a `T` between date and time also accepted and the fraction optional. `BOOLEAN` takes `TRUE`, `FALSE`, `1` or `0`. A `DECIMAL` value with more fraction digits than the column's scale is refused rather than rounded. - -## Structure - -The **Structure** tab reads the columns of tables and views, the primary key, identity columns, indexes with their type, and foreign keys with their update and delete rules, from HANA's `SYS` catalog views. - -The DDL view is built from the same catalog: `CREATE COLUMN TABLE` or `CREATE ROW TABLE`, each type with its length and scale, defaults, `NOT NULL`, identity and the primary key, followed by a `CREATE INDEX` for each other index and a `COMMENT ON` for each comment. A unique constraint comes back as a `CREATE UNIQUE INDEX`. Partitioning and other table options are not in it. A view's DDL is `CREATE VIEW` over the definition HANA stored. - -## SSL/TLS - -| Mode | What happens | -|---|---| -| **Disabled** | Plain TCP. HANA Cloud refuses it | -| **Preferred** | TLS, certificate not checked. There is no plain fallback, so this is the same as Required | -| **Required (skip verify)** | TLS, certificate not checked | -| **Verify CA** | Certificate chain checked against **CA Certificate**, which this mode requires. The hostname is not checked | -| **Verify Identity** | Chain checked against **CA Certificate**, or against the macOS trust store when it is empty, and the certificate must name **TLS Server Name**, or the host when that is empty | - -Keep **Verify Identity** for HANA Cloud, whose certificates chain to DigiCert roots macOS already trusts. For an on-premises server with a self-signed or internal certificate, point **CA Certificate** at the certificate or the CA that signed it. Every mode that encrypts uses TLS 1.2 or later. - -## Limitations - -- No SSH tunnel, SOCKS proxy, Cloudflare tunnel or tunnel command. For an on-premises server, forward a port yourself, for example `ssh -N -L 30015:hana-host:30015 bastion`, connect to `127.0.0.1`, and put `hana-host` in **TLS Server Name** so Verify Identity still passes. -- **Structure** saves nothing. Add or change columns, indexes and keys with `ALTER TABLE` in the SQL editor. -- Procedures, functions and triggers are not in the sidebar. Read one with `SELECT DEFINITION FROM SYS.PROCEDURES WHERE SCHEMA_NAME = 'APP' AND PROCEDURE_NAME = 'P'`, or from `SYS.FUNCTIONS` and `SYS.TRIGGERS` by their own name columns. -- Every statement commits as it runs. A batch or a grid save that fails partway keeps the statements before the failure, so check what landed before running it again. A statement that binds a `BLOB`, `CLOB` or `NCLOB` value runs in a transaction of its own, which resets the session's `SET TRANSACTION` isolation level and access mode. -- **matches regex** in the filter bar runs as a plain contains match. For a pattern, write `WHERE col LIKE_REGEXPR 'pattern'` in the SQL editor. -- A single result larger than 2 GiB fails with "The SAP HANA driver failed unexpectedly." Export and [Copy and duplicate](/features/copy-objects) read a whole table or result into memory before writing, and stop at 5,000,000 rows. Export a larger table in slices, with a `WHERE` on a key range or `LIMIT` and `OFFSET`. -- [Compare & Sync](/features/compare-sync) is not available. -- A `CALL` with query parameters runs, but its result sets are not shown, and a procedure with `OUT` parameters is refused. Write the arguments into the statement to see the result sets. - -## Troubleshooting - -### authentication failed - -HANA does not say which part was wrong. Check the username and password, that the user is not locked or deactivated, and that it logs on with a password rather than only through SSO or a certificate. - -### The server's TLS certificate could not be verified against any trusted root. - -The chain does not end at a certificate on your Mac. Set **CA Certificate** to the server's certificate or its CA as PEM. To test without it, **Required (skip verify)** encrypts without checking. - -### The server's TLS certificate does not match the hostname being connected to. - -Connecting by IP address, or through a name the certificate does not carry, fails **Verify Identity**. Put the name the certificate carries in **TLS Server Name**, or use **Verify CA**, which checks the chain and not the name. - -### The server does not accept encrypted connections but TablePro is configured to require TLS. - -The port answered in plain TCP. An on-premises or HANA Express server speaks TLS only once it has a server certificate. Configure one on the server, or set **SSL Mode** to **Disabled** for a server on a private network. - -### TablePro could not reach the SAP HANA server. - -Nothing answered at the host and port. Check both against the instance's SQL endpoint. A HANA Cloud -instance accepts connections only from the IP addresses allowed in **SAP HANA Cloud Central**, under -the instance's **Connections** settings, so add your Mac's public address there. A message that says -"within _n_ seconds" uses the **Connect timeout** under **Options**; an empty field uses 30 seconds. - -### Lost connection to the SAP HANA server. - -The network dropped, the server ended the session, or the driver failed. A driver failure drops that connection's session and TablePro keeps running. The connection reconnects on its own. Right after **Stop**, see [Stop reconnects instead of cancelling](#stop-reconnects-instead-of-cancelling). - -### insufficient privilege: … - -On **Query > Explain Query**, the user lacks `OPTIMIZER ADMIN`. Anywhere else, it lacks a privilege on the object the statement names. An administrator grants the first with `GRANT OPTIMIZER ADMIN TO user`. - -### Stop reconnects instead of cancelling - -The statement fails with "Lost connection to the SAP HANA server." for one of three reasons: the server refused `ALTER SYSTEM CANCEL SESSION` or did not answer it within 10 seconds, the statement was still running 30 seconds after **Stop**, or the driver had not answered 40 seconds after it. SAP files stopping a session under the `SESSION ADMIN` system privilege, which reaches every other user's session too, so check that the user holds it. With `GRANT SESSION ADMIN TO user` in place the server accepts the cancel, and a statement that takes longer than 30 seconds to stop still reconnects. - -### A schema or table is missing - -The `SYS` catalog views show only the objects your user holds a privilege on. `CATALOG READ` lists all of them without granting access to their data. - -## Related - -- [SSL/TLS](/connections/ssl) -- [EXPLAIN Visualization](/features/explain-visualization) -- [Query Parameters](/features/query-parameters) -- [Plugins & Themes](/features/plugins) diff --git a/docs/databases/snowflake.mdx b/docs/databases/snowflake.mdx deleted file mode 100644 index 537e741544..0000000000 --- a/docs/databases/snowflake.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Snowflake -description: Connect to Snowflake with password, key-pair, SSO, or OAuth auth ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -The account identifier is the whole address: `myorg-myaccount`, or the legacy locator `xy12345.us-east-1`, whatever sits in front of `.snowflakecomputing.com`. The form has no host or port field at all. - - - Snowflake connection form - Snowflake connection form - - -## Quick setup - -Click **New Connection…**, select **Snowflake**, enter the **Account Identifier**, set **Auth Method**, and click **Save & Connect**. The form shows only the fields that method needs. Any account works: the driver speaks the same connector REST protocol as Snowflake's own drivers, so there is no edition or version to match. - - - -## Connection settings - -| Field | Required | Notes | -|-------|----------|-------| -| **Account Identifier** | Yes | `myorg-myaccount` or `xy12345.us-east-1` | -| **Auth Method** | Yes | Password, Key Pair, SSO (Browser), or OAuth Token | -| **Username** | Most | Password, key-pair, and SSO need it | -| Credentials | Per method | The form reveals what the method needs: **Password** with **MFA Passcode (TOTP)**, **Private Key File** with **Private Key Passphrase**, or **OAuth Token** | -| **Warehouse** | No | Compute warehouse such as `COMPUTE_WH` | -| **Database** | No | Default database. Empty browses all of them | -| **Schema** | No | Default schema, `PUBLIC` if empty | -| **Role** | No | Session role, Options section | -| **CLI Connection Name** | No | Section in `~/.snowflake/connections.toml`, Options section | - -## Connection URL - -Snowflake registers no URL scheme. The CLI config stands in for one: name a section in **CLI Connection Name**, and every field left empty is read at connect from `~/.snowflake/connections.toml`, or a `[connections.]` section of `config.toml`. - -## Authentication - -### Password - -If the account enforces MFA, put a current code in **MFA Passcode (TOTP)**; it expires in under a minute, so refresh it immediately before connecting. Running `ALTER ACCOUNT SET ALLOW_CLIENT_MFA_CACHING = TRUE;` as ACCOUNTADMIN ends that chore: connect once with a fresh code, and Snowflake returns an MFA token that TablePro keeps in the Keychain. - -### Key pair - -Point **Private Key File** at an RSA private key in PKCS#8 (`.p8`) format, and add **Private Key Passphrase** for an encrypted key. Register the matching public key on the user first: - -```sql -ALTER USER jane_doe SET RSA_PUBLIC_KEY='MIIBIjANBgkq…'; -``` - -Every connect signs a one-hour JWT with that key, so the Mac's clock has to be accurate. - -### Browser SSO - -Sends you to your identity provider in the default browser and catches the reply on a loopback port, giving up after 2 minutes. With `ALLOW_ID_TOKEN = TRUE` on the account, Snowflake also returns a sign-in token; it goes to the Keychain, and later connects skip the browser until it is rejected. - -### OAuth token - -Paste an access token issued for the account. Nothing refreshes it for you. - -## Browsing and sessions - -The sidebar groups objects by database and schema. Object lists come from `SHOW` commands, which the metadata service answers with no warehouse running; column details come from `INFORMATION_SCHEMA`, which needs one. - -**Database > Session Context** moves the session to another **Warehouse** or **Role** with `USE WAREHOUSE` and `USE ROLE`, no reconnect. A tab bound to a second database stays on that session too: `USE DATABASE` runs ahead of its statements. One TablePro connection is one Snowflake session, held open while idle by a heartbeat. - -## Editing - -`GET_DDL` supplies the DDL view. Grid saves bind values server-side, VARIANT, OBJECT, and ARRAY cells through `PARSE_JSON`. The structure editor refuses a change Snowflake would reject before anything runs: - -| Change | Result | -|--------|--------| -| Add, rename, or drop a column | Runs | -| Widen a VARCHAR | Runs. Shrinking is refused | -| Change NUMBER precision | Runs at the same scale | -| Any other type change | Refused | -| NOT NULL, comment, primary key | Runs | -| Set a column default to a new value | Refused. Clearing one runs | - -## SSL/TLS - -No **SSL Mode**, and no plaintext option. Every request is HTTPS to the account endpoint on port 443. - -## Limitations - -- No SSH tunnel. The form has no Network tab, and the endpoint is reached over HTTPS only. -- A grid edit keys on the declared primary key, or on every column when there is none, and a duplicate key value updates or deletes every row that matches it. Snowflake never enforces key constraints, so check a table for duplicates before editing it. -- `TIMESTAMP_LTZ` values read as the UTC instant, marked `Z`, rather than converted to the session's `TIMEZONE`. - -## Troubleshooting - -### 394508, MFA is required - -The account enforces MFA on password logins. Enter a current code in **MFA Passcode (TOTP)**, or move to key-pair or browser SSO, which never prompt. - -### 394507, the passcode was rejected - -The code was wrong or already expired. A rejected code is remembered and never resent, so open the connection settings and type a fresh one. - -### JWT token is invalid - -Key-pair auth. The JWT issuer carries your account locator and a timestamp, so check the identifier and the Mac's clock, then confirm the public key is registered on the user. - -### No databases in the sidebar - -The connecting role needs `USAGE` on the databases. Set **Warehouse** too: column metadata runs on `INFORMATION_SCHEMA`, which needs one. - -### The browser never came back - -Sign-in has 2 minutes, and the reply arrives on a loopback port. Allow the popup and retry. diff --git a/docs/databases/spanner.mdx b/docs/databases/spanner.mdx deleted file mode 100644 index c1df098668..0000000000 --- a/docs/databases/spanner.mdx +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: Google Cloud Spanner -description: Connect to Cloud Spanner with a service account, ADC, a Google account, or the local emulator ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -One connection is one database, reached over Google's REST API: no host, no port, no tunnel. Connect reads the database's dialect, GoogleSQL or PostgreSQL, and quoting, catalog queries and the default schema follow it. - - - -## Quick setup - -Click **Create Connection…**, select **Spanner**, pick an auth method, fill in Project ID, Instance ID and Database, then click **Save & Connect**. - -## Connection settings - -| Field | Required | Notes | -|-------|----------|-------| -| **Auth Method** | Yes | Service Account Key, Application Default Credentials, Google Account (OAuth), or Emulator (no credentials) | -| **Project ID** | Yes | The ID (`my-project-123456`), not the name or number | -| **Instance ID** | Yes | The instance ID, not its display name | -| **Database** | Yes | The database ID. A second database needs a second connection | -| **Service Account Key** | Service account only | A `.json` key file path, or the key JSON itself | -| **OAuth Client ID**, **OAuth Client Secret** | OAuth only | From Google Cloud Console > APIs & Services > Credentials | -| **OAuth Refresh Token** | No | OAuth only. Tried before the token saved by sign-in | -| **REST Endpoint** | No | Options tab. Empty means `https://spanner.googleapis.com` | - -A custom **REST Endpoint** has to be an `https` address under `googleapis.com`, such as a regional or Private Service Connect endpoint. Credentials are never sent anywhere else. Any Spanner version works; it is a managed service. - -## Connection URL - -There is no URL scheme for Spanner. Create the connection from the form. - -## Authentication - -Pick Application Default Credentials on a Mac that already has `gcloud`. Use a service account key for a shared or unattended setup, and a Google account when you have neither. - -### Application default credentials - -```bash -gcloud auth application-default login --project=my-project -``` - -The driver reads `GOOGLE_APPLICATION_CREDENTIALS` when it is set, otherwise `~/.config/gcloud/application_default_credentials.json`. It accepts the `authorized_user`, `service_account` and `impersonated_service_account` types. - -### Service account key - -Point **Service Account Key** at a key file from Google Cloud Console (IAM > Service Accounts > Keys), or paste the JSON. The private key alone is not enough; paste the whole file. - -`roles/spanner.databaseUser` covers queries, grid edits, the DDL tab and schema changes. `roles/spanner.databaseReader` is enough for a connection that only reads. - -### Google account (OAuth 2.0) - - - - In [Google Cloud Console](https://console.cloud.google.com/), under APIs & Services > Credentials, click Create Credentials > OAuth client ID and pick Desktop app. - - - Paste the client ID and client secret into the connection form and connect. - - - The first connect stops at **Google Sign-In Required**. Click **Sign In** and approve access in the browser; the connection retries when sign-in finishes. The browser step times out after two minutes. - - - -The refresh token from that sign-in is kept in the macOS keychain, one per OAuth client, so later connects and reconnects need no browser. A background reconnect never opens one. When Google revokes the token, the next connect asks you to sign in again. - -One OAuth client signs in one Google account, shared by every connection that uses it. To use a second account, create a second OAuth client, or paste that account's refresh token into **OAuth Refresh Token**. - -### Local emulator - -Choose **Emulator (no credentials)** and set **REST Endpoint** to the emulator's REST port, `http://127.0.0.1:9020` by default. The project, instance and database must already exist in the [Spanner emulator](https://cloud.google.com/spanner/docs/emulator). This method only accepts a local address, and sends no credentials. - -## Schemas - -A GoogleSQL database's default schema has no name. The sidebar and the schema switcher show it as **(default)**, beside any named schemas. Its tables are written without a schema prefix, and a table in a named schema as `sales.Orders`. A PostgreSQL-dialect database starts on `public`. - -## Queries and transactions - -SELECT results stream, so a large result or a full-table export is not capped at Spanner's 10 MiB response limit. - -Each statement commits on its own. `BEGIN` opens a read-write transaction that holds every statement until `COMMIT` or `ROLLBACK`. While one is open, saving grid edits is refused, so a save never commits or abandons it. When Spanner aborts the transaction, later statements fail until you run `ROLLBACK`; then run them again. A single statement outside a transaction is retried automatically when Spanner aborts it. - -`CREATE`, `ALTER` and `DROP` go through Spanner's schema-change API, outside any transaction. The editor waits for the change up to the [query timeout](/customization/general-settings#query-timeout); a change still running then keeps running on the server. - -## Editing rows - -Grid edits, inserts and deletes become `INSERT`, `UPDATE` and `DELETE` keyed on the table's primary key. Values are sent as typed query parameters, so quotes, backslashes and the text `null` are stored as typed. A column left at its default or generated by Spanner is written as `DEFAULT`. Views are read-only. - -## EXPLAIN - -**Explain** shows Spanner's query plan as a tree. A DML statement is planned in a transaction that is rolled back, so nothing is written. DDL and transaction statements cannot be explained, and `EXPLAIN ANALYZE` is not available because it runs the statement. See [EXPLAIN Visualization](/features/explain-visualization). - -## SSL/TLS - -Traffic to Google is always HTTPS. There is no **SSL Mode**. The emulator is plain HTTP on your own Mac. - -## Limitations - -- The Structure tab is read-only. Change a table with `ALTER TABLE` in the editor. -- `DROP TABLE` fails while the table has secondary indexes or interleaved children. Drop those first. -- **Truncate Table** runs `DELETE … WHERE TRUE` in one transaction, which Spanner rejects past its mutation limit. Delete a large table in batches from the editor. -- SQL export writes `BYTES` as `X'…'` and booleans as quoted text, which Spanner will not load. Export data as CSV or JSON. -- Text filters such as contains and starts with fail on `JSON`, `ARRAY` and `STRUCT` columns. Filter those in the editor with JSON or array functions. -- Data Compare is not available for Spanner. -- On a PostgreSQL-dialect database, completions and the AI assistant still quote with backticks. Write `"double quotes"` yourself. -- The emulator returns no query plans, so **Explain** reports that no plan was returned. - -## Troubleshooting - -### Application default credentials not found. Run gcloud auth application-default login. - -No ADC file was found. Run the command, or set `GOOGLE_APPLICATION_CREDENTIALS` to a key file. Or switch the connection to a service account key. - -### Sign in with your Google account to use this connection. - -The OAuth connection has no saved sign-in yet. Click **Sign In** in the prompt that follows. - -### Google no longer accepts these credentials. Sign in again, or renew the key or the gcloud login. - -Google rejected the refresh token or the service account's signed request. For a Google account, sign in again from the prompt. For ADC, run `gcloud auth application-default login` again. For a service account, create a new key. - -### The REST endpoint must be an https address under googleapis.com. Choose the Emulator auth method for a local emulator. - -**REST Endpoint** names a host that is not Google's. Clear it for production, or pick **Emulator (no credentials)** for a local emulator. - -### Spanner aborted the transaction. Roll it back and run it again. - -Spanner ended the open transaction, usually over a lock conflict. Run `ROLLBACK`, then the statements again. - -### The connection failed while Spanner was committing, so the changes may have been saved. Check the data before running it again. - -The commit request left the Mac, but no answer came back. Spanner may or may not have saved the transaction. Query the rows before running the statements again, so nothing is written twice. - -### The schema change is still running on the server (…). - -The DDL outlived the query timeout. The change continues on Spanner; check it in Google Cloud Console before running it again. - -## Related - -- [EXPLAIN Visualization](/features/explain-visualization) -- [Google BigQuery](/databases/bigquery) diff --git a/docs/databases/sqlite.mdx b/docs/databases/sqlite.mdx deleted file mode 100644 index 9aff99f271..0000000000 --- a/docs/databases/sqlite.mdx +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: SQLite -description: Open and query SQLite database files directly, no server needed ---- - -To start a new database, click **New…** beside the file path, name the file, and click **Create**. The file is made when you connect, so a scratch database costs one filename and one click. Choosing a file that already exists there opens it as it is. - -## Quick setup - -Click **New Connection…**, select **SQLite**, pick the file with **Browse…** or name a new one with **New…**, and click **Save & Connect**. There is no host, port, or credential to fill in. The driver ships inside TablePro with its own SQLite 3.53.4, whatever macOS version you run, so there is no server version to match. - -`.db`, `.db3`, `.s3db`, `.sl3`, `.sqlite`, `.sqlite3`, and `.sqlitedb` files list TablePro under Finder's **Open With**, as an alternate handler rather than the default one. To make a double-click open them here, select one in Finder, press `Cmd+I`, set **Open with** to TablePro, and click **Change All…**. - -A database saved under some other name still opens. Drag it onto a TablePro window, pick it in **File > Open File…**, or drop it on the Dock icon, and the first sixteen bytes decide the driver: a file that starts `SQLite format 3` opens here whether it is called `store.bin`, `export`, or `data.csv`. Finder's double-click is the one route that still needs a name it knows. - - - SQLite connection form with a file path, Browse and New buttons - SQLite connection form with a file path, Browse and New buttons - - -## Common locations - -| Source | Path | -|--------|------| -| Safari history | `~/Library/Safari/History.db` | -| Photos | `~/Pictures/Photos Library.photoslibrary/database/Photos.sqlite` | -| Messages | `~/Library/Messages/chat.db` | -| Rails | `./db/development.sqlite3` | -| Django | `./db.sqlite3` | -| iOS Simulator | `~/Library/Developer/CoreSimulator/Devices//data/Containers/Data/Application//Documents/` | - - -The Safari, Photos, and Messages databases sit in folders macOS protects. Grant TablePro Full Disk Access in **System Settings > Privacy & Security > Full Disk Access**, then relaunch the app. Without it, opening those files fails with a permission error. - -Those databases are also often locked by the app that owns them. Quit it first. - - -## Connection URL - -```text -sqlite:///path/to/database.db -``` - -See [Connection URL Reference](/connections/urls) for all parameters. - -## Browsing - -The sidebar lists tables and views and hides the internal `sqlite_*` tables. Each table carries its columns, constraints, indexes, foreign keys, and DDL; triggers can be read and edited. - -Change the file outside TablePro and the object list reloads on its own. Rows already loaded in a tab stay until you refresh that tab. - -One connection is one file, with no database to switch between, and the object list reads the main database only: a file you `ATTACH` in the editor is queryable as `alias.table` but never appears in the sidebar. - -## Extensions - -A database built with sqlite-vec, SpatiaLite or another loadable extension cannot be read until that extension is loaded: a statement touching its tables fails with `no such module: vec0` or `no such function: ST_AsText`. - - - - Right-click the connection in the welcome window, choose **Edit…**, and select **Options**. A new connection from **File > New Connection…** has the same pane. - - - Click **+** under **Extensions** and choose the file: `vec0.dylib` from a [sqlite-vec release](https://github.com/asg017/sqlite-vec/releases), or `/opt/homebrew/lib/mod_spatialite.dylib` after `brew install libspatialite`. The file panel hides `/opt`: press `Cmd+Shift+G` in the panel and type the folder. A row's path can be edited in place afterwards, with the whole file name, `.dylib` included. - - - Click **Save**, then connect. The extension's functions and virtual tables answer in the editor, the sidebar and the Structure tab. - - - - - The Options pane of a new SQLite connection. The Extensions list holds /opt/homebrew/lib/mod_spatialite.dylib above ~/Downloads/vec0.dylib, each row's entry point field reads Default entry point, and the add and remove buttons sit under the list - The Options pane of a new SQLite connection. The Extensions list holds /opt/homebrew/lib/mod_spatialite.dylib above ~/Downloads/vec0.dylib, each row's entry point field reads Default entry point, and the add and remove buttons sit under the list - - -The list loads in order every time the connection opens, Test Connection and reconnects included. Drag a row to reorder it when one extension needs another loaded first. The first file that fails stops the connect, and the connection window names the file and the reason. - -Leave a row's entry point field empty for most libraries, where it reads Default entry point: SQLite derives it from the file name, `sqlite3_vec_init` for `vec0.dylib`. Fill it in for a renamed file, or to call a second function the same library exports, such as sqlite-vec's `sqlite3_vec_numpy_init`. - -Only this list loads code. The `load_extension()` SQL function stays refused, so a statement in the editor, an import or an AI client cannot load a library. - -An extension's functions can write files, so statements from MCP clients, AppleScript and the AI assistant may call only the functions built into SQLite on a connection that loads extensions. A vector search written with `MATCH` passes; `SELECT vec_distance_l2(a, b)` is refused with `This connection loads SQLite extensions`. Run such statements in the editor. - -### A list from somewhere else - -An extension runs inside TablePro with full access to your Mac. A list that arrived through iCloud, an imported connection file, a `tablepro://` link, a linked folder or the team library therefore loads nothing until you approve it: connecting shows a **Load Extensions** alert naming every file, and **Load Extensions** records the answer for that connection on this Mac. At launch such a window waits with a **Connect** button instead of asking. Files added in the form on this Mac need no approval. - -### A library downloaded in a browser - -macOS refuses a library that still carries the quarantine flag a browser sets, and the connect fails with `library load disallowed by system policy`. If you trust the file, clear the flag: - -```bash -xattr -d com.apple.quarantine ~/Downloads/vec0.dylib -``` - -A release built for the other processor fails with `mach-o file, but is an incompatible architecture`. sqlite-vec publishes separate `macos-aarch64` and `macos-x86_64` archives. - -## A database on another machine - -The **Remote File** section on the Network tab points the connection at a database on an SSH server, and the **Open** menu beside the path decides how it is reached. **On the Server** runs your statements on the server against the live file, so reads and writes land on the database the server's own programs are using. **As a Read-Only Copy** fetches a snapshot over SFTP and opens it on this Mac, and the original is never touched. - -On the Server needs `python3` on the host, which every mainstream cloud image already carries, and nothing is installed. The read-only copy needs only SFTP, and it runs at Safe Mode [**Read-Only**](/features/safe-mode#connections-that-are-always-read-only). See [Remote Database Files](/connections/remote-database-files). - -## Edits that recreate the table - -`ALTER TABLE` covers renaming a table or a column, adding a column, dropping a column, and from 3.53 adding or dropping a `CHECK`. Everything else the Structure tab offers is made by writing the table again: a foreign key added or removed, and a column's type, nullability or default changed. - -A rebuild writes the table with the definition you asked for, copies the rows across with their rowids, puts the indexes and triggers back, and checks the rows against any new foreign key before committing. The script is shown in full and runs only when you confirm it, and everything staged in the same save travels in the same transaction. - -Each column keeps its own stored text, so a `CHECK`, a `COLLATE`, a `GENERATED ALWAYS AS` and a `DEFAULT` containing a comma all survive a rebuild that touched a different column. - -A column declared without a type, such as `body` in `CREATE TABLE notes (id INTEGER PRIMARY KEY, body)`, has an empty **Type** cell. `CREATE TABLE … AS SELECT` declares one for every selected expression that is not a plain column or a `CAST`, so `price + 1 AS total` arrives typeless. Rename, index, drop or edit such a column like any other and it stays typeless. A type is required only on a column you add, or on one whose type you clear. - -Changing a column's type re-reads every value in it through the new affinity. Text that looks like a number becomes one, so `'007'` in a column retyped to `INTEGER` is stored as `7`. The review sheet says so before the script runs. - -A virtual table, such as an FTS5 table, cannot be recreated from what SQLite stored for it. The save is refused and names the table. - -## Limitations - -- Encrypted databases do not open. The driver's SQLite takes no key, and the form has nowhere to put one. Decrypt a SQLCipher file with the `sqlcipher` tool first. -- Extensions load only for a file opened on this Mac. A connection that runs **On the Server** refuses a list with `Extensions load only for a database file on this Mac`: remove the list, or open the file **As a Read-Only Copy**, which loads it. -- A column that is `INTEGER PRIMARY KEY` cannot be retyped: the key would stop generating values and start storing NULL. The save is refused and names the column. -- A primary key cannot be added, removed or moved. - -## Troubleshooting - -### database is locked - -Another process holds a write lock. Quit the app that owns the file, or look for `-wal` and `-shm` files a crashed process left behind. The wait runs until the [query timeout](/customization/general-settings#query-timeout); **Query > Cancel Query** ends it sooner. - -### unable to open database file - -The path is wrong, or the folder is one you cannot read. For anything under `~/Library`, grant Full Disk Access and relaunch. - -### file is not a database - -The file is encrypted or is not SQLite at all. `file database.db` names what it actually is. - -### no such module: … - -The database has a virtual table from an extension the connection does not load, such as `vec0` from sqlite-vec. Add the library under [Extensions](#extensions). - -### Could not load the extension "…". - -The line under it is the reason. `There is no file at "…"` means the path is wrong or the file moved. `The file has no function named …` means the entry point SQLite derived is not in the library: type the name its documentation gives into the row's entry point field. For a download or the wrong architecture, see [A library downloaded in a browser](#a-library-downloaded-in-a-browser). diff --git a/docs/databases/surrealdb.mdx b/docs/databases/surrealdb.mdx deleted file mode 100644 index 61c352258b..0000000000 --- a/docs/databases/surrealdb.mdx +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: SurrealDB -description: Connect to SurrealDB with namespace and database browsing, SurrealQL, and inline record editing ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Whichever level your user was defined at, Root, Namespace, Database, or Record Access, **Auth Level** has to name it. SurrealDB cannot infer it from the credentials, and the rest of the form follows from that one field. - -## Quick setup - -Click **New Connection…**, select **SurrealDB**, enter the host and port, pick the auth level that matches your user, and connect. The driver needs SurrealDB 2.0 or later and talks CBOR over the HTTP RPC endpoint, so record links, datetimes, durations, decimals, and UUIDs keep their types. - - - -## Connection settings - -| Field | Description | -|-------|-------------| -| **Host** | Server host, such as `localhost` | -| **Port** | HTTP port, default `8000` | -| **Namespace** | The namespace to open | -| **Auth Level** | Which level your credentials belong to | -| **Username / Password** | Every level except Token | -| **Database** | Shown for Database and Record Access. At the other levels, pick one from the sidebar after connecting | -| **Token** | A JWT, shown for Token | -| **Access Method** | The name from `DEFINE ACCESS`, shown for Record Access | - -## Connection URL - -```text -surrealdb://user:pass@host:8000 -``` - -The scheme is registered with macOS, so a link like that opens the connection form pre-filled. See [Connection URL Reference](/connections/urls). For a remote server, connect through an [SSH tunnel](/connections/ssh-tunneling). - -## Authentication - -| Auth Level | Use it for | Needs | -|-------|-------------|-------------| -| **Root** | A root user, defined with `DEFINE USER … ON ROOT` | Username, password | -| **Namespace** | A namespace user | Username, password, namespace | -| **Database** | A database user | Username, password, namespace, database | -| **Record Access** | A record user, defined with `DEFINE ACCESS … TYPE RECORD` | Namespace, database, access method, and the signin fields | -| **Token** | A JWT you already hold, including SurrealDB Cloud | The token | - -Only a root user lists every namespace; other levels browse the scope the user is limited to. - -## Namespaces and databases - -Namespaces sit at the top of the sidebar, databases inside them, tables inside a database. Neither switch reconnects. The namespace and database ride on each request, and a tab bound to a second database keeps the same connection. - -Right-click a namespace for **Drop Namespace…** (`REMOVE NAMESPACE`) or a database for **Drop Database…** (`REMOVE DATABASE`). Select several with Shift-click or Cmd-click and one confirmation covers all of them. See [Work on Several Databases at Once](/features/table-operations#databases-and-schemas). - -## Records in the grid - - - SurrealDB table records with record links and typed values in the data grid - SurrealDB table records with record links and typed values in the data grid - - -Changing a cell runs an `UPDATE` that sets only the fields you touched, leaving computed `FUTURE` fields and another writer's changes alone. Values go out as typed parameters, so an `int` stays an `int` and a filter value is never parsed as SurrealQL. - -- `id` is the primary key and read-only. Leave it empty on a new row for a generated one, or type `table:id` to choose it. -- A record link shows as `table:id`. Objects and arrays show as compact JSON and are edited as JSON. -- On a `RELATION` table, `in` and `out` sit next to `id`. -- A `SCHEMALESS` table has no declared fields, so its columns come from the rows fetched: a field only some records carry still gets a column, and the rest show an empty cell. - -## Filters - -Each filter bar operator becomes SurrealQL. **matches regex** runs `string::matches` and passes over records where the field is missing or NULL. **between** includes both bounds. **is empty** matches a missing field, NULL, or `''`. - -A **Raw SQL** row takes one SurrealQL condition, placed in parentheses in the `WHERE` clause: - -```sql -->likes->person CONTAINS person:alice -``` - -The condition must only read. One that holds a writing statement such as `CREATE`, `UPDATE` or `DELETE`, a `;`, a comment, or a call into `fn::`, `http::`, `api::`, `file::` or `sequence::` is refused, and the grid shows the error in place of rows. Run it in the editor instead. A record id named after one of those statements needs angle brackets: `person:⟨update⟩`. - -## SurrealQL - -The editor runs whole SurrealQL scripts, with completions for the common statements: - -```sql -SELECT * FROM person LIMIT 100; -RELATE person:alice->wrote->article:one SET time = time::now(); -``` - -The Explain button offers **Explain** and **Explain Full**, which rerun the current query with `EXPLAIN` and `EXPLAIN FULL` to show how it resolves, including whether an index is used. - -## SSL/TLS - -**Disabled** is the default and connects over HTTP; every other mode connects over HTTPS. **Preferred** and **Required (skip verify)** accept any certificate, **Verify CA** and **Verify Identity** check it against the system trust store. - - -**Skip TLS Verification**, a toggle in the Options section, trusts any certificate even when the mode is **Verify CA** or **Verify Identity**. Leave it off outside a development server with a self-signed certificate. - - -## Limitations - -- The structure editor is read-only. Create and change fields and indexes with `DEFINE FIELD` and `DEFINE INDEX` in the editor. -- An edit to `in` or `out` cannot be saved from the grid, on any table: the save stops, names the field, and keeps every edit pending. An `UPDATE` does not move a relation either, so delete it and `RELATE` the new pair. On any other record, change the field with an `UPDATE` in the editor. -- Multi-request transactions are not available over HTTP. Send `BEGIN TRANSACTION; … COMMIT TRANSACTION;` as one editor query instead. -- Live queries (`LIVE SELECT`) are not supported. Rerun the query to see new records. -- A CA certificate file set on the **Network** tab is ignored; verification goes through the system trust store, so install a private CA there. -- Import is not available. Export works, see [Import and Export](/features/import-export). -- Range values display but cannot be edited. -- The editor treats SurrealQL as plain text, with no syntax coloring. -- SurrealDB 1.x is not supported. - -## Troubleshooting - -### … is required for the selected authentication level - -The level needs a scope the form has not been given. Namespace, Database, and Record Access all need **Namespace**; the last two also need **Database**; Record Access also needs **Access Method**. - -### SurrealDB … is not supported. TablePro requires SurrealDB 2.0 or later. - -The version comes from the server's own `/version` reply. Upgrade the server, or point the connection at a 2.x or 3.x instance. - -### SurrealDB over HTTP does not support multi-request transactions. - -A transaction cannot span requests over HTTP RPC. Put the whole transaction in one editor query. - -### The credentials do not match the auth level - -A root user signed in at Database level fails, and so does the reverse. Match the level to how the user was defined. diff --git a/docs/databases/teradata.mdx b/docs/databases/teradata.mdx deleted file mode 100644 index 3cd571fcc7..0000000000 --- a/docs/databases/teradata.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Teradata -description: Browsing databases and objects on Teradata Vantage, with TLS and transaction mode ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Port `1025` carries a plain session. Any SSL mode moves the session to `443`, where it runs over the gateway's WebSocket endpoint, and that port cannot be changed. A native Swift driver handles both, with no client library to install. - - - Teradata connection form - Teradata connection form - - -## Quick setup - -Click **New Connection…**, select **Teradata**, enter the host, username, and password, and click **Save & Connect**. Any Vantage system that accepts a TD2 logon works; no version is checked. - - - -## Connection settings - -| Field | Description | -| --- | --- | -| **Host** | Teradata database hostname | -| **Port** | Plain gateway port, default `1025` | -| **Database** | Default database after logon. Optional; empty uses your account's default | -| **Username / Password** | Teradata credentials | -| **Logon Mechanism** | Options section. `TD2` or `TDNEGO`, uppercase. Default `TD2` | -| **Transaction Mode** | Options section. `DEFAULT`, `ANSI`, or `TERA`, uppercase | -| **SSL Mode** | **Network** tab. **Disabled** by default | - -Leave **Transaction Mode** on `DEFAULT`, which takes whatever the system is configured for, unless a script needs `ANSI` or `TERA` case sensitivity and commit rules on the session. A system reachable only through a bastion works over an [SSH tunnel](/connections/ssh-tunneling). - -## Connection URL - -```text -teradata://user:password@host:1025/database -``` - -**Import from URL…** accepts the scheme; macOS does not route `teradata://` links. See [Connection URL Reference](/connections/urls). - -## Databases and objects - -A database and a user are both namespaces in Teradata, and both appear as top-level databases in the sidebar with their tables and views inside. A tab bound to a second database keeps the same session: `DATABASE "name"` runs ahead of its statements instead of a reconnect. - -Identifiers are quoted with double quotes. Teradata has no `LIMIT`, so a table's first page comes back with `TOP` and later pages with `QUALIFY ROW_NUMBER()`. - -## SSL/TLS - -| Mode | What happens | -| --- | --- | -| **Disabled** | Plain session on the gateway port | -| **Preferred** | TLS on `443`, falling back to a plain session on `1025` when the HTTPS port is unreachable | -| **Required (skip verify)** | TLS, certificate not checked | -| **Verify CA** | Certificate chain checked against the file in **CA Certificate**, hostname not checked. Refused without a CA certificate | -| **Verify Identity** | Chain and hostname checked, against **CA Certificate** when set and the system trust store otherwise | - -A **CA Certificate** path that does not hold a readable certificate refuses the connection in both verify modes. - -Test TLS on **Required (skip verify)** rather than **Preferred**, or a failed handshake looks like a successful connect. - -## Limitations - -- Foreign keys are never listed. The Structure tab's Foreign Keys view stays empty for Teradata; read the constraints from its DDL view instead. -- Macros and stored procedures are not in the sidebar. Query them from the editor. -- LDAP, Kerberos, and JWT logon fail before the connect is attempted. Use `TD2` or `TDNEGO`. -- The TLS port is fixed at `443`. - -## Troubleshooting - -### unsupported: logon mechanism …; only TD2 and TDNEGO are supported - -**Logon Mechanism** holds something the driver does not implement. Set it to `TD2` or `TDNEGO`. A value the driver does not recognize, a mechanism typed in lowercase included, is read as `TD2` instead. - -### Cannot reach the server - -The connect gives up when the connection's **Connect timeout** expires; an empty field under -**Options** uses 30 seconds. Confirm the host, and that port `1025` is open from your network, or -`443` if an SSL mode is set. - -### Logon fails - -Check the username and password, and that the account is not locked. diff --git a/docs/databases/tidb.mdx b/docs/databases/tidb.mdx deleted file mode 100644 index 25eb2fbe63..0000000000 --- a/docs/databases/tidb.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: TiDB -description: Connect to TiDB and TiDB Cloud through the bundled MySQL driver on port 4000 ---- - -Point the connection at a TiDB server's SQL port, never at PD or TiKV. No minimum TiDB version is enforced. Host, user, password, SSH tunnels, SSL/TLS and `Cmd+K` database switching work as on [MySQL](/databases/mysql). - -## Connection settings - -| Field | Default | Notes | -|-------|---------|-------| -| **Port** | `4000` | TiDB Cloud uses 4000 as well | -| **Database** | empty | Optional. Leave it empty and open one with `Cmd+K` | -| **SSL Mode** | Preferred | TLS first, plain text if the server refuses. Use **Required** on TiDB Cloud | - -**Release the Server Connection After** is on the Options tab. AWS IAM, Cloud SQL Auth Proxy and Unix socket are not offered; reach a private cluster through an [SSH tunnel](/connections/ssh-tunneling). - -## Connection URL - -```text -tidb://user:password@host:4000/database -``` - -`tidb+ssh://` opens it through an SSH tunnel. See [Connection URL Reference](/connections/urls). - -## What differs from MySQL - -- `INFORMATION_SCHEMA`, `METRICS_SCHEMA`, `PERFORMANCE_SCHEMA`, `mysql` and `sys` are system databases: the database switcher lists them under **System**, and the sidebar tree shows them only with [**Show system databases and schemas**](/customization/general-settings#tabs-and-sidebar) on, or when one is the database in use. A TiDB server saved as a MySQL or MariaDB connection lists `METRICS_SCHEMA` with your own databases. Sequences are not listed as tables. -- **Stop** sends `KILL TIDB QUERY` for the connection's own session. -- `EXPLAIN` and `EXPLAIN ANALYZE` show TiDB's plan as text, not as a plan tree. -- CHECK constraints appear in the Structure tab on TiDB 7.2 and later, when the cluster has `tidb_enable_check_constraint` on. Adding one from the Structure tab is not offered. -- The type picker has MySQL's types without the Spatial group. -- Foreign keys can be created, without the `SET DEFAULT` action. Tables and views can be renamed, and virtual generated columns are supported. -- [Users & Roles](/features/users-roles) works, with no connection limit field. -- Table Maintenance offers `ANALYZE TABLE` only. - -A connection saved as **MySQL** that reaches a TiDB server still gets TiDB's **Stop**, sequence and Users & Roles handling. The sidebar, type picker, EXPLAIN and the other items above follow the connection type, so choose **TiDB** for all of them. - -## Limitations - -- No triggers, stored routines or events. -- No Server Dashboard, and **File > Backup Dump…** stays dimmed. Export with [Dumpling](https://docs.pingcap.com/tidb/stable/dumpling-overview) instead. -- Create Table has no engine, charset or collation pickers. -- [Compare & Sync](/features/compare-sync) writes no structure script between TiDB and MySQL or MariaDB. TiDB against TiDB works. -- Pages of an unsorted table can repeat or skip rows on a table spread over several regions. Sort by a column to get stable pages. -- Behind TiProxy or a load balancer, **Stop** needs Global Kill, on by default since TiDB 6.1. Without it, connect straight to one TiDB node. -- TiDB closes a session that receives `KILL TIDB QUERY` while idle, so **Stop** pressed just as a statement finishes can end the session, along with its open transaction, temporary tables and variables. - -## Related - -- [MySQL](/databases/mysql), for connection fields, SSL/TLS, and troubleshooting -- [MariaDB](/databases/mariadb) -- [SSH Tunneling](/connections/ssh-tunneling) diff --git a/docs/databases/trino.mdx b/docs/databases/trino.mdx deleted file mode 100644 index 66f870f59f..0000000000 --- a/docs/databases/trino.mdx +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Trino -description: Catalogs and schemas, type mapping, EXPLAIN variants, and the auth a Trino cluster accepts ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -What a write does depends on the catalog's connector, not on TablePro. A statement built correctly here can still come back as `NOT_SUPPORTED` because the connector behind that catalog is read-only or implements only part of SQL. - - - Trino connection form - Trino connection form - - -## Quick setup - -Click **New Connection…**, select **Trino**, enter the coordinator host and port, set **Username**, and click **Save & Connect**. A cluster with no authentication needs nothing else, and **Catalog** and **Schema** are optional starting points. - - - -## Connection settings - -| Field | Description | -| --- | --- | -| **Host** | Trino coordinator hostname | -| **Port** | Default `8080`, or `8443` when the coordinator serves HTTPS. Port `443` sets **SSL Mode** to **Verify Identity** | -| **Username** | The user the query runs as | -| **Catalog** | Default catalog for unqualified table names. Optional; empty browses every catalog | -| **Schema** | Default schema. Optional | -| **Auth Method** | **Authentication** section on General. `Username & Password` or `JWT Access Token` | -| **Password / Access Token** | The credential the chosen method needs: the account password, or the JWT in **Access Token** | -| **Time Zone** | Options section. Optional IANA zone (`America/New_York`) for the session | - -## Connection URL - -```text -trino://user@host:8080/catalog -``` - -**Import from URL…** accepts the scheme; macOS does not route `trino://` links. See [Connection URL Reference](/connections/urls). - -## Authentication - -A password or an access token goes only over TLS, as with the Trino JDBC driver. With **SSL Mode** Disabled, a connection that has either is refused before anything is sent. Leave both empty to connect to a cluster without authentication. - -| Method | What is sent | -| --- | --- | -| **Username & Password** | HTTP Basic auth, for LDAP or password-file authentication | -| **JWT Access Token** | The token as a bearer credential | -| Client certificate | The **Client Certificate** and **Client Key** on the Network tab, for mutual TLS. PEM or DER, with an unencrypted RSA or EC (P-256, P-384, P-521) key, and the certificate file may carry its chain. Works with any SSL mode other than Disabled. A key with no certificate is ignored | - -## Catalogs, schemas, and tables - -A connection opens on a catalog and schema, and every query can still name objects in full as `catalog.schema.table`: - -```sql -SELECT * FROM hive.web.clicks JOIN postgres.public.users USING (user_id); -``` - -Expand a catalog in the sidebar for its schemas, then a schema for its tables, with materialized views alongside tables and views. Row counts come from `SHOW STATS`. A tab bound to a second catalog keeps the same session; the catalog rides on each request. - -Identifiers are quoted with double quotes. Results page with `OFFSET n ROWS FETCH NEXT m ROWS ONLY`, the order Trino's grammar requires. - -## Types - -- `bigint` and `decimal` are read as exact text, not floating point. -- `varbinary` is shown as hex. -- `array`, `map`, and `row` are shown as JSON, and `json` opens in the JSON viewer. -- `timestamp`, `time`, and their `with time zone` forms keep the value the server returned. - -## Editing rows and schema - -Cell edits become `INSERT`, `UPDATE`, and `DELETE`. Values carry their Trino type, so a `varchar` holding digits stays quoted, and columns that cannot be compared with `=` (`array`, `map`, `row`) stay out of the `WHERE` clause. - - -Editing one of two identical rows changes both, and deleting one deletes both. With no primary keys reported, a row edit matches on every column of the row as it was read, and there is no transaction to roll it back. Check for duplicates before editing a table with no unique column. - - -The Structure tab creates tables and adds, drops, renames, and retypes columns, in autocommit. A type change is the operation fewest connectors accept. - -## Session and query control - -`SET SESSION` in the editor holds for the rest of the connection: the properties the coordinator reports back are sent with every later request, and `RESET SESSION` clears one. **Query > Cancel Query** (`Cmd+.`) tells the coordinator to kill the running query, which matters where a runaway query burns real compute. - -## EXPLAIN variants - -Click the Explain dropdown in the query editor to choose: - -| Variant | Statement | -| --- | --- | -| **Explain (Logical)** | `EXPLAIN`, the logical plan | -| **Explain (Distributed)** | `EXPLAIN (TYPE DISTRIBUTED)`, the plan split into stages | -| **Explain (IO)** | `EXPLAIN (TYPE IO)`, the tables and columns the query reads | -| **Explain (Validate)** | `EXPLAIN (TYPE VALIDATE)`, parses and validates without running the query | -| **Explain Analyze** | `EXPLAIN ANALYZE`, runs the query and reports actual timings | - -## SSL/TLS - -**Disabled** is the default and speaks plain HTTP. There is no plaintext fallback, so every other mode forces TLS. Port `443` turns on **Verify Identity**. See [Ports that mean TLS](/connections/ssl#ports-that-mean-tls). - -| Mode | Certificate check | -| --- | --- | -| **Preferred** | None | -| **Required (skip verify)** | None | -| **Verify CA** | Certificate chain, against the file under **CA Certificate**. Without that file the connection is refused | -| **Verify Identity** | Chain and hostname, against the CA file or, with none set, the system trust store | - -## Limitations - -- No primary keys, indexes, or foreign keys are reported. The Structure tab's Indexes view is always empty. -- Import is not available. Export works; see [Import and Export](/features/import-export). -- Kerberos and OAuth 2.0 authentication are not supported. -- Neither a connection's **Query timeout** override nor the inherited [global - setting](/customization/general-settings#query-timeout) reaches Trino. A long query runs until you - cancel it or the cluster ends it. -- Presto is not supported. It speaks the same protocol under an `X-Presto-` header prefix, and this driver always sends `X-Trino-`. - -## Troubleshooting - -### Cannot reach the coordinator - -Confirm the host and that the coordinator port is open. Trino serves HTTP on `8080` and HTTPS on `8443` by default, and a load balancer in front of it usually serves HTTPS on `443`. - -### "The server requires an encrypted connection" - -The port serves HTTPS, usually `443` behind a load balancer, and **SSL Mode** is Disabled. The server's reply, often `400 The plain HTTP request was sent to HTTPS port` or `301 redirect to https://…`, is printed underneath. Set **Verify Identity**. A cluster whose certificate comes from a private authority also needs its CA file under **CA Certificate**, or **Required (skip verify)**. - -### "The network connection was lost" - -A coordinator that serves HTTPS itself, on `8443`, drops a plain HTTP request without answering. Set an SSL mode. A server that demands a client certificate can drop the connection the same way when **Client Certificate** is empty. - -### Authentication failed - -The coordinator rejected the user, password or access token it was sent, and its reply is printed. Check **Username** and the credential for the chosen **Auth Method**, or ask the cluster's administrator which method it accepts. - -### "A password is sent only over TLS" - -**SSL Mode** is Disabled and the connection has a password, so it was not sent over plain HTTP. Set **SSL Mode** to **Verify Identity**, or clear the password if the cluster has no authentication. - -### "An access token is sent only over TLS" - -**SSL Mode** is Disabled and the connection has an **Access Token**, so it was not sent over plain HTTP. A coordinator on plain HTTP ignores the token and trusts the user name alone. Set **SSL Mode** to **Verify Identity**, or clear the **Access Token** if the cluster has no authentication. - -### "Verify CA needs a CA certificate" - -**SSL Mode** is Verify CA and **CA Certificate** is empty, as on a connection imported from a URL or synced from another Mac. Choose the CA certificate that signed the server's certificate, or set **Verify Identity** to check it against the system trust store. - -### "The server redirected the request to …" - -Trino never redirects, so a proxy or load balancer in front of it sent this, and the redirect is not followed. A redirect to `https://` on the same host means the proxy serves HTTPS: set **Port** to the one named, usually `443`, and **SSL Mode** to **Verify Identity**. Any other address, often a sign-in page, means the host and port reach the proxy instead of Trino. - -### "The server requires a client certificate for TLS mutual authentication" - -The server asked for a client certificate and **Client Certificate** is empty. A proxy that demands one fails the TLS handshake, and a coordinator with certificate authentication answers `Unauthorized`, printed underneath. Choose **Client Certificate** and **Client Key** on the SSL pane. - -### "The server did not accept the client certificate" - -The server asked for a client certificate and refused the one sent. Check that **Client Certificate** chains to the CA the server trusts and has not expired. - -### "The client key at … is encrypted" - -The key needs a passphrase, and Trino connections have no **Key Passphrase**. A first line of `BEGIN ENCRYPTED PRIVATE KEY`, or a `Proc-Type: 4,ENCRYPTED` line, marks an encrypted key. Write a decrypted copy with `openssl pkey -in encrypted.key -out client.key` and choose that file. - -### "The client key at … does not belong to the certificate at …" - -**Client Key** is not the private key of **Client Certificate**. Choose the key the certificate was issued for. - -### NOT_SUPPORTED - -The catalog's connector does not implement that operation, whatever Trino's grammar allows. Check the connector's own documentation for what it supports. diff --git a/docs/databases/typesense.mdx b/docs/databases/typesense.mdx deleted file mode 100644 index 982c2172af..0000000000 --- a/docs/databases/typesense.mdx +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: Typesense -description: Connect to Typesense with collection browsing, document editing, and a REST request console ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -A collection's schema decides more than its column list. A field is sortable only when it was created with `sort: true`, and a column header that refuses to sort is the schema saying so rather than the grid. Typesense 26.0 and later are supported, over the REST API. - - - -## Quick setup - -Click **New Connection…**, select **Typesense**, enter host, port and the **API Key** the server was started with, then click **Save & Connect**. - -There is no Database field. One connection reaches one cluster and its collections are the objects. - -For a node on a private server, open the **Network** tab and set **Connect via** to **SSH Tunnel**, then set **Host** to the address Typesense listens on as the SSH server sees it, usually `localhost`. See [SSH Tunneling](/connections/ssh-tunneling). - -## Connection settings - -| Field | Description | -|-------|-------------| -| **Host** | Node hostname. `localhost` for a local server, `xxx.a1.typesense.net` for Typesense Cloud | -| **Port** | `8108` by default. Typesense Cloud answers on `443` | -| **API Key** | Sent as `X-TYPESENSE-API-KEY` on every request. Stored in the Keychain | -| **Skip TLS Verification** | Options tab. Trusts any certificate, even under **Verify CA** or **Verify Identity** | - -## Connection URL - -Typesense has no URL scheme of its own, so there is nothing to import or export. Enter the host, port and key on the form. - -## Authentication - -The API key is the whole credential. Typesense has no user accounts, so the form drops the Username and Password rows and shows **API Key** in their place. - -A scoped or search-only key restricts what the connection can reach. The sidebar reads `GET /collections`, so a key without `collections:list` opens a connection that shows no collections; the admin key or a key holding `collections:*` browses everything. - -## Browsing collections - -Collections are tables and documents are rows. `id` leads every grid, is the primary key, and is read-only: Typesense refuses to rewrite a document's id. - -Columns come from the collection schema. An `object` field flattens to its dotted leaves, so `meta.pages` is a column and `meta` is not, and array and object values render as JSON in the cell. A leaf of an `object[]` field is a column of its own (`variants.sku`) holding one entry per element, `null` where an element omits it. A collection created with the `.*` auto-schema learns its fields from the documents it is given, and those learned fields become columns as soon as the structure is reloaded. - -Sorting reaches only the fields the schema marks `sort: true`. Numeric and boolean fields get it by default, a `string` field does not unless it was created with `sort: true`, and `id` never has it. A column with no sort stays unsorted rather than failing the read. Typesense takes at most three sort fields, so a fourth sorted column is dropped. - -A page is read in requests of 250 documents, the most Typesense returns at once, batched fifty to a `multi_search`. The 1,000-row default page is one round trip. - -## Filtering - -Column filters become a `filter_by` expression. - -| Operator | Sent as | -|---|---| -| equals, not equals | ``field:=`value` ``, ``field:!=`value` ``. Numbers and booleans go unquoted | -| greater than, greater or equal, less than, less or equal | `field:>value`, `field:>=value`, `field:2000 && tag:=[fiction]`. - -String matching in Typesense always ignores case. The filter bar's case control is dimmed, and reads **Not supported by this database**. - -Six operators have no `filter_by` equivalent and are refused with a message rather than silently matching the wrong rows: is NULL, is not NULL, is empty, is not empty, matches regex and ends with. Use contains or a raw filter instead. - -A filter value containing a backtick is refused for the same reason. Backticks are the only way Typesense quotes a value and it offers no escape for one inside it, so the rest of such a value would be read as filter syntax. - -## Editing documents - -Saving grid edits sends REST calls keyed by `id`. - -| Change | Request | -|--------|---------| -| New row | `POST /collections/:name/documents`, or with an `id` you typed | -| Edited cells | `PATCH /collections/:name/documents/:id`, carrying only the changed fields | -| Deleted row | `DELETE /collections/:name/documents/:id` | - -Values are typed from the schema on the way out: an `int32` field is sent as a number, a `bool` as `true` or `false`, and an array or object field as parsed JSON. - -## Collection operations - -| Action | Request | -|--------|---------| -| **Export** | `GET /collections/:name/documents/export`, read as JSONL a line at a time, so a collection larger than memory still exports and the 250-hit search ceiling does not apply | -| **Delete** a collection | `DELETE /collections/:name` | -| **Empty** a collection | `DELETE /collections/:name/documents?truncate=true`, which keeps the schema and the fields it has learned | -| **Compact** the database | `POST /operations/db/compact`, from the console. It is not in the Maintenance menu, which acts on the selected collection, while compaction acts on the whole database | - -## Users & roles - -Typesense has no user accounts, so **Database > Users & Roles** lists API keys instead. Each key shows the collections it reaches and the actions it holds, and a key scoped to `*` reads as server-wide. - -A key is immutable once created: Typesense has no endpoint that changes one, and the key's value is returned exactly once, when it is created. So a key can be added and deleted here, and editing one is unavailable rather than being carried out as a delete and a recreate, which would rotate the value every client is already using. - -## Server metrics - -**Database > Server Dashboard** reads `/metrics.json` and `/stats.json`: process and system memory, memory fragmentation, disk use, requests per second, search and write latency, pending write batches and the cache hit ratio. There are no sessions to list and no slow query log, so those panels stay empty. - -## Request console - -The editor takes a method, a path and an optional JSON body, the way the Typesense docs write their curl examples. - - - Typesense request console with a multi_search body and a grid of matching documents - Typesense request console with a multi_search body and a grid of matching documents - - -```http -GET /collections - -GET /collections/books/documents/search?q=dune&query_by=title - -POST /multi_search -{ - "searches": [ - { "collection": "books", "q": "*", "filter_by": "year:>1960", "sort_by": "year:desc" } - ] -} - -POST /collections/books/documents -{ "id": "1", "title": "Dune", "year": 1965 } -``` - -A response carrying `hits` renders as a grid of documents, and so does a `multi_search` holding a single search. A JSON array renders as a grid of objects and `GET /collections/:name/documents/export` renders its JSONL the same way. Anything else, a `multi_search` batch included, comes back as pretty-printed JSON in a single cell. - -Creating a collection happens here, with `POST /collections`. The structure editor does not write schema changes. - -## SSL/TLS - -The connection's SSL mode picks the scheme: **Disabled** talks HTTP, anything else talks HTTPS. Typesense Cloud is HTTPS on port 443. **Skip TLS Verification** on the Options tab accepts any certificate, which covers a self-signed proxy in front of a local node. See [SSL/TLS](/connections/ssl) for the concepts. - -## Limitations - -- No transactions. Each console request, and each row you save, is its own HTTP call. -- No views, no triggers, no routines, and no foreign keys. Typesense has none of them. -- The structure editor is read-only. Change a schema with `POST /collections` and the collection alias endpoints in the console. -- No import. Load documents with `POST /collections/:name/documents/import` in the console. -- is NULL, is not NULL, is empty, is not empty, matches regex and ends with are refused in the filter bar. Ends with needs a field created with `infix: true`, which the filter bar cannot reach. -- A filter value containing a backtick is refused. Match on a value without one, or write the filter in the **Raw SQL** column. -- The console takes a method and a path, not SQL. `DELETE FROM books` looks like an HTTP verb and a path, so it is refused rather than sent. - -## Troubleshooting - -### Forbidden - a valid `x-typesense-api-key` header must be sent. - -The key was missing or wrong. Check **API Key** against the value the server was started with, or the key you created in Typesense Cloud. - -### Could not find a field named `…` in the schema for sorting. - -The console asked to sort a field the schema does not mark `sort: true`. Add `sort` to the field with a schema change, or sort on a different column. The grid drops such a sort instead of sending it. - -### Only upto 250 hits can be fetched per page. - -A console request asked for more than 250 in one `per_page` or `limit`. Split it across pages with `offset` and `limit`, or put several searches in one `POST /multi_search`. - -### Could not find a filter field named `…` in the schema. - -The filter names a field the collection does not declare. On an auto-schema collection the field appears only once a document carries it; reload the table's structure after indexing one. - -### Not an int32 - -A filter or a document sent text where the field's type wants a number. Check the value, and that `1965.0` is not being written into an `int32` field. - -## Related - -- [Elasticsearch](/databases/elasticsearch), the other search engine with a request console -- [Filtering](/features/filtering) -- [Import & Export](/features/import-export) diff --git a/docs/databases/weaviate.mdx b/docs/databases/weaviate.mdx deleted file mode 100644 index a6ae9ed926..0000000000 --- a/docs/databases/weaviate.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Weaviate -description: Connect to Weaviate, browse collections, edit objects by uuid, and run GraphQL ---- - -import RegistryPlugin from "/snippets/registry-plugin.mdx"; - -Collections are tables, objects are rows, and `uuid` is the primary key. The editor speaks GraphQL, not SQL. Vector and hybrid search go through `/v1/graphql`. Browse and row edits use `/v1/objects`. - - - -## Quick setup - -Click **New Connection…**, select **Weaviate**, enter host and port, pick an **Auth Method**, then click **Save & Connect**. - -There is no Database field. One connection reaches one Weaviate instance, and its collections are the objects. - -For a node on a private server, open the **Network** tab and set **Connect via** to **SSH Tunnel**, then set **Host** to the address Weaviate listens on as the SSH server sees it, usually `localhost`. See [SSH Tunneling](/connections/ssh-tunneling). - -## Connection settings - -| Field | Description | -|-------|-------------| -| **Host** | Node hostname. `localhost` for Docker, a Weaviate Cloud hostname for hosted clusters | -| **Port** | `8080` by default. Weaviate Cloud answers on `443` | -| **Auth Method** | None, or API Key | -| **API Key** | Sent as an `Authorization: Bearer` header, and only when **Auth Method** is API Key | -| **Skip TLS Verification** | Options tab. Trusts any certificate, even under **Verify CA** or **Verify Identity** | - -## Authentication - -| Auth Method | What is sent | -|-------------|--------------| -| **None** | Nothing. For a local node with anonymous access | -| **API Key** | An `Authorization: Bearer` header. Paste the Weaviate Cloud key, or the key the local node was started with | - -Weaviate Cloud requires an API key and HTTPS. Set **SSL Mode** to **Required** (or **Verify Identity** if you have the CA) and **Auth Method** to API Key. - -## Browsing collections - -The sidebar lists collections from `GET /v1/schema`. - -Every grid has `uuid` first and `vector` last. `uuid` is the primary key. Both columns are read-only. Property columns come from the collection schema. Arrays and objects render as JSON in the cell. - -A vector is shown as a JSON number array. It is not a SQL BLOB and the inline editor does not write it back. - -Unfiltered pages load through `GET /v1/objects`. A column filter or a sort becomes a GraphQL `Get` with `where` and `sort`. - -## Filtering - -Filter values are typed from the collection schema: an `int` property filters as a number, a `date` property needs a full RFC 3339 timestamp such as `2024-01-31T00:00:00Z`, and an array property filters on one element. - -| Operators | What Weaviate runs | -|----------|--------------------| -| equals, not equals | `Equal`, `NotEqual` | -| greater than, greater or equal, less than, less or equal | Numbers and dates only | -| contains, not contains, starts with, ends with | `Like` with `*` wildcards, text properties only | -| in list, not in list | `ContainsAny`, `ContainsNone` over a comma-separated list | -| between | Two bounds, both inclusive | -| is NULL, is not NULL | `IsNull`, which needs `indexNullState` on the collection | -| is empty, is not empty | `len()`, which needs `indexPropertyLength` on the collection | - -**REGEX** has no Weaviate equivalent, and a filter Weaviate cannot run reports why instead of returning the whole collection. - -Grid edits are REST calls keyed by `uuid`. - -| Change | Request | -|--------|---------| -| New row | `POST /v1/objects`, with an `id` if you typed one | -| Edited cells | `PATCH /v1/objects/{uuid}?class=Collection` | -| Deleted row | `DELETE /v1/objects/{uuid}?class=Collection` | - -An update or delete with no `uuid` is skipped and logged. - -## GraphQL editor - -Type a GraphQL operation and run it. Weaviate's GraphQL API reads only: `{ Get { … } }` and `query { … }` work, and there is no mutation type. Writes go through REST. - -```graphql -{ - Get { - Article( - nearText: { concepts: ["search term"] } - limit: 10 - ) { - title - _additional { id distance } - } - } -} -``` - -`Get` responses render as a grid of the fields the query selected, with `_additional.id` mapped to `uuid`. The rest of `_additional`, such as `distance` and `score`, each become a column. Anything else, including `Aggregate`, is shown as formatted JSON. - -A REST line also runs, the way the other search drivers do: - -```http -GET /v1/schema -``` - -A path without `/v1` gets it, so `GET /nodes` reaches `/v1/nodes`. A body goes on the same line as the path or on the lines under it. - -## SSL/TLS - -New connections start on **Disabled**, plain HTTP. Every other mode goes over HTTPS. **Preferred** and **Required (skip verify)** accept a self-signed certificate. See [SSL/TLS](/connections/ssl). - -## Limitations - -- Oracle-style SQL is not available. Use GraphQL, or the REST console for `/v1/schema` and `/v1/objects`. -- gRPC is not used. -- Cross-references are properties, not foreign keys. There are no routines, triggers, or schema edits from Structure. -- Multi-tenancy is not exposed. Name a tenant in GraphQL if the collection requires one. -- Paging stops at row 10,000. Weaviate refuses an offset and limit that add up past `QUERY_MAXIMUM_RESULTS`, which defaults to 10,000. -- The plugin is registry-only. It is not on iPhone or iPad. - -## Troubleshooting - -### Authentication failed: … - -The API key was rejected, or the node expects a key and **Auth Method** is None. Weaviate Cloud needs both HTTPS and an API key. - -### The connection drops after the API key changes - -A revoked or rotated key fails the next health check, because the check reads `/v1/meta` rather than the unauthenticated readiness endpoint. Paste the new key and connect again. - -### Connection failed: … - -A TLS or network failure. For a self-signed certificate set **SSL Mode** to **Required (skip verify)**, or turn on **Skip TLS Verification**. - -## Related - -- [Import & Export](/features/import-export) -- [Filtering](/features/filtering) diff --git a/docs/developers/applescript.mdx b/docs/developers/applescript.mdx deleted file mode 100644 index f2f080783b..0000000000 --- a/docs/developers/applescript.mdx +++ /dev/null @@ -1,234 +0,0 @@ ---- -title: AppleScript -description: Scripting dictionary for connections, tabs, results and queries, with the gates a script has to clear ---- - -A script is an external client with no token, so what it may do is decided by the connection's -**External Clients** level and by [Safe Mode](/features/safe-mode). The first time a script targets -TablePro, macOS asks the sending app for Automation permission. - -Script Editor lists TablePro under Open Dictionary in its File menu. - -```applescript -tell application "TablePro" - set result to run query "SELECT id, email FROM users LIMIT 5" in connection "Production" - columns of result --> {"id", "email"} - row count of result --> 5 - values of item 1 of rows of result --> {"1", "ada@example.com"} -end tell -``` - -## What a script may do - -| Level on the connection | A script may | -|---|---| -| Blocked | Nothing. The connection is not in `connections` at all | -| Read only | Read. Any statement that is not a read is refused | -| Read & Write | Read and write, subject to Safe Mode | - -Read Only is the default and the right level for most connections: reporting and export scripts -need nothing more. Raise a connection to Read & Write with **External Clients** on its **Options** -tab only when a script has to write to it. - -Read & Write is not a way past Safe Mode. A statement that drops or truncates always raises a -confirmation naming the app that sent it, whatever the level says, and Safe Mode's authentication -step still runs. - -Automation permission is granted per sending app, not per connection. Granting Terminal control of -TablePro lets any script run from Terminal read every connection that is not Blocked. Revoke it in -**System Settings > Privacy & Security > Automation**. - -Every scripted statement is written to the history drawer under **AppleScript**, and to the -execution audit log. - -## Objects - -```text -application -└── connection - └── tab -``` - -`connections` lists every saved connection whose **External Clients** level is not Blocked, sorted -by name. Reach one by name or by id. - -```applescript -tell application "TablePro" - name of every connection - connection id "9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1" - every connection whose connected is true - every connection whose tags contains "production" - name of every connection whose group path contains "Acme" -end tell -``` - -### connection - -| Property | Type | | -|---|---|---| -| `id` | text | Stable. The same UUID the URL scheme takes | -| `name` | text | | -| `database type` | text | `PostgreSQL`, `MySQL`, and the rest | -| `host`, `port` | text, integer | | -| `current database` | text | The database in use, not the saved one | -| `current schema` | text | On engines that have schemas | -| `connected` | boolean | | -| `safe mode` | enumeration | `silent`, `alert`, `alert full`, `authenticate`, `authenticate full`, `read only` | -| `external access` | enumeration | `blocked`, `read only`, `read write` | -| `color` | enumeration | `none`, `red`, `orange`, `yellow`, `green`, `blue`, `purple`, `pink`, `gray` | -| `group path` | list of text | The group names from the top-level group down. Empty when the connection is in no group | -| `group color` | enumeration | `none` when the connection is in no group | -| `tags` | list of text | Tag names, in the order the user added them | - -In a `whose` clause, `tags` and `group path` match whole names, ignoring case: `tags contains "prod"` -does not match a tag named `production`. - -No credential is reachable from a script, and neither is the account name. Every property is read only. - -### tab - -| Property | Type | | -|---|---|---| -| `id`, `name` | text | | -| `kind` | enumeration | `query editor`, `table`, `create table`, `diagram`, `server dashboard`, `users and roles`, `query insights`, `object source`, `version history` | -| `table name`, `database name`, `schema name` | text | | -| `query` | text | The SQL of a query tab | -| `current result` | query result | The rows the tab is showing | -| `selection` | query result | The rows selected in its grid | - -`current tab` and `current connection` on the application are the front window's. - -Every property is read only. To put SQL in front of someone, use the URL scheme's -[query link](/developers/url-scheme#run-a-query), which shows them the statement first. - -### query result - -A record. Assign it to a variable and the rows stay with it. - -| Property | Type | | -|---|---|---| -| `columns` | list of text | | -| `rows` | list of result row | Each holds `values`, a list of text | -| `row count` | integer | | -| `rows affected` | integer | | -| `truncated` | boolean | True when `row limit` cut the result short | -| `execution time` | real | Milliseconds | -| `status message` | text | What the server said, when it said anything | -| `results` | list of result set | Every result set, when a SQL Server script returned more than one. Empty otherwise | - -A cell is always text. NULL is the empty string, and binary is Base64. - -### result set - -One entry of `results`, with the `columns`, `rows`, `row count`, `truncated` and `status message` -of that result set. - -## Commands - -### run query - -```applescript -run query "SELECT 1" in connection "Production" ¬ - database "analytics" schema "public" row limit 1000 timeout 60 -``` - -Runs one statement and returns a `query result`. No window opens and no tab is disturbed; the -connection opens first if it is closed, which can prompt for a password. - -`row limit` defaults to 500 and caps at 10000. `timeout` defaults to 30 seconds and caps at 600. -Several statements in one call are refused, as are statements that read files or run server-side -code. - -SQL Server is the exception to one statement per call: send the whole script, and it runs batch by -batch at its `GO` lines, as the query editor runs it. The record is the first result set, -`rows affected` counts the whole script, and `results` holds every result set when there is more -than one. `row limit` caps each result set on its own. - -```applescript -tell application "TablePro" - set r to run query "EXEC sp_help 'dbo.orders'" in connection "Warehouse" - set sets to results of r - repeat with one in sets - columns of one - end repeat -end tell -``` - -Read `results` into a variable before counting it. Inside a `tell` block, `count of results of r` -is sent to TablePro and answers 0. - -AppleScript gives up on any command after two minutes. Wrap a long query in -`with timeout of 300 seconds`. - -### open table - -```applescript -tell application "TablePro" - set t to open table "orders" in connection "Production" schema "public" - row count of current result of t -end tell -``` - -Opens the table in a tab and brings it forward, reusing a tab that already shows it. Returns the -tab. - -### connect, disconnect, show - -```applescript -tell application "TablePro" - connect connection "Production" - show connection "Production" - disconnect connection "Production" -end tell -``` - -`connect` opens the session without opening a window. `show` brings the connection's window -forward, opening one if it has none. - -### focus - -```applescript -tell application "TablePro" - focus tab 1 of connection "Production" -end tell -``` - -## Read the grid - -`selection` reads what is selected in the tab's grid, in display order, with hidden columns left out -and the filter bar applied. An empty selection reads as zero rows; `current result` reads them all. - -```applescript -tell application "TablePro" - set picked to selection of current tab - repeat with r in rows of picked - log item 1 of values of r - end repeat -end tell -``` - -## Errors - -A refusal is an ordinary AppleScript error carrying the reason. - -```applescript -try - tell application "TablePro" - run query "DELETE FROM users" in connection "Production" - end tell -on error message number code - message --> "This connection is read only for external clients." -end try -``` - -| Number | | -|---|---| -| `-1728` | No connection or tab by that name or id | -| `-50` | The request is malformed: several statements in one call, or no database to run against | -| `-10000` | Refused by Safe Mode or by **External Clients**, or cancelled at the confirmation | - -## Related - -- [URL scheme](/developers/url-scheme) drives the GUI and returns nothing -- [MCP tools](/developers/mcp-tools) cover schema, export and server work a script cannot reach -- [Safe Mode](/features/safe-mode) is the gate in front of every write diff --git a/docs/developers/connection-import.mdx b/docs/developers/connection-import.mdx deleted file mode 100644 index edbc4cd35a..0000000000 --- a/docs/developers/connection-import.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Connection Import -description: Every query parameter a tablepro://import link accepts, and which ones the app refuses ---- - -Passwords are never accepted in an import link, and neither is anything else that decides where a -credential comes from. What is left is the rest of the connection form, one query parameter per -field, and a review sheet the user reads before anything is saved. - -```text -tablepro://import?name=Staging&host=db.example.com&port=5432&type=postgresql&username=admin -``` - -`name`, `host` and `type` are required. `socket` can stand in for `host`. `type` takes any registered database type name, -case-insensitive: `MySQL`, `PostgreSQL`, `MongoDB`, `Redis`, `ClickHouse`, `Oracle`, `DuckDB`, -`Cassandra`, and every other type a loaded plugin registers. - -Enum-valued parameters must match a listed value exactly, since these are stored raw values rather -than display names, and an unrecognized one falls back rather than failing the link. Percent-encode -values that contain spaces: `sslMode=Verify%20CA`. - -## Core parameters - -| Parameter | Description | -|-----------|-------------| -| `port` | Server port. Defaults to the type's standard port. | -| `socket` | MySQL and MariaDB. Socket file path, in place of `host` and `port`. See [Socket](#socket). | -| `username` | Database username. | -| `database` | Default database name. | -| `color` | Sidebar color: `Red`, `Orange`, `Yellow`, `Green`, `Blue`, `Purple`, `Pink`, `Gray` or `None`. Falls back to `None`. | -| `icon` | SF Symbol name to draw instead of the type's icon, such as `server.rack`. Lowercase letters, digits and dots, up to 100 characters; anything else is dropped. A name this macOS lacks is kept and draws the type's icon. | -| `tagName` | Tag to assign. Repeat the parameter for more than one tag. | -| `groupName` | Group to place the connection in. Repeat the parameter for a nested group, outermost first. | -| `safeModeLevel` | `silent`, `alert`, `alertFull`, `safeMode`, `safeModeFull` or `readOnly`. Falls back to `silent`. | -| `aiPolicy` | `alwaysAllow`, `askEachTime` or `never`. Falls back to the app default. | -| `redisDatabase` | Redis database index to select on connect. | -| `startupCommands` | SQL to run after connecting. The review sheet shows it in full. | -| `connectTimeoutSeconds` | Seconds to wait for the connection, `1` to `600`. Out of range falls back to the app default. | -| `queryTimeoutSeconds` | Seconds a query may run before it is canceled, `0` to `2147483`. `0` turns the limit off. Out of range falls back to the app default. | -| `localOnly` | `1` keeps the connection off iCloud sync. | - -### Socket - -- `host` becomes optional and defaults to `localhost`. -- Other types and `ssh=1` ignore `socket`. -- The path must start with `/` and fit in 103 bytes. -- A path with a control or invisible character refuses the link. -- `af_localSocketPath` is read the same way. - -## SSH parameters - -Set `ssh=1`, or none of these are read. - -| Parameter | Description | -|-----------|-------------| -| `sshHost` | SSH server hostname. | -| `sshPort` | SSH port. Default `22`. | -| `sshUsername` | SSH username. | -| `sshAuthMethod` | `Password`, `Private Key`, `SSH Agent`, `Keyboard Interactive` or `None`. Falls back to `Password`. | -| `sshPrivateKeyPath` | Path to the private key file. | -| `sshAgentSocketPath` | Custom SSH agent socket path. | -| `sshJumpHosts` | JSON array of `{host, port, username, authMethod, privateKeyPath}`. Jump `authMethod` is `Private Key` or `SSH Agent`, falling back to `SSH Agent`. | -| `sshTotpMode` | `none`, `auto_generate` or `prompt_at_connect`. Falls back to `none`. | -| `sshTotpAlgorithm` | `SHA1`, `SHA256` or `SHA512`. Default `SHA1`. | -| `sshTotpDigits` | Code length. Default `6`. | -| `sshTotpPeriod` | Period in seconds. Default `30`. | - -## SSL parameters - -| Parameter | Description | -|-----------|-------------| -| `sslMode` | `Disabled`, `Preferred`, `Required`, `Verify CA` or `Verify Identity`. Falls back to `Required`, and the review sheet says so. | -| `sslCaCertPath` | CA certificate file path. | -| `sslClientCertPath` | Client certificate file path. | -| `sslClientKeyPath` | Client key file path. | - -## Driver fields - -An `af_` prefix passes a driver-specific field straight to the plugin: `af_replicaSet=myrs` sets -`replicaSet` on a [MongoDB](/databases/mongodb) connection. Field names come from the plugin, so the -engine's own page is where they are listed. - -Six field names are dropped on import, along with any name starting with `aws`, because each decides -where a credential comes from: `af_preConnectScript`, `af_preTunnelHost`, `af_preTunnelPort`, -`af_promptForPassword`, `af_sslClientKeyPassphrase` and `af_usePgpass`. The same rule covers -`.tablepro` files, described in -[Connection Sharing](/features/connection-sharing#import). Add any of these by hand in the connection -editor afterwards. - -## What the user sees - -The review sheet shows the type, host and port or socket, database, username, SSH auth method, SSL mode, color, -icon, tags, group path, driver fields, and startup SQL. Only the name is editable. **Add Connection** -saves it; when a connection to the same host, port, socket, database and username is already saved, the sheet -warns *"A connection to this server is already saved as “…”."* and the button becomes -**Add as Copy**. - -The saved connection has no password, so open it in the connection editor and add one before -connecting. diff --git a/docs/developers/index.mdx b/docs/developers/index.mdx deleted file mode 100644 index c8ac5b06bb..0000000000 --- a/docs/developers/index.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Build an integration -description: Pick the surface for the job, find TablePro by bundle ID, and know what stays stable before you ship ---- - -import StabilityMatrix from "/snippets/stability-matrix.mdx"; - -What you need back decides the surface. Putting something on screen needs no setup at all; reading -rows back needs AppleScript or a token. - -## Pick a surface - -| Job | Surface | Data back | What the user approves | -| --- | --- | --- | --- | -| Open a saved connection, table or query | [`tablepro://` links](/developers/url-scheme) | No | The SQL of a query link, and a connection's pre-connect script | -| Open a database nobody saved | [Database URLs](/connections/urls), opened by bundle ID | No | **Open External Database Connection?**, with **Always Allow** for a database on this Mac | -| Hand over a connection to keep | [Connection import](/developers/connection-import) | No | A review sheet before the connection is saved | -| List connections or run a query from a script | [AppleScript](/developers/applescript) | Yes | Automation permission, once per sending app | -| Give an AI client or a long-running tool schema and rows | [MCP](/developers/mcp-protocol) | Yes | A token or the bundled bridge, then each connection on first use | -| Get a token for your own extension | [Pairing](/developers/pairing) | A token | The pairing sheet | -| Add a database engine or a file format | [A plugin](/development/plugin-development) | Runs inside the app | Trust in the developer who signed it | - -A launcher needs two of these: AppleScript or MCP to list connections, and a -`tablepro://connect/` link to open one. On iPhone and iPad, an integration has -[two connection links](/developers/url-scheme#on-iphone-and-ipad) and the -[Shortcuts actions](/integrations/ios-shortcuts). - -## Find the app - -Look TablePro up by its bundle ID, `com.TablePro`, through Launch Services. A path check fails twice -over: the app runs from `/Applications`, `~/Applications` or any other folder, and an unrelated app -also installs itself as `/Applications/TablePro.app`. - - -```swift Swift -import AppKit - -if let app = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.TablePro") { - let version = Bundle(url: app)?.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String - let bridge = app.appendingPathComponent("Contents/MacOS/tablepro-mcp") -} -``` - -```bash Shell -# Opens the link in TablePro, or exits 1 when no app has that bundle ID -open -b com.TablePro "tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1" - -# Prints the app's path without launching it, or an empty line -osascript -l JavaScript -e 'ObjC.import("AppKit"); var u = $.NSWorkspace.sharedWorkspace.URLForApplicationWithBundleIdentifier("com.TablePro"); u.isNil() ? "" : u.path.js' -``` - -```ts Raycast -import { getApplications, open } from "@raycast/api"; - -const installed = (await getApplications()).some((app) => app.bundleId === "com.TablePro"); -if (installed) { - await open("tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1", "com.TablePro"); -} -``` - - -Check the version before relying on a newer surface: `CFBundleShortVersionString` in the bundle, -or the `serverInfo` version once the MCP server answers. - -Read nothing the app writes to disk: not `~/Library/Application Support/TablePro`, not the -`com.TablePro` preferences, not its Keychain items. None of them is under the contract, -[Versioning](/developers/versioning#what-is-not-under-the-contract) names what to call instead, and -the directory [refuses](/developers/listing-policy#rules) an integration that reads them. - -## What stays stable - - - -[Versioning](/developers/versioning) has the rule for each surface and how a break is announced. - -## Security model - -The MCP server binds `127.0.0.1`, and on stock settings every request carries a token. A call is -allowed only where the token's scope, the token's connection allowlist, and the connection's own -**External Clients** level all permit it; the effective permission is the lowest of them. -[Tokens](/developers/tokens) has the full model. On top of that, an AI policy of **Never** refuses -the connection outright, and [Safe Mode](/features/safe-mode) still holds destructive statements -behind a confirmation. - -AppleScript has no token. macOS asks the sending app for Automation permission instead, and the -connection's **External Clients** level and Safe Mode apply exactly as they do over MCP. The AI -policy does not: it governs the assistant, not other apps. - -A database URL that matches no saved connection opens at **Silent** unless it sets -`safeModeLevel`: `1` asks before each write and `2` refuses writes. A URL that matches a saved -connection runs at that connection's own level and ignores the parameter. A listed integration may -lower Safe Mode, such as with `safeModeLevel=0`, only for local development databases, and its -directory page says so. - -Each request lands in the activity log with the token behind it, and a statement is stored as a -SHA-256 digest rather than as text. Open **Settings > MCP** and click **View Activity** to -read it. - -## Publish it - -Listed integrations appear at [tablepro.app/integrations](https://tablepro.app/integrations), and -each one is a folder in [TableProApp/integrations](https://github.com/TableProApp/integrations). -[Submit to the directory](/developers/submit) walks through the pull request, the -[listing policy](/developers/listing-policy) is what review holds the entry to, and -[Review process](/developers/review) covers the time from pull request to listing. diff --git a/docs/developers/listing-policy.mdx b/docs/developers/listing-policy.mdx deleted file mode 100644 index 15545b24ba..0000000000 --- a/docs/developers/listing-policy.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: Listing policy -description: What gets an integration listed, labeled, archived or refused on tablepro.app/integrations ---- - -Review applies [POLICY.md](https://github.com/TableProApp/integrations/blob/main/POLICY.md) in the -registry, and where this summary differs from it, POLICY.md wins. A listing says the integration -worked with TablePro and met the policy when it was reviewed. It is not an endorsement or a security -audit, and support comes from the integration's author, not from TablePro. - -## Labels - -| Label | Built by | Assigned by | -| --- | --- | --- | -| Official | TablePro | A maintainer | -| Partner | The company that makes the other product, confirmed through its GitHub organization or a DNS record on its domain | A maintainer, after that check | -| Community | Anyone else | The default for every new entry | - -Every entry also shows **By** and its publisher, and a closed-source one shows **Closed source**. - -**Archived** is a status, not a label. An archived entry leaves the directory list and search, and -its page stays with a notice. An author who fixes the problem can ask for it to return. - -## Rules - -| Rule | What it asks | -| --- | --- | -| Documented interfaces | Only the surfaces in [these developer docs](/developers): the `tablepro://` URL scheme, database URLs, the `tablepro` command, AppleScript, MCP, pairing and tokens, and Shortcuts. Find TablePro by bundle ID `com.TablePro`, not by path | -| Private files | Never read, write or delete `connections.json`, `com.TablePro.plist`, `mcp-handshake.json`, TablePro's Keychain items or its SQLite databases. CI scans open-source entries for these paths, and a new entry gets no exception and no grace period | -| Who submits | The repository owner, a member of the owning organization, or a contributor with at least a third of the top contributor's commits. Anyone else needs the owner's consent in a comment on the pull request | -| License | An open-source entry has a `LICENSE` file with an SPDX license that matches the manifest. Closed source is allowed, and its disclosures rest on the publisher's word | -| Name | Your product is ` for TablePro`. `TablePro` alone and `TablePro for ` are reserved for TablePro's own products. The entry's name in the directory leaves TablePro out, and never says official, verified or certified | -| Icon | Your own. The TablePro app icon is refused as your icon, and a small TablePro badge in a corner of yours is fine | -| Safe Mode | Opening connections below the user's default, such as with `safeModeLevel=0`, is allowed only for local development databases. The manifest discloses it and the entry page shows it | -| Disclosures | What it reads and writes, whether it lowers Safe Mode, where it sends data, any account it needs, and what it costs. A wrong disclosure is a violation | -| Popularity | No minimum stars, installs or downloads | - -The [brand guidelines](https://tablepro.app/brand) apply on top. [Naming your -product](https://tablepro.app/brand#naming) lists every allowed and refused name form, and [Using -the logo and app icon](https://tablepro.app/brand#logo-use) keeps a badge to a third of your icon's -width. Text and image limits for the entry itself are on -[Submit to the directory](/developers/submit#manifest-fields). - -## Grace periods - -An entry listed before the policy took effect has 60 days, counted from the day its author is -contacted, to fix any problem with interfaces, private files, license, Safe Mode, disclosures, name -or icon that it already had. Its page says so while the time runs. If the problem is still there -when the 60 days end, the entry is archived. - -A later version of the policy gives existing entries 60 days from its effective date to meet a new -rule. - -## Removal - -An entry is removed when its publisher asks, or at once, without contact or waiting, for malware, -credential theft, hidden data collection or a deceptive install link. A removed slug is never used -again, and its page answers 410 Gone. [Review process](/developers/review#report-a-listed-integration) -says where to report one. - -## Your text and images - -Manifest text is released under CC0-1.0. Icons and screenshots stay yours, and submitting them lets -TablePro show them in the directory and in the registry's published data. diff --git a/docs/developers/mcp-prompts.mdx b/docs/developers/mcp-prompts.mdx deleted file mode 100644 index 452ab4b69b..0000000000 --- a/docs/developers/mcp-prompts.mdx +++ /dev/null @@ -1,143 +0,0 @@ ---- -title: MCP Prompts -description: The eight prompts TablePro renders from a live schema, and how completion/complete fills their arguments ---- - -import MCPGates from "/snippets/mcp-gates.mdx"; - -Picking `explain_table` from your client's prompt menu makes TablePro read the table first. So the model gets its real columns, indexes and DDL rather than a template asking it to guess. Rendering happens on the server, inside `prompts/get`. - -## Listing and getting - -`prompts/list` returns the catalog with each prompt's arguments. `prompts/get` renders one: - -```json -{ - "method": "prompts/get", - "params": { - "name": "explain_table", - "arguments": { "connection": "Production", "table": "orders", "audience": "analyst" }, - "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } - } -} -``` - -The result is a `description` and one user message. Argument values must be scalars: a number or a boolean is read as its text, an array or object is rejected. An argument the prompt does not declare is rejected with `-32602`, as is a missing required one. - - - -The read is recorded in the activity log. - -## Shared arguments - -Most prompts take these: - -| Argument | Required | Meaning | -|----------|:--------:|---------| -| `connection` | yes | Connection name or UUID. Name matching is case-insensitive and exact, and two connections sharing a name are ambiguous, so pass the UUID there. | -| `database` | no | Database to read. The connection's current one when omitted. | -| `schema` | no | Schema to read. The connection's current one when omitted. | -| `table` | varies | Table name. Required where the prompt is about one table. | - -## The catalog - -Required arguments come first; optional ones follow in parentheses. - -| Prompt | Arguments | What it renders and asks for | -|--------|-----------|------------------------------| -| `explain_schema` | `connection` (`database`, `schema`, `audience`) | An inventory of up to 200 tables with row counts, then full structure for the 8 largest. Asks for a guided tour of the schema. | -| `explain_table` | `connection`, `table` (`database`, `schema`, `audience`) | One table's columns, keys, indexes and DDL, explained to someone who has never used it, ending in three queries worth running. | -| `data_quality_audit` | `connection`, `table` (`database`, `schema`) | One table's structure, turned into a checklist of runnable data quality queries ordered cheapest first. | -| `question_to_sql` | `connection`, `question` (`tables`, `database`, `schema`) | An inventory of up to 150 tables plus full structure for up to 6, and rules that forbid inventing columns and require a `LIMIT`. | -| `review_query` | `connection`, `query` (`explain_plan`, `database`, `schema`) | Your query, your plan if you pass one, and the structure of up to 12 tables the SQL names, in query order, with the ones not found or not readable listed. Reviews correctness, then cost, then risk. | -| `propose_indexes` | `connection`, `query` (`explain_plan`, `database`, `schema`) | The same tables with the indexes they already have, including each index's predicate and whether it is valid. Asks for ranked `CREATE INDEX` statements and any index the query makes redundant. | -| `write_migration` | `connection`, `change` (`table`, `database`, `schema`) | Current structure with DDL when a table is named, plus the connection's Safe Mode level. Asks for the migration, the rollback and a verifying query. | -| `summarize_query_history` | `connection` (`period`, `limit`) | The recorded history for that connection, summarized by intent, with every write, every failure and the slowest statements called out. | - -`audience` is `newcomer` (default), `analyst` or `engineer`. `period` is `today`, `this_week` (default), `this_month` or `all`, and `limit` runs 1 to 500, default 50. `tables` on `question_to_sql` is comma-separated. The prompt carries the columns of at most 6 tables: the first 6 you name, or the first 6 by name when `tables` is omitted. When that leaves tables out, the prompt says how many it covers and tells the model to call `describe_table` for the rest. - -Statement text is truncated at 8,000 characters where `review_query` and `propose_indexes` echo your `query` and `explain_plan`, and at 600 characters per row in the `summarize_query_history` table. - -## What comes back - -One user message, as Markdown. `explain_table` renders this shape: - -````markdown -Explain the table `orders` on a live PostgreSQL database. - -## Connection -- Connection: Production (PostgreSQL) -- Scope: app.public -- Server version: 16.4 -- Safe mode: silent - -## Structure -### orders -Approximate rows: 1,204,331 - -| Column | Type | Nullable | Key | Default | Comment | -| --- | --- | --- | --- | --- | --- | -| id | uuid | no | PK | | | - -Indexes: -… - -DDL: -```sql -CREATE TABLE orders (…) -``` - -Cover, in this order: -1. What one row of this table represents, in one sentence. -… -```` - -## Completions - -`completion/complete` resolves a prompt argument or a resource-template variable against the live schema. It needs `resources:read`. - -```json -{ - "method": "completion/complete", - "params": { - "ref": { "type": "ref/prompt", "name": "explain_table" }, - "argument": { "name": "table", "value": "ord" }, - "context": { "arguments": { "connection": "Production", "database": "app" } }, - "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } - } -} -``` - -```json -{ "completion": { "values": ["orders", "order_items"], "total": 2, "hasMore": false } } -``` - -`ref` is either `{ "type": "ref/prompt", "name": ... }` or `{ "type": "ref/resource", "uri": ... }`. An unknown prompt or an unrecognized template is `-32602`. - -### What resolves - -| Argument or variable | Resolves to | -|----------------------|-------------| -| `connection` | Connection names, falling back to UUIDs when two connections share a name | -| `connection_id` | Connection UUIDs | -| `database` | Databases on the connection named in `context.arguments` | -| `schema` | Schemas in that database | -| `table` | Tables in that database and schema | -| `audience` | `newcomer`, `analyst`, `engineer` | -| `period` | `today`, `this_week`, `this_month`, `all` | -| `date_filter` | `today`, `thisWeek`, `thisMonth` | -| `row_counts` | `true`, `false` | - -Anything else returns an empty list rather than an error. - -### Context matters - -`database`, `schema` and `table` need to know which connection to read. Pass it in `context.arguments` under `connection`, `connection_id`, or `id`. Without one, the result is empty. - -For a `ref/resource` completion TablePro fills the context from the URI itself, so completing `table` in `tablepro://connections/{id}/tables/{table}` already knows the connection, and the `database` and `schema` query parameters if the URI carries them. Anything you pass in `context.arguments` wins. - -### Matching and limits - -Values whose name starts with what you typed come first, then values that merely contain it, each group sorted naturally, case ignored. Fixed value lists such as `audience` keep their declared order instead. Completing `connection` with a value that matches no name falls back to matching UUIDs, so pasting an id into a field that normally takes a name still works. - -At most 100 values come back; `total` counts the matches before that cap and `hasMore` says whether it applied. Schema reads are cached for 15 seconds per token and per scope, so typing a table name does not hit the database on every keystroke. The connection list is read on every request, so a connection set to **Never** or **Blocked** stops completing at once. diff --git a/docs/developers/mcp-protocol.mdx b/docs/developers/mcp-protocol.mdx deleted file mode 100644 index df17fa7502..0000000000 --- a/docs/developers/mcp-protocol.mdx +++ /dev/null @@ -1,345 +0,0 @@ ---- -title: MCP Protocol -description: "Wire contract for TablePro MCP server: protocol versions, required metadata, headers, result envelope, cache hints, and error codes" ---- - -`2026-07-28` is the revision to write against, and two older ones still answer for clients that have not moved. This page is the wire contract; for the tools themselves see [MCP Tools](/developers/mcp-tools). - -Most people never read it. The bundled `tablepro-mcp` stdio bridge speaks this for you, and every client in [MCP Clients](/integrations/mcp-clients) goes through the bridge. Read on if you are writing a client against the HTTP endpoint directly. - -## One round trip - -Everything below is a detail of this request. - -```bash -curl -sS http://127.0.0.1:23508/mcp \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer tp_your_token_here' \ - -H 'MCP-Protocol-Version: 2026-07-28' \ - -H 'Mcp-Method: tools/call' \ - -H 'Mcp-Name: list_connections' \ - -d '{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "list_connections", - "arguments": {}, - "_meta": { - "io.modelcontextprotocol/protocolVersion": "2026-07-28", - "io.modelcontextprotocol/clientCapabilities": {} - } - } - }' -``` - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": { - "content": [{ "type": "text", "text": "{\"connections\":[…]}" }], - "structuredContent": { "connections": [] }, - "isError": false, - "resultType": "complete", - "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "tablepro", "version": "0.67.0" } } - } -} -``` - -## Endpoint - -One path, one method: - -``` -POST http://127.0.0.1:/mcp -``` - -The server binds `127.0.0.1` and speaks plain HTTP, which the specification permits for a server on the same machine, and there is no remote mode. `GET` and `DELETE` on `/mcp` answer `405 Method Not Allowed` with `Allow: POST, OPTIONS`. `OPTIONS` answers `204` for CORS preflight. Any other path answers `404`. - -The `Host` header must name a loopback host (`localhost`, `127.0.0.1`, `::1`) and, if it carries a port, the port the server bound. Anything else answers `403 forbidden_host`. - -`Origin`, when present, must be `https://claude.ai` or `https://app.cursor.com`. Any other origin answers `403 forbidden_origin`. A request with no `Origin` is fine, which is the normal case for a local client. - -The port is `23508` by default. Make it a setting: when that port is busy the server binds another one, and the **Status** row in **Settings > MCP** shows the port in use. The handshake file is [not under the contract](/developers/versioning#what-is-not-under-the-contract). - -## Protocol versions - -| Version | Era | Notes | -|---------|-----|-------| -| `2026-07-28` | modern | The one to build against. Stateless. | -| `2025-11-25` | legacy | `initialize` handshake, `Mcp-Session-Id`. | -| `2025-06-18` | legacy | Same as above. | - -`2025-03-26` is no longer accepted. It requires JSON-RPC batching, which TablePro never implemented. - -`server/discover` serves the live list in `supportedVersions`. Read it from there rather than copying it into your client. An unsupported version comes back as error `-32022` with the supported list in `data.supported`. - -## Every request carries its version - -There is no handshake in the modern era: no `initialize`, no `Mcp-Session-Id`, no `ping`, no `GET` stream, no SSE resumability. Each POST stands on its own, so it has to say who is calling and what it speaks. Two `_meta` fields are required on every request: - -- `io.modelcontextprotocol/protocolVersion` (**required**): a supported version string. Missing or empty is `-32602`; unsupported is `-32022`. -- `io.modelcontextprotocol/clientCapabilities` (**required**): a JSON object. `{}` is valid and means you support nothing extra. A non-object is `-32602`. -- `io.modelcontextprotocol/clientInfo` (optional): `name`, `title`, `version`. It names your client in the activity log and in **Connected Clients**. -- `progressToken` (optional): string or integer. See [Progress](#progress). -- `traceparent`, `tracestate`, `baggage` (optional): W3C trace context, carried through untouched. - -Declaring `elicitation` in `clientCapabilities` changes how TablePro asks for approval of a write. See [Approvals](#approvals-and-input-required). - -## Required headers - -The modern era mirrors part of the body into headers so a proxy can route and audit without parsing JSON. TablePro checks that the copies agree, and rejects any disagreement with `-32020`. - -| Header | When | Must equal | -|--------|------|-----------| -| `Content-Type` | always | `application/json` (or any `+json` type) | -| `MCP-Protocol-Version` | always | `params._meta.io.modelcontextprotocol/protocolVersion` | -| `Mcp-Method` | always | the JSON-RPC `method` | -| `Mcp-Name` | `tools/call`, `prompts/get` | `params.name` | -| `Mcp-Name` | `resources/read` | `params.uri` | -| `Mcp-Param-` | optional | the matching value in the body | -| `Authorization` | when authentication is on | `Bearer ` | - -`Mcp-Param-` is opt-in: send it and it is validated, omit it and nothing happens. The name after the prefix is matched case-insensitively against the arguments object for `tools/call` and against `params` for everything else, descending into nested objects. Values compare as text against strings, numerically against numbers, and as `true`/`false` against booleans. An array or object value never matches. - -### Non-ASCII header values - -HTTP headers are ASCII. A table name with an accent, or a value with a leading space, is wrapped: - -``` -Mcp-Name: =?base64?Y2Fmw6k=?= -``` - -The payload between `=?base64?` and `?=` is standard base64 of the UTF-8 bytes, and TablePro decodes the wrapper before comparing to the body. Wrap only when you have to: a value that is already printable ASCII with no leading or trailing space goes as is. A malformed wrapper, or a header carrying a control character, is `-32020`. - -## Methods - -| Method | Scope | Era | -|--------|-------|-----| -| `server/discover` | none | modern only | -| `tools/list` | `tools:read` | both | -| `tools/call` | `tools:read` plus the tool's own | both | -| `resources/list` | `resources:read` | both | -| `resources/read` | `resources:read` | both | -| `resources/templates/list` | `resources:read` | both | -| `prompts/list` | `resources:read` | both | -| `prompts/get` | `resources:read` | both | -| `completion/complete` | `resources:read` | both | -| `subscriptions/listen` | none | modern only | -| `initialize` | none | legacy only | -| `ping` | none | legacy | -| `logging/setLevel` | none | legacy | - -An unknown method answers HTTP `404` with JSON-RPC `-32601`. A method that exists but is modern-only answers the same way to a legacy client. - -`notifications/cancelled` is the only notification the server acts on. Every other notification is accepted with `202` and dropped. - -### `server/discover` - -Replaces `initialize` for stateless clients. It takes no parameters beyond `_meta` and needs no scope, so it is the first call a client should make. - -```json -{ - "supportedVersions": ["2026-07-28", "2025-11-25", "2025-06-18"], - "capabilities": { - "tools": { "listChanged": false }, - "resources": { "subscribe": true, "listChanged": true }, - "prompts": { "listChanged": false }, - "completions": {}, - "extensions": {} - }, - "instructions": "TablePro is a native macOS database client...", - "resultType": "complete", - "ttlMs": 3600000, - "cacheScope": "public", - "_meta": { - "io.modelcontextprotocol/serverInfo": { - "name": "tablepro", - "title": "TablePro", - "version": "0.67.0", - "websiteUrl": "https://tablepro.app" - } - } -} -``` - -`instructions` is the same text a legacy `initialize` returns. It tells the model to start from `list_connections`, to call `describe_table` before writing SQL, and to treat a refusal as the user's policy rather than a transient error. - -The stdio bridge calls `server/discover` before it forwards anything, and refuses to start if the server does not list `2026-07-28`. - -## Result envelope - -Every modern result carries `resultType`: - -- `"complete"`: the normal case. The payload is the answer. -- `"input_required"`: the server needs the user to approve something. See [Approvals](#approvals-and-input-required). - -Only `tools/call`, `resources/read` and `prompts/get` may return `input_required`. - -Modern results also carry `_meta.io.modelcontextprotocol/serverInfo`. Legacy results carry neither field. - -### Cache hints - -Six methods return `ttlMs` and `cacheScope` so a client can cache without guessing: - -| Method | `ttlMs` | `cacheScope` | -|--------|---------|--------------| -| `server/discover` | 3600000 | `public` | -| `prompts/list` | 3600000 | `public` | -| `resources/templates/list` | 3600000 | `public` | -| `tools/list` | 300000 | `private` | -| `resources/list` | 30000 | `private` | -| `resources/read` | 5000 to 300000 by URI | `private` | - -`public` means the answer does not depend on the token. `private` means it does: `tools/list` is filtered by the token's scopes, and a resource read by its connection allowlist. A `ttlMs` of `0` means do not cache. No other method carries a cache hint, and a hint that reaches a result from a method that may not carry one is stripped before it goes out. - -## Pagination - -`tools/list`, `prompts/list`, `resources/list` and `resources/templates/list` page at 50 items. When more remain, the result carries `nextCursor`; pass it back as `params.cursor` for the next page. - -A cursor is opaque and bound to the method that issued it. Handing a `tools/list` cursor to `prompts/list` is `-32602`. - -## Progress - -Send `_meta.progressToken` on a request and the server streams `notifications/progress` back **on that request's own response stream**. The response switches to `text/event-stream`, progress notifications arrive as SSE frames with no event name, and the final JSON-RPC response is the last frame. There is no separate `GET` stream to open, and no notification ever arrives out of band. - -``` -data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"t1","progress":0.3,"total":1.0,"message":"Executing"}} - -data: {"jsonrpc":"2.0","id":1,"result":{}} -``` - -`progress` must advance; an update that does not move forward is dropped. `execute_query` reports at 0.0, 0.3 and 1.0. - -## Cancellation - -Send `notifications/cancelled` with the `requestId` of a request that is still running: - -```json -{ "jsonrpc": "2.0", "method": "notifications/cancelled", "params": { "requestId": 7, "reason": "user aborted" } } -``` - -The notification is answered with `202` whether or not anything matched. The cancelled request finishes with `-33002`. - -Cancellation is per principal: a token can only cancel its own requests. Revoking a token cancels everything it has in flight. - -The server also cancels for you. Every handler has a 330-second deadline, and the server polls for a dropped connection once a second, so a client that walks away does not leave a query running. [`subscriptions/listen`](/developers/mcp-subscriptions#closing) is the one method that deadline does not reach. - -## Approvals and input required - -A statement that writes needs the user's consent. Where that consent is collected depends on what your client declared in `clientCapabilities`. - -Without `elicitation`, TablePro shows its own confirmation dialog on the user's Mac and the call blocks until they answer. With it, the call returns `resultType: "input_required"` instead: - -```json -{ - "resultType": "input_required", - "inputRequests": { - "approve_statement": { - "method": "elicitation/create", - "params": { - "mode": "form", - "message": "Allow a destructive statement on 'Production'?", - "requestedSchema": { - "type": "object", - "properties": { - "approved": { "type": "boolean", "title": "Approve", "description": "DROP TABLE staging_users" } - }, - "required": ["approved"] - } - } - } - }, - "requestState": "..." -} -``` - -The `description` carries the statement verbatim, line breaks and all, up to 2,000 characters. Render it as a block rather than a one-line label: it is the only thing the person sees before they approve, and a `WHERE` clause that falls off the end changes what they agreed to. - -Present the form, then repeat the **same** call with the answer and the state echoed back: - -```json -{ - "name": "confirm_destructive_operation", - "arguments": { "connection_id": "...", "query": "DROP TABLE staging_users" }, - "requestState": "", - "inputResponses": { - "approve_statement": { "action": "accept", "content": { "approved": true } } - } -} -``` - -`requestState` is signed and bound to the token, the method, and a digest of the parameters. Change any argument between the two calls and it is rejected with `-32602`. It expires after 5 minutes. An `action` other than `accept`, or `approved: false`, fails the call with a `denied` tool error. - -A call that carries `inputResponses` or `requestState` is never cached, whatever the method's normal hint says. - -An accepted form stands in for TablePro's own dialog only on a `fullAccess` token. On a `readOnly` or `readWrite` token, and on the anonymous loopback principal, Safe Mode still puts its dialog on the Mac afterwards, so a write is confirmed twice: once in your interface, once in TablePro's. Ask for `fullAccess` at [pairing](/developers/pairing) if the second prompt is not what you want. - -## Errors - -TablePro's own error codes live in `-33000` and above. The `-32000` block is frozen by the specification and TablePro no longer uses it. The three codes in `-32020` and below are defined by the specification itself. - -| Code | HTTP | Meaning | -|------|------|---------| -| `-32700` | 400 | Parse error. The body is not JSON. | -| `-32600` | 400 | Invalid request. Bad envelope, or a JSON-RPC response sent to the server. | -| `-32601` | 404 | Method not found. | -| `-32602` | 400 | Invalid params. Also unknown tool, unknown prompt, unknown resource URI, bad cursor, bad `requestState`. | -| `-32603` | 500 | Internal error. | -| `-32020` | 400 | Header mismatch. A required header is missing, malformed, or disagrees with the body. | -| `-32021` | 400 | Missing required client capability. `data.requiredCapabilities` names it. | -| `-32022` | 400 | Unsupported protocol version. `data.supported` lists what is accepted. | -| `-33000` | 503 | Server error. Service unavailable. | -| `-33001` | 404 | Session not found. Legacy sessions only. | -| `-33002` | 200 | Request cancelled. | -| `-33003` | 200 | Request timeout. The statement or the handler deadline ran out. | -| `-33005` | 413 | Payload too large. The body cap is 10 MB. | -| `-33006` | 503 | Server disabled. | -| `-33007` | 403 | Forbidden. Token scope, connection allowlist, AI policy, or external access rejected the call. | -| `-33008` | 401 | Token expired. | -| `-33009` | 401 | Unauthenticated. No token, or an unknown or revoked one. | -| `-33010` | 429 | Rate limited. Carries `Retry-After`. | - -Resource-not-found is `-32602`, not a code of its own. An unknown method is HTTP `404`. - -`401` responses carry a `WWW-Authenticate` challenge: `Bearer realm="TablePro"` for a missing token, `Bearer error="invalid_token"` for an unknown or revoked one, and `error_description="token expired"` when it expired. A `403` from a scope check carries `Bearer error="insufficient_scope"` and lists the scopes in `data.requiredScopes`. - -### Tool errors are not protocol errors - -A tool that fails because the database said no returns a **successful** JSON-RPC result with `isError: true`: - -```json -{ - "content": [{ "type": "text", "text": "query_failed: relation \"user\" does not exist" }], - "isError": true, - "resultType": "complete" -} -``` - -The text starts with a machine-readable code: `invalid_argument`, `not_connected`, `not_found`, `denied`, `timeout`, `unsupported`, `query_failed`, `user_cancelled`, or `internal_failure`. Error text is redacted before it leaves the process, so a driver message that echoed a password does not reach the client. - -Protocol errors are reserved for the envelope: a malformed request, a missing scope, an expired token. A client that only handles JSON-RPC errors reads every failed query as a success. - -## Transport limits - -| Limit | Value | -|-------|-------| -| Request body | 10 MB | -| Request headers | 16 KB | -| Concurrent connections | 64 | -| Idle connection timeout | 30 seconds | -| Handler deadline | 330 seconds | - -Per-caller request rates, auth lockouts and pairing lockouts are counted separately and are listed under [rate limits](/developers/tokens#rate-limits). - -## Legacy era - -A request with no `io.modelcontextprotocol/protocolVersion` in `_meta` is treated as legacy, and legacy still works the old way: - -1. `POST /mcp` with `initialize` and a `protocolVersion` of `2025-11-25` or `2025-06-18`. Any other value is `-32022`. -2. The response carries `protocolVersion`, `capabilities`, `serverInfo` and `instructions`. -3. Every later request carries `Mcp-Session-Id`. A session belongs to the token that created it; presenting it under another token is `-33001`. -4. A session lives until the server stops. The store holds at most 16 and evicts the least recently used past that. A session the server does not recognize is `-33001`, and the client has to `initialize` again. - -Legacy clients see `resources.subscribe: false` and cannot call `server/discover` or `subscriptions/listen`. Their results carry no `resultType`, no cache hints, and no `serverInfo` in `_meta`. Legacy is there so an old client keeps working, not as a target to build against. diff --git a/docs/developers/mcp-resources.mdx b/docs/developers/mcp-resources.mdx deleted file mode 100644 index 5aee6b2e1e..0000000000 --- a/docs/developers/mcp-resources.mdx +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: MCP Resources -description: Read-only resources for connections, schema, tables, and query history, with their URI templates ---- - -import MCPGates from "/snippets/mcp-gates.mdx"; - -Eight URIs, all read-only, all JSON. A client reads them to find its way around before it calls a tool. - - - -Reads count against the caller's [request rate](/developers/tokens#rate-limits) like anything else. - -## Discovery - -`resources/list` returns `tablepro://connections`, plus four entries for each connection that is currently connected and visible to this token: its schema, its tables, its databases, and its history. The list is sorted by connection name. - -`resources/templates/list` returns the seven templated URIs, so a client can build one for a connection that is not open yet. Both methods page at 50 entries with `nextCursor`. - -## The URIs - -| URI | Name | Payload | -|-----|------|---------| -| `tablepro://connections` | `connections` | Every saved connection this token may use, with live session state | -| `tablepro://connections/{connection_id}/schema` | `connection_schema` | Tables and their columns for the browsed database | -| `tablepro://connections/{connection_id}/databases` | `connection_databases` | `{ "databases": ["app", "analytics"] }` | -| `tablepro://connections/{connection_id}/schemas{?database}` | `connection_schemas` | `{ "schemas": ["public", "reporting"], "database": "app" }` | -| `tablepro://connections/{connection_id}/tables{?database,schema,row_counts}` | `connection_tables` | Table and view names without columns, as the `list_tables` tool returns them | -| `tablepro://connections/{connection_id}/tables/{table}{?database,schema}` | `table_description` | Columns, indexes, foreign keys and an approximate row count, as `describe_table` returns them | -| `tablepro://connections/{connection_id}/tables/{table}/ddl{?database,schema}` | `table_ddl` | The `CREATE` statement as the engine reports it, as `get_table_ddl` returns it | -| `tablepro://connections/{connection_id}/history{?limit,search,date_filter}` | `connection_history` | Queries recently run against this connection, newest first | - -`{connection_id}` must be a UUID; a name is `-32602`. On the `tables` URI, `row_counts=true` asks for approximate counts and `1` and `yes` also work. `completion/complete` fills every variable in these templates against the live schema, including `table`, `database` and `schema`. See [Completions](/developers/mcp-prompts#completions). - - -These URIs live inside the MCP transport and are not the shell-level [URL scheme deep links](/developers/url-scheme), which happen to share the `tablepro://` prefix. - - -## Reading - -`resources/read` needs `params.uri` and, in the modern era, an `Mcp-Name` header carrying the same URI. Every URI except `tablepro://connections` reads a live session, and TablePro opens the connection first if it is not open yet. A connection that will not open is `-32602`. - -```json -{ - "contents": [ - { - "uri": "tablepro://connections", - "mimeType": "application/json", - "text": "{\"connections\":[…]}" - } - ] -} -``` - -The payload is a JSON string inside `text`. The shapes on the rest of this page are what you get after parsing it. Each read carries a `private` cache hint whose TTL depends on the URI; the numbers are in [Cache hints](/developers/mcp-protocol#cache-hints). - -## `tablepro://connections` - -```json -{ - "connections": [ - { - "id": "9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1", - "name": "Production", - "type": "PostgreSQL", - "host": "db.example.com", - "port": 5432, - "database": "app", - "is_connected": false, - "ai_policy": "askEachTime", - "external_access": "readOnly", - "safe_mode": "silent", - "color": "red", - "group": { - "id": "4b0f7c1e-8a52-4d0c-9a3e-6f2d1c7b5e90", - "name": "Acme", - "path": ["Clients", "Acme"], - "color": "blue" - }, - "tags": [ - { "id": "00000000-0000-0000-0000-000000000003", "name": "production", "color": "red" } - ] - } - ] -} -``` - -`database` is the session's browsed database when connected, the saved default otherwise. `type` uses display casing (`MySQL`, `PostgreSQL`, `SQLite`). Connections with `external_access: blocked` or `ai_policy: never` are omitted, as are connections outside the token's allowlist. - -`color`, `group` and `tags` are the same as in [`list_connections`](/developers/mcp-tools#connections): `color` and `group` are omitted when unset, and `tags` is always an array. - -## `tablepro://connections/{id}/schema` - -```json -{ - "database": "app", - "tables": [ - { - "name": "users", - "type": "TABLE", - "schema": "public", - "columns": [ - { "name": "id", "data_type": "uuid", "is_nullable": false, "is_primary_key": true }, - { "name": "email", "data_type": "text", "is_nullable": false, "is_primary_key": false } - ] - } - ] -} -``` - -Capped at 100 tables. Beyond that the payload also carries `truncated: true` and `total_tables`. For a larger schema use the `list_tables` tool, or `search_schema` to find a table in any schema or a column by name. - -This is the only subscribable resource. See [Subscriptions](/developers/mcp-subscriptions). - -## `tablepro://connections/{id}/history{?limit,search,date_filter}` - -| Parameter | Meaning | -|-----------|---------| -| `limit` | 1 to 500, default 50. Out-of-range values are clamped, not rejected. | -| `search` | Matched against the recorded statement text. | -| `date_filter` | `today`, `thisWeek` (last 7 days), or `thisMonth` (last 30 days). Anything else means no date filter. | - -```json -{ - "history": [ - { - "id": "9b2d3c5a-…", - "query": "SELECT * FROM users WHERE active = true", - "connection_id": "9f1f0c3e-…", - "database_name": "app", - "database_type": "PostgreSQL", - "source": "editor", - "statement_type": "select", - "executed_at": "2026-08-20T10:14:22Z", - "execution_time_ms": 18.4, - "row_count": 142, - "was_successful": true - } - ] -} -``` - -`executed_at` is ISO 8601. `schema_name` appears when the entry has one, and `error_message` when `was_successful` is false, redacted. - -## Errors - -| Code | Meaning | -|------|---------| -| `-32602` | Missing `uri`, wrong scheme, unknown URI, connection id that is not a UUID, empty table name, or a connection that would not open | -| `-32020` | The `Mcp-Name` header does not match `params.uri` | -| `-33007` | The token's allowlist rejects the connection, or its external access is `blocked` | - -Resource-not-found is `-32602` rather than a code of its own; [MCP Protocol](/developers/mcp-protocol#errors) lists the rest. diff --git a/docs/developers/mcp-subscriptions.mdx b/docs/developers/mcp-subscriptions.mdx deleted file mode 100644 index a6c4022a10..0000000000 --- a/docs/developers/mcp-subscriptions.mdx +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: MCP Subscriptions -description: subscriptions/listen, the notification filter TablePro honors, and what it deliberately does not send ---- - -Hold one POST open and TablePro streams every notification you asked for down it until you hang up. `subscriptions/listen` replaces `resources/subscribe` and the old `GET` stream, and it is modern-era only: a legacy client gets `-32601`. - -## Opening a subscription - -```json -{ - "jsonrpc": "2.0", - "id": "sub-1", - "method": "subscriptions/listen", - "params": { - "notifications": { - "resourcesListChanged": true, - "resourceSubscriptions": ["tablepro://connections/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1/schema"] - }, - "_meta": { - "io.modelcontextprotocol/protocolVersion": "2026-07-28", - "io.modelcontextprotocol/clientCapabilities": {} - } - } -} -``` - -`params.notifications` is required and must be an object. Send `Mcp-Method: subscriptions/listen` like any other request. - -The request id is the subscription id, so it must not be null. Every notification on this stream carries it in `_meta.io.modelcontextprotocol/subscriptionId`, which is how a client with several subscriptions open tells them apart. - -## The acknowledgment - -Before anything else, the server answers with what it will actually send: - -```json -{ - "jsonrpc": "2.0", - "method": "notifications/subscriptions/acknowledged", - "params": { - "notifications": { "resourcesListChanged": true, "resourceSubscriptions": ["tablepro://connections/9f1f.../schema"] }, - "_meta": { "io.modelcontextprotocol/subscriptionId": "sub-1" } - } -} -``` - -Compare it against what you asked for. Anything missing will never arrive. - -## What TablePro honors - -| Filter field | Honored | Notes | -|--------------|:--------:|-------| -| `resourcesListChanged` | yes | Fires when the set of connected connections this token can see changes. | -| `resourceSubscriptions` | yes | Only `tablepro://connections/{id}/schema` URIs. | -| `toolsListChanged` | no | Both lists are fixed, and `server/discover` reports `listChanged: false` for each. | -| `promptsListChanged` | no | Same. | - -Every URI in `resourceSubscriptions` is checked twice. It has to parse as a connection schema URI, and its connection has to be inside the token's allowlist. Anything else is dropped silently, so the acknowledgment is the only place you find out. A token without `resources:read` gets an empty filter back, whatever it asked for. - -The URI you get back is canonical, so one that differs only in case or percent-encoding comes back normalized. - -## Reading the stream - -The response switches to `text/event-stream` at the acknowledgment and stays open. Frames carry no event name, so each one is `data: ` plus a JSON line, terminated by a blank line. A line starting with `:` is the keepalive TablePro sends every 15 seconds to stop an intermediary timing the connection out; skip it. - -Tell the two kinds of frame apart by the envelope. A frame with an `id` equal to your request id is the final JSON-RPC response and the stream is over. A frame with a `method` and no `id` is a notification. - -## The notifications - -### `notifications/resources/list_changed` - -Sent when the set of connections visible to this token changes: one connects, one disconnects, one is blocked for external clients, or its AI policy changes to **Never**. - -TablePro compares the visible set against the last one it sent you, so a change that does not move your set produces no notification. A connection you cannot see connecting is not your event. - -```json -{ - "jsonrpc": "2.0", - "method": "notifications/resources/list_changed", - "params": { "_meta": { "io.modelcontextprotocol/subscriptionId": "sub-1" } } -} -``` - -### `notifications/resources/updated` - -Sent when a subscribed connection's schema changes, carrying the URI: - -```json -{ - "jsonrpc": "2.0", - "method": "notifications/resources/updated", - "params": { - "uri": "tablepro://connections/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1/schema", - "_meta": { "io.modelcontextprotocol/subscriptionId": "sub-1" } - } -} -``` - -It fires on a completed schema load, not on the start of one, so a refresh in flight and a schema that has never loaded both produce nothing. Re-read `tablepro://connections/{id}/schema` when it arrives. - -## Closing - -The subscription ends when the client closes the connection, cancels the request with `notifications/cancelled`, or the server stops. The final JSON-RPC response is an empty result carrying the subscription id in `_meta`. - -There is no deadline on `subscriptions/listen`. It is the one method exempt from the 330-second handler timeout and from the per-caller concurrency limit. The server checks every open subscription for a dropped connection every 15 seconds and closes the ones that went away, so a client that dies without closing does not leak. - -## Progress is not a subscription - -Progress notifications for a running request travel on that request's own response stream, not here. See [Progress](/developers/mcp-protocol#progress). Do not open a subscription expecting to receive them. diff --git a/docs/developers/mcp-tools.mdx b/docs/developers/mcp-tools.mdx deleted file mode 100644 index f5a04b4d64..0000000000 --- a/docs/developers/mcp-tools.mdx +++ /dev/null @@ -1,253 +0,0 @@ ---- -title: MCP Tools -description: The 47 tools TablePro's MCP server exposes, with arguments, defaults, result shapes, and scope requirements ---- - -If a dedicated tool covers the job, it beats hand-written SQL. It quotes identifiers for the engine in front of it, applies the user's row limits, and runs on connections where raw execution is refused. All 47 publish a JSON Schema for input and output, so a client can validate both sides without reading this page. For the wire format, headers and error codes see [MCP Protocol](/developers/mcp-protocol). - -## How to read this page - -In the tables below, required arguments come first and optional ones follow in parentheses. Unless a row says otherwise: - -- `connection_id` is a connection UUID from `list_connections`. -- `database` and `schema` default to whatever the connection is currently browsing, except that `search_schema` without `schema` looks in every schema. Passing them targets somewhere else **without moving the app's selection**. -- An unknown argument is rejected with `-32602`. There is no silent ignoring. -- The result comes back twice: as JSON text in `content[0]`, and as a typed object in `structuredContent`. Read `structuredContent`. -- Timestamps are ISO 8601, error text is redacted, and "a result set" means `columns[]`, `rows[][]`, `row_count`, `rows_affected`, `execution_time_ms`, `is_truncated`, plus `status_message`, `database` and `schema` when they apply. - -Row limits default to the server's **Default row limit** setting (500) and are capped by **Maximum row limit** (10,000). Timeouts default to **Query timeout** (30 seconds) and are capped at 300. Both live in **Settings > MCP**. - -## Tool index - -| Group | Tools | -|-------|-------| -| [Connections](#connections) | `list_connections` `connect` `disconnect` `get_connection_status` `switch_database` `switch_schema` | -| [Schema discovery](#schema-discovery) | `list_databases` `list_schemas` `list_tables` `describe_table` `get_table_ddl` `search_schema` `list_indexes` `list_foreign_keys` `list_triggers` `get_view_definition` `list_routines` `list_types` `list_partitions` `get_table_statistics` `get_database_statistics` | -| [Reading data](#reading-data) | `browse_table` `count_rows` `execute_query` `explain_query` `export_data` `quote_identifiers` | -| [Writing data](#writing-data) | `insert_rows` `confirm_destructive_operation` `transaction_control` | -| [Databases and objects](#databases-and-objects) | `describe_create_database_options` `create_database` `drop_database_object` `list_maintenance_operations` `run_maintenance` | -| [Server administration](#server-administration) | `get_server_dashboard` `stop_server_session` `list_principals` `list_grants` `list_session_contexts` | -| [The user's workspace](#the-users-workspace) | `list_favorite_tables` `list_recent_tables` `list_recent_tabs` `focus_query_tab` `open_connection_window` `open_table_tab` `search_query_history` | - -## Scopes and gates - -Fourteen tools need `tools:write`: `connect`, `disconnect`, `switch_database`, `switch_schema`, `insert_rows`, `confirm_destructive_operation`, `transaction_control`, `create_database`, `drop_database_object`, `run_maintenance`, `stop_server_session`, `focus_query_tab`, `open_connection_window`, `open_table_tab`. The other 33 need `tools:read`, `execute_query` among them until the statement writes. - -Past the scope, a call clears three more gates: - -1. **Connection allowlist.** A token limited to named connections gets `-33007` for anything else. -2. **Connection policy.** **External Clients** set to **Blocked** hides the connection: `list_connections` omits it and any tool that names it is refused. An AI policy of **Never** does the same, except that [`purpose: "display"`](#listing-for-display) still lists it. Otherwise the first call that touches a connection asks the user, as often as **Settings > MCP > Approval** says. That question is always a dialog on the user's Mac, never an elicitation: a client that could answer it could also grant itself the connection. It has no deadline, so the client owns the timeout, and a refusal stands for 60 seconds before the question can be put again. -3. **Safe Mode.** A write goes through the connection's Safe Mode, which may ask the user to confirm it or to authenticate. See [Approvals](/developers/mcp-protocol#approvals-and-input-required). - -`confirm_destructive_operation` needs `admin` as well, so it takes a Full Access token, and **Read Only** refuses it outright whatever the token carries. No token skips the user's approval. - -Statements that read or write files, or that run server-side code, are refused on every tool. So is more than one statement in a single call, and a statement a `GO 5` line would run five times, except in a SQL Server script sent to [`execute_query`](#execute_query). - -## Connections - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `list_connections` | `purpose` (optional) | `connections[]` with `id`, `name`, `type`, `host`, `port`, `database`, `is_connected`, `ai_policy`, `external_access`, `safe_mode`, `tags`, plus `color` and `group` when set. `is_connected` is false once a connection stops answering, before it is disconnected | -| `connect` | `connection_id` | `status` (`connected`), `connection_id`, `current_database`, plus `current_schema` and `server_version` when known. Returns once the driver is up | -| `disconnect` | `connection_id` | `status` (`disconnected`), `connection_id` | -| `get_connection_status` | `connection_id` | `status` (`connected`, `connecting`, `disconnected`, `error`), `connection_id`, `current_database`, plus `current_schema`, `server_version`, `connected_at`, `last_active_at` and `error` when they apply | -| `switch_database` | `connection_id`, `database` | `status` (`switched`), `connection_id`, `current_database` | -| `switch_schema` | `connection_id`, `schema` | `status` (`switched`), `connection_id`, `current_schema` | - -`list_connections` omits connections blocked for external clients, connections whose AI policy is **Never**, and connections outside the token's allowlist. Its `database` is the session's browsed database when connected, the saved default otherwise. - -The labels the user gave a connection come with it, so a client can show the list the way the app does: - -| Field | Shape | -|-------|-------| -| `color` | `red`, `orange`, `yellow`, `green`, `blue`, `purple`, `pink` or `gray`. Omitted when the connection has no color | -| `group` | `{ id, name, path[], color }`. `path` holds the group names from the top-level group down, ending with `name`. `color` is omitted when the group has none. The whole field is omitted when the connection is in no group | -| `tags` | `[{ id, name, color }]` in the order the user added them, `color` omitted when the tag has none. Always present, empty when there are no tags | - -Entries are sorted by name, Finder style (`db2` before `db10`), then by `id`. - -### Listing for display - -A launcher that shows the user their connections passes `purpose: "display"`. Leaving `purpose` out, or sending `"agent"`, gives the list described here. - -`purpose: "display"` needs two things, or it fails with `-33007` and `data.requiredScopes` set to `["connections:display"]`: - -- a token issued with the `connections:display` scope, which only [pairing](/developers/pairing#scopes) grants; -- **Allow apps to list connections hidden from AI** turned on under **Settings > MCP > Connection Access**. Turning it off stops every token at once. - -The display list adds connections whose AI policy is **Never**. Blocked connections and connections outside the token's allowlist stay out. Each entry has `id`, `name`, `type`, `host`, `port`, `database`, `is_connected`, `tags`, `color`, `group` and `username`, and no `ai_policy`, `external_access` or `safe_mode`. - -The scope only widens this list. Every other tool still refuses a connection whose AI policy is **Never**, so a launcher opens one with [`tablepro://connect/`](/developers/url-scheme), not over MCP. Keep the display list away from models: an AI feature in the same client calls `list_connections` without `purpose`. - -The two `switch_` tools move what the user sees in TablePro. To run one statement elsewhere, pass `database` or `schema` to that tool instead. - -## Schema discovery - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `list_databases` | `connection_id` | `databases[]`, sorted | -| `list_schemas` | `connection_id` (`database`) | `schemas[]`, `database` | -| `list_tables` | `connection_id` (`database`, `schema`, `include_row_counts`) | `tables[]` (`name`, `type`, `schema`, `comment`, `row_count`), `database`, `schema`, `row_counts_included`, `row_counts_are_approximate` | -| `describe_table` | `connection_id`, `table` (`database`, `schema`) | `table`, `database`, `schema`, `columns[]`, `indexes[]`, `foreign_keys[]`, `ddl`, `approximate_row_count` | -| `get_table_ddl` | `connection_id`, `table` (`database`, `schema`) | `table`, `schema`, `ddl` | -| `search_schema` | `connection_id`, `term` (`limit`, `database`, `schema`) | `term`, `database`, `schema`, `matches[]` (`kind` is `table` or `column`, plus `name`, `schema`, `table`, `object_type`, `data_type`), `is_truncated`, `unlisted_schemas[]`, `column_search`, `columns_schema` | -| `list_indexes` | `connection_id` (`table`, `database`, `schema`) | `database`, `schema`, `tables[]` of `{ table, indexes[] }`. Tables with no index are left out | -| `list_foreign_keys` | `connection_id` (`table`, `database`, `schema`) | `database`, `schema`, `tables[]` of `{ table, foreign_keys[] }` | -| `list_triggers` | `connection_id` (`table`, `database`, `schema`) | `triggers[]` (`name`, `table`, `schema`, `timing`, `event`, `orientation`, `statement`, `definition`, `is_enabled`), sorted by table then name, plus `table` when one was named | -| `get_view_definition` | `connection_id`, `view` (`database`, `schema`) | `view`, `schema`, `definition` | -| `list_routines` | `connection_id` (`kind`, `database`, `schema`) | `routines[]` (`name`, `kind`, `schema`, `qualified_name`, `signature`, `return_type`, `language`) | -| `list_types` | `connection_id` (`kind`, `database`, `schema`) | `types[]` (`name`, `kind`, `schema`, `qualified_name`, `labels`, `fields`, `base_type`, `definition`), sorted by qualified name | -| `list_partitions` | `connection_id`, `table` (`database`, `schema`) | `table`, `partitions[]` with `name` and `type` as in `list_tables`, plus `bound`, `ordinal_position`, `row_count`, `is_separate_relation`, `is_subpartitioned` and `parent_partition` | -| `get_table_statistics` | `connection_id`, `table` (`database`, `schema`) | `table` plus whatever the engine records: `data_size_bytes`, `index_size_bytes`, `total_size_bytes`, `average_row_length`, `row_count`, `comment`, `engine`, `collation`, `created_at`, `updated_at` | -| `get_database_statistics` | `connection_id` (`database`) | `databases[]` (`name`, `table_count`, `size_bytes`, `is_system_database`), sorted by name | - -`describe_table` is the one call to make before writing SQL against an unfamiliar table. A column always carries `name`, `data_type`, `is_nullable` and `is_primary_key`, and picks up `is_generated`, `default_value`, `extra`, `comment` and `allowed_values` where the engine reports them; indexes and foreign keys work the same way. An index always carries `name`, `columns`, `is_unique`, `is_primary`, `is_valid` and `type`. `is_valid` is `false` for a PostgreSQL index whose build did not finish, such as one a failed `CREATE INDEX CONCURRENTLY` left behind: queries skip it and `get_table_ddl` leaves it out. - -`include_row_counts` defaults to `false`. Counts come from engine statistics rather than `COUNT(*)`, and are fetched one table at a time, so `list_tables` skips them when the schema holds more than 200 objects. - -`get_database_statistics` leaves out a field the engine does not report. On PostgreSQL, `table_count` comes back only when `database` names the database the connection is browsing, and `size_bytes` only for a database the role has `CONNECT` on. - -`list_routines` takes `kind` as `procedure` or `function`, omitted for both, and its `signature` is the argument list, not the return type. `list_triggers` takes `table` to scope to one table, omitted for every trigger in the schema. `list_types` takes `kind` as `enum`, `composite`, `domain` or `range`, omitted for all four; `labels` is set for an enum, `fields` for a composite and `base_type` for a domain or range, and only PostgreSQL and PGlite answer with rows. - -### `search_schema` - -Leave out `schema` to ask where something lives. Tables, views and other table-like objects are then searched in every schema of the database except the system ones such as `pg_catalog`, and columns in the current schema alone. Name a schema to search only that one, its columns included. On an engine without schemas, such as MySQL or SQLite, the search covers the whole database. - -Table matches come first, those in the current schema ahead of the rest, then column matches. Every match carries its `schema`, `null` on an engine without schemas. `limit` runs 1 to 500, default 50, and `is_truncated` is set only when more matches existed than it allowed. - -Two fields report what the search could not cover: - -| Field | Meaning | -|-------|---------| -| `unlisted_schemas` | Schemas whose tables could not be read, usually for lack of privileges. A table in one of them is missing from `matches` | -| `column_search` | `searched`, with `columns_schema` naming the schema. `limit_reached` when table matches alone filled `limit`. `failed` when the columns could not be read, leaving table matches only | - -A lost connection, or a database where no schema could be read, fails the call instead of returning part of an answer. - -## Reading data - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `browse_table` | `connection_id`, `table` (`columns`, `filters`, `logic`, `sort`, `limit`, `offset`, `timeout_seconds`, `database`, `schema`) | A result set | -| `count_rows` | `connection_id`, `table` (`exact`, `filters`, `logic`, `database`, `schema`) | `table`, `row_count`, `is_approximate`, `filter_count` | -| `execute_query` | `connection_id`, `query` (`max_rows`, `timeout_seconds`, `database`, `schema`) | A result set, plus `result_sets[]` when a SQL Server script returns more than one | -| `explain_query` | `connection_id`, `query` (`analyze`, `variant`, `timeout_seconds`, `database`, `schema`) | `statement` as actually sent, `execution_time_ms`, `columns[]`, `rows[][]`, and where available `plan_text`, `plan` and `available_variants[]` | -| `export_data` | `connection_id`, `format` (`query` **or** `tables`, `sql_table`, `output_path`, `max_rows`, `database`, `schema`) | `format`, `rows_exported`, `is_truncated`, `path` when a file was written, and `exports[]` (`label`, `row_count`, `is_truncated`, `data`) | -| `quote_identifiers` | `connection_id` and at least one of `identifiers`, `literals` (`database`, `schema`) | `identifiers[]` as `{ input, quoted }` and `literals[]` as `{ input, escaped }`, in the order supplied | - -### `browse_table` - -A filter is `{ column, operator, value, second_value, case_sensitive }`. `column` and `operator` are required, `second_value` is the upper bound for `BETWEEN`, and `case_sensitive` defaults to whatever the operator normally does. `logic` is `and` (default) or `or`. The operator list is TablePro's own filter vocabulary, published in the tool's input schema as an `enum`; read it from `tools/list` rather than hardcoding it. Raw SQL filters are not available over MCP. - -A sort entry is `{ column, direction }`, `direction` being `ascending` (default) or `descending`, and entries apply in the order given. `offset` defaults to 0. - -On an engine that cannot skip rows, such as Cloudflare R2 SQL, a nonzero `offset` is refused as an invalid argument and `limit` is lowered to the engine's maximum. The result echoes the `limit` actually used and sets `is_truncated` when the rows reached it. - -Sorting on a column left out of `columns` appends it to the result. The columns you asked for keep the positions you asked for, and the sort column follows them, because some drivers can only order by a column they select. - -### `count_rows` - -Without filters this returns the engine's fast estimate unless `exact` is set. With filters it always counts for real, because an estimate cannot answer a filtered question. - -### `execute_query` - -One statement, 100 KB at most. `DROP` and `TRUNCATE` are refused here; use `confirm_destructive_operation`. On MongoDB, a statement is a read only when it is one call with literal arguments, as [Safe Mode](/features/safe-mode#what-counts-as-a-read) describes; any other JavaScript is a write and needs `tools:write`. When the request carries a `progressToken`, the tool emits progress at 0.0 (resolving), 0.3 (executing) and 1.0 (done). - -On SQL Server, send the whole script instead. A variable exists only in the batch that declares it, so a `DECLARE` and the `SELECT`s that read it belong in one call. The script is cut into batches at its `GO` lines, as the query editor cuts it, and `GO 5` runs a batch five times. The gates judge the script by its worst statement: one write anywhere needs `tools:write` and Safe Mode, and one `DROP` refuses the call. `DECLARE` counts as a write. - -The top-level fields describe the first result set, with `rows_affected` counting the whole script. When the script returns more than one result set, `result_sets[]` lists every one in order, each with `columns`, `rows`, `row_count` and `is_truncated`. `max_rows` caps each result set on its own. `status_message` says when the script left a transaction open. - -A batch that raises an error fails the call with the server's message and line number. The batches after it do not run, and the error says what stays applied. - -### `explain_query` - -Pass the query with no `EXPLAIN` prefix. Without `variant` the engine's first variant runs, and every result lists them in `available_variants[]`. `analyze: true` picks the variant that runs the statement, so an analyzed write needs `tools:write` and Safe Mode approval, and it is refused when the engine has no such variant or the `variant` you pass only estimates. An engine missing from the [support table](/features/explain-visualization#database-support) is refused. - -### `export_data` - -Formats are `csv`, `json` and `sql`; pass exactly one of `query` or `tables[]`. Without `output_path` the text comes back inline in each export entry. With it, the file lands in the user's Downloads folder and never overwrites, so a name that is taken gets `-1`, `-2` and so on; the path must stay inside Downloads, end in the format's extension, and not name a hidden file. SQL output uses the connection's own quoting and literal rules, so exporting a query needs `sql_table` to name the target table. - -## Writing data - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `insert_rows` | `connection_id`, `table`, `columns`, `rows` (`database`, `schema`) | `table`, `rows_submitted`, `rows_affected` | -| `confirm_destructive_operation` | `connection_id`, `query` (`timeout_seconds`, `database`, `schema`) | A result set | -| `transaction_control` | `connection_id`, `action` (`database`, `schema`) | `status`, the action that completed, and `connection_id` | - -`insert_rows` binds parameters, so values are never spliced into SQL text. Each row is an array with one value per column, and a call carries at most 1,000 rows. - -`confirm_destructive_operation` runs one `DROP`, `TRUNCATE` or `ALTER … DROP`. Anything that is not destructive is rejected; use `execute_query` for those. The user approves it first, through your elicitation prompt if your client supports elicitation, otherwise through TablePro's own dialog on their Mac. - -`transaction_control` takes `action` as `begin`, `commit` or `rollback`. The transaction stays open across calls until it is committed or rolled back, and it runs on the same session as the user's own tabs, so their editor runs and their grid saves land inside it while it is open. Leave nothing open. - -On Redis the three actions are `MULTI`, `EXEC` and `DISCARD`. Every command between `begin` and `commit` answers `QUEUED` and does nothing until `commit` runs them, and `commit` fails naming each command the server then refused. `rollback` drops the block whole. - -## Databases and objects - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `describe_create_database_options` | `connection_id` | `is_supported`, `fields[]` (`key`, `label`, `default_value`, `options[]`) | -| `create_database` | `connection_id`, `name` (`options`) | `status` (`created`), `database` | -| `drop_database_object` | `connection_id`, `kind`, `name` | `status` (`dropped`) and `database` or `schema` | -| `list_maintenance_operations` | `connection_id` | `operations[]` and `is_supported` | -| `run_maintenance` | `connection_id`, `operation` (`table`, `options`, `database`, `schema`) | `operation`, `statements[]`, `results[]`, one result set per statement | - -`create_database` takes `options` as a flat object of string values drawn from `describe_create_database_options`, and asks the user before it runs. `drop_database_object` takes `kind` as `database` or `schema`, deletes everything inside, cannot be undone, and is approved first. - -`run_maintenance` takes an `operation` name from `list_maintenance_operations` (`VACUUM`, `ANALYZE`, `OPTIMIZE` and whatever else the engine supports) and omits `table` for the whole database. TablePro generates the statements and runs them in order, asking for approval before each one. - - -`describe_create_database_options`, `list_maintenance_operations` and `list_session_contexts` answer `is_supported: false` on an engine that has no such concept rather than failing, and `list_partitions` answers with an empty list. Everything else that an engine cannot do comes back as an `unsupported` tool error. - - -## Server administration - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `get_server_dashboard` | `connection_id` (`panels`) | The requested panels: `sessions`, `metrics`, `slow_queries`, plus `errors` for any the server could not answer | -| `stop_server_session` | `connection_id`, `process_id` (`mode`) | The result set the engine produced | -| `list_principals` | `connection_id` | `principals[]` (`name`, `host`, `is_role`, `can_login`, `member_of[]`, `connection_limit`, `comment`, and `attributes[]` of `{ key, label, is_enabled }`), sorted by name | -| `list_grants` | `connection_id`, `principal` (`host`) | `principal`, `grants[]` (`privilege`, `scope`, `is_grantable`). `scope` is a dotted path, `*` for a server-wide grant | -| `list_session_contexts` | `connection_id` | `is_supported`, `contexts[]` (`id`, `label`, `value`, `options[]`) | - -`get_server_dashboard` reads the live panels TablePro shows, and runs on PostgreSQL, MySQL, SQL Server, ClickHouse, DuckDB and SQLite. Name the panels in `panels[]` or omit it for all three. A session carries `id` (the process id `stop_server_session` takes), `user`, `database`, `state`, `duration_seconds`, `query`, `can_kill` and `can_cancel`; a metric carries `id`, `label`, `value`, `unit`; a slow query carries `duration`, `query`, `user`, `database`. A panel the server could not answer is left out, and `errors` names it: one fixed sentence per panel, keyed by panel name. The server's own message is not sent to the client; it goes to the app's log, where the user can read it. When every requested panel fails, the call fails. - -`stop_server_session` takes `mode` as `cancel` (default) to stop the running query or `kill` to end the session. SQL Server, ClickHouse and MySQL before 5.0 take only `kill`: their sessions report `can_cancel` false, and `cancel` fails with `invalid_argument`. The user always approves this one, whatever Safe Mode says. - -`list_session_contexts` reports session-level contexts the engine exposes, such as a Snowflake warehouse or role, with the value the session currently holds. - -## The user's workspace - -These read and drive the TablePro app itself, and none of them bypasses the connection allowlist or a blocked connection. - -| Tool | Arguments | Returns | -|------|-----------|---------| -| `list_favorite_tables` | `connection_id` | `favorites[]` (`name`, `database`, `schema`), sorted | -| `list_recent_tables` | `connection_id` | `recent_tables[]` (`name`, `database`, `schema`, `is_view`, `opened_at` as Unix epoch seconds), newest first | -| `list_recent_tabs` | (`connection_id`, `limit`) | `tabs[]` (`connection_id`, `connection_name`, `tab_id`, `tab_type`, `display_title`, `is_active`, plus `table_name`, `database_name`, `schema_name` and `window_id` when known) | -| `focus_query_tab` | `tab_id` | `status` (`focused`), `tab_id`, `connection_id`, `window_id` | -| `open_connection_window` | `connection_id` | `status` (`opened`), `connection_id`, `tab_id`, `window_id`, `is_connected` | -| `open_table_tab` | `connection_id`, `table_name` (`database_name`, `schema_name`) | `status` (`opened`), `connection_id`, `table_name`, `tab_id`, `window_id` | -| `search_query_history` | `query` (`connection_id`, `limit`, `since`, `until`) | `entries[]` carrying `id`, `query`, the connection and database it ran on, `source`, `statement_type`, `executed_at`, `execution_time_ms`, `row_count`, `was_successful` and `error_message`, newest first | - -Starred tables are a good hint at what matters in a database. `list_recent_tabs` covers every readable connection when `connection_id` is omitted, its `limit` runs 1 to 500, default 20, and `tab_type` is one of `query`, `table`, `createTable`, `erDiagram`, `serverDashboard`, `insights`, `usersRoles`. - -`open_connection_window` and `open_table_tab` connect first if they have to, and return once the window has a tab or fail after 8 seconds. - -`search_query_history` searches the history TablePro keeps on this Mac. Pass `""` to match everything and filter by date alone; `since` and `until` are Unix epoch seconds, and `limit` runs 1 to 500, default 50. Results cover only connections this client may reach, with or without `connection_id`. - -## Annotations - -Every tool ships MCP annotations so a client can decide what to auto-run: - -- `readOnlyHint: true` on the 29 tools that only read. `execute_query` is not one of them. -- `destructiveHint: true` on `confirm_destructive_operation`, `drop_database_object` and `stop_server_session`. -- `idempotentHint: true` where repeating the call is safe. -- `openWorldHint: true` on the 14 that can reach the database server: every write except `switch_database`, `switch_schema` and `focus_query_tab`, plus `execute_query`, `explain_query` and `export_data`. - -## Errors - -A tool that fails returns a successful JSON-RPC result with `isError: true` and a text body starting with a code: `invalid_argument`, `not_connected`, `not_found`, `denied`, `timeout`, `unsupported`, `query_failed`, `user_cancelled`, `internal_failure`. - -Protocol errors, with a JSON-RPC `error` envelope, are reserved for the request itself: a missing scope, an unknown tool, a bad header. The full table is in [MCP Protocol](/developers/mcp-protocol#errors). diff --git a/docs/developers/pairing.mdx b/docs/developers/pairing.mdx deleted file mode 100644 index f9d76d8232..0000000000 --- a/docs/developers/pairing.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: Pairing -description: One-click flow that issues a scoped MCP token to an extension through a PKCE-style code exchange ---- - -Four steps: generate a verifier, open a deep link, catch the one-time code, trade it for a token over -localhost. TablePro releases the token only to the caller that can produce the verifier, so an app -that intercepts the redirect holds a code it cannot spend. - -Nothing about this is Raycast-specific. Any client that can receive a callback, through its own URL -scheme or a loopback HTTP listener, can pair. - -## Sequence - -```mermaid -sequenceDiagram - participant E as Extension - participant T as TablePro app - participant U as User - participant M as MCP server (HTTP) - - E->>E: verifier = randomBytes(32).base64url - E->>E: challenge = base64url(SHA-256(verifier)) - E->>T: open tablepro://integrations/pair
?client=…&challenge=…&redirect=…&response_mode=query&state=… - T->>M: lazy-start server - T->>U: Approval sheet (client, destination, scopes, connections, expiry) - U->>T: Approve - T->>T: mint token, hold pending exchange
{ code, plaintext, challenge }, 5 min - T->>E: open redirect URL
?code=…&state=… - E->>M: POST /v1/integrations/exchange
{ code, code_verifier: verifier } - M->>M: SHA-256(verifier) == challenge ? - M->>E: 200 { token: "tp_…" } - E->>E: store token in the Keychain -``` - -The name in the heading is whatever `client` asked to be called, so read the line under it: it names -the address the code is delivered to. Approve only when that address belongs to the app you started -the pairing from. - - - Approval sheet headed Allow Raycast to access TablePro, with the delivery address, a permissions picker, a connection list and an expiry picker - Approval sheet headed Allow Raycast to access TablePro, with the delivery address, a permissions picker, a connection list and an expiry picker - - -## The whole client, in one file - -```ts -import { randomBytes, createHash } from "node:crypto"; - -const b64url = (b: Buffer) => - b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); - -// Step 1. Keep the verifier and the state in memory until the exchange. Never log the verifier. -const verifier = b64url(randomBytes(32)); -const challenge = b64url(createHash("sha256").update(verifier).digest()); -const state = b64url(randomBytes(16)); - -// Step 2. Open the deep link. The parameters are on the URL scheme page. -const params = new URLSearchParams({ - client: "My Editor on macbook-pro", - challenge, - redirect: "http://127.0.0.1:7391/callback", - response_mode: "query", - state, - scopes: "readWrite", -}); -await openUrl(`tablepro://integrations/pair?${params}`); // however your host opens a URL - -// Step 3. Your callback receives ?code=&state=, or ?error=access_denied when the user says no. -// 23508 is the default port. Make it a setting: if the port is taken, the server binds another one, -// and the Status row in Settings > MCP shows the port in use. -export async function exchange(callback: URL, port = 23508): Promise { - const query = callback.searchParams; - if (query.get("state") !== state) throw new Error("pairing state mismatch"); - const code = query.get("code"); - if (!code) throw new Error(`pairing refused: ${query.get("error")}`); - - const res = await fetch(`http://127.0.0.1:${port}/v1/integrations/exchange`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ code, code_verifier: verifier }), - }); - if (!res.ok) throw new Error(`pairing failed: ${res.status}`); - - const { token, scope } = await res.json(); // scope: what the user granted, see Scopes - return token; // Step 4. Straight into the Keychain, never a plain file. -} -``` - -Three constraints decide whether TablePro accepts the request at all: - -- The **verifier** is 43 to 128 characters from `A-Z a-z 0-9 - . _ ~`. 32 random bytes in base64url - is 43. -- The **challenge** is its base64url SHA-256, so exactly 43 base64url characters. -- The **redirect** is a loopback `http` or `https` URL (`127.0.0.1`, `localhost`, `::1`), or a - private-use scheme an installed app has registered. Anything else is refused with *"The redirect - address is not a local callback, so pairing was refused."* That covers a redirect carrying - credentials, `tablepro://` and the database URL schemes TablePro opens, a scheme a web browser - handles (such as `google-chrome:`), and network schemes whose handler fetches from the host they - name, such as `smb://`, `ssh://` and `webcal://`. - -The exchange endpoint takes no bearer token: the single-use code plus the verifier is the credential. -It and `/mcp` are the only paths the server serves, and both accept `POST` only. - -## Response modes - -`response_mode` decides how the answer reaches `redirect`. Send `query`. Use `context` when your host -passes an extension a single launch parameter named `context`, as Raycast does. - -| `response_mode` | Approve | Deny | -|-----------------|---------|------| -| `query` | `?code=&state=` | `?error=access_denied&error_description=user_denied&state=` | -| `context` | `?context={"code":"","state":""}` | `?context={"error":"access_denied","error_description":"user_denied","state":""}` | -| absent (deprecated) | `?context={"code":""}` for `raycast://`, `?code=` for any other scheme | `error=denied` and `error_description=user_denied`, wrapped the same way | - -The `context` value is JSON with sorted keys, percent-encoded like any other query value. Any other -`response_mode` drops the link: no sheet opens and nothing reaches `redirect`. Query items already on -`redirect` stay as they were, with these appended after them. - -### State - -`state` is optional and opaque: up to 1,024 UTF-8 bytes, returned unchanged on approve and on deny, -in every mode. A longer one drops the link the same way. Send a fresh random value with each request -and drop any callback whose `state` does not match the request you started. - -### Deprecated: no `response_mode` - -A link without `response_mode` still gets the delivery in the last row of the table, and is -deprecated. A later release will read a missing `response_mode` as `query`, so send it explicitly. -[Versioning](/developers/versioning) has the policy. - -## Scopes - -`scopes` holds one permission level (`readOnly`, `readWrite` or `fullAccess`) plus, for a launcher, -`connections:display`, separated by spaces or commas. Unknown entries are skipped. When the list -names more than one level, the sheet starts at the lowest. - -`connections:display` lets the token call [`list_connections`](/developers/mcp-tools#listing-for-display) -with `purpose: "display"`, which adds connections hidden from AI and each connection's user name. Ask -for it only to show the user their connections, never to feed a model. The sheet offers it only while -**Allow apps to list connections hidden from AI** is on under **Settings > MCP**, and its checkbox -starts off. Otherwise the token is issued without it. - -The exchange response names what was granted: - -```json -{ "token": "tp_…", "scope": "connections:display resources:read tools:read" } -``` - -`scope` lists every granted scope, sorted and space separated. Read it instead of assuming the -request went through: the user can lower the permission level or leave the checkbox off. - -## What the user approves - -The sheet names the client and counts down the five minutes the request is good for, dimming -**Approve** when it runs out. Three controls sit under that: **Permission Level** (starting at what -the link asked for, movable in either direction), **Allowed Connections** (all, or a checked subset), -and **Expiration** (never, 1, 7, 30 or 90 days). A link that asks for `connections:display` adds -**List connections hidden from AI**, off until the user ticks it. The link's parameters are a -request, not a grant. - -Approving mints the token, holds the plaintext against a one-time code for 5 minutes, and opens the -redirect. Pairing again mints a second token rather than replacing the first, so an extension that -re-pairs should stop using its old one. Revoke either under -**Settings > MCP > Authentication**. - -## Security properties - -| Property | How | -|----------|-----| -| The token is never in a URL | It travels over localhost HTTP; the deep link carries only a code. | -| Intercepting the redirect is useless | The code cannot be exchanged without the verifier. | -| A code is single-use | Success, a failed verification, or 5 minutes deletes it. | -| The plaintext token is not persisted | Only a salted SHA-256 hash is saved, in the login keychain. | - -## Errors - -A failed verification burns the code, so retrying with a guessed verifier is not an option: start a -new pair request. - -| Code | Meaning | -|------|---------| -| `400` | Malformed body, a missing `code` or `code_verifier`, a field over 1,024 bytes, or a verifier that is not 43 to 128 unreserved characters. | -| `403 Challenge mismatch` | The verifier does not hash to the stored challenge. | -| `404 Pairing code not found` | The code never existed, or was already exchanged. | -| `410 Pairing code expired` | The pending exchange is older than 5 minutes. | -| `429 Too Many Requests` | Five failed exchanges from this address within 5 minutes. Locked out for 15, with `Retry-After` on the response. | - -Every failed exchange lands in the activity log under the `auth` category with outcome `denied`. - -Clicking **Deny** opens the redirect with the deny response for the request's -[mode](#response-modes). diff --git a/docs/developers/review.mdx b/docs/developers/review.mdx deleted file mode 100644 index 1766939425..0000000000 --- a/docs/developers/review.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Review process -description: How a directory pull request becomes a listing, the deadlines on both sides, and how to report a listed integration ---- - -Expect a first reply within 7 days. That is a best effort from the maintainers, not a promise, and -the review holds the entry to the [listing policy](/developers/listing-policy) rather than auditing -your code. - -## From pull request to listing - - - - **Registry Gate** runs the checks on - [Submit to the directory](/developers/submit#what-the-checks-look-at), and the pull request gets - labels for its tier and for owner consent when it needs one. - - - A maintainer checks by hand what CI cannot: that the disclosures are true, the name and icon - follow the brand guidelines, Safe Mode is lowered only for local development, and the - integration works with the current TablePro release. A maintainer who asks for changes labels - the pull request **waiting on author**. - - - After the merge, a maintainer publishes a new registry release. - - - The website looks for a new release once a day and opens a pull request with it, which a - maintainer merges. The entry then appears at - [tablepro.app/integrations](https://tablepro.app/integrations). - - - -## Deadlines - -| When | What happens | -| --- | --- | -| A pull request labeled **waiting on author** gets no reply or push for 14 days | It is marked stale, and closes 7 days later, 21 days in all. Open a new one at any time | -| Every week | A health check updates one issue with broken links, moved or archived repositories, removed licenses and private-file access. It never edits or deletes an entry | -| A listed entry breaks the policy or stops working | A maintainer contacts the author in the integration's own tracker. With no reply after 30 days, the entry is archived | -| An entry listed before the policy | Its [grace period](/developers/listing-policy#grace-periods) applies | - -## Report a listed integration - -| Problem | Where it goes | -| --- | --- | -| A bug in the integration | Its own issue tracker, linked on the entry page. Start there | -| No reply after 7 days, or a policy problem: a broken link, a wrong disclosure, a confusing name or icon, access to private files | **Report a problem with this listing** on the entry page. It opens the [**Report a listed integration**](https://github.com/TableProApp/integrations/issues/new?template=report-integration.yml) form with the entry filled in | -| Malware, credential theft or a vulnerability | The registry's [private security advisories](https://github.com/TableProApp/integrations/security/advisories/new), never a public issue. A confirmed report removes the entry at once | -| A bug in TablePro itself | [TablePro's issues](https://github.com/TableProApp/TablePro/issues) | - -## Appeals - -To contest an archive, a removal or a refused pull request, reply on the issue or pull request that -recorded it, or fix the problem and open a new pull request. diff --git a/docs/developers/submit.mdx b/docs/developers/submit.mdx deleted file mode 100644 index 11fd4e039b..0000000000 --- a/docs/developers/submit.mdx +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: Submit to the directory -description: List an integration on tablepro.app/integrations with one pull request to the registry ---- - -A listing is one folder in [TableProApp/integrations](https://github.com/TableProApp/integrations): -an `integration.json` manifest, a 512x512 `icon.png` and up to four screenshots. If you publish the -integration, open the pull request yourself. If you only use it, open a -[**Suggest an integration**](https://github.com/TableProApp/integrations/issues/new?template=suggest-integration.yml) -issue instead, and the maintainers ask the author before listing it. - -## Before you start - -- You own the repository, belong to the organization that owns it, or are a - [major contributor](/developers/listing-policy#rules). Anyone else needs the owner's consent in a - comment on the pull request. -- The integration calls only the surfaces on [Build an integration](/developers#pick-a-surface), - finds TablePro by bundle ID, and reads none of - [TablePro's private files](/developers/listing-policy#rules). -- The repository has a `LICENSE` file with an SPDX license, or the entry is closed source and shows - a **Closed source** label. -- The product name follows ` for TablePro`, and the icon is your own. The - [brand guidelines](https://tablepro.app/brand#naming) list the name forms. -- It works with the current TablePro release. - -## Add the entry - - - - Fork [TableProApp/integrations](https://github.com/TableProApp/integrations) and create - `integrations//`. The slug is 2 to 40 characters of lowercase words joined by hyphens, - without `tablepro`. It becomes the page address, `tablepro.app/integrations/`, and never - changes after the first merge. - - - Add `integration.json`. Start from the [sample](#sample-manifest), from a listed entry like - yours, or from the - [test fixtures](https://github.com/TableProApp/integrations/tree/main/scripts/tests/fixtures/integrations). - Keep the `$schema` line: an editor that reads JSON Schema completes and checks every field. - - - `icon.png` is a 512x512 PNG of at most 512 KB. SVG is refused. Screenshots are optional: up to - four 1600x1000 PNGs of at most 1 MB each, named `screenshot-1.png` upward without gaps, and each - listed under `screenshots` with alt text. - - - From the root of your fork, with Python 3.10 or later: - - ```bash - python3 -m venv .venv - .venv/bin/pip install --require-hashes -r requirements.txt - .venv/bin/python scripts/validate.py - ``` - - - The `/usr/bin/python3` that Xcode and its command line tools install is 3.9, and the pinned - requirements do not install on it. Install a newer Python, for example with - `brew install python`, and create the environment with that one. - - - The summary line reports `0 errors`. - - - One entry per pull request, titled `feat(): add `. Fill in the checklist in the - template, and link the owner's consent comment if you need one. - - - -## Sample manifest - -A fictional launcher command that lists connections over AppleScript and opens one with a -`tablepro://` link. It passes the validator as written. - -```json integrations/example-launcher/integration.json -{ - "$schema": "../../schema/integration.schema.json", - "schemaVersion": 1, - "slug": "example-launcher", - "name": "Example Launcher", - "summary": "Search your TablePro connections from Example Launcher and open one with Return.", - "description": "An Example Launcher command that lists your saved TablePro connections by name, group and tag. Press Return to open the selected connection in TablePro.\n\nIt reads the connection list through AppleScript, so macOS asks once for Automation permission. It needs Example Launcher 2.0 or later.", - "tier": "community", - "status": { "state": "active" }, - "publisher": { "name": "The Octocat", "github": "octocat", "githubId": 583231 }, - "source": { "type": "github", "repo": "octocat/example-launcher-tablepro", "repoId": 123456789 }, - "license": "MIT", - "install": { - "type": "github-release", - "url": "https://github.com/octocat/example-launcher-tablepro/releases/latest" - }, - "host": { "name": "Example Launcher", "url": "https://example.com", "minVersion": "2.0" }, - "platforms": ["macos"], - "categories": ["launchers"], - "surfaces": ["applescript", "url-scheme"], - "disclosures": { - "reads": ["connections"], - "writes": [], - "lowersSafeMode": false, - "network": "none", - "account": false, - "payment": "free" - }, - "links": { - "homepage": "https://github.com/octocat/example-launcher-tablepro", - "issues": "https://github.com/octocat/example-launcher-tablepro/issues" - }, - "icon": "icon.png", - "screenshots": [ - { "file": "screenshot-1.png", "alt": "Example Launcher listing three TablePro connections for the search prod" } - ], - "keywords": ["launcher", "connections"], - "addedAt": "2026-10-10", - "lastVerifiedAt": "2026-10-10" -} -``` - -## Manifest fields - -[`schema/integration.schema.json`](https://github.com/TableProApp/integrations/blob/main/schema/integration.schema.json) -is the full reference. This table covers what it leaves to you. - -| Field | What to put | -| --- | --- | -| `name` | Up to 32 characters, without TablePro: the directory already says the entry works with it. Official, Verified and Certified are refused | -| `summary` | One sentence of 20 to 120 characters, ending with a period | -| `description` | Plain text of 80 to 1,200 characters, paragraphs separated by one blank line. No HTML, Markdown or emoji | -| `tier` | `community`. Only a maintainer sets `official` or `partner` | -| `status` | `{ "state": "active" }` | -| `publisher` | The name shown as **By** on the page, a GitHub login, and that account's numeric `githubId` | -| `source` | `github` with `repo`, its numeric `repoId` and, inside a monorepo, `path`. Or `closed`, with an optional `note` | -| `license` | The repository's SPDX license. Required for a `github` source, left out for `closed` | -| `install` | Where the install button goes. Use the store the integration ships in: `raycast-store`, `vscode-marketplace`, `alfred-gallery`, `app-store`, or `homebrew` for a formula or cask in Homebrew's own repositories. Otherwise `github-release`, or `ships-with-host` when the other product includes it. `page`, a plain download page, needs a maintainer's approval | -| `host` | The other product: `name`, `url`, `minVersion`, and a `note` for a paid tier it needs | -| `platforms` | The TablePro apps it works with: `macos`, `ios`, `ipados` | -| `minTableProVersion` | Optional. The oldest TablePro release it works with, per platform | -| `categories` | One or two of `ai-clients`, `automation`, `cloud-hosting`, `command-line`, `editors`, `launchers`, `local-development`, `other` | -| `surfaces` | The TablePro interfaces it calls: `url-scheme`, `database-url`, `applescript`, `mcp`, `pairing`, `cli`, `shortcuts` | -| `disclosures` | What it reads, writes and sends, covered in [Disclosures](#disclosures) | -| `links` | `homepage`, plus `issues` or `support`. `docs`, `privacy` and `changelog` are optional | -| `keywords` | Optional. Up to eight lowercase search terms | -| `addedAt`, `lastVerifiedAt` | Today's date, as `YYYY-MM-DD` | - -Both numeric IDs come from the GitHub API: - -```bash -gh api users/ --jq .id -gh api repos// --jq .id -``` - -### Disclosures - -The entry page shows these under **What it uses**. The listing policy treats a wrong one as a -violation. - -| Field | What to put | -| --- | --- | -| `reads` | What it reads from TablePro: `connections`, `schema`, `data`. Empty when it only opens things | -| `writes` | `data`, `schema`, or empty | -| `accessNote` | Optional. What limits the access, such as the token scope picked when pairing | -| `lowersSafeMode` | `true` when it opens connections below the user's Safe Mode default, such as with `safeModeLevel=0`. Then `safeModeNote` is required, and `categories` must include `local-development` | -| `network` | Where the integration itself sends data, apart from TablePro and the user's databases: `none`, `local` for this Mac only, or `internet`, which requires `networkNote` | -| `account` | `true` when it needs an account with a service other than TablePro and the host. Then `links.privacy` is required | -| `payment` | `free`, `optional` or `paid`. The last two require `paymentNote` | - -## What the checks look at - -Every pull request runs **Registry Gate**, the one required check. A failure appears as an -annotation on the line in **Files changed**, and the run summary lists each error. Push a fix to -the same branch. The checks cover: - -- The manifest against the schema, and the text: normalized to NFC, with no emoji and no invisible - characters. -- The name and slug, against reserved words and the other entries. -- The folder: only the manifest, the icon and the listed screenshots, at the right sizes. An icon - that looks like the TablePro app icon fails. -- Every link: `https`, no IP address or URL shortener, and it answers. -- The GitHub IDs: `githubId` belongs to the login, `repoId` is the repository in `repo`, and that - repository is public and not archived. -- The license GitHub detects in the repository, against `license`. In a monorepo, or when GitHub - cannot identify the license, a maintainer checks it by hand. -- Who opened the pull request. Anyone other than the publisher, the repository owner or a public - member of its organization gets a **needs owner consent** label, which a maintainer removes once - the owner consents or your commits show you are a major contributor. -- The install target: the store page, release, formula or cask has to exist. -- The source: CI fetches the repository and searches its text files for the paths of TablePro's - private files. A match fails the check with its file and line. A fetch that fails leaves only a - warning. -- The scope: one entry folder per pull request, and no file outside it. - -Run without flags, the validator skips every check that needs the network: the GitHub IDs, the -license, who opened the pull request, links answering, the install target and the source scan. CI -runs them all. - -## Change or remove an entry - -To update an entry, change the files in its folder and open a pull request. The same checks run. -`slug` and `addedAt` never change. `tier`, `compliance`, `lastVerifiedAt`, `source.repoId` and -`publisher.githubId` change only in a maintainer's pull request, so after a repository rename, -update `source.repo` and keep `repoId`. - -To take an entry down, open a -[**Request removal or archive**](https://github.com/TableProApp/integrations/issues/new?template=request-removal.yml) -issue. A pull request that deletes the folder fails the checks. An archived entry keeps its page -with a notice. A removed one answers 410 Gone, and its slug is never used again. diff --git a/docs/developers/tokens.mdx b/docs/developers/tokens.mdx deleted file mode 100644 index cdb607db53..0000000000 --- a/docs/developers/tokens.mdx +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: Tokens -description: Token model, scopes, connection allowlists, expiry, revocation, and the activity log ---- - -**Read Only** is where the **Generate Token** sheet starts, and it is the right level for a client that only reads. Move to **Read & Write** when an agent has to change data, and to **Full Access** only for `DROP` and `TRUNCATE`, which nothing below it can reach. The plaintext is shown once at creation and never again; what persists is a salted SHA-256 hash in the login keychain. - - - MCP tokens - MCP tokens - - -## Permission levels - -| Level | Scopes | What it can do | -|-------|--------|----------------| -| Anonymous loopback | `tools:read`, `resources:read` | Read schema and run `SELECT`. | -| **Read Only** | `tools:read`, `resources:read` | The same, under an issued token. | -| **Read & Write** | plus `tools:write` | `INSERT`, `UPDATE`, `DELETE`, connect and disconnect, switch database or schema, open and focus tabs. | -| **Full Access** | plus `admin` | Also `confirm_destructive_operation`, the only tool that runs `DROP` or `TRUNCATE`. | - -Anonymous loopback is the floor and cannot be raised: the scope check refuses `tools:write` and `admin` to a caller with no issued token, whatever it asks for. - -`connections:display` sits outside the levels. Only [pairing](/developers/pairing#scopes) grants it, and only while **Allow apps to list connections hidden from AI** is on under **Settings > MCP > Connection Access**. The **Generate Token** sheet never does, and an anonymous caller never holds it. It does one thing: lets [`list_connections`](/developers/mcp-tools#listing-for-display) take `purpose: "display"`. A token holding it reads **Lists connections hidden from AI** in the token list. - -`confirm_destructive_operation` also needs the connection's external access to be **Read & Write**, and no permission level skips the user's approval. [Scopes and gates](/developers/mcp-tools#scopes-and-gates) names which tools sit on which scope. - -## Require authentication - -**Settings > MCP > Authentication > Require authentication** is on by default. Turning it on for the first time with no tokens yet mints a **Read & Write** token named "Default token" and reveals the plaintext once. - -With the toggle off, a request from your own machine with no `Authorization` header is accepted as the anonymous loopback caller in the table above. Presenting an `Authorization` header still goes through normal token validation, so an invalid token is rejected either way. The server is loopback only, with no remote mode. - -## Connection allowlist - -A token grants access to all connections or to a named set. A call that targets a connection outside the set is refused with `-33007` before any other check runs, and aggregate reads such as `search_query_history` and `list_recent_tabs` filter their results to the allowlist whether or not you passed a connection id. - -## Combining with external access - -The effective permission is the lower of the token's scope and the connection's **External Clients** level. - -| Token | Connection access | Effective | -|-------|------------------|-----------| -| **Read Only** | **Read & Write** | read only | -| **Read & Write** | **Read Only** | read only | -| **Full Access** | **Read Only** | read only, and `confirm_destructive_operation` is refused | -| **Full Access** | **Read & Write** | read and write | -| any | **Blocked** | denied, and the connection is not even listed | - -## Creating a token - -Pairing is the usual route: see [Pairing](/developers/pairing). To mint one by hand, open **Settings > MCP > Authentication**, click **Generate Token**, and pick a name, permission level, connection allowlist and expiry. The plaintext is shown once. - -## Expiry - -Optional, and worth setting for anything with write access. The sheet offers 30, 60 and 90 days, a custom date, or **Never**. An expired token stops authenticating: requests get `-33008` with HTTP `401` and `WWW-Authenticate: Bearer error="invalid_token", error_description="token expired"`. - -## Rotating a token - -Mint the replacement before you retire the old one; two tokens for one client are fine, and pairing the same client twice does exactly this. Point the client at the new token, confirm it works, then revoke the old one. Revoking first cuts the client off mid-call, because revocation takes effect immediately. - -## Revocation - -The token list in **Settings > MCP > Authentication** shows prefix, name, permission level, allowlist, last use, and expiry. Each row offers: - -- **Revoke**: marks the token inactive. It stays in the list and cannot be reactivated. -- **Delete…**: removes the row entirely. - -Revoking cancels every request that token has in flight and clears the connection approvals the user had granted it, so a re-issued token has to ask again. The next call gets `-33009` with HTTP `401`. - -**Disconnect** in the Connected Clients list revokes that client's token. It is not a soft disconnect: the client has to pair again or be given a new token. - -## The stdio bridge token - -Every server start mints a token named `__stdio_bridge__` and writes it into the handshake file for the bundled bridge. It is **Read & Write**, so an agent going through the bridge can read and write but cannot call `confirm_destructive_operation`. - -It expires after an hour and is rotated 15 minutes before that, deleted when the server stops, and any stale copy is removed the next time the store loads. It is hidden from the Settings token list, though its name can appear in the activity log. For a narrower scope, skip the bridge and connect over [HTTP with a token you mint](/integrations/mcp-clients#http-transport). - -## Stored fields - -| Field | Purpose | -|-------|---------| -| `name` | Label in the token list. Pairing uses the client name. | -| `prefix` | First 8 characters of the plaintext, so a token can be identified without revealing it. | -| `tokenHash`, `salt` | SHA-256 of salt plus plaintext. The plaintext is never persisted. | -| `permissions` | `readOnly`, `readWrite`, or `fullAccess`. | -| `connectionAccess` | All connections, or a named set of connection UUIDs. | -| `expiresAt` | Optional. Absent means never. | -| `isActive` | `false` after revocation. | -| `createdAt`, `lastUsedAt` | Shown in the token list. | - -The plaintext is `tp_` followed by 32 base64url-encoded random bytes. Tokens live in the login keychain under the `com.TablePro` service; a pre-existing `mcp-tokens.json` is migrated in on first load and deleted. - -## Activity log - -Every authentication, tool call, resource read, and query is recorded in `~/Library/Application Support/TablePro/mcp-audit.db` with the token id, never the plaintext. Open it with **Settings > MCP > View Activity…**. - -| Field | Values | -|-------|--------| -| Timestamp | when the call happened | -| Token | the token behind the call, by name and id | -| Category | `auth`, `access`, `admin`, `query`, `tool`, `resource` | -| Action | `auth.success`, `auth.anonymousLoopback`, `auth.failure`, `auth.rateLimited`, `pairing.exchange`, `pairing.redirectRejected`, `token.created`, `token.revoked`, `server.started`, `server.stopped`, `query.executed`, `tool.`, `resource.read` | -| Connection | the connection the call targeted, or `-` | -| Outcome | `success`, `denied`, `error`, `rateLimited` | - -Statement text is not stored. A `query.executed` row carries `sqlDigest`, the SHA-256 of the statement, alongside its duration and row count, which is enough to match a row against a statement you already hold. Error text that is stored is redacted and truncated first. - -Entries are chained: each row's hash covers the previous one, so a row cannot be removed or edited without breaking the chain. Entries older than 90 days are pruned when the store opens. - -## Rate limits - -| Limit | Value | -|-------|-------| -| Requests | 240 per 60 seconds per caller | -| Concurrent requests | 8 per caller | -| Failed auth attempts | 5 per 60 seconds | -| Auth lockout | 5 minutes | -| Failed pairing exchanges | 5 per 5 minutes | -| Pairing lockout | 15 minutes | - -Request limits are keyed by token id, or by client address for an anonymous caller, and `subscriptions/listen` is exempt. Failed auth attempts are counted per client address, and every client on the Mac shares one, so a client that keeps sending a bad token locks the others out too. A malformed `Authorization` header, a scheme other than `Bearer`, and an unknown, expired or revoked token each count. A request with no `Authorization` header gets the `401` challenge and does not count. A successful auth clears the count. Anything over a limit returns `-33010` with HTTP `429` and a `Retry-After` header. Transport limits such as the body cap are on [MCP Protocol](/developers/mcp-protocol#transport-limits). - -## What a token cannot do - -| Capability | Reachable | -|-----------|-----------| -| Read connection passwords | no | -| Read SSH keys | no | -| Read license data | no | -| Read or change app settings | no | -| Read files outside TablePro's own support directory | no | -| Change Safe Mode rules | no | -| Create, change, or read other tokens | no | -| Create or edit connection records | no | - -Statements that reach the filesystem or run server-side code are refused before they reach the driver, on every tool. The reachable surface is the [tool catalog](/developers/mcp-tools), the [resources](/developers/mcp-resources), and the [prompts](/developers/mcp-prompts). Nothing outside those lists is exposed. diff --git a/docs/developers/url-scheme.mdx b/docs/developers/url-scheme.mdx deleted file mode 100644 index 2ae7e4a219..0000000000 --- a/docs/developers/url-scheme.mdx +++ /dev/null @@ -1,209 +0,0 @@ ---- -title: URL Scheme -description: Every tablepro:// deep link action with parameters and examples ---- - -macOS hands the URL to TablePro, which parses it, confirms anything that could do damage, and opens -something on screen. Nothing comes back over the scheme: to read rows, use -[MCP](/developers/mcp-tools). - -Call it from a shell with `open`, from another app with `NSWorkspace.shared.open(url:)`, or from a -Raycast extension with `open()` from `@raycast/api`. - -| Path after `tablepro://` | Opens | -|---|---| -| [`connect/`](#open-a-connection) | The saved connection, or fronts its window | -| [`connect//table/…`](#open-a-table) | A table tab, optionally after switching database or schema | -| [`connect//query?sql=…`](#run-a-query) | A query tab with the SQL prefilled, after a confirmation | -| [`import?…`](#import-a-connection) | A review sheet that saves a new connection | -| [`integrations/pair?…`](#start-pairing) | The pairing approval sheet | -| [`integrations/start-mcp`](#lazy-start-the-mcp-server) | Nothing. Starts the MCP server and returns | -| [`settings/`](#open-a-settings-pane) | Settings on that pane, or on the last-used pane | - -## Connection IDs are UUIDs - -Connection paths take the connection's UUID, not its display name. - -```text -tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1 -``` - -No UI action copies a connect link. To find a UUID, call the MCP -[`list_connections`](/developers/mcp-tools) tool or read the -[`tablepro://connections`](/developers/mcp-resources) resource. The welcome window's -**Copy TablePro Link** context menu item copies a `tablepro://import?…` link for sharing a connection -definition, which is a different thing. - -## Open a connection - -```text -tablepro://connect/ -``` - -Opens the saved connection, or brings its window to front when it is already open. A UUID that -matches no saved connection shows an error sheet. - -A connection with a pre-connect script shows the script text in a confirmation dialog first, and -cancelling that dialog cancels the whole link. This applies to every `connect/…` form. - -```bash -open "tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1" -``` - -## Open a table - -```text -tablepro://connect//table/ -tablepro://connect//database//table/ -tablepro://connect//database//schema//table/ -``` - -The first form uses the connection's current database and schema. The second switches database first. -The third switches both, database before schema, which is the Postgres shape. - -On an engine with schemas and no database to switch between, such as Oracle, a lone `database` -segment names the schema. A segment naming a dimension the engine does not have is dropped rather -than applied to the other one. - -Percent-encode table and schema names that contain spaces or reserved characters. - -```bash -# The current database -open "tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1/table/users" - -# A named database -open "tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1/database/analytics/table/events" - -# Database and schema -open "tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1/database/app/schema/reporting/table/daily_events" -``` - -## Run a query - -```text -tablepro://connect//query?sql= -``` - -A dialog titled **Open Query from Link** appears first, previewing the first 300 characters of the -SQL; **Open Query** opens the tab. Nothing executes, the user runs it from the editor. A tab already -holding exactly this SQL comes to front instead of a second one opening. - -`sql` is required and caps at 51,200 UTF-16 units. Longer than that and the link is dropped. - -```bash -open "tablepro://connect/9f1f0c3e-2e3d-4b14-9c3a-1d2f4ad1f6f1/query?sql=SELECT%20*%20FROM%20users%20LIMIT%2010" -``` - -To run SQL from a script and read rows back, use the MCP -[`execute_query`](/developers/mcp-tools) tool. This link hands SQL to the GUI and stops there. - -## Start pairing - -```text -tablepro://integrations/pair?client=&challenge=&redirect=&response_mode=&state=&scopes=&connection-ids= -``` - -Opens the approval sheet. The user picks scope, connections and expiry, and TablePro returns a -one-time code to `redirect`. - -| Parameter | Required | Description | -|-----------|----------|-------------| -| `client` | yes | Display name shown in the approval sheet, such as `Raycast on macbook-pro`. | -| `challenge` | yes | Base64url SHA-256 of the verifier: exactly 43 characters (PKCE). | -| `redirect` | yes | Where the code is delivered. A loopback `http(s)` URL or a private-use scheme registered by an installed app; anything else is refused, including `tablepro://` and any scheme a web browser handles. | -| `response_mode` | no | `query` for flat `code` and `state` parameters, `context` for one JSON `context` parameter. Any other value refuses the link. Leaving it out is deprecated: see [Response modes](/developers/pairing#response-modes). | -| `state` | no | Opaque value of up to 1,024 UTF-8 bytes, returned unchanged with the code or the error. A longer one refuses the link. | -| `scopes` | no | A permission level, `readWrite` or `fullAccess`, plus `connections:display` for a launcher, separated by spaces or commas. Matching is case-insensitive and takes `read_write`, `read-write`, `full_access`, `full-access` and `full`. Unknown entries are skipped; no level requests `readOnly`, and several start at the lowest. See [Scopes](/developers/pairing#scopes). | -| `connection-ids` | no | Comma-separated UUIDs to preselect in the allowlist. Only omitting it means every connection: an empty value, or one entry that is not a UUID, refuses the link. | - -`scopes` and `connection-ids` are a request, not a grant: the sheet lets the user raise or lower -both. - -```bash -# The challenge for the RFC 7636 sample verifier, and a loopback listener on port 7391 -open "tablepro://integrations/pair?client=My%20Launcher&challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&redirect=http%3A%2F%2F127.0.0.1%3A7391%2Fcallback&response_mode=query&state=af0ifjsldkj&scopes=readWrite" -``` - -[Pairing](/developers/pairing) has the exchange step and the error codes. - -## Lazy-start the MCP server - -```text -tablepro://integrations/start-mcp -``` - -Starts the MCP server if it is not running, then returns. MCP does not have to be enabled in Settings -first. The bundled `tablepro-mcp` CLI uses this to bootstrap on a cold launch. - -The server takes the configured port (`23508` by default) and falls back to a kernel-assigned free -port when that one is busy. The **Status** row in **Settings > MCP** shows the port it bound. - -A launch whose only reason is this URL stays in the background: no Dock icon, no app switcher entry, -until it has a window to show. - -```bash -open "tablepro://integrations/start-mcp" -``` - -## Import a connection - -```text -tablepro://import?name=&host=&type=&… -``` - -Shows a review sheet with the parsed connection, then saves it on **Add Connection**. `name`, `host` -and `type` are required; the rest of the connection form maps to query parameters listed in -[Connection Import](/developers/connection-import). Passwords are never accepted in a link. - -```bash -open "tablepro://import?name=Staging&host=db.example.com&port=5432&type=postgresql&username=admin&database=mydb" -``` - -## Open a settings pane - -```text -tablepro://settings -tablepro://settings/ -``` - -The bare link opens Settings on the last-used pane, the same as `Cmd+,`. With a pane id it opens that -pane. The link only navigates and never changes a setting. - -| Pane | id | -|---|---| -| General | `general` | -| Appearance | `appearance` | -| Editor | `editor` | -| Data | `data` | -| Keyboard | `keyboard` | -| Profiles | `profiles` | -| Notifications | `notifications` | -| AI | `ai` | -| MCP | `mcp` | -| Plugins | `plugins` | -| Sync | `sync` | -| License | `license` | - -Ids are lowercase and matched exactly, and they stay the same when a pane is renamed. An unknown id -opens the last-used pane with no error, so a link to a pane added in a later version still opens -Settings on an older one. Path segments and query items after the id are ignored. - -```bash -open "tablepro://settings/mcp" -``` - -## On iPhone and iPad - -The iOS app answers two of these paths: `connect/` opens the connection, and -`connect//table/` opens it on that table. Any other path under a connection, such as -`database/…` or `query`, opens the connection alone. `import`, `integrations` and `settings` links do -nothing. - -## Errors - -A URL that fails to parse is dropped with no UI at all: an invalid UUID, an unknown path, a missing -required parameter, over-limit SQL. The reason goes to the system log under subsystem `com.TablePro`, -so check Console.app when a link does nothing. - -A URL that parses and then fails shows an error sheet. A valid UUID with no matching connection gives -`No saved connection with ID "…".` under the title **Connection Failed**. diff --git a/docs/developers/versioning.mdx b/docs/developers/versioning.mdx deleted file mode 100644 index 05250624b1..0000000000 --- a/docs/developers/versioning.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Versioning -description: What the stability contract covers on each surface, which MCP protocol versions the server accepts, and how a break is announced ---- - -import StabilityMatrix from "/snippets/stability-matrix.mdx"; - -Ask the server which protocol versions it takes, never this page: `server/discover` returns them in -`supportedVersions`, and an unsupported one is rejected with `-32022` whose `data.supported` carries -the same list. A client that hardcodes the list breaks on the release that adds one. - -The other number, TablePro's own, follows semver and governs every surface in -[the contract](#what-the-contract-covers). - -## What the contract covers - - - -## Which era gets new work - -The newest protocol version is the only one that gets new features. It is stateless, it carries two -methods no older version can reach, and it is what a new client should be built against. - -Older versions keep working through `initialize` and `Mcp-Session-Id` so a shipped client does not -break on an app update, and they see a reduced feature set. One stays accepted for as long as the -[specification](https://modelcontextprotocol.io) lists it as current or recent. Dropping one is a -`Removed` entry in the CHANGELOG, never a silent change. [MCP Protocol](/developers/mcp-protocol) -has the version list and the per-era differences. - -For stdio clients this is somebody else's problem: the bundled `tablepro-mcp` bridge answers the -client's `initialize` locally and speaks the newest version upstream. See -[MCP Clients](/integrations/mcp-clients). - -## Stability rules - -Within a major TablePro version every covered surface is additive only. Per surface, that means: - -### Links and database URLs - -- A `tablepro://` path or parameter is never removed and never changes meaning. -- A [settings pane id](/developers/url-scheme#open-a-settings-pane) is never removed and keeps - opening the same pane, whatever the pane is called. -- On iPhone and iPad, `connect/` and `connect//table/` keep working. Any other - path under a connection opens the connection alone. -- A scheme on [Connection URLs](/connections/urls) keeps opening the same database type, and a - documented query parameter keeps its meaning. -- A database URL's parameter names match in any case. An unknown one is ignored, except on MongoDB, - where it is passed to the driver. `tablepro://` parameter names are case-sensitive. - -### AppleScript - -- A class, property, command, parameter or enumerator is never removed or renamed, and its - four-character code never changes. -- A record a command returns keeps its fields. New fields can appear. - -### MCP and pairing - -- New tools, resources, prompts and error codes can appear. -- A new tool input field can appear when it is optional and has a default. -- A new tool output field can appear. -- A new pairing parameter can appear when it is optional. -- A token scope keeps its name and what it grants. `connections:display` only ever widens what - `list_connections` returns for `purpose: "display"`, never what any tool can run. - -And within a major version, none of these happen: - -- A tool or prompt is removed or renamed. -- A required input field is added to an existing tool. -- An output field is removed or changes type. -- A resource is removed. -- `/mcp` or `/v1/integrations/exchange` moves. -- A pairing parameter is removed, or the redirect changes how it delivers the code. -- A scope is removed or renamed, or the exchange response drops `scope`. -- The default port changes from `23508`. - -Error codes are the one place this already moved. TablePro's own codes left the `-32000` block, which -the specification froze, for `-33000` and above; a client that matched on the old numbers needs -updating once. The current table is in [MCP Protocol](/developers/mcp-protocol#errors). - -The default port is not the port on every Mac. The user can set another under -**Settings > MCP**, and when `23508` is taken at start the server binds a free port instead. -Treat the port as a setting that defaults to `23508`; the pane's **Status** row shows the one in use. - -### The tablepro command and the app's identity - -- `tablepro`, once installed from **Settings > General**, stays at `/usr/local/bin/tablepro` and - hands each argument to the app the way `open -b com.TablePro` does: database URLs, `tablepro://` - links, `.sql` files and database files. With no argument it opens the app. -- The bundle ID stays `com.TablePro`. -- The MCP bridge stays at `Contents/MacOS/tablepro-mcp` inside the app bundle. -- Where the bundle sits is up to the user. [Find the app](/developers#find-the-app) by bundle ID. - -## Breaking changes before 1.0 - -The app is on 0.x, so the minor version is the release boundary. A break can land between 0.67 and -0.68, but never silently. Once 1.0 ships, breaks move to major bumps. - -Anything deprecated is deprecated in the docs first, with the replacement named and a CHANGELOG -entry to match. It then keeps working for at least one more minor version. A later release removes -it, marked `BREAKING` in the CHANGELOG. - -Deprecated now: - -- Pairing links without `response_mode`. Send `response_mode=query`, or `response_mode=context` for a - host that takes one `context` launch parameter. See [Response modes](/developers/pairing#response-modes). - -## What is not under the contract - -These can change in any release, without notice: - -- Transport framing, internal message routing, and any HTTP path other than `/mcp` and - `/v1/integrations/exchange`. -- The handshake file at `~/Library/Application Support/TablePro/mcp-handshake.json`. Use the bundled - `tablepro-mcp` bridge, which finds the server on its own, or connect over HTTP to the port on the - **Status** row in **Settings > MCP**, `23508` by default. -- The connections file at `~/Library/Application Support/TablePro/connections.json`. List - connections with `list_connections` or AppleScript instead. -- The preferences in `~/Library/Preferences/com.TablePro.plist`, including the connection groups - and tags kept there. -- Keychain items the app writes, such as connection passwords and token hashes. -- The app's SQLite databases. Read query history with `search_query_history`, and the audit log at - `mcp-audit.db` in the **MCP Activity** window. -- Token storage. Tokens are managed in **Settings > MCP**. -- Where the app is installed. - -The [directory](/developers/listing-policy#rules) refuses an integration that reads the files, -preferences, Keychain items or databases on this list. - -## Reporting a contradiction - -File an issue at [github.com/TableProApp/TablePro](https://github.com/TableProApp/TablePro/issues) -with the TablePro version, the call, expected and actual behavior, and the matching activity log row -if there is one. diff --git a/docs/development/architecture.mdx b/docs/development/architecture.mdx deleted file mode 100644 index daf6b2c80d..0000000000 --- a/docs/development/architecture.mdx +++ /dev/null @@ -1,121 +0,0 @@ ---- -title: Architecture -description: How the app, plugin system, and core services fit together ---- - -import DriverCounts from "/snippets/driver-counts.mdx"; - -Every database driver is a bundle the app loads at runtime. That decision shapes the rest: nothing switches on database type, no driver list is compiled in, and `DatabaseType` is a string-based struct rather than an enum, so a type from a plugin the app has never heard of still round-trips through Codable. - -The UI is SwiftUI with AppKit underneath it for windows, menus, and the data grid. Async work is Swift concurrency throughout. Database connectivity is native C libraries, linked as static `.a` files out of `Libs/`. - -[`CLAUDE.md`](https://github.com/TableProApp/TablePro/blob/main/CLAUDE.md) in the repository root is the authority on the rules a change has to satisfy: the invariants that have caused real bugs, the ABI policy, and what has to happen before a commit lands. This page is the map of where things live, not the rulebook. - -## Plugin system - -`PluginManager` (`Core/Plugins/`) discovers bundles, version-checks them, and loads them. Four pieces carry the whole system: - -| Component | Location | Role | -|-----------|----------|------| -| TableProPluginKit | `Plugins/TableProPluginKit/` | Shared framework: the `TableProPlugin`, `DriverPlugin`, and `PluginDatabaseDriver` protocols plus the transfer types | -| PluginManager | `Core/Plugins/PluginManager.swift` | Discovers, validates, and loads plugin bundles | -| PluginDriverAdapter | `Core/Plugins/PluginDriverAdapter.swift` | Bridges `PluginDatabaseDriver` to the app's internal `DatabaseDriver` | -| DatabaseDriverFactory | `Core/Database/DatabaseDriver.swift` | Resolves a `DatabaseType` to a loaded plugin | - -`Packages/TableProCore/Sources/TableProPluginKit` is a symlink to the same files, so edit only the copy under `Plugins/TableProPluginKit/`. - - - -A driver is one of three roles a bundle can take. `ExportFormatPlugin` and `ImportFormatPlugin` are the others, which is how the CSV, JSON, SQL, XLSX, and MQL formats ship. `DocumentInspectorPlugin` is still declared in the kit, and the app no longer loads a bundle that provides it. - -### Opt-in protocols - -A driver adopts these on top of `PluginDatabaseDriver` to reach extra surfaces. The app finds them with a runtime cast, so skipping one costs nothing and no ABI bump is involved. - -| Protocol | What adopting it gets you | -|----------|---------------------------| -| `PluginDiagnosticProvider` | A driver-written explanation in front of a raw connection error | -| `PluginProcedureFunctionSupport` | Stored procedures and functions in the structure tab | -| `PluginDefaultSortProvider` | Per-table control of default sorting: `.useAppDefault`, `.suppress`, or `.forceColumns([String])` | -| `PluginPrincipalManagement` | Users, roles, and grants in the Users & Roles tab | -| `PluginBrowseFilterProvider` | Control of the sidebar object filter: glob syntax, case sensitivity, type scopes | -| `SettablePlugin` | A settings pane of your own under **Settings > Plugins** | - -### PluginKit ABI - -TableProPluginKit builds with `BUILD_LIBRARY_FOR_DISTRIBUTION = YES`, so its public ABI is resilient: the Swift runtime fills any requirement an already-built plugin never implemented from the protocol's default, and that plugin keeps loading under a newer app. Adding a requirement with a default is free. Removing one, or adding a parameter to an existing public initializer, breaks every shipped binary. - -Run `scripts/check-pluginkit-abi.sh` before merging anything under `Plugins/TableProPluginKit/`. [Plugin Development](/development/plugin-development#abi-compatibility) has the additive-versus-breaking rules and what a breaking change obliges you to do. - -## Key services - -`DatabaseManager` (`Core/Database/DatabaseManager.swift`) is the connection pool and the interface every view and coordinator goes through for connect, disconnect, reconnect, and session tracking. `ConnectionHealthMonitor` pings each live connection every 30 seconds and reconnects with exponential backoff. - -`MainContentCoordinator` is the central coordinator, one per connection session rather than per window, built by `SessionStateFactory` and split across extension files in `Views/Main/Extensions/` (`+Alerts`, `+Filtering`, `+Pagination`, and the rest). New coordinator behavior goes in a new extension file rather than the main one. - -Autocomplete is three pieces. `CompletionEngine` ranks the suggestions and knows nothing about the editor framework; `QueryCompletionAdapter` bridges it to CodeEdit. `SQLContextAnalyzer` reads what the cursor sits in: a table reference, a column reference, a keyword. `SQLSchemaProvider` is an actor that caches schema and hands concurrent callers the same in-flight `Task` rather than firing a second `fetchTables()`. - -## Change tracking - - - - `DataChangeManager` records the cell edit as a pending change. Nothing has reached the server. - - - `SQLStatementGenerator` turns the pending set into INSERT, UPDATE, and DELETE statements. - - - Grid edits register on the window's `UndoManager`, which `TabWindowController.windowWillReturnUndoManager` resolves to the selected `ConnectionWorkspace.undoManager`. One window hosts several connections, so the undo stack is per connection, not per window. Structure edits use a private `UndoManager` inside `StructureChangeManager`. - - - -`AnyChangeManager` wraps whichever concrete manager is active behind the `ChangeManaging` protocol, so callers do not care which one they have. - -## MCP server - -`Core/MCP/` holds a Model Context Protocol server bound to loopback, speaking plain HTTP through `NWListener`. Nothing in it is reachable off the machine: no TLS, no remote mode. `MCPProtocolDispatcher` is an actor that resolves the protocol era from the request, checks scopes, runs each handler in its own task under a 330-second deadline, and normalizes the result envelope. `MCPServerManager` owns the lifecycle on the main actor: port binding, the bridge credential, the handshake file. - -The wire contract, the tool catalog, and the version policy are documented for consumers: [MCP Protocol](/developers/mcp-protocol), [MCP Tools](/developers/mcp-tools), [Versioning](/developers/versioning). - -## Connecting - -```mermaid -flowchart TD - UI["ConnectionFormView"] --> DM["DatabaseManager"] - DM --> SSH["SSHTunnelManager (if SSH)"] - DM --> Factory["DatabaseDriverFactory"] - Factory --> PM["PluginManager"] - PM --> Adapter["PluginDriverAdapter"] - Adapter --> Connected["Connection ready"] - SSH --> Connected -``` - -A cancelled connect is the hard case here, because `Task.cancel()` cannot interrupt a driver blocked in a C call. `CLAUDE.md` sets out what a driver owes the app on that path, written down after the same bug shipped four times. - -## State and storage - -| Pattern | What it holds | Where | -|---------|---------------|-------| -| `@Observable` | UI state, sessions, active tab | ViewModels | -| Keychain | Connection passwords | `ConnectionStorage` | -| UserDefaults | Preferences, filter defaults, favorites, recents | `Core/Storage`, via `AppSettingsStorage` | -| SQLite FTS5 | Query history, full-text searchable | `QueryHistoryStorage` | -| JSON files | Tab state, per-table filters | `TabPersistenceCoordinator`, `TabDiskActor` | - -`CLAUDE.md` carries the full storage table, including which stores sync through iCloud and which stay device-local. - -## Dependencies - -| Package | Source | Purpose | -|---------|--------|---------| -| TableProEditor | Local package in `Packages/`, a heavily modified fork of CodeEditSourceEditor and CodeEditTextView | The SQL editor: text engine, layout, highlighting, folding | -| TableProGrammars | Local package in `Packages/` | The four tree-sitter grammars the editor highlights | -| TableProCore | Local package in `Packages/` | SwiftPM modules the macOS and iOS apps share | -| TableProOracle | Local package in `Packages/`, wraps a pinned TablePro fork of OracleNIO | Oracle wire protocol for OracleDriverPlugin | -| Sparkle | SPM, 2.10.0+ | Auto-update with EdDSA signing | -| swift-certificates | SPM, 1.19.0+ | X.509 types, linked by the app target | -| Yams | SPM, 5.1.0+ | YAML parsing for project folder import | - -`Plugins/DamengDriverPlugin` links a Rust wire-protocol bridge built from `Native/DamengBridge`. `Plugins/HanaDriverPlugin` links no Go: it starts `tablepro-hana-helper`, a Go executable built from `Native/HanaBridge` on SAP's [go-hdb](https://github.com/SAP/go-hdb) driver, as one process per session, and exchanges JSON with it over stdin and stdout. The metadata connection pool opens drivers of its own, so one SAP HANA connection can run several helpers. Neither bridge is an SPM package. - -For the repository layout, see [Project Structure](/development/setup#project-structure). diff --git a/docs/development/building.mdx b/docs/development/building.mdx deleted file mode 100644 index 1037d3e75c..0000000000 --- a/docs/development/building.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Building -description: Debug builds, the plugin compile check, clean builds, and what CI runs on a pull request ---- - -Xcode and `xcodebuild` write the built app to different directories. `Cmd+R` leaves it in -DerivedData; the command line leaves it in `build/Debug/` inside the repo. Both need the -**TablePro** scheme and the **My Mac** destination. - - -`TablePro.xcodeproj` is generated from `project.yml` by `scripts/generate-project.sh` and is not in -git. Run it after cloning, after editing `project.yml` or anything in `Configs/`, and after adding, -moving, or deleting a source file. See [Development Setup](/development/setup). - - -## Debug builds - -In Xcode, `Cmd+B` builds and `Cmd+R` builds and runs. From the command line: - -```bash -xcodebuild -project TablePro.xcodeproj -scheme TablePro -configuration Debug build -skipPackagePluginValidation -``` - -`-skipPackagePluginValidation` belongs on every `xcodebuild` invocation. -`scripts/build-release.sh`, `scripts/build-plugin.sh`, `scripts/check-pluginkit-abi.sh` and every -CI job that shells out to `xcodebuild` pass it. - -Debug builds sign with an Apple Development identity resolved from `TABLEPRO_DEVELOPMENT_TEAM`, so -signing matters even locally: a plugin in the user plugins directory has to carry the same team as -the running app, and the app derives the required team from its own signature. Set the variable in -`Configs/Secrets.xcconfig` as [Development Setup](/development/setup) describes. - -## Compiling every plugin - -An app build compiles only the plugin bundles the app embeds, which leaves every registry-only -driver uncompiled. The `AllPlugins` aggregate target covers all of them in one pass: - -```bash -xcodebuild build -project TablePro.xcodeproj -scheme AllPlugins -destination 'platform=macOS' -skipPackagePluginValidation CODE_SIGNING_ALLOWED=NO -``` - -Run it after changing anything under `Plugins/TableProPluginKit/`, which every plugin links. To -build one signed, packaged bundle instead, use `scripts/build-plugin.sh `; the target -names are the keys under `targets:` in `project.yml`, such as `OracleDriver` or -`SurrealDBDriverPlugin`. - -## Clean builds - -| Reach for | When | -|---|---| -| `Cmd+Shift+K` in Xcode | The usual case | -| `xcodebuild -project TablePro.xcodeproj -scheme TablePro clean` | Same, from a terminal | -| `rm -rf ~/Library/Developer/Xcode/DerivedData` | Package resolution is stuck or a stale module keeps reappearing | - -The last one deletes the caches for every project on the machine, and the next build starts from -nothing. - -## What a pull request has to pass - -`.github/workflows/macos-tests.yml` starts on every pull request, on Xcode 26.4.1. Its first job -decides whether the macOS suites run at all and skips them when nothing relevant changed, so the -workflow still reports a result on a docs-only change. - -| Job | What it does | -|---|---| -| Detect relevant changes | Decides whether the macOS suites run | -| Package Tests | `swift test` for TableProCore and TableProEditor | -| Build for testing | Builds `AllPlugins`, `dlopen`s each built bundle, then builds the app and both test bundles | -| Unit tests | `TableProTests` against the prebuilt products | -| UI tests | `TableProUITests`, split across three shards | -| macOS Tests Gate | Fails unless every other job reported success or skipped | - -A plugin that builds and signs can still fail to load, which is why the load step is separate: -"Bundle failed to load executable" has shipped twice. - -`.github/workflows/repo-hygiene.yml` runs beside it on Ubuntu in under a minute. It runs -`actionlint` over every workflow, which shells out to `shellcheck` for each inline `run:` block; -`shellcheck` at warning severity over every script; the plugin manifest check; and the -shared-contract drift gate in `scripts/audit-refactor-health.sh`. - -SwiftLint runs in its own workflow on every pull request. Run `swiftlint lint --strict` yourself -before pushing; see [Code Style](/development/code-style). - -## Related - -Release builds, notarization, DMG packaging and the tag that publishes them are on -[Releasing](/development/releasing). Publishing a driver plugin is on -[Plugin Registry](/development/plugin-registry). diff --git a/docs/development/code-style.mdx b/docs/development/code-style.mdx deleted file mode 100644 index c1b3fdd43a..0000000000 --- a/docs/development/code-style.mdx +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: Code Style -description: Swift conventions, tooling, naming, and file organization rules ---- - -Lint covers `TablePro/` and `Packages/`, which includes `TableProPluginKit` through the symlink in -`TableProCore`. The rest of `Plugins/` and the app's own test bundles get no automated style check, -so on a change there the conventions on this page are all there is. - -`.swiftlint.yml` settles the mechanical half. `AGENTS.md` carries the rest, the rules no linter can -check: comments that say why and never what, early returns over nested conditionals, explicit -access control, `String(localized:)` for user-facing strings, OSLog instead of `print()`. - -## Running the tools - -```bash -swiftlint lint --strict # the pre-commit gate, and what CI runs on every pull request -swiftlint --fix # apply the autocorrectable rules -``` - -`--strict` promotes every warning to an error. Several rules are configured as warnings on purpose, -so a plain `swiftlint lint` exits 0 on code CI rejects. There is no SwiftLint -build phase in the generated project, so nothing lints during a normal Xcode build. - -The scope comes from `included: [TablePro]` in `.swiftlint.yml`, and that key beats any path on the -command line: `swiftlint lint Plugins/` lints `TablePro/` and reports nothing about your plugin. It -exits 0, which reads like a pass. - -## Formatting - -Only the line limit is enforced. The rest is the convention the tree follows, so match the file -around you. - -| Rule | Value | -|---|---| -| Indentation | 4 spaces, never tabs; `case` not indented inside `switch` | -| Wrap target | 120 characters | -| Hard line limit | Warns at 180, errors at 300 (`line_length` in `.swiftlint.yml`) | -| Braces | K&R, `else` and `guard else` on the closing brace line | -| Wrapped arguments | One per line, opening paren last, closing paren balanced | -| Line endings | LF | -| Semicolons | None | -| `self.` | Only where the compiler needs it | - -`line_length` ignores URLs, function declarations, comments and interpolated strings, so a long -signature or a long log line is not what trips it. - -Trailing commas are omitted throughout the tree. SwiftLint has `trailing_comma` in -`disabled_rules`, so nothing enforces it. - -## Naming - -| Element | Convention | Example | -|---|---|---| -| Types and protocols | UpperCamelCase | `DatabaseConnection` | -| Enum cases | lowerCamelCase | `.postgresql` | -| Functions, variables, constants | lowerCamelCase | `executeQuery()`, `maxRetryAttempts` | -| Booleans | `is`, `has`, `can` or `should` prefix | `isConnected`, `hasValidCredentials` | -| Factory methods | `make` prefix | `makeConnection()` | -| Acronyms | All caps, as in Apple's own APIs | `SQLStatementGenerator`, `JSONExportPlugin`, `MCPAuditLogger` | - -Nothing enforces the acronym row: SwiftFormat's `acronyms` rule is disabled, so no tool rewrites -`Url` to `URL`. One corner of the tree disagrees with it. The MCP wire types under -`TablePro/Core/MCP/Wire/` spell it `HttpRequestParser` while the files beside them spell `MCP` in -full. New types take the all-caps form. - -## Imports - -One alphabetical block, no blank lines inside it, one blank line after it. SwiftFormat enforces -this through `--importgrouping alpha`, `blankLinesBetweenImports` and `blankLineAfterImports`, and -SwiftLint's `sorted_imports` catches an out-of-order import that never went through SwiftFormat. - -```swift -import AppKit -import Foundation -import os -import TableProEditorKit -import TableProPluginKit - -final class QueryRunner { … } -``` - -Lowercase module names sort by their own spelling, which is why `os` lands between `Foundation` and -`TableProEditorKit`. - -## Rules that bite - -`.swiftlint.yml` opts into 55 rules beyond the defaults. These are the ones that stop a clean-looking -change most often: - -| Rule | Flags | Severity | -|---|---|---| -| `implicit_return` | `return` in a single-expression function, closure or getter | warning | -| `explicit_init` | `Connection.init()` where `Connection()` compiles | warning | -| `force_unwrapping` | `!` on an optional | warning | -| `force_cast` | `as!` | warning | -| `empty_count` | `items.count == 0` instead of `items.isEmpty` | error | -| `number_separator` | `1000000` instead of `1_000_000` | warning | -| `yoda_condition` | `if 5 == limit` | warning | -| `sorted_imports` | An import out of alphabetical order | warning | -| `extension_access_modifier` | Access control repeated on members instead of set on the extension | warning | - -Every warning in that table fails under `--strict`, which is how the gate runs it. - -`force_try` sits in `disabled_rules`, so nothing flags `try!`. The rule against forcing still -applies; the linter is not what holds it up. - -## Custom rules - -Two project rules are configured at error severity, so they fire without `--strict`. Violate either -and a UI test writes into your own store instead of a throwaway one: - -| Rule | Fires on | Exempt | -|---|---|---| -| `storage_environment_directory` | `applicationSupportDirectory` | `AppStorageEnvironment.swift` | -| `storage_environment_defaults` | `UserDefaults.standard` | `AppStorageEnvironment.swift`, `GeneralSettings.swift`, `WorkspaceRailViewController.swift` | - -Resolve the directory through `AppStorageEnvironment.shared` and preferences through -`AppStorageEnvironment.shared.defaults`. Only a preference macOS itself owns reads the standard -domain. - -## Size limits - -`.swiftlint.yml` sets thresholds for `file_length`, `type_body_length`, `function_body_length` and -`cyclomatic_complexity`. Read the current numbers there rather than from memory. - -When a type approaches one, split it into `TypeName+Category.swift` files under an `Extensions/` -folder beside it, grouped by domain and not by line count. `MainContentCoordinator` is the worked -example: - - - - - - - - - - - -A new file is invisible to Xcode until `scripts/generate-project.sh` runs again. - -## Localization - -`CLAUDE.md` holds the localization rule in full. The trap worth repeating is interpolation: - -```swift -String(localized: "Preview \(name)") -``` - -That builds a different key on every call, so it never matches an entry in the strings catalog. The -string ships untranslated and no tool reports it. Take a format argument instead, which is one key -and one catalog entry: - -```swift -String(format: String(localized: "Preview %@"), name) -``` - -Technical terms stay unlocalized: font names, database types, SQL keywords, encoding names. -Exporting and merging a translation is on [Development Overview](/development/overview). diff --git a/docs/development/overview.mdx b/docs/development/overview.mdx deleted file mode 100644 index b249c00642..0000000000 --- a/docs/development/overview.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Development Overview -description: Build TablePro from source, contribute, or write a database driver plugin ---- - -Two files in the repo root outrank these pages. -[`CLAUDE.md`](https://github.com/TableProApp/TablePro/blob/main/CLAUDE.md) holds the architecture -invariants and the rules a change has to meet before it merges; -[`CONTRIBUTING.md`](https://github.com/TableProApp/TablePro/blob/main/CONTRIBUTING.md) holds branch -names, the commit format, and the pull request checklist. Both are maintained next to the code they -describe, and where either disagrees with a page here, it wins. - -What these pages add is the part neither file carries: the exact commands, the tool versions behind -them, and the plugin SDK. - - - - Clone, download the libraries, generate the project, build. - - - Module layout, plugin system, editor pipeline. - - - Debug builds, the plugin compile check, tests, pull request CI. - - - Release builds, notarization, DMG, static libraries, the release tag. - - - Lint and format config, naming, the rules CI enforces. - - - Write a driver plugin against TableProPluginKit. - - - Load a locally built plugin in a debug build. - - - Publish and distribute database driver plugins. - - - -## Translating the app - -Translate through Xcode's export and import, one XLIFF file per language. Never edit -`Localizable.xcstrings` by hand: Xcode is the only tool that writes it, so a translation diff holds -your language and nothing else. - -```bash -xcodebuild -exportLocalizations -project TablePro.xcodeproj -scheme TablePro \ - -localizationPath Localization -exportLanguage fr -xcodebuild -importLocalizations -project TablePro.xcodeproj -localizationPath Localization/fr.xcloc -scripts/localization.py status # coverage, per language -``` - -Between the two commands, translate `Localization/fr.xcloc` in Xcode, or its -`Localized Contents/fr.xliff` in any XLIFF editor. The export builds the app first. Commit the -catalog and not the export: `Localization/` is gitignored. For the iPhone and iPad app, run the same -commands on `TableProMobile/TableProMobile.xcodeproj` with the `TableProMobile` scheme. - -Plugin and package strings live in the app's catalog, marked "Managed Manually" so that Xcode keeps -them. Run `scripts/localization.py plugins --add` after adding a `String(localized:)` to a plugin or -a package. diff --git a/docs/development/plugin-development.mdx b/docs/development/plugin-development.mdx deleted file mode 100644 index 6cc14743a2..0000000000 --- a/docs/development/plugin-development.mdx +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: Plugin Development -description: Write a database driver plugin, implement the PluginKit protocols, and package it as a .tableplugin bundle ---- - -Start from a plugin that already works. `Plugins/SurrealDBDriverPlugin/` is the compact reference: no C bridge, an HTTP transport, the schema-aware query hooks filled in, and an editor language of its own. - -Two protocols do the work. `DriverPlugin` describes the database (name, port, capabilities, SQL dialect) and creates driver instances; `PluginDatabaseDriver` is the connection itself. Implement both in a `.tableplugin` bundle linked against **TableProPluginKit**, and `PluginDriverAdapter` bridges your driver to the app's internal `DatabaseDriver`. A driver that connects and lists tables gets the whole UI without asking: connection form, sidebar, data grid, editor, import, export. - -## Bundle layout - -A plugin is a macOS loadable bundle target with `WRAPPER_EXTENSION = tableplugin` that links TableProPluginKit. Set `INFOPLIST_KEY_NSPrincipalClass` to your `DriverPlugin` class. - -| Key | Type | Required | Purpose | -|-----|------|----------|---------| -| `TableProPluginKitVersion` | integer | Yes | The PluginKit ABI the plugin was built against. Current value: 37 | -| `TableProProvidesDatabaseTypeIds` | array of strings | Recommended | Database type IDs the plugin serves, which is what makes lazy loading possible | -| `CFBundleShortVersionString` | string | Yes | Plugin version, read by registry update checks | -| `TableProMinAppVersion` | string | No | The loader rejects the plugin on an older app | - -Leave `TableProProvidesDatabaseTypeIds` out and the plugin loads eagerly at startup, blocking launch, and `PluginManager` logs a warning naming the key. With it, the app registers your metadata from `Info.plist` and loads the binary on first use. - -## Implementing DriverPlugin - -Your principal class conforms to `TableProPlugin` and `DriverPlugin`. Nine members have no default, and this is all of them: - -```swift -final class SurrealDBPlugin: NSObject, TableProPlugin, DriverPlugin { - static let pluginName = "SurrealDB Driver" - static let pluginVersion = "1.0.0" - static let pluginDescription = "SurrealDB driver over the HTTP RPC protocol with SurrealQL" - static let capabilities: [PluginCapability] = [.databaseDriver] - - static let databaseTypeId = "SurrealDB" - static let databaseDisplayName = "SurrealDB" - static let iconName = "surrealdb-icon" - static let defaultPort = 8000 - - func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver { - SurrealDBPluginDriver(config: config) - } -} -``` - -`DriverConnectionConfig` carries host, port, username, password, database, SSL settings, and an `additionalFields` dictionary filled from whatever `additionalConnectionFields` you declared. - -The host also injects three reserved `additionalFields` keys before it creates a driver: `connectTimeoutSeconds`, `connectTimeoutMilliseconds`, and `queryTimeoutSeconds`. Their values are base-10 integer strings containing the effective connection policy, including the remaining shared connection budget and the resolved query timeout (`0` means no query limit). Read the unit your transport needs, but do not declare or reuse these names for plugin-specific fields. - -A `.number` field takes a whole number of 0 or more. The form keeps ASCII digits only and will not save anything else. Use `.stepper(range:)` for a bounded or signed integer, and `.text` for a value with a decimal point. - -The remaining fifty-odd statics all have defaults: connection mode, URL schemes, brand color, editor language, `sqlDialect` (keywords, functions, completions), navigation model, system database names, and two dozen `supports*` capability flags. Override the ones that differ. `Plugins/TableProPluginKit/DriverPlugin.swift` is the full list. - -## Implementing PluginDatabaseDriver - -Twelve requirements have no default. Everything else on the protocol does. - -| Group | Methods | -|-------|---------| -| Lifecycle | `connect()`, `disconnect()` | -| Queries | `execute(query:)` | -| Schema | `fetchTables(schema:)`, `fetchColumns(table:schema:)`, `fetchIndexes(table:schema:)`, `fetchForeignKeys(table:schema:)`, `fetchTableDDL(table:schema:)`, `fetchViewDefinition(view:schema:)`, `fetchTableMetadata(table:schema:)` | -| Databases | `fetchDatabases()`, `fetchDatabaseMetadata(_:)` | - -These defaults are worth a second look before you accept them: - -- `ping()` runs `SELECT 1`, and the transaction methods run `BEGIN` / `COMMIT` / `ROLLBACK` through `execute(query:)`. An engine without those keywords overrides all four. -- `sessionTransactionState()` answers `.unknown`, and a driver that cannot read its session leaves it there. The app treats `.idle` as permission to open a transaction of its own, so a guess hands a multi-statement batch or a grid save the power to commit work the user has not finished. Answer `.inTransaction`, `.abortedTransaction` or `.holdsSessionLocks` only from something the server said. -- `fetchAllColumns(schema:)` and `fetchAllForeignKeys(schema:)` loop one round-trip per table. Any SQL driver should replace them with a single catalog query. -- `quoteIdentifier`, `escapeStringLiteral`, `executeParameterized`, and `streamRows` assume generic SQL. -- The app calls the `context:` variants of `execute`, `executeParameterized`, `executeUserQuery` and `streamRows`, and the defaults forward to the plain ones. `context.readOnly` is true when the app ran the statement as a proven read, skipping Safe Mode's write confirmation. A driver whose statements can do more than their text shows, such as one that evaluates a script with state, implements the variants and refuses every write while `readOnly` is set. -- A driver that signs in again on its own, to reconnect or to open the side connection that stops a query, takes the password from `config.passwordForNewSignIn()`, not `config.password`. For an AWS or Google Cloud IAM connection the password is a token that expires, and the app mints a new one there. -- A non-SQL database implements `buildBrowseQuery`, `buildFilteredQuery`, and `generateRowWrites` instead, which is what makes browsing and editing work without SQL. Implement the `schema:`-aware overloads if your database has schemas; the schema-less defaults throw the schema away. -- Name the `rowIndex` of every change a `PluginRowWrite` writes, and throw `PluginRowWriteRefusal` with a reason for a change, or a value in one, that the driver cannot write. The app refuses a save in which a pending change has no statement, but it cannot see a value left out of one, so only the driver can refuse that. -- A database whose tables are not a list of typed columns returns a `PluginCreateTableFormSpec` from `createTableFormSpec(schema:)` and turns the filled-in form into statements in `createTableStatements(for:schema:)`. **New Table…** then shows that form in place of the column grid. The default returns `nil`, which keeps the grid. - -[Testing a Custom Plugin](/development/testing-plugins) covers getting the built bundle into a running app and reading the failure if it does not load. - -## ABI compatibility - -TableProPluginKit builds with Swift Library Evolution, so a plugin built against an older PluginKit keeps loading under a newer app: the runtime fills requirements it never implemented from their defaults. Adding a requirement that has a default costs nothing. - -Two changes look additive and are not. Both have shipped, and both surface the same way: every registry plugin fails to load with "Bundle failed to load executable". - -- **Removing a published requirement, even one that defaulted to `nil`.** Library Evolution rescues a requirement added after a plugin was built, never one removed out from under it. Removing one deletes its method descriptor and its default-implementation symbol, and every shipped plugin hard-references both in its witness table. If the app stops using a requirement, leave it in place with its default. (0.58, #1917: MongoDB, Oracle, Cassandra, Elasticsearch.) -- **Adding a parameter to an existing public initializer or function, even with a default value.** It replaces the mangled symbol. Add a new overload for the new field and mark the old one `@_disfavoredOverload`, so new code gets the full initializer and old binaries keep theirs. - -`CLAUDE.md` carries the full additive-versus-breaking list and the checklist a breaking bump obliges you to run, including re-releasing every registry plugin before the app ships. - -```bash -scripts/check-pluginkit-abi.sh [base-ref] -``` - -Run it before merging any change under `Plugins/TableProPluginKit/`. It builds the framework at your tree and at the base ref with one toolchain and diffs the two public interfaces, so a Swift version difference between machines can never fake a diff. The base ref defaults to `origin/main`; pass the merge base when reviewing a branch. Commit or stash first, since it refuses a dirty working tree. - -## Building outside this repository - -A plugin needs TableProPluginKit, not this repository. Every driver happens to live under `Plugins/` here. A new one does not have to. - -```bash -git checkout v # the oldest app release your plugin supports -scripts/generate-project.sh -scripts/build-pluginkit-xcframework.sh -# build/pluginkit/TableProPluginKit.xcframework, plus a zip and its SHA-256 -``` - -No prebuilt XCFramework is published, so build it this way and link the result from your own Xcode project. Build from a release tag, not `main`: the app rejects a plugin that declares a newer PluginKit than its own, and `main` moves ahead of the shipped app each time the PluginKit version is raised. Your plugin target then needs nothing else from this repository. - -Link it as **Do Not Embed**. The app supplies TableProPluginKit at runtime, so a copy inside your bundle is a second, conflicting one. The script refuses to emit a framework with no `.swiftinterface`, which is what a build without Library Evolution looks like: it links today and breaks every consumer on the next release. - -## Publishing - -Two steps put a plugin in TablePro's own registry, and both happen in this repository: - - - - Add your plugin to `.github/plugin-registry.json`, keyed by the slug its tag will use. Without an entry the release workflow stops with `Unknown plugin`. `icon` and `databaseTypeIds` there have to match what your `DriverPlugin` class declares, and `scripts/ci/check-plugin-manifest.py` fails PR CI and the release workflow when they drift. - - - `plugin--v`. CI builds both architectures, signs, notarizes, and updates `plugins.json`. - - - -CI signs with TablePro's certificate, so a plugin published there arrives through a pull request, not a tag of your own. The other route stays outside this repository: sign the bundle with your own Developer ID and serve it from a registry URL you host, and the app installs it once the user agrees to trust your team by name. [Plugin Registry](/development/plugin-registry) has the manifest format, the `metadata` block that renders your connection form before the download finishes, and how to point the app at a private registry. diff --git a/docs/development/plugin-registry.mdx b/docs/development/plugin-registry.mdx deleted file mode 100644 index 4c0ab70aff..0000000000 --- a/docs/development/plugin-registry.mdx +++ /dev/null @@ -1,182 +0,0 @@ ---- -title: Plugin Registry -description: Registry manifest format, binary selection, publishing, and PluginKit compatibility ---- - -The official manifest is `plugins.json` in [TableProApp/plugins](https://github.com/TableProApp/plugins), and TablePro's release workflow writes it: a plugin gets there by being contributed to TablePro's own repository and tagged there. A plugin of yours ships the other way: signed with your own Developer ID, it installs from a file or from a manifest you host in the same format. For what install and update look like to a user, see [Plugins & Themes](/features/plugins); to build the plugin in the first place, [Plugin Development](/development/plugin-development). - -The app fetches the manifest to fill **Settings > Plugins > Browse** and to auto-install a driver when someone picks a database type with no plugin loaded. - -## Manifest format - -```json -{ - "schemaVersion": 2, - "plugins": [ … ] -} -``` - -Schema version 2 is current. A manifest declaring a higher version is rejected and the app falls back to its cached copy. - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `id` | string | Yes | Bundle identifier, such as `com.TablePro.OracleDriver` | -| `name` | string | Yes | Display name | -| `version` | string | Yes | Semantic version | -| `summary` | string | Yes | One-line description | -| `author` | object | Yes | `{ "name": "…", "url": "…" }`, `url` optional | -| `homepage` | string | No | Project URL | -| `category` | string | Yes | `database-driver`, `export-format`, `import-format`, `theme`, `other` | -| `databaseTypeIds` | [string] | No | `DatabaseType.pluginTypeId` values, which is what drives auto-install | -| `binaries` | [object] | Yes | Per-architecture binaries | -| `minAppVersion` | string | No | Below this the install fails before any download | -| `iconName` | string | No | SF Symbol or bundled icon name | -| `isVerified` | bool | No | Defaults to `false` | -| `metadata` | object | No | Self-describing plugin metadata | - -Each entry in `binaries`: - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `architecture` | string | Yes | `arm64` or `x86_64` | -| `pluginKitVersion` | int | Yes for drivers | The PluginKit ABI the binary was built against | -| `downloadURL` | string | Yes | Direct URL to the `.zip` | -| `sha256` | string | Yes | SHA-256 hex of the ZIP | - - -v1 manifests carried top-level `downloadURL`, `sha256`, and `minPluginKitVersion` in place of `binaries`. The app still decodes them, synthesizing one entry per architecture. Write new entries with `binaries`. - - -## Binary selection - -For a driver, the app filters `binaries` to the running architecture, keeps those whose `pluginKitVersion` falls inside `[minimumCompatiblePluginKitVersion, currentPluginKitVersion]`, and installs the highest. Both bounds are declared in `PluginManager.swift`. A driver binary with no `pluginKitVersion` never resolves, and the install fails with `noCompatibleBinary`. - -Themes carry no native code, so they match on architecture alone. - -## Example entry - -```json -{ - "id": "com.TablePro.OracleDriver", - "name": "Oracle Driver", - "version": "1.0.26", - "summary": "Oracle Database 12c+ driver via OracleNIO", - "author": { "name": "TablePro", "url": "https://tablepro.app" }, - "homepage": "https://docs.tablepro.app/databases/oracle", - "category": "database-driver", - "databaseTypeIds": ["Oracle"], - "binaries": [ - { - "architecture": "arm64", - "pluginKitVersion": 37, - "downloadURL": "https://github.com/TableProApp/TablePro/releases/download/plugin-oracle-v1.0.26/OracleDriver-arm64.zip", - "sha256": "" - }, - { - "architecture": "x86_64", - "pluginKitVersion": 37, - "downloadURL": "https://github.com/TableProApp/TablePro/releases/download/plugin-oracle-v1.0.26/OracleDriver-x86_64.zip", - "sha256": "" - } - ], - "minAppVersion": "0.57.0", - "iconName": "server.rack", - "isVerified": true -} -``` - -## Plugin metadata - -The optional `metadata` object makes an entry self-describing, so the app renders the connection form, sidebar, and editor for a database type before the plugin is installed. Carry it on every driver entry: without it, someone picking your database type stares at a bare form until the download finishes. - -| Group | Fields | -|-------|--------| -| Identity and form | `displayName`, `iconName`, `defaultPort`, `brandColorHex`, `connectionMode`, `requiresAuthentication`, `additionalConnectionFields`, `postConnectActions`, `urlSchemes`, `fileExtensions` | -| Capabilities | `supportsSSH`, `supportsSSL`, `supportsForeignKeys`, `supportsSchemaEditing`, `supportsDatabaseSwitching`, `supportsImport`, `supportsExport`, `supportsReadOnlyMode`, `supportsHealthMonitor`, and the rest of the flags | -| Naming and navigation | `systemDatabaseNames`, `systemSchemaNames`, `defaultSchemaName`, `tableEntityName`, `containerEntityName`, `navigationModel`, `databaseGroupingStrategy` | -| Editor | `editorLanguage`, `queryLanguageName`, `sqlDialect` (keywords, functions, data types, pagination style), `statementCompletions`, `explainVariants`, `columnTypesByCategory` | - -`RegistryPluginMetadata` in `TablePro/Core/Plugins/Registry/RegistryModels.swift` is the full field list. `update-registry.py` copies an existing `metadata` block forward on every release, so it is edited by hand in the registry repository and never regenerated. - -## Publishing a plugin - -Every plugin CI can publish has an entry in `.github/plugin-registry.json` in TablePro's repository, keyed by the slug that appears in its tag. That file maps the slug to a build target, so the mapping has no derivation rule: `mssql` builds `MSSQLDriver` and `cloudflare-d1` builds `CloudflareD1DriverPlugin`. Add the entry before the first tag, or the workflow exits with `Unknown plugin`. - -```bash -git tag -a plugin-oracle-v1.0.26 -m "plugin-oracle-v1.0.26" -git push origin plugin-oracle-v1.0.26 -``` - - -Push plugin tags one at a time. A push carrying more than three tags creates no push events on GitHub, so no workflow fires and nothing is published. - - -Dispatching the workflow works too. Its `tags` input takes comma-separated `tag:pluginKitVersion` pairs, and dropping the `:` part makes the workflow read `currentPluginKitVersion` from `PluginManager.swift`: - -```bash -gh workflow run build-plugin.yml --field "tags=plugin-oracle-v1.0.26" -``` - -Either way CI builds both architectures, signs and notarizes the bundles, checks each bundle's declared PluginKit version against the release label, creates the GitHub release as a draft, checks the uploaded zips byte for byte, publishes it, re-verifies the published assets, and pushes the updated `plugins.json` to TableProApp/plugins through `.github/scripts/update-registry.py`, which writes atomically and rebases on a retry when the matrix jobs collide. - -A published release is immutable, and a rebuild never matches its bytes. A run for a tag whose release is already published with other zips fails, so a new binary ships under the next patch version. - -Bundled plugins ride with the app release. Ten of them also have an entry in `.github/plugin-registry.json` (SQLite, ClickHouse, Redis, XLSX export, XLSX import, MQL export, SQL import, HTML export, Markdown export, XML export), so a fix to one can be tagged and published to the registry. The app never installs or offers a registry update over a bundled plugin, so that build reaches only users who download its zip and install it from a file. Everyone else gets the fix with the next app release. A bulk ABI re-release skips them. The other seven have no entry and never reach `plugins.json`. - -## PluginKit compatibility - -A plugin built against any PluginKit version inside the app's `[minimum, current]` range loads, and the runtime fills newer requirements from their defaults. What that means for releases: - -- **Additive change** (a new requirement with a default, a new field on a non-frozen type): raise `currentPluginKitVersion` and the `TableProPluginKitVersion` of every plugin rebuilt against it, leave `minimumCompatiblePluginKitVersion` alone, and re-publish nothing. The binaries already out there keep serving, because the runtime fills a requirement they predate from its default. The bump is for the other direction: a plugin rebuilt against the new SDK references symbols an older app does not carry, and a manifest left at the old number passes that app's version check and then fails to load at all. Leaving the manifest behind is the one mistake this rule exists to stop. -- **Breaking change** (a removed or changed requirement, a frozen-layout change, a requirement without a default): raise `currentPluginKitVersion` and `minimumCompatiblePluginKitVersion` together, then run `scripts/release-all-plugins.sh `. It reads the registry-only plugins out of `.github/plugin-registry.json`, bumps each one's patch version, and fires a single `workflow_dispatch` so they all build as one matrix. -- **Retention**: `update-registry.py` keeps binaries for the three newest PluginKit versions per plugin. Older ones are pruned, and an app below every remaining binary resolves nothing at all, for every plugin. -- **Bump the kit at most once per release cycle.** Retention counts kit versions, not days, so the window's length in wall-clock time is set by how often the number moves. Eleven bumps between 2026-09-02 and 2026-09-12 took it from 20 to 30 and left the oldest published binary at kit 21, while v0.65.0 to v0.70.0 ship kit 19 and v0.71.0 ships kit 20. - -To reach users who have not updated, build the plugin against the release they are on rather than against `main`: - -```bash -scripts/release-plugin-for-shipped-app.sh plugin-mongodb-v1.0.45 v0.73.0 -``` - -It reads that tag's `currentPluginKitVersion` and dispatches the workflow with `baseRef` set, so the binary links against that release's PluginKit and declares the matching `TableProPluginKitVersion`. Both have to come from the same tree. Stamping a binary built from `main` with an older number makes the older app accept it and then fail `Bundle.loadAndReturnError`. - -The app's own release workflow runs `scripts/check-registry-readiness.py --floor --current ` and fails until every registry driver has a compatible binary, so the app cannot ship ahead of its plugins. When an installed driver predates a breaking bump, the app repairs it in the background on the next connect. See [After an app update](/features/plugins#after-an-app-update). - -## Caching - -The app fetches the manifest from `raw.githubusercontent.com/TableProApp/plugins/main/plugins.json`, which caches at the edge for about five minutes. Every fetch revalidates conditionally, the list refreshes at launch and when the plugin browser opens (throttled to one check per five minutes), and an install prompt forces a fresh fetch before it reports a plugin missing. CI also purges the jsDelivr cache after each registry push, for older app versions that still fetch from there. A newly published plugin shows up in the app within minutes. - -## Theme distribution - -Themes use the same manifest with `category: "theme"`. Four things differ from a driver: - -- Pure JSON data. No executable code, no code signing, no `.tableplugin` bundle -- The ZIP holds `.json` files, each a valid `ThemeDefinition`. Packs with several themes work -- They install to `~/Library/Application Support/TablePro/Themes/Registry/` -- No `pluginKitVersion` is needed, and the flat v1 fields still decode - -```json -{ - "id": "com.example.monokai-theme", - "name": "Monokai Theme", - "version": "1.0.0", - "summary": "Classic Monokai color scheme for TablePro", - "author": { "name": "Theme Author" }, - "category": "theme", - "downloadURL": "https://example.com/monokai-theme.zip", - "sha256": "", - "iconName": "paintpalette" -} -``` - -## Custom registry URL - -Point the app at a private or enterprise manifest, which is also how a plugin signed with your own Developer ID reaches your users: - -```bash -defaults write com.TablePro com.TablePro.customRegistryURL "https://your-registry.example.com/plugins.json" - -defaults delete com.TablePro com.TablePro.customRegistryURL -``` - -The custom manifest replaces the official one rather than adding to it. HTTP caching keys on the full URL, so a changed registry URL takes effect on the next fetch. A plugin served this way installs once the user agrees to trust its signing team by name. Both commands are also listed in [Settings](/customization/settings). diff --git a/docs/development/setup.mdx b/docs/development/setup.mdx deleted file mode 100644 index 172956ce43..0000000000 --- a/docs/development/setup.mdx +++ /dev/null @@ -1,236 +0,0 @@ ---- -title: Development Setup -description: Clone, configure signing, and build TablePro in Xcode ---- - -The Xcode project is generated and the static libraries are downloaded. A fresh clone carries -neither, so two scripts run before anything opens in Xcode. Xcode itself has to be 26.0 or newer: -the app calls SwiftUI's `glassEffect(_:in:)` behind `if #available(macOS 26.0, *)`, and that symbol -ships in the macOS 26 SDK. The deployment target stays at macOS 13.0, which is what the built app -runs on, not what you build on. - -## Prerequisites - -| Software | Version | Notes | -|----------|---------|-------| -| Xcode | 26.0+ | Every CI job pins 26.4.1 through `.github/actions/setup-xcode` | -| XcodeGen | 2.46.0 exactly | `brew install xcodegen`. Generated `pbxproj` output differs between XcodeGen versions, so `scripts/generate-project.sh` warns on any other version | -| SwiftLint | any | `brew install swiftlint`. The release workflow runs `swiftlint lint --strict` | -| SwiftFormat | any | `brew install swiftformat`. Not run in CI | -| GitHub CLI | any | `brew install gh`. Optional: `scripts/download-libs.sh` falls back to `curl` | -| Go | 1.27.1 | `brew install go`. Only for the SAP HANA plugin: run `scripts/build-hana.sh` with no argument, which builds the helper for both architectures, before building `HanaDriver` or `AllPlugins`. A thin `arm64` or `x86_64` helper only works in a build of that one architecture. The script builds with exactly 1.27.1 and fetches it when the installed `go` is another release | -| Rust | 1.91.1 | Install [rustup](https://rustup.rs). Only for the Dameng plugin: run `scripts/build-dameng.sh arm64` before building `DamengDriver` or `AllPlugins`. The script installs 1.91.1 through rustup | - -The Swift language mode comes from `SWIFT_VERSION = 6.0` in `Configs/Base.xcconfig`, and each -package under `Packages/` sets its own through `swift-tools-version: 6.0`. Never pass -`SWIFT_VERSION=` on an `xcodebuild` command line to test a language-mode change: the override -reaches the package targets too and reports their errors as yours. - -## Quick start - - - - ```bash - git clone https://github.com/TableProApp/TablePro.git && cd TablePro - ``` - - - - ```bash - brew install xcodegen swiftlint swiftformat - ``` - - - - ```bash - scripts/download-libs.sh - ``` - - Two archives come off the `libs-v1` GitHub Release: the macOS `.a` files (libmariadb, libpq, - libduckdb, libmongoc, and the rest) and the iOS xcframeworks. 257 MB downloaded, 621 MB on - disk. Every run, including one that downloads nothing, verifies `Libs/` against the checksums - committed at `HEAD`. - - None of it is in git, so a build that skips this step fails at link time on missing symbols. - - - - ```bash - cp Configs/Secrets.xcconfig.example Configs/Secrets.xcconfig - ``` - - Then edit the copy. The template ships the literal `YOUR_TEAM_ID`, which fails to sign exactly - the way leaving the file out does: - - ``` - TABLEPRO_DEVELOPMENT_TEAM = YOUR_TEAM_ID - TABLEPRO_APP_BUNDLE_IDENTIFIER = com.yourhandle.TablePro - ``` - - A free personal team works. Debug builds sign against `TablePro/TablePro.Debug.entitlements`, - which drops the iCloud container and CloudKit, so no paid Apple Developer Program membership is - needed and sync turns itself off at runtime. Plugin bundles and the MCP helper sign ad-hoc and - need no team at all. - - `Configs/Secrets.xcconfig` is gitignored, so these settings cannot reach a commit and they - survive regenerating the project. Changing signing in the Xcode UI has no effect that lasts: - the project is generated, and the next `scripts/generate-project.sh` discards the edit. - - - - ```bash - scripts/generate-project.sh - ``` - - This writes `TablePro.xcodeproj` and `TableProMobile/TableProMobile.xcodeproj` from their - `project.yml` specs. Pass `macos` or `ios` to generate just one of them. Run it again after - editing a `project.yml` or anything in `Configs/`, and after adding, moving, or deleting a - source file, because XcodeGen collects sources at generation time and a new file is not in - the project until you regenerate. - - - - ```bash - open TablePro.xcodeproj - ``` - - Select the **TablePro** scheme, set the destination to **My Mac**, press `Cmd+R`. Or stay on - the command line: - - ```bash - xcodebuild -project TablePro.xcodeproj -scheme TablePro -configuration Debug build -skipPackagePluginValidation - ``` - - The two write to different places. See [Building](/development/building). - - - - - Xcode navigator listing the TablePro group beside one group per plugin bundle, with the Open Quickly panel open over the editor - Xcode navigator listing the TablePro group beside one group per plugin bundle, with the Open Quickly panel open over the editor - - -Writing a driver plugin? [Plugin Development](/development/plugin-development) covers building one -and [Testing a Custom Plugin](/development/testing-plugins) covers running it in your local build. - -## Project structure - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`Packages/TableProCore/Sources/TableProPluginKit` is a symlink to `Plugins/TableProPluginKit`. Edit -the files under `Plugins/` only. - -## Running tests - -```bash -xcodebuild -project TablePro.xcodeproj -scheme TablePro test -skipPackagePluginValidation -``` - -Or press `Cmd+U` in Xcode. To narrow the run, pass `-only-testing:TableProTests/`, or -`-only-testing:TableProTests//()` for one Swift Testing case. The filter matches the -suite's Swift type name, and a filter that matches nothing still reports success, so check how many -tests ran. `-only-testing:TableProUITests` runs the UI automation on its own. - -## Linting - -```bash -swiftlint lint --strict -``` - -`--strict` turns warnings into errors, which is how CI runs it on every pull request, so a plain -`swiftlint lint` can pass on a change CI rejects. Full tooling reference: -[Code Style](/development/code-style). - -## Troubleshooting - -### `xcodegen: command not found` - -`scripts/generate-project.sh` needs XcodeGen on `PATH`. Run `brew install xcodegen`, then check -`xcodegen --version` reports 2.46.0. - -### `TablePro.xcodeproj` does not exist - -It is generated, not committed. Run `scripts/generate-project.sh`. - -### `Undefined symbols for architecture arm64` - -`Libs/` is empty or stale. Run `scripts/download-libs.sh`, or `scripts/download-libs.sh --force` to -overwrite what is already there. - -### `Build input file cannot be found: '…/tablepro-hana-helper'` - -`HanaDriver` and `HanaDriverTests` copy the SAP HANA helper into their bundle, and Xcode does not -build it. Run `scripts/build-hana.sh` with no argument, then build again. - -### `…/tablepro-hana-helper has no … slice` - -The helper was built thin for the other architecture, and every architecture Xcode builds needs a -slice. Run `scripts/build-hana.sh` with no argument, which builds both, then build again. - -### `Libs does not match the checksums committed in git.` - -`download-libs.sh` compares the extracted libraries against the baseline at `HEAD` and refuses to -continue when they differ. Re-run with `--force`. If you rebuilt a library on purpose, publish it -so the baseline moves with it: see [Releasing](/development/releasing). - -### Signing errors after a fresh clone - -`Configs/Base.xcconfig` defaults `TABLEPRO_DEVELOPMENT_TEAM` to the maintainer's team, and it -includes `Secrets.xcconfig` with `#include?`, so a build without that file still runs and still -fails to sign. Create the file and set your own team. - -### A new source file does not compile - -XcodeGen globs sources when the project is generated. Run `scripts/generate-project.sh`. - -### Package resolution fails - -Clean the build folder with `Cmd+Shift+K` and reopen Xcode. If that does not clear it, -`rm -rf ~/Library/Developer/Xcode/DerivedData`. diff --git a/docs/development/testing-plugins.mdx b/docs/development/testing-plugins.mdx deleted file mode 100644 index 598e62867d..0000000000 --- a/docs/development/testing-plugins.mdx +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Testing a Custom Plugin -description: Load a locally built driver plugin in a debug build of TablePro ---- - -A plugin's signature is checked only outside the app's own `PlugIns` directory. That one rule explains all three ways to run a plugin you built locally, and which of them a release build of TablePro would also accept. The dev script is the normal route; the other two are for when the signature check itself is in your way. See [Plugin Development](/development/plugin-development) for writing the driver and [Plugin Registry](/development/plugin-registry) for publishing it. - -## Where plugins load from - -`PluginManager` reads two directories on launch and treats them differently. - -| Location | Source | Checked before loading | -|----------|--------|------------------------| -| `PlugIns/` inside the app bundle | Built-in | `Info.plist` versions only | -| `~/Library/Application Support/TablePro/Plugins` | User-installed | `Info.plist` versions, then the code signature | - -The version keys go first either way: a plugin whose `TableProPluginKitVersion` falls outside the app's compatible range, or whose `TableProMinAppVersion` is newer than the running app, is rejected before anything looks at its signature. - -The signature check has two passes. A bundle signed by the same Apple team as the running app loads with no further question, which is why your own debug build accepts what your own Xcode signed. A bundle carrying any other Developer ID loads only if the user has trusted that team by name, and that consent is collected during an install through a registry, nowhere else ([Plugins & Themes](/features/plugins#plugins-from-other-developers)). Hand-copy such a bundle into the user plugins directory and it fails to load, because nothing on that path asks. - -Do not reuse a built-in plugin's bundle ID. Installing over one fails outright, and at discovery the higher version wins, with the built-in taking a tie and the losing user copy deleted from disk. - -## Install with the dev script - -```bash -scripts/install-plugin-dev.sh MyDriverPlugin -``` - -It finds the newest `MyDriverPlugin.tableplugin` under DerivedData, copies it into the user plugins directory, and writes the registry metadata sidecar. Relaunch TablePro to load it. Build from Xcode first: the script reads DerivedData, and a command-line `xcodebuild` writes to `build/Debug` instead, where the script never looks. - - -Never edit the copied bundle, `Info.plist` included. Any change invalidates the code signature and dyld then refuses to load the executable. If the plugin's `TableProMinAppVersion` is ahead of your built app, raise the app's `MARKETING_VERSION` in `Configs/Version.xcconfig` and rebuild the app. - - -## Bundle it into the app instead - -A plugin embedded in `PlugIns/` skips the signature check entirely, which is the shortest path when the team check is the thing in your way. Add the target to the app's dependency list in `project.yml`: - -```yaml -targets: - TablePro: - dependencies: - - target: MyDriverPlugin - embed: true - copy: - destination: plugins -``` - -Then run `scripts/generate-project.sh` and build. XcodeGen puts the bundle in an **Embed Dependencies** copy phase with `CodeSignOnCopy`, so it is re-signed with your debug identity on the way in. - - -`TablePro.xcodeproj` is generated and gitignored, so adding the plugin through the Xcode UI survives exactly until the next `scripts/generate-project.sh`. Edit `project.yml`. Take the entry back out before you open a pull request, or the plugin rides into a release build. - - -## Skip the signature check - -A Debug build skips signature verification altogether when `TABLEPRO_ALLOW_UNSIGNED_PLUGINS` is set to `1`. Set it under **Product > Scheme > Edit Scheme > Run > Arguments**, in the environment variables list. - -This is the route for a bundle built on another machine, or for testing your own Developer ID signing before there is any registry entry to install from, since it also bypasses the developer-trust prompt. The bundle still has to be validly signed for dyld to load it at all. The escape hatch is compiled out of Release builds, so it cannot mask a signing problem that would reach a user. - -## When it does not load - -The reason lands in a **Plugin Installation Failed** alert when installing, and in the plugin's row under **Settings > Plugins > Installed** otherwise. - -| Message | What it means | What to do | -|---------|---------------|------------| -| **Plugin code signature verification failed: …** | Unsigned, ad-hoc signed, modified after signing, or signed by a certificate that is neither your app's team nor a Developer ID | Rebuild rather than patch. Any edit to a signed bundle breaks the seal | -| **This plugin is signed by …, a developer you have not trusted yet.** | A valid Developer ID that this Mac has not consented to | Install it through a registry so the trust prompt runs, or set `TABLEPRO_ALLOW_UNSIGNED_PLUGINS=1` in a Debug build | -| **Plugin was built for PluginKit version …; this release of TablePro needs version …** | The bundle predates a breaking ABI bump | Rebuild the plugin against the current TableProPluginKit | -| **Plugin requires app version … or later, but current version is …** | `TableProMinAppVersion` is ahead of the app | Raise `MARKETING_VERSION` in `Configs/Version.xcconfig` and rebuild | -| **A built-in plugin "…" already provides this bundle ID** | The bundle ID collides with a bundled plugin | Give your plugin its own bundle ID | - -Once it loads and works, publish it through the [Plugin Registry](/development/plugin-registry) so users get a signed binary. diff --git a/docs/docs.json b/docs/docs.json deleted file mode 100644 index b79aff145a..0000000000 --- a/docs/docs.json +++ /dev/null @@ -1,519 +0,0 @@ -{ - "$schema": "https://mintlify.com/docs.json", - "theme": "aspen", - "name": "TablePro", - "description": "TablePro is a native macOS database client for MySQL, PostgreSQL, SQLite, MongoDB, Redis and more, built on AppKit and SwiftUI with a plugin-based driver system.", - "colors": { - "primary": "#A85A00", - "light": "#FFBB6B", - "dark": "#A85A00" - }, - "fonts": { - "family": "Inter" - }, - "favicon": "/favicon.png", - "logo": { - "light": "/logo/light.png", - "dark": "/logo/dark.png", - "href": "https://tablepro.app" - }, - "seo": { - "indexing": "navigable", - "metatags": { - "og:site_name": "TablePro", - "og:type": "website", - "twitter:card": "summary_large_image" - }, - "organization": { - "id": "https://tablepro.app/#organization", - "name": "TablePro", - "url": "https://tablepro.app", - "sameAs": [ - "https://github.com/TableProApp/TablePro" - ] - } - }, - "search": { - "prompt": "Search the docs" - }, - "errors": { - "404": { - "redirect": false, - "title": "That page moved or never existed", - "description": "Try [the docs home](/), or search with `Cmd+K`. If a link here sent you, [open an issue](https://github.com/TableProApp/TablePro/issues)." - } - }, - "redirects": [ - { - "source": "/features/csv-inspector", - "destination": "/features/data-files" - }, - { - "source": "/features/php-viewer", - "destination": "/features/json-viewer" - }, - { - "source": "/customization/overview", - "destination": "/customization/settings" - }, - { - "source": "/features/quick-switcher", - "destination": "/features/open-quickly" - }, - { - "source": "/features/export", - "destination": "/features/import-export" - }, - { - "source": "/features/import", - "destination": "/features/import-export" - }, - { - "source": "/databases/connection-urls", - "destination": "/connections/urls" - }, - { - "source": "/databases/ssh-tunneling", - "destination": "/connections/ssh-tunneling" - }, - { - "source": "/databases/cloudflare-tunnel", - "destination": "/connections/cloudflare-tunnel" - }, - { - "source": "/databases/cloud-sql-proxy", - "destination": "/connections/cloud-sql-proxy" - }, - { - "source": "/databases/socks-proxy", - "destination": "/connections/socks-proxy" - }, - { - "source": "/databases/aws-iam", - "destination": "/connections/aws-iam" - }, - { - "source": "/databases/overview", - "destination": "/connections" - }, - { - "source": "/features/ssh-profiles", - "destination": "/connections/ssh-profiles" - }, - { - "source": "/features/ssl", - "destination": "/connections/ssl" - }, - { - "source": "/external-api", - "destination": "/developers" - }, - { - "source": "/external-api/versioning", - "destination": "/developers/versioning" - }, - { - "source": "/external-api/url-scheme", - "destination": "/developers/url-scheme" - }, - { - "source": "/external-api/applescript", - "destination": "/developers/applescript" - }, - { - "source": "/external-api/connection-import", - "destination": "/developers/connection-import" - }, - { - "source": "/external-api/mcp-protocol", - "destination": "/developers/mcp-protocol" - }, - { - "source": "/external-api/mcp-tools", - "destination": "/developers/mcp-tools" - }, - { - "source": "/external-api/mcp-resources", - "destination": "/developers/mcp-resources" - }, - { - "source": "/external-api/mcp-prompts", - "destination": "/developers/mcp-prompts" - }, - { - "source": "/external-api/mcp-subscriptions", - "destination": "/developers/mcp-subscriptions" - }, - { - "source": "/external-api/pairing", - "destination": "/developers/pairing" - }, - { - "source": "/external-api/tokens", - "destination": "/developers/tokens" - }, - { - "source": "/external-api/raycast", - "destination": "/integrations/raycast" - }, - { - "source": "/external-api/terminal", - "destination": "/integrations/terminal" - }, - { - "source": "/external-api/ios-shortcuts", - "destination": "/integrations/ios-shortcuts" - }, - { - "source": "/external-api/mcp-clients", - "destination": "/integrations/mcp-clients" - } - ], - "navigation": { - "tabs": [ - { - "tab": "Documentation", - "groups": [ - { - "group": "Start here", - "pages": [ - "index", - "installation", - "quickstart", - "switching", - "faq", - "troubleshooting", - "changelog" - ] - }, - { - "group": "Connections", - "icon": "plug", - "pages": [ - "connections/index", - "connections/connection-form", - "connections/urls", - "connections/ssh-tunneling", - "connections/credential-profiles", - "connections/ssh-profiles", - "connections/remote-database-files", - "connections/cloudflare-tunnel", - "connections/cloud-sql-proxy", - "connections/socks-proxy", - "connections/tunnel-command", - "connections/aws-iam", - "connections/ssl", - "features/connection-sharing", - "features/project-folder-import" - ] - }, - { - "group": "Databases", - "icon": "database", - "pages": [ - "databases/index", - "features/plugins", - "databases/dynamodb", - "databases/redshift", - "databases/beancount", - "databases/cassandra", - "databases/clickhouse", - "databases/cloudflare-d1", - "databases/cloudflare-r2-sql", - "databases/cockroachdb", - "databases/dameng", - "databases/databend", - "databases/duckdb", - "databases/elasticsearch", - "databases/etcd", - "databases/bigquery", - "databases/kafka", - "databases/libsql", - "databases/mariadb", - "databases/mssql", - "databases/mongodb", - "databases/mysql", - "databases/oceanbase", - "databases/oracle", - "databases/pglite", - "databases/postgresql", - "databases/redis", - "databases/sap-hana", - "databases/snowflake", - "databases/spanner", - "databases/sqlite", - "databases/surrealdb", - "databases/teradata", - "databases/tidb", - "databases/trino", - "databases/typesense", - "databases/weaviate" - ] - }, - { - "group": "Write SQL", - "icon": "code", - "pages": [ - "features/sql-editor", - "features/code-folding", - "features/sql-files", - "features/autocomplete", - "features/query-parameters", - "features/vim-mode", - "features/query-history", - "features/query-insights" - ] - }, - { - "group": "Browse and edit data", - "icon": "table", - "pages": [ - "features/data-grid", - "features/query-results", - "features/filtering", - "features/change-tracking", - "features/json-viewer", - "features/data-files" - ] - }, - { - "group": "Schema and server", - "icon": "sitemap", - "pages": [ - "features/table-structure", - "features/routines-triggers", - "features/user-defined-types", - "features/table-operations", - "features/er-diagram", - "features/explain-visualization", - "features/server-dashboard", - "features/users-roles" - ] - }, - { - "group": "Import and export", - "icon": "arrow-right-arrow-left", - "pages": [ - "features/import-export", - "features/copy-objects", - "features/backup-restore", - "features/compare-sync" - ] - }, - { - "group": "AI", - "icon": "sparkles", - "pages": [ - "features/ai-assistant", - "features/agent-mode", - "features/mcp", - "features/mcp-outside-servers" - ] - }, - { - "group": "Integrations", - "icon": "puzzle-piece", - "pages": [ - "integrations/index", - "integrations/mcp-clients", - "integrations/raycast", - "integrations/terminal", - "integrations/ddev", - "integrations/ios-shortcuts" - ] - }, - { - "group": "Around the app", - "icon": "window-maximize", - "pages": [ - "features/overview", - "features/connection-window", - "features/tabs", - "features/workspace-rail", - "features/open-quickly", - "features/favorites", - "features/keyboard-shortcuts" - ] - }, - { - "group": "Sync and licensing", - "icon": "arrows-rotate", - "pages": [ - "features/icloud-sync", - "features/handoff", - "features/licensing", - "features/team" - ] - }, - { - "group": "Customization", - "pages": [ - "customization/settings", - "customization/general-settings", - "customization/appearance", - "customization/editor-settings", - "customization/data-settings", - "customization/notifications" - ] - }, - { - "group": "iPhone and iPad", - "icon": "mobile", - "pages": [ - "ios/index" - ] - }, - { - "group": "Security and privacy", - "icon": "shield", - "pages": [ - "security/privacy", - "security/managed-updates", - "features/safe-mode", - "features/acknowledgements" - ] - } - ] - }, - { - "tab": "Developers", - "icon": "code", - "groups": [ - { - "group": "Overview", - "pages": [ - "developers/index" - ] - }, - { - "group": "API reference", - "pages": [ - "developers/url-scheme", - "developers/applescript", - "developers/connection-import", - { - "group": "MCP", - "pages": [ - "developers/mcp-protocol", - "developers/mcp-tools", - "developers/mcp-resources", - "developers/mcp-prompts", - "developers/mcp-subscriptions" - ] - }, - "developers/pairing", - "developers/tokens", - "developers/versioning" - ] - }, - { - "group": "Publish", - "pages": [ - "developers/submit", - "developers/listing-policy", - "developers/review" - ] - } - ] - }, - { - "tab": "Contributing", - "icon": "code-branch", - "groups": [ - { - "group": "Build TablePro", - "pages": [ - "development/overview", - "development/setup", - "development/architecture", - "development/building", - "development/releasing", - "development/code-style" - ] - }, - { - "group": "Plugins", - "pages": [ - "development/plugin-development", - "development/plugin-registry", - "development/testing-plugins" - ] - } - ] - } - ], - "global": { - "anchors": [ - { - "anchor": "GitHub", - "href": "https://github.com/TableProApp/TablePro", - "icon": "github" - }, - { - "anchor": "Download", - "href": "https://tablepro.app/download", - "icon": "download" - }, - { - "anchor": "Discord", - "href": "https://discord.gg/hCNmUUbnD4", - "icon": "discord" - }, - { - "anchor": "Telegram", - "href": "https://t.me/tablepro_app", - "icon": "telegram" - } - ] - } - }, - "navbar": { - "links": [ - { - "label": "Website", - "href": "https://tablepro.app" - }, - { - "label": "Support", - "href": "mailto:hello@tablepro.app" - }, - { - "type": "github", - "href": "https://github.com/TableProApp/TablePro" - } - ], - "primary": { - "type": "button", - "label": "Download", - "href": "https://tablepro.app/download" - } - }, - "contextual": { - "options": [ - "copy", - "view", - "chatgpt", - "claude", - "perplexity", - "cursor", - "vscode" - ] - }, - "footer": { - "socials": { - "website": "https://tablepro.app", - "github": "https://github.com/TableProApp/TablePro", - "discord": "https://discord.gg/hCNmUUbnD4", - "telegram": "https://t.me/tablepro_app" - } - }, - "integrations": { - "ga4": { - "measurementId": "G-GQYNPKSK83" - }, - "cookies": { - "key": "tablepro:analytics-consent", - "value": "granted" - } - } -} diff --git a/docs/faq.mdx b/docs/faq.mdx deleted file mode 100644 index 9a7696138e..0000000000 --- a/docs/faq.mdx +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: FAQ -description: Short answers on licensing, privacy, offline use, database coverage, syncing, and moving from another client ---- - -import DriverCounts from "/snippets/driver-counts.mdx"; - -Short answers only, each linking to the page that carries the detail. Anything not covered is worth an issue on [GitHub](https://github.com/TableProApp/TablePro/issues), where driver requests and support questions are tracked in the open. - -## Cost and licensing - - - - -Yes, and there is no trial countdown. A license adds the features in the tier table and changes nothing else. See [Licensing](/features/licensing). - - - -Two tiers. Starter covers iCloud Sync, encrypted connection export, environment variables in connection fields, Linked Folders, Query Insights, result charts, Compare & Sync, and Data Rewind. Team adds the Team Catalog and the Team Library on top of Starter. [Licensing](/features/licensing) shows what each one does without a license, and prices are on [tablepro.app/pricing](https://tablepro.app/pricing). - - - -Yes. Nothing counts seats. To share connections and saved queries with teammates, the Team tier hands out seats from your account on tablepro.app, and each teammate activates with an invite code instead of a key. See [Team Plan](/features/team). - - - -The GNU Affero General Public License v3.0, plugins included. A TablePro license key is a separate thing: it activates the paid features, not the app. - - - - -## Privacy and security - - - - -Your queries, results, and schemas stay on your Mac. One usage heartbeat goes out every 24 hours. It carries a hashed machine ID and facts about the app, such as its version and the types of the open connections, with no query text, hostnames, database names, or connection names; [Privacy](/security/privacy#usage-heartbeat) lists every field in it. Turn it off with **Share usage data** in **Settings > General**. AI is the exception, since the context you allow reaches the provider you configured. - - - -Passwords go into the macOS Keychain, not the connections file. An ordinary export leaves them out, the Team Library carries the connection definition and query text only, and MCP clients query through your saved connections without seeing one. Two exceptions: **Copy Connection String** puts the password on the clipboard in plain text, and an encrypted export (Starter) carries credentials behind a passphrase. See [Connection Sharing](/features/connection-sharing). - - - -Under `~/Library/Application Support/TablePro`: connection definitions without their passwords, query history, saved queries, open tabs, per-table filters, and any plugin you installed. Preferences are in `~/Library/Preferences/com.TablePro.plist`. [Privacy](/security/privacy) names every file and what it holds. - - - -Drag the app to the Trash, then delete the Application Support folder and the preferences file. Keychain items and anything already synced to iCloud have to go separately. [Installation](/installation) has the commands. - - - - -## Databases and drivers - - - - -[Supported Databases](/databases) lists every engine with its default port. [Managing Connections](/connections) adds the transport each supports and whether it can go through an SSH tunnel, SSL, Cloudflare Tunnel, Cloud SQL Proxy, or a SOCKS proxy. - - - -Its driver is probably not installed yet. The database type chooser badges registry drivers **Not Installed**; pick one anyway and the download runs before the connection opens, with no restart. **Settings > Plugins > Browse** holds the full catalog. See [Plugins & Themes](/features/plugins). - - - - - -Each one must match the SHA-256 checksum in the registry manifest and carry a valid code signature before it loads. - - - -An app update can raise the plugin interface version, and a plugin built against the old one is rejected at load rather than crashing. TablePro repairs it at the next launch by reinstalling a compatible build from the registry. Until then the plugin sits in a banner in **Settings > Plugins > Installed** with the reason and an action. - - - - -## Platforms and sync - - - - -Connecting, browsing, and querying need nothing but the database. What does need the network: installing or updating a plugin, and any AI provider that is not local. A license keeps working for 30 days past its last successful validation when the license server cannot be reached. See [Licensing](/features/licensing). - - - -Through iCloud, with a Starter or Team license. Sync is off by default and each category has its own toggle: connections, groups and tags, SSH and credential profiles, saved queries, table and database favorites, table folders, and settings. Passwords are a separate opt-in that rides iCloud Keychain, and a connection can be marked **Local only** to stay off iCloud. See [iCloud Sync](/features/icloud-sync). - - - -Yes, for iPhone and iPad on iOS 18 or later. It reads the same CloudKit data as the Mac app but carries connections, groups, and tags only. See [iPhone and iPad](/ios). - - - -No, macOS and iOS only. A Linux port is a prototype with nothing to install yet, and there is no Windows build at all. Progress is tracked on [GitHub](https://github.com/TableProApp/TablePro). - - - -Releases go out when the work is ready, which lately averages one every three days. They should not interrupt you: a new version downloads in the background and installs the next time you quit, with no dialog. - -The one exception is a fix you need straight away, meaning data loss, a crash on a common path, or a security issue. Those are flagged so they surface immediately. - -**Settings > General** sets the check to daily or weekly, turns automatic installs off so you are asked each time, or turns checks off entirely. An administrator can pin all three for a fleet: see [Managed updates](/security/managed-updates). - - - - -## Moving from another client - - - - -**File > Import from Other App…** reads the connections TablePlus already has, groups included, and TablePlus does not need to be running. Each password is its own Keychain item, so macOS asks per item; choose **Always Allow** and it stops asking. Saved queries come across too, into a folder named TablePlus that every connection shows. Query history and column widths do not. The same dialog handles Sequel Ace, DBeaver, DataGrip, Beekeeper Studio, and Navicat. See [Switching to TablePro](/switching). - - - -Two other routes. **File > Import > Import from URL…** takes a `postgres://` or `mysql://` URL and fills in the form. [Open Project Folder](/features/project-folder-import) reads database settings out of a repository's `.env`, `docker-compose.yml`, or framework config, so a project becomes a connection without typing a host. - - - diff --git a/docs/favicon.png b/docs/favicon.png deleted file mode 100644 index ee1e7094a4..0000000000 Binary files a/docs/favicon.png and /dev/null differ diff --git a/docs/features/acknowledgements.mdx b/docs/features/acknowledgements.mdx deleted file mode 100644 index 6c148b9961..0000000000 --- a/docs/features/acknowledgements.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: "Acknowledgements" -description: "The open source libraries TablePro ships, and the licenses that cover them" ---- - -**Help > Acknowledgements** opens a window listing every library that ends up inside the app or one of its plugin bundles. That covers the C libraries linked into the drivers, MariaDB Connector/C, libpq, FreeTDS, libssh2, hiredis, the MongoDB C driver, the DataStax C/C++ driver, DuckDB and OpenSSL among them; go-hdb and the Go runtime inside the SAP HANA driver; the Swift packages the app links, such as Sparkle, Yams and swift-certificates; and the editor packages vendored into the repository. - -Pick one and the detail pane gives its exact shipped version, its SPDX identifier, its copyright lines, and the full license text. All of it is selectable, so a compliance list is a matter of copy and paste. **Homepage** and **License** link out to the project itself. - -Two labels carry meaning: - -- **Modified** marks a library TablePro patches before building. The patches live in `scripts/patches/` in the public repository, next to the build script that pins the version. FreeTDS is patched for federated authentication and libssh2 for backported security fixes; nothing else is. -- **License Not Yet Confirmed** is a separate section for libraries whose own project publishes no license file. TablePro cannot state one for them, and lists them anyway so the gap stays visible. - -TablePro's own source is available under the AGPL v3 at [github.com/TableProApp/TablePro](https://github.com/TableProApp/TablePro). If a library is missing, credited wrongly, or attributed to the wrong license, [open an issue](https://github.com/TableProApp/TablePro/issues). diff --git a/docs/features/agent-mode.mdx b/docs/features/agent-mode.mdx deleted file mode 100644 index ba43845321..0000000000 --- a/docs/features/agent-mode.mdx +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: Agent mode -description: "Give one AI session the whole connection window: its sessions, its conversation, and the SQL it proposed" ---- - -The chat in the [inspector](/features/ai-assistant#chat) is for a question. Agent mode is for a job. Press `Cmd+Option+Shift+A`, or pick **Agent** under **View > Mode** or under **Mode** in the toolbar's **Actions** menu, and the window's three columns become the sessions on this connection, the conversation, and what that session proposed and ran. - -Nothing closes. The object browser and the editor tabs are where you left them, and **Browse** in the same place puts them back: the same tabs, the same scroll position and selection in the grid, the same undo stack and find panel in the editor, and a half-typed Create Table still half-typed. - -## The three columns - -| Column | Holds | -|--------|-------| -| Sessions | One row per session on this connection, each naming what it is doing | -| Conversation | The same chat as the inspector's, at a reading measure in the middle of the window | -| Result | What this session proposed, ran and changed | - -The choice is per connection, so one connection sits in Agent mode while another in the same window stays on a table. Drag either divider to resize; the widths are the ones the window already had. - -While the mode is on, the window names the session it is drawing, or **Agent** until the session has a name, and carries no file icon in its titlebar. The editor tab strip goes with the tabs it lists, and the sidebar's **Tables** and **Favorites** chooser and filter field go with the object list: the session rail takes their height. - -A line above the transcript names the connection, the session, and what is holding [Safe Mode](/features/safe-mode) at the level it is on. Point at that last part for the full sentence. - -## Writes wait for you - -Agent mode holds the connection at Safe Mode **Alert** for as long as the mode is on, whatever level the connection itself is set to. Every `INSERT`, `UPDATE` and `DELETE` the assistant proposes waits on its card for **Run** or **Reject**. - -Nothing below **Alert** is offered while the mode is on: the padlock and **Database > Safe Mode Level** list the levels at or above it and print the reason underneath, and the padlock's tooltip carries the same sentence. A connection already at Alert or stricter keeps its own level, and **Read-Only** stays read-only. Leaving Agent mode hands back the level you set, and nothing is written to the connection. - -Each card names the statement and the connection it will run against, and shows the database when the assistant named one. The full request is one click away under the tool name. - - -The floor is a habit, not a lock. It is one keystroke from off, so treat it as a reminder that a write is about to happen rather than as protection against one. What actually holds the assistant is the connection's AI Policy and Safe Mode, which apply in both modes. - - -`DROP`, `TRUNCATE` and `ALTER…DROP` are unchanged: each one is confirmed on its own every time, and no standing grant covers the next. - -## Sessions - -A session belongs to one connection and holds one conversation. Switching to Agent mode opens the first. The list runs newest first by when each session last did something, and the one the window is drawing carries a checkmark. - -| State | Meaning | -|-------|---------| -| **Working** | Streaming a reply, or running a tool call | -| **Waiting on you** | Holding a statement that needs an answer | -| **Queued** | Another session is streaming on the same provider | -| **Ready** | Waiting for your next message | -| **Stopped** | Its window closed. The transcript is intact | -| **Failed** | The provider returned an error, or the app quit mid-reply | - -Clicking a row highlights it and nothing more. To open one, double-click it, press `Return` on it, or choose **Open Session** from its context menu. **+** and **-** at the foot of the column start a session and delete one. - -**Close Session** ends a session and keeps its transcript in the list, to be opened again later. It asks first only when the session is working or waiting on your answer, and says which. **Delete Session…** throws the session and its conversation away, always asks, and names what it is about to stop; the **-** button, the context menu and the `Delete` key all reach it. - -Close or delete the session the window is drawing and the most recent live session takes its place. With none left, the two columns read **No Session Open** and offer **New Session**. - -Closing a window stops that connection's sessions and keeps what they said. Open one again and it continues. Nothing is replayed: a statement that was waiting for an answer when the window closed was rejected by the stop, so the assistant is asked again rather than the call being re-issued. - -Sessions stay on this Mac. They are not part of [iCloud sync](/features/icloud-sync). - -### From the menu bar - -**File > Session** holds the same commands, so a session is reachable with the rail collapsed: - -| Item | Acts on | -|---|---| -| **New Session** | This connection | -| **Open Session** | The session the rail has highlighted | -| **Recent Sessions** | Any session on this connection, the one on screen ticked | -| **Close Session**, **Delete Session…** | The session the rail has highlighted | -| **New Conversation**, **Conversation History**, **Clear Recents…** | The conversation, in either mode | - -New Session, Open Session, Close Session, Delete Session and New Conversation ship with no shortcut. Bind one in **Settings > Keyboard**, under **Navigation**. - -## The result pane - -Two views of one session, chosen from the menu at the trailing end of the column's header. The choice belongs to the session, so switching sessions does not inherit the other one's view. - -| View | Shows | -|------|-------| -| **SQL** | Every statement the session proposed, in order, with what became of it | -| **Results** | The rows it read, in the [data grid](/features/data-grid) | - -**Results** is the data grid, so sorting, column widths, selection and `Cmd+C` work the way they do in a query tab, and values use the Data Grid font. A query the session ran more than once has a picker above the rows. - -A run with no rows to draw says which of three things happened: **No Rows** for a query that matched nothing, **Statement Completed** for a write, with the number of rows it changed, and **Can't Show This Result** for a reply the grid cannot read, which the conversation still has in full. - -`Cmd+Option+I` opens and closes this column, and the View menu's item reads **Show Result** while the mode is on. The column itself reads **No Session Open** until a session starts, and **Not Connected** while the connection is down. - -## When the connection drops - -The conversation column is replaced by the same unavailable screen browsing shows: the error itself, **Manage Connections…**, and **Reconnect** for a connection that dropped or **Try Again** for one that failed. Reconnecting puts the same session and the same transcript back. - -## What Agent mode dims - -The commands that act on the browse content have nothing to act on while the conversation fills the detail column, so the menu bar dims them: **Refresh**, **Save**, **Add Row**, **Restore Previous Values…**, **Preview SQL**, **Show Results**, **Show Query History**, **New Tab**, **Open Quickly…**, **Export Tables…**, **Import Data…**, **Server Dashboard**, **Back** and **Forward**. - -The [toolbar](/features/connection-window#what-each-context-leaves-out) drops Refresh and the commit control outright, and dims any of the rest you added to it. - -Everything that acts on the window or the session stays live: **Switch Connection…**, **Close Connection**, **Safe Mode Level**, **Mode**, and every command under **File > Session**. - -## Starting from the welcome window - -Right-click a connection in the welcome window and choose **Open in Agent Mode**. The connection opens with its window already in Agent mode, and a connection that is already open switches where it stands rather than opening a second time. - -The composer stays live while a connection is still being made, so a question typed during the connect is sent once the session can take it. - -## Related - -- [AI Assistant](/features/ai-assistant) for providers, tool calling and the inspector chat -- [Connection window](/features/connection-window) for the toolbar and the trailing pane in both modes -- [Safe Mode](/features/safe-mode) for the levels and what each one gates -- [Keyboard shortcuts](/features/keyboard-shortcuts) diff --git a/docs/features/ai-assistant.mdx b/docs/features/ai-assistant.mdx deleted file mode 100644 index 7010f8307d..0000000000 --- a/docs/features/ai-assistant.mdx +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: AI Assistant -description: "Built-in AI for SQL: chat with tool calling, inline suggestions, and review, explain, optimize, and fix-error across 15 providers" ---- - -One provider is active at a time and answers every AI request in the app, from the chat panel to the ghost text in the editor. Switch it and all of them switch with it. Keys are yours and live in the macOS Keychain, so every request bills to your own account with that provider. - -## Configure a provider - -Open **Settings > AI** (`Cmd+,`). **Enable AI Features** at the top gates the whole tab; turning it off also hides the per-connection AI policy in the connection form. - - - AI settings - AI settings - - - - - Click **Add Provider…** and pick a type. **Add Custom Provider…** at the bottom takes any OpenAI-compatible endpoint. - - - Paste the API base from the server's own documentation, version segment included: `https://api.openai.com/v1`, `https://api.z.ai/api/paas/v4`, `http://localhost:1234/v1`. Under the field is the URL a request resolves to. A full chat completions URL works as well and is used as it stands. - - - Paste an API key, or sign in for GitHub Copilot, ChatGPT, Cursor, and xAI. Leave the key empty for a Custom provider whose server checks none, such as a self-hosted vLLM or LM Studio with authentication off. - - - Type a model name or pick one from the fetched list, then click **Test Connection**. A provider that lists several models with no default starts on **Other…** with an empty **Model ID**, so pick one before saving. - - - -The **Active Provider** picker decides which one handles requests. Setting it to **None** leaves any open conversation readable but read-only, with a **Settings…** button under it. Where a model supports reasoning effort, its detail sheet adds a picker for it and replies render their thinking in a collapsible **Reasoning** block. Gemini and Ollama have no picker, and on a router the picker follows the model: it is gone for a model the router lists as non-reasoning. - -With no preference, start with Claude or OpenAI on an API key: both take a key, tools, and images with no further setup. The others each come with something to know. - -| Provider | Sign-in | Worth knowing | -|---|---|---| -| GitHub Copilot | GitHub device flow | Runs its own tool loop, so the tool call limit never applies | -| ChatGPT | Your ChatGPT account, on the Codex quota from Plus, Pro, Business, and Enterprise plans. **Import from Codex CLI** reuses an existing login | Unofficial interface, may change | -| Cursor | API key, or **Sign in with Cursor** with the Cursor CLI installed | Runs as an agent, not a chat-completions endpoint, so it cannot call TablePro's tools: Edit and Agent modes run no queries through it | -| xAI | API key, or **Sign in with xAI** on a SuperGrok or X Premium+ subscription | Sign-in opens a Grok Build consent screen and is unofficial. Presets: Grok 4.5, Grok 4.3 | -| Requesty | API key | An OpenAI-compatible router. The Base URL is filled in; set it to `https://router.eu.requesty.ai` for EU routing | -| Ollama, llama.cpp, MLX | None. Start the server first | Presets fill in `http://localhost:11434` for Ollama and `http://localhost:8080` for `llama-server` and `mlx_lm.server`. Start `llama-server` with `--jinja` for tool calling | -| Claude Agent | Claude Code's `claude` command, no key | Bills against a Claude subscription, with tradeoffs | - -### Claude Agent - -Install Claude Code, run `claude /login` in Terminal, then pick Opus, Sonnet, or Haiku. Replies bill against your Claude subscription instead of an API key. The provider answers from your schema when the MCP server is on in **Settings > MCP**, and falls back to plain chat when it is off. - -The `claude` tool runs headless here. Claude Code is built for you to use directly rather than as a backend for other apps, so this path can break with any Claude Code release. Your subscription is governed by Anthropic's [Consumer Terms](https://www.anthropic.com/legal/consumer-terms) and [Usage Policy](https://www.anthropic.com/legal/aup), which TablePro cannot grant rights under. Replies also count against the limits you share with claude.ai and your own Claude Code sessions, so running out here stops your work there. For a metered path on commercial terms, add the **Claude** provider with an API key. - -## Chat - -Press `Cmd+Option+A`, or choose **View > Show Assistant**, to open the assistant in the window's [trailing pane](/features/connection-window#the-trailing-pane). It shares that pane with the inspector, and the two-segment picker at the leading end of the pane's header moves between them. Over an open inspector the shortcut swaps rather than closes; over an open assistant it closes the pane. - -Whichever segment you pick is remembered for that connection, and only a segment you pick counts: opening the pane for a row you clicked, with **Auto-show inspector on row select** on, leaves a pane you left on the assistant alone. The assistant has no toolbar button of its own: the pane toggle opens the column and the picker chooses what it draws. - -With AI features off in **Settings > AI**, the command is dimmed and the picker is gone, so the pane holds the inspector alone. In [Agent mode](/features/agent-mode) the command is dimmed too, because the conversation is the window's content column there; **View > Focus > Focus Assistant** (`Ctrl+Cmd+Option+A`) puts the insertion point in its composer. - - - Assistant pane holding a chat thread beside a data grid - Assistant pane holding a chat thread beside a data grid - - -Type a question and press Return. Code blocks carry **Copy** and **Insert**, and Insert fills the current query tab when it is empty and opens a new one otherwise. A token count sits under each response, a failed one offers **Retry**, a finished one **Regenerate**, and **Stop Generating** cancels a reply mid-stream. - -Conversations save themselves and take their title from your first message. The ellipsis menu at the trailing end of the pane's header holds **New Conversation**, **Conversation History** with the current conversation ticked, and **Clear Recents**, which asks before it deletes every one of them. **File > Session** carries the same three, so they work with the pane closed and in [Agent mode](/features/agent-mode). Editing a message you already sent puts its text and attachments back in the composer and drops that turn and everything after it. - -Paste or drag images into the composer on any provider that takes them, which is all of them except GitHub Copilot, Cursor, ChatGPT, and Claude Agent. On OpenRouter and Requesty the model decides: one the router lists as text-only takes no images. A drop that carries files the composer cannot read attaches the rest and says how many it kept. - -The focused composer carries a colored highlight. Right-click it and turn **Highlight When Focused** off for the standard macOS focus ring instead. The choice is app-wide and survives restarts. Reduce Transparency and Increase Contrast, both under **System Settings > Accessibility > Display**, also replace the highlight with the focus ring. - -### Chat modes - -The mode picker in the composer footer controls which tools the AI can call. It is an app-level setting that survives restarts, and a fresh install starts in **Ask**. - -| Mode | Tools available | When to use | -|------|----------------|-------------| -| **Ask** | Read-only schema lookups: `list_connections`, `get_connection_status`, `list_databases`, `list_schemas`, `list_tables`, `describe_table`, `get_table_ddl`. | Questions, exploration, drafting queries you run yourself. | -| **Edit** | All Ask tools, plus `execute_query` for `SELECT`, `INSERT`, `UPDATE`, `DELETE`. Destructive DDL (`DROP`, `TRUNCATE`, `ALTER…DROP`) stays blocked. | Letting the AI run the queries it proposes. | -| **Agent** | All tools, plus `confirm_destructive_operation` for destructive DDL. Runs tools in a loop. | Multi-step migrations and schema changes. | - -Mode and [Safe Mode](/features/safe-mode) are independent gates. Agent mode does not bypass Safe Mode. - -### Tool calling - -In Edit and Agent modes each tool call appears as a card in the reply. Read-only tools run immediately. Write tools wait for **Run**, for **Always for this connection** (which whitelists that one tool on that one connection and syncs with it over iCloud), or for **Cancel**, which hands the model an error result and lets it carry on. - - - Per-card tool approval - Per-card tool approval - - -Safe Mode **Silent** auto-approves write tools and **Read-Only** auto-denies them. Destructive operations are the exception: `confirm_destructive_operation` always needs a click, Silent does not auto-approve it, **Always for this connection** is refused for it, and the model must pass the verbatim phrase `I understand this is irreversible`. - -Every provider except Cursor can call tools. Claude Agent reaches them through the MCP server, so that has to be on, and local models depend on the model. - -A reply pauses after 25 tool calls, keeps everything it has done, and offers **Continue** for a fresh budget or **Adjust Limit** to change the number, 5 to 200, under **Agent** in **Settings > AI**. Every call is another request with the schema attached, so a higher limit costs tokens. - -### Attach context with `@` - -Type `@` in the composer to open a picker at the caret: **Schema**, a specific **Table**, **Current Query**, **Query Results**, or a **Saved Query** ([favorites](/features/favorites)). Return or Tab inserts, Escape dismisses. The `@` button in the footer offers the same list minus saved queries. A saved query chip resolves at send time, so the AI sees its current SQL. - -### Slash commands - -Type `/` for **`/review`**, **`/explain`**, **`/optimize`**, and **`/fix`**, or **`/help`** for the list. The first three act on the text typed after the command, or on the whole query tab when there is none, and attach the same table structure as the menu actions. **`/fix`** sends the tab's last failed statement with its error. Add your own under **Settings > AI > Custom Slash Commands**: a template substitutes `{{query}}`, `{{schema}}`, `{{database}}`, and `{{body}}`, the text typed after the command, at send time. A custom command with a built-in's name runs only from the command menu. - -### Model picker - -The cpu icon beside the mode picker lists every configured provider and its models. A pick there overrides the active provider for this chat panel until you change it, across turns. - -### The whole window - -A long job reads badly in a 270pt pane. [Agent mode](/features/agent-mode) (`Cmd+Option+Shift+A`) gives the same session the whole window: the sessions on this connection, the conversation, and a pane holding the SQL it proposed and the rows it read. It is one conversation shown two ways, so switching does not start another. - -## Review, explain, optimize, and fix - -Put the cursor in a statement, or select the part you want, then pick an action. Each one sends what **Run** would run, never the whole tab, and opens a new conversation. While the assistant is still replying or waiting on your approval, the action opens a new session instead, and the busy one keeps running under **File > Session > Recent Sessions**. - -| Action | Where to find it | What comes back | -|--------|------------------|-----------------| -| **Review with AI** | **Review** in the query bar, **Query > Review with AI** (`Cmd+Option+Shift+L`), the editor's context menu | Findings ranked by severity, each with its fix | -| **Explain with AI** | **Query > Explain with AI** (`Cmd+L`), the context menu, the menu beside **Review** | What the statement does, step by step | -| **Optimize with AI** | **Query > Optimize with AI** (`Cmd+Option+L`), the context menu, the menu beside **Review** | A rewrite or an index to add, with the reason | -| **Fix with AI** | The error banner after a query fails | The failed statement, corrected against its error | - -Start with **Review with AI** when you are unsure what to ask: it checks correctness, cost and safety in one pass, and says so when the statement is fine. - - - Query bar with the Review button, and the assistant answering a review of a JOIN - Query bar with the Review button, and the assistant answering a review of a JOIN - - -The request carries the structure of the tables the statement names, read from the tab's own database and schema: columns and types, keys, indexes with their predicates, foreign keys, and estimated row counts. Up to 12 tables go, in the order the statement names them. The chip above your message lists them; hover it to see any table that could not be read or was not found. If the tab already shows an EXPLAIN plan for the same statement, the plan goes too. Building this reads only the database's catalog: your statement never runs, and no rows are sent. Turn off **Include database schema** in **Settings > AI** to send the statement alone. - -With no provider set up the actions are dimmed. With AI turned off, or on a connection whose AI policy is **Never**, they are hidden. - -The reply comes back as a walkthrough. Optimize and Fix diff your statement against the rewritten one, switchable between **Unified** and **Split**; Review and Explain anchor their steps to the statement as it stands. Each numbered step carries a one-line reason and a tag (**Critical**, **Change**, or **Context**), expands to the lines it refers to, and offers **Jump to lines** and **Ask about this change**, which keeps a follow-up anchored to that step. **Apply to Editor** replaces the statement you sent after you confirm. If you edited that statement since, the suggestion opens as a new query instead. A model that answers in prose renders as a normal message. - -## Per-connection AI rules - -Pin plain-text context to a connection so the AI sees it every turn: open the connection's edit form, select **Options**, and fill in **AI Rules**. Use it for what the schema cannot show, like `Always filter orders by deleted_at IS NULL` or `Never select users.ssn`. The text joins the system prompt and syncs with the connection over iCloud. - -## Inline suggestions - -Turn on **Enable inline suggestions while typing** in the AI tab; it is off by default. Pause typing and the active provider suggests a completion as ghost text at the cursor. `Tab` accepts, `Escape` dismisses, and typing anything else dismisses it too. - -The pause is the **Debounce** setting beside the toggle: 500 ms by default, adjustable from 100 to 3,000 ms in 50 ms steps. With Copilot active the suggestions come from Copilot's inline-completion model; every other provider serves them through chat completions. - -Suggestions follow the connection's AI policy, read again at every pause, so a policy you change in the connection form applies from the next one. **Always Allow** suggests right away. On **Ask Each Time**, suggestions start once you click **Allow** in the chat's **Allow AI Access** prompt for that connection, and stop when the connection disconnects. A **Never** connection gets no suggestions, and with Copilot active its editor text is not sent to Copilot either. - -## What leaves your Mac - -Every message carries a system prompt built from **Settings > AI > Context**, so these settings decide what the provider sees on every turn, not only the first. - -| Setting | Default | What it sends | -|---------|---------|---------------| -| Include database schema | On | Columns and foreign keys for up to **Max schema tables** tables | -| Include current query | On | The editor's text, truncated at 2,000 characters | -| Include query results | Off | The rows in the results grid | -| Max schema tables | 20 | | - -Ollama, llama.cpp, and MLX run on your own machine, so nothing reaches a third party. Every other provider is a network call to that vendor under the terms of your account with them. - -Per connection, **AI Policy** under **Access** on the connection form's **Options** tab takes **Use Default**, **Always Allow**, **Ask Each Time**, or **Never**. A change applies to an open connection from the next message or tool call. The app-wide default is **Ask Each Time**: the chat asks before its first message on a connection, and one **Allow** covers every chat and the inline suggestions on that connection until it disconnects. **Never** also blocks external AI tool calls against that connection. External clients such as Raycast and Claude Desktop reach the same tools through the [MCP server](/features/mcp), bounded by the connection's **External Clients** level and the token's scope. diff --git a/docs/features/autocomplete.mdx b/docs/features/autocomplete.mdx deleted file mode 100644 index cda78bf67a..0000000000 --- a/docs/features/autocomplete.mdx +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: Autocomplete -description: Schema-aware SQL autocomplete for keywords, tables, columns, and functions ---- - -An alias and a dot give you that table's columns, and that holds for a subquery or a `WITH` table that exists only in the statement you are writing. Everything the popup offers is decided by where the cursor sits. - -It opens itself with nothing typed after FROM, JOIN, INTO, ON, SET, USING, RETURNING, a `::` cast, an INSERT or ALTER column list, and the first item after SELECT. Where a full list would be noise, such as in WHERE or inside a function call, type a character or press `Ctrl+Space`. - - - Autocomplete - Autocomplete - - -| Key | Action | -|-----|--------| -| `Ctrl+Space` | Open the popup anywhere, even with an empty prefix | -| `Up` `Down` | Move through suggestions | -| `Return` / `Tab` | Accept the selected suggestion | -| `Escape` | Dismiss and keep typing | - -Autocomplete is always on and there is nothing to turn off. What the popup offers depends entirely on where the cursor sits; the one setting is the case keywords come back in. - -## SQL keywords - -```sql -SEL| -- SELECT -FROM users WH| -- WHERE -``` - -## Keyword case - -Keywords and built-in functions come back in the case you are typing: - -```sql -sel| -- select -SEL| -- SELECT -cou| -- count() -COU| -- COUNT() -``` - -The first letter decides, so `Sel` completes to `SELECT` and `sElE` to `select`. A phrase moves as one: `group b` completes to `group by`. Table, column, schema and alias names are never re-cased, and neither are saved [favorite keywords](#favorite-keywords) or the values of an enum column. - -**Settings > Editor > Keyword case** holds four values: - -| Value | Typing `sel` | Nothing typed | -|-------|--------------|---------------| -| Match what I type, otherwise UPPERCASE | `select` | `SELECT` | -| Match what I type, otherwise lowercase | `select` | `select` | -| UPPERCASE | `SELECT` | `SELECT` | -| lowercase | `select` | `select` | - -The two absolute values also rewrite keywords as you type them and set the case [Format SQL](/features/sql-editor#formatting) produces. - -## Table names - -Tables appear after FROM, JOIN, INSERT INTO and similar keywords, and lead the list ahead of keywords there. The clause is read at the cursor, so a second or third JOIN still offers tables. - -```sql -SELECT * FROM | -- All tables -SELECT * FROM us| -- Tables starting with "us": users, user_roles -SELECT * FROM a JOIN b ON a.id = b.id JOIN | -- All tables, even after an ON condition -``` - -## Column names - -A FROM clause anywhere in the statement, even after the cursor, decides which tables columns come from. With no FROM clause yet every cached table contributes, and ambiguous names are qualified: `users.id`, `orders.id`. - -```sql -SELECT na| -- Columns matching "na" from all cached tables -SELECT | FROM users -- Columns from users -SELECT u.| FROM users u -- Columns from users via alias -SELECT * FROM users WHERE | -- Ctrl+Space for columns from users -``` - -## Aliases, subqueries and CTEs - -An alias followed by `.` gives that table's columns. A derived table or a `WITH` table gives the columns its own SELECT list produces: - -```sql -SELECT ahs.| -- country, avg_score -FROM happiness_scores hs -LEFT JOIN ( - SELECT country, AVG(score) AS avg_score - FROM happiness_scores - GROUP BY country -) ahs ON hs.country = ahs.country - -WITH totals AS ( - SELECT region, SUM(amount) AS total FROM sales GROUP BY region -) -SELECT t.| FROM totals t -- region, total -``` - -`AS name`, bare names and qualified `t.col` all resolve. A `SELECT *` subquery and an unaliased `AVG(score)` have no name to offer, so they are skipped. - -## Functions and operators - -Functions appear in SELECT, WHERE and expression contexts, the connection's own dialect alongside the common SQL ones: `CONVERT_TIMEZONE` on Snowflake, `SAFE_CAST` on BigQuery. - -```sql -SELECT COUNT(| -- Ctrl+Space for columns and * -WHERE date_column > | -- Ctrl+Space for NOW(), CURRENT_DATE, … -``` - -Operators the dialect declares appear in WHERE, ON, HAVING and AND, each with what it does and the types it takes. On PostgreSQL that covers JSON, array and range containment, regex, full-text search and the network operators: - -```sql -WHERE data -| -- -> (field as json), ->> (field as text) -WHERE payload @| -- @> contains, @? JSON path returns any item -WHERE tags &| -- && arrays overlap -WHERE email ~| -- ~ POSIX regex, ~* case insensitive -``` - -An operator restricted to `jsonb` is held back on a `json` column rather than offered and rejected by the server. - -## Casts and enum values - -Typing `::` offers the dialect's type names in the spelling you write, not the internal catalog name. Comparing against a column whose type declares a fixed set of values, a PostgreSQL enum for instance, offers those values quoted: - -```sql -SELECT id::| -- integer, bigint, text, timestamptz, jsonb, uuid, … -SELECT payload::js| -- json, jsonb, jsonpath -WHERE status = | -- Ctrl+Space for 'pending', 'active', 'archived' -WHERE mood <> 'ha| -- 'happy' -``` - -## Schema names - -On multi-schema engines, schemas complete in FROM and `public.users` resolves in FROM, JOIN, UPDATE, INSERT INTO and CREATE INDEX. - -A bare table name completes with the tables the server finds without a schema: those in the tab's own schema, or on Spanner those in the default schema. For a table anywhere else, type its schema and a dot. That schema's tables follow, including a schema the sidebar has never opened: - -```sql -SELECT * FROM times| -- tables in the tab's own schema -SELECT * FROM attendance.| -- tables in attendance -``` - -Where the hierarchy is database, schema, table (Snowflake, BigQuery), every segment completes: - -```sql -SELECT * FROM ANALYTICS_| -- databases -SELECT * FROM ANALYTICS_PROD.| -- schemas -SELECT * FROM ANALYTICS_PROD.DBT_MARTS.| -- tables in that schema -SELECT * FROM ANALYTICS_PROD.DBT_MARTS.ORDERS o WHERE o.| -- columns -``` - -## Which database a tab completes against - -Every query tab carries its own database and schema, named in the picker at the top of the editor. Suggestions come from there, not from whatever the object browser is pointing at, so selecting a different database in the sidebar leaves open tabs alone. - -Two tabs on one connection can sit on different databases and complete against each. Change a tab's database in the picker and its suggestions follow on the next keystroke. - -On engines with schemas under a database, the picker names both, as `analytics · public`. - -## Favorite keywords - -A favorite with a keyword, stored in the database or set by `@keyword` frontmatter in a linked file, appears as a top-priority match. Type the keyword, accept it, and the favorite's full SQL replaces it inline. A `;;` in that SQL says where the cursor lands afterwards. See [Favorites](/features/favorites#cursor-placement). - -## MongoDB - -MongoDB connections complete MQL, so the popup follows the shape of the query rather than SQL clauses: - -```js -db.| // collections, plus getCollectionNames(), createCollection(), … -db.users.| // find(), aggregate(), updateOne(), insertMany(), … -db.users.find({ | // field names, then $eq, $gt, $in, $exists, $regex, … -db.users.find({}, { | // field names, then $slice, $elemMatch, $meta -db.users.updateOne({}, { | // $set, $unset, $inc, $push, $addToSet, … -db.users.updateOne({}, [{ | // the six stages an update pipeline allows -db.orders.aggregate([{ | // $match, $group, $lookup, $unwind, $facet, … -db.orders.aggregate([{ $group: { | // $sum, $avg, $first, $push, and the expression operators -``` - -`$set` and `$unset` mean different things as an update operator and as a pipeline stage; the description shown is whichever applies at the cursor. - -Field names come from a sample of the collection's documents, nested paths included, so `address: { city, zip }` offers `address`, `address.city` and `address.zip`. Objects inside an array contribute paths too, and shallower fields sort first. The sample is cached per collection and cleared when you switch database or refresh the connection. - -Comments suppress completion, and a brace or bracket inside a string literal does not open a document. - -## My new table does not complete - -Table names are cached when a connection opens, and column names the first time a query tab on that database is on screen. Moving between connections in the window keeps the cache. A change made from TablePro reloads it on its own: a statement run in the SQL editor, a save from the sidebar, an import, a restore, Copy Objects, Compare & Sync, and statements run through MCP or the AI assistant. A migration or a `CREATE TABLE` run from another client stays invisible until the cache reloads. Press `Cmd+R` (**Database > Refresh**), or right-click the sidebar's Tables header and choose **Refresh**. Switching databases reloads it too. - -## Large files - -Completion works on files of any size, multi-megabyte dumps included. Past 500 KB only a 10 KB window around the cursor is read, so suggestions follow the statements near you rather than the whole file. diff --git a/docs/features/backup-restore.mdx b/docs/features/backup-restore.mdx deleted file mode 100644 index c1eba84bcb..0000000000 --- a/docs/features/backup-restore.mdx +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Backup & Restore -description: Dump and restore with each engine's own tools, with scope, progress, cancel, and SSH tunnel reuse ---- - -Most engines here need a command line tool that does not ship inside TablePro. Install the one for your engine first: - -| Engine | Tools | Install | Dump file | -|---|---|---|---| -| PostgreSQL, Redshift | `pg_dump`, `pg_restore` | `brew install libpq && brew link --force libpq` | `.dump`, custom archive | -| MySQL, MariaDB | `mysqldump`, `mysql` | `brew install mysql-client` | `.sql` | -| MongoDB | `mongodump`, `mongorestore` | `brew install mongodb-database-tools` | `.archive`, gzipped | -| SQLite, libSQL | `sqlite3` | `brew install sqlite` | `.sql` | -| SQL Server | `sqlpackage` | [Download from Microsoft](https://learn.microsoft.com/sql/tools/sqlpackage/sqlpackage-download) | `.bacpac` | -| DuckDB | none, the engine writes it | nothing to install | `.duckdb`, or a folder of Parquet | - -MariaDB 11.0 renamed its clients, so `mariadb-dump` and `mariadb` are accepted in place of `mysqldump` and `mysql`. - -The lookup takes the first match from `/usr/bin/which`, then `/opt/homebrew/bin`, `/usr/local/bin`, and Postgres.app's latest version. That path decides which client version does the work. - -On PostgreSQL that match is checked against the server first, for both directions. `pg_dump` and `pg_restore` refuse a server newer than themselves, 15 and later refuse servers older than 9.2, and TablePro names the database with `-d`, which arrived in 9.3. When the first match cannot reach the server, the newest one that can is taken from the Homebrew `postgresql@…` and `libpq` installs, every Postgres.app version, and `/Library/PostgreSQL`. When none can, the run stops before it starts and names the version to install: PostgreSQL 9.1 needs `pg_dump` 9.3 to 14, so `brew install postgresql@14`. A binary that does not answer `--version` is used anyway. - -DuckDB is the exception: its dump is a statement the engine already in front of TablePro runs, over the connection that is already open. - -**File > Backup Dump…** is dimmed on an engine that is not in the table, and on a libSQL connection pointed at a Turso URL rather than a local file, which `sqlite3` cannot reach. For those, use a SQL export from [Import & Export](/features/import-export). - - -Restore is disabled on a read-only [Safe Mode](/features/safe-mode) connection. Backup is not, because it writes nothing to the database. - - - - Backup Dump sheet - Backup Dump sheet - - -## Backup dump - - - - Choose **File > Backup Dump…** on a connected session, or right-click one or more databases in the connection tree and choose **Back Up…**. The tree's selection arrives ticked. - - - The database being browsed starts ticked and whole. Tick more databases to write more files. Open a database's disclosure triangle to reach its tables, all of them ticked, and untick the ones to leave out. Partitioned tables and MariaDB sequences are listed there beside the plain tables. - - - Only DuckDB offers a choice of format. Every file lands in the folder named under **Save to**, called `-` with the engine's extension. - - - **Cancel** confirms, stops the run, and removes the partial file. Databases already written are kept. The result sheet lists every database with its file and size, and **Show in Finder** selects them. - - The bar shows a percentage on PostgreSQL and MySQL, which answer a database size query cheaply. The rest show an indeterminate bar with a running byte count. - - - -## What goes in - -Ticking a whole database dumps everything in it: tables, views, routines, triggers and sequences, whatever the engine's tool carries. Unticking even one table narrows the dump to the ticked ones, and every engine narrows differently. - -| Engine | Narrows to | What a narrowed dump leaves out | -|---|---|---| -| PostgreSQL, Redshift | tables, plus each partitioned table's partitions | Sequences, types, schemas, and tables the selection references. The dump will not restore on its own | -| MySQL, MariaDB | tables | Views, and tables the selection references. It restores, with those references dangling | -| MongoDB | collections | Nothing | -| SQLite, libSQL | tables, plus each one's indexes and triggers | Nothing | -| SQL Server | table data | Nothing. The `.bacpac` always carries the whole schema, and a table the selection references by foreign key has to be ticked too | -| DuckDB | nothing, the picker is off | The whole database goes in every time | - -The sheet repeats the relevant row under the tree as soon as a selection is narrowed. Leave every table ticked to get the whole database, which is what the sheet starts on. - -## Backing up several databases - -Tick more than one database and the run writes one file per database into the chosen folder. Two databases whose names differ only by a character a file name cannot hold, such as `a/b` and `a:b`, get a numbered suffix rather than one overwriting the other. - -Each database runs on its own. A failure stops that one and the run carries on, and the result sheet says which database failed and why alongside the ones that worked. - -## Restore dump - - - - Choose **File > Restore Dump…** and select a file the matching backup tool wrote. The panel names the formats this connection's engine accepts. - - - Engines with more than one database ask which. SQLite and DuckDB reach exactly one, so they skip straight to the confirmation. - - `pg_restore` runs with `--no-owner --no-acl`, so the connection user ends up owning the restored objects. `mongorestore` is scoped with `--nsInclude` to the database picked. - - - The dump is replayed into a database that already has contents. Objects it names are overwritten. - - - Restore shows no percentage. **Cancel** confirms and stops the run, and the target database is left as it stands: drop it and restore into a fresh one, or clean up the partial objects yourself. - - - -No `--clean` is passed, so restoring on top of a schema that already holds conflicting objects produces errors instead of replacing them. A DuckDB restore behaves the same way, and reports the first object whose name is already taken. - -Restore covers the whole file. There is no table picker on the way back in. - -## What a dump carries - -`mysqldump` runs with `--single-transaction --routines --triggers --events`, so a MySQL dump is consistent with itself and carries stored routines, triggers and scheduled events. `pg_dump -Fc` carries the whole database. `sqlite3 .dump` writes the schema and every row as SQL. - -DuckDB writes one of two things. A `.duckdb` file carries tables, views, indexes, foreign keys and sequences, and only DuckDB reads it, which is also true of `pg_dump`'s archive and MongoDB's. A Parquet folder holds `schema.sql`, `load.sql` and one Parquet file per table, which pandas, Spark and BigQuery read directly. Pick the file to restore into TablePro later, the folder to hand the data to something else. - -## SSH tunnels and SSL - -Both flows reuse the connection's active SSH tunnel, with no second port forward. The whole of the connection's SSL configuration reaches the tool, the CA certificate and the client certificate and key included: PostgreSQL takes them in `PGSSLMODE`, `PGSSLROOTCERT`, `PGSSLCERT` and `PGSSLKEY`, and MySQL and MariaDB in their client's own flags. SQLite and DuckDB open a file, so neither applies. - -MySQL's clients and MariaDB's no longer take the same flags. MySQL's take `--ssl-mode`, with `VERIFY_CA` and `VERIFY_IDENTITY` included. MariaDB's take `--ssl`, `--skip-ssl` and `--ssl-verify-server-cert`, and reject `--ssl-mode`. Which family a tool belongs to comes from its `--version` rather than its name: Homebrew's `mariadb` formula installs MariaDB's dump tool as `mysqldump`. A tool that answers no version is used for **Disabled** and **Preferred**, and refused for **Required**, **Verify CA** and **Verify Identity**. - -MariaDB's clients have no flag for encrypting without verifying, so **Required** reaches them as `--ssl --ssl-verify-server-cert`. That refuses a server offering no TLS. With no CA certificate set it checks the encryption rather than the server's identity. - -## Server-side export - -Oracle, Snowflake and BigQuery do not have a client-side dump. They unload to somewhere the server can write: a `DIRECTORY` object, a stage, a Cloud Storage bucket. Choose **File > Server-Side Export…** for those. - -Pick a table and name the destination. Oracle takes the name of a `DIRECTORY` object rather than a path, because the path belongs to the server. Snowflake takes a stage, with or without its `@`. BigQuery takes a `gs://` prefix and shards its output under it. - -The statement runs on your own connection, so it carries your privileges and the server's own error comes back when the destination is not writable. - -Snowflake and BigQuery block until the unload finishes, so the file exists by the time the sheet says so. **Stop** asks the server to cancel one and stops waiting either way. - -Oracle is different. The Data Pump block starts a job and detaches, so the statement returns before anything is written and the sheet says the job was started rather than finished. Watch `DBA_DATAPUMP_JOBS` for its progress, and stop it there. - - -Nothing lands on your Mac. The result is on the server or in the bucket, and the sheet says where it went. - - -## Passwords - -Your password never reaches the tool's argument list, which every process on the machine can read through `ps`. PostgreSQL gets `PGPASSWORD` and MySQL gets `MYSQL_PWD`, both in the environment. MongoDB's tools read neither, so TablePro writes a `0600` config file and deletes it when the process exits. DuckDB is asked for nothing: it is already connected. - - -SqlPackage is the exception. It accepts a password only inside its connection string, which means the command line, where other processes on the Mac can read it while the tool runs. A dump and a restore both ask before starting. Windows or Entra authentication avoids it entirely: leave the username empty and the tool uses integrated security. - - -## Failures - -A non-zero exit shows the last 64 KB of the tool's stderr in a scrollable monospaced view, and DuckDB's own error text in the same place. Four causes account for most of them. - -| What you see | What to do | -|---|---| -| *"… was not found on this system"* followed by an install command | Run that command so the binaries land on `PATH`. The message names the tool and the package for your engine | -| An authentication failure | The password goes through the environment or a config file rather than a prompt, so this is the account or the database. Check that the role can log in | -| *"could not tell whether … is MySQL's client or MariaDB's"* | The tool on `PATH` did not answer `--version`, and the two families take different SSL flags. Reinstall `mysql-client` or `mariadb` | -| Objects that conflict with the dump | Restore into a fresh database, or drop the conflicting objects first | -| *"a single transaction can only write to a single attached database"* | A DuckDB query tab has an open transaction that has already written. Commit or roll it back, then run the backup again | - -One non-zero exit is not a failure. A `pg_restore` newer than the server sets parameters the server does not have, such as `transaction_timeout` on anything before PostgreSQL 17, and exits 1 after restoring everything. When those are its only errors, the restore reports complete and names the settings the server skipped. Any other error in the same run still fails it. So does a server whose `lc_messages` is not English, because its errors cannot be read. diff --git a/docs/features/change-tracking.mdx b/docs/features/change-tracking.mdx deleted file mode 100644 index f5354b1815..0000000000 --- a/docs/features/change-tracking.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Change Tracking -description: Queue cell edits, row inserts and deletions, review the SQL, then save ---- - -import StagedUntilSave from "/snippets/staged-until-save.mdx"; - - - -Modified cells stay highlighted, `Cmd+Shift+P` shows the statements the queue will produce, and `Cmd+S` runs them. The queue belongs to the tab, so switching tabs leaves it alone. - -A queued change also marks the value itself, so it stays legible once the row is selected and reads aloud to VoiceOver. An edited value and a new row are underlined, and a row queued for deletion is struck through. - - - Pending edits, inserts, and deletions highlighted in the data grid - Pending edits, inserts, and deletions highlighted in the data grid - - -## Editing a cell - -Double-click a cell, or press `Enter` on it. `Enter` commits the edit to the queue, `Escape` cancels. Setting a value back to what it was drops it from the queue again. Some types open an editor of their own: - -| Column type | Editor | -|---|---| -| Date, time, datetime, timestamp | Calendar and time picker, or edit as text | -| Foreign key | Searchable list of referenced values | -| Boolean, `BIT`, `TINYINT(1)` | Checkbox, with a third state for nullable columns | -| `ENUM` | Searchable value list | -| `SET` (MySQL/MariaDB) | Multi-select checkboxes | -| Array of a simple type (PostgreSQL) | Ordered list, one row per element | -| `jsonb[]`, `json[]` (PostgreSQL) | Ordered list, the selected element in the [JSON viewer](/features/json-viewer) | -| `JSON`, `JSONB`, `BLOB`, binary | [Cell viewers](/features/json-viewer) | - - - Cell editor - Cell editor - - -Right-click an editable cell and open **Set Value** for the common ones without typing: **Empty**, **NULL** on a nullable column, **Default** on a column the server fills in, and date functions such as `NOW()` or `CURRENT_DATE` on date columns. - -On MongoDB a document can lack a field altogether, which the grid shows as **No Field**. **Remove Field** on the same right-click menu deletes the field rather than storing NULL. See [missing fields and null](/databases/mongodb#missing-fields-and-null). - -## Adding and deleting rows - -- **Add Row** (`Cmd+Shift+N`), the **+** button, or **Edit > Add Row**. The row appears at the bottom with the cursor in its first writable cell. Later edits fold into the INSERT rather than queueing as updates. -- **Duplicate Row** (`Cmd+Shift+D`) on the right-click menu copies a row and resets its primary key and its identity columns to `DEFAULT`, so the database assigns new ones. -- **Delete**: select rows by their row numbers (`Shift`-click for a range, `Cmd`-click for separate rows) and press `Delete`. They stay visible with a strikethrough, and saving asks "Delete 5 rows?" before they go. -- **Paste** on the right-click menu inserts copied rows as new rows. -- **Fill Column** on the header right-click menu writes one value into every loaded row. It is absent on primary key columns and on columns the server owns. - -A new row starts at `DEFAULT` in every column the server fills in. That covers a column with a default expression, and an identity or `AUTO_INCREMENT` column, which carries no default of its own. The INSERT leaves those columns out, so the server assigns them. - -Add Row and Duplicate Row stay dimmed until the table's schema has loaded, which is what says which columns the server fills in. On a table opened for the first time that is a fraction of a second. - -## When the grid will not edit - -A tab opened from the sidebar edits its table directly. Views, materialized views, sequences and external tables open read-only; to change what a view shows, edit the tables it reads from. - -A query tab edits only when the app can prove the rows came from exactly one table: - -| Query | Editable | -|---|---| -| `SELECT * FROM orders o WHERE o.id = 1` | Yes, alias or not | -| Comments, `DISTINCT`, `ORDER BY`, `LIMIT`, `FOR UPDATE` | Yes | -| `SELECT * FROM public.orders` on the schema you are browsing | Yes | -| The same query naming any other schema | No | -| Joins, comma joins, subqueries in `FROM`, CTEs, `UNION`, `EXCEPT`, `INTERSECT`, `FROM ONLY` | No | - -To change rows a join returned, open one of its tables from the sidebar and edit there. To edit through another schema, switch the session to that schema first. - -Two more shapes look editable and refuse. A column renamed with `AS` cannot be written back, though the rest of the row still can. A query that renames or omits the primary key blocks the save outright, because nothing is left to identify the row by. - -Individual columns stay read-only where the server owns them: generated, computed and Oracle virtual columns, which are computed on write and left out of every INSERT and UPDATE, SQL export included; `GENERATED ALWAYS AS IDENTITY` columns on PostgreSQL and Oracle, and SQL Server `IDENTITY` columns, which the server refuses an explicit value for; SQL Server `rowversion` and system-versioned period columns, which the server writes itself; and columns the driver marks immutable, such as MongoDB's `_id`. `GENERATED BY DEFAULT AS IDENTITY` and `AUTO_INCREMENT` columns stay editable: type a value to override the sequence, or leave the cell at `DEFAULT`. A connection set to the Read-Only [safe mode](/features/safe-mode) level edits nothing at all. - -## Saving - -Press `Cmd+S`, or click the toolbar checkmark. Values go out as bound parameters; `DEFAULT` and SQL functions such as `NOW()` are written into the statement as they stand. - -| Change | Generated SQL | -|--------|--------------| -| Cell edit | `UPDATE … SET column = ? WHERE pk = ?` | -| Row insertion | `INSERT INTO … (columns) VALUES (?, …)` | -| Row deletion | `DELETE FROM … WHERE pk = ? OR pk = ?` | - -A table without a primary key is matched on every original column value instead, with `IS NULL` for the nulls. On MySQL, MariaDB, TiDB and OceanBase a `FLOAT`, `DOUBLE` or `JSON` column is compared through `CONCAT()`, because the text those types read back as does not compare equal to the value it came from. On SQL Server an `ntext`, `text`, `image`, `xml`, `geography`, `geometry` or `hierarchyid` column is compared through `CAST`, because `=` refuses those types or the value reads back as the type's bytes. A `sql_variant` column cannot be compared without losing its type, so a save that has to match one stops and names it. - -On an engine with transactions, the statements run inside one, so a failure rolls the whole save back and the table is left as it was. Without them, the statements that ran before the failure stand. Either way a failed save reports **Save Failed** with the server's message and keeps the queue intact, so correct the value and save again. A save that succeeds clears the queue, clears undo, and reloads the grid at the same place, with its sort, value filter and selection. In a query tab the reload runs the statement behind the result again, not the one at the caret. - -Every queued change has to become a statement before anything is sent. When a change cannot, such as a new Redis row with no key, or it holds a value the driver cannot write, such as an edit to one leaf of an Elasticsearch nested array or to a column the server owns made before the table's structure finished loading, the save stops, names what it could not write, and keeps the whole queue. Undo that change, or make it with a query, then save again. - -Each generated SQL statement is held to the number of rows it was written for. On a table with no primary key, two identical rows cannot be told apart, so a statement meant for one of them matches both; the save stops there and reports what happened instead of rewriting the other row. - -Such a statement is held to the other end too. Finding fewer rows than it was written for means the row it meant to write is no longer there, so the save rolls back and keeps the edits rather than reporting a success that wrote nothing. Engines whose drivers report no real count, ClickHouse among them, are not held to it. Of the drivers that write their own statements, SQL Server and Oracle are held to it on a table without a primary key, and MongoDB and Redis are not. SQL Server bounds that match with `TOP (1)` and Oracle with `ROWNUM = 1`, so of two identical rows they write one. - -There is no discard button. Undo the edits, or take the **Discard Unsaved Changes?** prompt that appears when you refresh, sort, filter, change page, or close the tab with edits pending. - -## Previewing the SQL - -`Cmd+Shift+P`, or **Query > Preview SQL**, lists the statements with their parameter values inlined, so the preview reads as what will run. **Copy All** copies every statement. - -## Undo and redo - -`Cmd+Z` and `Cmd+Shift+Z`. Focus decides what they act on: text in the SQL editor, queued changes in the grid. Deleting several rows undoes as one step. The stacks are per tab, and a save clears them. - -## Restoring a save - -**Edit > Restore Previous Values…**, or the same item under **Actions** in the toolbar, puts back what the rows held before the last save on the current table. It ships with no shortcut; bind one in **Settings > Keyboard**, under **Data Grid**. Starter license. - -Values are kept on this Mac for 7 days, encrypted, and never synced. Turn the capture off, or delete what is stored, in **Settings > Data > Data Rewind**. - -Every row is planned against the table as it stands now, and the sheet says what will happen to each one before anything runs: - -| Outcome | Means | -|---|---| -| Will restore | The row still holds what the save wrote | -| Changed since the save | Someone else wrote to the same column, and the row is left alone | -| Already restored | The row is back at its old value, so a rewind that stopped halfway is safe to run again | -| Row no longer exists | Nothing left to write to | -| A row with this key already exists | Something took the deleted row's key | - -**Restore** writes the changes back the way any other save does: through the same [safe mode](/features/safe-mode) prompt, in one transaction where the engine has them, and into query history. The restore is itself restorable. - - -Only what TablePro wrote is restored. Triggers that fired, audit rows they wrote, and rows a cascade deleted stay as they are. - - -A deleted row whose key the server allocates comes back under the same key. SQL Server restores it inside `SET IDENTITY_INSERT`, which needs ALTER permission on the table, and PostgreSQL writes it with `OVERRIDING SYSTEM VALUE`. Other engines have no way to write such a key, so the restore stops and says so, and so does a save that an earlier version recorded without noting which columns were identity. - -A save is not restorable at all when it also truncated or dropped a table. Within a save, a row is skipped when the table has no primary key, when a column was set to `DEFAULT` or a function such as `NOW()` whose stored value the app never read back, when the server chose the key of an inserted row, or when a value was larger than 1 MB. - -## Structure changes - -Column, index, foreign key, and primary key edits queue in the same place and go out with the same Save, previewed as DDL instead of DML. See [Table Structure](/features/table-structure). diff --git a/docs/features/code-folding.mdx b/docs/features/code-folding.mdx deleted file mode 100644 index f5524f1fae..0000000000 --- a/docs/features/code-folding.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Code Folding -description: Collapse statements, CTEs, subqueries and BEGIN blocks in the SQL editor and every other code view ---- - -Move the pointer into the gutter and chevrons appear. Every region that spans more than one line gets one, which covers whole statements, table bodies, CTEs, subqueries, `BEGIN` blocks and block comments. Click to collapse, click again to expand. - - - Code folding in the SQL editor - Code folding in the SQL editor - - -The chevron points down while the region is showing and right while it is folded, the way a disclosure control behaves everywhere else in macOS. Resting on one marks how far its region reaches. Where a statement and the block inside it open on the same line, the chevron folds the larger of the two. - -A folded region keeps its chevron whether or not the pointer is in the gutter, so nothing is hidden without a visible way back. Move the pointer away from a gutter with nothing folded and it is line numbers again. - -## Collapsed chips - -A collapsed region is replaced inline by a chip showing the start of what it hides and how many lines that is, so a folded `CREATE TABLE` still says what is inside it. Click the chip to bring the region back. Pointing at a chip also marks its chevron in the gutter, since the two are ways to reach the same fold. - -In the query editor, resting on a chip peeks at the block without expanding it. The peek shows the whole block, opening line included, with the editor's own highlighting and theme, and says how many lines it left out when the block is longer than it can show. Moving away, scrolling, typing or clicking closes the peek, and the caret never leaves the editor. - -## Commands - -| Command | Shortcut | -|---|---| -| **Toggle Fold** | `Cmd+Option+Left` | -| **Fold All** | `Cmd+Option+Shift+Left` | -| **Unfold All** | `Cmd+Option+Shift+Right` | - -All three are on the Query menu and rebindable in **Settings > Keyboard**. Fold All collapses top-level statements only; fold the regions inside them one at a time. The editor's right-click menu carries **Fold** over an expanded region and **Unfold** over a collapsed one. - -Chevrons are reachable with VoiceOver. Each reports as a disclosure control that says whether its block is folded, and pressing it folds or unfolds the block. - -## What folds and what does not - -Strings, comments and PostgreSQL dollar-quoted bodies are never read as structure, so a `(` inside a string opens no fold. Documents over 2 million characters get no folding at all, the same limit that turns off syntax highlighting. - -Collapsed regions survive closing and reopening a tab. If the file changed while the tab was closed, the folds that no longer fit are dropped. - -## Where it works - -Turn folding off in **Settings > Editor**. Beyond the SQL editor it covers the DDL and trigger views, the SQL import preview, the AI review sheet, AI chat code blocks, and the JSON cell viewer, all of which number their lines while folding is on. - -Turning line numbers off in **Settings > Editor** removes the gutter and the chevrons with it. Toggle Fold, Fold All, Unfold All and the collapsed chips all still work. diff --git a/docs/features/compare-sync.mdx b/docs/features/compare-sync.mdx deleted file mode 100644 index b9ca0f7d17..0000000000 --- a/docs/features/compare-sync.mdx +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Compare & Sync -description: Compare two databases and generate the SQL script that brings one in line with the other ---- - -Pick a source and a target, press **Compare**, and every object that differs lists in one window. Include the ones to change, generate the script, and read it before anything runs. Requires a Starter license. - - - Compare and Sync window with a results table on the left and a two-up definition diff on the right - Compare and Sync window with a results table on the left and a two-up definition diff on the right - - -## Opening it - -- **Database > Compare > Compare & Sync Databases…** -- Right-click a connection in the connection list and choose **Compare/Sync with…**. The connection clicked becomes the source. - -The window comes back on the source, target, mode and options it last held, so running the same comparison again is one press of **Compare**. Opening it from a connection makes that connection the source, and the remembered target comes back only when it was the target of that same source. - -Every toolbar control also sits under **Database > Compare**, so the whole flow is reachable from the keyboard. - -## Choosing the two sides - -**Source** and **Target** are database pickers, not connection pickers: each one walks connection, then database, then schema. Two databases on one server are a valid pair, and so are two schemas in one database. - -**Comparisons** in the toolbar lists every saved setup and sets both pickers from the one chosen. It does not need a pair to be chosen first. - -The **source** never changes. The **target** is written to. A connection whose safe mode level is **Read-Only** is disabled in the target picker with the reason shown, so the refusal arrives at selection time rather than after comparing. **Swap** reverses the direction. - -Nothing is written until **Apply**. Until then the strip along the top reads **Comparing only. Nothing has been written.** - -## What takes part - -**Options** chooses the object kinds. Tables are always compared; views, materialized views, procedures, functions and triggers are opt-in. - -| Kind | Compared as | -|---|---| -| Tables | Parsed columns, indexes, foreign keys, storage engine and collation | -| Views | Normalized definition text | -| Materialized views | Normalized definition text, plus parsed indexes on PostgreSQL and PGlite | -| Procedures, functions | Normalized definition text, matched on name and argument list | -| Triggers | Normalized definition text, per table | - -Tables are never compared as DDL text. Driver-rendered DDL varies by formatting and by system-generated constraint names, which reports identical tables as different. A routine has no parsed form to compare instead, so its body is the definition. The normalizer compares the text each side would run and folds line endings and, when the options say so, whitespace and identifier case. A semicolon that only separates statements is not a difference. The one after an Oracle unit's `END` is, because Oracle stores the unit INVALID without it. - -A column's type is compared as the server declares it, so `character varying(10)` against `character varying(50)` and `numeric(10,2)` against `numeric(12,4)` are differences, and so are two different enum types. The schema a type was found in is not part of the comparison: two schemas that each carry their own `status` enum compare equal, and a PostGIS column matches whichever schema each side installed the extension into. A type the target does not have at all is still written, and its `ALTER … TYPE` fails there with `type "…" does not exist`. - -### What is ignored - -These drift between environments by design, so they are ignored by default. Turn one off to have the difference reported. - -| Option | What it ignores | -|---|---| -| Identifier case | `Orders` against `orders` | -| Column order | The same columns in a different order | -| Whitespace in text | Indentation inside default values, comments and routine bodies | -| Auto-increment seed | The counter's current value | -| Collation and character set | A table-level or column-level collation difference. A column changed for another reason keeps the target's collation, unless its type changes | -| Comments, owners, definers | Descriptive metadata with no runtime effect | - -With **Collation and character set** off, a PostgreSQL column whose collation differs is retyped to the type it already has, `ALTER COLUMN … TYPE text COLLATE …`. That rebuilds the column's indexes, and PostgreSQL refuses it on a column a view or a generated column reads: drop the view or the generated column first, or leave the option on. - -## Reading the results - -Each object lands in one of four states: **only in source**, **only in target**, **different**, or **identical**. **Group By** sections the table by difference or by object kind, and the search field filters by name. Identical objects stay hidden until **Show Identical Objects**. - -Every row carries an **Include** checkbox, and a group header carries one for everything under it. **Select > All** covers everything the pane is currently showing, so the search field and **Show Identical Objects** narrow what it reaches. Nothing is included until it is checked. - -An object that could not be read keeps its own **Could Not Compare** section with the reason, and no script touches it. A view, procedure, function or trigger lands there when the server refuses its definition, returns an empty one, or returns a body rather than a `CREATE` statement. On MySQL, an account without the `SHOW VIEW` privilege lists every view and is refused each definition. One unreadable object never stops the rest of the comparison. - -A procedure, function or trigger list that cannot be read stops the comparison and names the connection. To compare everything else, deselect that kind under **Objects to Compare** in **Options**. - -On MySQL a list that is short is not an error. An account without `EXECUTE` lists no procedures or functions, and one without `TRIGGER` lists no triggers, so the other side's copies show as only on that side. Compare from accounts that hold both privileges. - -The detail pane on the right has three tabs. **Definitions** shows the source and target side by side, split or unified, rendered from the same function so a formatting difference cannot appear as a real one. A PostgreSQL or PGlite materialized view shows its indexes under its definition, and one whose text matches but whose indexes differ is listed as **different**. **Rows** is the data comparison. **Script** is the generated SQL. - -## Comparing rows - -Switch the mode control to **Data**. The left pane lists the tables present on both sides as soon as there is a pair to list them for, without reading a row. - -Tables start unchecked. Tick the ones to compare, then press **Compare**: a data comparison reads every row of every ticked table on both sides, so comparing a whole database by accident is expensive. - -Rows are matched by key, read in key order from both sides and walked in lockstep, so neither side is ever held in memory in full. - -### Setting a table's scope - -Select a table, then set its scope at the top of the **Rows** tab. Each table keeps its own settings. - -| Setting | What it does | -|---|---| -| **Key columns** | The columns that identify a row. Defaults to the primary key; composite keys work. A key that matches more than one row stops that table's comparison and names the key. | -| **Compared columns** | The columns checked for differences. A column left out is still written on insert and update. | -| **Filter** | A SQL condition, such as `status = 'active'`, read on both sides. Clear **Use the same filter for the target** to give the target its own condition. | -| **Rows** | **All**, or the first N rows in key order. | - -A filter is a single condition. A semicolon, a comment, or an unclosed quote or parenthesis is refused before either side is read. Press `Return` or leave the field to apply it. - -- A table with no usable key lists as not comparable rather than being matched on a guess. -- A generated column is read and compared but never written. An identity column the target always generates is inserted with `OVERRIDING SYSTEM VALUE` on PostgreSQL and `IDENTITY_INSERT` on SQL Server, and never updated. -- NULL equals only NULL. Numeric tolerance applies to numeric columns and timestamp precision to date and time columns, both set in **Options**. -- A row with NULL in a key column is left out and counted. - -### Row limits and filters - -A row limit reads the first N rows of each side in key order, and every key both sides were read past is compared. Rows past that point are not read, so none of them is inserted or deleted. The review pane names the last key compared: put it in the **Filter** on both sides, as `id > 5000`, to compare the next stretch. - -With a filter, a row that matches on one side and exists outside the filter on the other is listed as **Outside Filter** and never written. Widen the filter to sync it. - -### Reviewing rows - -Every column shows in a grid. A row only in the source is underlined, a row only in the target is struck through, and a changed row shows its source line above its target line with each differing value marked. - -**Show** narrows the grid to **All Rows**, **Difference**, **Insert**, **Update**, **Delete** or **Same**, plus **Outside Filter** when a filter produced any. Clear a row's **Include** checkbox, or select rows and press `Space`, to leave them out of the script. The **Include** menu above the grid includes or excludes every listed row. - -The grid lists up to 5,000 differences and 1,000 matching rows. Past either cap the pane says so, and **Apply** still covers every difference. - -### One connection, two databases - -A data comparison needs both sides open at once. On an engine that pools connections, two databases on one connection is fine. On an engine that cannot pool, both sides share one driver and one database position, so the comparison refuses that pair by name and asks for a second connection for the target. - -## The script - -**Generate Script** builds the SQL. It is read-only: each statement carries the hazards its operation plan computed, and those cannot be recovered from edited text. **Copy** and **Save…** take it elsewhere, and the query editor runs a hand-edited version through the normal path. - -Statements are ordered by foreign key dependency, not alphabetically. Tables are created parent-first and dropped child-first; row inserts run parent-first and row deletes child-first. - -A view, routine or trigger that differs is dropped and created again. On Oracle it is replaced by its own `CREATE OR REPLACE` instead, with no `DROP` first, so a definition the server refuses leaves the target's object in place. - -A PostgreSQL or PGlite materialized view whose definition matches and whose indexes differ keeps its rows: only its indexes are dropped and created. One whose definition differs is dropped, created again and computed again, then given the source's indexes. Its comments and privileges are not recreated. A change that leaves it with no unique index `REFRESH MATERIALIZED VIEW CONCURRENTLY` can use is listed among the warnings. The script stops at a materialized view whose indexes would be written into a schema other than the one its definition names: exclude it, or compare two schemas with the same name. - -A saved script is written for the target engine's own client, so each statement ends the way that client reads it: - -| Target | A plain statement ends with | A routine, trigger or block ends with | -|---|---|---| -| Oracle, Dameng | `;` | a line holding only `/` | -| MySQL, MariaDB | `;` | its body inside `DELIMITER //` and `//`, then `DELIMITER ;` | -| SQL Server | `;` and a `GO` line | `;` and a `GO` line | -| PostgreSQL, SQLite and every other engine | `;` | `;` | - -Run the Oracle script in SQL*Plus or SQLcl, the Dameng script in DISQL, the MySQL script in the `mysql` client, and the SQL Server script in `sqlcmd` or SQL Server Management Studio. The query editor reads the Oracle, PostgreSQL and SQLite forms as saved, but sends a `DELIMITER` or `GO` line to the server as a statement: remove those lines before running a MySQL or SQL Server script there. - -SQL*Plus ends a plain statement at a blank line and reports `SP2-0734: unknown command` for the rest of it. When a view's definition holds a blank line, run `SET SQLBLANKLINES ON` before the script. - -A data script is built from a fresh read of both sides. An included table that has not been compared with its current scope holds the script back until it is compared or excluded, and a table whose rows changed since the comparison stops the script with that table named. Compare again, then generate. - -A structure script is checked the same way. Both schemas are read again, and an object whose structure moved since the comparison stops the script with that object named rather than writing DDL against a schema that no longer exists. - -Script generation needs matching database types, with MySQL and MariaDB counting as one family. A cross-engine pair still compares, read-only: column data types are engine-specific strings, so generating DDL for one engine from another's metadata is not sound. - -## Applying - -Anything that would destroy data is generated, listed, and held back. Dropping a table, dropping a column, narrowing a type, adding NOT NULL, changing a primary key and deleting a row each need an explicit allowance, and that allowance covers one run and is never saved. - -A type narrows when the new one holds less: a shorter length, a length where there was none, fewer decimal places, fewer digits before the point, fewer fractional-second digits, or an enum or set label removed. An integer's display width and the order of enum labels are not a narrowing. - -**Apply…** opens a sheet with the script, a summary, and the warnings, building the script first when **Generate Script** has not run. **Cancel** is the default button and **Apply** is marked destructive. Allow a held-back statement in the sheet or in the **Script** pane; the sheet's **Apply** stays disabled while any included statement is still held back. - - - Applying runs the script against the target. Statements that already ran stay applied unless the whole run is inside a transaction that rolls back. - - -**On error** chooses between stopping and rolling back, stopping and keeping what ran, and skipping and continuing. A transaction cannot be combined with skip and continue, because together they leave the target half-applied. - -Whether a transaction covers the run depends on the engine. MySQL, MariaDB and Oracle commit implicitly on every DDL statement, so a structure sync on those engines runs without one rather than implying a rollback that will not happen. A data sync into a MySQL or MariaDB table on a storage engine that cannot roll back, such as MyISAM, keeps the rows a rolled-back run already wrote, and the result names those tables. - -An UPDATE or DELETE that changes more than the one row it was built for counts as a failed statement, and **On error** decides what happens next. - -The source, the target, the mode and **Options** stay locked while a run applies. A target whose connection is switched to **Read-Only** after it was picked turns **Apply** off. - -Closing the window mid-run asks first, and says that statements which already ran stay applied. After a run, press **Compare** to see where the target stands; the included tables stay checked for the next sync. - -## Saving a comparison - -**Save Comparison…** names the current setup: the source, the target, the mode, the object kinds, the options, the included objects, and each table's key columns, compared columns, filter and row limit. Rows left out of a script are not saved. It sits in the **Comparisons** menu and under **Database > Compare**. - -Loading one sets both pickers. **Comparisons** lists them all whatever pair is on screen; **Options** lists them too, with a **Delete** for each. - -A saved comparison whose connection has since been deleted says so instead of loading half a pair. diff --git a/docs/features/connection-sharing.mdx b/docs/features/connection-sharing.mdx deleted file mode 100644 index 18f51208e6..0000000000 --- a/docs/features/connection-sharing.mdx +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: Connection Sharing -description: Export connections and their saved queries to a file, share links, import from other apps, and resolve passwords from secret managers ---- - -Passwords stay in your Keychain, so every route here hands over a connection definition and leaves the other person to supply their own credential. An encrypted export is the one exception. - -| Route | Use it when | Passwords | Needs | -|---|---|---|---| -| `.tablepro` file | One-off, any number of connections | Only with **Include Credentials** | Nothing | -| `tablepro://` link | One connection, pasted into a chat | Never | Nothing | -| [Linked Folder](#linked-folders) | A shared set has to stay current | Never | Starter | -| [Team Catalog](#team-catalog) | Your team already shares a repo or a drive | Never | Team | -| [Team Library](/features/team#share-connections-with-your-team) | Your team shares nothing on disk | Never | Team | -| [iCloud Sync](/features/icloud-sync) | Your own second Mac, not a colleague | Optional | Starter | - -## Export - -Right-click a connection > **Share > Export to File…**, selecting several first to export them together, or **File > Export > Export Connections…** for all of them. - -The file carries what the connection form holds, from host and port through SSH and SSL configuration, color, icon, tags, the full group path, Safe Mode level, startup commands, AI policy, the Local only flag, and any driver field the plugin does not mark secret. Passwords, key passphrases, TOTP secrets, and secret driver fields stay behind. - - - Export connections - Export connections - - -The export sheet adds these options: - -| Option | Default | Adds to the file | -|---|---|---| -| **Include Saved Queries** | On | The [saved queries](/features/favorites#saving-a-query) that belong to the exported connections, with their folders and keywords | -| **Also include global saved queries** | Off | The saved queries every connection shows. Dimmed while **Include Saved Queries** is off | -| **Include Credentials** | Off | Passwords and other secrets, under a passphrase. See [Encrypted export](#encrypted-export) | - -The saved query options appear only when there are saved queries to export, and they need no license. Saved queries are written as readable text unless **Include Credentials** encrypts the file. Version history, [linked SQL folders](/features/favorites#linked-sql-folders) and Team Library queries are never exported. - - -**Copy Connection String** writes a database URL that carries the password in plain text, plus the SSH password when the connection tunnels. TablePro marks the clipboard item concealed, which keeps it out of the history of clipboard managers that follow that convention and out of nothing else. **Copy TablePro Link** and **Copy as JSON** carry no secrets. - - -### Encrypted export - -Needs a [Starter license](/features/licensing). - -Turn on **Include Credentials** in the export sheet and enter a passphrase of 8 characters or more. Credentials go out under AES-256-GCM, keyed from the passphrase with PBKDF2 at 600,000 iterations. Importing asks for the passphrase. - - - Encrypted export - Encrypted export - - -## Import - -Open a `.tablepro` file through **File > Import > Import Connections…**, by right-clicking the empty area of the connection list, by double-clicking the file, or by dragging it onto TablePro. **File > Import > Import from URL…** takes a database URL instead, described in [Connection URLs](/connections/urls). - -A review lists the connections, then the saved queries, before anything is saved. The checkbox in each section header selects or deselects the whole section. A green checkmark marks a connection ready; a yellow triangle marks a missing SSH key or certificate, or an SSL mode TablePro does not recognize, which imports as **Required**. A "duplicate" tag marks one already in your library, with the name it matches. - -Duplicates match by host, port, socket path, database, and username, and start deselected. Select one, then choose what happens to it: - -| Choice | Result | -|---|---| -| **Keep Existing, Add Queries** | Your connection stays as it is and gains the file's saved queries for it. Offered only when the row carries saved queries | -| **As Copy** | A second connection named "*name* (Imported)" | -| **Replace** | The file's settings overwrite yours. Only one row can replace a given connection | - -A duplicate that carries saved queries starts on **Keep Existing, Add Queries**; any other duplicate starts on **As Copy**. To skip a connection, deselect it. - -Each saved query shows its connection, or **All connections** for a global one, and its folder path. A query whose connection is deselected cannot be selected. A query you already have is tagged "already saved" and stays deselected: same name, same SQL, and the same connection, or global in both places. A note on the row says what else changes: - -| Note | What happens | -|---|---| -| A saved query with this name exists. | It imports beside the one you have | -| Keyword “…” is in use, so it imports without one. | The query that has the keyword keeps it | -| Keyword “…” is not valid, so it imports without one. | Keywords cannot hold spaces | -| Too large to save (…) | Over the 900 KB limit for a saved query, so it cannot be selected | -| Its connection is not imported. | Select the connection to bring its queries | - -Group and folder paths match by name from the top down: an existing group or folder is reused, and a missing one is created. A created group takes the file's color and icon; an existing one keeps its own. Only the groups, tags, and credential profiles that added or replaced connections use are created. - - - Import preview - Import preview - - -Anything that decides where a credential comes from is stripped on the way in, from a file and from a link alike: AWS options including IAM authentication, the region, the profile and the RDS endpoint; **Use Password File**; **Prompt for password**; the SSL client key passphrase; the pre-tunnel host and port; and the **Pre-Connect Script**. Startup SQL and tunnel commands run on every connect, so a file brings them only if you agree: importing lists each one and asks, and **Import Without Commands** is the default. A link's sheet shows its startup SQL in full before you add it. - -## Share via link - -**Share > Copy TablePro Link** produces a `tablepro://import?…` URL with the name, host, port, type, username, database, and any SSH or SSL settings. The recipient opens it, reviews the prefilled form, adds a password, and saves. - -```text -tablepro://import?name=Staging&host=db.example.com&port=5432&type=PostgreSQL&username=admin -``` - -`tablepro://connect/` opens a saved connection rather than importing one. No menu item builds it; see [URL Scheme](/developers/url-scheme#open-a-connection). - -## Import from other apps - -Choose **File > Import > Import from Other App…**, pick the source, then review the list and resolve duplicates before clicking **Import**. Groups and folders carry over, and the source app does not have to be running. Leave **Include saved queries** selected to bring the source's saved queries into the same review; the source list says how many it found. - - - Import from other app - source picker - Import from other app - source picker - - -| App | Databases | Passwords | Saved queries | Notes | -|-----|-----------|-----------|---------------|-------| -| TablePlus | Every TablePlus engine except Vertica and Greenplum | From Keychain, and the password mode with them | Favorites and shared favorite folders, global, in a folder named TablePlus | SQLite and DuckDB connections bring their file path. Check the account ID of a Cloudflare D1 connection and the URL of a libSQL one in the connection form after importing | -| Sequel Ace | MySQL | From Keychain | Query favorites, global, in a folder named Sequel Ace | | -| DBeaver | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, and more | Decrypted from config file | Scripts, with the connection each one runs on | Read from the data folder, so every edition works | -| DataGrip | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, and more | From Keychain or `c.kdbx` | Query consoles, with their data source | Reads recent projects with SSH and SSL settings. A master password blocks it | -| Beekeeper Studio | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, and more | Decrypted from its `app.db` store | Saved queries, global, in a folder named Beekeeper Studio | Brings SSH bastion hosts across | -| Navicat | MySQL, MariaDB, PostgreSQL, SQLite, SQL Server, Oracle, MongoDB | Decrypted from `.ncx` file | None: the `.ncx` file has no saved queries | Export from Navicat first, with **Export Password** on, then pick the `.ncx` | - -Saved queries keep their folders inside the app's folder. A query keeps one keyword: a Sequel Ace tab trigger, or the first keyword without spaces from a TablePlus favorite. A DBeaver script with no connection lands in a global folder named DBeaver. A script or console whose connection is not part of the import lands in a global DBeaver or DataGrip folder, deselected. Scripts and consoles that still carry the name the app gave them, such as `Script-3` or `console_2`, also start deselected. Empty files are skipped, and placeholders such as `${1:name}` arrive as written. - -## Import from AWS - -Choose **File > Import > Import from AWS…** to list the RDS instances and Aurora clusters an AWS profile can see, and import them with their endpoint, port, and engine already filled in. See [AWS IAM Authentication](/connections/aws-iam#import-from-aws). - -## On iPhone - -TablePro for iPhone reads and writes the same file. Tap the **more** menu (•••) above the connection list and choose **Import Connections**, or open a `.tablepro` file from Files or AirDrop. **Export Connections** shares through the system share sheet, leaving passwords out unless you turn on **Include passwords** and set a passphrase. - -Groups nest on iPhone the way they do on the Mac. Saved queries do not: an iPhone import skips the ones in a file, and an iPhone export carries none, so a Mac file exported again from an iPhone loses its queries. - -## Linked Folders - -Needs a [Starter license](/features/licensing). - -Press `Cmd+,`, then **Settings > General > Linked Folders > Add Folder…** and point it at a directory of `.tablepro` files: a Git repo, a Dropbox folder, a network drive. Those connections appear read-only in the sidebar, and each person enters their own password. - - - General pane with one linked folder, its on switch and the Add Folder button - General pane with one linked folder, its on switch and the Add Folder button - - -## Team Catalog - -Needs a [Team license](/features/licensing). - -**Share > Publish to Team Catalog…** writes each selected connection as its own `.tablepro` file into a shared folder, asking for the folder the first time. Republishing overwrites the file, and passwords, passphrases, TOTP secrets, and saved queries are never written. Teammates add that folder as a Linked Folder. An older TablePro skips these files, as [File format](#file-format) explains. - -## Environment variables - -Needs a [Starter license](/features/licensing). - -Write `$VAR` or `${VAR}` in a `.tablepro` file and it resolves from TablePro's process environment at connect time. An app launched from the Dock inherits no shell exports, so set the variable with `launchctl setenv NAME value` or launch TablePro from a terminal. - -```json -{ "host": "${DB_HOST}", "username": "$DB_USER" } -``` - -## Password sources - -A connection in `~/Library/Application Support/TablePro/connections.json` can say where its password comes from instead of keeping one in the Keychain, which suits a script that provisions connections. The source resolves at connect time, replaces the Keychain lookup rather than supplementing it, and never syncs to iCloud. A source that fails to resolve fails the connection. - -```json -{ "passwordSource": { "kind": "file", "path": "~/.config/tablepro/secrets/feature-x.pw" } } -{ "passwordSource": { "kind": "env", "variable": "STAGING_DB_PASSWORD" } } -{ "passwordSource": { "kind": "command", "shell": "op read op://vault/feature-x/password" } } -{ "passwordSource": { "kind": "onePassword", "reference": "op://vault/feature-x/password" } } -{ "passwordSource": { "kind": "vault", "path": "secret/data/staging/db", "field": "password" } } -{ "passwordSource": { "kind": "awsSecretsManager", "secretId": "prod/db", "jsonKey": "password" } } -``` - -| `kind` | Reads from | Set up first | -|---|---|---| -| `file` | The file at `path`, minus a trailing newline | `chmod 600` the file | -| `env` | The named environment variable | Same Dock caveat as environment variables | -| `command` | stdout of the command, run through `/bin/bash`. A non-zero exit fails the connect | | -| `onePassword` | `op read` on the reference | `op signin` | -| `vault` | `vault kv get` for one field at a path | `VAULT_ADDR` and `VAULT_TOKEN` in TablePro's environment | -| `awsSecretsManager` | `aws secretsmanager get-secret-value`. `jsonKey` pulls one field out of a JSON secret; omit it for the whole value | | - -Every command gets 30 seconds. The three CLI kinds quote each argument, so a reference cannot break out into the shell, and they need the tool on `PATH`, in `/usr/local/bin`, or in `/opt/homebrew/bin`. - -Editing `connections.json` by hand costs one extra step. TablePro stamps the file when it writes it, and a file that changed underneath it fails the connect with *"Your connections file was changed outside TablePro, so this connection's password source was not run. Open the connection and save it again to confirm the change."* Save it once from the app and sources run again. - -## File format - -JSON, at format version 2. A file needs `formatVersion`, `exportedAt`, `appVersion`, and `connections`. Each connection needs `ref`, `name`, `host`, `port`, `database`, `username`, and `type`, and an empty string is fine where a value does not apply. Paths use `~/` so they travel. - -```json -{ - "formatVersion": 2, - "exportedAt": "2026-10-10T09:00:00Z", - "appVersion": "0.80.0", - "connections": [ - { - "ref": "c1", - "name": "Orders", - "host": "db.example.com", - "port": 5432, - "database": "orders", - "username": "app", - "type": "PostgreSQL", - "groupRef": "g2", - "tagNames": ["production"] - } - ], - "groups": [ - { "ref": "g1", "name": "Client A", "color": "Blue" }, - { "ref": "g2", "name": "Production", "parentRef": "g1" } - ], - "tags": [{ "name": "production", "color": "Red" }], - "queryFolders": [{ "ref": "f1", "name": "Reports", "connectionRef": "c1" }], - "savedQueries": [ - { "ref": "q1", "name": "Daily active users", "sql": "SELECT …", "keyword": "dau", "folderRef": "f1", "connectionRef": "c1" }, - { "ref": "q2", "name": "Locks", "sql": "SELECT …" } - ] -} -``` - -A `ref` links records inside one file and means nothing outside it. Refs must be unique within each kind. A ref that points at nothing, or a parent chain that loops, fails the import with an error naming the ref. - -| Key | Holds | -|---|---| -| `groups` | Groups with a `ref`, a `name`, and an optional `color` and `iconName`, nested through `parentRef`. A connection points at the innermost one with `groupRef` | -| `tags` | Tag colors. A connection lists its tags in `tagNames`, and a name missing here imports with no color | -| `credentialProfiles` | Profiles with `ref`, `name`, `username`, and `passwordMode` (`stored`, `prompt`, or `pgpass`), named by a connection's `credentialProfileRef`. A profile's password never travels, so a `stored` one imports as `prompt` | -| `queryFolders` | Saved query folders with a `ref` and a `name`, nested through `parentRef`. A `connectionRef` scopes a folder to that connection; without one it is global | -| `savedQueries` | A `ref`, `name`, and `sql`, and an optional `keyword`, `folderRef`, and `connectionRef`. Without a `connectionRef` a query is global | -| `credentials` | Only inside an encrypted file: secrets keyed by connection `ref` | - -Version 1 files still import. Their `groupName` becomes a group at the top level of the library, and `tagName` or `tagNames` become tags. A TablePro version from before saved query export cannot open a version 2 file and reports that it needs a newer version; Linked Folders on such a version skip these files, Team Catalog ones included. diff --git a/docs/features/connection-window.mdx b/docs/features/connection-window.mdx deleted file mode 100644 index 5072397b8f..0000000000 --- a/docs/features/connection-window.mdx +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: Connection window -description: The toolbar, the Actions menu, the sidebar's list chooser, filters and folders, and the trailing pane ---- - -Nothing in the titlebar is fixed furniture. The toolbar carries the commands for the tab you are on, and what it leaves out is one click away under **Actions** or in the menu bar. - -## The toolbar - -Eight controls, laid out by pane: the sidebar toggle over the sidebar, the connection and its database in the centre, the tab's own commands after them, and the trailing pane's toggle at the end. - -| Control | What it does | -|---|---| -| Sidebar | Shows and hides the sidebar (`Cmd+0`) | -| Connection | The engine's glyph and the connection's name. Click for the [connection switcher](/connections#switch-connections-and-databases) (`Ctrl+Cmd+C`) | -| Database | The database this window is browsing. Click for the list (`Cmd+K`) | -| Refresh | Reloads what the tab is showing (`Cmd+R`) | -| Save Changes | Commits the tab's staged changes (`Cmd+S`) | -| Actions | The rest of the commands for this tab and this connection | -| Safe Mode | A padlock carrying the current [level](/features/safe-mode), open for **Silent** and closed for **Read-Only** | -| Inspector | Opens and closes the [trailing pane](#the-trailing-pane) (`Cmd+Option+I`) | - -Two of them change their name. The database control follows the engine's own word: **Schema** on [Oracle](/databases/oracle), **Catalog** on [Trino](/databases/trino), **Keyspace** on [Cassandra](/databases/cassandra). The commit control follows the tab: **Create Table** on a new table's tab, **Apply Changes** on [Users & Roles](/features/users-roles), **Save Changes** everywhere else. - -### What each context leaves out - -A control the tab cannot use leaves the toolbar rather than standing there dimmed. - -| Where you are | Not there | -|---|---| -| A new table's tab | Refresh. The definition is not on the server yet | -| ER diagram, Server Dashboard, Query Insights, a DDL tab, a history tab | The commit control. None of them stages an edit of its own | -| [Agent mode](/features/agent-mode) | Refresh and the commit control. The grid and the object browser are behind the conversation | -| An engine with nothing to switch between | The database control. On [SQLite](/databases/sqlite) and [DuckDB](/databases/duckdb) the file beside it is the database; on an engine with a single namespace there is no second level to name | - -That needs macOS 15. On macOS 13 and 14 the same eight stand in every context and dim where they cannot act, except in two places. Refresh stays enabled on a new table's tab. So does the commit control on an ER diagram, Server Dashboard, Query Insights, DDL or history tab while a **Truncate** or a **Delete** is staged in the sidebar: that queue belongs to the connection, not to the tab in front of it. - -The shape settles on a tab switch, a mode switch and a connection switch, and at no other moment. A staged edit, a running query or a dropped connection dims a control; it never moves one. - -## Actions - -One pull-down, rebuilt from the tab you are on each time it opens. Every entry is also a menu-bar command, so nothing lives only here. - -| On | Actions carries | -|---|---| -| A table tab | **Add Row** while the Data view is showing, **Restore Previous Values**, **Back**, **Forward**, **Preview SQL**, **Export Results**, **Show DDL**, **Copy DDL** | -| A query tab | **Restore Previous Values**, **Preview SQL**, **Show Results**, **Export Results** | -| A new table's tab | **Preview SQL** | -| A DDL tab | **Show DDL**, **Copy DDL** | -| Every browse tab | **Export Tables**, **Import Data** and its format list where the driver imports, **Show Query History**, **Users & Roles**, **Query Insights**, **Server Dashboard** where the engine has one, **New Tab**, **Open Quickly** | -| Agent mode | **New Session**, **Open Session**, **Close Session**, **Delete Session**, **New Conversation** | - -Every context ends with **Mode**, **Switch Connection** and **Close Connection**, and **Reconnect** joins them while the connection is down. A window that has not connected carries those alone, and **Mode** is absent with AI features off in **Settings > AI**. - -**Import Data** takes the format from the file you pick and **Import Data From** beside it names the format and offers every file, which is the same pair the menu bar draws under **File > Import**. - -## Add a control back - -The toolbar is still yours to arrange. Choose **View > Customize Toolbar**, or right-click the toolbar, and drag in any of these. - -| Tile | Runs | -|---|---| -| **Navigation** | Back and Forward, as one pair (`Ctrl+Cmd+[` and `Ctrl+Cmd+]`) | -| **New Tab** | A query tab (`Cmd+T`) | -| **Open Quickly** | The object and query search (`Cmd+Shift+O`) | -| **Add Row** | A row at the end of the grid (`Cmd+Shift+N`) | -| **Restore Previous Values** | The [rewind](/features/change-tracking#restoring-a-save) of the last save | -| **Preview** | The SQL behind the staged changes (`Cmd+Shift+P`) | -| **Results** | The query editor's results pane (`Cmd+Option+R`) | -| **Export** | Export Tables (`Cmd+Shift+E`) | -| **Import** | The import format list (`Cmd+Shift+I`) | -| **Dashboard** | The [server dashboard](/features/server-dashboard) | -| **History** | The [query history](/features/query-history) drawer (`Cmd+Y`) | - -A control you add yourself stays put. It stands in every context and dims when it has nothing to do, rather than leaving the way the default eight do. Your arrangement, and the **Show** setting in the same sheet, survive a relaunch. - - -The palette offers a tile for a control the tab you are on has hidden. Drag **Refresh** in while a new table's tab is in front and nothing appears until you switch tabs. - - -## The sidebar's list chooser - -**Tables** and **Favorites** sit above the filter field, at the top of the sidebar. **Tables** is the object browser; **Favorites** is the [favorites](/features/favorites) list for this connection. - -**View > Show Tables** and **View > Show Favorites** pick the same two and reveal a collapsed sidebar on the way. Neither ships with a shortcut. Bind one in **Settings > Keyboard**, where they are listed under **Navigation**. - -In [Agent mode](/features/agent-mode) the chooser and the filter field go, and the session list takes their height. - -## Filtering the sidebar - -The filter field matches any part of a name, so `sheet` finds `timesheet`. How far it reaches depends on the layout: - -| Layout | Searches | -|---|---| -| **View > Sidebar as List** | The browsed schema, then every other schema holding a match, listed below it and opened to show the matches | -| **View > Sidebar as Tree** | Every schema of every database whose schemas the tree has shown, expanded or not | - -Neither reaches a schema the [schema filter](#filtering-schemas) hides. - -A schema you have not opened is searched from one listing of the whole database, so only its tables and views match. Open the schema to search its procedures, functions, triggers and types too. Until that listing arrives, those schemas stay in the tree, collapsed. Oracle, Snowflake, BigQuery and the other engines whose sidebar lists schemas with no database above them show that tree in both layouts, and the filter checks the tables of every schema in it. - -A refresh, or a change made from TablePro, reads again only the schemas the tree has open. A closed schema is read the next time you open it. - - - Sidebar with timesheet in public at the top, and attendance and payroll below it, each opened to its timesheet table - Sidebar with timesheet in public at the top, and attendance and payroll below it, each opened to its timesheet table - - -Type a dot to filter by schema. `attendance.time` keeps the objects in `attendance` whose names contain `time`, `attendance.` shows everything in `attendance`, and `shop.attendance.time` names the database too. A table whose own name holds a dot, such as `audit.events`, still matches when you type that name. [Open Quickly](/features/open-quickly#other-schemas) takes the same form with fuzzy matching. - -## Filtering schemas - -Choose **View > Filter Schemas**, or right-click the empty area below the sidebar's last row, and select the schemas you work in. The sidebar narrows as you select, and selecting none lists every schema again. The filter applies to the database you are browsing, so switch database to filter another. On an engine without schemas, such as MySQL, the command is dimmed. - - - Filter popover over the sidebar with a Search schemas field, a checkbox for each schema, and a Show All button at its foot - Filter popover over the sidebar with a Search schemas field, a checkbox for each schema, and a Show All button at its foot - - -The filter is kept per connection and per database, on this Mac. The schema you are browsing stays listed whatever the filter says, and so does any schema holding a staged drop or truncate. A renamed schema keeps its place in the filter, and a dropped one leaves it. - -The sidebar and its search, [Open Quickly](/features/open-quickly), the schema picker at the foot of the object list, **Database > Schema**, the schema switcher, the [export dialog](/features/import-export#what-the-tree-lists) and the column type picker all list only the schemas it keeps. The pickers and the switcher still offer every system schema. Typed SQL, autocomplete, MCP clients and the AI assistant reach every schema, filtered or not. - -A hidden schema still shows up in three places. A starred table in it stays in Favorites, the export dialog lists it when you export from it or a saved selection names it, and the type picker offers its types when the table you are editing lives there. - -While the filter hides anything, a line above the schema tree counts the schemas it shows. Click **Show All** there, or choose **View > Show All Schemas**, to clear it. - -## Folders - -A folder groups the tables and views of one database or schema. It exists only in TablePro, so filing a table into one changes nothing on the server. - - - - Click **+** at the foot of the sidebar and choose **New Folder**, or choose **Database > New Folder**. Right-click a table and choose **Move to > New Folder** to file it in the new folder straight away. Type a name and press `Return`. - - - Drag tables and views onto the folder, or right-click one and choose **Move to** and the folder. A selection moves together. - - - -A filed object moves out of its **Tables** or **Views** section and into the folder. With **View > Sidebar as List** the folders sit under **Folders**, above **Tables**. With **View > Sidebar as Tree** each database or schema lists its own folders above its object kinds. - - - Sidebar with a Folders section holding Music, with Album and Artist, and Sales, with Invoice and InvoiceLine, above the Tables section - Sidebar with a Folders section holding Music, with Album and Artist, and Sales, with Invoice and InvoiceLine, above the Tables section - - -To take an object out, choose **Remove from Folder**, or drag it onto its **Tables** or **Views** section. Right-click a folder to rename it or choose **Delete Folder**. Deleting puts its objects back in their sections without asking, and `Cmd+Z` restores it. - -The filter field reaches inside folders. A folder holding a match opens to show it, and a folder without one is hidden until the field is cleared. - -Renaming or dropping a table keeps its folder in step, whether you do it from the sidebar or with SQL as [Drop or rename with SQL](/features/table-operations#drop-or-rename-with-sql) describes. A renamed table stays filed under its new name, and a dropped one leaves the folder. - -## The trailing pane - -One column on the trailing edge, holding one surface at a time. Its header keeps one height across all three, so switching surface moves nothing beneath it. - -| Surface | Holds | -|---|---| -| **Inspector** | The selected row's fields, or the table's own details when nothing is selected. See [Cell and Row Viewers](/features/json-viewer) | -| **Assistant** | The connection's [chat](/features/ai-assistant#chat) | -| **Result** | What an agent session proposed, ran and changed | - -The picker at the leading end of the header moves between **Inspector** and **Assistant**, and the segment you pick is remembered for that connection. Where there is nothing to pick the header draws the surface's name instead: with AI features off in **Settings > AI** the pane holds the inspector alone, and Agent mode fixes it on **Result**. - -The ellipsis at the trailing end holds that surface's own commands: **Fields** and **JSON** plus the JSON reader's options on the inspector, **New Conversation**, **Conversation History** and **Clear Recents** on the assistant, and **SQL** and **Results** on the result column. - -`Cmd+Option+I` opens and closes the pane, and `Cmd+Option+A` swaps an open inspector for the assistant rather than closing the column. **View > Show Inspector** and **View > Show Assistant** are the same two, each reading **Hide** once its surface is on screen. - -## Related - -- [Query Tabs](/features/tabs) for the strip under the toolbar and what a tab is bound to -- [Connections strip](/features/workspace-rail) for the narrow strip on the leading edge -- [Keyboard Shortcuts](/features/keyboard-shortcuts) for every default binding diff --git a/docs/features/copy-objects.mdx b/docs/features/copy-objects.mdx deleted file mode 100644 index c52ce8a99d..0000000000 --- a/docs/features/copy-objects.mdx +++ /dev/null @@ -1,326 +0,0 @@ ---- -title: Copy and duplicate -description: Copy tables and databases to another database, another connection, or another engine, structure, data, or both ---- - -Right-click what you want and pick where it goes. Rows stream from one connection to the other in -batches, so a table larger than memory copies at the same cost as a small one, and nothing is -written until you have read the script. - -## Opening it - - - - A table, a view, or a multiple selection of them offers **Copy To…**. A database row offers - **Copy To…** and **Duplicate Database…**. A schema row offers **Copy To…**. - - **Database > Copy To…** and **Database > Duplicate Database…** reach the same sheet from the - keyboard, acting on the database being browsed. - - - **Copy To…** opens a picker that walks connection, then database, then schema, the same picker - Compare & Sync uses. Each level has a search field. **Duplicate Database…** asks for a name - instead and creates the database on the connection you right-clicked. - - - Structure only, data only, or both. Tick the objects taking part. The count under the list - counts the whole selection, not the part the search is showing. - - The funnel beside a table narrows the rows it contributes. See - [Copying part of a table](#copying-part-of-a-table). - - - **Continue** reads both databases and shows the DDL that will run, the rows each table expects, - and anything left out. **Copy** is the first thing that writes. - - - -## What each object carries - -| Kind | Structure | Data | -|---|---|---| -| Tables | Columns, primary key, indexes, foreign keys, storage engine and collation | Every row, streamed in batches | -| Views | The source's definition | None to carry | -| Materialized views | The source's definition, plus its indexes into PostgreSQL and PGlite | None to carry | -| Procedures, functions | The source's definition | None to carry | -| Triggers | The source's definition | None to carry | - -A data-only copy leaves views, routines and triggers out and says so in the review step: they hold -no rows. - -A materialized view's indexes are created right after the view. A target other than PostgreSQL or -PGlite gets the view without them, and so does a duplicate whose index statements would name a -schema other than the view's own; both are listed under **Partly copied**. A view whose indexes the -source refuses to list is left out with the server's reason when the target would take them. - -Within one engine, generated and computed columns are dropped from the write. The server recomputes -them, and every engine that has them rejects an `INSERT` that names one. Crossing to another engine -they arrive as ordinary columns carrying the values they held on the source. - -Within PostgreSQL an index is created from the server's own definition of it: expression keys, -operator classes, sort order, `INCLUDE` columns, storage parameters and a partial index's condition -all arrive as they were. - -An index keeps its type when both ends are the same database type, so a SQL Server `CLUSTERED` index -stays clustered. Between two database types, a PostgreSQL `gin`, `hnsw` or `bloom` index is left out -of a copy to any engine outside PostgreSQL and named in the review step. So are a Redshift `DISTKEY` -and `SORTKEY`, a Snowflake clustering key, and BigQuery clustering and partitioning. - -MySQL, MariaDB, SQL Server and CockroachDB name an index within its table, so several tables can each -have a `user_id` index. PostgreSQL, SQLite, DuckDB and Oracle name it within the schema, so a copy -from the first group into the second puts the table's name in front, as in `orders_user_id`, unless -the index name already has it as a word, as `idx_orders_created_at` does. A name past the target's -limit, 63 bytes or 30 on Oracle, is cut. A name that matches another index in the copy or in the -target schema gets a number at the end, as `orders_user_id_2`. On PostgreSQL and SQLite, so does a name -that matches a table or view there. An index on a table the copy replaces does not count, because the -copy drops that table first. A copy between two databases of the same type keeps every index name the -target schema does not already use. - -## When the target already has the object - -Chosen once, before the run, and applied to every object in it. - -| Choice | Structure and data | Data only | -|---|---|---| -| **Skip it** | The object is left out | The object is left out | -| **Replace it** | The target's object is dropped and built again | The target's rows are removed first | -| **Add rows to it** | The target keeps its structure, rows are appended | Rows are appended | - -On Oracle, **Replace it** replaces a view, routine or trigger with its own `CREATE OR REPLACE` and drops nothing first, so a definition the target refuses leaves the target's object as it was. - -**Add rows to it** writes only the columns both sides have, matched without regard to case. A column -the target does not have is not written; one the source does not have keeps its default. - -Pick **Skip it** unless you are refreshing a copy you made earlier. It is the only one of the three -that cannot lose anything already in the target. - -## Copying part of a table - -The funnel beside a table in the object list opens a `WHERE` and a row limit for that table alone. -Both narrow what the copy reads; neither changes the structure it writes, so a copy set to carry -structure and data still creates every column. - -Write the filter in the source engine's own dialect, without the keyword: `status = 'active'`, -`created_at > '2026-01-01'`. It is one expression. A semicolon means a second statement, and **Copy** -stays disabled until it is gone. - -A filtered table shows `Unknown` for its row count in the review step. A row limit on its own shows -a number, since it is a ceiling the copy will not pass. - -**Replace it** still empties the whole target table, filter or no filter. Pair a filter with **Add -rows to it** when you mean to top up a table rather than rebuild it. - -## Crossing engines - -A table crosses. MySQL to PostgreSQL, SQL Server to MySQL, Oracle to SQLite: its columns, keys and -indexes are said again in the target's own types before any DDL is generated, and its rows are -reshaped where the source and the target spell a value differently. `TINYINT(1)` arrives as `BOOLEAN`, -`LONGTEXT` as `text`, `INT UNSIGNED` as `BIGINT`, and a PostgreSQL `t` arrives in a MySQL -`TINYINT(1)` as `1`. - -Lengths, precision, scale and fractional seconds cross with the type. From PostgreSQL to MySQL, -`character varying(50)` arrives as `VARCHAR(50)`, `numeric(10,2)` as `DECIMAL(10, 2)` and -`timestamp(3) with time zone` as `DATETIME(3)`. A PostgreSQL `timestamp` or `time` declared without a -precision stores microseconds, so it arrives with 6: `DATETIME(6)` on MySQL, `DATETIME2(6)` on SQL -Server. ClickHouse counts no characters, so text of any declared length arrives there as `String`. - -A scale the target cannot declare keeps the digits the values have. `numeric(5,-2)` holds whole -hundreds and arrives on MySQL as `DECIMAL(7, 0)`, and `numeric(3,5)` holds fractions under 0.01 and -arrives as `DECIMAL(5, 5)`, both marked widened. Oracle declares both as written. MySQL keeps at most -30 digits after the point, so `numeric(40,35)` arrives as `DECIMAL(35, 30)`, marked approximated. - -A PostgreSQL `numeric` declared without a precision has no limit, and most engines need one. It -arrives at the target's widest, marked approximated: `DECIMAL(65, 30)` on MySQL, `DECIMAL(38, 18)` on -SQL Server and DuckDB, `Decimal(76, 30)` on ClickHouse, a bare `NUMBER` on Oracle. A value with more -digits than that is rounded or refused. To keep it exact, declare the precision on the source before -copying, or create the target table yourself and use **Add rows to it**. - -Nothing is dropped for want of a type. A type the target does not have becomes its widest text -column, so the values arrive as text rather than the column going missing. The target server's -version counts too: a JSON column crossing into PostgreSQL arrives as `jsonb` from 9.4, `json` on 9.2 -and 9.3, and `text` on 9.1. - -A kept length can pass a limit the source does not have. Each change in this table is a line in the -review step. - -| Target | Limit | What the copy does | -|---|---|---| -| MySQL, MariaDB | A primary key, foreign key or index over 3,072 bytes, counting 4 bytes a character | Cuts the widest key column to `VARCHAR(255)`, then divides the 3,072 bytes between the key's text columns. An index column gets a 255-character prefix instead, and an index that still does not fit is left out | -| MySQL, MariaDB | A row over 65,535 bytes, or an InnoDB record over 8,126. MySQL counts a text column in the record as 41 bytes at most, MariaDB counts one of up to 63 characters whole | Creates the widest column as `TEXT` until the table fits. Primary and foreign key columns never move, and indexed columns move last | -| SQL Server | A primary key over 900 bytes, an index over 1,700, counting 2 bytes a character | Cuts a primary key column over 450 characters to `NVARCHAR(450)`, and turns `NCHAR` key columns that pass the limit together into `NVARCHAR`. An index whose `NCHAR` columns pass 1,700 bytes is left out | -| SQL Server | `NVARCHAR(n)` counts UTF-16 units, so most emoji count as two | Keeps `n`. One line per table names the columns where a value full of emoji can be refused | -| Oracle | 4,000 bytes in a `VARCHAR2`, 2,000 in a `CHAR` | Counts lengths in characters, `VARCHAR2(50 CHAR)`. A column over 1,000 characters becomes a `CLOB` | -| Oracle | A primary key or index over 6,398 bytes at the default 8 KB block, counting 4 bytes a character and 1 a column | Divides the 6,398 bytes between the primary key's text columns. An index that does not fit is left out | - -A key column cut shorter refuses a row whose key is longer, and a unique index cut to a prefix refuses -two rows that agree for their first 255 characters. SQL Server creates a key of `NVARCHAR` columns -longer than 900 bytes and refuses a row whose key passes that. MySQL creates a table its record count -allows and refuses a row that fills it: 41 `VARCHAR(50)` columns each holding 50 emoji pass 8,126 -bytes. If your data holds such rows, create the target table yourself with types that fit, then use -**Add rows to it**. - -The review step lists every column and index the crossing changed, one line each, with the table it -belongs to. A type change too long for the pane is cut in the middle; hover over it to read the whole -type. It is the only place those conversions appear, and it is shown before **Copy** does anything. - -| Line | Means | What to do | -|---|---|---| -| An arrow, in grey | Widened. `uuid` to `CHAR(36)`, `INT UNSIGNED` to `BIGINT`. Every source value still fits | Nothing | -| A triangle, in orange | Approximated. A time zone dropped, an `ENUM` become text, an index left out | Read it. This is where a copy loses something | - -Views, materialized views, procedures, functions and triggers do not cross. Their definitions are -the source engine's own SQL, unparsed and uncorrected, so each is listed under **Left out** and the -tables beside it copy anyway. - -Character sets, collations, storage engines and `ON UPDATE CURRENT_TIMESTAMP` are dropped; each -names something only the source engine has. A MySQL `0000-00-00` is written as `NULL`, the only -value the rest accept for it. - -MySQL and MariaDB count as one engine, and so do PostgreSQL, Redshift, CockroachDB and PGlite. A copy -between two of those writes every column as the source declares it and reports no conversion for any -of them. Its indexes follow the [index rules](#what-each-object-carries) for two database types, so a -copy from Redshift leaves out `DISTKEY` and `SORTKEY` and names both in the review step. - -## When both sides are one connection - -A copy whose source and target are the same connection is done by the server: one -`INSERT INTO … SELECT` per table, shown in the review step in place of the query it would otherwise -have walked. The rows never reach the app, which on a large table is the difference between minutes -and seconds. - -Two things go with it. There is no row-by-row progress, and **Stop** cannot interrupt a statement the -server has already started. The plan says so above the script. - -PostgreSQL takes this path only between two schemas of one database. MySQL, MariaDB, SQL Server and -ClickHouse take it across databases too. Everything else streams through the app as before. - -## The namespace rule - -A database-level copy covers every schema. Each schema's objects are read and written in their own -scope, and a duplicate recreates each of them under the same name in the new database. A new -database arrives with only the schema its engine gives it, so a duplicate creates the rest before -its first table. PostgreSQL does this; on an engine where a schema is not something one statement -can make, such as Oracle, where it is a user, the schemas have to be there first. - -Views, routines and triggers copy only where both sides share a namespace, which is the schema on -PostgreSQL and SQL Server and the database on MySQL and MariaDB. Their definition is the source's -own SQL text and nothing rewrites the objects it names, so anywhere else it would point back at the -source. They are left out with the reason shown, which is why duplicating a MySQL database carries -its tables and not its views. - -A driver that answers with a view's `SELECT` rather than its `CREATE`, which ClickHouse, Dameng and -BigQuery do, has that view left out for the same reason. A definition the source refuses to return -is left out with the server's reason. So is every selected procedure and function, or every selected -trigger, when the source cannot list that kind; the rest of the copy goes ahead. - -## Ordering and foreign keys - -Everything is torn down children first, so a foreign key is gone before the table it points at, then -built parents first, both in one pass so a stop between them cannot leave objects dropped with -nothing put back. Rows are copied after that. Triggers and materialized views go in last: a trigger -installed before the rows fires on the copy itself, and a materialized view is filled at the moment -it is created. Its indexes follow it, and a unique index its rows violate fails with the view named -in the result. - -A copied foreign key is repointed at the copy. A key that referenced the source's own schema -references the target's afterwards, so the duplicate stands on its own; one that referenced a third -schema is left as it was. - -Two tables that reference each other cannot be ordered. The second `CREATE TABLE` fails, its error -appears in the result, and the rest of the copy is unaffected. - -## Stopping a copy - -**Stop** takes effect at the next batch boundary. Each table is its own transaction where the engine -supports one, so the table being copied rolls back and the tables already finished stay. A driver -blocked inside a network call finishes that batch first. - -A Replace that removes the target's rows is the exception: every table is emptied before any of them -is filled, so that a cascading foreign key cannot reach a table you did not select, and the whole -data phase is then one transaction. Stopping it, or a failure under **Stop and roll back**, puts -every row back. **Skip and continue** promises no rollback, so there the clearing stands. - -A stopped copy reports only what was committed. The table it was in the middle of counts as neither -copied nor failed. - -When the target connection already holds a transaction, the copy runs inside it and opens none of its own. Nothing it wrote is committed until you commit that transaction, and the result says so. - -## Duplicating a database - -**Duplicate Database…** creates the new database with the character set and collation the engine -offers, then copies every object into it. The name is prefilled with the source name plus `_copy`. - -Available on engines that have databases to create. SQLite, DuckDB and the other single-file engines -have none, so the command does not appear on them. - -## Limitations - -Copying is SQL only. MongoDB, Redis, DynamoDB, Elasticsearch, Typesense, Kafka, etcd and SurrealDB have no -`CREATE TABLE` and no row writer to copy through, so neither command appears on them, and neither -end of a copy may be one of them. - -A crossing translates types, defaults and indexes, and nothing else. Check constraints, partitioning, -table options, an index's `INCLUDE` columns and anything the source expressed as an expression, -including an index over one, stay behind. A copied table is a table with the same columns and the -same rows, not a replica of the original. - -Redshift takes PostgreSQL's type names and not all of PostgreSQL's types. A copy from PostgreSQL -into it is treated as one engine and passes its types through, so a `jsonb`, `uuid` or array column -is refused by the server with its own error. Change those columns to `varchar` on the source, or -create the target table first and use **Add rows to it**. - -A copy from PostgreSQL into an older PostgreSQL passes its types through the same way, so a `jsonb` -column is refused by a server before 9.4. Create the target table first with `json` or `text`, then -use **Add rows to it**. - -A PostgreSQL column typed with a domain crosses as the domain's base type without its modifier. A -`price` domain over `numeric(12,2)` arrives on MySQL as `DECIMAL(65, 30)`, and a domain over -`varchar(20)` as `LONGTEXT`. Create the target table with the types you want, then use **Add rows to -it**. - -A PostgreSQL column or index that uses an extension or user-defined type, operator class or function, -such as a PostGIS `geometry`, an enum, a `gin_trgm_ops` index, or a `DEFAULT` or index condition that -calls `ST_GeomFromText`, is created naming each one in the schema it lives in on the source: -`public.geometry(Point,4326)`, `email public.gin_trgm_ops`. A target database without them in that -schema refuses the table with `type "…" does not exist`, `function … does not exist` or -`operator class "…" does not exist for access method "…"`. Install the extension or create the type -there first, in the same schema. - -A PostgreSQL column declared with a collation of its own keeps it, named with its schema: -`COLLATE pg_catalog."C"`, `COLLATE app."Case Insens"`. Collation names differ between operating -systems and between builds with and without ICU, so a target that lacks one refuses the table with -`collation "…" for encoding "UTF8" does not exist`, or `schema "…" does not exist` for a collation -created in a schema the target does not have. Create the collation on the target first, in the same -schema. - -Duplicate Database needs a driver that creates one. Where it does not, the command still appears and -the sheet names the engine that cannot rather than failing part way in. - -Identity and auto-increment values are written as they are. On SQL Server and on PostgreSQL columns -declared `GENERATED ALWAYS AS IDENTITY`, the server refuses an explicit value and the table's error -appears in the result. Copy the structure, then the data with the identity column removed from the -target, or use **Add rows to it** against a table whose key is plain. - -A PostgreSQL `SERIAL` column's default names a sequence. Within PostgreSQL a sequence in the table's -own schema is copied ahead of the table, and the copied default reads the copy, including a default -that passes the sequence to a function. A sequence in any other schema is not copied: the default -keeps naming it there, and a target database without it refuses the table with -`relation "…" does not exist`. Create that sequence on the target first. Crossing to another engine -there is no sequence to copy, so the column is created with that engine's own auto-increment and the -counter starts where the target starts it, not where the source left off. - -Two databases on one connection can be copied between only where the driver opens a second -connection of its own. DuckDB and PGlite hold their database inside the driver instance, so the -sheet refuses and names the reason. - -Dameng, SAP HANA and Teradata read a table whole before the first batch is written, because their -drivers do not stream rows. On those three, a table larger than memory is a table this cannot copy, -and **Stop** waits for the read. - -Read-only connections are dimmed in the target picker with the reason shown. - -## Related - -- [Compare & Sync](/features/compare-sync) for bringing two databases that both already exist into - line, statement by statement -- [Import and export](/features/import-export) for moving data through a file diff --git a/docs/features/data-files.mdx b/docs/features/data-files.mdx deleted file mode 100644 index 260f15a07d..0000000000 --- a/docs/features/data-files.mdx +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Data Files -description: Open CSV, TSV, JSON and Excel files in their own window, then filter, clean up, save or export them without importing anything ---- - -A spreadsheet export gets the same tools as a table, minus the database: the data grid, the table filter bar, a find bar and Row Details, all working on the file itself in a window of its own. Nothing is imported and no connection is needed. - - - orders.csv open with Hanoi typed in the toolbar search field, every visible row in Hanoi, and 2,498 of 20,000 rows in the status bar - orders.csv open with Hanoi typed in the toolbar search field, every visible row in Hanoi, and 2,498 of 20,000 rows in the status bar - - -## Files that open - -| File | Opens | Save writes | -|---|---|---| -| `.csv`, `.tsv`, `.psv` | Editable | The same format, with the detected delimiter, encoding, line ending and BOM | -| `.txt`, `.dat` | Editable, delimiter detected from the contents | The same format | -| `.json` (an array of objects) | Editable, one column per key | JSON | -| `.jsonl`, `.ndjson` | Editable, one object per line | JSON Lines | -| `.xlsx` | Read-only, one tab per sheet | Save As a CSV, TSV or pipe-separated file | -| `.csv.gz` and other compressed files | Read-only | Save As an uncompressed file | - -Open a file from Finder with a double-click or **Open With > TablePro**, drag it onto the Dock icon, or choose **File > Open File…** (`Cmd+O`). `.json` and `.ndjson` files open here even when the DuckDB driver is installed; `.parquet` files still open as a DuckDB connection. - -The window works on a private copy of the file taken when it opens, so another app writing the file cannot change what you are looking at. When that happens, the window reloads if you have no unsaved edits, and asks whether to reload or keep your changes if you do. - -## How a delimited file is read - -| Setting | How it is chosen | -|---|---| -| Encoding | A byte order mark picks UTF-8, UTF-16 LE or UTF-16 BE. Without one, 64 KB starting at the first line with a non-ASCII byte decide: valid UTF-8 reads as UTF-8, and anything else is matched against Shift JIS, EUC-JP, GB 18030, Big5, EUC-KR and Windows-1252. UTF-16 without a mark is recognized by its zero bytes | -| Delimiter | Comma, tab, semicolon or pipe, whichever appears most often outside quotes in the first 64 KB. `.tsv` always uses tab and `.psv` always uses pipe | -| Line ending | CRLF, LF or CR, whichever comes first outside quotes | -| Header row | Row one is the header when at least half of its cells are text rather than numbers | -| Column types | Integer, decimal, boolean, date or text, from the first 200 non-blank values in each column | - -Two columns with the same header, or an empty one, get unique names such as `amount (2)` and `Column 3`. A row with more fields than the first row widens the table, and the status bar counts how many rows differ. A byte the encoding cannot read shows as `�`. In a Shift JIS, EUC-JP, GB 18030, Big5, EUC-KR or UTF-16 file, the status bar also counts the lines that hold one. - -When a guess is wrong, choose **Edit > Data > File Properties…** to set the delimiter, quote, escape character, encoding, line ending and header row by hand, then click **Reload**. The window keeps those settings when it reloads the file after another app changes it. To flip only the header, choose **Edit > Data > Switch First Row Between Header and Data** (`Cmd+Shift+H`). **Change Type** on a column header overrides an inferred type, which changes how the column sorts, filters and appears in statistics, never what is written to disk. - -## Filter, search and sort - -`Cmd+Shift+F` shows the filter bar. It is the filter bar a table tab uses, run over the file instead of a database: all 18 operators, **Match all** or **Match any**, a checkbox per row, **Match Case**, and saved presets. It has no raw SQL row. See [Filtering](/features/filtering) for how each operator reads a value. - -A comparison is numeric whenever the cell and the value both read as numbers, so `> 50` keeps `100` and drops `9`. Anything else compares as text. A CSV cell is never NULL, so **is NULL** matches nothing and **is empty** matches blank cells. In JSON, a `null` value and a missing key are both NULL. - -Right-click a cell and choose **Filter** to add a condition built from its value. The **Search All Columns** field in the toolbar keeps the rows where any column contains the text, ignoring case and accents, and works alongside the filter bar. - -Click a column header to sort by it, and `Shift`-click another header to add a tie-breaker. Numbers sort by value, text sorts naturally so `Item 2` comes before `Item 10`, and blank cells always sort last. - -Changing a filter, the search or the sort goes back to page 1. - -## Find and replace - -`Cmd+F` opens the find bar and `Cmd+Option+F` opens it with a replace field. It searches every row in the file, not the current page, and counts the matches as `3 of 1,204`. `Return` or `Cmd+G` moves to the next match and reveals it; `Cmd+Shift+G` goes back. - -The options menu beside the field turns on **Match Case**, **Whole Words** and **Regular Expression**. With a regular expression, `$1` in the replacement inserts the first capture group. - -**Replace All** changes every match in one step, so one `Cmd+Z` puts all of them back. To search one column, right-click its header and choose **Replace in Column…**. Hidden columns are not searched. - -## Column statistics - - - Statistics popover under the status column listing count, empty, distinct and length, then four top values with their counts and shares - Statistics popover under the status column listing count, empty, distinct and length, then four top values with their counts and shares - - -Right-click a column header and choose **Column Statistics…**, or select a cell and choose **Edit > Data > Column Statistics…**. The popover shows: - -- the row count, empty cells and distinct values -- minimum, maximum, sum, mean and median for a number column, plus how many cells are not numbers -- the earliest and latest value for a date column -- the shortest and longest value, in characters -- the most common values with their counts, the first ten until you click **Show All** - -Click a value in the list to filter the file to it. - -To add up a few values instead, select the cells: the status bar shows their sum, average and count, as in the [data grid](/features/data-grid#selection-summary). That summary covers the current page; Column Statistics covers every row that passes the filter. - -## Row details - -`Cmd+Option+I` opens Row Details beside the grid. It shows the selected row as a list of fields, each editable in place. An edit is committed when you stop typing, and `Cmd+Z` undoes it like any other edit. - -## Edit rows and columns - -Double-click a cell, or select it and press `Return`, to edit it. `Cmd+Shift+N` adds a row at the end; **Insert Row Above** and **Insert Row Below** are on a row's context menu. Select rows and press `Cmd+Delete` to delete them. - -`Cmd+C` copies the selected rows as tab-separated text. With a cell selected, `Cmd+V` pastes a tab-separated block into the cells from there rightward and down. With no cell selected, it adds the lines as new rows, and a block wider than the table adds the columns it needs. - -Right-click a column header, or use the **Columns** menu in the toolbar, to rename, insert, split, merge, retype or delete columns. **Split Column…** splits every value at a separator or a regular expression, and **Merge Columns…** joins a column with the one to its right. - -## Clean up data - -These commands are on **Edit > Data** and on the column header's context menu. With a whole column selected they change every visible row; with cells selected they change only those cells. Each one is a single undo step. - -| Command | Does | -|---|---| -| **Fill Down** | Copies the first targeted cell's value into the others | -| **Set Cells to Value…** | Writes one value into every targeted cell | -| **Trim Whitespace** | Removes spaces, tabs and line breaks at the start and end of each value | -| **Change Case** | Converts to UPPERCASE, lowercase or Title Case | -| **Replace in Column…** | Opens the find bar limited to that column | -| **Remove Duplicate Rows…** | Keeps the first of each group of rows that match on the columns you choose, optionally ignoring case and surrounding whitespace | - -Rows hidden by a filter or search are left alone by every command here. - -## Large files - -A file opens without being read into memory: the rows are indexed, and cells are read when they are shown, filtered or sorted. Rows appear a page at a time, at the **Default page size** from **Settings > Data**, and the status bar has the page controls. - -Filtering, sorting, searching, statistics and bulk edits run in the background. When one takes longer than a moment, the status bar shows its progress and a **Cancel** button; a cancelled bulk edit changes nothing. Editing is paused while a bulk edit runs. - -On a 12-core Apple silicon Mac, a 1 GB CSV with 6.9 million rows opens in about 0.4 s, filters in about 0.3 s, and searches every column or sorts in about a second. - -## JSON files - -Each object is a row and each key is a column. A key missing from an object reads as NULL, the same as `null`, and nested objects and arrays appear as their JSON text. - -An edited value keeps its JSON type. Type `42` over a number and it stays a number; type text over it and it becomes a string. A value typed into an empty cell takes the column's type, so a number typed into a number column is saved as a number. Text typed over an object or array that is not valid JSON is saved as a string. - -Save rewrites only the objects you changed, and only the values in them you edited, so formatting, key order and number spellings elsewhere are kept exactly. Renaming or deleting a column renames or removes that key in every object. - -## Excel workbooks - -Every sheet in an `.xlsx` gets a tab under the grid; hidden sheets are marked as hidden. A sheet is read when you first open its tab. Dates are shown as ISO dates and times, and numbers keep the digits stored in the file. - -A workbook is read-only. To keep a sheet, choose **File > Save As** and pick CSV, or export it. - -## Save and export - -`Cmd+S` writes the file back in its own format. Rows you did not touch are copied byte for byte, so a large file with a handful of edits saves quickly and differs from the original only where you changed it. If a value holds a character the file's encoding cannot store, the save stops, names the row as the grid numbers it and the column by name, and selects that row. In a Shift JIS or EUC-JP file, `¥` saves as the byte Japanese Windows shows as `¥`, which reads back as `\`. Choose **File > Save As** and set **Text Encoding** to UTF-8 to keep it; later saves keep the new encoding. - -**File > Save As** (`Cmd+Shift+S`) writes a delimited file as CSV, TSV or pipe-separated text, and a JSON file as JSON, JSON Lines, CSV or TSV. - -To write another format, choose **File > Export > Export Results…**, or right-click the grid and choose **Export Results…**. The export dialog offers every installed export format that works without a database, including CSV, JSON, SQL, XLSX, Markdown and HTML, and exports all rows, the rows passing the current filter, or the selected rows. - -### Import into a table - -To load the file into a database, open a connection first, then choose **Edit > Data > Import into Table…** and pick the connection. Its import sheet opens with the rows of this file, unsaved edits included, and maps them onto a table the usual way. Read-only connections are not offered. See [Import and Export](/features/import-export). diff --git a/docs/features/data-grid.mdx b/docs/features/data-grid.mdx deleted file mode 100644 index 0fe209a969..0000000000 --- a/docs/features/data-grid.mdx +++ /dev/null @@ -1,218 +0,0 @@ ---- -title: Data Grid -description: Sort, size, hide, highlight, chart, map and copy the rows a table or a query puts in the grid ---- - -Most of the grid's controls sit in the status bar beneath it. The view switcher is at the leading edge, followed by the row count and, while cells are selected, their [sum](#selection-summary). The columns, highlight, filter and page buttons sit at the trailing edge. - -A narrow window trims that bar rather than cutting it off. The Columns and Filters buttons drop their titles first, and **First** and **Last** leave with them; the Query menu still carries all four page commands. Narrower still, the view switcher becomes a pull-down naming the current mode and rows-per-page moves into the page indicator's menu. Everything else keeps its place. - - - Data grid - Data grid - - -## Sorting - -Click a header to cycle through the first direction, its opposite, and off. `Shift`-click a second header to sort by both. Sorting re-runs the query with a new `ORDER BY`, so it orders the whole table rather than the page in front of you. In a query tab that query is the one statement behind the result, never the rest of the script. - -A Cassandra or ScyllaDB table has no header sort, and a query result there sorts the rows it holds. See [Cassandra](/databases/cassandra#browsing-and-editing). - -**Sort direction** in [Settings > Data](/customization/data-settings) decides which way that first click goes. It is Ascending out of the box; set it to Descending to open on the newest rows and reach them in one click. - -To sort on open, set **Default row sort** to **Primary key** or **First column**; out of the box the order is the server's. That order is marked with a dimmed arrow rather than the solid one a click leaves, so a table you have not sorted does not look sorted. Clicking the same header reverses it, and **Don't Sort** in the header menu returns to the server's order and stays there. - - -Sorting on a `BLOB`, `JSON`, or spatial column the server cannot order on fails the query. Sort on another column, or set **Default row sort** back to the engine's order. - - -## Filtering - -The header menu carries two filters that answer different questions. - -| Header menu item | What it filters | -|---|---| -| **Filter Values…**, or the funnel icon on hover | The rows already loaded, without a re-query | -| **Filter with column** | The whole table. It adds a row to the [filter bar](/features/filtering) and opens it | - -The cell menu has a third: **Filter** turns the clicked cell's value into a condition such as `status = “paid”` and runs it against the whole table. [Filter by cell value](/features/filtering#filter-by-cell-value) lists what it offers. - -**Filter Values…** lists every value in the column with its count, NULL and empty among them. Select the values to keep and click **Apply**. To keep one value out of hundreds, type part of it in the search field and press `Return`: the search selects every value that contains what you typed and leaves out the rest, so `paid` keeps `unpaid` too. **Select All Results** selects or deselects only the values found, and clearing the search brings back what was selected before it. Filter several columns and a row has to match all of them; **Clear Value Filter** and **Clear All Value Filters** are on the same menu. - - - Value filter popover - Value filter popover - - -A value filter stays until you clear it or the tab shows something else: another table, a query you run, or another result. Saving, `Cmd+R`, sorting, paging, the filter bar and **Fetch All** keep it and apply it to the rows they load. Hiding a filtered column keeps it filtered. Edit a cell so its row no longer matches and the row stays where it is until the rows load again. The filter also runs again when you change it or the column's display format. - -## Highlighting - -Right-click a cell and open **Highlight**. A color under **Rows Where status = “paid”** tints every row holding that value; a color under **Cells Where status = “paid”** tints only that cell. The palette marks the color a matching rule already uses, and **Remove Highlight** takes the rule away. - -For anything other than an exact match, click the highlighter button in the status bar or choose **View > Highlight Rules**. A rule is a column, an operator from the [filter bar](/features/filtering), a value, a color, and **Row** or **Cell**. **Highlight Values…** in the header menu starts a rule on that column. A rule left without a value is dropped when the popover closes. A rule is marked in the popover, and colors nothing, when its column is not in this result or when its regular expression will not compile. - - - Highlight Rules popover listing three rules over an Invoice grid with tinted rows and cells - Highlight Rules popover listing three rules over an Invoice grid with tinted rows and cells - - -Rules run top to bottom and the first match colors the row, so drag the rule that should win to the top. A new rule picked from the cell menu goes in first. Picking a color for a rule that is already in the list changes its color and leaves it where it is. A cell rule tints its own cell over the row's color. Rules read the stored value rather than the text a **Display As** format shows: a numeric column compares as numbers, a boolean column accepts `true`, `1`, `t` and `yes` alike, and `NULL` never satisfies a comparison: match it with **is NULL** or **is empty**. - -A row waiting to be inserted or deleted keeps its [change tracking](/features/change-tracking) tint over any rule, and a selected row shows the selection. On a table you edit, give row rules a color other than the green and red those tints use, so a highlight never reads as a pending change. - -Rules belong to the table, scoped to the connection, database, and schema, and follow the table through a rename. A query result that comes from one table uses that table's rules. Rules on any other query result are not saved, and go when the tab closes or the app quits. Saved rules stay on this Mac and do not sync. - -## Columns - -Drag a border to resize a column, or double-click it to fit the content; **Size to Fit** and **Size All Columns to Fit** on the header menu do the same. A fitted column stops at half the visible grid width. - -Hide columns from the columns button in the status bar or from the header menu. A hidden column is not fetched, so hiding a large one makes the table load faster; the primary key and the sorted column are fetched either way. On a schemaless store the popover lists every field the grid can draw, including ones that turn up only in later documents. - -Widths, order, and hidden columns are remembered per table, scoped to the connection, database, and schema; **Reset Columns** in the popover puts them back. Right-click a header and choose **Display As** to read a column's stored values in a different form; [Cell and Row Viewers](/features/json-viewer) lists the formats. - -### Jump to a column - -`Cmd+Shift+J`, or **Edit > Find > Jump to Column**, lists every column of the result with its type and its place in the grid. Type part of a name; matching is fuzzy, so `crat` finds `created_at`. `Return` scrolls the selected column into view and puts the cell cursor in it, on the selected row. A hidden column is listed too, marked **Hidden**, and jumping to it shows it first. **Jump to Column** at the foot of the columns popover opens the same panel with the popover's search carried over. - - - A floating panel over the data grid listing columns with their types and positions - A floating panel over the data grid listing columns with their types and positions - - -## Foreign keys - -A foreign key cell carries an arrow on its right edge. Click it to open the referenced table filtered to the matching row, or right-click for **Preview Referenced Row**, which shows that row in a popover. The reference arrives in a tab of its own, so the rows you were reading stay open behind it. A tab already showing that same reference comes forward instead of a second one opening, and `Cmd`-click opens a new tab even then. - -Clicking an arrow that points back into the table the tab already shows re-filters that tab rather than opening another. `Ctrl+Cmd+[` returns to the rows it left. - -Double-click a foreign key cell, press `Return` on it, or open its right-click menu to pick the key rather than type it. The picker lists rows from the referenced table, each key with a label beside it, and narrows as you type. `Return` in the search field commits the text as typed, which covers a key the search has not turned up. **Set NULL** appears on a nullable column. - -**Label** at the foot of the picker opens the referenced table's columns. Tick one or several: a parent whose rows are unique only on `descrizione` and `marchio` together reads as six identical rows under either column alone. Ticked columns show beside the key in the table's own order, and the search matches any of them. **None** clears them and lists keys on their own. A date or a number shows beside the key but carries no text search, so a picker with no text column to search says so rather than reporting no matches. The choice is remembered for the referenced table, so every column pointing at it shows the same ones. A search fetches the first 50 matches, which keeps the list quick on a large table. - -A column of a foreign key that spans several columns keeps the text editor. The picker sets one column, and a key it offered might not pair with the values the row holds in the constraint's other columns. Type the key, or open the referenced table and read the pair off it. - - - Foreign key value picker listing employee ids with a last and first name beside each - Foreign key value picker listing employee ids with a last and first name beside each - - -The inspector's **JSON** view follows a key without leaving the row: see [Row as JSON](/features/json-viewer#row-as-json). - -Step back with **View > Back** (`Ctrl+Cmd+[`) and **View > Forward** (`Ctrl+Cmd+]`), or with the same pair under **Actions** in the toolbar. Add the buttons to the toolbar with [Customize Toolbar](/features/connection-window#add-a-control-back) to keep them in reach. Back restores the table you came from as you left it: same filters, sort, page, and selected row. Each tab keeps its own history and Back never closes a tab. With unsaved edits in the tab, Back asks to discard them first; with a staged structure edit it stands down, because that work cannot be discarded from here. - - - Foreign key lookup - Foreign key lookup - - -## Inspector - -Press `Cmd+Option+I`, or click the button at the trailing end of the toolbar, to open the inspector beside the grid. The table and which row of how many is selected sit under the [pane's header](/features/connection-window#the-trailing-pane), and **Fields** and **JSON** are the two renderings of that row, picked from the header's ellipsis menu. [Cell and Row Viewers](/features/json-viewer) covers both. Turn on **Auto-show inspector on row select** in [Settings > Data](/customization/data-settings) to open it whenever a row is picked, in a query result as well as a table. - -Short values sit on one line, label leading and value trailing. Long text, JSON, PHP, binary and images take the full width. Long text and JSON carry a resize handle; PHP, binary and images draw at a fixed height. `Tab` and `Shift+Tab` move between fields; the menu at the trailing edge of each field carries **Set NULL**, **Set DEFAULT**, **Set EMPTY** and **SQL Functions**, with `Ctrl+Option+N` and `Ctrl+Option+D` as shortcuts for the first two. Search filters the list by column name or stored value, and the pencil button narrows it to fields already edited. - -With no row selected and a table open, the inspector shows that table's size, row count, engine, collation and timestamps, as far as the database reports them. They are read only while the inspector is open. - - - Inspector listing a row's columns beside a data grid - Inspector listing a row's columns beside a data grid - - -## Find in the loaded rows - -`Cmd+F` opens the find bar above the grid. `Return` and `Cmd+G` step forward, `Cmd+Shift+G` back, `Escape` clears the term and then closes the bar. Matching ignores case and accents and runs over the text as displayed; spatial columns are skipped, and so is a binary column still showing hex. The counter says which rows were searched: - -| What you see | What it means | -|---|---| -| `3 of 12` | Every row is loaded, so this is the whole table | -| `3 of 12 on this page` | More rows exist that were never fetched | -| `No matches` | Every row is loaded and none match | -| `Not on this page` | None of the loaded rows match, and more rows exist | - -**Search All Rows** turns the term into a filter that runs at the database, covering the whole table. It replaces the tab's filters, so it appears only when none are applied; with a filter set, add the term to the filter bar instead. Cassandra and ScyllaDB cannot match any one of several columns, so it never appears there. - -## Pages and row counts - -A table tab pages. The status bar offers 5, 10, 20, 100, 500 or 1,000 rows per page, **Custom…** up to 1,000,000, and **All rows…**, plus First, Previous, Next and Last; `Cmd+[` and `Cmd+]` step pages, and clicking the page indicator (`3 / 12`) jumps to a page number. In a narrow window that indicator is a menu carrying **Go to page…** and the row-count choices together. The size a table opens with is a [data setting](/customization/data-settings). - -A large table shows an estimated total prefixed with `~` instead of running a slow `COUNT(*)`. **Count Exactly** beside it runs the real count in the background, and `Cmd+.` cancels that. While a total is an estimate, **Last** and **All rows…** stay unavailable and **Next** works as long as the last page came back full. - -Cassandra and ScyllaDB have no estimate, and their count reads every partition, so a table there shows no total until you click **Count Exactly**. - -A query tab does not page. It stops at the [row cap](/customization/data-settings) instead and offers **Fetch All** to load the rest, and a query carrying its own `LIMIT`, `FETCH FIRST`, or `TOP` is never capped. `Cmd+.` cancels a running query or a Fetch All. - -Some engines cannot skip rows and return a fixed maximum from one query. On those, a table tab shows its leading rows only: First, Previous, Next, Last and **All rows…** are gone, the rows-per-page menu stops at the engine's maximum, and the total is counted only when you click **Count Exactly**. Filter or sort to decide which rows load. [Cloudflare R2 SQL](/databases/cloudflare-r2-sql#pagination) works this way. - -## Reloading - -`Cmd+R` reads the rows again and leaves the grid where it was. Scrolled down, the row at the top stays at the top, matched by primary key, so a row another session added takes its sorted place without moving what you are reading. At the top of the grid, the grid stays at the top and rows added above come into view. On a table without a primary key, or with a key that repeats, the scroll position holds instead. Selected rows stay selected, matched by primary key; on a table without one, the selection clears. - -On a table tab, sorting, filtering, and changing page or page size start at the first row. The horizontal scroll position stays. - -Saving edits, importing rows, **Insert Document…**, a structure change and **Refresh Materialized View…** reload every tab showing the table they changed, in every window of the connection. The tab in front reloads at once and keeps its place. A tab behind it reloads when you switch to it. A tab with a cell open or unsaved edits is not interrupted. It reloads as soon as the cell is closed and its edits are saved, undone or discarded. Tabs on other tables stay as they are. A SQL file import reaches every table tab of the connection the same way. A statement run from the SQL editor reloads nothing, so press `Cmd+R` on the tabs it touched. - -## Copying - -Click a cell to select it, drag or `Shift`-click for a range, and `Cmd`-click to add or remove a cell. Click a row number for a whole row. For a whole column, `Cmd`-click its header or right-click the header and choose **Select Column**; a plain click on a header sorts. The row-number gutter stays at the left edge on a table wider than the window, so whole rows are still selectable when the columns have scrolled past it. `Shift+Space` widens whatever is selected to every row it touches. Copy acts on the whole selection. - -| Shortcut | Copies | -|---|---| -| `Cmd+C` | The focused cell, or the selected rows as TSV | -| `Cmd+Shift+C` | The selected rows as TSV | -| `Cmd+Option+C` | The same, with a header row | -| `Cmd+Option+J` | The selected rows as JSON | - -Right-click and open **Copy as** for CSV, CSV with Headers, Markdown, and **IN Clause**, which writes `('a', 'b', 'c')` for a `WHERE col IN (…)`. **INSERT Statement(s)** and **UPDATE Statement(s)** appear when the result comes from a single table whose structure has loaded, and never on MongoDB or Redis. UPDATE is also missing on a table without a primary key that has a column no match can compare, such as a SQL Server `sql_variant`. **Copy Column Values** on the header menu copies one column, one value per line. - -Every copy follows the grid as shown: hidden columns are left out, columns keep their current order, and over a cell selection only those columns go out. INSERT and UPDATE write what a save of the same row would. They leave out the columns the server fills in, such as identity and computed columns. INSERT leaves a cell set to `DEFAULT` to the server, and UPDATE assigns it `DEFAULT`. The table name carries its schema. UPDATE finds the row as it is stored, not as you have edited it: by every column of the primary key, whether or not those cells are selected, or on a table without one by every column, hidden ones included, which also changes any identical copy of the row. A row you added and have not saved gets no UPDATE. - -## Selection summary - -Select two or more cells and the status bar shows their **Sum**, **Average** and **Count**. A narrow window shows the sum alone, then only the Σ button. Click it for the full list: **Count**, **Numbers**, **Empty**, **Sum**, **Average**, **Minimum** and **Maximum**, each with a copy button. Right-click inside the selection for **Copy Sum** and **Copy Average**. - -- **Count** is the selected cells that are not NULL. **Average** divides the sum by **Numbers**, the cells that hold a number. -- Integer and decimal columns are summed. A text column is not, so a ZIP code such as `00501` never joins a total. A column with no declared type, such as a SQLite expression, is summed when its value is a plain number. -- `NaN`, `Infinity` and PostgreSQL `money` values are left out and counted under **Not a number**. -- A column shown with **Display As**, such as a Unix timestamp shown as a date, is counted but not summed. -- Rows marked for deletion are left out, so the figures match the table after you save. -- Only loaded rows count. On a paged table or a query that stopped at its row limit, a whole selected column covers the rows on screen, and the popover says so. -- The arithmetic is exact: `0.1` added ten times is `1`, and a `BIGINT UNSIGNED` sum keeps every digit. Copy writes the full value without group separators, ready to paste into SQL. - -## View modes - -Switch between **Data**, **Structure**, **JSON**, **Chart**, **Map**, and **Output** with the switcher at the leading edge of the status bar, or from **View > Result View**. Query tabs have no Structure mode, Map appears only while the result holds a column of drawable geometry, Output appears only when the statement printed something on the server (see [Server output](/features/query-results#server-output)), the mode is remembered per tab, and [Cell and Row Viewers](/features/json-viewer) covers JSON mode. - -### Chart mode - -Needs a [Starter license](/features/licensing). - -Pick a bar, line, area, or scatter chart, then a numeric Y column. X takes row numbers, another numeric column, a date or timestamp column, or a categorical column; an optional text, boolean, enum, or set column splits the result into series. Date and timestamp columns plot on a real time axis, whatever order the rows arrive in. Hover the plot for exact values. - -A chart draws the loaded rows, up to 2,000 points, 20 series, and 50,000 inspected rows, and the toolbar says when it hit one: "Showing the first 2,000 points of 8,431 loaded rows". Null, binary, and unrepresentable axis values are skipped and counted. Grid selection, Find, hidden columns, and value filters do not narrow a chart, and the page controls and **Fetch All** stay in the status bar. Chart type and axes belong to the tab and follow the column names, so they survive a page turn, a sort, and a re-run. - -### Map mode - -**Map** appears when a column of the result holds values the map can read. A `geometry`, `geography` or `geo_shape` column usually does, and so does a MongoDB field holding GeoJSON, which arrives typed as JSON. A result of plain columns never carries the segment, and neither does a spatial column whose values are in a format the reader does not cover. - -Points, lines and polygons draw over Apple's own tiles. Nothing about the result reaches a third-party tile server. Click a shape to select its row, then switch to **Data** to see that row selected in the grid; select rows in the grid and their shapes come back highlighted. **Fit to Result** frames every drawn shape. A result with more than one geometry column gets a picker to choose between them. - -For one row's geometry on its own, select the row and open the inspector: a [geometry field](/features/json-viewer#geometry-fields) there draws that value alone, in any view mode. - -Values are read as WKT, EWKT, WKB and EWKB hex, GeoJSON, ClickHouse point and ring tuples, and all six spellings of an Elasticsearch `geo_point`, so PostGIS, MySQL, MariaDB, MongoDB, Snowflake, Elasticsearch and ClickHouse draw without any setup. The map draws the rows the grid is showing, so a value filter narrows it and the page controls and **Fetch All** stay in the status bar. - -The toolbar says what was drawn and what was not: `Drawing 4,102 shapes in SRID 4326. 88 rows in other coordinate systems are not drawn.` One coordinate system is drawn at a time, whichever most of the column uses. A member of a multi-part geometry that the map cannot place is left out and counted in the same line, and the rest of that value still draws. - -Maps place longitude and latitude. Geographic SRIDs 4326, 4979, 4269, 6318, 4258, 4283, 7844, 4490, 4674 and 6668 draw as stored, with no datum shift. Web Mercator (3857, 900913, 3785, 102100, 102113) is converted, and a column carrying no SRID draws when every coordinate falls inside the range of longitude and latitude. - -Any other SRID, such as the projected 27700 or 32633, draws nothing: the pane names the SRID and asks for `ST_Transform(geom, 4326)`, which produces a result the map draws. Curved and polyhedral geometry (`CIRCULARSTRING`, `CURVEPOLYGON`, `COMPOUNDCURVE`, `MULTICURVE`, `MULTISURFACE`, `POLYHEDRALSURFACE`, `TIN`, `TRIANGLE`) is counted and named rather than drawn; `ST_CurveToLine` converts it. Latitude past 85.05 degrees flattens to that value, so Arctic and Antarctic shapes land in the wrong place. - -The map is one element to VoiceOver and does not publish its shapes, so the grid stays the readable representation of spatial rows. - -SQL Server, Oracle, SpatiaLite, Teradata, Trino, Spanner, Cassandra and BigQuery hand geometry over in formats the reader does not cover yet. Their spatial columns show text and carry no Map segment, because the segment follows what the values are rather than what the column is called. - -## Editing and display - -Double-click a cell to edit it. A long value stays on one line and scrolls sideways rather than wrapping into the column's width. Nothing reaches the database until you save, and [Change Tracking](/features/change-tracking) covers the type-specific editors, the row operations, and what a Save runs. - -NULL renders as styled `NULL` text. That text, the date format, row height, row numbers, and alternate row backgrounds are [data settings](/customization/data-settings). Every shortcut the grid answers to, and how to rebind it, is in [Keyboard Shortcuts](/features/keyboard-shortcuts). diff --git a/docs/features/er-diagram.mdx b/docs/features/er-diagram.mdx deleted file mode 100644 index 0c2bd24d38..0000000000 --- a/docs/features/er-diagram.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: ER Diagram -description: View table relationships as an interactive entity-relationship diagram ---- - -Right-click the empty space below the sidebar’s object list and choose **View ER Diagram**, or use **Database > View ER Diagram**. What opens is the current schema, drawn entirely from the foreign keys the driver reports: tables outside that schema are left out, and a foreign key pointing at one of them is not drawn. - - - ER diagram - ER diagram - - -The diagram is a snapshot taken when the tab opens, and it does not follow later schema changes. After a migration, close the tab and open it again. - -## Reading the diagram - -Each node lists the table's columns with their types and badges the primary and foreign keys. Tables connected by foreign keys share a header color; a table with no relationships stays uncolored. With the system **Differentiate Without Color** setting on, the tint goes and grouping is left to position. - -Edges use crow's foot notation. The referenced end always carries a single bar, "exactly one". The foreign key end shows the cardinality inferred from that column's primary key and unique index data, so an edge from `orders.user_id` to `users.id` forks at `orders` and bars at `users`. - -| Foreign key column | Marker | Meaning | -|--------------------|--------|---------| -| Unique and NOT NULL | bar + bar | One-to-one | -| Unique and nullable | bar + circle | Zero-or-one-to-one | -| Not unique, NOT NULL | fork + bar | Many-to-one | -| Not unique, nullable | fork + circle | Zero-or-many-to-one | - -A foreign key that points at its own table, `employees.manager_id` to `employees.id`, is drawn as a -loop out of the table's right edge and back, with the same two markers. - -## Navigation - -| Action | Input | -|--------|-------| -| Pan | Click and drag on empty space, or scroll wheel / trackpad | -| Zoom | Pinch on trackpad, or Cmd + scroll wheel | -| Zoom in / out | `Cmd+=` and `Cmd+-`, or the **+** and **-** toolbar buttons | -| Fit to window | Fit toolbar button | -| Reset to 100% | Click the zoom percentage label | -| Copy diagram | `Cmd+C` | - -The buttons and keyboard commands step through fixed levels from 5% to 300%. A pinch, Cmd + scroll and Fit reach 1%. - -The keyboard commands act on the diagram while it has focus. Opening or returning to the tab gives it focus unless the sidebar or another pane holds it, and a click anywhere in the diagram takes it back. - -Drag a node to move it, toward the edge of the viewport to auto-scroll. Positions are saved and survive across sessions; **Reset Layout**, the circular arrow button, puts every table back where the automatic layout had it. - -VoiceOver reads the diagram as a layout area of tables. Each table announces its name, its column -count and what it is joined to; step into one to hear each column with its type and whether it is a -primary key, a foreign key, or optional. Selecting a table in the diagram moves the VoiceOver cursor -to it. - -## Compact mode - -The compress button in the toolbar cuts every table down to its primary key and foreign key columns, which is what makes a wide schema fit on screen. - -## Junction tables - -A table whose primary key is made of two foreign keys, `user_roles` and its like, is read as a many-to-many join. **Collapse Junction Tables** is on by default: the junction table is hidden and one many-to-many edge, forked at both ends, joins the two related tables. Turn it off to draw the junction table itself, with its two one-to-many edges and a distinct header icon. The toggle appears only when the schema has junction tables. - -## Export - -**Export as PNG** renders the whole diagram at twice screen resolution with 40 points of padding, whatever the current zoom and scroll position, and saves it as `er-diagram.png`. `Cmd+C` puts the same image on the clipboard. Compact mode and expanded junction tables carry into the render, so set the view up first. - -**Export as SQL** opens a query tab with `CREATE TABLE` and foreign key statements for the diagram's tables in the connection's dialect. SQLite inlines the foreign keys; other engines add them afterwards with `ALTER TABLE … ADD CONSTRAINT`, so the script survives circular references. - -## Database support - -Edges are drawn on MySQL, MariaDB, PostgreSQL, Redshift, CockroachDB, PGlite, SQLite, SQL Server, Oracle, DuckDB, Snowflake, libSQL, and Cloudflare D1. Every other driver reports no foreign keys at all, so the command still opens a diagram and every table in it arrives unconnected. diff --git a/docs/features/explain-visualization.mdx b/docs/features/explain-visualization.mdx deleted file mode 100644 index 2e7e9eafc5..0000000000 --- a/docs/features/explain-visualization.mdx +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: EXPLAIN Visualization -description: View query execution plans as diagrams, trees, or raw output ---- - -**EXPLAIN** estimates. **EXPLAIN ANALYZE** runs the query and reports what actually happened. Choose between them from the dropdown beside **Explain** in the query editor toolbar, or use **Query > Explain Query** (`Cmd+Option+E`); engines with a single variant show a plain button instead. - -The plan arrives as a result beside your query results, so going back to the data costs nothing, and pinning it keeps a plan while you try another query. Typing an `EXPLAIN` statement in the editor and running it opens the same viewer. - - - MySQL EXPLAIN ANALYZE rendered as a plan diagram - MySQL EXPLAIN ANALYZE rendered as a plan diagram - - -## Finding the expensive step - -Every node carries a badge for its own share of the plan's total cost, taken after its children's cost is subtracted, so it marks where the work sits rather than where it accumulates. - -| Badge | Share of the plan's cost | -|---|---| -| Green circle | up to 5% | -| Yellow diamond | over 5% | -| Orange triangle | over 20% | -| Red warning triangle | over 50% | - -Shape escalates alongside color, so the badge still reads with the system **Differentiate Without Color** setting on. A plan that reports no cost at all, which is every SQLite, ClickHouse, DuckDB and Dameng plan, carries no badge rather than a green one. - -Then compare estimate against measurement. A node whose **Rows** estimate is orders of magnitude from its **Actual Rows** is why the planner chose that shape, so refresh the table's statistics before rewriting the query. On PostgreSQL a **Loops** value above 1 means the node ran once per row of its parent, which makes **Actual Time** the per-loop figure rather than the total. - - - EXPLAIN diagram view - EXPLAIN diagram view - - -## View modes - -The segmented control above the results switches between three views. A plan the parser cannot read as a tree has no control at all and shows the raw output under a banner saying so. - -The mode and the Compare baseline stay with the editor tab, so another plan in it opens in the same mode. Each plan keeps its own selected step, zoom and scroll position while its result is open. Running the statement again replaces an unpinned plan with a new one at 100%. - -### Diagram - -Boxes joined by arrows, top to bottom. Zoom with a trackpad pinch, a two-finger double tap, or Cmd and scroll; the controls in the bottom-right corner zoom, reset to 100%, and fit the plan to the window. Click the plan to give it focus, and `Cmd+=`, `Cmd+-` and `Cmd+C` act on it instead of on the editor above. Right-click copies a step, and the export button saves the diagram as a PNG. - - - EXPLAIN tree view - EXPLAIN tree view - - -### Tree - -An expandable outline with a chart column beside **Operation**, **Cost**, **Rows**, and **Actual Time**. Drag a column edge to resize it and the width is remembered. Clicking a header sorts the steps under each parent without flattening the plan. Arrow keys move between steps, right-click copies one, and the divider resizes the detail panel. - -Only the columns the plan can fill are shown. A SQLite plan reports no cost, rows or timing, so it lists its steps and nothing else. - -Each row draws a bar for the metric in the **Metric** menu, so the expensive steps stand out without reading the digits. The longest bar is the largest step in the plan and every other bar is drawn against it. Cost and time bars take the badge's colors, because a step's share of the plan is what those bands measure. Row bars are drawn in the accent color: a row count is not a share of anything, and a plan that passes the same rows through every step would otherwise come out red from top to bottom. - -| Metric | What it measures | -|---|---| -| **Self Cost** | The node's own estimated cost, with its children's subtracted | -| **Self Time** | The measured time spent in the node itself, multiplied by **Loops** and divided across parallel workers | -| **Estimated Rows**, **Actual Rows** | Rows out of the node, with **Actual Rows** multiplied by **Loops** | - -The menu offers only the metrics the plan reports, and the chart column sorts by the metric it shows. Measured plans open on **Self Time** and estimated ones on **Self Cost**. - -**Self Time** is the total work in a node, so it differs from the **Actual Time** column, which is the per-loop figure the database prints. A node with `loops=500` at 0.4ms each shows 0.4ms in the column and 200ms in the chart. - -### Raw - -The original EXPLAIN output as text, with a copy button and a font size stepper. Planning and execution times, where the plan reports them, sit beside the view switcher in every mode. - -## Compare - -A fourth mode beside Diagram, Tree and Raw. Pick an earlier run from the **Baseline** menu and the pane reports what changed between it and the plan on screen. - -It leads with the verdict: whether the query got faster or slower, whether the plan shape changed, or whether nothing measurable moved. Below that come cost, estimated rows, planning time, execution time and node count, then the plan steps that were added, removed or changed. A timing difference under 15% counts as noise, so two runs of an unchanged plan report no change rather than a list of near-identical numbers. - -Compare is a mode, not a sheet, so the editor stays usable. Change the query or add an index, run it again, and the comparison follows the new plan. - -If either plan cannot be read as a tree, the two runs are compared as text instead. - -### Which runs can be compared - -A run appears as a baseline when it matches the current one on statement shape, connection, database, schema, EXPLAIN variant and output format. - -Statement shape means the query normalized the way `pg_stat_statements` normalizes one: literals, comments, whitespace and identifier quoting stop mattering. Reformatting a query, or running it with a different `WHERE` value, keeps its earlier plans. Changing which tables or columns it touches does not, and neither does switching between `EXPLAIN` and `EXPLAIN ANALYZE`, which report different things. - -### What is kept - -Plans are saved alongside [Query History](/features/query-history), and pausing history stops saving them. They are not deleted with it: clearing or pruning history leaves the plans in place, because a baseline you kept is worth more than the row that produced it. - -Click the pin beside the baseline menu to keep a plan permanently. Unpinned plans are pruned oldest first once they pass 100 MB in total, on the same schedule as history cleanup. - -A plan over 2 MB is not saved, and neither is the plan of a query that carries parameters, because a database can print the parameter values into its plan output. In both cases the run keeps its history entry, and Compare says which of the two happened. - -## Node details - -Selecting a node in either view fills the detail panel: - -| Field | What it holds | -|---|---| -| **Table** | The relation this step reads | -| **Cost** | `startup..total`, or the total alone when that is all the plan carries | -| **Rows**, **Width** | Estimated row count and row width | -| **Actual Time**, **Actual Rows**, **Loops** | Present only on a measured plan | -| **Details** | Everything else the driver reported: join type, index name, filter conditions, sort keys | - -Properties the driver reported as false or zero are left out of **Details**. - -## Database support - -| Database | Variants | Diagram and tree | -|---|---|---| -| PostgreSQL, PGlite | EXPLAIN, EXPLAIN ANALYZE | From `EXPLAIN (FORMAT JSON)` | -| Redshift | EXPLAIN, EXPLAIN VERBOSE | Raw text only | -| CockroachDB | EXPLAIN, EXPLAIN ANALYZE | From the text plan | -| MySQL | EXPLAIN, EXPLAIN (JSON) | From JSON, and from a typed `EXPLAIN FORMAT=TREE` or `EXPLAIN ANALYZE`. Plain EXPLAIN stays in the results grid | -| MariaDB | EXPLAIN, EXPLAIN (JSON) | From JSON, including a typed `ANALYZE FORMAT=JSON`. Plain EXPLAIN stays in the results grid | -| SQLite, Cloudflare D1, libSQL / Turso | Query Plan | From `EXPLAIN QUERY PLAN` | -| ClickHouse | Plan, Pipeline, AST, Syntax, Estimate | From the indented text | -| DuckDB | Explain | From the indented text | -| Snowflake | Explain (Text) | Raw text only | -| Trino | Logical, Distributed, IO, Validate, Analyze | Raw text only | -| SurrealDB | Explain, Explain Full | Raw text only | -| Cloudflare R2 SQL | Explain, Explain (JSON) | Raw text only | -| BigQuery | Dry Run (Cost) | A dry run cost estimate, no plan | -| Spanner | Plan | From the indented text | -| SAP HANA | Plan | From the indented text | -| Teradata | EXPLAIN | Raw text only | - -An engine missing from the table has no plan to show, and **Query > Explain Query** is dimmed for it. On MongoDB, call `.explain()` on the query in the editor instead. - - -`EXPLAIN` does not run the query. `EXPLAIN ANALYZE` does, so on a production server it costs whatever the query costs. Safe Mode asks before an EXPLAIN too, whichever way you started it, and the Stop button cancels one that is taking too long. - diff --git a/docs/features/favorites.mdx b/docs/features/favorites.mdx deleted file mode 100644 index 34837af5fd..0000000000 --- a/docs/features/favorites.mdx +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: Favorites -description: Pin the databases and tables you keep returning to, save SQL, and link a folder of .sql files ---- - -The query you retype every Monday is worth saving once. Give it the keyword `dau`: type those three letters in the editor and the whole statement expands, cursor already in place. - -The Favorites tab holds databases, tables, saved SQL, a **Team Library** on a Team license, and any linked folders of `.sql` files. - -Reach it from the **Tables** / **Favorites** control at the top of the sidebar, or from **View > Show Favorites**, which reveals a collapsed sidebar on the way. Neither command ships with a shortcut; bind one in **Settings > Keyboard**, under **Navigation**. - -## Database favorites - -| From | How | -|------|-----| -| The sidebar, in Tree layout | Hover a database row and click its star | -| The database switcher | Press `Cmd+K`, then right-click a row | -| The menu bar | **Database > Favorite Database**, on the database you are in | - -Start with the star. It lives only in the Tree layout (**View > Sidebar as Tree**), so use the menu bar in a flat list or from the keyboard. - -The star files the database under **Unassigned**. A right-click sets the environment in one move: **Add to Favorites > Production**. Once a database is a favorite, that submenu reads **Environment**. Right-clicking inside a multiple selection acts on the whole selection. - -In the Favorites tab, databases come first, one row per environment. The **Environment** menu narrows the list to one group; the search field filters by name. Double-click, or press `Return`, to switch to a database. Right-click for **Use as Active Database**, a different environment, or **Remove from Favorites**. `Delete` removes it too. - - - Favorite databases grouped into Development, Testing, and Production in the Favorites sidebar - Favorite databases grouped into Development, Testing, and Production in the Favorites sidebar - - -Database favorites belong to the connection, and go when you delete it. A dropped database stays in the list until you remove it. - -## Table favorites - -Hover a table row and click the star at its trailing edge, or right-click and choose **Add to Favorites**. A pinned row keeps its star, filled and yellow. - -Pinned tables sit under **Tables** in the Favorites tab, from every schema of the database you are browsing. A table outside the schema you browse shows its schema beside its name. Double-click, or press `Return`, to open one in a tab of its own schema; the schema you browse stays where it is. Its right-click menu has **Open Table**, **View ER Diagram**, and **Remove from Favorites**; the diagram is of the table's own schema, and `Delete` removes the selection. - -The search field matches table names. Add the schema to narrow by it: `sales.` lists every favorite in `sales`, and `sales.ord` the ones whose names contain `ord`. - -Table favorites are scoped to the connection, database, and schema, and sync through [iCloud](/features/icloud-sync). A favorite from another database is listed once you browse that database. One whose table no longer exists is hidden rather than shown broken. - -## Recent tables - -Turn on **Show recent tables** in [General settings](/customization/general-settings) for a **Recent** section at the top of the object list. It holds the last 10 tables per connection and database, newest first, and stepping through preview tabs records only the one you stop on. - -Click a row to reopen the table. Right-click to remove one entry or clear the list. Recents stay on this Mac. - -## Saving a query - -| From | How | -|------|-----| -| The editor | Click the star above it, press `Cmd+D`, or choose **Query > Save as Favorite** | -| Selected SQL | Right-click the selection, then **Save as Favorite** | -| [Query history](/features/query-history) | Right-click an entry, then **Save as Favorite** | -| The Favorites tab | Click **+** at the foot of the sidebar, then **New Favorite…** | - -The dialog takes a name, the SQL, and an optional keyword, 900 KB at most between them. Keep a bigger script as a `.sql` file in a [linked folder](#linked-sql-folders). Select **Global** for a query that names nothing specific to one database. Leave it off and the favorite belongs to the connection you are in, and goes with it when that connection is deleted. - -Folders carry the same setting, so the **Folder** list offers only the ones that match: a global query goes in a global folder, a connection's own query goes in either. - - - A dialog with Name, Query, Keyword fields and a Global checkbox - A dialog with Name, Query, Keyword fields and a Global checkbox - - -The same **+** menu carries **New Query**, **New Folder**, and **Add Linked SQL Folder…**, and so does the right-click menu over empty space. - -## Keywords - -A keyword is what you type instead of the query. Type its first letters, in any case, and the favorite appears in the completion popup with a star; `Tab` or `Return` inserts the SQL. See [Autocomplete](/features/autocomplete). - -Two favorites that reach the same connection cannot share a keyword, and the dialog says so as you type. If two Macs give one keyword to two queries saved to the same connection before either syncs, the older query keeps it and the other loses it on every Mac. A global query and a connection's own query can end up with the same keyword this way; inside that connection, its own query wins. - - - A completion popup with a starred favorite matching the typed keyword - A completion popup with a starred favorite matching the typed keyword - - -Typing a keyword in [Open Quickly](/features/open-quickly) finds the same query without touching the sidebar. - -### Cursor placement - -Put `;;` in the SQL to say where the cursor lands. The marker is removed on insert: - -```sql -SELECT COUNT(*) -FROM orders -WHERE orders.;; -``` - -The cursor lands right after `orders.`, ready for a column name. Only the first `;;` counts; without one it lands at the end. - -## Working with saved queries - -Double-click a favorite, or press `Return`, to insert it into the editor. The right-click menu has **Insert in Editor**, **Run in New Tab**, **Copy Query**, **Edit…**, **Show History**, **Move to**, and **Delete**. Arrow keys move between rows, typing jumps to a name, and `Delete` asks first. - -Folders come from the same background menu. A folder's own menu has **New Favorite…**, **New Subfolder**, **Rename**, **Global**, and **Delete Folder**; deleting one moves what was inside up to the parent level. A global folder carries a globe, and **Global** changes that folder alone: the queries inside keep the scope they had, so mark them global too if you want them everywhere. Anything kept in a folder a connection cannot see is listed at that connection's top level, whether it is a global query or a global subfolder. Drag a query or a linked file out of the sidebar to drop its SQL into the editor or another app. - -## Saved query versions - -Each time a saved query's SQL changes, the text it replaced is kept. That includes an edit arriving from another Mac through iCloud, so a sync that overwrites your change leaves it recoverable. - -Right-click a saved query and choose **Show History**. A tab opens with the versions down the left, newest first. **Changes** compares the selected version with the current SQL; **Content** shows it read-only, with **Copy**, **Export…**, and **Open in Editor**. - -Choose **Restore This Version** to put it back. The SQL it replaces becomes a version too, so a restore is undone the same way. - -Each query keeps its last 50 versions, on this Mac only: versions never sync. Renaming, moving, or changing a keyword records nothing, and deleting a saved query deletes its versions. - -## Export and import - -Saved queries travel in a `.tablepro` file with the connections they belong to, folders and keywords included, and global ones join them when you select **Also include global saved queries**. Importing that file, or importing from another app, lists them below the connections for review. [Connection Sharing](/features/connection-sharing#export) covers both, including where each app's queries land. Version history and linked SQL folders are never exported. - -## Team Library - -On a Team license the Favorites tab gains a **Team Library** section holding what your team published. Double-click one, or press `Return`, to run it in a new tab. - -To publish your own, right-click empty space and choose **Publish Saved Queries to Team…** It replaces everything you published before. See [Team Plan](/features/team). - -## Linked SQL folders - -Link a folder of `.sql` files on disk and they appear next to your saved queries. The point is a Git repo of shared queries: clone it, link it, and a `git pull` keeps the team current. - - -This is not [Linked Folders](/features/connection-sharing#linked-folders), which watches a folder of shared `.tablepro` connection files. - - -Click **+** at the foot of the sidebar, choose **Add Linked SQL Folder…**, and pick any folder. `.sql`, `.psql`, and `.pgsql` are indexed, subfolders nest the way they do on disk, and hidden files are skipped. - -Linked folders are global, so every connection shows them. Only the first 4 KB of each file is read to build the sidebar, so a large repo stays cheap. - -### Editing files - -Click a linked file to open it as an ordinary editor tab. `Cmd+S` writes back in the encoding the file was read with, detected from the file itself and falling back to UTF-8, then ISO Latin-1. - -A file changed on disk gets the same banner and diff sheet as any other [SQL file](/features/sql-files#when-the-file-changes-underneath-you). Files added and removed outside TablePro reach the sidebar a second later. - -A file in an encoding that cannot store every character, such as ISO Latin-1 or Shift JIS, carries a yellow warning triangle. Saving keeps its encoding, and a character it cannot represent, an emoji in an ISO Latin-1 file, fails the save with an error rather than dropping the character. - -### Frontmatter - -Leading SQL comments set the name, the keyword, and the tooltip: - -```sql --- @name: Active Users (24h) --- @keyword: dau --- @description: Daily active users from the last 24 hours -SELECT user_id, last_seen -FROM users -WHERE last_seen > NOW() - INTERVAL 24 HOUR; -``` - -| Key | Effect | -|-----|--------| -| `@name` | Display name in the sidebar. Falls back to the filename without its extension. | -| `@keyword` | Autocomplete trigger, exactly as for a saved favorite. | -| `@description` | Shown in tooltips. Optional. | - -The block ends at the first line that is not a `-- @key: value` comment, so these go at the very top. Other keys, such as `-- @author: alice`, may share the block and are ignored. Keys are case-insensitive and a UTF-8 BOM is handled. A file without frontmatter appears under its filename with no keyword. The `;;` [cursor marker](#cursor-placement) works here too. - -Right-click a row and choose **Edit Metadata…** to change frontmatter without opening the file. Only the `@name`, `@keyword`, and `@description` lines change. Every other line in the block stays where it was, exactly as written, and the file keeps its line endings. A description typed over several lines is saved as one. - -### Managing linked files - -Press `Delete` on a linked file, or right-click and choose **Move File to Trash**: it goes to the macOS Trash and stays recoverable. The rest of that menu is **Open in Editor**, **Edit Metadata…**, **Copy Query**, and **Show in Finder**, plus the [Git commands](#git-status-and-history) inside a repository. - -A folder's root row carries **Show in Finder**, **Copy Path**, **Disable**, **Reload**, **Add Another SQL Folder…**, and **Remove from Sidebar**. Removing unlinks the folder and leaves the files where they are. - -Linked folder paths are the one part of Favorites that does not sync, so each Mac links its own copy. - -### Git status and history - -When a linked folder sits inside a Git working copy, a changed file carries a letter at the end of its row. Hover it to see whether the change is staged. - -| Letter | Meaning | -|--------|---------| -| M | Modified | -| A | Added to the index, not yet committed | -| R | Renamed in the index | -| U | Untracked | -| ! | Merge conflict | - -A file with no letter matches its last commit. Letters refresh when a file changes, when the repository changes, and when you switch back to TablePro. - - - Sidebar with M and U letters beside linked files, next to a history tab listing three commits and a split diff - Sidebar with M and U letters beside linked files, next to a history tab listing three commits and a split diff - - -Right-click a file for the Git commands: - -- **Show History** opens a tab listing the commits that changed the file, following it across renames, up to the 200 most recent. The top row is the file on disk; select it to see what changed since the last commit. -- **Restore This Version**, in that tab, writes the selected commit's text into the file. It asks first when the file has uncommitted changes, because those are lost. -- **Discard Changes…** puts the file back to its staged version, or to its last commit when nothing is staged. It cannot be undone. - -Both write the text exactly as Git stores it, without line-ending conversion or filters. A file stored with Git LFS cannot be restored or discarded here; use your Git client. If the file, or for Discard its staged version, changes while you confirm, nothing is written. - -Stage, commit, and push in your Git client. The sidebar reads the repository and never touches the index. - -Git comes from Xcode, the Command Line Tools, or Homebrew. When none is installed the letters and commands stay hidden, and the Command Line Tools installer never opens. - - -Link only repositories you trust. Reading a file's status runs the repository's own content filters, exactly as `git status` in Terminal does. - diff --git a/docs/features/filtering.mdx b/docs/features/filtering.mdx deleted file mode 100644 index a382113be2..0000000000 --- a/docs/features/filtering.mdx +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: Filtering -description: Build a WHERE clause from the filter bar, or write one by hand, and save it as a preset ---- - -A filter is a WHERE clause, and it runs at the database. That is the difference between filtering and the find bar: matches come from every row in the table, not from the page you are looking at. Open the filter bar with `Cmd+Shift+F`, type a condition into the row that appears, and press `Enter`. - - - Filter panel above the data grid - Filter panel above the data grid - - -Each row has a drag grip, a checkbox, a column picker, an operator, a value field, and **+**/**−** buttons. Right-clicking a column header in the grid and choosing **Filter with column** pre-fills a row for that column. A value field suggests column names and keywords as you type; `Enter` runs what you typed, and `Down` then `Enter` takes a suggestion instead. - -| Control | What it does | -|---------|--------------| -| Header **Apply**, `Enter` in a value field, or `Cmd+Enter` | Runs every checked row | -| Row checkbox | Turns that row off without deleting it. Press **Apply** to re-run | -| Row **Apply** | Filters by that row alone, without touching the checkboxes, so the header **Apply** brings the checked set back | -| **Clear** | Drops the applied filter and keeps the rows | -| Row grip, or right-click > **Move Up** / **Move Down** | Reorders the list. Order never changes the result | -| ⋯ > **Remove All Filters** | Deletes every saved row for this table | - -Filters are saved per connection, database, schema, and table, and survive a relaunch, unchecked rows and rows you never applied included. **Clear** keeps the rows and stops filtering, so they come back when you reopen the table. A table with an active filter opens in a new tab rather than replacing the current one. - - -Redis shows a key-pattern search bar (`user:*` and the like) with a key-type picker instead of the filter bar. In a [data file](/features/data-files) window the same filter bar runs over the file itself, with no raw SQL row. - - -## Filter by cell value - -Right-click a cell in a table and open **Filter**. The conditions it offers are built from that cell's stored value, not from its **Display As** text: - -| Cell | Conditions | -|---|---| -| A value | `status = “paid”`, `status != “paid”`, plus `>` and `<` on a number or date column | -| NULL | **is NULL**, **is not NULL** | -| Empty text | **is empty**, **is not empty** | - - - Cell context menu with the Filter submenu open, listing AlbumId = 3, != 3, > 3 and < 3 - Cell context menu with the Filter submenu open, listing AlbumId = 3, != 3, > 3 and < 3 - - -Choose one and it lands in the filter bar as a checked row and runs. It narrows what the grid already shows: rows that were running stay checked, and every other row is unchecked, including the ones **Clear** left behind. Under **Match any**, with two or more rows running, the new condition runs alone. A `!=` condition also leaves out rows where the column is NULL. - -There is no **Filter** item on a query result, on Redis, or on a new row or an edited cell. Binary, JSON, XML, spatial, array and large-object columns have none either, and neither does text with a leading or trailing space or over 10,000 characters: type those conditions into the filter bar instead. - -## Operators - -Eighteen of them, and the menu shows the SQL each one produces. - -| Operators | SQL | -|---|---| -| equals, not equals | `=`, `!=` | -| contains, not contains | `LIKE %..%`, `NOT LIKE %..%` | -| starts with, ends with | `LIKE ..%`, `LIKE %..` | -| greater than, greater or equal, less than, less or equal | `>`, `>=`, `<`, `<=` | -| is NULL, is not NULL | `IS NULL`, `IS NOT NULL` | -| is empty, is not empty | On a text column, NULL or `''`. On any other column, the same as is NULL | -| in list, not in list | `IN (..)`, `NOT IN (..)`, from comma-separated values | -| between | `BETWEEN`, with two value fields | -| matches regex | `~`, or the engine's own regex function. Where the engine has none, it falls back to contains | - -## Case sensitivity - -Contains, not contains, starts with, and ends with ignore case. Equals, not equals, IN, NOT IN, and regex respect it. Open the operator menu and use **Match Case** to flip either one for that row; an `Aa` badge marks a row that is not on its usual setting. - - -On MySQL, MariaDB, SQL Server, SQLite, libSQL, and Cloudflare D1 the column's collation decides, and no query can override it, so **Match Case** is dimmed. Their default collations already ignore case. Cassandra and Redis have no case-insensitive matching at all, so it is dimmed there too. - - -## Combining conditions - -**Match all** and **Match any** in the header join every row with AND or OR. Cassandra and ScyllaDB have no OR, so their filter bar offers **Match all** only. There is no per-row choice and no grouping, so a mixed expression cannot be built out of rows. Put the parentheses in a raw SQL row instead, which enters the WHERE clause as a single parenthesized condition: - -```sql -(status = 'open' AND priority > 3) OR escalated -``` - -⋯ > **Preview Query** shows the WHERE clause the rows produce, with a copy button. - -An applied filter reaches an export only through the grid. **File > Export > Export Results…** writes the rows the grid holds; **Export Tables…** beside it reads the table itself and ignores the filter bar. See [Import & Export](/features/import-export). - -## Nested fields - -On a document store such as MongoDB, a field holding an object or an array of objects is one column showing its JSON. The column picker also lists the paths inside it, so `customer.country` and `items.sku` filter directly. - - - Filter column picker with nested paths grouped under their top-level field - Filter column picker with nested paths grouped under their top-level field - - -The paths come from a sample of the collection and only the first two levels reach the picker. The button beside it opens a searchable list of every path found, and a path typed there that no sampled document carried is used anyway, which is how to reach a field only some documents have. See [MongoDB](/databases/mongodb) for the fields this leaves out. - -### Fields inside an array - -A row on a field inside an array gets one more control: - -| Setting | What it matches | -|---------|-----------------| -| **any element** | The row is satisfied by any entry in the array. Two rows can be satisfied by two different entries | -| **same element** | Every row set to **same element** for that array must be satisfied by one single entry | - -Filtering `items.price` greater than 500 and `items.name` equal to `Laptop` with **any element** returns an order holding an expensive item and, separately, an item called Laptop. With **same element** on both rows it returns only orders holding one item that is both. **any element** is the default. - - - Filter rows on an array field showing the any element and same element control - Filter rows on an array field showing the any element and same element control - - -Rows sharing an array and set to **same element** group together, and **Match all** / **Match any** applies inside the group too. - -## Raw SQL - -A new row starts in raw SQL, unless **Default Column** in the settings says otherwise, and picking a column from its picker switches it to column mode. Type any WHERE condition: - -```sql -created_at > NOW() - INTERVAL 7 DAY -``` - -Autocomplete offers the table's columns and SQL keywords, after AND and OR included, on the same rules as the [query editor](/features/autocomplete): a typed character opens it, so does a dot and a `::` cast, and a position where the whole column list would be noise stays shut. Nothing in the list is selected until `Up` or `Down` selects it, so `Return` applies the filter. Once a suggestion is selected, `Return` or `Tab` inserts it. `Escape` dismisses the list without closing the filter bar. On a database that does not speak SQL the row is labeled **Raw Filter** and takes that database's own syntax: on MongoDB, a filter document such as `{"customer.country": "US"}`. - - -The text goes into the WHERE clause as written. A condition whose syntax does not match the database fails the query. - - -## Presets and settings - -Both live in the ⋯ menu. **Save as Preset…** stores the current rows, clicking a preset name loads it, and **Delete Preset** removes one. - -**Filter Settings** sets what a new row starts as: **Default Column** is Raw SQL, Primary Key, or Any Column, and **Default Operator** is Equal or Contains. **Always Show Filter Bar** keeps the bar open on a table that has no filter. - -**Saved Filters** decides what reopening a table does with the rows saved for it. - -| Option | Reopening a table | -|---|---| -| **Restore and apply** | Runs the filter the table was last showing | -| **Restore without applying** | Brings the rows back in the filter bar over unfiltered results. Press **Apply** to run them | -| **Don't save** | Opens unfiltered. Filters last as long as the tab, and rows already saved are kept rather than deleted | - -**Restore and apply** is the default. Pick **Restore without applying** to see a table whole before deciding to filter it. diff --git a/docs/features/handoff.mdx b/docs/features/handoff.mdx deleted file mode 100644 index 7bb5ca7211..0000000000 --- a/docs/features/handoff.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Handoff -description: Resume the active connection or table on another device signed into the same iCloud account ---- - -Continuity carries which connection you have open, never the connection's settings, so the receiving device needs that connection saved already. [iCloud Sync](/features/icloud-sync) is what puts it there. - - - Handoff icon in the Dock - Handoff icon in the Dock - - -## Requirements - -- Both devices signed into the same iCloud account -- Both on the same Wi-Fi network, with Bluetooth on -- Handoff turned on in **System Settings > General > AirDrop & Handoff** -- TablePro running on both, and the connection saved on both - -## What carries over - -The connection in front of you, and the table too when the frontmost tab is a table tab. Query text, scroll position, and unsaved edits stay put. [TablePro for iPhone and iPad](/ios) takes part on both sides. - -## Picking it up - -The TablePro Handoff icon appears in the receiving Mac's Dock and app switcher (`Cmd+Tab`), and at the bottom of the app switcher on iPhone and iPad. Open it to get the same connection, and the table if there was one. - -## When nothing arrives - -Handoff is a system service, so the app cannot report why it stayed quiet. Check both devices: same iCloud account, awake and unlocked, Bluetooth and Wi-Fi on, Handoff enabled. - -### No saved connection with ID "…". - -The icon arrived, so Handoff worked; the connection itself is not on this device. Turn on [iCloud Sync](/features/icloud-sync) on both, or export the connection and import it here. diff --git a/docs/features/icloud-sync.mdx b/docs/features/icloud-sync.mdx deleted file mode 100644 index 3f64bad43e..0000000000 --- a/docs/features/icloud-sync.mdx +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: iCloud Sync -description: Sync connections, groups, and tags to your Macs, iPhone, and iPad, and settings, table favorites, and SSH profiles between Macs ---- - -Sync needs a Starter or Team [license](/features/licensing) and starts off. Press `Cmd+,`, open **Settings > Sync**, turn on **Sync this Mac with iCloud**, pick the categories, and click **Sync Now**. The iPhone and iPad app shares the same CloudKit data but carries connections, groups, and tags only. - - - Sync pane with the Sync this Mac with iCloud switch and its Starter badge - Sync pane with the Sync this Mac with iCloud switch and its Starter badge - - -## What syncs - -Each synced category has its own toggle under **Sync Categories**. - -| Data | Synced | Notes | -|------|--------|-------| -| **Connections** | Yes | Host, port, username, database type, SSH/SSL config, and whether the connection is a favorite | -| **Passwords** | Opt-in | Nested under Connections, and carried by Apple's iCloud Keychain rather than TablePro's own records. Turning it on affects new saves only, so re-save a password to sync it. On iPhone and iPad the same switch is **Settings > iCloud > Sync Passwords** | -| **Groups & Tags** | Yes | Nested group hierarchy and sort order included | -| **SSH Profiles** | Yes | Named [SSH profiles](/connections/ssh-profiles) | -| **Credential Profiles** | Yes | Between Macs only. Named [credential profiles](/connections/credential-profiles): the name, the username, and where the password comes from. Which connections use a profile is kept on each Mac | -| **Table Favorites** | Yes | The names shown in the Favorites tab and pinned in table lists | -| **Table Folders** | Yes | Between Macs only. The sidebar's [folders](/features/connection-window#folders) and the tables and views filed in them | -| **Database Favorites** | Yes | Favorited databases and their Development / Testing / Production tags | -| **Saved Queries** | Yes | Saved SQL and their folders | -| **Settings** | Yes | Between Macs only. General, Appearance, Editor, Data Grid, History, Tabs, Keyboard, and AI settings, plus custom AI slash commands and saved per-table column widths and order | -| **Linked SQL Folders** | No | Folder paths are per-Mac. Link the same Git repo on each Mac after cloning. The cached file index stays local too | -| **Sidebar state** | No | The sidebar's database and schema filters and recent tables lists stay on each Mac | -| **Connection list order** | No | Recent connections and whether that section is shown, the sort order, and the order of favorites stay on each device | - -A sync runs at launch, when you switch back to the app, 2 seconds after you change anything synced, and when the network returns after a sync that could not reach iCloud. - -## Keeping a connection off iCloud - -A localhost or throwaway database is rarely worth a round trip. Mark it **Local only** under **iCloud Sync** on the connection form's **Options** tab, or right-click it and choose **Exclude from iCloud Sync**. It shows a struck-through cloud icon in the connection list, and the flag survives duplicating and exporting. - -What belongs to the connection stays on this Mac with it: its table and database favorites, the saved queries and folders scoped to it, and its column widths, order, and hidden columns. Edits and removals you make to them wait here while the connection is Local only, and the first sync after you turn it off sends them. Deleting a Local only connection removes those items from this Mac and from nowhere else. - -## Checking that it worked - -**Status** in the **Sync Status** section reads **Up to Date** after a clean run. **Last Synced** is the last run that both sent this Mac's changes and brought the other devices' in, so it stops moving while uploads fail even though downloads keep arriving. - -When the same connection changed on two devices, TablePro merges field by field, so a name edited on one Mac and a port edited on the other both survive. When one field changed in both places, the device that syncs last wins. - -## Troubleshooting - -A condition that a retry cannot clear shows as a notice at the top of **Settings > Sync**, with the action that clears it. While it lasts, edits stop going up on every change: they wait on this Mac, and nothing local is deleted. The welcome window's sync indicator names the same condition and opens the pane when clicked. - -### iCloud Storage Is Full - -Every upload fails, deletions included, until iCloud has room. Changes from your other devices still arrive. Click **Manage Storage…**, or open **System Settings**, click your name, then click **iCloud**, and free up space or add storage. The upload is tried again after 5 minutes, then twice as long each time up to an hour; click **Sync Now** to try at once. - -### Not Signed In to iCloud - -No Apple Account is signed in to iCloud on this Mac. Click **Open System Settings…** and sign in. Sync starts by itself once you do. - -### iCloud Is Not Ready - -The account is signed in, but iCloud is not ready for apps yet, which happens briefly after signing in. Sync resumes by itself. If the notice stays, look in **System Settings > Apple Account** for a prompt to confirm your password. - -### iCloud Is Restricted - -Screen Time or a device management profile does not allow iCloud on this Mac. Sync stays off until whoever manages the Mac lifts the restriction. - -### TablePro Data Was Removed from iCloud - -TablePro's data is no longer in iCloud, most often because it was deleted under **Manage Storage**. Nothing uploads until you choose: **Upload Again** sends everything on this Mac back up, and **Turn Off Sync** keeps it on this Mac only. If another device uploads first, this Mac picks that up and syncs normally again. - -### Update Required - -iCloud no longer accepts this version of TablePro. Click **Check for Updates…** and install the update. - -### Offline - -iCloud could not be reached. Changes go up as soon as the network returns, without a click. - -### Waiting to Retry - -iCloud asked TablePro to slow down. The next attempt waits for the time iCloud named, and at least 30 seconds. - -### Some Changes Not Uploaded - -iCloud refused the changes counted under **Sync Now** and accepted the rest. The refused ones stay on this Mac and go again on the next sync. - -### Other cases - -Signing in to a different iCloud account starts sync over for that account: the next sync is a full fetch rather than a delta, and changes waiting on this Mac go to the new account. Expect the first sync to take longer. - -When a license expires, sync stops and local data stays where it is. Re-activate to resume. - -A license TablePro has not been able to check for 30 days pauses sync separately, under **Sync Paused** rather than **Sync Off**. Click the indicator, or **Check Again** in **Settings > Sync**, to try the server again. Local data stays where it is either way. diff --git a/docs/features/import-export.mdx b/docs/features/import-export.mdx deleted file mode 100644 index b8d2fefb23..0000000000 --- a/docs/features/import-export.mdx +++ /dev/null @@ -1,395 +0,0 @@ ---- -title: Import & Export -description: Export to CSV, JSON, SQL, MQL, or XLSX. Import SQL, JSON, and CSV files with column mapping and transaction safety ---- - -**File > Export** (`Cmd+Shift+E`) re-reads every table you tick with `SELECT * FROM`, so what is on screen has no bearing on the file: filters, sort, hidden columns, and the page you are on are all ignored, and table size does not matter. **Export Results…** on the grid's right-click menu writes what the tab has loaded instead. Import is `Cmd+Shift+I`. - -## Export data - - - - Open a table or run a query, then choose **File > Export** (`Cmd+Shift+E`), or right-click the results grid and choose **Export Results…**. - - - Pick a format, tick objects in the tree, and set the options for that format. - - - Click **Export**. The destination file appears only on success: a failed or cancelled export removes its partial file. - - - -The last format and its options come back next time, but only after an export succeeds; cancelling discards the changes. **Reset to Defaults** restores the stock settings for the current format. - -The bookmark button above the tree saves the current selection under a name and reapplies it later, including each table's options and row filter. A saved selection naming tables the database no longer has says how many it still matches, and applying it selects those and clears the rest. - - - Export dialog - Export dialog - - -### What the tree lists - -SQL exports more than tables. A database holding more than one kind of object groups them, in the order a restore needs them: - -| Group | Written as | Engines | -|---|---|---| -| Types | `CREATE TYPE` | PostgreSQL and its forks | -| Sequences | `CREATE SEQUENCE` | PostgreSQL and its forks | -| Tables | `CREATE TABLE` plus `INSERT` and `CREATE INDEX` | every SQL engine | -| Foreign Tables | `CREATE FOREIGN TABLE` | PostgreSQL | -| Views | `CREATE VIEW` from the server's own definition | every SQL engine | -| Materialized Views | `CREATE MATERIALIZED VIEW` | PostgreSQL, Oracle | -| Routines | `CREATE FUNCTION` or `CREATE PROCEDURE` | engines with stored routines | -| Triggers | `CREATE TRIGGER` | engines with triggers | -| Events | `CREATE EVENT` | MySQL, MariaDB | -| Privileges | `GRANT`, one principal per row | engines with user management | - -An Oracle or Dameng routine or trigger is followed by a `/` line, and a MySQL or MariaDB routine, trigger or event with a body sits in a `DELIMITER` block, the same form a [saved sync script](/features/compare-sync#the-script) uses. Restore those dumps with SQL*Plus, DISQL or the `mysql` client. A SQL Server dump ends every statement with a `GO` line, so each one runs as its own batch in `sqlcmd` and SQL Server Management Studio. - -A database with only tables lists them flat, with no group to open first. - -On an engine that groups by database, each database is listed from what the server says it holds. One the account can list but not open is left out, so the rest of the tree still opens. - -On an engine that groups by schema, the tree lists the schemas the sidebar's [schema filter](/features/connection-window#filtering-schemas) keeps, plus the schema you are browsing, the default schema (`public` on PostgreSQL), the schema of each table you opened the dialog on, and every schema a saved selection names. Right-click a database or schema and choose **Export…** to list every schema it covers, filter or not. - -The default schema and the schemas of the tables you opened the dialog on open expanded, and the rest collapsed. A collapsed schema lists its tables and views only: expand it, or select its checkbox, to read its routines, triggers, sequences and types. **Export** is dimmed until they arrive. A database or schema exported with **Export…** is read in full from the start. - -Arrow keys move through the tree, Left and Right close and open a group, and Space ticks every selected row. Ticking a group ticks everything under it. - -Only SQL writes every group. CSV, JSON and XLSX take tables and views, and switching to one of them drops the rest from the tree. Per-object checkboxes follow the kind: **Data** is off the row for anything without rows, and a privilege row carries neither **Drop** nor **Data**. - - -Privileges are server-wide, so they appear once, under the container the dialog opened on. A `GRANT` naming an object the dump does not create fails on restore. - - -### Narrowing what a table exports - -Click the filter icon on a table row to write a `WHERE` clause, cap the row count, or pick columns. The icon fills in once a table is narrowed, and its tooltip repeats what the narrowing is. - -The clause is your engine's own SQL, spliced into the `SELECT` the export runs. It has to be one expression: a semicolon anywhere but the end refuses the filter, and the export summary says the table went out whole. - -Ticking no column exports every column, which is also what a table with a column added later gets. - -### What ends up in the file - -A whole-table export streams from the database at constant memory, with no row-count limit, and can be cancelled from the progress dialog. - -**Export Results…** writes what the tab holds in memory, which is the output of the query that filled it. The filter bar and a sorted header are part of that query, and so is column visibility on a table tab: hiding a column re-queries without it, keeping only the primary key and anything being sorted on. Two things are not part of it. A column value filter narrows the loaded rows in the grid afterwards, so it never reaches the file. And a truncated result with more rows behind it is re-run and streamed in full rather than written as far as it got. - -As SQL, a results export writes `INSERT` statements only. A result set is the output of a query rather than a table, so there is no schema to recreate and nothing to drop: the **Structure** and **Drop** options belong to **File > Export**, which reads real tables. Identifiers are quoted and values escaped the way the source engine reads them, so the file imports back into it. - -### Formats - - - - | Option | Default | - |--------|---------| - | Header row | Yes | - | Delimiter (comma, semicolon, tab, pipe) | Comma | - | Quote handling (always, as needed, never) | As needed | - | NULL to empty strings | Yes | - | Line breaks in values to spaces | No | - | Line ending (LF, CRLF, CR) | LF | - | Decimal separator (period, comma) | Period | - | Sanitize formula-like values | Yes | - | Encoding (UTF-8, ISO Latin 1, Windows-1252) | UTF-8 | - | Include byte order mark | No | - - Sanitizing prefixes a value starting with `=`, `+`, `-`, or `@` with a single quote, so a spreadsheet treats it as text. - - Excel on Windows needs the byte order mark to read a UTF-8 file. Readers that do not expect one read it as part of the first column name. Only UTF-8 carries a mark, so the checkbox dims for the other two encodings. - - ISO Latin 1 and Windows-1252 cover fewer characters than the data might hold. A character neither can represent is written as `?`, and the export finishes with a warning listing the characters that were dropped. - - - One object per table, each holding an array of rows. - - | Option | Default | - |--------|---------| - | Layout (one JSON object, one row per line) | One JSON object | - | Pretty print | Yes | - | Include NULL values | Yes | - | Preserve all values as strings | No | - - **One row per line** writes NDJSON to a `.ndjson` file: no wrapping object, no array, one row per line, so a stream reader can process a file larger than memory a line at a time. Pretty print does not apply to it and is dimmed. Import reads `.json`, `.jsonl` and `.ndjson`, detecting the layout from the file. - - - | Option | Default | - |--------|---------| - | Compress with gzip (`.sql.gz`) | No | - | Batch size (rows per INSERT: 1, 100, 500, 1,000) | 500 | - | Max INSERT size (no limit, 256 KB, 1, 4, 16, 64 MB) | 1 MB | - | On existing rows (insert, skip, replace, update) | Insert | - | Split every (one file, 8, 32, 128, 512 MB) | One file | - | Read every table at one snapshot | No | - | Exclude the AUTO_INCREMENT counter | Yes | - | Exclude DEFINER clauses | Yes | - - Structure (CREATE TABLE), Drop (DROP TABLE IF EXISTS), and Data (INSERT statements) are per-table checkboxes, and a multi-table export can mix them. - - Structure carries the table's indexes. They are written after the rows and before the deferred foreign keys, the order `pg_dump` uses, so the rows load without index upkeep and a foreign key that references a unique index finds it in place. On MySQL, MariaDB, ClickHouse, Trino and CockroachDB the server writes them inside `CREATE TABLE`, so they arrive with the table instead. A materialized view's indexes follow the view. An invalid PostgreSQL index, which a failed `CREATE INDEX CONCURRENTLY` leaves behind, is left out, as `pg_dump` leaves it out. - - On PostgreSQL, Structure carries comments as well. Each table, view and materialized view gets its own `COMMENT ON` and one `COMMENT ON COLUMN` per commented column, directly after its `CREATE`. A foreign table is the exception: its comments are dropped, because the dump writes `CREATE TABLE` for it and PostgreSQL then refuses `COMMENT ON FOREIGN TABLE`. Where the server's own `CREATE TABLE` already carries the comment, MySQL and MariaDB among them, it arrives with the table instead. - - - All three are on by default, so an SQL export carries `DROP TABLE IF EXISTS` unless you untick **Drop**. Run that file against the wrong database and it drops the tables first. - - - A multi-table export orders the tables by their foreign keys, so a parent is created and filled before the rows that reference it and dropped after them. Foreign keys between two tables that reference each other leave no such order: those tables keep the order the export listed them in, the file says so in a comment, and the summary repeats it. Import that one with **Disable foreign key checks** ticked. - - The last two exclusions cover MySQL and MariaDB, and pass every other engine through untouched. - - An INSERT closes at **Max INSERT size** or at **Batch size**, whichever it reaches first. MySQL and MariaDB treat one statement as one packet and reject anything over `max_allowed_packet`, which a table of wide rows passes long before it reaches 500 rows: 500 rows of a megabyte each is a 524 MB statement, and the server answers `Server has gone away`. The default of 1 MB sits under every `max_allowed_packet` those servers have shipped with, and every other value in the menu is one of those defaults. Choose **No limit** to bound statements by row count alone. A single row wider than the limit cannot be split, so it goes out as a statement of its own that still passes the limit, and the summary names how many did. - - The summary reports the largest INSERT the export wrote and how many rows it carried. A figure well under the limit means the row count is what closed the statements; a figure at it means the opposite. - - Insert mode is spelled differently on each engine: `INSERT IGNORE` and `REPLACE INTO` on MySQL and MariaDB, `INSERT OR IGNORE` and `INSERT OR REPLACE` on SQLite, `ON CONFLICT` on PostgreSQL. Updating needs a primary key to name as the conflict target. An engine with no spelling for the mode writes plain inserts, and the export summary says which tables that happened to. - - Splitting writes `dump.part1.sql`, `dump.part2.sql` and so on, rotating between statements so no part ends mid-`INSERT`. The summary names the first and last part; restore them in order. Each part re-opens the session state its own statements need, so a part restores on its own: on SQL Server that means `SET IDENTITY_INSERT`, which a part boundary used to leave behind. A part that cannot be written stops the export, and the error names the parts already on disk, because a rename cannot be taken back. A gzipped export is one file, so the two settings do not combine and the summary says so. - - A dump is UTF-8 and says so in every part, the way the engine's own dump tool does: - - | Engine | Each part opens with | - |---|---| - | MySQL, MariaDB | `SET NAMES utf8mb4`, and ends by putting the session's character set back | - | PostgreSQL, CockroachDB, PGlite | `SET client_encoding = 'UTF8';` | - | Amazon Redshift | Nothing. Set `PGCLIENTENCODING=UTF8` before restoring it into a database that is not UTF-8 | - - `mysql < dump.sql` and `psql < dump.sql` then restore Japanese, emoji and other non-Latin text intact, even into a `LATIN1` or `EUC_JP` database or from a client whose default is Latin 1. - - One snapshot opens `START TRANSACTION WITH CONSISTENT SNAPSHOT` on MySQL, `BEGIN ISOLATION LEVEL REPEATABLE READ` on PostgreSQL, and a deferred transaction on SQLite. It holds that transaction open for the whole export. - - Excluding the counter drops `AUTO_INCREMENT=` from the table options and leaves the column's own `AUTO_INCREMENT` attribute alone. Restoring rows sets the counter one past the highest key in the data, so a source counter that had run ahead of its rows, after deletes or a reset, does not carry over. - - Excluding definers drops `DEFINER=user@host` from a view. The account running the import becomes the definer, and `SQL SECURITY` is untouched, so a definer-rights view then runs with that account's privileges. Keep the clause and the import fails with `ERROR 1227 (42000): Access denied; you need (at least one of) the SET USER privilege(s) for this operation` unless the importing account is privileged, and a view that does get created answers `ERROR 1446 (HY000): The user specified as a definer ('…') does not exist` on every query against it. An invoker-rights view still runs as its caller. - - Not available on MongoDB or Redis. - - - MongoDB only. Writes a `.js` file of `insertMany()` calls that runs in `mongosh`, 500 documents per call by default, with per-collection checkboxes for drop, indexes, and data. - - ```javascript - db.users.insertMany([ - {"_id": {"$oid": "507f1f77bcf86cd799439011"}, "name": "Alice", "age": 30}, - {"_id": {"$oid": "507f1f77bcf86cd799439012"}, "name": "Bob", "age": 25} - ]); - ``` - - Top-level values keep their type: `ObjectId`, `ISODate`, `BinData` with its real subtype. A typed value nested inside a subdocument or array is written as a string, so re-importing gives you a string where the original held an ObjectId, date, or binary. Export those collections as JSON, or use `mongodump`. - - **Indexes** writes each index as a `createIndex()` call and the collection's validator as a `collMod` command. Any other statement in the collection's DDL is left out, and the line `// Skipped a statement that is not an index or a validator` marks where it stood. - - - | Option | Default | - |--------|---------| - | Include headers (bold first row) | Yes | - | NULL as empty cells | Yes | - - Each table becomes its own worksheet and numbers are stored as numeric cells. A table over 1,048,576 rows, Excel's limit, splits across sheets. - - - GitHub-flavoured tables. - - | Option | Default | - |--------|---------| - | Align columns | Yes | - | Write each table's name as a heading | Yes | - | NULL shows as | `NULL` | - - Column widths come from the header and the first 200 rows, so a wide value later in a long table is written whole rather than widening every row before it. A pipe or a line break inside a value is neutralised: both would end the cell early. - - - | Option | Default | - |--------|---------| - | Write a full HTML document | Yes | - | Write each table's name as a heading | Yes | - | Mark NULL cells | Yes | - - A full document carries its own stylesheet and follows the reader's light or dark setting. Turn it off to paste bare `` elements into a page that has its own styling. - - - One element per row, one child element per column. - - | Option | Default | - |--------|---------| - | Pretty print | Yes | - | Mark NULL with `xsi:nil` | Yes | - | Row element | `row` | - - A column name that is not a legal XML element name is rewritten: a leading digit gains an underscore, an illegal character becomes one, and a name starting `xml` is prefixed. Control characters XML 1.0 forbids are dropped, so a binary column cannot make the document unparseable. - - - Install the Parquet plugin from **Settings > Plugins** first. It carries its own copy of DuckDB, which does the encoding, and is too large to ship in the app. - - | Option | Default | - |--------|---------| - | Compression (Snappy, Zstd, Gzip, None) | Snappy | - | Rows per group | 122,880 | - - Column types come from the source engine's own declarations, so numbers, dates and booleans arrive as those rather than as strings. A value that will not convert is written as null rather than failing the export. - - - Parquet holds one table per file. Selecting several writes `dump.users.parquet`, `dump.orders.parquet` and so on beside the name you chose. - - - - -## Transfer to another connection - -Right-click tables in the sidebar and choose **Transfer To…** to copy their rows straight into another connection, with no file in between. - - - - The destination list holds the connections that are already open. A connection that is not open does not appear. - - - Then tick the tables to copy. - - - Each table is wrapped in its own transaction by default, so a table that fails leaves its own rows untouched and the ones before it committed. - - - -Rows only, into a destination table that already exists. A per-table row filter set in the export tree is not carried over; narrow the transfer by transferring fewer tables. - - -**Delete existing rows first** empties each destination table before writing. There is no undo. - - -### Column mapping - -Each ticked table shows how many of its columns map. Columns match by name, the same spelling first and then the same name in another case. Click the count to point a column at a different destination column, or pick **Skip** to leave it out. **Match by Name** puts every column back. - -A destination column takes one source column. Map a second column onto it and **Transfer** stays off until one of the two moves or is skipped. - -## Clipboard paste (CSV/TSV) - -Select a row in the data grid and press `Cmd+V` to paste tabular data straight in. Tabs parse as TSV, commas as CSV. - -## Import data - -**File > Import > Import Data…** (`Cmd+Shift+I`) takes `.sql` and `.sql.gz` files, whose statements execute directly against the database, and `.json`, `.jsonl`, `.ndjson`, `.csv`, `.tsv`, and `.xlsx` files, which load into a table you pick or one TablePro creates. - -The extension picks the format, so one command covers all of them. A file no format reads is refused in the file panel, which names the extensions this connection takes. Only a SQL dump is read compressed here. To import a `.csv.gz`, open it as a [data file](/features/data-files) and choose **Edit > Data > Import into Table…**, which also carries any edits you made first. - -**File > Import > Import Data From** beside it names the format instead and offers every file, which is the route for a CSV saved as `orders.txt` or with no extension at all. **Actions** in the toolbar carries the same pair. - - - - Choose **File > Import > Import Data…** and select the file. The sheet that opens depends on what you picked. - - - Encoding, transaction wrapping, and error handling for SQL; parsing and column mapping for CSV and JSON. TablePro remembers the options from your last successful import, and **Reset to Defaults** restores the stock ones. - - - Check the preview, statement count, and file size, then click **Import**. - - - - - Import dialog - Import dialog - - -### Options - -| Option | What it does | Default | -|--------|-------------|---------| -| On error | Stop and Rollback, Stop and Commit, or Skip and Continue | Stop and Rollback | -| Encoding | UTF-8, UTF-16, UTF-16 LE, UTF-16 BE, Latin-1, Windows-1252, ASCII, Shift JIS, EUC-JP, GB 18030, Big5, or EUC-KR | UTF-8 | -| Wrap in transaction | Runs every statement inside one transaction. Dimmed in Skip and Continue | Yes | -| Disable foreign key checks | Suspends constraint checks for the import | Yes | - -| Mode | What happens on a failed statement | -|------|-----------| -| **Stop and Rollback** | Stops there. With the transaction on, everything rolls back | -| **Stop and Commit** | Stops there, keeping what already succeeded | -| **Skip and Continue** | Logs it and carries on, including a line the parser cannot read. No transaction | - -**UTF-16** reads the byte order from the mark at the start of the file and falls back to big-endian, which is what a file with no mark means. Pick **UTF-16 LE** or **UTF-16 BE** for a file that has no mark and is not big-endian. Latin-1 and Windows-1252 differ over the bytes `0x80` to `0x9F`: a dump written by MySQL keeps its curly quotes, en dashes and euro sign there, so Windows-1252 is the one to pick for it. - -When the preview cannot read the file in the chosen encoding, the dialog switches to the encoding the dump declares in its `SET NAMES` or `SET client_encoding`, or else to the encoding the text matches. It picks Shift JIS, GB 18030 or Big5 only when the dump declares it, because a byte in those encodings can hide a quote's escaping backslash; choose one by hand for a script that declares nothing. A switch the dialog made is not remembered for the next import. A dump read in any encoding but UTF-8 or ASCII reaches the server as UTF-8, so its `SET NAMES cp932` or `SET client_encoding = 'SJIS'` does not run as written: MySQL and MariaDB keep the connection's own character set, and PostgreSQL gets `SET client_encoding = 'UTF8'`. On MySQL and MariaDB, a `_binary` value in a Latin-1 or Windows-1252 dump is sent as the bytes it held. Any other non-UTF-8 dump stops at a `_binary` value holding non-ASCII bytes, since those cannot be read back exactly; export it with `--hex-blob` instead. - -On SQL Server a file that holds a `GO` line runs a batch at a time, cut at its `GO` lines the way `sqlcmd` cuts it, so every count and failure here is a batch rather than a statement. A file with no `GO` line runs a statement at a time. See [Scripts and batches](/databases/mssql#scripts-and-batches). - -Skip and Continue collects up to 1,000 failures with their line numbers and messages, and the summary counts successes against failures behind a **Copy Details** button. **Save Report…** writes them all to a CSV with a line, a statement and the database's own error per row, so a large import's failures can be sorted and searched rather than scrolled. A stop shows the line, the database's own message, and the failing statement, with the dialog still open behind it, ready for a changed setting and another run. The alert shows the first 10,000 characters of a longer statement and the first five errors of a batch that raised more, and **Copy Details** copies all of them. - -### Disabling foreign key checks - -The checkbox runs a different statement per engine, and one of them needs a privilege. - -| Engine | Statement | Needs | -|---|---|---| -| PostgreSQL, Redshift, CockroachDB, PGlite | `SET session_replication_role = replica` | Superuser, or `GRANT SET` on the parameter from PostgreSQL 15 | -| MySQL, MariaDB | `SET FOREIGN_KEY_CHECKS=0` | Nothing | -| SQLite, libSQL, Cloudflare D1 | `PRAGMA foreign_keys = OFF` | Nothing | -| Everything else | Nothing. The option is ignored | | - -A server that rejects the statement stops the import with that error, so clear the checkbox and run it again. A dump TablePro exported usually needs no privilege, because its tables are already ordered parents first. On PostgreSQL and Oracle, whose `CREATE TABLE` leaves foreign keys out, the dump adds them with `ALTER TABLE … ADD CONSTRAINT` after the data instead. - -### Import JSON - -The sheet accepts an array of objects `[{…}, {…}]`, newline-delimited JSON streamed a line at a time, and TablePro's own export shape `{ "table": [ {…} ] }`, so an export round-trips. Then choose a destination: - -- **Existing table**: map each JSON field to a column, as [Map fields to columns](#map-fields-to-columns) describes. A column with no field mapped to it keeps its default or NULL. The list holds tables and partitioned tables, and the server routes each row to the right partition. -- **New table**: the name field opens on a name derived from the file, already selected, so one keystroke replaces it. Review the inferred columns underneath. Name, type, primary key, nullable flag, and default are all editable before the table is created. - -Both lists come from every row in the file. A key that only the last rows carry is still offered, and an inferred type fits every value the field holds, so a field that is a number in all rows but one is typed as text. - -The proposed name drops the extension, turns spaces and punctuation into underscores, and lowercases the result. Letters from any script are kept as they are. On Oracle the name comes through in upper case instead, and is cut to 30 bytes rather than 63. A name an existing table or view already holds gains a numeric suffix, so re-importing `users.csv` next to a `users` table proposes `users_2`. Whatever you type over it is held to the same rules: a name already taken, one longer than the engine allows, or one starting with a prefix the engine keeps for itself is reported in the sheet, with **Import** off until it changes. - -The table list, the new table and the rows all belong to the database and schema the connection was on when the sheet opened. Switching database in another window while the sheet is open leaves the import where it was. - -Rows insert through parameterized statements, so a JSON value is never concatenated into SQL. Nested objects and arrays are stored as JSON text. In a new PostgreSQL table their column is `jsonb`, or `json` on 9.2 and 9.3, and `text` on 9.1, which has no JSON type. - -### Import XLSX - -Reads the first worksheet of an `.xlsx` workbook. The destinations and the proposed new-table name -behave as they do for JSON. - -| Option | Default | -|--------|---------| -| First row holds column names | Yes | -| Trim whitespace | No | -| Treat empty cells as NULL | Yes | - -A row that omits its empty cells still lands in the right columns: each cell's own reference decides where it goes, not its position among the cells that were written. Text stored in the workbook's shared string table is resolved, including a value split across several formatting runs. - -The workbook is read whole rather than streamed, because a sheet's rows refer back to a string table that has to be held anyway. - -### Import CSV - -CSV and TSV open the same sheet as JSON, with parsing options in front of the mapping. The delimiter and encoding are detected from the file. Changing any option reads the file again. A field that is still there keeps the column it was mapped to. In a new table each column setting you changed stays with its field, even through a read that misses the field, such as one with the wrong delimiter. Settings you left alone follow the new read, such as a type inferred again from trimmed values. - -| Option | What it does | Default | -|--------|-------------|---------| -| Delimiter | Comma, semicolon, tab, or pipe | Auto-detect | -| Quote character | Double or single quote | Double quote (`"`) | -| Encoding | UTF-8, UTF-16 LE, UTF-16 BE, ISO Latin 1, Windows-1252, Shift JIS, EUC-JP, GB 18030, Big5, or EUC-KR | Auto-detect | -| First row is a header | Row 1 becomes the column names; off imports it as data | Yes | -| Trim leading and trailing spaces | Trims each field before import | No | -| Treat empty values as NULL | Inserts NULL instead of empty text | Yes | -| NULL text | One more value to import as NULL, `\N` for instance | None | - -Quoted fields keep embedded commas and newlines (RFC 4180), and a doubled quote (`""`) decodes to one. A line the encoding cannot read stops the import before any row is deleted or inserted, and the error names the line. - -### Map fields to columns - -Every field in the file gets a row with a checkbox and a column menu. A field named like a column, ignoring case, starts mapped to it. The rest start on **Skip**. - -**Match Columns** above the rows fills them all at once: - -| Item | Result | -|---|---| -| **Match by Name** | Each field goes to the column of the same name, ignoring case. Fields with no such column are skipped | -| **Match by Position** | The first field goes to the first column, the second to the second, and so on. Dimmed for JSON, whose fields are listed alphabetically | -| **Use Saved Mapping** | Puts back the mapping saved for this table | - -Clicking **Import** saves each choice that differs from Match by Name for that table, whether the import succeeds or not. The next file imported into the same table starts from those choices, and the sheet reads **Restored the mapping saved for** and the table name. A saved column the table no longer has falls back to the name match. Importing into a new table saves any column you renamed, so the same file lands on the renamed columns next time. - -Saved mappings stay on this Mac. They follow a renamed table and are forgotten when the table is dropped, from the sidebar or with SQL as [Drop or rename with SQL](/features/table-operations#drop-or-rename-with-sql) describes, and when the connection is deleted. - -### Row imports - -CSV and JSON insert in batches, and the on-error and transaction options behave as they do for SQL. They add **Delete existing rows before import**, which clears the target table inside the import transaction, so a failure in the default Stop and Rollback mode brings the rows back. A row where no field matches a mapped column writes nothing and is reported rather than counted. diff --git a/docs/features/json-viewer.mdx b/docs/features/json-viewer.mdx deleted file mode 100644 index 5fa33d2bf8..0000000000 --- a/docs/features/json-viewer.mdx +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: Cell and Row Viewers -description: View and edit JSON, PHP serialized, image and binary cell values, set per-column display formats, and inspect whole rows in the sidebar ---- - -Click the chevron on a JSON or blob cell to open its value in a popover anchored to that cell. Which viewer opens follows the column: its declared type, or the **Display As** choice on its header, and that choice changes rendering only, never what is stored. - -Whether it opens for editing follows the row and the column. A row marked for deletion opens nothing at all. A result the app cannot write back to, a generated column, and a column the driver marks immutable such as MongoDB's `_id` all open read-only; [Change Tracking](/features/change-tracking) covers which results are writable. PHP serialized values, and binary past the hex cap, are read-only for everyone. - -## Display as formats - -Right-click a column header and pick **Display As**. The submenu lists the formats that fit the column type, with a checkmark on the active one. - -| Format | Column types | Effect | -| --- | --- | --- | -| Raw Value | all | Shows the stored value, and turns off detection for that column | -| Text | binary | Reads the bytes as UTF-8, trailing `BINARY(N)` padding trimmed | -| UUID | binary, text | Renders 16 raw bytes or a 32-digit hex string as a hyphenated UUID | -| Unix Timestamp (seconds) | integer | Renders the number as a formatted date | -| Unix Timestamp (milliseconds) | integer | Same, treating the number as milliseconds | -| JSON | text | Opens the cell in the JSON viewer | -| PHP Serialized | text | Opens the cell in the PHP serialized viewer | - -An override persists per column, scoped to the connection and table. A column with a format set copies in that format when you copy the single cell; a block of cells or a whole row always copies the stored value. - -Id-like binary and character columns, integer columns whose names read as timestamps, and binary columns whose first rows all read as UTF-8 pick up their format on their own. **Smart value detection** in [Settings > Data](/customization/data-settings) turns that off, and a manual choice always wins over it, **Raw Value** included. MongoDB binary columns never offer UUID, since the driver decodes them under its own Legacy UUID Encoding setting; see [MongoDB](/databases/mongodb). - -**Text** decides per cell, not per column: an image or a hash sitting in a column set to **Text** still reads as `0x89504E…`. The cell edits as hex either way. - -## JSON viewer - -A JSON-typed column opens on the chevron; double-click or `Enter` edits it inline instead. A text column holding JSON needs **Display As > JSON** first. - -The popover has two modes. **Text** is syntax-highlighted and pretty-printed, and editable when the cell is. **Tree** is a collapsible outline with a search field. It never edits, but its rows and the text in them select and copy. Both keep your key order and exact number values, integers larger than JavaScript can represent included. - - - JSON editor popover - JSON editor popover - - -The mode you switch to becomes the default for the next value, which is the same preference as **JSON Viewer > Default view** in Settings. The pop-out button detaches the viewer into its own resizable window. - -Opening a value and seeing it pretty-printed is not an edit: the row is marked changed only once you alter the content. **Save** commits a compact value, key order and numbers preserved, through the [change tracking](/features/change-tracking) queue. Text that is not valid JSON prompts first. - -A NULL or empty cell reads **Empty Value** in **Tree** mode. On an editable cell, switch to **Text** to enter JSON. - -### Selecting and copying in a tree - -Click a row to select it. The arrow keys move the selection, `Shift` and `Cmd` extend it, and `Cmd+A` selects every row. `Cmd+C` copies the selected values, one per line: a string without its quotes, an object or array as compact JSON. A row inside a selected object or array is already part of that JSON and is not copied a second time. - -For part of a value, select its row, drag across the text or double-click a word, then press `Cmd+C`. Text copies as the row shows it: a long string shortened, a line break written as `\n`. **Copy Value** on the right-click menu copies the whole value. - -**Copy Key** and **Copy Key Path** sit on the same menu. A path reads `$.user.emails[0]`. A key that is not a plain identifier is quoted, as in `$."content-type"`, the form SQLite, PostgreSQL, MariaDB and DuckDB read in a JSON path. - -`Escape` ends a text selection first, then clears the row selection. Pressed once more, it closes the popover and drops an edit you have not saved. - -### Links and color swatches - -A string that is one whole `http`, `https` or `mailto` address is drawn as a link. The first click selects its row. Click the link in a selected row and it opens in your browser or mail app. `Space` opens the link of a single selected row, the right-click menu has **Open Link** and **Copy Link**, and holding the pointer over the link shows the address in full. - -Everything else stays text: `file:` and every other scheme, `www.example.com` with no scheme, an address inside a longer string or with a space beside it, an address carrying a user name and password, a `mailto:` with anything after the address, and an address too long for its row that is cut off before the end of its host name, since the part shown could name another site. **Copy Value** still copies that address whole. An address has to be plain ASCII with the rest percent-escaped, so `/wiki/Vi%E1%BB%87t_Nam` links and `/wiki/Việt_Nam` does not. - -A string that is a CSS color carries a swatch of that color beside it. - -| Format | Examples | -| --- | --- | -| Hex, 3, 6 or 8 digits | `#f80`, `#ff8800`, `#ff880080` | -| `rgb()` and `rgba()` | `rgb(255, 136, 0)`, `rgb(255 136 0 / 50%)` | -| `hsl()` and `hsla()` | `hsl(32, 100%, 50%)`, `hsl(32deg 100% 50% / 0.5)` | - -Any 3, 6 or 8 hex digits after `#` count, so an order number stored as `#404` gets a swatch too. Four-digit hex such as `#f80c` and color names such as `orange` get none. - -### Filtering a tree - -The search field above the tree filters keys and values as you type. The [PHP serialized tree](#php-serialized-viewer) works the same way. - -- Matching ignores case and accents, so `cafe` finds `café`. Full-width and half-width differ: `ABC` does not find `ABC`. -- A row matches on its whole value, not the shortened form shown, so a match deep inside a long string counts. A match under a collapsed parent opens its parents, and a key that matches keeps its contents and stays expandable. -- Only text from the document matches. A JSON array position such as `[1]`, a count such as `{2 keys}` and the `…` row are the tree's own labels, and no filter finds them. -- A filter that matches nothing says so, and says separately when the document was cut at 5,000 nodes, since the rows past the cut were never searched. - -### Arrays of JSON - -A PostgreSQL `jsonb[]` or `json[]` cell opens on its elements: the list at the top, the selected element in the same Text and Tree viewer below. - -Add, remove and reorder elements with the controls under the list. **NULL** on an element writes a SQL NULL, which is a different value from the JSON `null` an element can hold: the list shows the first as `NULL` and the second as `null`. **Edit as Text** switches to the raw `{…}` literal. - -Reading an element does not change it. Only an element whose JSON you alter is rewritten, and it is written back compact, so a `json[]` column keeps the exact text it stored for every element you left alone. - -## PHP serialized viewer - -Set **Display As > PHP Serialized** on a text column, then double-click or press `Enter`. The viewer is read-only: PHP serialized values round-trip through PHP itself. - -**Tree** mode is the JSON tree with PHP's types: it filters, [selects, copies](#selecting-and-copying-in-a-tree) and draws [links and color swatches](#links-and-color-swatches) the same way, with type badges on the nodes (`str`, `int`, `arr`, `obj`, `ser`, `ref`) and visibility badges on object members (`protected`, `private (ClassName)`). A custom-serialized class (the `C:` token) is one opaque leaf that copies as its whole payload, and a reference (`r:`/`R:`) shows as `→ #N` and is not followed. **Raw** mode shows the original string with text selection. - -An array or object row copies as its summary, such as `[2 items]`, not as its contents, so each row you select inside it copies its own value after the summary. Copy the serialized text from **Raw** mode instead. - -## Blob and hex - -A blob cell renders in the grid as compact hex (`0x48656C…`), unless its column reads as text. Click the chevron or double-click for the popover: a classic hex dump with offset, byte columns, and ASCII, plus an editable hex field under it on an editable table. That field takes spaced or continuous hex, with or without a `0x` prefix, and invalid hex disables **Save**. - -Bytes that read as an image open on the picture instead, with **Hex** the second segment. The [image viewer](#image-viewer) covers that. - -## Image viewer - -A cell whose content is an image opens on the drawing, with its stored form one segment away: **Hex** for a binary column, **Source** for SVG markup in a text column. The source stays editable wherever the cell is, and **Save** commits it through the same [change tracking](/features/change-tracking) queue as any other edit. - -Detection reads the value, not the column, since the same PNG turns up in `BLOB`, `BYTEA` and `VARBINARY`, and SVG arrives as often in a text column as a binary one. PNG, JPEG, JPEG 2000, GIF, TIFF, BMP, ICO, WebP, HEIC, HEIF and AVIF are identified from their header; SVG from its root element, behind any byte order mark, XML declaration, comment or doctype. A PDF, a Photoshop file and a camera raw file are left as hex, since a database client that opened them as "the image in this cell" would be guessing. **Display As > Raw Value** on the column turns detection off. - - - A cell popover and the row inspector each drawing an SVG logo on a transparency checkerboard, both showing Image and Source segments - A cell popover and the row inspector each drawing an SVG logo on a transparency checkerboard, both showing Image and Source segments - - -Transparency draws on a checkerboard. The footer names the format and, for a raster image, the stored pixel size, which is what the file holds rather than what fits the pane. **Copy Image** puts the drawing on the clipboard as an image; **Copy** on the cell itself still copies the stored value. The pop-out button opens a resizable window, read-only there. - -SVG rasterizes in a separate process that reads the markup and nothing else. It reaches no network and no file: an ``, an `@import`, an external entity and a `