mirror of
https://github.com/directus/directus.git
synced 2026-01-30 18:18:10 -05:00
Restructure fn helper
This commit is contained in:
@@ -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 = /\(([^)]+)\)/;
|
||||
|
||||
12
api/src/database/functions/base.ts
Normal file
12
api/src/database/functions/base.ts
Normal 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;
|
||||
}
|
||||
42
api/src/database/functions/dialects/postgres.ts
Normal file
42
api/src/database/functions/dialects/postgres.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
29
api/src/utils/get-column.ts
Normal file
29
api/src/utils/get-column.ts
Normal 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]);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
13
api/src/utils/strip-function.ts
Normal file
13
api/src/utils/strip-function.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user