Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Support insert into ... select syntax #2783

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

L-Mario564
Copy link
Collaborator

Addresses #398, #2330.
PR #1605 closed in favor of this one.

This PR adds support for the insert into ... select syntax to all dialects. The implementation is based on a comment made by @AndriiSherman on my previous PR where I attempted to implement this same feature. Read more here: #1605 (comment).

The following is valid Drizzle syntax:

const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  role: text('role').notNull()
});

const employees pgTable('employees', {
  id: serial('id').primaryKey(),
  name: text('name').notNull()
});

// Copy users who are employees into the employees table, also returns the inserted rows
const insertedEmployees = await db
  .insert(employees)
  .select(
    db.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
  )
  .returning({
    id: employees.id,
    name: employees.name
  });
  
// All of these are also valid
await db.insert(employees).select(
    () => db.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
);

await db.insert(employees).select(
    (qb) => qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
);

const qb = new QueryBuilder();
await db.insert(employees).select(
    qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
);

await db.insert(employees).select(
    () => qb.select({ name: users.name }).from(users).where(eq(users.role, 'employee'))
);

await db.insert(employees).select(
    sql`select "users"."name" as "name" from "users" where "users"."role" = 'employee'`
);

await db.insert(employees).select(
    () => sql`select "users"."name" as "name" from "users" where "users"."role" = 'employee'`
);

Unlike the previous PR, this has been implemented for all dialects.

Like the previous implementation, There's a catch. I'll just quote what I said originally since the same thing still applies:

$defaultFn realistically can't be implemented here, so there's no support for it when using this syntax. Reason being is that the default function can't be called in the query, since it's a Typescript function, not an SQL one, meaning that this can lead to issues, like for example:
Using the default function to generate a UUID with Javascript. This function will only run once before the SQL query is built, meaning that, if the select part of the query returns multiple rows, it will lead to duplicate UUIDs if the column can be null, or an error will be thrown if the column can't be null. This isn't the case for SQL functions like now() because this is left for the database to handle instead of the Javascript runtime.

@louneskmt
Copy link

Would this be valid with CTEs as well?

const insertedUsers = db
  .$with('inserted_users')
  .as(
    db
      .insert(users)
      .values({ name: 'Bob', role: 'employee' })
      .returning({ name: users.name })
  )

const insertedEmployees = await db
  .with(insertedUsers)
  .insert(employees)
  .select(db.select({ name: insertedUsers.name }).from(insertedUsers))
  .returning()

@L-Mario564
Copy link
Collaborator Author

@louneskmt CTEs should work as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants