Redshift Data API
Protocol: JSON 1.1
Endpoint: POST http://localhost:4566/ with X-Amz-Target: RedshiftData.<Operation> and Content-Type: application/x-amz-json-1.1
Backing data plane: the PostgreSQL container behind a Redshift cluster created through the Redshift emulator
Floci emulates the Amazon Redshift Data API: the HTTP API that Lambda, Step Functions, and EventBridge use to run SQL on a cluster without opening a PostgreSQL wire connection. Floci resolves the target cluster, connects straight to its container over JDBC, queues the SQL on a shared worker pool, and stores the statement and its result set through the configured storage backend so the polling operations (DescribeStatement, GetStatementResult) can read them back.
For the upstream API shape, see the AWS documentation:
- Using the Amazon Redshift Data API
ExecuteStatementBatchExecuteStatementDescribeStatementGetStatementResult
Supported Actions
| Action | Description |
|---|---|
ExecuteStatement |
Submit one SQL statement asynchronously against the cluster container |
BatchExecuteStatement |
Run Sqls in order on one connection in a single transaction |
DescribeStatement |
Return a stored statement's status, timings, row counts, and sub-statements |
GetStatementResult |
Page through a finished statement's rows as typed Records |
GetStatementResultV2 |
Same rows in the V2 envelope, with CSVRecords when the statement used ResultFormat=CSV |
ListStatements |
List stored statements newest first, filtered by StatementName or Status |
CancelStatement |
Cancel queued or running SQL; returns whether cancellation was accepted, and rejects terminal statements |
ListDatabases |
List databases on the cluster (pg_database) |
ListSchemas |
List schemas, optionally filtered by SchemaPattern |
ListTables |
List tables and views, optionally filtered by SchemaPattern and TablePattern |
DescribeTable |
List a table's columns from information_schema.columns |
Authentication modes
A request identifies its target cluster or serverless workgroup one of three ways:
ClusterIdentifier+DbUser+Database. TheDbUseris the cluster master, or the prefixed name returned byGetClusterCredentials/GetClusterCredentialsWithIAM(for exampleIAM:analyst) while that credential is unexpired. Floci connects to the container as the cluster master in both cases. Any otherDbUserreturnsValidationException.-
SecretArn+ClusterIdentifier+Database. The secret must be a local Secrets Manager secret holding JSON credentials (usernameoruser, pluspassword). A cross-regionSecretArnis rejected. -
WorkgroupName+Database, with or withoutSecretArn.WorkgroupNameis a workgroup name or workgroup ARN created through Redshift Serverless.Databasemust be the namespacedbName. Without aSecretArn, Floci connects as the namespace admin; AWS derives the database user from the signing identity (IAM:<name>), andDbUseris not used. With aSecretArn, the secret must hold credentials the workgroup backend accepts. SendingClusterIdentifieras well returnsValidationException, and so does a workgroup whose runtime is not running.
A statement run against a workgroup reports WorkgroupName and omits ClusterIdentifier, and the reverse holds for a cluster statement. ListDatabases, ListSchemas, ListTables and DescribeTable accept WorkgroupName the same way.
Compatibility Notes
- Execution is asynchronous.
ExecuteStatementandBatchExecuteStatementreturn anIdafter admission to the shared bounded worker pool.DescribeStatementprogresses throughSUBMITTED,STARTED, andFINISHED,FAILEDorABORTED. Poll before reading results. OptionalWaitTimeSecondsaccepts integers from 1 to 30 and waits up to that duration. HTTP, Step Functions and Scheduler use the same executor. Saturation returnsActiveStatementsExceededException. - Execution errors are not HTTP errors. A statement that fails to run is stored with
Status=FAILEDand anErrormessage, andExecuteStatementstill returns 200 with the statementId. Callers see the failure throughDescribeStatement, matching AWS. - Storage and recovery. Statement metadata and results use the configured storage mode. A sweep evicts terminal statements older than the 24 hour TTL; active statements are retained. With persistence enabled, interrupted nonterminal statements become
FAILEDafter restart, without replaying SQL or emitting events. Memory mode loses results on restart. ExecuteStatementtakes one statement. If theSqlcontains more than one statement separated by;, it is rejected withValidationException. UseBatchExecuteStatementfor multiple statements.BatchExecuteStatementruns every entry ofSqlsin order on one connection in a single transaction: it commits at the end, and on the first failing sub-statement it rolls back and marks the batchFAILEDwith that sub-statement's error.DescribeStatementon the parent id returnsSubStatements, one per entry, each with its own id<parentId>:<n>.GetStatementResulton the parent id returns the rows of the last sub-statement that produced a result set; on a sub-statement id it returns that sub-statement's rows.GetStatementResultandGetStatementResultV2return the typedRecordsshape.GetStatementResultV2also returnsResultFormat, and when the statement was run withResultFormat=CSVit returnsRecordsasCSVRecordsstrings. A statement that produced no result set (anINSERT,UPDATE,DELETE, or DDL statement) returnsValidationExceptionwith the messageStatement has no result set.- Paging. The page size is 1000 rows.
NextTokenis an opaque base64 row offset; there is no server-side cursor. SqlParameters/Parametersare bound into aPreparedStatement. Named:placeholdermarkers are rewritten to positional JDBC bind parameters. Colons inside string literals, quoted identifiers, comments, PostgreSQL::casts, and dollar-quoted strings are left untouched. Redshift Data API parameter values are always strings on the wire; PostgreSQL coerces each bound value to the column type.CancelStatementreturns{ "Status": true }when cancellation is accepted for queued or running work. Running transactional SQL receives JDBC cancellation and rolls back before reachingABORTED; cancelling a batch rolls back its earlier writes. Cancelling a statement that is already terminal returnsValidationException("Could not cancel a query that is already in ABORTED state with ID:"), as AWS does. Repeated cancellation and statements that already own commit return falsein Floci. Commands requiring auto-commit, including Spectrum external DDL andVACUUM, own completion before performing irreversible work and reject cancellation during that work. An unknown statement id returnsResourceNotFoundException.WithEvent=truepublishes one terminal status event per parent statement to the default EventBridge bus in the captured account and region. Batch children do not emit events. False or omittedWithEventemits none. Delivery occurs after terminal metadata is stored, on a separate bounded pool; disabled EventBridge, rejected delivery or target failures do not change the SQL outcome. Delivery is best effort, with no durable outbox or restart replay.ExecuteSqlandBatchExecuteSql(the deprecated pre-2020 operations) returnValidationException.- Type mapping. JDBC
BOOLEANandBITmap tobooleanValue; integer types tolongValue; floating-point types todoubleValue;NUMERICandDECIMALtostringValue(as AWS does); binary types toblobValue; everything else, including dates, timestamps, and uuid, tostringValue. A SQLNULLmaps toisNull. A result column of typeline,json, orjsonbfails the statement with the Redshift error text.
Statement status events
Redshift Data API status events
use source aws.redshift-data and detail type Redshift Data Statement Status Change. The
detail contains statementId, optional statementName and resolved principal,
redshiftQueryId, state, rows and expireAt in epoch seconds. Query ids match
DescribeStatement; Floci derives them from statement ids rather than backend query ids.
resources contains the cluster ARN, or the workgroup ARN for Floci's Serverless support.
EventBridge rules can route these events to SQS, Lambda or other supported targets.
Spectrum relational queries
The Data API prepares Glue-backed external CSV tables on the same JDBC connection that executes
the SQL. JOINs with internal tables, multiple external tables, grouping, ordering and parameters
follow the Redshift Spectrum relational path.
Create the external schema through either the wire endpoint or the Data API; both use the same
runtime and database binding. Batch preparation runs inside the existing batch transaction, so
failure rolls back the batch. External read or preparation failures are stored as FAILED and
reported by DescribeStatement, as other SQL execution failures are.
Create external schemas and tables outside a batch: CREATE EXTERNAL TABLE cannot run inside a transaction block on AWS, and a batch runs as one transaction, so both statements are rejected there.
Data API session reuse is not supported. The legacy Phase 1 catalog is not the Glue-backed relational path.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_REDSHIFT_DATA_ENABLED |
true |
Enable or disable the Redshift Data API service |
FLOCI_SERVICES_REDSHIFT_DATA_RESULT_TTL_HOURS |
24 |
Hours terminal statement metadata and results are retained before eviction |
FLOCI_SERVICES_REDSHIFT_DATA_MAX_CONCURRENT_STATEMENTS |
10 |
Number of shared SQL workers |
FLOCI_SERVICES_REDSHIFT_DATA_QUEUE_CAPACITY |
100 |
Maximum queued statements, also the event delivery queue capacity |
FLOCI_SERVICES_REDSHIFT_DATA_SHUTDOWN_TIMEOUT_SECONDS |
10 |
Shared deadline for draining SQL and event delivery on shutdown |
The Redshift Data API also requires the Redshift service itself to be enabled, because it resolves ClusterIdentifier values to local Redshift clusters.
Example
export AWS_ENDPOINT_URL=http://localhost:4566
aws redshift create-cluster \
--cluster-identifier wh \
--node-type dc2.large \
--master-username admin \
--master-user-password Secret123 \
--endpoint-url "$AWS_ENDPOINT_URL"
STATEMENT_ID=$(aws redshift-data execute-statement \
--cluster-identifier wh \
--db-user admin \
--database dev \
--sql "create table t (id int, name varchar(20))" --wait-time-seconds 30 \
--query Id --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
aws redshift-data describe-statement --id "$STATEMENT_ID" --endpoint-url "$AWS_ENDPOINT_URL"
aws redshift-data execute-statement \
--cluster-identifier wh --db-user admin --database dev \
--sql "insert into t values (1, 'a'), (2, 'b')" --wait-time-seconds 30 \
--endpoint-url "$AWS_ENDPOINT_URL"
SELECT_ID=$(aws redshift-data execute-statement \
--cluster-identifier wh --db-user admin --database dev \
--sql "select id, name from t order by id" --wait-time-seconds 30 \
--query Id --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
aws redshift-data get-statement-result --id "$SELECT_ID" --endpoint-url "$AWS_ENDPOINT_URL"
import time
import boto3
data = boto3.client("redshift-data", endpoint_url="http://localhost:4566")
started = data.execute_statement(
ClusterIdentifier="wh", DbUser="admin", Database="dev",
Sql="select id, name from t where id = :id",
Parameters=[{"name": "id", "value": "2"}],
)
statement_id = started["Id"]
while data.describe_statement(Id=statement_id)["Status"] not in ("FINISHED", "FAILED", "ABORTED"):
time.sleep(0.2)
result = data.get_statement_result(Id=statement_id)
print(result["ColumnMetadata"], result["Records"])