-
Notifications
You must be signed in to change notification settings - Fork 514
Expand file tree
/
Copy pathroute.ts
More file actions
120 lines (112 loc) · 4.03 KB
/
route.ts
File metadata and controls
120 lines (112 loc) · 4.03 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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { getClickhouseExternalClient, getQueryTimingStats, isClickhouseConfigured } from "@/lib/clickhouse";
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";
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).default(10_000),
}).defined(),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
result: jsonSchema.defined(),
stats: yupObject({
cpu_time: yupNumber().defined(),
wall_clock_time: yupNumber().defined(),
}).defined(),
}).defined(),
}),
async handler({ body, auth }) {
if (body.include_all_branches) {
throw new StackAssertionError("include_all_branches is not supported yet");
}
if (!isClickhouseConfigured()) {
throw new StackAssertionError("ClickHouse is not configured");
}
const client = getClickhouseExternalClient();
const queryId = 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);
if (message === null) {
throw new StackAssertionError("Unknown Clickhouse error", { cause: resultSet.error });
}
throw new KnownErrors.AnalyticsQueryError(message);
}
const rows = await resultSet.data.json<Record<string, unknown>[]>();
const stats = await getQueryTimingStats(client, queryId);
return {
statusCode: 200,
bodyType: "json",
body: {
result: rows,
stats: {
cpu_time: stats.cpu_time_ms,
wall_clock_time: stats.wall_clock_time_ms,
},
},
};
},
});
const SAFE_CLICKHOUSE_ERROR_CODES = [
62, // SYNTAX_ERROR
159, // TIMEOUT_EXCEEDED
164, // READONLY
158, // TOO_MANY_ROWS
396, // TOO_MANY_ROWS_OR_BYTES
636, // CANNOT_EXTRACT_TABLE_STRUCTURE
];
const UNSAFE_CLICKHOUSE_ERROR_CODES = [
36, // BAD_ARGUMENTS
60, // UNKNOWN_TABLE
497, // ACCESS_DENIED
];
const DEFAULT_CLICKHOUSE_ERROR_MESSAGE = "Error during execution of this query.";
const MAX_RESULT_ROWS = 10_000;
const MAX_RESULT_BYTES = 10 * 1024 * 1024;
function getSafeClickhouseErrorMessage(error: unknown): string | null {
if (typeof error !== "object" || error === null || !("code" in error) || typeof error.code !== "string") {
return null;
}
const errorCode = Number(error.code);
if (isNaN(errorCode)) {
return null;
}
const message = "message" in error && typeof error.message === "string" ? error.message : null;
if (SAFE_CLICKHOUSE_ERROR_CODES.includes(errorCode)) {
return message;
}
if (UNSAFE_CLICKHOUSE_ERROR_CODES.includes(errorCode)) {
return DEFAULT_CLICKHOUSE_ERROR_MESSAGE;
}
return null;
}