Restructure fn helper

This commit is contained in:
rijkvanzanten
2021-06-16 15:41:16 -04:00
parent d5685d69e9
commit d52f27339c
11 changed files with 114 additions and 101 deletions

View File

@@ -41,3 +41,7 @@ export const ASSET_TRANSFORM_QUERY_KEYS = ['key', 'width', 'height', 'fit', 'wit
export const FILTER_VARIABLES = ['$NOW', '$CURRENT_USER', '$CURRENT_ROLE'];
export const ALIAS_TYPES = ['alias', 'o2m', 'm2m', 'm2a', 'files', 'files', 'translations'];
export const COLUMN_TRANSFORMS = ['year', 'month', 'day', 'weekday', 'hour', 'minute', 'second'];
export const REGEX_BETWEEN_PARENS = /\(([^)]+)\)/;

View File

@@ -0,0 +1,12 @@
import { Knex } from 'knex';
export interface IBaseHelper {
year(table: string, column: string): Knex.Raw;
month(table: string, column: string): Knex.Raw;
week(table: string, column: string): Knex.Raw;
day(table: string, column: string): Knex.Raw;
weekday(table: string, column: string): Knex.Raw;
hour(table: string, column: string): Knex.Raw;
minute(table: string, column: string): Knex.Raw;
second(table: string, column: string): Knex.Raw;
}

View File

@@ -0,0 +1,42 @@
import { Knex } from 'knex';
import { IBaseHelper } from '../base';
export class HelperPostgres implements IBaseHelper {
private knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
year(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(YEAR FROM ??.??) as ??', [table, column, `${column}_year`]);
}
month(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(MONTH FROM ??)', [column]);
}
week(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(WEEK FROM ??)', [column]);
}
day(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(DAY FROM ??)', [column]);
}
weekday(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(DOW FROM ??)', [column]);
}
hour(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(HOUR FROM ??)', [column]);
}
minute(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(MINUTE FROM ??)', [column]);
}
second(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(SECOND FROM ??)', [column]);
}
}

View File

