Skip to content

Commit

Permalink
Add CTAS and CVAS operations
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubknejzlik committed Feb 8, 2024
1 parent 762ab9b commit 159db68
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 23 deletions.
49 changes: 49 additions & 0 deletions src/CreateTableAsSelect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CreateTableAsSelect } from "./CreateTableAsSelect";
import { Cond } from "./Condition";
import { Q } from "./Query";
import { MySQLFlavor } from "./flavors/mysql";
import { MetadataOperationType } from "./interfaces";

describe("CreateTableAsSelect", () => {
const initialSelectQuery = Q.select()
.from("users", "u")
.where(Cond.equal("u.id", 1));
const tableName = "new_users_table";

it("should clone itself correctly", () => {
const ctas = Q.createTableAs(tableName, initialSelectQuery);
const clone = ctas.clone();

expect(clone).not.toBe(ctas);
expect(clone.toSQL(new MySQLFlavor())).toBe(ctas.toSQL(new MySQLFlavor()));
});

it("should generate the correct SQL", () => {
const ctas = Q.createTableAs(tableName, initialSelectQuery);
const expectedSQL = `CREATE TABLE \`${tableName}\` AS SELECT * FROM \`users\` AS \`u\` WHERE \`u\`.\`id\` = 1`;
expect(ctas.toSQL(new MySQLFlavor())).toBe(expectedSQL);
});

it("should serialize and deserialize correctly", () => {
const ctas = Q.createTableAs(tableName, initialSelectQuery);
const serialized = ctas.serialize();
const deserialized = Q.deserialize(serialized);

expect(deserialized).toEqual(ctas);
expect(deserialized.toSQL(new MySQLFlavor())).toBe(
ctas.toSQL(new MySQLFlavor())
);
});

it("should fetch table names correctly", () => {
const ctas = Q.createTableAs(tableName, initialSelectQuery);
expect(ctas.getTableNames()).toEqual([tableName, "users"]);
});

it("should return correct operation type", () => {
const ctas = Q.createTableAs(tableName, initialSelectQuery);
expect(ctas.getOperationType()).toEqual(
MetadataOperationType.CREATE_TABLE_AS
);
});
});
50 changes: 50 additions & 0 deletions src/CreateTableAsSelect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Condition, ConditionValue } from "./Condition";
import { ISQLFlavor } from "./Flavor";
import { Q, SelectQuery, Table } from "./Query";
import { MySQLFlavor } from "./flavors/mysql";
import {
IMetadata,
ISequelizable,
ISerializable,
MetadataOperationType,
} from "./interfaces";

export class CreateTableAsSelect
implements ISerializable, ISequelizable, IMetadata
{
constructor(private _tableName: string, private _select: SelectQuery) {}

public clone(): this {
return new (this.constructor as any)(this._tableName, this._select.clone());
}

getOperationType(): MetadataOperationType {
return MetadataOperationType.CREATE_TABLE_AS;
}

getTableNames(): string[] {
return [this._tableName, ...this._select.getTableNames()];
}

toSQL(flavor: ISQLFlavor = new MySQLFlavor()): string {
return `CREATE TABLE ${flavor.escapeTable(
this._tableName
)} AS ${this._select.toSQL(flavor)}`;
}

serialize(): string {
return JSON.stringify(this.toJSON());
}

toJSON() {
return {
type: MetadataOperationType.CREATE_TABLE_AS,
select: this._select.toJSON(),
tableName: this._tableName,
};
}

static fromJSON({ tableName, select }: any): CreateTableAsSelect {
return new CreateTableAsSelect(tableName, SelectQuery.fromJSON(select));
}
}
54 changes: 54 additions & 0 deletions src/CreateViewAsSelect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { CreateViewAsSelect } from "./CreateViewAsSelect"; // Adjust the import path as needed
import { Cond } from "./Condition";
import { Q } from "./Query";
import { MySQLFlavor } from "./flavors/mysql";
import { MetadataOperationType } from "./interfaces";

describe("CreateViewAsSelect", () => {
const initialSelectQuery = Q.select()
.from("users", "u")
.where(Cond.equal("u.id", 1));
const viewName = "user_view";

it("should clone itself correctly", () => {
const cvas = Q.createOrReaplaceViewAs(viewName, initialSelectQuery);
const clone = cvas.clone();

expect(clone).not.toBe(cvas);
expect(clone.toSQL(new MySQLFlavor())).toBe(cvas.toSQL(new MySQLFlavor()));
});

it("should generate the correct SQL with OR REPLACE", () => {
const cvas = Q.createOrReaplaceViewAs(viewName, initialSelectQuery);
const expectedSQL = `CREATE OR REPLACE VIEW \`${viewName}\` AS SELECT * FROM \`users\` AS \`u\` WHERE \`u\`.\`id\` = 1`;
expect(cvas.toSQL(new MySQLFlavor())).toBe(expectedSQL);
});

it("should generate the correct SQL without OR REPLACE", () => {
const cvas = Q.createViewAs(viewName, initialSelectQuery);
const expectedSQL = `CREATE VIEW \`${viewName}\` AS SELECT * FROM \`users\` AS \`u\` WHERE \`u\`.\`id\` = 1`;
expect(cvas.toSQL(new MySQLFlavor())).toBe(expectedSQL);
});

it("should serialize and deserialize correctly", () => {
const cvas = Q.createTableAs(viewName, initialSelectQuery);
const serialized = cvas.serialize();
const deserialized = Q.deserialize(serialized);

expect(deserialized.toSQL(new MySQLFlavor())).toEqual(
cvas.toSQL(new MySQLFlavor())
);
});

it("should fetch table names correctly", () => {
const cvas = Q.createViewAs(viewName, initialSelectQuery);
expect(cvas.getTableNames()).toEqual([viewName, "users"]);
});

it("should return correct operation type", () => {
const cvas = Q.createViewAs(viewName, initialSelectQuery);
expect(cvas.getOperationType()).toEqual(
MetadataOperationType.CREATE_VIEW_AS
);
});
});
63 changes: 63 additions & 0 deletions src/CreateViewAsSelect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { SelectQuery } from "./Query";
import { ISQLFlavor } from "./Flavor";
import { MySQLFlavor } from "./flavors/mysql";
import {
IMetadata,
ISequelizable,
ISerializable,
MetadataOperationType,
} from "./interfaces";

