Skip to content

Commit

Permalink
alter query implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
shivajivarma committed Dec 20, 2023
1 parent ebe2c35 commit 6eafa6b
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 78 deletions.
5 changes: 5 additions & 0 deletions .run/Deno_ create.test.ts.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deno: create.test.ts" type="DenoConfigurationType" nameIsGenerated="true" inputPath="$PROJECT_DIR$/test/unit/schema/create.test.ts" programParameters="test -A" workingDirectory="$PROJECT_DIR$/test/unit/schema">
<method v="2" />
</configuration>
</component>
28 changes: 14 additions & 14 deletions src/ORM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import TimeDataType from "./data-types/types/TimeDataType.ts";
import CharDataType from "./data-types/types/CharDataType.ts";
import TableSchema from "./table/TableSchema.ts";
import { UUIDUtils } from "./utils.ts";
import TableNameUtils from "./table/TableNameUtils.ts";

/**
* JUSTAOS's ORM (Object Document Mapper) is built for Deno and provides transparent persistence for JavaScript objects to Postgres database.
Expand Down Expand Up @@ -46,12 +47,12 @@ export default class ORM {
readonly #logger = Logger.createLogger({ label: ORM.name });
readonly #config: DatabaseConfiguration;
readonly #dataTypeRegistry: Registry<DataType> = new Registry<DataType>(
function (dataType) {
function(dataType) {
return dataType.getName();
}
);
readonly #tableDefinitionRegistry: Registry<TableDefinition> =
new Registry<TableDefinition>(function (tableDefinition) {
new Registry<TableDefinition>(function(tableDefinition) {
return `${tableDefinition.schema}.${tableDefinition.name}`;
});
readonly #schemaRegistry: Map<string, null> = new Map<string, null>();
Expand All @@ -63,6 +64,14 @@ export default class ORM {
this.#config = config;
}

static generateRecordId(): UUID {
return UUIDUtils.generateId();
}

static isValidRecordId(id: UUID): boolean {
return UUIDUtils.isValidId(id);
}

async connect(createDatabaseIfNotExists?: boolean): Promise<ORMConnection> {
try {
const conn = new ORMConnection(
Expand Down Expand Up @@ -98,14 +107,13 @@ export default class ORM {
}

isTableDefined(tableName: string): boolean {
return this.#tableDefinitionRegistry.has(TableSchema.getSchemaAndTableName(tableName));
return this.#tableDefinitionRegistry.has(TableNameUtils.getFullFormTableName(tableName));
}

getTableSchema(tableName: string): TableSchema | undefined {
tableName = TableNameUtils.getFullFormTableName(tableName);
const tableDefinition: TableDefinition | undefined =
this.#tableDefinitionRegistry.get(
TableSchema.getSchemaAndTableName(tableName)
);
this.#tableDefinitionRegistry.get(tableName);
if (tableDefinition) {
return new TableSchema(
tableDefinition,
Expand All @@ -115,14 +123,6 @@ export default class ORM {
}
}

static generateRecordId(): UUID {
return UUIDUtils.generateId();
}

static isValidRecordId(id: UUID): boolean {
return UUIDUtils.isValidId(id);
}

addDataType(dataType: DataType): void {
this.#dataTypeRegistry.add(dataType);
}
Expand Down
51 changes: 31 additions & 20 deletions src/ORMConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DatabaseErrorCode, ORMError } from "./errors/ORMError.ts";

import Query from "./query/Query.ts";
import ORM from "./ORM.ts";
import TableNameUtils from "./table/TableNameUtils.ts";

export default class ORMConnection {
readonly #orm: ORM;
Expand Down Expand Up @@ -68,6 +69,10 @@ export default class ORMConnection {
return result;
}

deregisterTable(tableName: string) {
this.#tableDefinitionRegistry.delete(TableNameUtils.getFullFormTableName(tableName));
}

async defineTable(tableDefinitionRaw: TableDefinitionRaw | Function) {
if (typeof tableDefinitionRaw === "function") {
// @ts-ignore
Expand Down Expand Up @@ -108,39 +113,45 @@ export default class ORMConnection {
LIMIT 1);`;

if (!tableExists) {
const query = new Query(this.#conn.getNativeConnection());
query.create(tableSchema.getFullName());
const createQuery = new Query(this.#conn.getNativeConnection());
createQuery.create(tableSchema.getTableNameWithSchema());
for (const column of tableSchema.getOwnColumnSchemas()) {
const columnDefinition = column.getDefinition();
query.addColumn({
createQuery.addColumn({
name: column.getName(),
data_type: column.getColumnType().getNativeType(),
not_null: column.isNotNull(),
unique: column.isUnique(),
foreign_key: columnDefinition.foreign_key
});
}
query.inherits(tableSchema.getInherits());
this.#logger.info(`Create Query -> \n ${query.getSQLQuery()}`);
await reserved.unsafe(query.getSQLQuery());
const inherits = tableSchema.getInherits();
if (inherits)
createQuery.inherits(TableNameUtils.getFullFormTableName(inherits));
this.#logger.info(`Create Query -> \n ${createQuery.getSQLQuery()}`);
await createQuery.execute();
} else {
const columns =
await reserved`SELECT column_name FROM information_schema.columns WHERE table_schema = ${tableSchema.getSchemaName()} AND table_name = ${tableSchema.getName()};`;
const columnNames = columns.map((column: { column_name: string }) => column.column_name);
const newColumns = tableSchema
.getOwnColumnSchemas()
.filter((column) => !columnNames.includes(column.getName()));
const existingColumnNames = columns.map((column: { column_name: string }) => column.column_name);
const columnSchemas = tableSchema.getOwnColumnSchemas();
// Create new columns
if (newColumns.length > 0) {
const query = `ALTER TABLE ${tableSchema.getFullName()} \n\t${newColumns
.map((column) => {
return `ADD COLUMN ${column.getName()} ${column
.getColumnType()
.getNativeType()}`;
})
.join(",\n\t")}\n`;
this.#logger.info(`Alter Query -> \n ${query}`);
await reserved.unsafe(query);
if (columnSchemas.length > existingColumnNames.length) {
const alterQuery = new Query(this.#conn.getNativeConnection());
alterQuery.alter(tableSchema.getTableNameWithSchema());
for (const column of tableSchema.getOwnColumnSchemas()) {
const columnDefinition = column.getDefinition();
if (!existingColumnNames.includes(column.getName()))
alterQuery.addColumn({
name: column.getName(),
data_type: column.getColumnType().getNativeType(),
not_null: column.isNotNull(),
unique: column.isUnique(),
foreign_key: columnDefinition.foreign_key
});
}
this.#logger.info(`Alter Query -> \n ${alterQuery.getSQLQuery()}`);
await alterQuery.execute();
}
}
} finally {
Expand Down
71 changes: 71 additions & 0 deletions src/query/AlterQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
type ColumnDefinitionNative = {
name: string;
data_type: string;
not_null?: boolean;
default?: unknown;
unique?: boolean;
foreign_key?: {
table: string;
column: string;
on_delete?: "NO ACTION" | "CASCADE" | "SET NULL" | "SET DEFAULT";
};
};

export type { ColumnDefinitionNative };

export class AlterQuery {
readonly #tableName: string;

#addColumns: ColumnDefinitionNative[] = [];

#inherits?: string;

constructor(nameWithSchema: string) {
this.#tableName = nameWithSchema;
}

addColumn(column: ColumnDefinitionNative): AlterQuery {
column = { not_null: false, unique: false, ...column };
this.#addColumns.push(column);
return this;
}


buildQuery(): string {
let query = `ALTER TABLE ${this.#tableName} \n\t`;
query += this.#prepareColumns();
return query;
}

#prepareColumn(column: ColumnDefinitionNative): string {
let query = `ADD COLUMN "${column.name}" ${column.data_type}`;
if (column.foreign_key) {
const onDelete = column.foreign_key.on_delete
? ` ON DELETE ${column.foreign_key.on_delete}`
: "";
query += ` REFERENCES ${column.foreign_key.table} ("${column.foreign_key.column}") ${onDelete}`;
} else {
if (column.not_null) query += ` NOT NULL`;
if (column.unique) query += ` UNIQUE`;
}
return query;
}

#prepareColumns(): string {
return this.#addColumns
.map((column) => {
return this.#prepareColumn(column);
})
.join("\n\t");
}

#prepareInherits(): string {
if (!this.#inherits) return "";
return ` INHERITS (${this.#inherits})`;
}

#preparePrimaryKey(): string {
if (this.#inherits) return "";
return `, PRIMARY KEY (id)`;
}
}
22 changes: 11 additions & 11 deletions src/query/CreateQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,23 @@ export class CreateQuery {
return this;
}

buildQuery(): string {
let query = `CREATE TABLE ${this.#tableName}`;
query += ` (`;
query += this.#prepareColumns();
query += this.#preparePrimaryKey();
query += `)`;
query += this.#prepareInherits();
return query;
}

#prepareColumn(column: ColumnDefinitionNative): string {
let query = `"${column.name}" ${column.data_type}`;
if (column.foreign_key) {
const onDelete = column.foreign_key.on_delete
? ` ON DELETE ${column.foreign_key.on_delete}`
: "";
query += ` REFERENCES ${column.foreign_key.table} (${column.foreign_key.column}) ${onDelete}`;
query += ` REFERENCES ${column.foreign_key.table} ("${column.foreign_key.column}") ${onDelete}`;
} else {
if (column.not_null) query += ` NOT NULL`;
if (column.unique) query += ` UNIQUE`;
Expand All @@ -66,14 +76,4 @@ export class CreateQuery {
if (this.#inherits) return "";
return `, PRIMARY KEY (id)`;
}

buildQuery(): string {
let query = `CREATE TABLE ${this.#tableName}`;
query += ` (`;
query += this.#prepareColumns();
query += this.#preparePrimaryKey();
query += `)`;
query += this.#prepareInherits();
return query;
}
}
11 changes: 9 additions & 2 deletions src/query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import DeleteQuery from "./DeleteQuery.ts";
import { ColumnDefinitionNative, CreateQuery } from "./CreateQuery.ts";
import InsertQuery from "./InsertQuery.ts";
import UpdateQuery from "./UpdateQuery.ts";
import { AlterQuery } from "./AlterQuery.ts";

export default class Query {
readonly #sql: any;

#query?: SelectQuery | DeleteQuery | CreateQuery | InsertQuery | UpdateQuery;
#query?: SelectQuery | DeleteQuery | CreateQuery | InsertQuery | UpdateQuery | AlterQuery;

constructor(sql: any) {
this.#sql = sql;
Expand All @@ -31,6 +32,11 @@ export default class Query {
return this;
}

alter(nameWithSchema: string): Query {
this.#query = new AlterQuery(nameWithSchema);
return this;
}

addColumn(column: ColumnDefinitionNative): Query {
const query = <CreateQuery>this.#getQuery();
query.addColumn(column);
Expand Down Expand Up @@ -181,7 +187,8 @@ export default class Query {
| DeleteQuery
| CreateQuery
| InsertQuery
| UpdateQuery {
| UpdateQuery
| AlterQuery {
if (!this.#query)
throw new ORMError(
DatabaseErrorCode.GENERIC_ERROR,
Expand Down
12 changes: 5 additions & 7 deletions src/record/Record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,13 @@ export default class Record {
}

async insert(): Promise<Record> {
const tableSchema = this.#table.getTableSchema();
let [record] = await this.#table.intercept("INSERT", "BEFORE", [this]);

const recordJson = record.toJSON();
await this.#validateRecord(recordJson);
this.#queryBuilder.insert();
this.#queryBuilder.into(tableSchema.getFullName());
this.#queryBuilder.columns(tableSchema.getColumnNames());
this.#queryBuilder.into(this.#table.getTableNameWithSchema());
this.#queryBuilder.columns(this.#table.getColumnNames());
this.#queryBuilder.values([recordJson]);
this.#queryBuilder.returning("*");

Expand Down Expand Up @@ -121,14 +120,13 @@ export default class Record {
}

async update(): Promise<Record> {
const tableSchema = this.#table.getTableSchema();
let [record] = await this.#table.intercept("UPDATE", "BEFORE", [this]);

const recordJson = record.toJSON();
await this.#validateRecord(recordJson);
this.#queryBuilder.update();
this.#queryBuilder.into(tableSchema.getFullName());
this.#queryBuilder.columns(tableSchema.getColumnNames().filter((col) => {
this.#queryBuilder.into(this.#table.getTableNameWithSchema());
this.#queryBuilder.columns(this.#table.getColumnNames().filter((col) => {
return col !== "id";
}));
this.#queryBuilder.where("id", record.getID());
Expand Down Expand Up @@ -168,7 +166,7 @@ export default class Record {
const [record] = await this.#table.intercept("DELETE", "BEFORE", [this]);

this.#queryBuilder.delete();
this.#queryBuilder.from(tableSchema.getFullName());
this.#queryBuilder.from(this.#table.getTableNameWithSchema());
this.#queryBuilder.where("id", record.getID());

this.#logger.info(`[Query] ${this.#queryBuilder.getSQLQuery()}`);
Expand Down
18 changes: 13 additions & 5 deletions src/table/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,23 @@ export default class Table {
}

getName(): string {
return this.getTableSchema().getName();
return this.#schema.getName();
}

getSchemaName(): string {
return this.#schema.getSchemaName();
}

getTableNameWithSchema(): string {
return this.#schema.getTableNameWithSchema();
}

getTableSchema(): TableSchema {
return this.#schema;
}

getSchemaName(): string {
return this.getTableSchema().getSchemaName();
getColumnNames(): string[] {
return this.#schema.getColumnNames();
}

createNewRecord(): Record {
Expand All @@ -55,7 +63,7 @@ export default class Table {
select(...args: any[]): Table {
this.#queryBuilder = this.#queryBuilder.getInstance();
this.#queryBuilder.select.apply(this.#queryBuilder, args);
this.#queryBuilder.from(this.getTableSchema().getFullName());
this.#queryBuilder.from(this.#schema.getTableNameWithSchema());
return this;
}

Expand Down Expand Up @@ -83,7 +91,7 @@ export default class Table {
if (!this.#queryBuilder.getType()) {
this.#queryBuilder = this.#queryBuilder.getInstance();
this.#queryBuilder.select();
this.#queryBuilder.from(this.getTableSchema().getFullName());
this.#queryBuilder.from(this.getTableNameWithSchema());
}
if (this.#queryBuilder.getType() !== "select") {
throw new Error("Count can only be called on select query");
Expand Down
Loading

0 comments on commit 6eafa6b

Please sign in to comment.