Skip to content

Commit

Permalink
Add support for insert select
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubknejzlik committed Dec 5, 2024
1 parent 813795b commit e518ae3
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 20 deletions.
12 changes: 11 additions & 1 deletion src/Mutation-serialization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,23 @@ describe("Mutation serialization and deserialization", () => {
expect(deserializedQuery.toSQL()).toEqual(originalQuery.toSQL());
});

it("should handle round-trip JSON serialization and deserialization for an insert", () => {
it("should handle round-trip JSON serialization and deserialization for an insert with values", () => {
const originalQuery = Q.insert("table").values([{ foo: 123, bar: "baz" }]);
const jsonStr = JSON.stringify(originalQuery.toJSON());
const deserializedQuery = MutationBase.deserialize(jsonStr);
expect(deserializedQuery.toSQL()).toEqual(originalQuery.toSQL());
});

it("should handle round-trip JSON serialization and deserialization for an insert with select", () => {
const originalQuery = Q.insert("users_backup").select(
Q.select().addField("id").addField("username").from("users"),
["id", "username"]
);
const jsonStr = JSON.stringify(originalQuery.toJSON());
const deserializedQuery = MutationBase.deserialize(jsonStr);
expect(deserializedQuery.toSQL()).toEqual(originalQuery.toSQL());
});

it("should handle round-trip JSON serialization and deserialization for an update", () => {
const originalQuery = Q.update("table")
.set({
Expand Down
27 changes: 26 additions & 1 deletion src/Mutation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ describe("Mutation builder SQL", () => {
);
});

it("should return SQL for insert", () => {
it("should fail with empty insert", () => {
expect(() => console.log(Q.insert("users").toSQL())).toThrow(
new Error("values or select must be set for insert query")
);
});

it("should return SQL for insert values", () => {
expect(
Q.insert("users")
.values([{ name: "John Doe", age: 42, isActive: true }])
Expand All @@ -19,6 +25,25 @@ describe("Mutation builder SQL", () => {
);
});

it("should generate SQL for insert select", () => {
expect(
Q.insert("users_backup").select(Q.select().from("users")).toSQL()
).toEqual("INSERT INTO `users_backup` SELECT * FROM `users`");
});

it("should generate SQL for insert select with columns", () => {
expect(
Q.insert("users_backup")
.select(Q.select().addField("id").addField("username").from("users"), [
"id",
"username",
])
.toSQL()
).toEqual(
"INSERT INTO `users_backup` (`id`, `username`) SELECT `id`, `username` FROM `users`"
);
});

it("should return SQL for update", () => {
expect(
Q.update("users")
Expand Down
64 changes: 46 additions & 18 deletions src/Mutation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Condition } from "./Condition";
import { Expression, ExpressionValue } from "./Expression";
import { ISQLFlavor } from "./Flavor";
import { Q, Table } from "./Query";
import { Q, SelectQuery, Table } from "./Query";
import { MySQLFlavor } from "./flavors/mysql";
import {
IMetadata,
Expand Down Expand Up @@ -103,39 +103,57 @@ export class InsertMutation
extends MutationBase
implements ISerializable, ISequelizable, IMetadata
{
protected _values: RowRecord[] = [];
protected _values?: RowRecord[];
protected _selectWithColumns: [SelectQuery, string[] | undefined];

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

public clone(): this {
const clone = super.clone();
clone._values = [...this._values];
clone._values = this._values && [...this._values];
return clone;
}

values(values: RowRecordInput[]): this {
const clone = this.clone();
clone._values = [...clone._values, ...values];
if (clone._selectWithColumns) throw new Error("select already set");
clone._values = [...(clone._values ?? []), ...values];
return clone;
}

toSQL(flavor: ISQLFlavor = new MySQLFlavor()): string {
if (this._values.length === 0) throw new Error("No values to insert");
select(query: SelectQuery, columns?: string[]): this {
const clone = this.clone();
if (clone._values) throw new Error("values already set");
clone._selectWithColumns = [query, columns];
return clone;
}

return `INSERT INTO ${this._table.toSQL(flavor)} (${Object.keys(
this._values[0]
)
.map((k) => flavor.escapeColumn(k))
.join(", ")}) VALUES ${this._values
.map(
(value) =>
`(${Object.values(value)
.map((v) => flavor.escapeValue(v))
.join(", ")})`
toSQL(flavor: ISQLFlavor = new MySQLFlavor()): string {
if (this._values) {
return `INSERT INTO ${this._table.toSQL(flavor)} (${Object.keys(
this._values[0]
)
.join(", ")}`;
.map((k) => flavor.escapeColumn(k))
.join(", ")}) VALUES ${this._values
.map(
(value) =>
`(${Object.values(value)
.map((v) => flavor.escapeValue(v))
.join(", ")})`
)
.join(", ")}`;
}
if (this._selectWithColumns) {
const [query, columns] = this._selectWithColumns;
return `INSERT INTO ${this._table.toSQL(flavor)}${
columns
? ` (${columns.map((k) => flavor.escapeColumn(k)).join(", ")})`
: ""
} ${query.toSQL(flavor)}`;
}
throw new Error("values or select must be set for insert query");
}

serialize(): string {
Expand All @@ -147,12 +165,22 @@ export class InsertMutation
type: OperationType.INSERT,
table: this._table.toJSON(),
values: this._values,
select: this._selectWithColumns && [
this._selectWithColumns[0].toJSON(),
this._selectWithColumns[1],
],
};
}

static fromJSON({ table, values }: any): InsertMutation {
static fromJSON({ table, values, select }: any): InsertMutation {
const insertMutation = new InsertMutation(table.source, table.alias);
insertMutation._values = values;
if (select) {
insertMutation._selectWithColumns = [
SelectQuery.fromJSON(select[0]),
select[1],
];
}
return insertMutation;
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/stories/1_Query.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,22 @@ Q.insert('table').values([{foo:'blah'}]);
`}
/>

### Insert select

<QueryPreview
code={`
Q.insert('table').select(Q.select().from('table2'));
`}
/>

You can also specify column names:

<QueryPreview
code={`
Q.insert('user_stats').select(Q.select().from('users').addField('region').addField(Fn.count('*')).groupBy('region'),['region','count']);
`}
/>

### Update

<QueryPreview
Expand Down

0 comments on commit e518ae3

Please sign in to comment.