-
Notifications
You must be signed in to change notification settings - Fork 514
Expand file tree
/
Copy pathroute.ts
More file actions
76 lines (71 loc) · 2.79 KB
/
route.ts
File metadata and controls
76 lines (71 loc) · 2.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { getClickhouseExternalClient } from "@/lib/clickhouse";
import { getSafeClickhouseErrorMessage } from "@/lib/clickhouse-errors";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { KnownErrors } from "@stackframe/stack-shared";
import { adaptSchema, adminAuthTypeSchema, jsonSchema, yupBoolean, yupMixed, yupNumber, yupObject, yupRecord, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { randomUUID } from "crypto";
const MAX_QUERY_TIMEOUT_MS = 120_000;
const DEFAULT_QUERY_TIMEOUT_MS = 10_000;
export const POST = createSmartRouteHandler({
metadata: { hidden: true },
request: yupObject({
auth: yupObject({
type: adminAuthTypeSchema,
tenancy: adaptSchema,
}).defined(),
body: yupObject({
include_all_branches: yupBoolean().default(false),
query: yupString().defined().nonEmpty(),
params: yupRecord(yupString().defined(), yupMixed().defined()).default({}),
timeout_ms: yupNumber().integer().min(1_000).max(MAX_QUERY_TIMEOUT_MS).default(DEFAULT_QUERY_TIMEOUT_MS),
}).defined(),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
result: jsonSchema.defined(),
query_id: yupString().defined(),
}).defined(),
}),
async handler({ body, auth }) {
if (body.include_all_branches) {
throw new StackAssertionError("include_all_branches is not supported yet");
}
const client = getClickhouseExternalClient();
const queryId = `${auth.tenancy.project.id}:${auth.tenancy.branchId}:${randomUUID()}`;
const resultSet = await Result.fromPromise(client.query({
query: body.query,
query_id: queryId,
query_params: body.params,
clickhouse_settings: {
SQL_project_id: auth.tenancy.project.id,
SQL_branch_id: auth.tenancy.branchId,
max_execution_time: body.timeout_ms / 1000,
readonly: "1",
allow_ddl: 0,
max_result_rows: MAX_RESULT_ROWS.toString(),
max_result_bytes: MAX_RESULT_BYTES.toString(),
result_overflow_mode: "throw",
},
format: "JSONEachRow",
}));
if (resultSet.status === "error") {
const message = getSafeClickhouseErrorMessage(resultSet.error, body.query);
throw new KnownErrors.AnalyticsQueryError(message);
}
const rows = await resultSet.data.json<Record<string, unknown>[]>();
return {
statusCode: 200,
bodyType: "json",
body: {
result: rows,
query_id: queryId,
},
};
},
});
const MAX_RESULT_ROWS = 10_000;
const MAX_RESULT_BYTES = 10 * 1024 * 1024;