From d2b9cb3cf55728effafa0d05f944e22120402f08 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 30 Jan 2024 14:25:04 -0500 Subject: [PATCH] refactor: move field queries to field-queries.test --- packages/db/test/unit/field-queries.test.js | 392 ++++++++++++++++++++ packages/db/test/unit/queries.test.js | 390 ------------------- 2 files changed, 392 insertions(+), 390 deletions(-) create mode 100644 packages/db/test/unit/field-queries.test.js delete mode 100644 packages/db/test/unit/queries.test.js diff --git a/packages/db/test/unit/field-queries.test.js b/packages/db/test/unit/field-queries.test.js new file mode 100644 index 0000000000..7331414217 --- /dev/null +++ b/packages/db/test/unit/field-queries.test.js @@ -0,0 +1,392 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { getCollectionChangeQueries, getMigrationQueries } from '../../dist/cli/queries.js'; +import { collectionSchema } from '../../dist/types.js'; +import { getCreateTableQuery } from '../../dist/internal.js'; +import { field } from '../../dist/config.js'; + +const COLLECTION_NAME = 'Users'; + +const userInitial = collectionSchema.parse({ + fields: { + name: field.text(), + age: field.number(), + email: field.text({ unique: true }), + mi: field.text({ optional: true }), + }, + writable: false, +}); + +const defaultPromptResponse = { + allowDataLoss: false, + fieldRenames: new Proxy( + {}, + { + get: () => false, + } + ), + collectionRenames: new Proxy( + {}, + { + get: () => false, + } + ), +}; + +function userChangeQueries(oldCollection, newCollection, promptResponses = defaultPromptResponse) { + return getCollectionChangeQueries({ + collectionName: COLLECTION_NAME, + oldCollection, + newCollection, + promptResponses, + }); +} + +function configChangeQueries( + oldCollections, + newCollections, + promptResponses = defaultPromptResponse +) { + return getMigrationQueries({ + oldSnapshot: { schema: oldCollections, experimentalVersion: 1 }, + newSnapshot: { schema: newCollections, experimentalVersion: 1 }, + promptResponses, + }); +} + +describe('field queries', () => { + describe('getMigrationQueries', () => { + it('should be empty when collections are the same', async () => { + const oldCollections = { [COLLECTION_NAME]: userInitial }; + const newCollections = { [COLLECTION_NAME]: userInitial }; + const queries = await configChangeQueries(oldCollections, newCollections); + expect(queries).to.deep.equal([]); + }); + + it('should create table for new collections', async () => { + const oldCollections = {}; + const newCollections = { [COLLECTION_NAME]: userInitial }; + const queries = await configChangeQueries(oldCollections, newCollections); + expect(queries).to.deep.equal([getCreateTableQuery(COLLECTION_NAME, userInitial)]); + }); + + it('should drop table for removed collections', async () => { + const oldCollections = { [COLLECTION_NAME]: userInitial }; + const newCollections = {}; + const queries = await configChangeQueries(oldCollections, newCollections); + expect(queries).to.deep.equal([`DROP TABLE "${COLLECTION_NAME}"`]); + }); + + it('should rename table for renamed collections', async () => { + const rename = 'Peeps'; + const oldCollections = { [COLLECTION_NAME]: userInitial }; + const newCollections = { [rename]: userInitial }; + const queries = await configChangeQueries(oldCollections, newCollections, { + ...defaultPromptResponse, + collectionRenames: { [rename]: COLLECTION_NAME }, + }); + expect(queries).to.deep.equal([`ALTER TABLE "${COLLECTION_NAME}" RENAME TO "${rename}"`]); + }); + }); + + describe('getCollectionChangeQueries', () => { + it('should be empty when collections are the same', async () => { + const queries = await userChangeQueries(userInitial, userInitial); + expect(queries).to.deep.equal([]); + }); + + it('should be empty when type updated to same underlying SQL type', async () => { + const blogInitial = collectionSchema.parse({ + ...userInitial, + fields: { + title: field.text(), + draft: field.boolean(), + }, + }); + const blogFinal = collectionSchema.parse({ + ...userInitial, + fields: { + ...blogInitial.fields, + draft: field.number(), + }, + }); + const queries = await userChangeQueries(blogInitial, blogFinal); + expect(queries).to.deep.equal([]); + }); + + describe('ALTER RENAME COLUMN', () => { + it('when renaming a field', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + }, + }; + userFinal.fields.middleInitial = userFinal.fields.mi; + delete userFinal.fields.mi; + + const queries = await userChangeQueries(userInitial, userFinal, { + ...defaultPromptResponse, + fieldRenames: { middleInitial: 'mi' }, + }); + expect(queries).to.deep.equal([ + `ALTER TABLE "${COLLECTION_NAME}" RENAME COLUMN "mi" TO "middleInitial"`, + ]); + }); + }); + + describe('Lossy table recreate', () => { + it('when changing a field type', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + age: field.text(), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal, { + ...defaultPromptResponse, + allowDataLoss: true, + }); + + expect(queries).to.deep.equal([ + 'DROP TABLE "Users"', + `CREATE TABLE "Users" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" text NOT NULL, "email" text NOT NULL UNIQUE, "mi" text)`, + ]); + }); + + it('when adding a required field without a default', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + phoneNumber: field.text(), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal, { + ...defaultPromptResponse, + allowDataLoss: true, + }); + + expect(queries).to.deep.equal([ + 'DROP TABLE "Users"', + `CREATE TABLE "Users" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text, "phoneNumber" text NOT NULL)`, + ]); + }); + }); + + describe('Lossless table recreate', () => { + it('when adding an optional unique field', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + phoneNumber: field.text({ unique: true, optional: true }), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal, { + ...defaultPromptResponse, + allowDataLoss: true, + }); + expect(queries).to.have.lengthOf(4); + + const tempTableName = getTempTableName(queries[0]); + expect(tempTableName).to.be.a('string'); + expect(queries).to.deep.equal([ + `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text, "phoneNumber" text UNIQUE)`, + `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, + 'DROP TABLE "Users"', + `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, + ]); + }); + + it('when dropping unique column', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + }, + }; + delete userFinal.fields.email; + + const queries = await userChangeQueries(userInitial, userFinal); + expect(queries).to.have.lengthOf(4); + + const tempTableName = getTempTableName(queries[0]); + expect(tempTableName).to.be.a('string'); + expect(queries).to.deep.equal([ + `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "mi" text)`, + `INSERT INTO "${tempTableName}" ("_id", "name", "age", "mi") SELECT "_id", "name", "age", "mi" FROM "Users"`, + 'DROP TABLE "Users"', + `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, + ]); + }); + + it('when updating to a runtime default', async () => { + const initial = collectionSchema.parse({ + ...userInitial, + fields: { + ...userInitial.fields, + age: field.date(), + }, + }); + + const userFinal = { + ...userInitial, + fields: { + ...initial.fields, + age: field.date({ default: 'now' }), + }, + }; + + const queries = await userChangeQueries(initial, userFinal); + expect(queries).to.have.lengthOf(4); + + const tempTableName = getTempTableName(queries[0]); + expect(tempTableName).to.be.a('string'); + expect(queries).to.deep.equal([ + `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" text NOT NULL DEFAULT CURRENT_TIMESTAMP, "email" text NOT NULL UNIQUE, "mi" text)`, + `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, + 'DROP TABLE "Users"', + `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, + ]); + }); + + it('when adding a field with a runtime default', async () => { + const userFinal = { + fields: { + ...userInitial.fields, + birthday: field.date({ default: 'now' }), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal); + expect(queries).to.have.lengthOf(4); + + const tempTableName = getTempTableName(queries[0]); + expect(tempTableName).to.be.a('string'); + expect(queries).to.deep.equal([ + `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text, "birthday" text NOT NULL DEFAULT CURRENT_TIMESTAMP)`, + `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, + 'DROP TABLE "Users"', + `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, + ]); + }); + + /** + * REASON: to follow the "expand" and "contract" migration model, + * you'll need to update the schema from NOT NULL to NULL. + * It's up to the user to ensure all data follows the new schema! + * + * @see https://planetscale.com/blog/safely-making-database-schema-changes#backwards-compatible-changes + */ + it('when changing a field to required', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + mi: field.text(), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal, { + ...defaultPromptResponse, + allowDataLoss: true, + }); + + expect(queries).to.have.lengthOf(4); + + const tempTableName = getTempTableName(queries[0]); + expect(tempTableName).to.be.a('string'); + expect(queries).to.deep.equal([ + `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text NOT NULL)`, + `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, + 'DROP TABLE "Users"', + `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, + ]); + }); + + it('when changing a field to unique', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + age: field.number({ unique: true }), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal, { + ...defaultPromptResponse, + allowDataLoss: true, + }); + expect(queries).to.have.lengthOf(4); + + const tempTableName = getTempTableName(queries[0]); + expect(tempTableName).to.be.a('string'); + expect(queries).to.deep.equal([ + `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL UNIQUE, "email" text NOT NULL UNIQUE, "mi" text)`, + `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, + 'DROP TABLE "Users"', + `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, + ]); + }); + }); + + describe('ALTER ADD COLUMN', () => { + it('when adding an optional field', async () => { + const userFinal = { + ...userInitial, + fields: { + ...userInitial.fields, + birthday: field.date({ optional: true }), + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal); + expect(queries).to.deep.equal(['ALTER TABLE "Users" ADD COLUMN "birthday" text']); + }); + + it('when adding a required field with default', async () => { + const defaultDate = new Date('2023-01-01'); + const userFinal = collectionSchema.parse({ + ...userInitial, + fields: { + ...userInitial.fields, + birthday: field.date({ default: new Date('2023-01-01') }), + }, + }); + + const queries = await userChangeQueries(userInitial, userFinal); + expect(queries).to.deep.equal([ + `ALTER TABLE "Users" ADD COLUMN "birthday" text NOT NULL DEFAULT '${defaultDate.toISOString()}'`, + ]); + }); + }); + + describe('ALTER DROP COLUMN', () => { + it('when removing optional or required fields', async () => { + const userFinal = { + ...userInitial, + fields: { + name: userInitial.fields.name, + email: userInitial.fields.email, + }, + }; + + const queries = await userChangeQueries(userInitial, userFinal); + expect(queries).to.deep.equal([ + 'ALTER TABLE "Users" DROP COLUMN "age"', + 'ALTER TABLE "Users" DROP COLUMN "mi"', + ]); + }); + }); + }); +}); + +/** @param {string} query */ +function getTempTableName(query) { + return query.match(/Users_([a-z0-9]+)/)?.[0]; +} diff --git a/packages/db/test/unit/queries.test.js b/packages/db/test/unit/queries.test.js deleted file mode 100644 index d356111871..0000000000 --- a/packages/db/test/unit/queries.test.js +++ /dev/null @@ -1,390 +0,0 @@ -import { expect } from 'chai'; -import { describe, it } from 'mocha'; -import { getCollectionChangeQueries, getMigrationQueries } from '../../dist/cli/queries.js'; -import { collectionSchema } from '../../dist/types.js'; -import { getCreateTableQuery } from '../../dist/internal.js'; -import { field } from '../../dist/config.js'; - -const COLLECTION_NAME = 'Users'; - -const userInitial = collectionSchema.parse({ - fields: { - name: field.text(), - age: field.number(), - email: field.text({ unique: true }), - mi: field.text({ optional: true }), - }, - writable: false, -}); - -const defaultPromptResponse = { - allowDataLoss: false, - fieldRenames: new Proxy( - {}, - { - get: () => false, - } - ), - collectionRenames: new Proxy( - {}, - { - get: () => false, - } - ), -}; - -function userChangeQueries(oldCollection, newCollection, promptResponses = defaultPromptResponse) { - return getCollectionChangeQueries({ - collectionName: COLLECTION_NAME, - oldCollection, - newCollection, - promptResponses, - }); -} - -function configChangeQueries( - oldCollections, - newCollections, - promptResponses = defaultPromptResponse -) { - return getMigrationQueries({ - oldSnapshot: { schema: oldCollections, experimentalVersion: 1 }, - newSnapshot: { schema: newCollections, experimentalVersion: 1 }, - promptResponses, - }); -} - -describe('getMigrationQueries', () => { - it('should be empty when collections are the same', async () => { - const oldCollections = { [COLLECTION_NAME]: userInitial }; - const newCollections = { [COLLECTION_NAME]: userInitial }; - const queries = await configChangeQueries(oldCollections, newCollections); - expect(queries).to.deep.equal([]); - }); - - it('should create table for new collections', async () => { - const oldCollections = {}; - const newCollections = { [COLLECTION_NAME]: userInitial }; - const queries = await configChangeQueries(oldCollections, newCollections); - expect(queries).to.deep.equal([getCreateTableQuery(COLLECTION_NAME, userInitial)]); - }); - - it('should drop table for removed collections', async () => { - const oldCollections = { [COLLECTION_NAME]: userInitial }; - const newCollections = {}; - const queries = await configChangeQueries(oldCollections, newCollections); - expect(queries).to.deep.equal([`DROP TABLE "${COLLECTION_NAME}"`]); - }); - - it('should rename table for renamed collections', async () => { - const rename = 'Peeps'; - const oldCollections = { [COLLECTION_NAME]: userInitial }; - const newCollections = { [rename]: userInitial }; - const queries = await configChangeQueries(oldCollections, newCollections, { - ...defaultPromptResponse, - collectionRenames: { [rename]: COLLECTION_NAME }, - }); - expect(queries).to.deep.equal([`ALTER TABLE "${COLLECTION_NAME}" RENAME TO "${rename}"`]); - }); -}); - -describe('getCollectionChangeQueries', () => { - it('should be empty when collections are the same', async () => { - const queries = await userChangeQueries(userInitial, userInitial); - expect(queries).to.deep.equal([]); - }); - - it('should be empty when type updated to same underlying SQL type', async () => { - const blogInitial = collectionSchema.parse({ - ...userInitial, - fields: { - title: field.text(), - draft: field.boolean(), - }, - }); - const blogFinal = collectionSchema.parse({ - ...userInitial, - fields: { - ...blogInitial.fields, - draft: field.number(), - }, - }); - const queries = await userChangeQueries(blogInitial, blogFinal); - expect(queries).to.deep.equal([]); - }); - - describe('ALTER RENAME COLUMN', () => { - it('when renaming a field', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - }, - }; - userFinal.fields.middleInitial = userFinal.fields.mi; - delete userFinal.fields.mi; - - const queries = await userChangeQueries(userInitial, userFinal, { - ...defaultPromptResponse, - fieldRenames: { middleInitial: 'mi' }, - }); - expect(queries).to.deep.equal([ - `ALTER TABLE "${COLLECTION_NAME}" RENAME COLUMN "mi" TO "middleInitial"`, - ]); - }); - }); - - describe('Lossy table recreate', () => { - it('when changing a field type', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - age: field.text(), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal, { - ...defaultPromptResponse, - allowDataLoss: true, - }); - - expect(queries).to.deep.equal([ - 'DROP TABLE "Users"', - `CREATE TABLE "Users" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" text NOT NULL, "email" text NOT NULL UNIQUE, "mi" text)`, - ]); - }); - - it('when adding a required field without a default', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - phoneNumber: field.text(), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal, { - ...defaultPromptResponse, - allowDataLoss: true, - }); - - expect(queries).to.deep.equal([ - 'DROP TABLE "Users"', - `CREATE TABLE "Users" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text, "phoneNumber" text NOT NULL)`, - ]); - }); - }); - - describe('Lossless table recreate', () => { - it('when adding an optional unique field', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - phoneNumber: field.text({ unique: true, optional: true }), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal, { - ...defaultPromptResponse, - allowDataLoss: true, - }); - expect(queries).to.have.lengthOf(4); - - const tempTableName = getTempTableName(queries[0]); - expect(tempTableName).to.be.a('string'); - expect(queries).to.deep.equal([ - `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text, "phoneNumber" text UNIQUE)`, - `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, - 'DROP TABLE "Users"', - `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, - ]); - }); - - it('when dropping unique column', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - }, - }; - delete userFinal.fields.email; - - const queries = await userChangeQueries(userInitial, userFinal); - expect(queries).to.have.lengthOf(4); - - const tempTableName = getTempTableName(queries[0]); - expect(tempTableName).to.be.a('string'); - expect(queries).to.deep.equal([ - `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "mi" text)`, - `INSERT INTO "${tempTableName}" ("_id", "name", "age", "mi") SELECT "_id", "name", "age", "mi" FROM "Users"`, - 'DROP TABLE "Users"', - `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, - ]); - }); - - it('when updating to a runtime default', async () => { - const initial = collectionSchema.parse({ - ...userInitial, - fields: { - ...userInitial.fields, - age: field.date(), - }, - }); - - const userFinal = { - ...userInitial, - fields: { - ...initial.fields, - age: field.date({ default: 'now' }), - }, - }; - - const queries = await userChangeQueries(initial, userFinal); - expect(queries).to.have.lengthOf(4); - - const tempTableName = getTempTableName(queries[0]); - expect(tempTableName).to.be.a('string'); - expect(queries).to.deep.equal([ - `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" text NOT NULL DEFAULT CURRENT_TIMESTAMP, "email" text NOT NULL UNIQUE, "mi" text)`, - `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, - 'DROP TABLE "Users"', - `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, - ]); - }); - - it('when adding a field with a runtime default', async () => { - const userFinal = { - fields: { - ...userInitial.fields, - birthday: field.date({ default: 'now' }), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal); - expect(queries).to.have.lengthOf(4); - - const tempTableName = getTempTableName(queries[0]); - expect(tempTableName).to.be.a('string'); - expect(queries).to.deep.equal([ - `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text, "birthday" text NOT NULL DEFAULT CURRENT_TIMESTAMP)`, - `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, - 'DROP TABLE "Users"', - `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, - ]); - }); - - /** - * REASON: to follow the "expand" and "contract" migration model, - * you'll need to update the schema from NOT NULL to NULL. - * It's up to the user to ensure all data follows the new schema! - * - * @see https://planetscale.com/blog/safely-making-database-schema-changes#backwards-compatible-changes - */ - it('when changing a field to required', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - mi: field.text(), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal, { - ...defaultPromptResponse, - allowDataLoss: true, - }); - - expect(queries).to.have.lengthOf(4); - - const tempTableName = getTempTableName(queries[0]); - expect(tempTableName).to.be.a('string'); - expect(queries).to.deep.equal([ - `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL, "email" text NOT NULL UNIQUE, "mi" text NOT NULL)`, - `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, - 'DROP TABLE "Users"', - `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, - ]); - }); - - it('when changing a field to unique', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - age: field.number({ unique: true }), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal, { - ...defaultPromptResponse, - allowDataLoss: true, - }); - expect(queries).to.have.lengthOf(4); - - const tempTableName = getTempTableName(queries[0]); - expect(tempTableName).to.be.a('string'); - expect(queries).to.deep.equal([ - `CREATE TABLE "${tempTableName}" (_id INTEGER PRIMARY KEY, "name" text NOT NULL, "age" integer NOT NULL UNIQUE, "email" text NOT NULL UNIQUE, "mi" text)`, - `INSERT INTO "${tempTableName}" ("_id", "name", "age", "email", "mi") SELECT "_id", "name", "age", "email", "mi" FROM "Users"`, - 'DROP TABLE "Users"', - `ALTER TABLE "${tempTableName}" RENAME TO "Users"`, - ]); - }); - }); - - describe('ALTER ADD COLUMN', () => { - it('when adding an optional field', async () => { - const userFinal = { - ...userInitial, - fields: { - ...userInitial.fields, - birthday: field.date({ optional: true }), - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal); - expect(queries).to.deep.equal(['ALTER TABLE "Users" ADD COLUMN "birthday" text']); - }); - - it('when adding a required field with default', async () => { - const defaultDate = new Date('2023-01-01'); - const userFinal = collectionSchema.parse({ - ...userInitial, - fields: { - ...userInitial.fields, - birthday: field.date({ default: new Date('2023-01-01') }), - }, - }); - - const queries = await userChangeQueries(userInitial, userFinal); - expect(queries).to.deep.equal([ - `ALTER TABLE "Users" ADD COLUMN "birthday" text NOT NULL DEFAULT '${defaultDate.toISOString()}'`, - ]); - }); - }); - - describe('ALTER DROP COLUMN', () => { - it('when removing optional or required fields', async () => { - const userFinal = { - ...userInitial, - fields: { - name: userInitial.fields.name, - email: userInitial.fields.email, - }, - }; - - const queries = await userChangeQueries(userInitial, userFinal); - expect(queries).to.deep.equal([ - 'ALTER TABLE "Users" DROP COLUMN "age"', - 'ALTER TABLE "Users" DROP COLUMN "mi"', - ]); - }); - }); -}); - -/** @param {string} query */ -function getTempTableName(query) { - return query.match(/Users_([a-z0-9]+)/)?.[0]; -}