From 27dbd447411f95de2bf2be43e1e8f926ecbc48f5 Mon Sep 17 00:00:00 2001 From: Pablo Baleztena Date: Fri, 4 Oct 2024 11:56:35 -0300 Subject: [PATCH] [fabric/store-sqlite] Pass all tests for base sqlite-driver implementation --- .../fabric/store-sqlite/src/model-to-sql.ts | 45 +++++--- .../store-sqlite/src/sqlite-driver.spec.ts | 105 ++++++++++++------ .../fabric/store-sqlite/src/sqlite-driver.ts | 10 +- 3 files changed, 109 insertions(+), 51 deletions(-) diff --git a/packages/fabric/store-sqlite/src/model-to-sql.ts b/packages/fabric/store-sqlite/src/model-to-sql.ts index 2859b6c..9032b8d 100644 --- a/packages/fabric/store-sqlite/src/model-to-sql.ts +++ b/packages/fabric/store-sqlite/src/model-to-sql.ts @@ -1,47 +1,60 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { VariantTag } from "@fabric/core"; -import { BaseField, FieldDefinition, Model } from "@fabric/domain"; +import { Variant, VariantTag } from "@fabric/core"; +import { FieldDefinition, getTargetKey, Model } from "@fabric/domain"; type FieldMap = { [K in FieldDefinition[VariantTag]]: ( + name: string, field: Extract, ) => string; }; const FieldMap: FieldMap = { - StringField: (f) => { - return "TEXT" + modifiersFromOpts(f); + StringField: (n, f) => { + return [n, "TEXT", modifiersFromOpts(f)].join(" "); }, - UUIDField: (f) => { + UUIDField: (n, f) => { return [ + n, "TEXT", f.isPrimaryKey ? "PRIMARY KEY" : "", modifiersFromOpts(f), ].join(" "); }, - IntegerField: function (): string { - throw new Error("Function not implemented."); + IntegerField: function (n, f): string { + return [n, "INTEGER", modifiersFromOpts(f)].join(" "); }, - ReferenceField: function (): string { - throw new Error("Function not implemented."); + ReferenceField: function (n, f): string { + return [ + n, + "TEXT", + modifiersFromOpts(f), + ",", + `FOREIGN KEY (${n}) REFERENCES ${f.targetModel}(${getTargetKey(f)})`, + ].join(" "); }, }; -function modifiersFromOpts(options: BaseField) { +function modifiersFromOpts(field: FieldDefinition) { + if (Variant.is(field, "UUIDField") && field.isPrimaryKey) { + return; + } return [ - !options.isOptional ? "NOT NULL" : "", - options.isUnique ? "UNIQUE" : "", + !field.isOptional ? "NOT NULL" : "", + field.isUnique ? "UNIQUE" : "", ].join(" "); } -function fieldDefinitionToSQL(field: FieldDefinition) { - return FieldMap[field[VariantTag]](field as any); +function fieldDefinitionToSQL(name: string, field: FieldDefinition) { + return FieldMap[field[VariantTag]](name, field as any); } export function modelToSql( model: Model>, ) { - return Object.entries(model.fields) - .map(([name, type]) => `${name} ${fieldDefinitionToSQL(type)}`) + const fields = Object.entries(model.fields) + .map(([name, type]) => fieldDefinitionToSQL(name, type)) .join(", "); + + return `CREATE TABLE ${model.name} (${fields})`; } diff --git a/packages/fabric/store-sqlite/src/sqlite-driver.spec.ts b/packages/fabric/store-sqlite/src/sqlite-driver.spec.ts index cd71e6c..8dc3f8c 100644 --- a/packages/fabric/store-sqlite/src/sqlite-driver.spec.ts +++ b/packages/fabric/store-sqlite/src/sqlite-driver.spec.ts @@ -1,15 +1,14 @@ -import { createModel, Field, isError } from "@fabric/core"; +import { isError } from "@fabric/core"; +import { defineModel, Field } from "@fabric/domain"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { SQLiteStorageDriver } from "./sqlite-driver.js"; describe("SQLite Store Driver", () => { - const model = createModel({ - name: "test", - fields: { - id: Field.uuid({}), + const schema = { + users: defineModel("users", { name: Field.string(), - }, - }); + }), + }; let store: SQLiteStorageDriver; @@ -23,71 +22,115 @@ describe("SQLite Store Driver", () => { }); test("should be able to synchronize the store and insert a record", async () => { - const result = await store.sync([model]); + const result = await store.sync(schema); if (isError(result)) throw result; - await store.insert("test", { id: "1", name: "test" }); + await store.insert("users", { + id: "1", + name: "test", + streamId: "1", + streamVersion: 1, + }); - const records = await store.select({ from: "test" }); + const records = await store.select({ from: "users" }); - expect(records).toEqual([{ id: "1", name: "test" }]); + expect(records).toEqual([ + { id: "1", name: "test", streamId: "1", streamVersion: 1 }, + ]); }); test("should be able to update a record", async () => { - const result = await store.sync([model]); + const result = await store.sync(schema); if (isError(result)) throw result; - await store.insert("test", { id: "1", name: "test" }); + await store.insert("users", { + id: "1", + name: "test", + streamId: "1", + streamVersion: 1, + }); - await store.update("test", "1", { name: "updated" }); + await store.update("users", "1", { name: "updated" }); - const records = await store.select({ from: "test" }); + const records = await store.select({ from: "users" }); - expect(records).toEqual([{ id: "1", name: "updated" }]); + expect(records).toEqual([ + { id: "1", name: "updated", streamId: "1", streamVersion: 1 }, + ]); }); test("should be able to delete a record", async () => { - const result = await store.sync([model]); + const result = await store.sync(schema); if (isError(result)) throw result; - await store.insert("test", { id: "1", name: "test" }); + await store.insert("users", { + id: "1", + name: "test", + streamId: "1", + streamVersion: 1, + }); - await store.delete("test", "1"); + await store.delete("users", "1"); - const records = await store.select({ from: "test" }); + const records = await store.select({ from: "users" }); expect(records).toEqual([]); }); test("should be able to select records", async () => { - const result = await store.sync([model]); + const result = await store.sync(schema); if (isError(result)) throw result; - await store.insert("test", { id: "1", name: "test" }); - await store.insert("test", { id: "2", name: "test" }); + await store.insert("users", { + id: "1", + name: "test", + streamId: "1", + streamVersion: 1, + }); + await store.insert("users", { + id: "2", + name: "test", + streamId: "2", + streamVersion: 1, + }); - const records = await store.select({ from: "test" }); + const records = await store.select({ from: "users" }); expect(records).toEqual([ - { id: "1", name: "test" }, - { id: "2", name: "test" }, + { id: "1", name: "test", streamId: "1", streamVersion: 1 }, + { id: "2", name: "test", streamId: "2", streamVersion: 1 }, ]); }); test("should be able to select one record", async () => { - const result = await store.sync([model]); + const result = await store.sync(schema); if (isError(result)) throw result; - await store.insert("test", { id: "1", name: "test" }); - await store.insert("test", { id: "2", name: "test" }); + await store.insert("users", { + id: "1", + name: "test", + streamId: "1", + streamVersion: 1, + }); + await store.insert("users", { + id: "2", + name: "test", + streamId: "2", + streamVersion: 1, + }); - const record = await store.selectOne({ from: "test" }); + const record = await store.selectOne({ from: "users" }); - expect(record).toEqual({ id: "1", name: "test" }); + expect(record).toEqual({ + id: "1", + name: "test", + streamId: "1", + streamVersion: 1, + }); }); }); diff --git a/packages/fabric/store-sqlite/src/sqlite-driver.ts b/packages/fabric/store-sqlite/src/sqlite-driver.ts index d900b00..24fa8a0 100644 --- a/packages/fabric/store-sqlite/src/sqlite-driver.ts +++ b/packages/fabric/store-sqlite/src/sqlite-driver.ts @@ -4,6 +4,7 @@ import { unlink } from "fs/promises"; import { CircularDependencyError, + ModelSchema, QueryDefinition, StorageDriver, StoreQueryError, @@ -35,6 +36,7 @@ export class SQLiteStorageDriver implements StorageDriver { // Enable Write-Ahead Logging, which is faster and more reliable. this.db.run("PRAGMA journal_mode= WAL;"); + this.db.run("PRAGMA foreign_keys = ON;"); } /** @@ -109,13 +111,13 @@ export class SQLiteStorageDriver implements StorageDriver { * Sincronice the store with the schema. */ async sync( - schema: ModelDefinition[], + schema: ModelSchema, ): AsyncResult { try { await dbRun(this.db, "BEGIN TRANSACTION;"); - for (const model of schema) { - const query = `CREATE TABLE ${model.name} (${modelToSql(model)});`; - await dbRun(this.db, query); + for (const modelKey in schema) { + const model = schema[modelKey]; + await dbRun(this.db, modelToSql(model)); } await dbRun(this.db, "COMMIT;"); } catch (error: any) {