diff --git a/jest.config.js b/jest.config.js index 54dbdde..b8cec38 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,9 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - testMatch: ['**/test/**/*.test.ts'], + testMatch: ['**/test/**/manager.test.ts'], maxWorkers: 12, - collectCoverageFrom: ['/src/**/*.ts', '!/src/index.ts', '!/src/types/**/*.ts'], + collectCoverageFrom: ['/src/**/types.ts', '/src/**/manager.ts'], transform: { '^.+\\.(ts|tsx)$': [ 'ts-jest', diff --git a/package-lock.json b/package-lock.json index eca9b35..79300a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sqlitecloud-js", - "version": "0.0.25", + "version": "0.0.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sqlitecloud-js", - "version": "0.0.25", + "version": "0.0.26", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.1", diff --git a/src/index.ts b/src/index.ts index 72f02d9..6e54bb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,3 +10,15 @@ export { SQLiteCloudRowset, SQLiteCloudRow } from './rowset' export { SQLiteCloudConnection } from './connection' export { escapeSqlParameter, prepareSql } from './utilities' + +export { SQLiteManager } from './manager' +export { + SQLiteManagerType, + SQLiteManagerColumn, + SQLiteManagerTable, + SQLiteManagerConstraints, + SQLiteManagerForeignKeyOn, + SQLiteManagerForeignKeyOptions, + SQLiteManagerCollate, + SQLiteManagerDefault +} from './types' diff --git a/src/manager.ts b/src/manager.ts new file mode 100644 index 0000000..2dc9465 --- /dev/null +++ b/src/manager.ts @@ -0,0 +1,412 @@ +/* eslint-disable prettier/prettier */ +import { + SQLiteManagerType, + SQLiteManagerColumn, + SQLiteManagerTable, + SQLiteManagerConstraints, + SQLiteManagerDefault, + SQLiteManagerCollate, + SQLiteManagerForeignKeyOptions, + SQLiteManagerForeignKeyOn, + keywords +} from './types' + +enum AT { + ADD_COLUMN, + DROP_COLUMN, + RENAME_COLUMN, + RENAME_TABLE +} + +interface constraints { + constrain: string + active: boolean +} + +/** + * + * When creating a new istance of the SQLiteManager class, the constructor: + * - will get you in the alter table section if you pass an entire table + * - will get you to the create table section if you just pass the name of the table or you pass nothing + * IMPORTANT: if you don't call sqlite_schema before making any change, then old views, triggers and indexes will be lost + * + * */ +export class SQLiteManager { + private table: SQLiteManagerTable + private create = false + private query = '' + private sql: string[] = [] + + constructor(table?: SQLiteManagerTable) { + if (typeof table === 'undefined') { + this.create = true + this.table = {} as SQLiteManagerTable + } else { + if (table.name) { + this.create = true + if (table.columns) { + this.create = false + this.table = table + } else { + this.table = { name: this.escape(table.name) } as SQLiteManagerTable + } + } else { + this.create = true + this.table = {} as SQLiteManagerTable + } + } + } + + /** Pass to this method the result of this query: SELECT sql FROM sqlite_schema WHERE tbl_name='X'; where X is the name of the table you're using */ + sqlite_schema(sql?: string[]): void { + if (sql) { + this.sql = sql + } else { + if (!this.create) { + throw new Error('Alter table needs SELECT sql FROM sqlite_schema WHERE tbl_name=X; as sql') + } + } + } + + /** If changing name in altertable you need to manually call the queryBuilder() */ + set name(name: string) { + if (this.create) { + this.table.name = this.escape(name) + } else { + this.table.name = this.escape(name) + this.queryBuilder(AT.RENAME_TABLE, { name: this.table.name } as SQLiteManagerColumn) + } + } + + queryBuilder(op?: AT | string, column?: SQLiteManagerColumn, newColumn?: string): string { + let query = '' + + if (this.table.columns) { + if (this.create) { + query += 'CREATE TABLE "' + this.table.name + '" (' + + for (let j = 0; j < this.table.columns.length; j++) { + query += this.queryBuilderColumn(this.table.columns[j]) + if (j < this.table.columns.length - 1) { + query += ', ' + } + } + + query += ');' + } else { + if (typeof op != 'undefined' && column) { + let oldname: string + + switch (op) { + case AT.RENAME_TABLE: + this.query += 'ALTER TABLE "' + this.table.name + '" RENAME TO "' + column.name + '";\n' + break + case AT.ADD_COLUMN: + if ( + column.constraints?.PRIMARY_KEY || + column.constraints?.UNIQUE || + column.constraints?.Default != SQLiteManagerDefault.NULL || + (typeof column.constraints.NOT_NULL != 'undefined' && column.constraints?.Default == SQLiteManagerDefault.NULL) || + (column.constraints?.ForeignKey?.enabled && + column.constraints?.ForeignKey?.table && + column.constraints?.ForeignKey?.column && + column.constraints?.Default != SQLiteManagerDefault.NULL) + ) { + query += this.queryBuilder('', {} as SQLiteManagerColumn) + } else { + this.query += 'ALTER TABLE "' + this.table.name + '" ADD COLUMN ' + this.queryBuilderColumn(column) + ';\n' + } + break + case AT.DROP_COLUMN: + if ( + this.is(column, true) || + column.constraints?.PRIMARY_KEY || + column.constraints?.UNIQUE || + this.sql?.includes(column.name) || + this.is(column, false, true) + // can't check for generated columns and outside this table CHECK constraints + ) { + query += this.queryBuilder('' + AT[op], column) + } else { + this.query += 'ALTER TABLE "' + this.table.name + '" DROP COLUMN "' + column.name + '";\n' + } + break + case AT.RENAME_COLUMN: + if (newColumn) this.query += 'ALTER TABLE "' + this.table.name + '" RENAME COLUMN "' + column.name + '" TO "' + newColumn + '";\n' + break + default: + this.query += '\n\nPRAGMA foreign_keys = OFF;\n' + this.query += 'BEGIN TRANSACTION;\n' + this.create = true + oldname = this.table.name + if (typeof this.findColumn('new_' + this.table.name) == 'undefined') { + this.table.name = 'new_' + this.table.name + } else { + throw new Error('Column new_' + this.table.name + 'already exists') + } + this.query += this.queryBuilder() + this.create = false + this.query += '\nINSERT INTO "' + this.table.name + '" SELECT * FROM "' + oldname + '";\n' + this.query += 'DROP TABLE "' + oldname + '";\n' + this.query += 'ALTER TABLE "' + this.table.name + '" RENAME TO "' + oldname + '";\n' + this.table.name = oldname + + if (this.sql) { + if (op == 'DROP_COLUMN' && column) { + this.sql.forEach(element => { + if (!element.includes(column.name)) { + this.query += element + '\n' + } + }) + } else { + this.sql.forEach(element => { + this.query += element + '\n' + }) + } + } + + this.query += 'PRAGMA foreign_key_check("' + this.table.name + '");\n' + this.query += 'COMMIT;\n' + this.query += 'PRAGMA foreign_keys = ON;\n' + + break + } + } + + query = this.query + } + } + + return query + } + + private queryBuilderColumn(column: SQLiteManagerColumn): string { + let query = '' + query += '"' + column.name + '" ' + SQLiteManagerType[column.type] + + if (column.constraints) { + const constraints: string[] = Object.keys(column.constraints).filter(key => { + if (column.constraints) { + const constcol = column.constraints[key as keyof SQLiteManagerConstraints] + if (typeof constcol == 'boolean' && constcol == true) { + return constcol + } + } + }) + + constraints.forEach(constraint => { + if (!(constraint == 'AUTOINCREMENT' && column.type != SQLiteManagerType.INTEGER)) { + query += ' ' + constraint.replace('_', ' ') + } + }) + + if (column.constraints.Check) { + query += ' CHECK (' + column.constraints.Check + ')' + } + + if (column.constraints.Default) { + query += ' DEFAULT ' + if (typeof column.constraints.Default === 'string') { + query += column.constraints.Default + } else { + query += SQLiteManagerDefault[column.constraints.Default] + } + } + + if (column.constraints.Collate) { + query += ' COLLATE ' + if (typeof column.constraints.Collate === 'string') { + query += column.constraints.Collate + } else { + query += SQLiteManagerCollate[column.constraints.Collate] + } + } + + if (column.constraints.ForeignKey) { + if (column.constraints.ForeignKey.enabled) { + query += ' REFERENCES ' + column.constraints.ForeignKey.table + '(' + column.constraints.ForeignKey.column + ')' + + if (typeof column.constraints.ForeignKey.onDelete !== 'undefined') { + query += ' ON DELETE ' + SQLiteManagerForeignKeyOn[column.constraints.ForeignKey.onDelete].replace('_', ' ') + } + + if (typeof column.constraints.ForeignKey.onUpdate !== 'undefined') { + query += ' ON UPDATE ' + SQLiteManagerForeignKeyOn[column.constraints.ForeignKey.onUpdate].replace('_', ' ') + } + + if (column.constraints.ForeignKey.match) { + query += ' MATCH ' + column.constraints.ForeignKey.match + } + + if (typeof column.constraints.ForeignKey.options !== 'undefined') { + query += ' ' + SQLiteManagerForeignKeyOptions[column.constraints.ForeignKey.options].replace('_', ' ') + } + } + } + } + return query + } + + /** + * column: the SQLiteManagerColumn you want to add to the table + * sql[]: SELECT sql FROM sqlite_schema WHERE tbl_name='X'; where X is the name of the table you're using + */ + addColumn(column: SQLiteManagerColumn, sql?: string[]): string { + column.name = this.escape(column.name) + if (this.table.columns) { + if (typeof this.findColumn(column.name) == 'undefined') { + this.table.columns.push(column) + } else { + throw new Error('Column already exists') + } + } else { + this.table.columns = [column] + } + + this.sqlite_schema(sql) + return this.queryBuilder(AT.ADD_COLUMN, column) + } + + /** + * name: name of the column you want to delete + * sql[]: SELECT sql FROM sqlite_schema WHERE tbl_name='X'; where X is the name of the table you're using + */ + deleteColumn(name: string, sql?: string[]): string { + let query = '' + const i = this.findColumn(name) + + if (this.table.columns && typeof i != 'undefined') { + if (this.create) { + this.table.columns.splice(i, 1) + query = this.queryBuilder(AT.DROP_COLUMN, this.table.columns[i]) + } else { + query = this.queryBuilder(AT.DROP_COLUMN, this.table.columns[i]) + this.table.columns.splice(i, 1) + } + } + + this.sqlite_schema(sql) + return query + } + + renameColumn(oldColumnName: string, newColumnName: string): string { + const i = this.findColumn(oldColumnName) + newColumnName = this.escape(newColumnName) + let query = '' + + if (typeof i != 'undefined' && this.table.columns) { + if (typeof this.findColumn(newColumnName) == 'undefined') { + this.table.columns[i].name = newColumnName + } else { + throw new Error('Column already exists') + } + query = this.queryBuilder(AT.RENAME_COLUMN, { name: oldColumnName } as SQLiteManagerColumn, newColumnName) + } + + return query + } + + /** + * name: name of the column you want to change the type of + * type: the new type you want to give to the column + * sql[]: SELECT sql FROM sqlite_schema WHERE tbl_name='X'; where X is the name of the table you're using + */ + changeColumnType(name: string, type: SQLiteManagerType, sql?: string[]): string { + return this.generalFun(name, (column: SQLiteManagerColumn) => (column.type = type), sql) + } + + /** + * name: name of the column you want to change constraints of + * constraits: edited constraints you get from getConstraints() + * sql[]: SELECT sql FROM sqlite_schema WHERE tbl_name='X'; where X is the name of the table you're using + */ + changeColumnConstraints(name: string, constraints: SQLiteManagerConstraints, sql?: string[]): string { + return this.generalFun(name, (column: SQLiteManagerColumn) => (column.constraints = { ...column.constraints, ...constraints }), sql) + } + + /** name: name of the column OR name of the type you want to get the compatible constraints of */ + getCompatibleConstraints(name: string | SQLiteManagerType): constraints[] { + const constraints: constraints[] = [] + let constrain: constraints + + const cns = ['PRIMARY_KEY', 'AUTOINCREMENT', 'NOT_NULL', 'UNIQUE', 'Check', 'Default', 'Collate', 'ForeignKey'] + + this.create = true + + if (typeof name == 'string') { + this.generalFun(name, (column: SQLiteManagerColumn) => + cns.forEach(key => { + constrain = { constrain: key, active: true } + if (column.type != SQLiteManagerType.INTEGER && key == 'AUTOINCREMENT') { + constrain.active = false + } + constraints.push(constrain) + }) + ) + } else { + cns.forEach(key => { + constrain = { constrain: key, active: true } + if (name != SQLiteManagerType.INTEGER && key == 'AUTOINCREMENT') { + constrain.active = false + } + constraints.push(constrain) + }) + } + + this.create = false + + return constraints + } + + private generalFun(name: string, fun: (column: SQLiteManagerColumn) => void, sql?: string[], qb1?: any, qb2?: SQLiteManagerColumn): string { + const i = this.findColumn(name) + + if (typeof i != 'undefined' && this.table.columns) { + /* console.log('IN: ') //DEBUG + console.log(this.table.columns[i]) */ + fun(this.table.columns[i]) + /* console.log('OUT: ') //DEBUG + console.log(this.table.columns[i]) */ + } + + this.sqlite_schema(sql) + + return this.queryBuilder(qb1 ? qb1 : '', qb2 ? qb2 : ({} as SQLiteManagerColumn)) + } + + private findColumn(name: string): number | undefined { + if (this.table.columns) { + const i = this.table.columns.findIndex(column => column.name === name) + if (i > -1) { + return i + } + } + } + + private is(column: SQLiteManagerColumn, referenced?: boolean, checked?: boolean): boolean { + if (this.table.columns) { + for (let i = 0; i < this.table.columns.length; i++) { + if (referenced && this.table.columns[i].constraints?.ForeignKey?.column == column.name) { + return true + } + if (checked && this.table.columns[i].constraints?.Check?.includes(column.name)) { + return true + } + } + } + return false + } + + private escape(name: string): string { + keywords.forEach(keyword => { + if (name.toUpperCase() == keyword) { + throw new Error("You can't use a SQLite keyword as a name") + } + }) + + name = name.replace(/'/g, "''") + name = name.replace(/"/g, '""') + + return name + } +} diff --git a/src/types.ts b/src/types.ts index 040d57a..bf8c624 100644 --- a/src/types.ts +++ b/src/types.ts @@ -129,3 +129,303 @@ export enum SQLiteCloudArrayType { ARRAY_TYPE_SQLITE_STATUS = 50 // used in sqlite_status } + +/** SQLite Datatypes*/ +export enum SQLiteManagerType { + NULL, + INTEGER, + REAL, + TEXT, + BLOB +} + +/** SQLite Default clause */ +export enum SQLiteManagerDefault { + NULL, + CURRENT_TIME, + CURRENT_DATE, + CURRENT_TIMESTAMP +} + +/** SQLite Collate clause */ +export enum SQLiteManagerCollate { + BINARY, + NOCASE, + RTRIM +} + +/** SQLite column foreign key options */ +export enum SQLiteManagerForeignKeyOptions { + NONE, + DEFERRABLE, + DEFERRABLE_INITIALLY_DEFERRED, + DEFERRABLE_INITIALLY_IMMEDIATE, + NOT_DEFERRABLE, + NOT_DEFERRABLE_INITIALLY_DEFERRED, + NOT_DEFERRABLE_INITIALLY_IMMEDIATE +} + +/** SQLite ON DELETE and ON UPDATE Actions */ +export enum SQLiteManagerForeignKeyOn { + NO_ACTION, + RESTRICT, + SET_NULL, + SET_DEFAULT, + CASCADE +} + +/** SQLite foreign key */ +export class SQLiteManagerForeignKey { + enabled = false + table = '' + column = '' + options: SQLiteManagerForeignKeyOptions = SQLiteManagerForeignKeyOptions.NONE + onDelete: SQLiteManagerForeignKeyOn = SQLiteManagerForeignKeyOn.NO_ACTION + onUpdate: SQLiteManagerForeignKeyOn = SQLiteManagerForeignKeyOn.NO_ACTION + match = '' + + constructor( + enabled: boolean, + table: string, + column: string, + options?: SQLiteManagerForeignKeyOptions, + onDelete?: SQLiteManagerForeignKeyOn, + onUpdate?: SQLiteManagerForeignKeyOn, + match?: string + ) { + if (enabled) { + this.enable(table, column, options, onDelete, onUpdate, match) + } else { + this.disable() + } + } + + /** By disabling foreign key you delete references */ + disable(): void { + this.enabled = false + this.table = '' + this.column = '' + this.options = SQLiteManagerForeignKeyOptions.NONE + this.onDelete = SQLiteManagerForeignKeyOn.NO_ACTION + this.onUpdate = SQLiteManagerForeignKeyOn.NO_ACTION + this.match = '' + } + + enable( + table: string, + column: string, + options?: SQLiteManagerForeignKeyOptions, + onDelete?: SQLiteManagerForeignKeyOn, + onUpdate?: SQLiteManagerForeignKeyOn, + match?: string + ): void { + this.enabled = true + this.table = table + this.column = column + + if (options) { + this.options = options + } + + if (onDelete) { + this.onDelete = onDelete + } + + if (onUpdate) { + this.onUpdate = onUpdate + } + + if (match) { + this.match = match + } + } +} + +/** SQLite column constraints */ +export interface SQLiteManagerConstraints { + PRIMARY_KEY?: boolean + AUTOINCREMENT?: boolean + NOT_NULL?: boolean + UNIQUE?: boolean + Check?: string + + /** You can leave it undefined or pass: SQLiteManagerDefault.NULL, SQLiteManagerDefault.CURRENT_TIME, etc. */ + Default?: SQLiteManagerDefault | string + + /** You can leave it undefined or pass: SQLiteManagerCollate.BINARY, SQLiteManagerCollate.NOCASE, etc. */ + Collate?: SQLiteManagerCollate | string + + /** You should pass: new SQLiteManagerForeignKey(), or you can leave it undefined */ + ForeignKey?: SQLiteManagerForeignKey +} + +/** SQLite column interface */ +export interface SQLiteManagerColumn { + /** Column name */ + name: string + + /** Data type */ + type: SQLiteManagerType + + /** Constraints */ + constraints?: SQLiteManagerConstraints +} + +/** SQLite table interface */ +export interface SQLiteManagerTable { + /** Name of the table */ + name: string + + /** Columns */ + columns?: SQLiteManagerColumn[] +} + +export const keywords = [ + 'ABORT', + 'ACTION', + 'ADD', + 'AFTER', + 'ALL', + 'ALTER', + 'ALWAYS', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ATTACH', + 'AUTOINCREMENT', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BY', + 'CASCADE', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'CONFLICT', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'DATABASE', + 'DEFAULT', + 'DEFERRABLE', + 'DEFERRED', + 'DELETE', + 'DESC', + 'DETACH', + 'DISTINCT', + 'DO', + 'DROP', + 'EACH', + 'ELSE', + 'END', + 'ESCAPE', + 'EXCEPT', + 'EXCLUDE', + 'EXCLUSIVE', + 'EXISTS', + 'EXPLAIN', + 'FAIL', + 'FILTER', + 'FIRST', + 'FOLLOWING', + 'FOR', + 'FOREIGN', + 'FROM', + 'FULL', + 'GENERATED', + 'GLOB', + 'GROUP', + 'GROUPS', + 'HAVING', + 'IF', + 'IGNORE', + 'IMMEDIATE', + 'IN', + 'INDEX', + 'INDEXED', + 'INITIALLY', + 'INNER', + 'INSERT', + 'INSTEAD', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'KEY', + 'LAST', + 'LEFT', + 'LIKE', + 'LIMIT', + 'MATCH', + 'MATERIALIZED', + 'NATURAL', + 'NO', + 'NOT', + 'NOTHING', + 'NOTNULL', + 'NULL', + 'NULLS', + 'OF', + 'OFFSET', + 'ON', + 'OR', + 'ORDER', + 'OTHERS', + 'OUTER', + 'OVER', + 'PARTITION', + 'PLAN', + 'PRAGMA', + 'PRECEDING', + 'PRIMARY', + 'QUERY', + 'RAISE', + 'RANGE', + 'RECURSIVE', + 'REFERENCES', + 'REGEXP', + 'REINDEX', + 'RELEASE', + 'RENAME', + 'REPLACE', + 'RESTRICT', + 'RETURNING', + 'RIGHT', + 'ROLLBACK', + 'ROW', + 'ROWS', + 'SAVEPOINT', + 'SELECT', + 'SET', + 'TABLE', + 'TEMP', + 'TEMPORARY', + 'THEN', + 'TIES', + 'TO', + 'TRANSACTION', + 'TRIGGER', + 'UNBOUNDED', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'USING', + 'VACUUM', + 'VALUES', + 'VIEW', + 'VIRTUAL', + 'WHEN', + 'WHERE', + 'WINDOW', + 'WITH', + 'WITHOUT' +] diff --git a/test/assets/manager-test-tables.ts b/test/assets/manager-test-tables.ts new file mode 100644 index 0000000..d1c6f31 --- /dev/null +++ b/test/assets/manager-test-tables.ts @@ -0,0 +1,98 @@ +/* eslint-disable prettier/prettier */ + +import * as types from '../../src/types' + +export const testTable = { + name: 'myTable', + columns: [ + { + name: 'column1', + type: types.SQLiteManagerType.INTEGER, + constraints: { + PRIMARY_KEY: true, + AUTOINCREMENT: true, + NOT_NULL: true, + UNIQUE: true + } + }, + { + name: 'column2', + type: types.SQLiteManagerType.REAL, + constraints: { + NOT_NULL: true, + UNIQUE: true + } + }, + { + name: 'column3', + type: types.SQLiteManagerType.INTEGER, + constraints: { + NOT_NULL: true, + UNIQUE: true + } + }, + { + name: 'column4', + type: types.SQLiteManagerType.INTEGER, + constraints: { + NOT_NULL: true, + UNIQUE: true + } + } + ] +} + +export const testTable2 = { + name: 'myTable', + columns: [ + { + name: 'test1', + type: types.SQLiteManagerType.REAL, + constraints: { + PRIMARY_KEY: false, + AUTOINCREMENT: true, + UNIQUE: false, + Default: types.SQLiteManagerDefault.NULL + } + }, + { + name: 'test2', + type: types.SQLiteManagerType.BLOB, + constraints: { + NOT_NULL: true, + UNIQUE: true + } + }, + { + name: 'test3', + type: types.SQLiteManagerType.TEXT, + constraints: { + NOT_NULL: false, + UNIQUE: true + } + }, + { + name: 'test4', + type: types.SQLiteManagerType.INTEGER, + constraints: { + NOT_NULL: true, + UNIQUE: true, + CHECK: 'test4 > 0', + ForeignKey: new types.SQLiteManagerForeignKey( + true, + 'myTable2', + 'test1', + types.SQLiteManagerForeignKeyOptions.NONE, + types.SQLiteManagerForeignKeyOn.CASCADE, + types.SQLiteManagerForeignKeyOn.CASCADE + ) + } + } + ] +} + +export const sqlite_schema = [ + 'CREATE VIEW "myView" AS SELECT "myTable"."column1", "myTable"."column2", "myTable"."column3", "myTable"."column4" FROM "myTable";', + 'CREATE TRIGGER "myTrigger" AFTER INSERT ON "myTable" BEGIN INSERT INTO "myTable2" ("test1", "test2", "test3", "test4") VALUES (new."column1", new."column2", new."column3", new."column4"); END;', + 'CREATE INDEX "myIndex" ON "myTable" ("column1", "column2", "column3", "column4");' +] diff --git a/test/manager.test.ts b/test/manager.test.ts new file mode 100644 index 0000000..9c799aa --- /dev/null +++ b/test/manager.test.ts @@ -0,0 +1,74 @@ +/* eslint-disable prettier/prettier */ +import { SQLiteManager } from '../src/manager' +import { SQLiteManagerType } from '../src/types' +import { testTable, testTable2, sqlite_schema } from './assets/manager-test-tables' + +describe('Create a table', () => { + let manager: SQLiteManager + + it('tests create table', () => { + manager = new SQLiteManager({ name: testTable.name }) + + expect(manager.addColumn(testTable.columns[0])).toContain( + 'CREATE TABLE "' + testTable.name + '" ("' + testTable.columns[0].name + '" ' + SQLiteManagerType[testTable.columns[0].type] + ) + + manager.addColumn(JSON.parse(JSON.stringify(testTable.columns[1]))) + expect(manager.deleteColumn(testTable.columns[0].name)).not.toContain(testTable.columns[0].name) + + const risRen: string = manager.renameColumn(testTable.columns[1].name, testTable.columns[2].name) + + expect(risRen).not.toContain(testTable.columns[1].name) + expect(risRen).toContain(testTable.columns[2].name) + + const risCh: string = manager.changeColumnType(testTable.columns[2].name, SQLiteManagerType.TEXT) + + expect(risCh).toContain(SQLiteManagerType[SQLiteManagerType.TEXT]) + expect(risCh).not.toContain(SQLiteManagerType[SQLiteManagerType.INTEGER]) + + const risCnstr: string = manager.changeColumnConstraints(testTable.columns[2].name, { NOT_NULL: false }) + + expect(risCnstr).toContain('UNIQUE') + }) + + it('tests alter table', () => { + manager = new SQLiteManager(testTable) + + const addColumn: string = manager.addColumn(testTable2.columns[0], sqlite_schema) + + expect(addColumn).toContain('ALTER TABLE') + expect(addColumn).toContain(testTable.name) + expect(addColumn).toContain('ADD COLUMN') + expect(addColumn).toContain(testTable2.columns[0].name) + expect(addColumn).toContain(SQLiteManagerType[testTable2.columns[0].type]) + + manager.addColumn(JSON.parse(JSON.stringify(testTable2.columns[1])), sqlite_schema) + expect(manager.deleteColumn(testTable2.columns[0].name, sqlite_schema)).toContain( + 'ALTER TABLE "' + testTable.name + '" DROP COLUMN "' + testTable2.columns[0].name + '";' + ) + + const risRen: string = manager.renameColumn(testTable.columns[1].name, testTable2.columns[2].name) + + expect(risRen).toContain(testTable.columns[1].name) + expect(risRen).toContain(testTable2.columns[2].name) + + manager.name = testTable2.name + const renTable: string = manager.queryBuilder() + + expect(renTable).toContain('ALTER TABLE "' + testTable.name + '" RENAME TO "' + testTable2.name + '";') + + const risCh: string = manager.changeColumnType(testTable.columns[2].name, SQLiteManagerType.TEXT, sqlite_schema) + + expect(risCh).toContain('PRAGMA foreign_keys = OFF;') + expect(risCh).toContain('BEGIN TRANSACTION;') + expect(risCh).toContain('ALTER TABLE "new_' + testTable.name + '" RENAME TO "' + testTable.name + '";') + expect(risCh).toContain('CREATE TABLE "new_' + testTable.name + '" ("') + expect(risCh).toContain('"' + testTable.columns[2].name + '" TEXT') + expect(risCh).toContain('DROP TABLE "' + testTable.name) + expect(risCh).toContain('COMMIT;') + expect(risCh).toContain('PRAGMA foreign_keys = ON;') + + const risCnstr: string = manager.changeColumnConstraints(testTable.columns[2].name, testTable2.columns[2].constraints, sqlite_schema) + expect(risCnstr).toContain('"' + testTable.columns[2].name + '" TEXT UNIQUE') + }) +})