1import type * as data from "@distilled.cloud/aws/redshift-data";
2import * as Data from "effect/Data";
3import type * as Effect from "effect/Effect";
4import * as Binding from "../../Binding.ts";
5import type { Workgroup } from "../RedshiftServerless/Workgroup.ts";
6
7/**
8 * Options that pin every statement run through this binding to a database and,
9 * optionally, an authentication method.
10 */
11export interface StatementsOptions {
12 /**
13 * Database to run statements against.
14 * @default "dev"
15 */
16 database?: string;
17 /**
18 * ARN of a Secrets Manager secret holding the database credentials. Omit to
19 * authenticate with the Lambda's IAM identity via
20 * `redshift-serverless:GetCredentials`.
21 */
22 secretArn?: string;
23 /**
24 * Database user to connect as. Only used with secret/temporary-credential
25 * auth flows that require an explicit user.
26 */
27 dbUser?: string;
28}
29
30/**
31 * Fields the binding injects on every request: the workgroup, the pinned
32 * database, and the auth fields resolved from {@link StatementsOptions}
33 * (cluster-only fields are excluded because this binding targets Serverless).
34 */
35type InjectedFields =
36 | "WorkgroupName"
37 | "Database"
38 | "ClusterIdentifier"
39 | "SecretArn"
40 | "DbUser";
41
42/**
43 * `executeStatement` input minus the identifiers this binding injects
44 * (workgroup, database, and the cluster-only fields).
45 */
46export interface ExecuteStatementRequest extends Omit<
47 data.ExecuteStatementInput,
48 InjectedFields
49> {}
50
51/**
52 * `batchExecuteStatement` input minus the identifiers this binding injects
53 * (workgroup, database, and the cluster-only fields).
54 */
55export interface BatchExecuteStatementRequest extends Omit<
56 data.BatchExecuteStatementInput,
57 InjectedFields
58> {}
59
60/**
61 * `describeTable` input minus the injected identifiers. Use
62 * `ConnectedDatabase` to introspect a database other than the pinned one.
63 */
64export interface DescribeTableRequest extends Omit<
65 data.DescribeTableRequest,
66 InjectedFields
67> {}
68
69/**
70 * `listDatabases` input minus the injected identifiers.
71 */
72export interface ListDatabasesRequest extends Omit<
73 data.ListDatabasesRequest,
74 InjectedFields
75> {}
76
77/**
78 * `listSchemas` input minus the injected identifiers.
79 */
80export interface ListSchemasRequest extends Omit<
81 data.ListSchemasRequest,
82 InjectedFields
83> {}
84
85/**
86 * `listTables` input minus the injected identifiers.
87 */
88export interface ListTablesRequest extends Omit<
89 data.ListTablesRequest,
90 InjectedFields
91> {}
92
93/**
94 * `listStatements` input minus the workgroup identifier this binding injects.
95 * `Database`, `Status`, and `StatementName` remain available as filters.
96 */
97export interface ListStatementsRequest extends Omit<
98 data.ListStatementsRequest,
99 "ClusterIdentifier" | "WorkgroupName"
100> {}
101
102/**
103 * Raised when a statement reaches a terminal `FAILED` or `ABORTED` status
104 * while a composite `query` waits for it to finish.
105 */
106export class RedshiftStatementFailed extends Data.TaggedError(
107 "RedshiftStatementFailed",
108)<{
109 /**
110 * ID of the failed statement.
111 */
112 readonly statementId: string;
113 /**
114 * Terminal status the statement ended in (`"FAILED"` or `"ABORTED"`).
115 */
116 readonly status: string;
117 /**
118 * Error message reported by Redshift, if any.
119 */
120 readonly error: string | undefined;
121}> {}
122
123/**
124 * Runtime client for the Redshift Data API scoped to a single workgroup +
125 * database.
126 */
127export interface StatementsClient {
128 /**
129 * Submit a SQL statement. Returns immediately with the statement `Id`;
130 * results are fetched asynchronously via {@link describe}/{@link getResult}.
131 */
132 readonly execute: (
133 request: ExecuteStatementRequest,
134 ) => Effect.Effect<data.ExecuteStatementOutput, data.ExecuteStatementError>;
135 /**
136 * Submit a batch of SQL statements that run in order as one transaction.
137 * Returns immediately with the batch statement `Id`; sub-statement ids
138 * (`{Id}:1`, `{Id}:2`, ...) are reported by {@link describe}.
139 */
140 readonly executeBatch: (
141 request: BatchExecuteStatementRequest,
142 ) => Effect.Effect<
143 data.BatchExecuteStatementOutput,
144 data.BatchExecuteStatementError
145 >;
146 /**
147 * Describe a submitted statement (status, timing, row counts).
148 */
149 readonly describe: (
150 id: string,
151 ) => Effect.Effect<
152 data.DescribeStatementResponse,
153 data.DescribeStatementError
154 >;
155 /**
156 * Cancel a running statement. Returns `Status: true` when the cancellation
157 * was accepted.
158 */
159 readonly cancel: (
160 id: string,
161 ) => Effect.Effect<data.CancelStatementResponse, data.CancelStatementError>;
162 /**
163 * Fetch the cached result rows of a finished statement (JSON result
164 * format). Pass `nextToken` to page through large result sets.
165 */
166 readonly getResult: (
167 id: string,
168 nextToken?: string,
169 ) => Effect.Effect<
170 data.GetStatementResultResponse,
171 data.GetStatementResultError
172 >;
173 /**
174 * Fetch the cached result rows of a finished statement that ran with
175 * `ResultFormat: "CSV"`. Pass `nextToken` to page through large result
176 * sets.
177 */
178 readonly getResultV2: (
179 id: string,
180 nextToken?: string,
181 ) => Effect.Effect<
182 data.GetStatementResultV2Response,
183 data.GetStatementResultV2Error
184 >;
185 /**
186 * Describe a table's columns from the database metadata.
187 */
188 readonly describeTable: (
189 request: DescribeTableRequest,
190 ) => Effect.Effect<data.DescribeTableResponse, data.DescribeTableError>;
191 /**
192 * List the databases in the workgroup.
193 */
194 readonly listDatabases: (
195 request?: ListDatabasesRequest,
196 ) => Effect.Effect<data.ListDatabasesResponse, data.ListDatabasesError>;
197 /**
198 * List the schemas in the pinned database.
199 */
200 readonly listSchemas: (
201 request?: ListSchemasRequest,
202 ) => Effect.Effect<data.ListSchemasResponse, data.ListSchemasError>;
203 /**
204 * List the tables in the pinned database, optionally filtered by
205 * `SchemaPattern`/`TablePattern`.
206 */
207 readonly listTables: (
208 request?: ListTablesRequest,
209 ) => Effect.Effect<data.ListTablesResponse, data.ListTablesError>;
210 /**
211 * List statements previously run through the Data API on this workgroup
212 * by the caller's identity.
213 */
214 readonly listStatements: (
215 request?: ListStatementsRequest,
216 ) => Effect.Effect<data.ListStatementsResponse, data.ListStatementsError>;
217 /**
218 * Run a SQL statement and wait (bounded) for it to finish, returning its
219 * result rows. Fails with {@link RedshiftStatementFailed} if the statement
220 * ends in `FAILED`/`ABORTED`.
221 */
222 readonly query: (
223 sql: string,
224 parameters?: data.SqlParameter[],
225 ) => Effect.Effect<
226 data.GetStatementResultResponse,
227 | data.ExecuteStatementError
228 | data.DescribeStatementError
229 | data.GetStatementResultError
230 | RedshiftStatementFailed
231 >;
232}
233
234/**
235 * Runtime binding for the Amazon Redshift Data API against a Serverless
236 * {@link Workgroup}. Exposes the full Data API surface —
237 * `execute`/`executeBatch`/`describe`/`cancel`/`getResult`/`getResultV2`,
238 * the metadata reads (`describeTable`, `listDatabases`, `listSchemas`,
239 * `listTables`, `listStatements`) — plus a composite `query` that submits a
240 * statement and polls until it finishes.
241 *
242 * At deploy time it grants the workgroup-scoped Data API actions on the
243 * workgroup (plus `redshift-serverless:GetCredentials` for IAM auth) and the
244 * statement-scoped actions (`DescribeStatement`, `GetStatementResult`,
245 * `GetStatementResultV2`, `CancelStatement`, `ListStatements`) which AWS
246 * authorizes per statement owner rather than by ARN. The Data API is
247 * HTTP-based — no driver, no VPC reach, and no credential plumbing required.
248 * Provide the implementation with
249 * `Effect.provide(AWS.RedshiftData.StatementsHttp)`.
250 *
251 * ### Running SQL
252 * **Example:** Query a Workgroup
253 * ```typescript
254 * const sql = yield* RedshiftData.Statements(workgroup, { database: "dev" });
255 * const result = yield* sql.query("SELECT 1 AS n");
256 * // result.Records -> [[{ longValue: 1 }]]
257 * ```
258 *
259 * **Example:** Run a Batch of Statements in One Transaction
260 * ```typescript
261 * const submitted = yield* sql.executeBatch({
262 * Sqls: ["CREATE TABLE IF NOT EXISTS events(id int)", "INSERT INTO events VALUES (1)"],
263 * });
264 * // sub-statement ids are `${submitted.Id}:1`, `${submitted.Id}:2`
265 * const described = yield* sql.describe(submitted.Id!);
266 * ```
267 *
268 * **Example:** Cancel a Running Statement
269 * ```typescript
270 * const submitted = yield* sql.execute({ Sql: "SELECT count(*) FROM big_table" });
271 * const { Status } = yield* sql.cancel(submitted.Id!);
272 * ```
273 *
274 * ### Browsing Metadata
275 * **Example:** List Databases, Schemas and Tables
276 * ```typescript
277 * const { Databases } = yield* sql.listDatabases();
278 * const { Schemas } = yield* sql.listSchemas({ SchemaPattern: "public" });
279 * const { Tables } = yield* sql.listTables({ SchemaPattern: "public" });
280 * const { ColumnList } = yield* sql.describeTable({ Schema: "public", Table: "events" });
281 * ```
282 *
283 * **Example:** Serve Query Results from a Lambda Function
284 * ```typescript
285 * export default QueryFunction.make(
286 * { main: import.meta.url, functionUrl: true },
287 * Effect.gen(function* () {
288 * const namespace = yield* RedshiftServerless.Namespace("Analytics", {
289 * dbName: "analytics",
290 * adminUsername: "admin",
291 * manageAdminPassword: true,
292 * });
293 * const workgroup = yield* RedshiftServerless.Workgroup("Analytics", {
294 * namespaceName: namespace.namespaceName,
295 * baseCapacity: 8,
296 * });
297 * // init — bind the Data API to the workgroup
298 * const sql = yield* RedshiftData.Statements(workgroup, {
299 * database: "analytics",
300 * });
301 *
302 * return {
303 * fetch: Effect.gen(function* () {
304 * // runtime — submit a statement and wait for its rows
305 * const result = yield* sql.query("SELECT count(*) AS n FROM events");
306 * return yield* HttpServerResponse.json({ records: result.Records });
307 * }).pipe(Effect.orDie),
308 * };
309 * }).pipe(Effect.provide(RedshiftData.StatementsHttp)),
310 * );
311 * ```
312 *
313 * @binding
314 */
315export interface Statements extends Binding.Service<
316 Statements,
317 "AWS.RedshiftData.Statements",
318 (
319 workgroup: Workgroup,
320 options?: StatementsOptions,
321 ) => Effect.Effect<StatementsClient>
322> {}
323
324export const Statements = Binding.Service<Statements>(
325 "AWS.RedshiftData.Statements",
326);