Skip to content

Commit 185e93e

Browse files
authored
feat(athena): complete SQL-workload coverage with prepared statements, catalogs, and workgroups (#7614)
* feat(athena): complete SQL-workload coverage with prepared statements, catalogs, and workgroups Adds 15 tools so the Athena block covers the full SQL workload surface: prepared statements (create/get/batch get/update/list/delete), get query runtime statistics, batch get and update named queries, list/get data catalogs, get database, get table metadata, and list/get workgroups. Start query now accepts execution parameters and result reuse configuration. Contracts share one connection schema with region validation, and list databases now returns database parameters. Claude-Session: https://claude.ai/code/session_01AtPwvDwhkhed2MwcZudrPN * fix(athena): keep zero-minute result reuse age, enforce name patterns, declare table parameters output Claude-Session: https://claude.ai/code/session_01AtPwvDwhkhed2MwcZudrPN
1 parent 74e77c6 commit 185e93e

60 files changed

Lines changed: 5051 additions & 149 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/integrations/athena.mdx

Lines changed: 417 additions & 4 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/athena.ts

Lines changed: 698 additions & 18 deletions
Large diffs are not rendered by default.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { z } from 'zod'
2+
import {
3+
athenaConnectionSchema,
4+
athenaNamedQuerySchema,
5+
} from '@/lib/api/contracts/tools/aws/athena-shared'
6+
import type {
7+
ContractBody,
8+
ContractBodyInput,
9+
ContractJsonResponse,
10+
} from '@/lib/api/contracts/types'
11+
import { defineRouteContract } from '@/lib/api/contracts/types'
12+
13+
const BatchGetNamedQuerySchema = athenaConnectionSchema.extend({
14+
namedQueryIds: z
15+
.array(z.string().trim().min(1))
16+
.min(1, 'At least one named query ID is required')
17+
.max(50, 'A maximum of 50 named query IDs can be requested at once'),
18+
})
19+
20+
const BatchGetNamedQueryResponseSchema = z.object({
21+
success: z.literal(true),
22+
output: z.object({
23+
namedQueries: z.array(athenaNamedQuerySchema),
24+
unprocessedNamedQueryIds: z.array(
25+
z.object({
26+
namedQueryId: z.string().nullable(),
27+
errorCode: z.string().nullable(),
28+
errorMessage: z.string().nullable(),
29+
})
30+
),
31+
}),
32+
})
33+
34+
export const awsAthenaBatchGetNamedQueryContract = defineRouteContract({
35+
method: 'POST',
36+
path: '/api/tools/athena/batch-get-named-query',
37+
body: BatchGetNamedQuerySchema,
38+
response: { mode: 'json', schema: BatchGetNamedQueryResponseSchema },
39+
})
40+
export type AwsAthenaBatchGetNamedQueryRequest = ContractBodyInput<
41+
typeof awsAthenaBatchGetNamedQueryContract
42+
>
43+
export type AwsAthenaBatchGetNamedQueryBody = ContractBody<
44+
typeof awsAthenaBatchGetNamedQueryContract
45+
>
46+
export type AwsAthenaBatchGetNamedQueryResponse = ContractJsonResponse<
47+
typeof awsAthenaBatchGetNamedQueryContract
48+
>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { z } from 'zod'
2+
import {
3+
athenaConnectionSchema,
4+
athenaPreparedStatementSchema,
5+
athenaStatementNameSchema,
6+
athenaWorkGroupSchema,
7+
} from '@/lib/api/contracts/tools/aws/athena-shared'
8+
import type {
9+
ContractBody,
10+
ContractBodyInput,
11+
ContractJsonResponse,
12+
} from '@/lib/api/contracts/types'
13+
import { defineRouteContract } from '@/lib/api/contracts/types'
14+
15+
const BatchGetPreparedStatementSchema = athenaConnectionSchema.extend({
16+
preparedStatementNames: z
17+
.array(athenaStatementNameSchema)
18+
.min(1, 'At least one prepared statement name is required')
19+
.max(256, 'A maximum of 256 prepared statement names can be requested at once'),
20+
workGroup: athenaWorkGroupSchema,
21+
})
22+
23+
const BatchGetPreparedStatementResponseSchema = z.object({
24+
success: z.literal(true),
25+
output: z.object({
26+
preparedStatements: z.array(athenaPreparedStatementSchema),
27+
unprocessedPreparedStatementNames: z.array(
28+
z.object({
29+
statementName: z.string().nullable(),
30+
errorCode: z.string().nullable(),
31+
errorMessage: z.string().nullable(),
32+
})
33+
),
34+
}),
35+
})
36+
37+
export const awsAthenaBatchGetPreparedStatementContract = defineRouteContract({
38+
method: 'POST',
39+
path: '/api/tools/athena/batch-get-prepared-statement',
40+
body: BatchGetPreparedStatementSchema,
41+
response: { mode: 'json', schema: BatchGetPreparedStatementResponseSchema },
42+
})
43+
export type AwsAthenaBatchGetPreparedStatementRequest = ContractBodyInput<
44+
typeof awsAthenaBatchGetPreparedStatementContract
45+
>
46+
export type AwsAthenaBatchGetPreparedStatementBody = ContractBody<
47+
typeof awsAthenaBatchGetPreparedStatementContract
48+
>
49+
export type AwsAthenaBatchGetPreparedStatementResponse = ContractJsonResponse<
50+
typeof awsAthenaBatchGetPreparedStatementContract
51+
>

apps/sim/lib/api/contracts/tools/aws/athena-batch-get-query-execution.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
import { z } from 'zod'
2+
import { athenaConnectionSchema } from '@/lib/api/contracts/tools/aws/athena-shared'
23
import type {
34
ContractBody,
45
ContractBodyInput,
56
ContractJsonResponse,
67
} from '@/lib/api/contracts/types'
78
import { defineRouteContract } from '@/lib/api/contracts/types'
8-
import { validateAwsRegion } from '@/lib/core/security/input-validation'
99

10-
const BatchGetQueryExecutionSchema = z.object({
11-
region: z
12-
.string()
13-
.min(1, 'AWS region is required')
14-
.refine((v) => validateAwsRegion(v).isValid, {
15-
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
16-
}),
17-
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
18-
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
10+
const BatchGetQueryExecutionSchema = athenaConnectionSchema.extend({
1911
queryExecutionIds: z
2012
.array(z.string().trim().min(1))
2113
.min(1, 'At least one query execution ID is required')

apps/sim/lib/api/contracts/tools/aws/athena-create-named-query.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { z } from 'zod'
2+
import { athenaConnectionSchema } from '@/lib/api/contracts/tools/aws/athena-shared'
23
import type {
34
ContractBody,
45
ContractBodyInput,
56
ContractJsonResponse,
67
} from '@/lib/api/contracts/types'
78
import { defineRouteContract } from '@/lib/api/contracts/types'
89

9-
const CreateNamedQuerySchema = z.object({
10-
region: z.string().min(1, 'AWS region is required'),
11-
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
12-
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
10+
const CreateNamedQuerySchema = athenaConnectionSchema.extend({
1311
name: z.string().min(1, 'Query name is required'),
1412
database: z.string().min(1, 'Database is required'),
1513
queryString: z.string().min(1, 'Query string is required'),
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { z } from 'zod'
2+
import {
3+
athenaConnectionSchema,
4+
athenaStatementNameSchema,
5+
athenaSuccessResponseSchema,
6+
athenaWorkGroupSchema,
7+
} from '@/lib/api/contracts/tools/aws/athena-shared'
8+
import type {
9+
ContractBody,
10+
ContractBodyInput,
11+
ContractJsonResponse,
12+
} from '@/lib/api/contracts/types'
13+
import { defineRouteContract } from '@/lib/api/contracts/types'
14+
15+
const CreatePreparedStatementSchema = athenaConnectionSchema.extend({
16+
statementName: athenaStatementNameSchema,
17+
workGroup: athenaWorkGroupSchema,
18+
queryStatement: z.string().min(1, 'Query statement is required'),
19+
description: z
20+
.string()
21+
.min(1, 'Description cannot be empty')
22+
.max(1024, 'Description must be at most 1024 characters')
23+
.optional(),
24+
})
25+
26+
const CreatePreparedStatementResponseSchema = athenaSuccessResponseSchema
27+
28+
export const awsAthenaCreatePreparedStatementContract = defineRouteContract({
29+
method: 'POST',
30+
path: '/api/tools/athena/create-prepared-statement',
31+
body: CreatePreparedStatementSchema,
32+
response: { mode: 'json', schema: CreatePreparedStatementResponseSchema },
33+
})
34+
export type AwsAthenaCreatePreparedStatementRequest = ContractBodyInput<
35+
typeof awsAthenaCreatePreparedStatementContract
36+
>
37+
export type AwsAthenaCreatePreparedStatementBody = ContractBody<
38+
typeof awsAthenaCreatePreparedStatementContract
39+
>
40+
export type AwsAthenaCreatePreparedStatementResponse = ContractJsonResponse<
41+
typeof awsAthenaCreatePreparedStatementContract
42+
>

apps/sim/lib/api/contracts/tools/aws/athena-delete-named-query.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
import { z } from 'zod'
2+
import { athenaConnectionSchema } from '@/lib/api/contracts/tools/aws/athena-shared'
23
import type {
34
ContractBody,
45
ContractBodyInput,
56
ContractJsonResponse,
67
} from '@/lib/api/contracts/types'
78
import { defineRouteContract } from '@/lib/api/contracts/types'
8-
import { validateAwsRegion } from '@/lib/core/security/input-validation'
99

10-
const DeleteNamedQuerySchema = z.object({
11-
region: z
12-
.string()
13-
.min(1, 'AWS region is required')
14-
.refine((v) => validateAwsRegion(v).isValid, {
15-
message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)',
16-
}),
17-
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
18-
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
10+
const DeleteNamedQuerySchema = athenaConnectionSchema.extend({
1911
namedQueryId: z.string().trim().min(1, 'Named query ID is required'),
2012
})
2113

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
athenaConnectionSchema,
3+
athenaStatementNameSchema,
4+
athenaSuccessResponseSchema,
5+
athenaWorkGroupSchema,
6+
} from '@/lib/api/contracts/tools/aws/athena-shared'
7+
import type {
8+
ContractBody,
9+
ContractBodyInput,
10+
ContractJsonResponse,
11+
} from '@/lib/api/contracts/types'
12+
import { defineRouteContract } from '@/lib/api/contracts/types'
13+
14+
const DeletePreparedStatementSchema = athenaConnectionSchema.extend({
15+
statementName: athenaStatementNameSchema,
16+
workGroup: athenaWorkGroupSchema,
17+
})
18+
19+
const DeletePreparedStatementResponseSchema = athenaSuccessResponseSchema
20+
21+
export const awsAthenaDeletePreparedStatementContract = defineRouteContract({
22+
method: 'POST',
23+
path: '/api/tools/athena/delete-prepared-statement',
24+
body: DeletePreparedStatementSchema,
25+
response: { mode: 'json', schema: DeletePreparedStatementResponseSchema },
26+
})
27+
export type AwsAthenaDeletePreparedStatementRequest = ContractBodyInput<
28+
typeof awsAthenaDeletePreparedStatementContract
29+
>
30+
export type AwsAthenaDeletePreparedStatementBody = ContractBody<
31+
typeof awsAthenaDeletePreparedStatementContract
32+
>
33+
export type AwsAthenaDeletePreparedStatementResponse = ContractJsonResponse<
34+
typeof awsAthenaDeletePreparedStatementContract
35+
>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { z } from 'zod'
2+
import {
3+
athenaConnectionSchema,
4+
athenaOptionalWorkGroupSchema,
5+
} from '@/lib/api/contracts/tools/aws/athena-shared'
6+
import type {
7+
ContractBody,
8+
ContractBodyInput,
9+
ContractJsonResponse,
10+
} from '@/lib/api/contracts/types'
11+
import { defineRouteContract } from '@/lib/api/contracts/types'
12+
13+
const GetDataCatalogSchema = athenaConnectionSchema.extend({
14+
name: z.string().trim().min(1, 'Data catalog name is required'),
15+
workGroup: athenaOptionalWorkGroupSchema,
16+
})
17+
18+
const GetDataCatalogResponseSchema = z.object({
19+
success: z.literal(true),
20+
output: z.object({
21+
name: z.string(),
22+
type: z.string(),
23+
description: z.string().nullable(),
24+
status: z.string().nullable(),
25+
connectionType: z.string().nullable(),
26+
error: z.string().nullable(),
27+
parameters: z.record(z.string(), z.string()),
28+
}),
29+
})
30+
31+
export const awsAthenaGetDataCatalogContract = defineRouteContract({
32+
method: 'POST',
33+
path: '/api/tools/athena/get-data-catalog',
34+
body: GetDataCatalogSchema,
35+
response: { mode: 'json', schema: GetDataCatalogResponseSchema },
36+
})
37+
export type AwsAthenaGetDataCatalogRequest = ContractBodyInput<
38+
typeof awsAthenaGetDataCatalogContract
39+
>
40+
export type AwsAthenaGetDataCatalogBody = ContractBody<typeof awsAthenaGetDataCatalogContract>
41+
export type AwsAthenaGetDataCatalogResponse = ContractJsonResponse<
42+
typeof awsAthenaGetDataCatalogContract
43+
>

0 commit comments

Comments
 (0)