15.8.2023

Spot the Bug - Typescript

Lately we stumbled upon a pretty well hidden bug in one of our backend projects. The code shown below is a stripped down version of the original and does the following:

  1. fetch all templates
  2. fetch all customer ids
  3. for each customer id and each template create a concrete value
  4. store the concrete values in the database

What happens is that the database complains about a unique constraint violation for the concrete values.

Can you spot the bug?

CREATE TABLE "template" (
  "id" TEXT NOT NULL,
  "startTime" TIMESTAMP(3) NOT NULL,
  "price" DOUBLE PRECISION NOT NULL,
  "listId" TEXT NOT NULL,

  CONSTRAINT "template_pkey" PRIMARY KEY ("id")
)

CREATE TABLE "concrete" (
    "id" TEXT NOT NULL,
    "listId" CITEXT NOT NULL,
    "startTime" TIMESTAMP(3) NOT NULL,
    "price" DOUBLE PRECISION NOT NULL,
    "customerId" CITEXT NOT NULL,

    CONSTRAINT "concrete_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "concrete_id_startTime_listId_key" ON "concrete"("id", "startTime", "listId");
interface Template {
  id: string;
  startTime: Date;
  price: number;
  listId: string;
}

interface Concrete {
  id: string;
  startTime: Date;
  price: number;
  listId: string;
  customerId: string;
}

async function getAllTemplates(): Promise<Template[]> {
  return query('SELECT * FROM template');
}

async function getAllCustomerIds(): Promise<string[]> {
  return Promise.resolve(['1', '2']); // no query here for simplicity
}

async function createConcreteValues(values: Omit<Concrete, 'id'>[]): Promise<void> {
  // insert values into DB
}

async function applyTemplates() {
  const allTemplates = await getAllTemplates();
  const allCustomerIds = await getAllCustomerIds();

  const concreteValues: Omit<Concrete, 'id'>[] = allTemplates.flatMap((template) => {
    return allCustomerIds.map((customerId) => ({
      ...template,
      customerId,
    });
  };

  // this call will throw a unique constraint violation exception; why?
  await createConcreteValues(concreteValues);
}
Solution We fetch every property of a template including their ids. When we create the `concreteValues` array by blindly copying over the template properties, the ids get copied as well. Thus for each `customerId` we have a duplicated the template's id value. Storing those will obviously trigger a unique constraint violation. There are many possible solutions to fix this but the learning of this bug is Typescript related:

Objects can satisfy an interface but have other properties as well that the interface does not have. The compiler won't complain about them.

Sven

Softwareentwickler

Zur Übersicht

Standort Hannover

newcubator GmbH
Bödekerstraße 22
30161 Hannover

Standort Dortmund

newcubator GmbH
Westenhellweg 85-89
44137 Dortmund