This is a way to insert a new record, or update an existing record, based on a unique key. This is a very common operation in many applications, and is often referred to as Upserting.
It's best to be used when you control the primary key of the record, and you want to update the record if it exists, or insert it if it doesn't. Let's use an example for this.
type User = {
id: number;
name: string;
email: string;
};
const user: User = {
id: 1,
name: "John Doe",
email: "john-doe@gmail.com",
};
In this case, our user will always have an ID of 1. An UPSERT query in SQL looks something like
INSERT INTO USERS (id, name, email) VALUES (1, 'John Doe', 'john-doe@gmail.com')
ON CONFLICT(id) DO UPDATE SET name = 'John Doe', email = 'john-doe@gmail.com'
WHERE id = 1;
This query attempts to insert our user with an ID of 1, and if it already exists, the ON CONFLICT clause is called, instead updating our user.
The QueryBuilder supports this operation, and it's very easy to use. Let's see how we can do this.
import { GenerateQuery, QueryType } from "d1-orm";
type User = {
id: number;
name: string;
email: string;
};
const user: User = {
id: 1,
name: "John Doe",
email: "john-doe@gmail.com",
};
const statement = GenerateQuery(
QueryType.UPSERT,
"users",
{
data: user,
upsertOnlyUpdateData: {
name: user.name,
email: user.email,
},
where: {
id: user.id,
},
},
"id"
);
/* Returns:
{
query: "INSERT INTO users (id, name, email) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = ?, email = ? WHERE id = ?",
bindings: [1, "John Doe", "john-doe@gmail.com", "John Doe", "john-doe@gmail.com", 1]
}
*/
That might have looked like a lot. If you're confused about the first statements, reading the Query Building guide will help you out. The important part here is the final code block.
We generate a statement with the GenerateQuery
method, and pass in the QueryType.UPSERT
parameter. This tells the method that we want to generate an UPSERT statement. We then give it the name of the table to use, in this case "users".
The next parameter is the QueryOptions
object. This is where we specify the data we want to insert, and the data we want to update. We specify three objects within this:
data
: This is the data we want to insert. In this case, it's the user
object we created earlier.where
: This is the unique key we want to use to determine if the record exists. In this case, we use the id
field. id
is the only field that's useful here, but you can use multiple fields if you want.upsertOnlyUpdateData
: This is the data we want to update if the record exists. In this case, we use the name
and email
fields.Finally, the QueryBuilder allows you to provide a primary key for UPSERT operations, if you so choose. It will default to id
. This is the key that is used in the ON CONFLICT
statement. In this case, we use id
as well. You can specify an array of keys if you're using composite primary keys.
*Note: This primary key value will be ignored for all other operations, and will only be used for UPSERT operations.
This follows the same process as the previous example, but with a Model instead of the raw query builder interface.
import { Model, DataTypes } from "d1-orm";
import type { Infer } from "d1-orm";
const users = new Model(
{
tableName: "users",
D1Orm: MyD1OrmInstance,
},
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
notNull: true,
},
email: {
type: DataTypes.STRING,
},
}
);
type User = Infer<typeof users>;
const user: User = {
id: 1,
name: "John Doe",
email: "john-doe@gmail.com",
};
await users.Upsert({
data: user,
upsertOnlyUpdateData: {
name: user.name,
email: user.email,
},
where: {
id: user.id,
},
});
Generated using TypeDoc