Skip to content

Commit

Permalink
Allow migration of adding or dropping unique columns (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
colodenn authored Jan 29, 2025
1 parent a748935 commit 91edf0f
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 7 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"resolve-from": "5.0.0"
},
"devDependencies": {
"ronin": "6.0.25",
"ronin": "6.0.27",
"@biomejs/biome": "1.9.4",
"@types/bun": "1.2.1",
"@types/ini": "4.1.1",
Expand Down
30 changes: 25 additions & 5 deletions src/utils/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export const diffFields = async (
}
}

diff.push(...createFields(fieldsToAdd, modelSlug));
diff.push(...deleteFields(fieldsToDelete, modelSlug));
diff.push(...createFields(fieldsToAdd, modelSlug, definedFields));
diff.push(...deleteFields(fieldsToDelete, modelSlug, definedFields));

for (const field of queriesForAdjustment || []) {
// SQLite's ALTER TABLE is limited - adding UNIQUE or NOT NULL to an existing column
Expand Down Expand Up @@ -212,10 +212,24 @@ export const fieldsToCreate = (
export const createFields = (
fields: Array<ModelField>,
modelSlug: string,
definedFields?: Array<ModelField>,
): Array<string> => {
const diff: Array<string> = [];

for (const fieldToAdd of fields) {
if (fieldToAdd.unique) {
const existingFields = definedFields?.filter(
(f) => !fields.find((f2) => f2.slug === f.slug),
);
return createTempModelQuery(
modelSlug,
definedFields || [],
[],
[],
[],
existingFields,
);
}
diff.push(createFieldQuery(modelSlug, fieldToAdd));
}

Expand Down Expand Up @@ -247,10 +261,16 @@ export const fieldsToDrop = (
*
* @returns An array of SQL queries for deleting fields.
*/
const deleteFields = (fields: Array<ModelField>, modelSlug: string): Array<string> => {
const deleteFields = (
fieldsToDrop: Array<ModelField>,
modelSlug: string,
fields: Array<ModelField>,
): Array<string> => {
const diff: Array<string> = [];

for (const fieldToDrop of fields) {
for (const fieldToDrop of fieldsToDrop) {
if (fieldToDrop.unique) {
return createTempModelQuery(modelSlug, fields, [], [], [], fields);
}
diff.push(dropFieldQuery(modelSlug, fieldToDrop.slug));
}

Expand Down
9 changes: 8 additions & 1 deletion src/utils/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const createTempModelQuery = (
_indexes: Array<ModelIndex>,
triggers: Array<ModelTrigger>,
customQueries?: Array<string>,
includeFields?: Array<ModelField>,
): Array<string> => {
const queries: Array<string> = [];

Expand All @@ -140,7 +141,13 @@ export const createTempModelQuery = (
queries.push(createModelQuery(tempModelSlug, { fields }));

// Move all the data to the copied model
queries.push(`add.${tempModelSlug}.with(() => get.${modelSlug}())`);
queries.push(
`add.${tempModelSlug}.with(() => get.${modelSlug}(${
includeFields
? JSON.stringify({ selecting: includeFields.map((field) => field.slug) })
: ''
}))`,
);

if (customQueries) {
queries.push(...customQueries);
Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,10 @@ export const TestM = model({
name: string(),
},
}) as unknown as Model;

export const TestN = model({
slug: 'test',
fields: {
name: string(),
},
}) as unknown as Model;
44 changes: 44 additions & 0 deletions tests/utils/apply.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TestK,
TestL,
TestM,
TestN,
} from '@/fixtures/index';

import { describe, expect, test } from 'bun:test';
Expand Down Expand Up @@ -457,4 +458,47 @@ describe('apply', () => {
// @ts-expect-error This is defined!
expect(newModels[0]?.fields[1]?.type).toBe('json');
});

test('adding a unique field', async () => {
const definedModels: Array<Model> = [TestG];
const existingModels: Array<Model> = [TestN];

const db = await queryEphemeralDatabase(existingModels);
const packages = await getLocalPackages();
const models = await getModels(packages, db);

const modelDiff = await diffModels(definedModels, models);

const protocol = new Protocol(packages, modelDiff);
await protocol.convertToQueryObjects();

const statements = protocol.getSQLStatements(models);
await db.query(statements);

const newModels = await getModels(packages, db);
expect(newModels).toHaveLength(1);
// @ts-expect-error This is defined!
expect(newModels[0]?.fields[0]?.unique).toBe(true);
});

test('removing a unique field', async () => {
const definedModels: Array<Model> = [TestN];
const existingModels: Array<Model> = [TestG];

const db = await queryEphemeralDatabase(existingModels);
const packages = await getLocalPackages();
const models = await getModels(packages, db);

const modelDiff = await diffModels(definedModels, models);
const protocol = new Protocol(packages, modelDiff);
await protocol.convertToQueryObjects();

const statements = protocol.getSQLStatements(models);
await db.query(statements);

const newModels = await getModels(packages, db);
expect(newModels).toHaveLength(1);
// @ts-expect-error This is defined!
expect(newModels[0]?.fields[0]?.type).toBe('string');
});
});

0 comments on commit 91edf0f

Please sign in to comment.