schema.test.ts118 lines · main
1import assert from 'node:assert/strict';
2import { test } from 'node:test';
3
4import {
5 diff,
6 generateSql,
7 schema,
8 table,
9 text,
10 timestamp,
11 vector,
12 type SchemaDef,
13} from './index.js';
14
15test('vector columns throw until DoltGres/LanceDB support lands', () => {
16 // DoltGres has no vector(N) type; defining one must fail early at
17 // build time rather than emitting SQL that 500s at apply time.
18 assert.throws(() => vector(3), /not supported yet on DoltGres/);
19});
20
21test('schema rejects reserved _briven_ prefix', () => {
22 assert.throws(() =>
23 schema({
24 _briven_meta: table({ id: text().primaryKey() }),
25 }),
26 );
27});
28
29test('table requires at least one primary key', () => {
30 assert.throws(() => table({ body: text() }));
31});
32
33test('composite primary key emits a single table-level constraint', () => {
34 // M2M-style table where neither column alone identifies a row.
35 const s = schema({
36 project_members: table({
37 projectId: text().primaryKey(),
38 userId: text().primaryKey(),
39 role: text().notNull(),
40 }),
41 });
42 const sql = generateSql(s);
43 // PRIMARY KEY appears once at the table level, never on individual columns.
44 assert.match(sql, /PRIMARY KEY \("projectId", "userId"\)/);
45 // The per-column inline form must NOT be present for either PK column.
46 assert.equal(/"projectId" text PRIMARY KEY/.test(sql), false);
47 assert.equal(/"userId" text PRIMARY KEY/.test(sql), false);
48 // Non-PK columns keep their NOT NULL.
49 assert.match(sql, /"role" text NOT NULL/);
50});
51
52test('generateSql emits CREATE TABLE IF NOT EXISTS', () => {
53 const s = schema({
54 notes: table({
55 id: text().primaryKey(),
56 body: text().notNull(),
57 createdAt: timestamp().default('now()').notNull(),
58 }),
59 });
60 const sql = generateSql(s);
61 assert.match(sql, /CREATE TABLE IF NOT EXISTS "notes"/);
62 assert.match(sql, /"id" text PRIMARY KEY/);
63 assert.match(sql, /"body" text NOT NULL/);
64 assert.match(sql, /"createdAt" timestamptz NOT NULL DEFAULT now\(\)/);
65});
66
67test('diff detects added tables and columns', () => {
68 const prev: SchemaDef | null = null;
69 const next = schema({
70 notes: table({
71 id: text().primaryKey(),
72 body: text().notNull(),
73 }),
74 });
75 const firstPass = diff(prev, next);
76 assert.equal(firstPass.changes.length, 1);
77 assert.equal(firstPass.changes[0]?.kind, 'create_table');
78 assert.equal(firstPass.destructive, false);
79
80 const withTitle = schema({
81 notes: table({
82 id: text().primaryKey(),
83 body: text().notNull(),
84 title: text(),
85 }),
86 });
87 const secondPass = diff(next, withTitle);
88 assert.equal(secondPass.changes.length, 1);
89 assert.equal(secondPass.changes[0]?.kind, 'add_column');
90 assert.equal(secondPass.destructive, false);
91
92 const withoutBody = schema({
93 notes: table({
94 id: text().primaryKey(),
95 title: text(),
96 }),
97 });
98 const thirdPass = diff(withTitle, withoutBody);
99 assert.equal(
100 thirdPass.changes.some((c) => c.kind === 'drop_column' && c.column === 'body'),
101 true,
102 );
103 assert.equal(thirdPass.destructive, true);
104});
105
106test('table foreign key reference renders in SQL', () => {
107 const s = schema({
108 users: table({
109 id: text().primaryKey(),
110 }),
111 posts: table({
112 id: text().primaryKey(),
113 userId: text().notNull().references('users', 'id', { onDelete: 'cascade' }),
114 }),
115 });
116 const sql = generateSql(s);
117 assert.match(sql, /FOREIGN KEY \("userId"\) REFERENCES "users" \("id"\) ON DELETE CASCADE/);
118});