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, runs the SQL, and stores the statement and its result set in memory 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 |
Run one SQL statement synchronously against the cluster container and store the result |
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 |
Mark a non-finished statement ABORTED; returns { "Status": true } |
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 one of two 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 (Amazon Redshift Serverless) is rejected with ValidationException. Redshift Serverless is not emulated.
Compatibility Notes
- Execution is synchronous.
ExecuteStatementruns the SQL and returns only once the statement is terminal.DescribeStatementreportsFINISHEDorFAILED, orABORTEDafter aCancelStatement. It never reportsSUBMITTEDorSTARTED, and there is no progression over wall-clock time. - 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. - Results are in memory. Statement metadata and result sets are held in a store swept on a 24 hour TTL and are lost when Floci restarts.
ListStatementsreturns only what is currently in memory. 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 }. In Floci a statement is already terminal by the time it can be cancelled, soCancelStatementsetsStatus=ABORTEDonly when the statement was not alreadyFINISHED. An unknown statement id returnsResourceNotFoundException.WithEventis accepted and ignored: no EventBridge event is published.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.
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 a finished statement and its result set are kept in memory before the sweep evicts them |
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))" \
--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')" \
--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" \
--query Id --output text \
--endpoint-url "$AWS_ENDPOINT_URL")
aws redshift-data get-statement-result --id "$SELECT_ID" --endpoint-url "$AWS_ENDPOINT_URL"
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"):
pass
result = data.get_statement_result(Id=statement_id)
print(result["ColumnMetadata"], result["Records"])