Quarry
Quarry est un constructeur de requêtes type-safe pour PetraDB qui génère des objets AST au lieu de chaînes SQL, contournant entièrement le parseur. Les définitions de schémas servent de source unique de vérité pour le DDL, les requêtes et les types TypeScript à la compilation.
Installation
Section intitulée « Installation »npm install @petradb/quarryConfiguration
Section intitulée « Configuration »import { Session } from "@petradb/engine";import { quarry } from "@petradb/quarry";
const session = new Session({ storage: "memory" });const db = quarry(session);Modes de stockage
Section intitulée « Modes de stockage »// En mémoire (par défaut)new Session({ storage: "memory" });
// Stockage persistant sur fichiernew Session({ storage: "persistent", path: "./mydb.petra" });Définition du schéma
Section intitulée « Définition du schéma »Définissez les tables en utilisant les constructeurs de colonnes de Quarry. Le schéma pilote la création de tables, la construction de requêtes et l’inférence de types TypeScript :
import { table, serial, text, integer, boolean } from "@petradb/quarry";
const users = table("users", { id: serial("id").primaryKey(), name: text("name").notNull(), email: text("email").notNull().unique(), age: integer("age"), active: boolean("active").notNull().default(true),});Types de colonnes
Section intitulée « Types de colonnes »| Constructeur | Type SQL | Type TypeScript |
|---|---|---|
serial(name) | SERIAL | number |
bigserial(name) | BIGSERIAL | number |
text(name) | TEXT | string |
varchar(name, length?) | VARCHAR(n) | string |
char(name, length?) | CHAR(n) | string |
integer(name) | INTEGER | number |
smallint(name) | SMALLINT | number |
bigint(name) | BIGINT | number |
doublePrecision(name) | DOUBLE | number |
real(name) | REAL | number |
numeric(name, precision?, scale?) | NUMERIC(p,s) | number |
boolean(name) | BOOLEAN | boolean |
uuid(name) | UUID | string |
timestamp(name) | TIMESTAMP | string |
timestamptz(name) | TIMESTAMPTZ | string |
date(name) | DATE | string |
time(name) | TIME | string |
timetz(name) | TIMETZ | string |
interval(name) | INTERVAL | string |
json(name) | JSON | unknown |
bytea(name) | BYTEA | number[] |
Modificateurs de colonnes
Section intitulée « Modificateurs de colonnes »| Modificateur | Effet |
|---|---|
.notNull() | La colonne ne peut pas être null ; le type InferSelect exclut null |
.default(value) | La colonne est optionnelle dans InferInsert |
.primaryKey() | Clé primaire ; implique notNull + hasDefault (auto-incrément pour serial) |
.unique() | Ajoute une contrainte d’unicité |
.references(table, column) | Ajoute une référence de clé étrangère |
Types inférés
Section intitulée « Types inférés »Quarry infère deux types à partir de chaque définition de table :
import type { InferSelect, InferInsert } from "@petradb/quarry";
type User = InferSelect<typeof users>;// { id: number, name: string, email: string, age: number | null, active: boolean }
type NewUser = InferInsert<typeof users>;// { name: string, email: string, age?: number | null, active?: boolean, id?: number }InferSelect — le type de ligne retourné par les requêtes :
- colonnes
notNull-> type non-nullable - colonnes nullables ->
type | null
InferInsert — le type accepté par .values() :
- colonnes
notNullsans valeur par défaut -> obligatoire - colonnes avec valeur par défaut (
.default(),.primaryKey(), serial) -> optionnel - colonnes nullables -> optionnel, accepte
null
Créer une table
Section intitulée « Créer une table »await db.createTable(users);Cela génère et exécute une commande CREATE TABLE à partir de la définition du schéma — pas de SQL nécessaire.
Insertion
Section intitulée « Insertion »// Ligne unique — retourne la ligne insérée avec toutes les colonnesconst [user] = await db .insert(users) .values({ name: "Alice", email: "alice@example.com", age: 30 }) .execute();// user.id → serial auto-généré// user.active → true (valeur par défaut)
// Lignes multiplesawait db .insert(users) .values( { name: "Bob", email: "bob@example.com", age: 25 }, { name: "Charlie", email: "charlie@example.com" }, ) .execute();L’insertion requiert toutes les colonnes notNull sans valeur par défaut. Les champs optionnels peuvent être omis. TypeScript impose cela à la compilation.
RETURNING
Section intitulée « RETURNING »Par défaut, l’insertion retourne toutes les colonnes (*). Utilisez .returning() pour sélectionner des colonnes spécifiques :
const [{ id }] = await db .insert(users) .values({ name: "Alice", email: "alice@example.com" }) .returning(users.id) .execute();Upsert (ON CONFLICT)
Section intitulée « Upsert (ON CONFLICT) »Gérez les conflits lors de l’insertion avec onConflictDoNothing() ou onConflictDoUpdate() :
// Ignorer silencieusement les lignes en conflitawait db .insert(users) .values({ name: "Alice", email: "alice@example.com" }) .onConflictDoNothing() .execute();
// Mettre à jour des colonnes spécifiques en cas de conflitawait db .insert(users) .values({ name: "Alice", email: "alice@example.com", age: 31 }) .onConflictDoUpdate(["email"], { name: "Alice Updated", age: 31 }) .execute();Le premier argument de onConflictDoUpdate spécifie les colonnes de conflit, le second spécifie les colonnes à mettre à jour. Les deux sont type-safe — TypeScript impose que seules les clés de colonnes valides soient utilisées.
INSERT…SELECT
Section intitulée « INSERT…SELECT »Insérez des lignes à partir d’une requête au lieu de valeurs littérales :
// Archiver tous les utilisateurs actifsconst query = db .select(users.name, users.email) .from(users) .where(eq(users.active, true));
await db.insertFrom(archive, query, ["name", "email"]).execute();Le deuxième argument est un select builder. Le troisième argument optionnel spécifie les colonnes cibles à remplir — s’il est omis, le moteur attend que la requête produise des valeurs pour toutes les colonnes.
// Sans liste de colonnes (la requête doit correspondre à toutes les colonnes cibles)await db.insertFrom(archive, query).execute();
// Avec onConflictDoNothingawait db.insertFrom(archive, query, ["name", "email"]).onConflictDoNothing().execute();Sélection
Section intitulée « Sélection »import { eq, gt, asc, desc } from "@petradb/quarry";
// Toutes les lignes (SELECT *)const allUsers = await db.from(users).execute();
// Clause whereconst alice = await db .from(users) .where(eq(users.name, "Alice")) .execute();
// Colonnes spécifiquesconst names = await db .select(users.name, users.email) .from(users) .execute();
// Tri, limite, décalageconst page = await db .from(users) .orderBy(asc(users.name)) .limit(10) .offset(20) .execute();
// Distinctconst statuses = await db .select(users.active) .from(users) .distinct() .execute();
// Distinct on — une ligne par valeur distincte des colonnes donnéesconst perCategory = await db .from(products) .distinctOn(products.category) .orderBy(asc(products.category), asc(products.price)) .execute();// Retourne le produit le moins cher dans chaque catégorieRéférences de colonnes
Section intitulée « Références de colonnes »Les colonnes sont accessibles directement comme propriétés sur l’objet table. TypeScript empêche l’accès aux colonnes qui n’existent pas dans le schéma :
users.name; // ✓ compileusers.title; // ✗ erreur de compilation — 'title' n'existe pas dans usersExpressions
Section intitulée « Expressions »Comparaison
Section intitulée « Comparaison »import { eq, ne, gt, gte, lt, lte, like, ilike } from "@petradb/quarry";import { notLike, notIlike, isDistinctFrom, isNotDistinctFrom } from "@petradb/quarry";
eq(users.name, "Alice") // name = 'Alice'ne(users.name, "Bob") // name != 'Bob'gt(users.age, 21) // age > 21gte(users.age, 18) // age >= 18lt(users.age, 65) // age < 65lte(users.age, 30) // age <= 30like(users.name, "A%") // name LIKE 'A%'notLike(users.name, "A%") // name NOT LIKE 'A%'ilike(users.email, "%@x%") // email ILIKE '%@x%'notIlike(users.email, "%@x%")
// Comparaison null-safeisDistinctFrom(users.age, null) // age IS DISTINCT FROM NULLisNotDistinctFrom(users.age, null) // age IS NOT DISTINCT FROM NULLimport { and, or, not } from "@petradb/quarry";
and(eq(users.active, true), gt(users.age, 18))or(eq(users.name, "Alice"), eq(users.name, "Bob"))not(eq(users.active, false))and() et or() acceptent un nombre quelconque d’arguments :
and(cond1, cond2, cond3) // cond1 AND cond2 AND cond3Vérification de null
Section intitulée « Vérification de null »import { isNull, isNotNull } from "@petradb/quarry";
isNull(users.age) // age IS NULLisNotNull(users.age) // age IS NOT NULLTests booléens
Section intitulée « Tests booléens »import { isTrue, isNotTrue, isFalse, isNotFalse, isUnknown, isNotUnknown } from "@petradb/quarry";
isTrue(users.active) // active IS TRUEisNotTrue(users.active) // active IS NOT TRUEisFalse(users.active) // active IS FALSEisNotFalse(users.active) // active IS NOT FALSEisUnknown(users.active) // active IS UNKNOWNisNotUnknown(users.active) // active IS NOT UNKNOWNCollections
Section intitulée « Collections »import { inList, notInList, between, notBetween, betweenSymmetric } from "@petradb/quarry";
inList(users.name, ["Alice", "Bob", "Charlie"]) // name IN (...)notInList(users.id, [1, 2, 3]) // id NOT IN (...)between(users.age, 18, 65) // age BETWEEN 18 AND 65notBetween(users.age, 18, 65) // age NOT BETWEEN 18 AND 65betweenSymmetric(users.age, 65, 18) // age BETWEEN SYMMETRIC 65 AND 18notBetweenSymmetric(users.age, 65, 18) // age NOT BETWEEN SYMMETRIC 65 AND 18Arithmétique
Section intitulée « Arithmétique »import { add, sub, mul, div, mod, pow, neg } from "@petradb/quarry";
add(users.age, 10) // age + 10sub(users.age, 5) // age - 5mul(users.age, 2) // age * 2div(users.age, 3) // age / 3mod(users.age, 2) // age % 2pow(users.age, 2) // age ^ 2neg(users.age) // -ageOpérateurs de chaînes
Section intitulée « Opérateurs de chaînes »import { concat } from "@petradb/quarry";
concat(users.name, " Jr.") // name || ' Jr.'Opérateurs bit à bit
Section intitulée « Opérateurs bit à bit »import { bitAnd, bitOr, bitXor, bitNot, leftShift, rightShift } from "@petradb/quarry";
bitAnd(users.flags, 0xFF) // flags & 255bitOr(users.flags, 1) // flags | 1bitXor(users.flags, 0xFF) // flags # 255bitNot(users.flags) // ~flagsleftShift(users.flags, 2) // flags << 2rightShift(users.flags, 1) // flags >> 1Opérateurs JSON
Section intitulée « Opérateurs JSON »import { jsonGet, jsonGetText, jsonPath, jsonPathText } from "@petradb/quarry";import { jsonContains, jsonContainedBy, jsonHasKey, jsonHasAnyKey, jsonHasAllKeys } from "@petradb/quarry";
jsonGet(t.data, "name") // data -> 'name'jsonGetText(t.data, "name") // data ->> 'name'jsonPath(t.data, path) // data #> pathjsonPathText(t.data, path) // data #>> pathjsonContains(t.data, other) // data @> otherjsonContainedBy(t.data, other) // data <@ otherjsonHasKey(t.data, "key") // data ? 'key'jsonHasAnyKey(t.data, keys) // data ?| keysjsonHasAllKeys(t.data, keys) // data ?& keysOpérateurs de tableaux
Section intitulée « Opérateurs de tableaux »import { arrayOverlap } from "@petradb/quarry";
arrayOverlap(t.tags, t.otherTags) // tags && otherTags (les tableaux se chevauchent)Opérateurs génériques
Section intitulée « Opérateurs génériques »Pour les opérateurs non couverts par un helper nommé, utilisez op() et unaryOp() :
import { op, unaryOp } from "@petradb/quarry";
op(users.age, ">=", 18) // age >= 18unaryOp("NOT", eq(users.active, true))Expression CASE
Section intitulée « Expression CASE »import { caseWhen, literal } from "@petradb/quarry";
caseWhen( [ { when: gt(users.age, 60), then: literal("senior") }, { when: gt(users.age, 18), then: literal("adult") }, ], "minor", // sinon)Expression CAST
Section intitulée « Expression CAST »import { cast } from "@petradb/quarry";
cast(users.age, "text") // CAST(age AS TEXT)cast(users.age, "double") // CAST(age AS DOUBLE)Alias et littéraux
Section intitulée « Alias et littéraux »import { alias, literal } from "@petradb/quarry";
alias(add(users.age, 10), "age_plus_10")
literal("hello") // chaîneliteral(42) // nombreliteral(true) // booléenliteral(null) // nullAgrégats et regroupement
Section intitulée « Agrégats et regroupement »Agrégats intégrés
Section intitulée « Agrégats intégrés »import { count, sum, avg, min, max, alias } from "@petradb/quarry";import { stringAgg, arrayAgg, boolAnd, boolOr, jsonAgg, jsonObjectAgg } from "@petradb/quarry";
// Compter toutes les lignesconst [{ total }] = await db .select(alias(count(), "total")) .from(users) .execute();
// Regrouper avec agrégatconst stats = await db .select(users.active, alias(count(), "cnt")) .from(users) .groupBy(users.active) .execute();
// Havingconst popular = await db .select(users.active, alias(count(), "cnt")) .from(users) .groupBy(users.active) .having(gt(alias(count(), "cnt"), 5)) .execute();
// Autres agrégatssum(users.age) // SUM(age)avg(users.age) // AVG(age)min(users.age) // MIN(age)max(users.age) // MAX(age)stringAgg(users.name, ", ") // STRING_AGG(name, ', ')arrayAgg(users.name) // ARRAY_AGG(name)boolAnd(users.active) // BOOL_AND(active)boolOr(users.active) // BOOL_OR(active)jsonAgg(users.name) // JSON_AGG(name)jsonObjectAgg(users.name, users.age) // JSON_OBJECT_AGG(name, age)Agrégats statistiques
Section intitulée « Agrégats statistiques »import { variance, varSamp, varPop, stddev, stddevSamp, stddevPop } from "@petradb/quarry";
variance(emp.salary) // VARIANCE(salary) — variance échantillonvarSamp(emp.salary) // VAR_SAMP(salary) — identique à variancevarPop(emp.salary) // VAR_POP(salary) — variance de populationstddev(emp.salary) // STDDEV(salary) — écart type échantillonstddevSamp(emp.salary) // STDDEV_SAMP(salary) — identique à stddevstddevPop(emp.salary) // STDDEV_POP(salary) — écart type de populationAgrégats bit à bit
Section intitulée « Agrégats bit à bit »import { bitAndAgg, bitOrAgg, bitXorAgg } from "@petradb/quarry";
bitAndAgg(emp.flags) // BIT_AND(flags)bitOrAgg(emp.flags) // BIT_OR(flags)bitXorAgg(emp.flags) // BIT_XOR(flags)import { every } from "@petradb/quarry";
every(emp.active) // EVERY(active) — vrai quand toutes les lignes sont vraiesFILTER sur les agrégats
Section intitulée « FILTER sur les agrégats »Restreignez les lignes traitées par un agrégat avec filter() :
import { filter } from "@petradb/quarry";
// COUNT(*) FILTER (WHERE salary > 100)filter(count(), gt(emp.salary, 100))
// SUM(salary) FILTER (WHERE active = true)filter(sum(emp.salary), eq(emp.active, true))Exemple avec plusieurs agrégats filtrés :
const [row] = await db .select( alias(count(), "total"), alias(filter(count(), gt(employees.salary, 100)), "high_earners"), alias(filter(sum(employees.salary), eq(employees.active, true)), "active_payroll"), ) .from(employees) .execute();Fonctions
Section intitulée « Fonctions »Appelez toute fonction SQL avec fn() :
import { fn } from "@petradb/quarry";
fn("upper", users.name) // UPPER(name)fn("coalesce", users.age, 0) // COALESCE(age, 0)fn("length", users.name) // LENGTH(name)fn("lower", users.email) // LOWER(email)fn("abs", users.age) // ABS(age)fn("round", users.score, 2) // ROUND(score, 2)Jointures
Section intitulée « Jointures »Quarry supporte les jointures inner, left, right, full et cross avec typage des résultats à la compilation.
Jointure inner
Section intitulée « Jointure inner »Toutes les colonnes des deux tables sont incluses dans le résultat. La nullabilité est préservée depuis le schéma original :
const posts = table("posts", { id: serial("id").primaryKey(), userId: integer("user_id").notNull(), title: text("title").notNull(), body: text("body"),});
const rows = await db .from(users) .innerJoin(posts, eq(users.id, posts.userId)) .where(eq(users.name, "Alice")) .execute();
// Type du résultat : (InferSelect<users> & InferSelect<posts>)[]// rows[0].name → string// rows[0].title → string// rows[0].body → string | null (nullable dans le schéma posts)Jointure left
Section intitulée « Jointure left »Les colonnes de la table jointe deviennent toutes nullables, car les lignes non correspondantes produisent null :
const rows = await db .from(users) .leftJoin(posts, eq(users.id, posts.userId)) .execute();
// Type du résultat : (InferSelect<users> & Nullable<InferSelect<posts>>)[]// rows[0].name → string (table de base, non affectée)// rows[0].title → string | null (la jointure left la rend nullable)// rows[0].userId → number | null (la jointure left la rend nullable)Jointure right
Section intitulée « Jointure right »Les colonnes de la table de base deviennent nullables, les colonnes de la table jointe préservent leur nullabilité originale :
const rows = await db .from(users) .rightJoin(posts, eq(users.id, posts.userId)) .execute();
// Type du résultat : (Nullable<InferSelect<users>> & InferSelect<posts>)[]// rows[0].name → string | null (la jointure right rend la table de base nullable)// rows[0].title → string (table jointe, non affectée)Jointure full
Section intitulée « Jointure full »Les deux côtés deviennent nullables :
const rows = await db .from(users) .fullJoin(posts, eq(users.id, posts.userId)) .execute();
// Type du résultat : (Nullable<InferSelect<users>> & Nullable<InferSelect<posts>>)[]// rows[0].name → string | null// rows[0].title → string | nullJointure cross
Section intitulée « Jointure cross »Produit le produit cartésien des deux tables — pas de condition on :
const rows = await db .from(users) .crossJoin(posts) .execute();
// Type du résultat : (InferSelect<users> & InferSelect<posts>)[]// Chaque combinaison utilisateur x articleJointures chaînées
Section intitulée « Jointures chaînées »Les jointures multiples accumulent les types correctement :
const comments = table("comments", { id: serial("id").primaryKey(), postId: integer("post_id").notNull(), content: text("content").notNull(),});
const rows = await db .from(users) .innerJoin(posts, eq(users.id, posts.userId)) .leftJoin(comments, eq(posts.id, comments.postId)) .execute();
// colonnes posts : non-null (jointure inner)// colonnes comments : nullable (jointure left)// rows[0].title → string (jointure inner)// rows[0].content → string | null (jointure left)Jointure avec sélection de colonnes
Section intitulée « Jointure avec sélection de colonnes »const rows = await db .select(users.name, posts.title) .from(users) .innerJoin(posts, eq(users.id, posts.userId)) .execute();Jointure avec agrégats
Section intitulée « Jointure avec agrégats »const rows = await db .select(users.name, alias(count(), "post_count")) .from(users) .innerJoin(posts, eq(users.id, posts.userId)) .groupBy(users.name) .orderBy(desc(alias(count(), "post_count"))) .execute();Alias de tables
Section intitulée « Alias de tables »Utilisez tableAs() pour créer des tables avec alias pour les auto-jointures ou quand la même table apparaît plusieurs fois :
import { tableAs } from "@petradb/quarry";
const mgr = tableAs(employees, "mgr");const emp = tableAs(employees, "emp");
const rows = await db .select( alias(emp.name, "employee"), alias(mgr.name, "manager"), ) .from(emp) .leftJoin(mgr, eq(emp.managerId, mgr.id)) .execute();Les alias sont type-safe — mgr.name impose toujours que name existe dans le schéma employees.
Sous-requêtes
Section intitulée « Sous-requêtes »Sous-requête IN
Section intitulée « Sous-requête IN »import { inSubquery, notInSubquery } from "@petradb/quarry";
// Utilisateurs ayant au moins un articleconst rows = await db .from(users) .where( inSubquery(users.id, db.select(posts.userId).from(posts)), ) .execute();
// Utilisateurs n'ayant AUCUN articleconst rows = await db .from(users) .where( notInSubquery(users.id, db.select(posts.userId).from(posts)), ) .execute();Sous-requête EXISTS
Section intitulée « Sous-requête EXISTS »import { exists } from "@petradb/quarry";
const rows = await db .from(users) .where( exists( db.select(literal(1)).from(posts).where(eq(posts.userId, users.id)), ), ) .execute();Sous-requête scalaire
Section intitulée « Sous-requête scalaire »Utilisez subquery() pour encapsuler une sélection comme valeur scalaire :
import { subquery } from "@petradb/quarry";
// Utilisateurs plus âgés que l'âge moyenconst rows = await db .from(users) .where( gt(users.age, subquery(db.select(avg(users.age)).from(users))), ) .execute();Les fonctions de sous-requête (subquery, exists, inSubquery, notInSubquery) acceptent directement n’importe quel query builder — aucune conversion intermédiaire n’est nécessaire.
import { asc, desc } from "@petradb/quarry";
// Tri simpledb.from(users).orderBy(asc(users.name))db.from(users).orderBy(desc(users.age))
// Colonnes multiplesdb.from(users).orderBy(asc(users.name), desc(users.age))
// NULLS FIRST / NULLS LASTdb.from(users).orderBy(asc(users.age, { nulls: "first" }))db.from(users).orderBy(desc(users.age, { nulls: "last" }))Lorsque nulls n’est pas spécifié, le moteur utilise le comportement par défaut (les null sont triés en dernier en ordre ascendant, en premier en ordre descendant).
Mise à jour
Section intitulée « Mise à jour »// Mise à jour avec whereconst result = await db .update(users) .set({ age: 31 }) .where(eq(users.name, "Alice")) .execute();// result.rowCount → 1
// Mettre à jour plusieurs champsawait db .update(users) .set({ name: "Alice Smith", age: 32, active: false }) .where(eq(users.id, 1)) .execute();
// Mettre à nullawait db .update(users) .set({ age: null }) .where(eq(users.name, "Bob")) .execute();La méthode .set() accepte Partial<InferSelect<T>> — TypeScript impose des noms et types de colonnes valides.
UPDATE…FROM
Section intitulée « UPDATE…FROM »Joignez une autre table pour piloter les mises à jour :
const priceUpdates = table("price_updates", { id: serial("id").primaryKey(), productName: text("product_name").notNull(), newPrice: integer("new_price").notNull(),});
await db .update(products) .set({ price: 0 }) // valeur définie ; utilisez les références de colonnes dans WHERE pour la logique conditionnelle .from(priceUpdates) .where(eq(products.name, priceUpdates.productName)) .execute();.from() accepte plusieurs tables :
db.update(t1).set({ ... }).from(t2, t3).where(and(...))RETURNING
Section intitulée « RETURNING »Update et delete supportent .returning() pour récupérer les lignes affectées :
const result = await db .update(users) .set({ active: false }) .where(lt(users.age, 18)) .returning(users.id, users.name) .execute();// result.rows → [{ id: 3, name: "Charlie" }, ...]Suppression
Section intitulée « Suppression »const result = await db .delete(users) .where(eq(users.name, "Alice")) .execute();// result.rowCount → 1DELETE…USING
Section intitulée « DELETE…USING »Joignez une autre table pour déterminer les lignes à supprimer :
const deleteList = table("delete_list", { id: serial("id").primaryKey(), userName: text("user_name").notNull(),});
await db .delete(users) .using(deleteList) .where(eq(users.name, deleteList.userName)) .execute();.using() accepte plusieurs tables :
db.delete(t1).using(t2, t3).where(and(...))Transactions
Section intitulée « Transactions »Encapsulez plusieurs opérations dans une transaction avec commit/rollback automatique :
const result = await db.transaction(async (tx) => { const [user] = await tx .insert(users) .values({ name: "Alice", email: "alice@example.com" }) .execute();
await tx .insert(posts) .values({ userId: user.id, title: "First Post" }) .execute();
return user;});// Si une opération échoue, la transaction entière est annuléeLe callback reçoit une instance QuarryDB scopée à la transaction. La valeur de retour du callback devient la valeur de retour de transaction(), avec le type préservé.
Inspection de l’AST
Section intitulée « Inspection de l’AST »Chaque builder dispose d’une méthode .toAST() qui retourne l’objet AST brut sans l’exécuter. Utile pour le débogage, la journalisation ou la construction d’abstractions de niveau supérieur :
const ast = db .from(users) .where(eq(users.name, "Alice")) .orderBy(asc(users.id)) .limit(10) .toAST();
console.log(JSON.stringify(ast, null, 2));// {// "kind": "query",// "query": {// "kind": "select",// "exprs": [{ "kind": "star" }],// "from": [{ "kind": "table", "name": "users" }],// "where": { "kind": "binary", "left": ..., "op": "=", "right": ... },// "orderBy": [{ "expr": ..., "direction": "asc" }],// "limit": 10// }// }Comment ça fonctionne
Section intitulée « Comment ça fonctionne »Quarry construit de simples objets JavaScript (unions discriminées avec un champ kind) qui représentent l’AST de la requête. Lorsque vous appelez .execute(), ces objets sont passés à la méthode executeAST() du moteur, qui les convertit directement dans l’AST interne Scala du moteur — en évitant entièrement la génération et l’analyse de chaînes SQL.
Schéma → API Builder → Objets AST JS → AST Moteur → Réécriture → Exécution ↑ pas de parseur SQLCela donne à Quarry les mêmes capacités de requête que le SQL tout en éliminant le coût d’analyse et en permettant une sécurité de type complète à la compilation.
Nettoyage
Section intitulée « Nettoyage »await session.close();