Ir para o conteúdo

Inserir, atualizar, deletar

Ler é metade da história. Agora vamos escrever — e ver como o tempest-db-js usa o sistema de tipos pra evitar um erro clássico: apagar uma tabela inteira sem querer.

INSERT

insert(Model).values(...) é tipado por InferInsert (lembra da página de Modelos: PK e defaults são opcionais):

import { insert } from "tempest-db-js";

insert(User).values({ name: "Ben", age: 30, nickname: null });

Faltou uma coluna obrigatória? Erro de compilação:

// ❌ erro: `age` é obrigatório
insert(User).values({ name: "Ben", nickname: null });

Inserir várias linhas

values aceita uma linha ou um array:

insert(User).values([
  { name: "Ana", age: 22, nickname: null },
  { name: "Beto", age: 41, nickname: "B" },
]);

.returning() — recuperar o que foi inserido

Sem returning, o resultado da execução é o número de linhas afetadas. Com returning, é a linha (ou uma projeção dela):

// linha completa
const full = insert(User)
  .values({ name: "x", age: 1, nickname: null })
  .returning();
// resultado inferido: UserRow

// só algumas colunas
const onlyId = insert(User)
  .values({ name: "x", age: 1, nickname: null })
  .returning(["id"]);
// resultado inferido: { id: number }

UPDATE — e o guard de segurança

update(Model).set(...) define as colunas a alterar (parcial — só as informadas mudam):

import { update } from "tempest-db-js";

update(User).set({ age: 31 }).where({ id: 1 });

set valida as colunas:

// ❌ erro: `bogus` não é coluna
update(User).set({ bogus: 1 });

O guard tipado contra full-table writes

Aqui está a parte importante. Um UPDATE sem WHERE altera todas as linhas — quase sempre um acidente. O tempest-db-js modela isso no tipo: um update começa no estado Guarded = false e só vira executável depois de um .where(...):

const safe = update(User).set({ age: 31 }).where({ id: 1 });
//    ^ Guarded = true  → o session.execute aceita

const unsafe = update(User).set({ age: 0 });
//    ^ Guarded = false → o session.execute REJEITA em tempo de compilação

Precisa mesmo atualizar a tabela inteira? Diga isso explicitamente com .unguarded() — fica óbvio na revisão de código que foi intencional:

const all = update(User).set({ age: 0 }).unguarded();
//    ^ Guarded = true  → liberado, mas de propósito

Por que isso importa

UPDATE users SET age = 0 sem WHERE zera a idade de todo mundo. Em outros ORMs isso compila sem reclamar. No tempest-db-js, ou você filtra com .where(), ou declara .unguarded() na cara — não tem caminho silencioso pro desastre.

DELETE — mesmo guard

del(Model) (o nome é del porque delete é palavra reservada em JS) segue exatamente a mesma regra:

import { del } from "tempest-db-js";

del(User).where({ id: 1 });        // ✅ guarded, seguro
del(User).unguarded();             // ✅ apaga tudo, mas de propósito
del(User);                         // ⚠️ Guarded = false → execução rejeitada

returning também funciona em delete:

const removed = del(User).where({ id: 1 }).returning(["name"]);
// resultado inferido: { name: string }

Escrita que lê outra tabela

INSERT ... SELECT

await session
  .execute(
    insert(ArchivedOrder).fromSelect(
      ["id", "total"],
      select(Order, ["id", "total"]).where({ createdAt: { lt: corte } }),
    ),
  )
  .rowsAffected();

As linhas não passam pelo processo Node. Arquivar um milhão de pedidos vira um statement, não um milhão de idas e voltas.

UPDATE ... FROM

update(Order)
  .set({ tier: col("c.tier") })     // (1)!
  .from(Customer, "c")
  .where({ customerId: col("c.id") });   // (2)!
  1. O set() passou a aceitar referência de coluna, não só valor e sql.raw.
  2. A condição de junção vai no where, que é onde o SQL a quer.

DELETE ... USING

del(Order).using(Customer, "c").where({ customerId: col("c.id"), "c.banned": true });

Nem todo banco tem as três

PostgreSQL SQLite MySQL
INSERT ... SELECT ✅ ✅ ✅
UPDATE ... FROM ✅ ✅ (3.33+) erro
DELETE ... USING ✅ erro erro

Onde não existe, o compilador lança com a alternativa na mensagem — no SQLite, where({ id: { in: select(Other, ["id"]).asSubquery("id") } }). Emitir a cláusula assim mesmo daria erro do servidor; ignorá-la mudaria quais linhas são escritas.

Recap

  • insert(Model).values(...) — tipado por InferInsert; aceita 1 ou N linhas.
  • .returning() → linha completa; .returning([cols]) → Pick.
  • update(Model).set(...) valida as colunas; é parcial.
  • Guard tipado: update/del só executam após .where() ou .unguarded() explícito — full-table write acidental vira erro de compilação.
  • del é o delete (palavra reservada).

Agora que você sabe montar todas as queries, vamos executá-las contra um banco de verdade. 👉 Executando queries