export class CreateViewAsSelect
implements ISerializable, ISequelizable, IMetadata
{
constructor(
private _viewName: string,
private _select: SelectQuery,
private _orReplace: boolean = false
) {}

public clone(): this {
return new (this.constructor as any)(
this._viewName,
this._select.clone(),
this._orReplace
);
}

getOperationType(): MetadataOperationType {
return MetadataOperationType.CREATE_VIEW_AS;
}

getTableNames(): string[] {
return [this._viewName, ...this._select.getTableNames()];
}

toSQL(flavor: ISQLFlavor = new MySQLFlavor()): string {
const orReplaceStr = this._orReplace ? "OR REPLACE " : "";
return `CREATE ${orReplaceStr}VIEW ${flavor.escapeTable(
this._viewName
)} AS ${this._select.toSQL(flavor)}`;
}

serialize(): string {
return JSON.stringify(this.toJSON());
}

toJSON() {
return {
type: MetadataOperationType.CREATE_VIEW_AS,
select: this._select.toJSON(),
viewName: this._viewName,
orReplace: this._orReplace,
};
}

static fromJSON({ viewName, select, orReplace }: any): CreateViewAsSelect {
return new CreateViewAsSelect(
viewName,
SelectQuery.fromJSON(select),
orReplace
);
}
}
13 changes: 10 additions & 3 deletions src/Mutation-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Q } from "./Query";
import { MetadataOperationType } from "./interfaces";

describe("Query builder metadata", () => {
it("should return list of tables in insert query", () => {
Expand All @@ -17,8 +18,14 @@ describe("Query builder metadata", () => {
expect(tables).toEqual(["table"]);
});
it("should get operation type", () => {
expect(Q.insert("table").getOperationType()).toEqual("insert");
expect(Q.update("table").getOperationType()).toEqual("update");
expect(Q.delete("table").getOperationType()).toEqual("delete");
expect(Q.insert("table").getOperationType()).toEqual(
MetadataOperationType.INSERT
);
expect(Q.update("table").getOperationType()).toEqual(
MetadataOperationType.UPDATE
);
expect(Q.delete("table").getOperationType()).toEqual(
MetadataOperationType.DELETE
);
});
});
20 changes: 10 additions & 10 deletions src/Mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export class MutationBase {

static deserialize(json: string) {
const parsed = JSON.parse(json);
switch (parsed.type) {
case "DeleteMutation":
switch (parsed.type as MetadataOperationType) {
case MetadataOperationType.DELETE:
return DeleteMutation.fromJSON(parsed);
case "InsertMutation":
case MetadataOperationType.INSERT:
return InsertMutation.fromJSON(parsed);
case "UpdateMutation":
case MetadataOperationType.UPDATE:
return UpdateMutation.fromJSON(parsed);
default:
throw new Error("Unknown mutation type");
Expand All @@ -48,7 +48,7 @@ export class DeleteMutation
protected _where: Condition[] = [];

public getOperationType(): MetadataOperationType {
return "delete";
return MetadataOperationType.DELETE;
}

public clone(): this {
Expand Down Expand Up @@ -79,7 +79,7 @@ export class DeleteMutation

toJSON() {
return {
type: "DeleteMutation",
type: MetadataOperationType.DELETE,
table: this._table.toJSON(),
where: this._where.map((condition) => condition.toJSON()),
};
Expand All @@ -101,7 +101,7 @@ export class InsertMutation
protected _values: Record<string, ConditionValue> = {};

public getOperationType(): MetadataOperationType {
return "insert";
return MetadataOperationType.INSERT;
}

public clone(): this {
Expand Down Expand Up @@ -134,7 +134,7 @@ export class InsertMutation

toJSON() {
return {
type: "InsertMutation",
type: MetadataOperationType.INSERT,
table: this._table.toJSON(),
values: this._values,
};
Expand All @@ -155,7 +155,7 @@ export class UpdateMutation
protected _where: Condition[] = [];

public getOperationType(): MetadataOperationType {
return "update";
return MetadataOperationType.UPDATE;
}

public clone(): this {
Expand Down Expand Up @@ -202,7 +202,7 @@ export class UpdateMutation

toJSON() {
return {
type: "UpdateMutation",
type: MetadataOperationType.UPDATE,
table: this._table.toJSON(),
values: this._values,
where: this._where.map((condition) => condition.toJSON()),
Expand Down
3 changes: 2 additions & 1 deletion src/Query-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Q } from "./Query";
import { Fn } from "./Function";
import { MetadataOperationType } from "./interfaces";

describe("Query builder metadata", () => {
it("should return list of tables in simple query", () => {
Expand Down Expand Up @@ -59,6 +60,6 @@ describe("Query builder metadata", () => {
it("should get operation type for query", () => {
const query = Q.select();
const operation = query.getOperationType();
expect(operation).toEqual("select");
expect(operation).toEqual(MetadataOperationType.SELECT);
});
});
Loading

0 comments on commit 159db68

Please sign in to comment.