1
import { pruneDatabase } from "@/src/__tests__/test-utils";
6
} from "@/src/server/api/services/query-builder";
7
import { type aggregations } from "@/src/server/api/services/sqlInterface";
8
import { prisma } from "@/src/server/db";
9
import { type z } from "zod";
11
describe("Build valid SQL queries", () => {
12
beforeEach(async () => await pruneDatabase());
14
describe("should enrich mandatory filters", () => {
18
values: ["project-id"],
19
strings: [' FROM traces t WHERE t."project_id" = ', ";"],
22
table: "traces_metrics",
23
values: ["project-id"],
24
strings: [' FROM traces_view t WHERE t."project_id" = ', ";"],
27
table: "traces_observations",
28
values: ["project-id", "project-id"],
30
' FROM traces t LEFT JOIN observations o ON t.id = o.trace_id WHERE t."project_id" = ',
31
' AND o."project_id" = ',
36
table: "traces_observationsview",
37
values: ["project-id", "project-id"],
39
' FROM traces t LEFT JOIN observations_view o ON t.id = o.trace_id WHERE t."project_id" = ',
40
' AND o."project_id" = ',
45
table: "observations",
46
values: ["project-id"],
47
strings: [' FROM observations_view o WHERE o."project_id" = ', ";"],
50
table: "traces_scores",
51
values: ["project-id"],
53
' FROM traces t JOIN scores s ON t.id = s.trace_id WHERE t."project_id" = ',
58
it(`should enrich mandatory filters ${prop.table}`, () => {
59
const preparedQuery = enrichAndCreateQuery("project-id", {
63
expect(preparedQuery.values).toEqual(prop.values);
64
expect(preparedQuery.strings).toEqual(prop.strings);
69
describe("should build safe SQL", () => {
70
it("should build a simple filter query", () => {
71
const preparedQuery = createQuery({
75
type: "string" as const,
76
column: "tracesProjectId",
77
operator: "=" as const,
78
value: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
82
select: [{ column: "traceId" }],
85
expect(preparedQuery.values).toEqual([
86
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
90
it("should build a simple group by and filter query", () => {
91
const preparedQuery = createQuery({
95
type: "string" as const,
96
column: "tracesProjectId",
97
operator: "=" as const,
98
value: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
101
groupBy: [{ type: "string", column: "version" }],
102
select: [{ column: "traceId" }],
105
expect(preparedQuery.values).toEqual([
106
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
110
it("should build a time series group", () => {
111
const preparedQuery = createQuery({
112
from: "observations",
118
value: new Date("2021-01-01T00:00:00.000Z"),
124
value: new Date("2021-01-04T00:00:00.000Z"),
128
{ type: "datetime", column: "startTime", temporalUnit: "day" },
130
select: [{ column: "completionTokens", agg: "SUM" }],
133
expect(preparedQuery.values).toEqual([
134
new Date("2021-01-01T00:00:00.000Z"),
135
new Date("2021-01-04T00:00:00.000Z"),
136
new Date("2021-01-01T00:00:00.000Z"),
137
new Date("2021-01-04T00:00:00.000Z"),
141
it("should not filter an unknown column", () => {
146
{ type: "string", column: "unknown", operator: "=", value: "" },
150
).toThrow("Invalid filter column: unknown");
153
it("should not select an unknown column", () => {
157
select: [{ column: "unknown" }],
159
).toThrow('Column "unknown" not found in table traces');
162
it("should not group by an unknown column", () => {
166
groupBy: [{ column: "unknown", type: "string" }],
169
).toThrow('Column "unknown" not found in table traces');
172
it("should not order by an unknown column", () => {
177
orderBy: [{ column: "unknown", direction: "ASC" }],
179
).toThrow('Column "unknown" not found in table traces');
183
describe("should retrieve data", () => {
184
it("should get a simple trace", async () => {
185
await prisma.project.upsert({
186
where: { id: "different-project-id" },
188
id: "different-project-id",
189
name: "test-project",
194
await prisma.trace.createMany({
199
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
201
metadata: { key: "value" },
208
projectId: "different-project-id",
210
metadata: { key: "value" },
217
const result = await executeQuery(
219
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
222
select: [{ column: "traceId" }],
226
expect(result).toEqual([{ traceId: "trace-1" }]);
232
first: { sumCompletionTokens: 8, name: "trace-1" },
233
second: { sumCompletionTokens: 4, name: "trace-2" },
237
first: { avgCompletionTokens: 4, name: "trace-1" },
238
second: { avgCompletionTokens: 4, name: "trace-2" },
242
first: { minCompletionTokens: 3, name: "trace-1" },
243
second: { minCompletionTokens: 4, name: "trace-2" },
247
first: { maxCompletionTokens: 5, name: "trace-1" },
248
second: { maxCompletionTokens: 4, name: "trace-2" },
252
first: { countCompletionTokens: 2, name: "trace-1" },
253
second: { countCompletionTokens: 1, name: "trace-2" },
255
].forEach((prop) => {
256
it(`should group by name and aggregate ${prop.agg}`, async () => {
257
await prisma.trace.create({
261
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
265
await prisma.observation.createMany({
270
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
277
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
284
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
291
const result = await executeQuery(
293
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
295
from: "observations",
296
groupBy: [{ type: "string", column: "name" }],
298
{ column: "completionTokens", agg: prop.agg as "SUM" | "AVG" },
304
expect(result[0]!).toStrictEqual(prop.first);
305
expect(result[1]!).toStrictEqual(prop.second);
309
it("should order by a column", async () => {
310
await prisma.trace.create({
314
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
318
await prisma.observation.createMany({
323
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
326
startTime: new Date("2021-01-01T00:00:00.000Z"),
331
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
334
startTime: new Date("2021-01-01T00:00:00.000Z"),
339
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
342
startTime: new Date("2021-01-02T00:00:00.000Z"),
347
const result = await executeQuery(
349
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
351
from: "observations",
357
value: new Date("2021-01-01T00:00:00.000Z"),
363
value: new Date("2021-01-04T00:00:00.000Z"),
367
select: [{ column: "completionTokens" }],
368
orderBy: [{ column: "completionTokens", direction: "ASC" }],
372
expect(result).toStrictEqual([
373
{ completionTokens: 3 },
374
{ completionTokens: 4 },
375
{ completionTokens: 5 },
379
[{ agg: "SUM", one: 8, two: 4 }].forEach((prop) => {
380
it(`should aggregate time series ${prop.agg}`, async () => {
381
await prisma.trace.create({
385
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
389
await prisma.observation.createMany({
394
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
397
startTime: new Date("2021-01-01T00:00:00.000Z"),
402
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
405
startTime: new Date("2021-01-01T00:00:00.000Z"),
410
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
413
startTime: new Date("2021-01-02T00:00:00.000Z"),
418
const result = await executeQuery(
420
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
422
from: "observations",
428
value: new Date("2021-01-01T00:00:00.000Z"),
434
value: new Date("2021-01-04T00:00:00.000Z"),
438
{ type: "datetime", column: "startTime", temporalUnit: "day" },
441
{ column: "completionTokens", agg: prop.agg as "SUM" | "AVG" },
446
expect(result).toStrictEqual([
448
startTime: new Date("2021-01-01T00:00:00.000Z"),
449
sumCompletionTokens: 8,
452
startTime: new Date("2021-01-02T00:00:00.000Z"),
453
sumCompletionTokens: 4,
456
startTime: new Date("2021-01-03T00:00:00.000Z"),
457
sumCompletionTokens: null,
460
startTime: new Date("2021-01-04T00:00:00.000Z"),
461
sumCompletionTokens: null,
469
percentile: "50thPercentile",
472
startTime: new Date("2021-01-01T00:00:00.000Z"),
473
percentile50Duration: 8,
476
startTime: new Date("2021-01-02T00:00:00.000Z"),
477
percentile50Duration: 5,
480
startTime: new Date("2021-01-03T00:00:00.000Z"),
481
percentile50Duration: null,
484
startTime: new Date("2021-01-04T00:00:00.000Z"),
485
percentile50Duration: null,
490
percentile: "99thPercentile",
493
startTime: new Date("2021-01-01T00:00:00.000Z"),
494
percentile99Duration: 10,
497
startTime: new Date("2021-01-02T00:00:00.000Z"),
498
percentile99Duration: 5,
501
startTime: new Date("2021-01-03T00:00:00.000Z"),
502
percentile99Duration: null,
505
startTime: new Date("2021-01-04T00:00:00.000Z"),
506
percentile99Duration: null,
511
percentile: "90thPercentile",
514
startTime: new Date("2021-01-01T00:00:00.000Z"),
515
percentile90Duration: 10,
518
startTime: new Date("2021-01-02T00:00:00.000Z"),
519
percentile90Duration: 5,
522
startTime: new Date("2021-01-03T00:00:00.000Z"),
523
percentile90Duration: null,
526
startTime: new Date("2021-01-04T00:00:00.000Z"),
527
percentile90Duration: null,
531
].forEach((props) => {
532
it(`should calculate right percentiles ${props.percentile}`, async () => {
533
await prisma.trace.create({
537
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
541
await prisma.observation.createMany({
546
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
549
startTime: new Date("2021-01-01T00:00:00.000Z"),
550
endTime: new Date("2021-01-01T00:00:10.000Z"),
555
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
558
startTime: new Date("2021-01-01T00:00:00.000Z"),
559
endTime: new Date("2021-01-01T00:00:08.000Z"),
564
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
567
startTime: new Date("2021-01-01T00:00:00.000Z"),
568
endTime: new Date("2021-01-01T00:00:01.000Z"),
573
projectId: "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
576
startTime: new Date("2021-01-02T00:00:00.000Z"),
577
endTime: new Date("2021-01-02T00:00:05.000Z"),
582
const result = await executeQuery(
584
"7a88fb47-b4e2-43b8-a06c-a5ce950dc53a",
586
from: "observations",
592
value: new Date("2021-01-01T00:00:00.000Z"),
598
value: new Date("2021-01-04T00:00:00.000Z"),
602
{ type: "datetime", column: "startTime", temporalUnit: "day" },
607
agg: props.percentile as z.infer<typeof aggregations>,
613
expect(result).toStrictEqual(props.expectedOutcome);