Skip to content

Commit

Permalink
fix: updated qb insert
Browse files Browse the repository at this point in the history
added isUseDefaultValues
  • Loading branch information
JS-AK committed Nov 30, 2024
1 parent 67f0bc8 commit fd85ed3
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 111 deletions.
1 change: 0 additions & 1 deletion src/lib/mysql/model/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export const generateTimestampQuery = (type: "timestamp" | "unix_timestamp") =>
return "UTC_TIMESTAMP()";
case "unix_timestamp":
return "ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)";

default:
throw new Error("Invalid type: " + type);
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/mysql/query-builder/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,15 @@ export class QueryBuilder {
* Inserts records into the database.
*
* @param options - The options for the insert operation.
* @param [options.isUseDefaultValues] - Use default values for missing columns when options.params is an array. Defaults to false.
* @param [options.onConflict] - Conflict resolution strategy.
* @param options.params - The parameters to insert.
* @param [options.updateColumn] - Optional default system column for updates.
*
* @returns The current QueryBuilder instance for method chaining.
*/
insert<T extends SharedTypes.TRawParams = SharedTypes.TRawParams>(options: {
isUseDefaultValues?: boolean;
onConflict?: string;
params: T | T[];
updateColumn?: { title: string; type: "unix_timestamp" | "timestamp"; } | null;
Expand Down
80 changes: 61 additions & 19 deletions src/lib/mysql/query-builder/query-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export class QueryHandler {
* conflict handling using the `onConflict` option, and automatic updates of timestamp columns if specified.
*
* @param options - Options for constructing the INSERT query.
* @param [options.isUseDefaultValues] - Use default values for missing columns when options.params is an array.
* @param options.params - The parameters for the INSERT operation, which can be a single object or an array of objects.
* @param [options.onConflict] - Optional SQL clause to handle conflicts, typically used to specify `ON CONFLICT DO UPDATE`.
* @param [options.updateColumn] -
Expand All @@ -236,60 +237,101 @@ export class QueryHandler {
* @throws {Error} Throws an error if parameters are invalid or if fields are undefined.
*/
insert<T extends SharedTypes.TRawParams = SharedTypes.TRawParams>(options: {
isUseDefaultValues?: boolean;
onConflict?: string;
params: T | T[];
updateColumn?: { title: string; type: "unix_timestamp" | "timestamp"; } | null;
}): void {
const v = [];
const k = [];
const headers = new Set<string>();

let insertQuery = "";

if (Array.isArray(options.params)) {
const [example] = options.params;
const k: [string, string | undefined][][] = [];

if (!example) throw new Error("Invalid parameters");
const collectHeaders = (params: T[]) => {
for (const p of params) {
const keys = Object.keys(p);

const params = SharedHelpers.clearUndefinedFields(example);
for (const key of keys) {
headers.add(key);
}
}

return headers;
};

Object.keys(params).forEach((e) => headers.add(e));
collectHeaders(options.params);

for (const pR of options.params) {
const params = SharedHelpers.clearUndefinedFields(pR);
const keys = Object.keys(params);
if (options.updateColumn) {
headers.add(options.updateColumn.title);
}

if (!keys.length) throw new Error(`Invalid params, all fields are undefined - ${Object.keys(pR).join(", ")}`);
const headersArray = Array.from(headers);

for (const key of keys) {
if (!headers.has(key)) {
throw new Error(`Invalid params, all fields are undefined - ${Object.keys(pR).join(", ")}`);
for (const p of options.params) {
const keys: [string, string | undefined][] = [];

const preparedParams = headersArray.reduce<SharedTypes.TRawParams>((acc, e) => {
const value = p[e];

if (options.updateColumn?.title === e) {
return acc;
}
}

v.push(...Object.values(params));
if (value === undefined) {
if (options.isUseDefaultValues) {
keys.push([e, "DEFAULT"]);
} else {
throw new Error(`Invalid parameters - ${e} is undefined at ${JSON.stringify(p)} for INSERT INTO ${this.#dataSourceRaw}(${Array.from(headers).join(",")})`);
}

return acc;
}

acc[e] = value;

keys.push([e, undefined]);

return acc;
}, {});

v.push(...Object.values(preparedParams));

if (options.updateColumn) {
keys.push(`${options.updateColumn.title} = ${generateTimestampQuery(options.updateColumn.type)}`);
keys.push([options.updateColumn.title, generateTimestampQuery(options.updateColumn.type)]);
}

k.push(keys);
}

insertQuery += k.map((e) => e.map(() => "?")).join("),(");
insertQuery += k.map((e) => e.map((el) => {
if (el[1]) return el[1];

return "?";
})).join("),(");
} else {
const k: [string, string | undefined][] = [];

const params = SharedHelpers.clearUndefinedFields(options.params);

Object.keys(params).forEach((e) => { headers.add(e); k.push(e); });
Object.keys(params).forEach((e) => { headers.add(e); k.push([e, undefined]); });
v.push(...Object.values(params));

if (!headers.size) throw new Error(`Invalid params, all fields are undefined - ${Object.keys(options.params).join(", ")}`);

if (options.updateColumn) {
k.push(`${options.updateColumn.title} = ${generateTimestampQuery(options.updateColumn.type)}`);
headers.add(options.updateColumn.title);

k.push([options.updateColumn.title, generateTimestampQuery(options.updateColumn.type)]);
}

insertQuery += k.map(() => "?").join(",");
insertQuery += k.map((e) => {
if (e[1]) return e[1];

return "?";
}).join(",");
}

this.#mainQuery = `INSERT INTO ${this.#dataSourceRaw}(${Array.from(headers).join(",")}) VALUES(${insertQuery})`;
Expand Down
13 changes: 12 additions & 1 deletion src/lib/pg/model/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import * as SharedTypes from "../../../shared-types/index.js";

export const generateTimestampQuery = (type: "timestamp" | "unix_timestamp") => {
switch (type) {
case "timestamp":
return "NOW()";
case "unix_timestamp":
return "ROUND((EXTRACT(EPOCH FROM NOW()) * (1000)::NUMERIC))";
default:
throw new Error("Invalid type: " + type);
}
};

export default {
/**
* Generates an SQL `INSERT` statement for inserting multiple rows into a table.
Expand Down Expand Up @@ -326,7 +337,7 @@ export default {
* @param updateField.title - The name of the column for the timestamp.
* @param updateField.type - The type of timestamp to insert.
* @param [returning] - An optional array of column names to return after the update.
*
*
* @returns The generated SQL `UPDATE` statement.
*
* @throws {Error} If an invalid `updateField.type` is provided.
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pg/query-builder/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,15 @@ export class QueryBuilder {
* Inserts records into the database.
*
* @param options - The options for the insert operation.
* @param [options.isUseDefaultValues] - Use default values for missing columns when options.params is an array. Defaults to false.
* @param [options.onConflict] - Conflict resolution strategy.
* @param options.params - The parameters to insert.
* @param [options.updateColumn] - Optional default system column for updates.
*
* @returns The current QueryBuilder instance for method chaining.
*/
insert<T extends SharedTypes.TRawParams = SharedTypes.TRawParams>(options: {
isUseDefaultValues?: boolean;
onConflict?: string;
params: T | T[];
updateColumn?: { title: string; type: "unix_timestamp" | "timestamp"; } | null;
Expand Down
123 changes: 65 additions & 58 deletions src/lib/pg/query-builder/query-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Helpers from "../helpers/index.js";
import * as ModelTypes from "../model/types.js";
import * as SharedHelpers from "../../../shared-helpers/index.js";
import * as SharedTypes from "../../../shared-types/index.js";
import { generateTimestampQuery } from "../model/queries.js";

/**
* Class to handle SQL query construction.
Expand Down Expand Up @@ -261,6 +262,7 @@ export class QueryHandler {
* conflict handling using the `onConflict` option, and automatic updates of timestamp columns if specified.
*
* @param options - Options for constructing the INSERT query.
* @param [options.isUseDefaultValues] - Use default values for missing columns when options.params is an array.
* @param options.params - The parameters for the INSERT operation, which can be a single object or an array of objects.
* @param [options.onConflict] - Optional SQL clause to handle conflicts, typically used to specify `ON CONFLICT DO UPDATE`.
* @param [options.updateColumn] -
Expand All @@ -272,54 +274,70 @@ export class QueryHandler {
* @throws {Error} Throws an error if parameters are invalid or if fields are undefined.
*/
insert<T extends SharedTypes.TRawParams = SharedTypes.TRawParams>(options: {
isUseDefaultValues?: boolean;
onConflict?: string;
params: T | T[];
updateColumn?: { title: string; type: "unix_timestamp" | "timestamp"; } | null;
}): void {
const v = [];
const k = [];
const headers = new Set<string>();

let insertQuery = "";

if (Array.isArray(options.params)) {
const [example] = options.params;
const k: [string, string | undefined][][] = [];

if (!example) throw new Error("Invalid parameters");
const collectHeaders = (params: T[]) => {
for (const p of params) {
const keys = Object.keys(p);

const params = SharedHelpers.clearUndefinedFields(example);
for (const key of keys) {
headers.add(key);
}
}

Object.keys(params).forEach((e) => headers.add(e));
return headers;
};

for (const pR of options.params) {
const params = SharedHelpers.clearUndefinedFields(pR);
const keys = Object.keys(params);
collectHeaders(options.params);

if (!keys.length) throw new Error(`Invalid params, all fields are undefined - ${Object.keys(pR).join(", ")}`);
if (options.updateColumn) {
headers.add(options.updateColumn.title);
}

for (const key of keys) {
if (!headers.has(key)) {
throw new Error(`Invalid params, all fields are undefined - ${Object.keys(pR).join(", ")}`);
}
}
const headersArray = Array.from(headers);

v.push(...Object.values(params));
for (const p of options.params) {
const keys: [string, string | undefined][] = [];

if (options.updateColumn) {
switch (options.updateColumn.type) {
case "timestamp": {
keys.push(`${options.updateColumn.title} = NOW()`);
break;
}
case "unix_timestamp": {
keys.push(`${options.updateColumn.title} = ROUND((EXTRACT(EPOCH FROM NOW()) * (1000)::NUMERIC))`);
break;
}
const preparedParams = headersArray.reduce<SharedTypes.TRawParams>((acc, e) => {
const value = p[e];

default: {
throw new Error("Invalid type: " + options.updateColumn.type);
if (options.updateColumn?.title === e) {
return acc;
}

if (value === undefined) {
if (options.isUseDefaultValues) {
keys.push([e, "DEFAULT"]);
} else {
throw new Error(`Invalid parameters - ${e} is undefined at ${JSON.stringify(p)} for INSERT INTO ${this.#dataSourceRaw}(${Array.from(headers).join(",")})`);
}

return acc;
}

acc[e] = value;

keys.push([e, undefined]);

return acc;
}, {});

v.push(...Object.values(preparedParams));

if (options.updateColumn) {
keys.push([options.updateColumn.title, generateTimestampQuery(options.updateColumn.type)]);
}

k.push(keys);
Expand All @@ -329,36 +347,38 @@ export class QueryHandler {

let idx = valuesOrder;

insertQuery += k.map((e) => e.map(() => "$" + (++idx))).join("),(");
insertQuery += k.map((e) => e.map((el) => {
if (el[1]) return el[1];

return "$" + (++idx);
})).join("),(");
} else {
const k: [string, string | undefined][] = [];

const params = SharedHelpers.clearUndefinedFields(options.params);

Object.keys(params).forEach((e) => { headers.add(e); k.push(e); });
Object.keys(params).forEach((e) => { headers.add(e); k.push([e, undefined]); });
v.push(...Object.values(params));

if (!headers.size) throw new Error(`Invalid params, all fields are undefined - ${Object.keys(options.params).join(", ")}`);

if (options.updateColumn) {
switch (options.updateColumn.type) {
case "timestamp": {
k.push(`${options.updateColumn.title} = NOW()`);
break;
}
headers.add(options.updateColumn.title);

case "unix_timestamp": {
k.push(`${options.updateColumn.title} = ROUND((EXTRACT(EPOCH FROM NOW()) * (1000)::NUMERIC))`);
break;
}

default: {
throw new Error("Invalid type: " + options.updateColumn.type);
}
}
k.push([options.updateColumn.title, generateTimestampQuery(options.updateColumn.type)]);
}

const valuesOrder = this.#valuesOrder;

insertQuery += k.map((_, idx) => "$" + (idx + 1 + valuesOrder)).join(",");
let idx = valuesOrder;

insertQuery += k.map((e) => {
if (e[1]) return e[1];

idx += 1;

return "$" + (idx);
}).join(",");
}

this.#mainQuery = `INSERT INTO ${this.#dataSourceRaw}(${Array.from(headers).join(",")}) VALUES(${insertQuery})`;
Expand Down Expand Up @@ -425,20 +445,7 @@ export class QueryHandler {
let updateQuery = k.map((e: string, idx: number) => `${e} = $${idx + 1 + valuesOrder}`).join(",");

if (options.updateColumn) {
switch (options.updateColumn.type) {
case "timestamp": {
updateQuery += `, ${options.updateColumn.title} = NOW()`;
break;
}
case "unix_timestamp": {
updateQuery += `, ${options.updateColumn.title} = ROUND((EXTRACT(EPOCH FROM NOW()) * (1000)::NUMERIC))`;
break;
}

default: {
throw new Error("Invalid type: " + options.updateColumn.type);
}
}
updateQuery += `, ${options.updateColumn.title} = ${generateTimestampQuery(options.updateColumn.type)}`;
}

this.#mainQuery = `UPDATE ${this.#dataSourceRaw} SET ${updateQuery}`;
Expand Down
Loading

0 comments on commit fd85ed3

Please sign in to comment.