Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/lib/mysql/helpers/compare-fields.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as SharedHelpers from "../../../shared-helpers/index.js";

import * as Types from "../model/types.js";

import { processMappings } from "./process-mappings.js";
Expand All @@ -15,6 +17,9 @@ import { processMappings } from "./process-mappings.js";
export const compareFields = (
params: Types.TSearchParams = {},
paramsOr?: Types.TSearchParams[],
options?: {
tableFieldsSet?: Set<string>;
},
): {
queryArray: Types.TField[];
queryOrArray: { query: Types.TField[]; }[];
Expand All @@ -23,12 +28,14 @@ export const compareFields = (
const queryArray: Types.TField[] = [];
const values: unknown[] = [];

const { tableFieldsSet } = options || {};

for (const entry of Object.entries(params)) {
const key = entry[0];
const value = entry[1];

if (value === null) {
queryArray.push({ key: `${key} IS NULL`, operator: "$withoutParameters" });
queryArray.push({ key: `${SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet })} IS NULL`, operator: "$withoutParameters" });
} else if (typeof value === "object") {
if (Array.isArray(value)) {
for (const v of value) {
Expand All @@ -39,7 +46,7 @@ export const compareFields = (
throw new Error(`Invalid value.key ${k}, Available values: ${Array.from(processMappings.keys())}`);
}

processFunction(key, v, queryArray, values);
processFunction(SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet }), v, queryArray, values);
}
}
} else {
Expand All @@ -50,11 +57,11 @@ export const compareFields = (
throw new Error(`Invalid value.key ${k}, Available values: ${Array.from(processMappings.keys())}`);
}

processFunction(key, value, queryArray, values);
processFunction(SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet }), value, queryArray, values);
}
}
} else if (value !== undefined) {
queryArray.push({ key, operator: "=" });
queryArray.push({ key: SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet }), operator: "=" });
values.push(value);
}
}
Expand All @@ -74,7 +81,7 @@ export const compareFields = (
const value = entry[1];

if (value === null) {
queryOrArrayLocal.push({ key: `${key} IS NULL`, operator: "$withoutParameters" });
queryOrArrayLocal.push({ key: `${SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet })} IS NULL`, operator: "$withoutParameters" });
} else if (typeof value === "object") {
if (Array.isArray(value)) {
for (const v of value) {
Expand All @@ -85,7 +92,7 @@ export const compareFields = (
throw new Error(`Invalid value.key ${k}, Available values: ${Array.from(processMappings.keys())}`);
}

processFunction(key, v, queryOrArrayLocal, values);
processFunction(SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet }), v, queryOrArrayLocal, values);
}
}
} else {
Expand All @@ -96,11 +103,11 @@ export const compareFields = (
throw new Error(`Invalid value.key ${k}, Available values: ${Array.from(processMappings.keys())}`);
}

processFunction(key, value, queryOrArrayLocal, values);
processFunction(SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet }), value, queryOrArrayLocal, values);
}
}
} else if (value !== undefined) {
queryOrArrayLocal.push({ key, operator: "=" });
queryOrArrayLocal.push({ key: SharedHelpers.quoteMysqlIdent(key, { tableFieldsSet }), operator: "=" });
values.push(value);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/lib/mysql/helpers/compare-fields.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,15 @@ describe("compareFields", () => {
]);
expect(result.values).toEqual(["active", "John", 30]);
});

it("should quote identifiers when tableFieldsSet is provided", () => {
const tableFieldsSet = new Set(["typeID", "published"]);
const result = compareFields({ published: true, typeID: 34 }, undefined, { tableFieldsSet });

expect(result.queryArray).toEqual([
{ key: "`published`", operator: "=" },
{ key: "`typeID`", operator: "=" },
]);
expect(result.values).toEqual([true, 34]);
});
});
90 changes: 67 additions & 23 deletions src/lib/mysql/model/materialized-view.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import mysql from "mysql2/promise";

import * as Helpers from "../helpers/index.js";
import * as SharedHelpers from "../../../shared-helpers/index.js";
import * as SharedTypes from "../../../shared-types/index.js";
import * as Types from "./types.js";
import * as connection from "../connection.js";
import { QueryBuilder } from "../query-builder/index.js";
import queries from "./queries.js";
import { setLoggerAndExecutor } from "../helpers/index.js";

