RDS Data API
Protocol: REST JSON
Endpoint: POST http://localhost:4566/{operation}
Backing data plane: Local RDS MySQL / MariaDB / PostgreSQL containers
Floci implements the AWS RDS Data API routes used by AWS SDK clients and executes raw SQL against local RDS resources created through the RDS emulator. It supports MySQL, MariaDB, and PostgreSQL resources for local development workflows that already use ExecuteStatement and transactions.
For the upstream API shape, see the AWS RDS Data API documentation:
- Using the Data API for Aurora DB clusters
- Data API operations
ExecuteStatementBeginTransactionCommitTransactionRollbackTransactionBatchExecuteStatement
Supported Actions
| Action | Route | Required request fields | Description |
|---|---|---|---|
ExecuteStatement |
POST /Execute |
resourceArn, secretArn, sql |
Execute raw SQL against a local RDS cluster or instance |
BeginTransaction |
POST /BeginTransaction |
resourceArn, secretArn |
Open a JDBC transaction and return a transaction ID |
CommitTransaction |
POST /CommitTransaction |
resourceArn, secretArn, transactionId |
Commit an open transaction |
RollbackTransaction |
POST /RollbackTransaction |
resourceArn, secretArn, transactionId |
Roll back an open transaction |
BatchExecuteStatement |
POST /BatchExecute |
resourceArn, secretArn, sql |
Run one SQL statement once per entry in parameterSets |
The deprecated ExecuteSql operation is recognized at POST /ExecuteSql and returns an AWS-style BadRequestException.
Compatibility Notes
resourceArnandsecretArnare required on Data API requests.resourceArnmust identify an existing local RDS cluster or instance.- Resolving a cluster ARN retries missing active member relays even if the cluster backend is already running. A failed member relay does not block SQL through the healthy cluster backend and is retried on a subsequent request. Healthy relays are not rebound and stopped members remain stopped. See recoverable RDS backend startup failures.
databaseis optional when the resolved RDS resource has a database name; otherwise it must be provided. TransactionalExecuteStatementrequests must use the same database as the active transaction whendatabaseis present.- Transaction requests validate
resourceArnagainst the active transaction resource. Floci resolves accepted ARN aliases to the local resource before comparing transaction identity. - Requests sharing a
transactionIddo not queue behind an active request:ExecuteStatement,BatchExecuteStatement,CommitTransaction, andRollbackTransactionimmediately return HTTP 400DatabaseErrorExceptionwithTransaction is still running a query. Retry after the active request finishes. Other transactions remain independent, and expiry cleanup skips active requests. The idle timeout starts when the statement or batch finishes. - MySQL, MariaDB, and PostgreSQL resources are supported. Aurora PostgreSQL resources resolve to the same PostgreSQL execution path.
- SQL is sent directly to the local database engine through JDBC.
SqlParameterbinding is supported for all engines: named:placeholdermarkers are rewritten to positional JDBC bind parameters and executed through aPreparedStatement. Colons inside string literals, quoted/backtick identifiers, comments, PostgreSQL::casts, and PostgreSQL dollar-quoted strings are left untouched. A placeholder used more than once binds its value at each position. Supported value variants arestringValue,booleanValue,longValue,doubleValue,blobValue, andisNull;typeHintvaluesDECIMAL,TIMESTAMP,DATE,TIME,UUID, andJSONare honored.arrayValueparameters are not supported yet and returnBadRequestException, as do malformedparameterspayloads (not a JSON array, or an entry missingname). - Result records include Data API field variants such as
stringValue,longValue,blobValue,booleanValue,doubleValue,arrayValue, andisNull. One-dimensional SQL arrays uselongValues,stringValues,booleanValues, ordoubleValuesaccording to their JDBC element type, including typed empty arrays. Decimal arrays use strings to preserve precision. Multidimensional arrays returnUnsupportedResultException, as documented forExecuteStatement. Arrays with null elements or binary elements also returnUnsupportedResultException: the supportedArrayValuemembers cannot represent those elements without losing information. PostgreSQL bit-string arrays are rejected with the same error before JDBC decodes them as booleans; PostgreSQL boolean arrays remain supported. A null array column returnsisNull.recordsis returned only for statements that produce a result set, empty when the query matched no rows, and is omitted entirely from the response to a statement that reports an update count, as AWS does. AnINSERT,UPDATE,DELETE, or DDL statement therefore answers withnumberOfRecordsUpdatedandgeneratedFieldsand norecordsfield at all. includeResultMetadatareturns the full AWSColumnMetadatashape per column:label,name,type,typeName,tableName,schemaName,nullable,precision,scale,isSigned,isCaseSensitive,isCurrency,isAutoIncrement, andarrayBaseColumnType.labelis the result-set label (the alias when the query aliases a column) andnameis the underlying column name, falling back to the label for computed columns. Clients that hydrate rows bylabelneed it present.typeis a JDBC type code andtypeNameis the engine's own type name (for example4andint4for a PostgreSQL integer), both taken from the driver so they match the engine actually serving the query.arrayBaseColumnTypeis the JDBC element type of a non-null array value, including an empty array. It remains0for non-array columns, queries matching no rows, or array columns containing only null values, because JDBC result metadata alone does not expose the element type.ExecuteStatementreturnsgeneratedFieldsfor statements that report an update count on MySQL and MariaDB, carrying the auto-increment keys the engine generated. As on Aurora PostgreSQL, PostgreSQL resources always return an empty list — use aRETURNINGclause to read generated values.generatedFieldsis omitted for statements that return a result set.BatchExecuteStatementruns the statement once per entry inparameterSetsthrough a JDBC batch and returns oneupdateResultsentry per set, each carrying thegeneratedFieldsthat set produced under the same engine rules asExecuteStatement— a set inserting several rows reports one field per generated key. On MySQL and MariaDB the sets are executed one at a time so each entry owns its own keys, becausegetGeneratedKeys()after a JDBC batch reports every key the batch generated with nothing tying a key back to the set that produced it. It acceptstransactionId, so a batch can take part in a Data API transaction; without one the batch commits automatically. An absent or emptyparameterSetsruns nothing and returns an emptyupdateResults: AWS runs the statement once per parameter set provided and points a caller who wants a single parameterless execution at one empty set ([[]], which runs once here) or atExecuteStatement. A statement that returns rows rather than an update count — aSELECT, or a CTE ending in one — is rejected withBadRequestExceptionbefore any set runs, because a batch reports oneUpdateResultper set and has nowhere to carry a result set, and AWS documents the operation as taking a DML statement. The check reads the driver's description of the prepared statement, so nothing executes first, and a DML statement that also reports rows through a PostgreSQLRETURNINGclause is left alone: the batch still performs its writes, andgeneratedFieldsis empty on PostgreSQL anyway.- A result set in which any single row is larger than 64 KB is rejected with
UnsupportedResultExceptionand the messagePacket for query is too large, as the Data API troubleshooting guide describes. A row is measured by its values as the driver returns them (the raw length of a binary value, the UTF-8 length of anything else), so JSON escaping and base64 encoding in the response do not count toward it. - A PostgreSQL result set with a
point,box,circle,line,lseg,path,polygon,interval, ormoneycolumn is rejected withUnsupportedResultException, as on Aurora PostgreSQL. Cast the column to text to read it. - Each executed SQL string must contain one statement. Multi-statement input is rejected with
ValidationExceptionandMultistatements aren't supported.before any statement runs, including in a transaction or a nonempty batch. Semicolons inside quoted literals, identifiers, comments, or dollar-quoted strings do not separate statements. - SQL errors are returned as
DatabaseErrorExceptionso AWS SDK callers can handle database failures with normal AWS error decoding. - If
secretArnpoints to a local Secrets Manager secret with JSON credentials (usernameoruser, pluspassword), those credentials are used. A missing or unavailable secret returnsSecretsErrorException; malformed or incomplete credentials returnInvalidSecretException. Floci does not fall back to the resolved RDS resource's master credentials when a secret ARN was supplied. formatRecordsAs=JSON,formattedRecords, andresultSetOptionsare not implemented yet, andExecuteStatementrequests asking for those result modes returnBadRequestException. They areExecuteStatement-only fields in the AWS API, soBatchExecuteStatement— whose input is limited toresourceArn,secretArn,sql,database,schema,parameterSets, andtransactionId— ignores them as AWS does rather than rejecting them.- RDS records a cluster's
HttpEndpointEnabled(EnableHttpEndpoint/DisableHttpEndpoint, see RDS), but the Data API does not check it: availability is controlled byFLOCI_SERVICES_RDS_DATA_ENABLEDand whether the target local RDS resource is running.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_RDS_DATA_ENABLED |
true |
Enable or disable the RDS Data API service |
FLOCI_SERVICES_RDS_DATA_TRANSACTION_TTL_SECONDS |
180 |
Idle timeout, in seconds, before leaked Data API transactions expire |
FLOCI_SERVICES_RDS_DATA_DATABASE_RESUMING_EXCEPTION_ENABLED |
false |
Return DatabaseResumingException (HTTP 400) while an auto-paused Aurora backend resumes, instead of waiting for it |
The RDS Data API also requires the RDS service itself to be enabled because it resolves resourceArn values to local RDS containers.
With database-resuming-exception-enabled=true, ExecuteStatement, BatchExecuteStatement,
and BeginTransaction initiate resume in the background and return the retryable error before
opening a database connection. Calls made while resume is in progress return the same error;
retry after a few seconds. Once ready, requests run normally. Existing transactions keep their
backend awake until commit, rollback, or expiry. The default remains blocking resume for local
compatibility; TCP database connections and internal RDS commands always wait for resume.
Example
export AWS_ENDPOINT_URL=http://localhost:4566
aws rds create-db-cluster \
--db-cluster-identifier appdb \
--engine aurora-mysql \
--master-username admin \
--master-user-password secret123 \
--database-name app \
--endpoint-url "$AWS_ENDPOINT_URL"
RESOURCE_ARN=$(aws rds describe-db-clusters \
--db-cluster-identifier appdb \
--query 'DBClusters[0].DBClusterArn' \
--output text \
--endpoint-url "$AWS_ENDPOINT_URL")
SECRET_ARN=$(aws secretsmanager create-secret \
--name appdb/data-api \
--secret-string '{"username":"admin","password":"secret123"}' \
--query ARN \
--output text \
--endpoint-url "$AWS_ENDPOINT_URL")
aws rds-data execute-statement \
--resource-arn "$RESOURCE_ARN" \
--secret-arn "$SECRET_ARN" \
--database app \
--sql "select 1 as count" \
--include-result-metadata \
--endpoint-url "$AWS_ENDPOINT_URL"
aws rds-data batch-execute-statement \
--resource-arn "$RESOURCE_ARN" \
--secret-arn "$SECRET_ARN" \
--database app \
--sql "insert into items (id, title) values (:id, :title)" \
--parameter-sets '[
[{"name":"id","value":{"longValue":1}},{"name":"title","value":{"stringValue":"first"}}],
[{"name":"id","value":{"longValue":2}},{"name":"title","value":{"stringValue":"second"}}]
]' \
--endpoint-url "$AWS_ENDPOINT_URL"