@@ -1,16 +1,14 @@
import { Knex } from 'knex';
import { DateTimeHelperPostgres } from './datetime';
import { HelperPostgres } from './dialects/postgres';
export function knexTransforms(knex: Knex) {
export function FunctionsHelper(knex: Knex) {
switch (knex.client.constructor.name) {
// case 'Client_MySQL':
// constructor = require('./dialects/mysql').default;
// break;
case 'Client_PG':
return {
datetime: new DateTimeHelperPostgres(knex),
};
return new HelperPostgres(knex);
// case 'Client_SQLite3':
// constructor = require('./dialects/sqlite').default;
// break;

View File

@@ -1,90 +0,0 @@
import { Knex } from 'knex';
interface IDateTimeHelper {
year(column: string): Knex.Raw;
month(column: string): Knex.Raw;
week(column: string): Knex.Raw;
day(column: string): Knex.Raw;
weekday(column: string): Knex.Raw;
hour(column: string): Knex.Raw;
minute(column: string): Knex.Raw;
second(column: string): Knex.Raw;
}
class DateTimeHelper implements IDateTimeHelper {
protected knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
year(column: string): Knex.Raw {
throw new Error(`Method "year" not implemented for dialect ${this.knex.client.constructor.name}`);
}
month(column: string): Knex.Raw {
throw new Error(`Method "month" not implemented for dialect ${this.knex.client.constructor.name}`);
}
week(column: string): Knex.Raw {
throw new Error(`Method "week" not implemented for dialect ${this.knex.client.constructor.name}`);
}
day(column: string): Knex.Raw {
throw new Error(`Method "date" not implemented for dialect ${this.knex.client.constructor.name}`);
}
weekday(column: string): Knex.Raw {
throw new Error(`Method "weekday" not implemented for dialect ${this.knex.client.constructor.name}`);
}
hour(column: string): Knex.Raw {
throw new Error(`Method "hour" not implemented for dialect ${this.knex.client.constructor.name}`);
}
minute(column: string): Knex.Raw {
throw new Error(`Method "minute" not implemented for dialect ${this.knex.client.constructor.name}`);
}
second(column: string): Knex.Raw {
throw new Error(`Method "second" not implemented for dialect ${this.knex.client.constructor.name}`);
}
}
export class DateTimeHelperPostgres extends DateTimeHelper {
constructor(knex: Knex) {
super(knex);
}
year(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(YEAR FROM ??) as ??', [column, `${column}_year`]);
}
month(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(MONTH FROM ??)', [column]);
}
week(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(WEEK FROM ??)', [column]);
}
day(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(DAY FROM ??)', [column]);
}
weekday(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(DOW FROM ??)', [column]);
}
hour(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(HOUR FROM ??)', [column]);
}
minute(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(MINUTE FROM ??)', [column]);
}
second(column: string): Knex.Raw {
return this.knex.raw('EXTRACT(SECOND FROM ??)', [column]);
}
}

View File

@@ -4,6 +4,8 @@ import { PayloadService } from '../services/payload';
import { Item, Query, SchemaOverview } from '../types';
import { AST, FieldNode, NestedCollectionNode } from '../types/ast';
import applyQuery from '../utils/apply-query';
import { getColumn } from '../utils/get-column';
import { stripFunction } from '../utils/strip-function';
import { toArray } from '../utils/to-array';
import getDatabase from './index';
@@ -111,8 +113,9 @@ async function parseCurrentLevel(
for (const child of children) {
if (child.type === 'field') {
if (columnsInCollection.includes(child.name) || child.name === '*') {
columnsToSelectInternal.push(child.name);
const fieldKey = stripFunction(child.name);
if (columnsInCollection.includes(fieldKey) || fieldKey === '*') {
columnsToSelectInternal.push(child.name); // maintain original name here (includes functions)
}
continue;
@@ -154,7 +157,7 @@ function getDBQuery(
query: Query,
nested?: boolean
): Knex.QueryBuilder {
const dbQuery = knex.select(columns.map((column) => `${table}.${column}`)).from(table);
const dbQuery = knex.select(columns.map((column) => getColumn(knex, table, column))).from(table);
const queryCopy = clone(query);

View File

@@ -12,6 +12,7 @@ import { EndpointRegisterFunction, HookRegisterFunction } from './types';
import { getSchema } from './utils/get-schema';
import listFolders from './utils/list-folders';
import { schedule, validate } from 'node-cron';
import { REGEX_BETWEEN_PARENS } from './constants';
export async function ensureFoldersExist(): Promise<void> {
const folders = ['endpoints', 'hooks', 'interfaces', 'modules', 'layouts', 'displays'];
@@ -98,7 +99,7 @@ function registerHooks(hooks: string[]) {
for (const [event, handler] of Object.entries(events)) {
if (event.startsWith('cron(')) {
const cron = event.match(/\(([^)]+)\)/)?.[1];
const cron = event.match(REGEX_BETWEEN_PARENS)?.[1];
if (!cron || validate(cron) === false) {
logger.warn(`Couldn't register cron hook. Provided cron is invalid: ${cron}`);

View File

@@ -58,7 +58,7 @@ export default async function getASTFromQuery(
/**
* When using aggregate functions, you can't have any other regular fields
* selected. This makes sure you never end up in a nonaggregate fields selection error
* selected. This makes sure you never end up in a non-aggregate fields selection error
*/
if (Object.keys(query.aggregate || {}).length > 0) {
fields = [];

View File

@@ -0,0 +1,29 @@
import { Knex } from 'knex';
import { FunctionsHelper } from '../database/functions';
import { REGEX_BETWEEN_PARENS } from '../constants';
/**
* Return column prefixed by table. If column includes functions (like `year(date_created)`, the
* column is replaced with the appropriate SQL)
*
* @param knex Current knex / transaction instance
* @param collection Collection or alias in which column resides
* @param field name of the column
* @returns Knex raw instance
*/
export function getColumn(knex: Knex, table: string, column: string): Knex.Raw {
const fn = FunctionsHelper(knex);
if (column.includes('(') && column.includes(')')) {
const functionName = column.split('(')[0];
const columnName = column.match(REGEX_BETWEEN_PARENS)![1];
if (functionName in fn) {
return fn[functionName as keyof typeof fn](table, columnName);
} else {
throw new Error(`Invalid function specified "${functionName}"`);
}
}
return knex.raw('??.??', [table, column]);
}

View File

@@ -1,3 +1,4 @@
import { REGEX_BETWEEN_PARENS } from '../constants';
import { Accountability, Filter } from '../types';
import { toArray } from '../utils/to-array';
import { adjustDate } from './adjust-date';
@@ -16,7 +17,7 @@ export function parseFilter(filter: Filter, accountability: Accountability | nul
if (val && typeof val === 'string' && val.startsWith('$NOW')) {
if (val.includes('(') && val.includes(')')) {
const adjustment = val.match(/\(([^)]+)\)/)?.[1];
const adjustment = val.match(REGEX_BETWEEN_PARENS)?.[1];
if (!adjustment) return new Date();
return adjustDate(new Date(), adjustment);
}

View File

@@ -0,0 +1,13 @@
import { REGEX_BETWEEN_PARENS } from '../constants';
/**
* Strip the function declarations from a list of fields
*/
export function stripFunction(field: string): string {
if (field.includes('(') && field.includes(')')) {
console.log(field.match(REGEX_BETWEEN_PARENS));
return field.match(REGEX_BETWEEN_PARENS)![1].trim();
} else {
return field;
}
}