Spot the Bug - Typescript
Recently, we faced an intriguing bug in one of our backend projects. The code below is a simplified version of the original code and it executes the following processes:
Fetch all templates
Fetch all customer ids
Generate a concrete value for each customer id and template
Save the concrete values in the database
However, the database throws an error regarding a unique constraint violation for the concrete values. Can you figure out what's causing the problem?
This is the SQL code:
1CREATE TABLE "template" (
2 "id" TEXT NOT NULL,
3 "startTime" TIMESTAMP(3) NOT NULL,
4 "price" DOUBLE PRECISION NOT NULL,
5 "listId" TEXT NOT NULL,
6
7 CONSTRAINT "template_pkey" PRIMARY KEY ("id")
8)
9
10CREATE TABLE "concrete" (
11 "id" TEXT NOT NULL,
12 "listId" CITEXT NOT NULL,
13 "startTime" TIMESTAMP(3) NOT NULL,
14 "price" DOUBLE PRECISION NOT NULL,
15 "customerId" CITEXT NOT NULL,
16
17 CONSTRAINT "concrete_pkey" PRIMARY KEY ("id")
18);
19
20CREATE UNIQUE INDEX "concrete_id_startTime_listId_key" ON "concrete"("id", "startTime", "listId");
And the following Typescript code:
1interface Template {
2 id: string;
3 startTime: Date;
4 price: number;
5 listId: string;
6}
7
8interface Concrete {
9 id: string;
10 startTime: Date;
11 price: number;
12 listId: string;
13 customerId: string;
14}
15
16async function getAllTemplates(): Promise<Template[]> {
17 return query('SELECT * FROM template');
18}
19
20async function getAllCustomerIds(): Promise<string[]> {
21 return Promise.resolve(['1', '2']); // no query here for simplicity
22}
23
24async function createConcreteValues(values: Omit<Concrete, 'id'>[]): Promise<void> {
25 // insert values into DB
26}
27
28async function applyTemplates() {
29 const allTemplates = await getAllTemplates();
30 const allCustomerIds = await getAllCustomerIds();
31
32 const concreteValues: Omit<Concrete, 'id'>[] = allTemplates.flatMap((template) => {
33 return allCustomerIds.map((customerId) => ({
34 ...template,
35 customerId,
36 });
37 };
38
39 // this call will throw a unique constraint violation exception; why?
40 await createConcreteValues(concreteValues);
41}
When generating the concreteValues
array, all properties of a template, including their id, are fetched. Consequently, for each customerId
, the template's id values are duplicated. Storing these duplicates triggers a unique constraint violation.
The bug can be fixed in several ways but the primary learning here involves Typescript:
An object can comply with an interface and yet include properties that the interface doesn't dictate – and the compiler won't object.