/**
* @experimental
Expand All @@ -16,6 +16,7 @@ import { setLoggerAndExecutor } from "../helpers/index.js";
export class BaseMaterializedView<const T extends readonly string[] = readonly string[]> {
#sortingOrders = new Set(["ASC", "DESC"]);
#coreFieldsSet;
#coreFieldsCoreSet;
#isLoggerEnabled: boolean | undefined;
#logger?: SharedTypes.TLogger;
#executeSql;
Expand All @@ -31,6 +32,8 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
*/
#executor: Types.TExecutor;

#nameQuoted;

/**
* The name of the materialized view.
*/
Expand Down Expand Up @@ -67,7 +70,10 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
}

this.name = data.name;
this.coreFields = data.coreFields;
this.#nameQuoted = SharedHelpers.quoteMysqlIdent(this.name, { force: true });

this.coreFields = [...data.coreFields];
this.#coreFieldsCoreSet = new Set(this.coreFields);

this.#coreFieldsSet = new Set([
...this.coreFields,
Expand Down Expand Up @@ -118,12 +124,18 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
* @param logger - The logger to use for the materialized view.
*/
setLogger(logger: SharedTypes.TLogger) {
const preparedOptions = setLoggerAndExecutor(
const preparedOptions = Helpers.setLoggerAndExecutor(
this.#executor,
{ isLoggerEnabled: true, logger },
);

const { executeSqlStream } = Helpers.setStreamExecutor(
this.#executor,
{ isLoggerEnabled: true, logger },
);

this.#executeSql = preparedOptions.executeSql;
this.#executeSqlStream = executeSqlStream;
this.#isLoggerEnabled = preparedOptions.isLoggerEnabled;
this.#logger = preparedOptions.logger;
}
Expand All @@ -134,12 +146,18 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
* @param executor - The executor to use for the materialized view.
*/
setExecutor(executor: Types.TExecutor) {
const preparedOptions = setLoggerAndExecutor(
const preparedOptions = Helpers.setLoggerAndExecutor(
executor,
{ isLoggerEnabled: this.#isLoggerEnabled, logger: this.#logger },
);

const { executeSqlStream } = Helpers.setStreamExecutor(
executor,
{ isLoggerEnabled: this.#isLoggerEnabled, logger: this.#logger },
);

this.#executeSql = preparedOptions.executeSql;
this.#executeSqlStream = executeSqlStream;
this.#isLoggerEnabled = preparedOptions.isLoggerEnabled;
this.#logger = preparedOptions.logger;
this.#executor = executor;
Expand All @@ -153,6 +171,10 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
return this.#executeSql;
}

get executeSqlStream() {
return this.#executeSqlStream;
}

/**
* Sets the client in the current class.
*
Expand Down Expand Up @@ -183,7 +205,7 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
*
* @returns A new instance of the base class with the new connection client.
*/
setClientInBaseClass(client: Types.TExecutor): BaseMaterializedView {
setClientInBaseClass(client: Types.TExecutor): BaseMaterializedView<T> {
return new BaseMaterializedView(
{ ...this.#initialArgs.data },
this.#initialArgs.dbCreds ? { ...this.#initialArgs.dbCreds } : undefined,
Expand Down Expand Up @@ -221,12 +243,16 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
*/
getArrByParams: (
{ $and = {}, $or }: { $and: Types.TSearchParams; $or?: Types.TSearchParams[]; },
selected: string[] = ["*"],
selected = ["*"],
pagination?: SharedTypes.TPagination,
order?: { orderBy: string; ordering: SharedTypes.TOrdering; }[],
): { query: string; values: unknown[]; } => {
const orderResult: { orderBy: string; ordering: SharedTypes.TOrdering; }[] = [];

if (order?.length) {
for (const o of order) {
orderResult.push({ orderBy: SharedHelpers.quoteMysqlIdent(o.orderBy, { tableFieldsSet: this.#coreFieldsCoreSet }), ordering: o.ordering });

if (!this.#coreFieldsSet.has(o.orderBy)) {
const allowedFields = Array.from(this.#coreFieldsSet).join(", ");

Expand All @@ -237,13 +263,19 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
}
}

if (!selected.length) selected.push("*");
const selectedResult = [];

const { queryArray, queryOrArray, values } = this.compareFields($and, $or);
const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selected, pagination, order);
if (!selected.length) {
selectedResult.push("*");
} else {
selected.forEach((e) => selectedResult.push(SharedHelpers.quoteMysqlIdent(e, { tableFieldsSet: this.#coreFieldsCoreSet })));
}

const { queryArray, queryOrArray, values } = this.compareFields($and, $or, { tableFieldsSet: this.#coreFieldsCoreSet });
const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selectedResult, pagination, orderResult);

return {
query: queries.getByParams(this.name, selectedFields, searchFields, orderByFields, paginationFields),
query: queries.getByParams(this.#nameQuoted, selectedFields, searchFields, orderByFields, paginationFields),
values,
};
},
Expand All @@ -259,11 +291,11 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
getCountByParams: (
{ $and = {}, $or }: { $and: Types.TSearchParams; $or?: Types.TSearchParams[]; },
): { query: string; values: unknown[]; } => {
const { queryArray, queryOrArray, values } = this.compareFields($and, $or);
const { queryArray, queryOrArray, values } = this.compareFields($and, $or, { tableFieldsSet: this.#coreFieldsCoreSet });
const { searchFields } = this.getFieldsToSearch({ queryArray, queryOrArray });

return {
query: queries.getCountByParams(this.name, searchFields),
query: queries.getCountByParams(this.#nameQuoted, searchFields),
values,
};
},
Expand All @@ -279,16 +311,22 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
*/
getOneByParams: (
{ $and = {}, $or }: { $and: Types.TSearchParams; $or?: Types.TSearchParams[]; },
selected: string[] = ["*"],
selected = ["*"],
): { query: string; values: unknown[]; } => {
if (!selected.length) selected.push("*");
const selectedResult = [];

if (!selected.length) {
selectedResult.push("*");
} else {
selected.forEach((e) => selectedResult.push(SharedHelpers.quoteMysqlIdent(e, { tableFieldsSet: this.#coreFieldsCoreSet })));
}

const { queryArray, queryOrArray, values } = this.compareFields($and, $or);
const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selected, { limit: 1, offset: 0 });
const { queryArray, queryOrArray, values } = this.compareFields($and, $or, { tableFieldsSet: this.#coreFieldsCoreSet });
const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selectedResult, { limit: 1, offset: 0 });

return {
query: queries.getByParams(
this.name,
this.#nameQuoted,
selectedFields,
searchFields,
orderByFields,
Expand All @@ -313,7 +351,7 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
*/
streamArrByParams: (
{ $and = {}, $or }: { $and: Types.TSearchParams; $or?: Types.TSearchParams[]; },
selected: string[] = ["*"],
selected = ["*"],
pagination?: SharedTypes.TPagination,
order?: { orderBy: string; ordering: SharedTypes.TOrdering; }[],
): { query: string; values: unknown[]; } => {
Expand All @@ -329,13 +367,19 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
}
}

if (!selected.length) selected.push("*");
const selectedResult = [];

if (!selected.length) {
selectedResult.push("*");
} else {
selected.forEach((e) => selectedResult.push(SharedHelpers.quoteMysqlIdent(e, { tableFieldsSet: this.#coreFieldsCoreSet })));
}

const { queryArray, queryOrArray, values } = this.compareFields($and, $or);
const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selected, pagination, order);
const { queryArray, queryOrArray, values } = this.compareFields($and, $or, { tableFieldsSet: this.#coreFieldsCoreSet });
const { orderByFields, paginationFields, searchFields, selectedFields } = this.getFieldsToSearch({ queryArray, queryOrArray }, selectedResult, pagination, order);

return {
query: queries.getByParams(this.name, selectedFields, searchFields, orderByFields, paginationFields),
query: queries.getByParams(this.#nameQuoted, selectedFields, searchFields, orderByFields, paginationFields),
values,
};
},
Expand Down Expand Up @@ -411,7 +455,7 @@ export class BaseMaterializedView<const T extends readonly string[] = readonly s
* @returns A promise that resolves when the view is refreshed.
*/
async refresh(concurrently: boolean = false): Promise<void> {
const query = `REFRESH MATERIALIZED VIEW ${concurrently ? "CONCURRENTLY" : ""} ${this.name}`;
const query = `REFRESH MATERIALIZED VIEW ${concurrently ? "CONCURRENTLY " : ""}${this.#nameQuoted}`;

await this.#executeSql({ query });
}
Expand Down
Loading
Loading