mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'aggregation' into insights
This commit is contained in:
42
api/src/database/functions/dialects/mssql.ts
Normal file
42
api/src/database/functions/dialects/mssql.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Knex } from 'knex';
|
||||
import { HelperFn } from '../types';
|
||||
|
||||
export class HelperMSSQL implements HelperFn {
|
||||
private knex: Knex;
|
||||
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
|
||||
year(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(year, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
month(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(month, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
week(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(week, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
day(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(day, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
weekday(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(weekday, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
hour(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(hour, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
minute(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(minute, ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
second(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DATEPART(second, ??.??)', [table, column]);
|
||||
}
|
||||
}
|
||||
42
api/src/database/functions/dialects/mysql.ts
Normal file
42
api/src/database/functions/dialects/mysql.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Knex } from 'knex';
|
||||
import { HelperFn } from '../types';
|
||||
|
||||
export class HelperMySQL implements HelperFn {
|
||||
private knex: Knex;
|
||||
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
|
||||
year(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('YEAR(??.??)', [table, column]);
|
||||
}
|
||||
|
||||
month(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('MONTH(??.??)', [table, column]);
|
||||
}
|
||||
|
||||
week(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('WEEK(??.??)', [table, column]);
|
||||
}
|
||||
|
||||
day(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DAYOFMONTH(??.??)', [table, column]);
|
||||
}
|
||||
|
||||
weekday(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('DAYOFWEEK??.??)', [table, column]);
|
||||
}
|
||||
|
||||
hour(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('HOUR(??.??)', [table, column]);
|
||||
}
|
||||
|
||||
minute(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('MINUTE(??.??)', [table, column]);
|
||||
}
|
||||
|
||||
second(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('SECOND(??.??)', [table, column]);
|
||||
}
|
||||
}
|
||||
42
api/src/database/functions/dialects/oracle.ts
Normal file
42
api/src/database/functions/dialects/oracle.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Knex } from 'knex';
|
||||
import { HelperFn } from '../types';
|
||||
|
||||
export class HelperOracle implements HelperFn {
|
||||
private knex: Knex;
|
||||
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
|
||||
year(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'IYYY')", [table, column]);
|
||||
}
|
||||
|
||||
month(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'MM')", [table, column]);
|
||||
}
|
||||
|
||||
week(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'IW')", [table, column]);
|
||||
}
|
||||
|
||||
day(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'DD')", [table, column]);
|
||||
}
|
||||
|
||||
weekday(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'D')", [table, column]);
|
||||
}
|
||||
|
||||
hour(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'HH24')", [table, column]);
|
||||
}
|
||||
|
||||
minute(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'MI')", [table, column]);
|
||||
}
|
||||
|
||||
second(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("TO_CHAR(??.??, 'SS')", [table, column]);
|
||||
}
|
||||
}
|
||||
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 { HelperFn } from '../types';
|
||||
|
||||
export class HelperPostgres implements HelperFn {
|
||||
private knex: Knex;
|
||||
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
|
||||
year(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(YEAR FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
month(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(MONTH FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
week(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(WEEK FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
day(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(DAY FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
weekday(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(DOW FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
hour(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(HOUR FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
minute(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(MINUTE FROM ??.??)', [table, column]);
|
||||
}
|
||||
|
||||
second(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw('EXTRACT(SECOND FROM ??.??)', [table, column]);
|
||||
}
|
||||
}
|
||||
42
api/src/database/functions/dialects/sqlite.ts
Normal file
42
api/src/database/functions/dialects/sqlite.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Knex } from 'knex';
|
||||
import { HelperFn } from '../types';
|
||||
|
||||
export class HelperSQLite implements HelperFn {
|
||||
private knex: Knex;
|
||||
|
||||
constructor(knex: Knex) {
|
||||
this.knex = knex;
|
||||
}
|
||||
|
||||
year(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%Y', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
month(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%m', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
week(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%W', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
day(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%d', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
weekday(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%w', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
hour(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%H', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
minute(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%M', ??.??)", [table, column]);
|
||||
}
|
||||
|
||||
second(table: string, column: string): Knex.Raw {
|
||||
return this.knex.raw("strftime('%S', ??.??)", [table, column]);
|
||||
}
|
||||
}
|
||||
25
api/src/database/functions/index.ts
Normal file
25
api/src/database/functions/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
import { HelperPostgres } from './dialects/postgres';
|
||||
import { HelperMySQL } from './dialects/mysql';
|
||||
import { HelperMSSQL } from './dialects/mssql';
|
||||
import { HelperSQLite } from './dialects/sqlite';
|
||||
import { HelperOracle } from './dialects/oracle';
|
||||
|
||||
export function FunctionsHelper(knex: Knex) {
|
||||
switch (knex.client.constructor.name) {
|
||||
case 'Client_MySQL':
|
||||
return new HelperMySQL(knex);
|
||||
case 'Client_PG':
|
||||
return new HelperPostgres(knex);
|
||||
case 'Client_SQLite3':
|
||||
return new HelperSQLite(knex);
|
||||
case 'Client_Oracledb':
|
||||
case 'Client_Oracle':
|
||||
return new HelperOracle(knex);
|
||||
case 'Client_MSSQL':
|
||||
return new HelperMSSQL(knex);
|
||||
default:
|
||||
throw Error('Unsupported driver used: ' + knex.client.constructor.name);
|
||||
}
|
||||
}
|
||||
12
api/src/database/functions/types.ts
Normal file
12
api/src/database/functions/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export interface HelperFn {
|
||||
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;
|
||||
}
|
||||
@@ -83,9 +83,6 @@ export async function up(knex: Knex): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Can't reliably have circular cascade
|
||||
const action = constraint.many_collection === constraint.one_collection ? 'NO ACTION' : 'SET NULL';
|
||||
|
||||
// MySQL doesn't accept FKs from `int` to `int unsigned`. `knex` defaults `.increments()`
|
||||
// to `unsigned`, but defaults `.integer()` to `int`. This means that created m2o fields
|
||||
// have the wrong type. This step will force the m2o `int` field into `unsigned`, but only
|
||||
@@ -104,12 +101,15 @@ export async function up(knex: Knex): Promise<void> {
|
||||
}
|
||||
|
||||
const indexName = getDefaultIndexName('foreign', constraint.many_collection, constraint.many_field);
|
||||
|
||||
table
|
||||
const builder = table
|
||||
.foreign(constraint.many_field, indexName)
|
||||
.references(relatedPrimaryKeyField)
|
||||
.inTable(constraint.one_collection!)
|
||||
.onDelete(action);
|
||||
.inTable(constraint.one_collection!);
|
||||
|
||||
// Can't reliably have circular cascade
|
||||
if (constraint.many_collection !== constraint.one_collection) {
|
||||
builder.onDelete('SET NULL');
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
|
||||
@@ -3,7 +3,10 @@ import { clone, cloneDeep, pick, uniq } from 'lodash';
|
||||
import { PayloadService } from '../services/payload';
|
||||
import { Item, Query, SchemaOverview } from '../types';
|
||||
import { AST, FieldNode, NestedCollectionNode } from '../types/ast';
|
||||
import { applyFunctionToColumnName } from '../utils/apply-function-to-column-name';
|
||||
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';
|
||||
|
||||
@@ -113,8 +116,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;
|
||||
@@ -157,7 +161,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);
|
||||
|
||||
@@ -170,7 +174,7 @@ function getDBQuery(
|
||||
delete queryCopy.limit;
|
||||
}
|
||||
|
||||
applyQuery(table, dbQuery, queryCopy, schema);
|
||||
applyQuery(knex, table, dbQuery, queryCopy, schema);
|
||||
|
||||
return dbQuery;
|
||||
}
|
||||
@@ -391,14 +395,14 @@ function removeTemporaryFields(
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure any new aliased aggregate fields are included
|
||||
// Make sure any requested aggregate fields are included
|
||||
if (ast.query?.aggregate) {
|
||||
for (const [_operation, aliasMap] of Object.entries(ast.query.aggregate)) {
|
||||
if (!aliasMap) continue;
|
||||
for (const [operation, aggregateFields] of Object.entries(ast.query.aggregate)) {
|
||||
if (!fields) continue;
|
||||
|
||||
for (const [_column, alias] of Object.entries(aliasMap)) {
|
||||
fields.push(alias);
|
||||
}
|
||||
if (operation === 'count' && aggregateFields.includes('*')) fields.push('count');
|
||||
|
||||
fields.push(...aggregateFields.map((field) => `${field}_${operation}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,7 +423,9 @@ function removeTemporaryFields(
|
||||
);
|
||||
}
|
||||
|
||||
item = fields.length > 0 ? pick(rawItem, fields) : rawItem[primaryKeyField];
|
||||
const fieldsWithFunctionsApplied = fields.map((field) => applyFunctionToColumnName(field));
|
||||
|
||||
item = fields.length > 0 ? pick(rawItem, fieldsWithFunctionsApplied) : rawItem[primaryKeyField];
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user