Dsql
AWS LabsOfficialPublished by AWS Labs
- Skill
- Tool integration
- Data and AI
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The dsql skill provides a structured framework for interacting with Amazon Aurora DSQL. It encompasses schema management, migration strategies from MySQL and PostgreSQL, performance diagnostics, and bulk data loading. The skill integrates with MCP tools for direct database operations and provides extensive documentation on IAM authentication, multi-tenant patterns, and transaction management to ensure safe and efficient database operations.
What it does
- Executes SQL queries and DDL operations via MCP tools.
- Provides migration paths and type mapping for MySQL and PostgreSQL to DSQL.
- Offers diagnostic workflows for cluster performance and query plan explainability.
- Facilitates bulk data loading using specialized loaders.
- Enforces best practices for IAM authentication and scoped database roles.
- Validates SQL compatibility and provides linting for DSQL-specific constraints.
When to use it
- When building or migrating applications to Aurora DSQL.
- When performing schema changes or complex DDL migrations.
- When troubleshooting performance issues or query plan inefficiencies.
- When setting up multi-tenant database architectures.
- When bulk loading data into a DSQL cluster.
Advantages
- Ensures adherence to DSQL-specific transaction and concurrency limits.
- Promotes secure access patterns through IAM and scoped roles.
- Provides clear, phased workflows for complex tasks like performance tuning.
- Reduces migration friction with automated linting and type mapping guidance.
- Standardizes diagnostic reporting for query plan analysis.
Usage examples
- “How do I migrate my existing PostgreSQL schema to Aurora DSQL?”
- The agent will guide you through pg-migrations/type-mapping.md and schema-objects.md, providing steps to convert indexes, enums, and data types while ensuring compatibility.
- “My query is running slowly on DSQL, can you help?”
- The agent will initiate Workflow 9 (Query Plan Explainability) to analyze the query plan, check for full scans, and provide a structured diagnostic report.
- “How do I set up a multi-tenant table in DSQL?”
- The agent will provide instructions on using tenant_id in your schema design, enforcing it in WHERE clauses, and using separate transact calls for DDL.
- “What are the transaction limits for DSQL?”
- The agent will consult the limits-and-error-codes.md or awsknowledge tool to provide the exact limits for row modifications, data size, and duration.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/awslabs/dsql/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip dsql.zip -d .agents/skills
Documentation by AWS Labs
Amazon Aurora DSQL Skill
Aurora DSQL is a serverless, PostgreSQL-compatible distributed SQL database. This skill covers direct query execution via MCP tools, schema management, migrations, multi-tenant isolation, IAM auth, and bulk data loading via aurora-dsql-loader.
Reference Files
Load these files as needed for detailed guidance:
Core:
| Reference | When to Load | Contains |
|---|---|---|
| development-guide.md | ALWAYS before schema changes or DB operations | Best practices, DDL rules, transaction limits, foreign key constraints |
| foreign-keys.md | MUST load for foreign key operations or migrations | FK syntax, actions, validation, tenant keys |
| language.md | MUST load for language-specific choices | Driver selection, DSQL Connectors, connection code |
| access-control.md | MUST load for roles, grants, or sensitive data | Scoped role setup, IAM-to-database role mapping |
| troubleshooting.md | SHOULD load for errors or unexpected behavior | OCC and 23503, FK, connections, cluster state |
| limits-and-error-codes.md | SHOULD load before sizing batches, keys or indexes, or on 54000/0A000/54011 | Every limit, rejection and quota with SQLSTATE and exact message |
| dsql-examples.md | Load for implementation examples | Multi-tenant access, batch operations, identity and sequences, connection pooling |
| onboarding.md | User requests "Get started with DSQL" | Interactive step-by-step guide |
| occ-retry-patterns.md | MUST load for OCC retry code or conflict mitigation | Connectors, 40001 retry, non-retryable 23503, FK read conflicts, idempotent design |
MCP:
| Reference | When to Load | Contains |
|---|---|---|
| mcp-setup.md | Always for MCP server guidance | Setup instructions, 2 configuration options |
| mcp-tools.md | For MCP tool syntax and examples | Tool parameters, input validation |
| dsql-lint.md | MUST load before running dsql_lint or processing external SQL | Tool reference, fix statuses, unfixable error resolution |
DDL Migrations:
| Reference | When to Load | Contains |
|---|---|---|
| ddl-migrations/overview.md | MUST load for ALTER TYPE, SET NOT NULL, MODIFY PRIMARY KEY | Direct ALTER coverage and last-resort table recreation |
| ddl-migrations/column-operations.md | DROP COLUMN, ALTER TYPE, SET/DROP NOT NULL/DEFAULT | Column-level migration patterns |
| ddl-migrations/constraint-operations.md | ADD/DROP CONSTRAINT, VALIDATE CONSTRAINT, MODIFY PRIMARY KEY | Constraint and structural changes |
| ddl-migrations/batched-migration.md | Tables exceeding 3,000 rows | Batching patterns, progress tracking |
MySQL Migrations:
| Reference | When to Load | Contains |
|---|---|---|
| mysql-migrations/type-mapping.md | MUST load for MySQL → DSQL migration | Data type mappings, feature alternatives |
| mysql-migrations/ddl-operations.md | Translating MySQL DDL to DSQL | AUTO_INCREMENT, ENUM, SET, FK patterns |
| mysql-migrations/full-example.md | Complete MySQL table migration | End-to-end example with decision summary |
PostgreSQL Migrations:
| Reference | When to Load | Contains |
|---|---|---|
| pg-migrations/type-mapping.md | MUST load for DSQL NUMERIC or PG type questions | C collation rules, NUMERIC(p,s), JSON/JSONB |
| pg-migrations/index-conversion.md | MUST load for any index conversion or index support question | GIN/GiST/BRIN → btree, operator class removal, partial + expression indexes, sort order |
| pg-migrations/schema-objects.md | MUST load for ENUM, materialized views, extensions, multi-schema, CREATE FUNCTION | ENUM → CHECK, views, role/IAM mapping, LANGUAGE sql functions |
| pg-migrations/multi-region.md | Multi-region, active-active, or HA questions | Architecture, geographic partitioning |
ORM Guides:
| Reference | When to Load | Contains |
|---|---|---|
| orm-guides/overview.md | Migrating an ORM or using ORM locking with DSQL | Adapter names, locking, and key gotchas for Django/EF Core/Hibernate/Rails/SQLAlchemy |
Data Loading:
| Reference | When to Load | Contains |
|---|---|---|
| data-loading.md | Planning or running bulk loads with aurora-dsql-loader | Fresh-vs-warm partitions, resume/retry, --on-conflict semantics, throughput diagnostics |
System Diagnostics:
| Reference | When to Load | Contains |
|---|---|---|
| system-diagnostics/workflow.md | MUST load at Workflow 12 entry — cluster performance diagnostics | Prerequisites, 5 diagnostic phases, temporal comparison, handoff |
| system-diagnostics/wait-events.md | ALWAYS load when interpreting AAS results | Canonical DSQL wait event descriptions and investigation guidance |
| system-diagnostics/promql-patterns.md | Load when constructing PromQL queries | Reusable query templates for AAS breakdown, top-SQL, temporal compare |
Query Plan Explainability:
| Reference | When to Load | Contains |
|---|---|---|
| query-plan/workflow.md | MUST load at Workflow 9 entry — gates all other files | Trigger criteria, context disambiguation, routing, phased workflow |
| query-plan/plan-interpretation.md | MUST load at Workflow 9 Phase 0 | DSQL node types, Node Duration math, estimation-error bands |
| query-plan/catalog-queries.md | MUST load at Workflow 9 Phase 0 | pg_class/pg_stats/pg_indexes SQL, correlated-predicate verification |
| query-plan/guc-experiments.md | MUST load at Workflow 9 Phase 0 | GUC experiment procedures, 30-second skip protocol |
| query-plan/report-format.md | MUST load at Workflow 9 Phase 0 | Required report structure, element checklist, support request template |
| query-plan/query-rewrites-generic.md | SHOULD load at Phase 0; sub-files on-demand | Index of 10 generic rewrite patterns |
| query-plan/query-rewrites-dsql-specific.md | SHOULD load at Phase 0; sub-files on-demand | Index of DSQL-specific rewrite patterns |
Choosing How to Connect: MCP vs CLI/psql
The aurora-dsql MCP server binds a single cluster at startup (--cluster_endpoint), so
using it for another cluster means editing .mcp.json and restarting the session.
Use the
aurora-dsqlMCP tools (readonly_query,transact,get_schema) ONLY when the server already targets the cluster you need.Otherwise — unconfigured, disabled, or bound to a different cluster — do NOT reconfigure it. Use the CLI +
psqlpath instead:scripts/psql-connect.sh<cluster-id> --region <region> --command "SELECT ..."(mints an IAM token and runs viapsql).If you cannot confirm which cluster the MCP targets, confirm first or use the CLI/psql path — running against the wrong cluster is worse than the check.
The doc-only MCP tools (dsql_lint, dsql_*_documentation, dsql_recommend) need no cluster.
The CloudWatch MCP (Workflow 12) takes region/cluster_id per call, so one running server can
query clusters in any PromQL-enabled region (pass each cluster's region on the call). Details:
connectivity-tools.md.
MCP Tools Available
The aurora-dsql MCP server provides these tools:
Database Operations:
readonly_query - Execute SELECT queries (returns list of dicts)
transact - Execute DDL/DML statements in transaction (takes list of SQL statements)
get_schema - Get table structure for a specific table
SQL Validation:
dsql_lint - Validate SQL for DSQL compatibility and optionally auto-fix issues. Use before executing externally-sourced SQL.
Documentation & Knowledge:
dsql_search_documentation - Search Aurora DSQL documentation
dsql_read_documentation - Read specific documentation pages
dsql_recommend - Get DSQL best practice recommendations
Note: There is no list_tables tool. Use readonly_query with information_schema.
See mcp-setup.md for detailed setup instructions. See mcp-tools.md for detailed usage and examples.
AWS Knowledge MCP (awsknowledge)
Consult for verifying DSQL service limits before advising users. The numeric limits below are defaults that may change — when a user's decision depends on an exact limit, verify it first:
| Limit | Default | Verify query |
|---|---|---|
| Max row modifications per transaction | 3,000 | aurora dsql transaction limits |
| Max data size per transaction | 10 MiB | aurora dsql transaction limits |
| Max transaction duration | 5 minutes | aurora dsql transaction limits |
| Max connections per cluster | 10,000 | aurora dsql connection limits |
| Auth token expiry | 15 minutes | aurora dsql authentication token |
| Max connection duration | 60 minutes | aurora dsql connection limits |
| Max indexes per table | 24 incl. PK | aurora dsql index limits |
| Max columns per index | 8 | aurora dsql index limits |
| IDENTITY/SEQUENCE CACHE values | 1 or >= 65536 | aurora dsql sequence cache |
| Supported column data types | See docs | aurora dsql supported data types |
When to verify: Before recommending batch sizes, connection pool settings, or schema designs where hitting a limit would cause failures; any time the exact number can affect user decision. For the full limit set with each SQLSTATE and its exact error message, see limits-and-error-codes.md; where it marks a value observed and the docs differ, use the observed value.
Fallback: If awsknowledge is unavailable, use the defaults above and flag that limits should be verified against DSQL documentation.
CLI Scripts Available
Bash scripts in scripts/ for cluster management (create, delete, list, cluster info), psql connection, and bulk data loading from local/s3 csv/tsv/parquet files. See scripts/README.md for usage and hook configuration.
Quick Start
Pick a connection path: confirm the
aurora-dsqlMCP targets your cluster; if not, use the CLI/psqlpath instead — see Choosing How to Connect. The steps below name MCP tools; the equivalent SQL runs the same way throughpsql-connect.sh --command "...".Explore: Use
readonly_querywithinformation_schemato list tables. Useget_schemafor table structure.Query: Use
readonly_queryfor SELECT queries. MUST includetenant_idin WHERE for multi-tenant apps. MUST build SQL withsafe_query.build().Schema changes: Use
transactwith one DDL per transaction. MUST batch DML under 3,000 rows. MUST useCREATE INDEX ASYNCin a separate call. Usedsql_lintto validate first.Bulk load data: Use
aurora-dsql-loaderfor CSV/TSV/Parquet. Load data-loading.md for details. Use--dry-runfirst.
Performance Routing
When the user reports a performance problem, use this table to select the correct workflow:
| User signal | Route to |
|---|---|
| General performance complaint, "cluster is slow", "something changed", latency regression, no specific query identified | Workflow 12 (System Diagnostics) — observe via CloudWatch first |
| Specific query or query_id to investigate, "explain this plan", "why is this query slow" | Workflow 9 (Query Plan Explainability) — direct EXPLAIN analysis |
| OCC conflicts, commit errors, retry storms | Workflow 12 (System Diagnostics) — confirm via CW metrics before investigating |
| Cost optimization, "where is compute time spent" | Workflow 12 (System Diagnostics) — identify top contributors first |
Rule: When in doubt, start with Workflow 12. It identifies specific queries to investigate and routes to Workflow 9 with context.
Common Workflows
Workflow 1: Create Multi-Tenant Schema
Create tenant-owned tables with
tenant_idusing separatetransactcallsUse composite tenant FKs for tenant-owned parents and ordinary FKs for shared parents
Create tenant and query-pattern indexes with separate
CREATE INDEX ASYNCcallsVerify with
get_schema
MUST issue each DDL in its own transact call:
transact(["CREATE TABLE ..."])MUST serialize arrays into a single-column representation — DSQL has no array column type; PREFER
JSONB(operators work directly); MAY useTEXTwhen the column is opaque to the database; ASK the user. ForJSONBarrays, expand at query time withjsonb_array_elements_text(data)
Workflow 2: Safe Data Migration
MUST validate every DDL with dsql_lint(fix=true) before executing. DML does not require linting.
Validate DDL with
dsql_lint(sql=..., fix=true)— handle diagnostics per dsql-lint.mdExecute the reviewed
fixed_sqlwhen present, otherwise the reviewed source statementAdd column in its own
transactcallPopulate existing rows with UPDATE (batched under 3,000 rows)
Verify with readonly_query COUNT
Create an index if needed: validate then execute the reviewed DDL in its own
transactcall
MUST issue each
ALTER TABLEin its owntransactcall — DSQL rejects multi-DDL transactions withmultiple ddl statements not supported in a transactionMUST add column with only name and type; apply DEFAULT via separate UPDATE
MUST batch updates under 3,000 rows in separate transact calls
Recovery: Resume failed batches by filtering WHERE new_column IS NULL.
Workflow 3: Bulk Data Loading
Use aurora-dsql-loader for CSV, TSV, or Parquet loads. MUST load data-loading.md before advising on throughput or diagnosing slow loads.
Validate with
--dry-runfirstRun with
--manifest-diron persistent storage (not/tmp— tmpfs on AL2023, lost on crash) and--headerif file has a header rowOn failure: resume with
--resume-job-id; for duplicates use--on-conflict do-nothingFor large tables: create secondary indexes after load using
CREATE INDEX ASYNC
Workflow 4: Foreign Key Constraints
MUST load and follow foreign-keys.md before creating, altering, dropping, or migrating foreign keys.
Workflow 5: Query with Tenant Isolation
MUST authorize the caller against the tenant — format validation does not establish authorization
MUST build SQL with
safe_query.build()— useallow()/regex()for values (emits'v'),ident()for table/column names (emits"v"). See input-validation.mdMUST include
tenant_idin the WHERE clause; reject cross-tenant access at the application layer
Workflow 6: Set Up Scoped Database Roles
MUST load access-control.md for role setup, IAM mapping, and schema permissions.
Workflow 7: Table Recreation DDL Migration
For ALTER COLUMN TYPE, SET NOT NULL, or MODIFY PRIMARY KEY, MUST
load ddl-migrations/overview.md. Use a direct ALTER form
when supported; otherwise, present a user-approved table-recreation plan.
Workflow 8: Validate and Migrate to DSQL
MUST load dsql-lint.md before running dsql_lint. Run dsql_lint(sql=source_sql, fix=true) to validate and auto-convert. For MySQL-origin SQL, MUST cross-check against mysql-migrations/type-mapping.md even when lint returns clean. On parse_error, fall back to manual conversion then re-lint.
Workflow 9: Query Plan Explainability
Explains why the DSQL optimizer chose a particular plan. Triggered by slow queries, high DPU, unexpected Full Scans, or plans the user doesn't understand. REQUIRES a structured Markdown diagnostic report as the deliverable.
MUST load query-plan/workflow.md at entry — it defines trigger criteria, context disambiguation, routing, and the full phased workflow (Phase 0–4). Workflow.md specifies which reference files to load at each phase.
Safety. Plan capture uses readonly_query exclusively. Rewrite DML to SELECT for plan capture. MUST NOT use transact --allow-writes for plan capture.
Workflow 10: Full PostgreSQL → DSQL Schema Migration
MUST load pg-migrations/type-mapping.md, pg-migrations/index-conversion.md, pg-migrations/schema-objects.md, and foreign-keys.md. Run dsql_lint(fix=true) first for mechanical fixes, preserve foreign-key relationships, translate unsupported source syntax or options, then apply semantic conversions from the pg-migrations references for unfixable diagnostics and patterns the linter cannot handle. Re-lint the final output before deploying.
Workflow 11: ORM Migration and Locking (Django/EF Core/Hibernate/Rails/SQLAlchemy)
Load orm-guides/overview.md for adapter names, locking guidance, and framework-specific gotchas.
Workflow 12: System Diagnostics (CloudWatch AAS)
Diagnose cluster performance by querying db.active_sessions.avg via PromQL. Detects temporal anomalies in wait event distribution, identifies regressed queries, and routes to Workflow 9 for per-query investigation.
Requires: CloudWatch MCP server (awslabs.cloudwatch-mcp-server) enabled and configured with PromQL access in the same region as the cluster — see mcp/mcp-setup.md for enabling it, region requirements, and the session restart needed for its tools to register.
MUST load system-diagnostics/workflow.md at entry — it defines prerequisites, 5 diagnostic phases, temporal baselines, and the routing to Workflow 9 for identified queries.
Error Scenarios
awsknowledgereturns no results: Use the default limits in the table above and note that limits should be verified against DSQL documentation.dsql_lintunavailable or timing out: See the Error Handling section of dsql-lint.md. Do not silently skip validation — inform the user and require explicit confirmation before proceeding with manual rules from development-guide.md.OCC serialization error (
40001): Retry with backoff, and keep(OC001)in the loop too — see occ-retry-patterns.md.Foreign key violation (
23503): Correct the relationship or referential action; MUST NOT send it through the40001retry loop — see troubleshooting.md.Transaction exceeds limits: Split batches under 3,000 rows and 10 MiB; size is checked at
COMMIT— see limits-and-error-codes.md to size them and batched-migration.md for the pattern.Token expiration mid-operation: Generate a fresh IAM token — see authentication-guide.md. See troubleshooting.md for other issues.
Additional Resources
Files
- ATTRIBUTION.mdAdded by AutoGineer868 B
- LICENSELicence file added by AutoGineer9.9 KiB
- NOTICELicence file added by AutoGineer89 B
- SKILL.md27.9 KiB
- mcp/mcp-setup.md6.7 KiB
- mcp/mcp-tools.md2.0 KiB
- mcp/platforms/claude-code.md3.6 KiB
- mcp/platforms/codex.md1.6 KiB
- mcp/platforms/gemini.md2.1 KiB
- mcp/platforms/kiro.md2.8 KiB
- mcp/tools/database-tools.md4.6 KiB
- mcp/tools/documentation-tools.md1.3 KiB
- mcp/tools/input-validation.md3.3 KiB
- mcp/tools/safe_query.pyScript10.4 KiB
- mcp/tools/workflow-patterns.md3.1 KiB
- references/access-control.md5.0 KiB
- references/auth/authentication-guide.md4.6 KiB
- references/auth/connectivity-tools.md8.7 KiB
- references/auth/scaling-guide.md3.9 KiB
- references/data-loading.md6.0 KiB
- references/ddl-migrations/batched-migration.md2.5 KiB
- references/ddl-migrations/column-operations.md5.2 KiB
- references/ddl-migrations/constraint-operations.md6.7 KiB
- references/ddl-migrations/overview.md4.8 KiB
- references/development-guide.md9.6 KiB
- references/dsql-examples.md1.5 KiB
- references/dsql-lint.md10.8 KiB
- references/examples/connection.md2.5 KiB
- references/examples/data-operations.md2.9 KiB
- references/examples/migrations.md1.6 KiB
- references/examples/patterns.md5.5 KiB
- references/examples/schema.md1.6 KiB
- references/foreign-keys.md6.0 KiB
- references/language.md8.6 KiB
- references/limits-and-error-codes.md19.2 KiB
- references/mysql-migrations/ddl-auto-increment.md4.3 KiB
- references/mysql-migrations/ddl-batching.md1.0 KiB
- references/mysql-migrations/ddl-column-changes.md1.3 KiB
- references/mysql-migrations/ddl-constraints.md782 B
- references/mysql-migrations/ddl-operations.md1.5 KiB
- references/mysql-migrations/ddl-structural.md3.7 KiB
- references/mysql-migrations/ddl-type-alternatives.md4.5 KiB
- references/mysql-migrations/full-example.md7.3 KiB
- references/mysql-migrations/type-mapping.md14.3 KiB
- references/occ-retry-patterns.md9.6 KiB
- references/onboarding.md13.8 KiB
- references/orm-guides/overview.md10.9 KiB
- references/pg-migrations/index-conversion.md15.7 KiB
- references/pg-migrations/multi-region.md2.9 KiB
- references/pg-migrations/schema-objects.md15.0 KiB
- references/pg-migrations/type-mapping.md5.3 KiB
- references/query-plan/catalog-queries.md8.0 KiB
- references/query-plan/guc-experiments.md7.5 KiB
- references/query-plan/plan-interpretation.md15.0 KiB
- references/query-plan/query-rewrites-dsql-specific.md900 B
- references/query-plan/query-rewrites-generic.md1.9 KiB
- references/query-plan/query-rewrites/cte-late-materialization.md1.4 KiB
- references/query-plan/query-rewrites/flatten-union-all.md946 B
- references/query-plan/query-rewrites/left-join-to-inner.md794 B
- references/query-plan/query-rewrites/not-in-to-not-exists.md1.4 KiB
- references/query-plan/query-rewrites/or-to-in.md889 B
- references/query-plan/query-rewrites/propagate-filter.md1.0 KiB
- references/query-plan/query-rewrites/push-computation-to-constant.md1.0 KiB
- references/query-plan/query-rewrites/push-group-by-into-subquery.md1.6 KiB
- references/query-plan/query-rewrites/reltuples-estimate.md1.2 KiB
- references/query-plan/query-rewrites/split-large-joins.md2.2 KiB
- references/query-plan/query-rewrites/subquery-unnesting-correlated.md1.6 KiB
- references/query-plan/query-rewrites/subquery-unnesting-scalar.md1.4 KiB
- references/query-plan/query-rewrites/subquery-unnesting-uncorrelated.md1.4 KiB
- references/query-plan/report-format.md10.9 KiB
- references/query-plan/workflow.md9.4 KiB
- references/system-diagnostics/promql-patterns.md8.6 KiB
- references/system-diagnostics/wait-events.md13.7 KiB
- references/system-diagnostics/workflow.md19.4 KiB
- references/troubleshooting.md11.7 KiB
references/pg-migrations/index-conversion.md
Index Conversion for DSQL
Run dsql_lint(fix=true) first — it handles most index conversions automatically (ASYNC,
USING gin/gist/brin/hash → btree, CONCURRENTLY removal, INCLUDE preservation, and stripping
ASC/DESC from index keys — DSQL rejects sort direction outright, see
Index Key Sort Order).
This file covers the cases a mechanical fix cannot resolve:
GIN, GiST, and BRIN indexes —
dsql_lintstrips theUSINGclause, but the resulting btree index does not serve the same queries. These need a redesign.Operator classes such as
text_pattern_ops—dsql_lintleaves these in place with no diagnostic, so strip them by hand. DSQL rejects them withERROR: 0A000: opclass not supported for index keysSort direction —
index_sort_direction; auto-fixed, but the fix changes whichORDER BYthe index serves, and forASCit is reported as neither an error nor a warning. See Index Key Sort Order. NULLS placement is not part of this rule and is never stripped — see NULLS placement is retained.
Partial indexes (WHERE) and expression indexes are supported — add ASYNC and keep them as
written. No redesign is needed.
Sources:
Table of Contents
GIN Index Conversion
GiST Index Conversion
BRIN Index Conversion
Partial Indexes
Expression Indexes
Index Limits
Index Key Sort Order
Monitoring Async Index Status
Conversion Decision Flowchart
GIN Index Conversion
GIN indexes are used for full-text search, JSONB containment, and array operations. DSQL uses btree indexes exclusively — convert GIN to btree where possible.
JSONB GIN → Expression Index on Extracted Key
-- PostgreSQL: GIN index on JSONB column
CREATE INDEX idx_users_prefs ON users USING gin (preferences);
-- Used for: preferences @> '{"theme":"dark"}'
-- DSQL: No equivalent index. JSONB operators work at runtime without index.
-- The query still works, just without index acceleration:
SELECT * FROM users WHERE preferences @> '{"theme":"dark"}';
-- If you need indexed lookup on a specific JSON key, index the extraction expression.
CREATE INDEX ASYNC idx_users_pref_theme ON users ((preferences->>'theme'));
-- Query: SELECT * FROM users WHERE preferences->>'theme' = 'dark';Array GIN → Join Table
-- PostgreSQL: GIN index on array column
CREATE INDEX idx_posts_tags ON posts USING gin (tags);
-- Used for: tags @> ARRAY['database']
-- DSQL: Array column types not supported. Normalize tags into a join table for
-- indexed lookup, or store as jsonb if indexed lookup isn't needed.
CREATE TABLE post_tags (
post_id uuid NOT NULL,
tag text NOT NULL
);
CREATE INDEX ASYNC idx_post_tags_tag ON post_tags (tag);
CREATE INDEX ASYNC idx_post_tags_post ON post_tags (post_id);
-- Query: SELECT DISTINCT post_id FROM post_tags WHERE tag = 'database';Full-Text Search GIN → External Service
-- PostgreSQL: GIN index for full-text search
CREATE INDEX idx_articles_search ON articles USING gin (to_tsvector('english', title || ' ' || body));
-- DSQL: No equivalent. Use OpenSearch/Elasticsearch for full-text search.
-- Store the text in DSQL, index in OpenSearch, query OpenSearch for IDs, then fetch from DSQL.
-- Remove the index entirely from the DSQL schema.Trigram GIN (pg_trgm) → Application Layer
-- PostgreSQL: Trigram index for LIKE '%pattern%'
CREATE INDEX idx_users_name_trgm ON users USING gin (name gin_trgm_ops);
-- DSQL: No equivalent. Options:
-- 1. Use prefix matching (LIKE 'pattern%') with a btree index
CREATE INDEX ASYNC idx_users_name ON users (name);
-- 2. Use OpenSearch for fuzzy/substring matching
-- 3. Accept full scan for infrequent LIKE '%pattern%' queriesGiST Index Conversion
GiST indexes are used for geometric data, range types, and exclusion constraints.
Geometric GiST → No Index
-- PostgreSQL: GiST index on point column
CREATE INDEX idx_locations_coords ON locations USING gist (coords);
-- DSQL: Geometric types are not supported — ERROR: 0A000: datatype point not supported
-- Option 1: Store lat/lng as separate numeric columns, index those
ALTER TABLE locations ADD COLUMN lat double precision;
ALTER TABLE locations ADD COLUMN lng double precision;
CREATE INDEX ASYNC idx_locations_lat ON locations (lat);
CREATE INDEX ASYNC idx_locations_lng ON locations (lng);
-- Bounding box queries: WHERE lat BETWEEN x1 AND x2 AND lng BETWEEN y1 AND y2
-- Option 2: Use a geohash text column for proximity queries
ALTER TABLE locations ADD COLUMN geohash text;
CREATE INDEX ASYNC idx_locations_geohash ON locations (geohash);
-- Prefix matching: WHERE geohash LIKE 'dr5ru%'Range GiST → Separate Columns
-- PostgreSQL: GiST index on range type
CREATE INDEX idx_events_during ON events USING gist (during);
-- Used for: during && '[2024-01-01, 2024-02-01)'
-- DSQL: Store range as two columns
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
start_time timestamptz NOT NULL,
end_time timestamptz NOT NULL
);
CREATE INDEX ASYNC idx_events_start ON events (start_time);
CREATE INDEX ASYNC idx_events_end ON events (end_time);
-- Overlap query: WHERE start_time < '2024-02-01' AND end_time > '2024-01-01'BRIN Index Conversion
BRIN indexes are used for large, naturally-ordered tables (time-series data).
-- PostgreSQL: BRIN index on timestamp column
CREATE INDEX idx_logs_created ON logs USING brin (created_at);
-- DSQL: Use btree. DSQL's PK-ordered storage provides similar benefits
-- if created_at correlates with PK order.
CREATE INDEX ASYNC idx_logs_created ON logs (created_at);
-- If the table is very large and you need to limit index size,
-- use a composite index with the most selective column first.
-- No ASC/DESC on index keys — see "Index Key Sort Order" below.
CREATE INDEX ASYNC idx_logs_tenant_created ON logs (tenant_id, created_at);Partial Indexes
DSQL supports partial indexes. Keep the WHERE clause — only add ASYNC.
-- PostgreSQL: Partial index
CREATE INDEX idx_orders_pending ON orders (customer_id, created_at)
WHERE status = 'pending';
-- DSQL: WHERE clause preserved
CREATE INDEX ASYNC idx_orders_pending ON orders (customer_id, created_at)
WHERE status = 'pending';DSQL uses a partial index only when it can prove the query's WHERE conditions imply the index
predicate. Queries that do not mention status will not use the index above.
-- Eligible to use idx_orders_pending (status = 'pending' implies the predicate)
SELECT * FROM orders WHERE customer_id = $1 AND status = 'pending';
-- Does NOT use it — the predicate is not implied
SELECT * FROM orders WHERE customer_id = $1;Expression Indexes
DSQL supports expression indexes. Keep the expression — only add ASYNC.
-- PostgreSQL: Expression index
CREATE INDEX idx_users_email_lower ON users (lower(email));
-- DSQL: expression preserved
CREATE INDEX ASYNC idx_users_email_lower ON users ((lower(email)));
-- Query: WHERE lower(email) = lower($1)-- PostgreSQL: Expression index on JSON field
CREATE INDEX idx_users_city ON users ((preferences->>'city'));
-- DSQL: expression preserved
CREATE INDEX ASYNC idx_users_city ON users ((preferences->>'city'));
-- Query: WHERE preferences->>'city' = 'Seattle'All functions and operators in the expression must be IMMUTABLE. Mark user-defined functions IMMUTABLE, and rebuild the index if the function definition changes.
If the expression is not IMMUTABLE, make it immutable rather than replacing the index:
-- Rejected: extract on timestamptz depends on the session time zone
CREATE INDEX ASYNC idx_orders_year ON orders ((extract(year FROM created_at)));
-- ERROR: 42P17: functions in index expression must be marked IMMUTABLE
-- Pin the time zone
CREATE INDEX ASYNC idx_orders_year
ON orders ((extract(year FROM created_at AT TIME ZONE 'UTC')));
-- Query: WHERE extract(year FROM created_at AT TIME ZONE 'UTC') = 2024
-- must repeat the pinned expression, or the index is not usedA bare created_at::timestamp cast does NOT work — it depends on the session time zone too.
Do not reach for a computed column instead: ALTER TABLE ... ADD COLUMN ... GENERATED fails with
ALTER TABLE ADD COLUMN with constraint not supported, and the generation expression would have
to be IMMUTABLE anyway.
Index Limits
| Limit | Value | SQLSTATE | Message when exceeded |
|---|---|---|---|
| Max indexes per table | 24, incl. the PK | 54000 | more than 24 indexes per table are not allowed |
| Max columns per index | 8 | 54011 | more than 8 column keys in an index are not supported |
| Max PK/index key size | ~1,981 bytes observed; docs say 1 KiB | 54000 | key size too large |
For what takes one of the 24 slots, the key-size budget and the rest of the limit set, see limits-and-error-codes.md.
Strategy when approaching 24 index limit:
Use composite indexes instead of multiple single-column indexes
Use INCLUDE columns for covering indexes (avoids storage round-trips)
Remove indexes for rarely-used query patterns
Consider if the query can use an existing composite index with a prefix match
Index Key Sort Order
DSQL rejects ASC/DESC on index keys — both of them, with
ERROR: 0A000: specifying sort order not supported for index keys. dsql_lint reports this as rule
index_sort_direction and fix=true strips the direction, but the severity depends on which
direction you wrote:
| Input | fix_result.status | summary |
|---|---|---|
created_at DESC | fixed_with_warning | {errors: 0, warnings: 1, fixed: 0} |
created_at ASC | fixed | {errors: 0, warnings: 0, fixed: 1} |
So a pipeline that gates on errors == 0, or that consumes only fixed_sql, proceeds without
surfacing that the statement changed — and for ASC even a stricter pipeline gating on
warnings == 0 proceeds, because ASC raises no warning at all. Review every diagnostic rather
than either counter, and see dsql-lint.md for what zero diagnostics does and
does not mean.
-- PostgreSQL
CREATE INDEX idx_logs_tenant_created ON logs (tenant_id, created_at DESC);
-- DSQL: ERROR: 0A000: specifying sort order not supported for index keys
-- Drop the direction:
CREATE INDEX ASYNC idx_logs_tenant_created ON logs (tenant_id, created_at);This costs no capability for a uniformly-ordered ORDER BY. The planner reads an index
backwards, so a single direction-free index serves both directions. Against
(tenant_id, created_at), with a projection the index covers:
ORDER BY | Plan |
|---|---|
tenant_id, created_at | Index Only Scan |
tenant_id DESC, created_at DESC | Index Only Scan Backward |
tenant_id, created_at DESC | Incremental Sort above the scan, Presorted Key: tenant_id |
tenant_id DESC, created_at | Incremental Sort above a backward scan |
The projection matters as much as the direction. The plans above hold for
SELECT tenant_id, created_at. A secondary index is not covering, so SELECT * adds a Sort above
a primary-key scan in all four cases — the ordering is lost to the column lookup, not to the
direction. Use INCLUDE to cover the projection, or accept the sort.
The rule is direction uniformity over a leading prefix — uniformity alone is not enough. An
ORDER BY is served by a scan when its keys are a leading prefix of the index keys and all point
the same way. ORDER BY created_at is perfectly uniform and still gets a full Sort against
(tenant_id, created_at), because created_at is not a prefix. A mixed-direction ORDER BY over
a prefix keeps the Incremental Sort, and no DSQL index can remove it, because index keys carry no
direction to match against. If a mixed ordering is on a hot path, either sort in the application or
store an inverted column (for example a negated numeric, or a computed descending rank) and order on
it uniformly.
NULLS placement is retained
NULLS FIRST / NULLS LAST is accepted on an index key, and dsql_lint keeps it — only
ASC/DESC are removed. A backward scan flips the NULLS placement along with the direction, so
an index serves exactly two orderings: its own placement ascending, and the opposite placement
descending. A default index (NULLS LAST) therefore covers the two default forms:
| Index | Served by forward scan | Served by backward scan |
|---|---|---|
(a) | ORDER BY a | ORDER BY a DESC |
(a NULLS FIRST) | ORDER BY a NULLS FIRST | ORDER BY a DESC NULLS LAST |
Only add NULLS FIRST to serve a query that explicitly asks for it. An index declared
NULLS FIRST no longer satisfies the plain ORDER BY a or ORDER BY a DESC — both fall back to
a Sort — so adding one to fix a single query can cost the default ordering on every other.
Nullable columns need no special handling otherwise: the default index covers both directions.
Monitoring Async Index Status
Indexes created with ASYNC are not immediately usable. Monitor:
-- Check for indexes still being built
SELECT indexrelid::regclass AS index_name, indisvalid AS is_ready
FROM pg_index
WHERE NOT indisvalid;
-- If this returns rows, those indexes are still building.
-- Queries work but won't use the index until indisvalid = true.Do NOT rely on index performance until indisvalid = true.
Conversion Decision Flowchart
Is it a btree index?
├── Yes → CREATE INDEX ASYNC (preserve columns, INCLUDE, WHERE, IMMUTABLE expressions;
│ drop ASC/DESC — a backward scan serves uniformly-descending ORDER BY)
│
├── Is it GIN?
│ ├── For JSONB containment → unindexed at runtime; expression index for key equality
│ ├── For array ops → normalize to join table + btree
│ ├── For FTS → remove (use OpenSearch)
│ └── For trigram → remove or use prefix btree
│
├── Is it GiST?
│ ├── For geometry → separate lat/lng columns + btree
│ ├── For ranges → separate start/end columns + btree
│ └── For exclusion → remove (enforce in application)
│
├── Is it BRIN?
│ └── Convert to btree (DSQL PK-order gives similar benefit)
│
└── Is it CONCURRENTLY?
└── Remove CONCURRENTLY, use ASYNCRequirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs Python, Needs Node.js
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- AWS Labs
- Publisher
- AWS Labs (Official)
- Source
- awslabs/agent-plugins · plugins/databases-on-aws/skills/dsql · commit fbefb37
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- NOTICE (added by AutoGineer from NOTICE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by AWS Labs.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download dsql.zip