diff --git a/src/Mutation-serialization.test.ts b/src/Mutation-serialization.test.ts index 74b804d..144830f 100644 --- a/src/Mutation-serialization.test.ts +++ b/src/Mutation-serialization.test.ts @@ -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({ diff --git a/src/Mutation.test.ts b/src/Mutation.test.ts index 4855fb8..2e63602 100644 --- a/src/Mutation.test.ts +++ b/src/Mutation.test.ts @@ -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 }]) @@ -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") diff --git a/src/Mutation.ts b/src/Mutation.ts index 85b860b..21257e9 100644 --- a/src/Mutation.ts +++ b/src/Mutation.ts @@ -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, @@ -103,7 +103,8 @@ 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; @@ -111,31 +112,48 @@ export class InsertMutation 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 { @@ -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; } } diff --git a/src/stories/1_Query.stories.mdx b/src/stories/1_Query.stories.mdx index d9cd3c3..d25a827 100644 --- a/src/stories/1_Query.stories.mdx +++ b/src/stories/1_Query.stories.mdx @@ -291,6 +291,22 @@ Q.insert('table').values([{foo:'blah'}]); `} /> +### Insert select + + + +You can also specify column names: + + + ### Update