Improve helpers structure (#10052)

* Improve helpers structure

* Added DatabaseHelper base class

* Refactor index.ts
This commit is contained in:
Oreille
2021-12-01 21:08:24 +01:00
committed by GitHub
parent 13deca8ddc
commit bc864d1f51
28 changed files with 288 additions and 348 deletions

View File

@@ -1,27 +0,0 @@
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';
import { HelperFn } from './types';
export function FunctionsHelper(knex: Knex): HelperFn {
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);
}
}

View File

@@ -1,12 +0,0 @@
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;
}

View File

@@ -1,41 +0,0 @@
import { Knex } from 'knex';
import getDatabase from '..';
let dateHelper: KnexDate | undefined;
export function getDateHelper(): KnexDate {
if (!dateHelper) {
const db = getDatabase();
const client = db.client.config.client as string;
const constructor = {
mysql: KnexDate,
mariadb: KnexDate,
sqlite3: KnexDate_SQLITE,
pg: KnexDate,
postgres: KnexDate,
redshift: KnexDate,
mssql: KnexDate,
oracledb: KnexDate,
}[client];
if (!constructor) {
throw new Error(`Geometry helper not implemented on ${client}.`);
}
dateHelper = new constructor(db);
}
return dateHelper;
}
class KnexDate {
constructor(protected knex: Knex) {}
parseDate(date: string): string {
return date;
}
}
class KnexDate_SQLITE extends KnexDate {
parseDate(date: string): string {
const newDate = new Date(date);
return (newDate.getTime() - newDate.getTimezoneOffset() * 60 * 1000).toString();
}
}

View File

@@ -1,13 +1,7 @@
import { DateHelper } from '../types';
import { Knex } from 'knex';
import { HelperFn } from '../types';
export class HelperMSSQL implements HelperFn {
private knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
export class DateHelperMSSQL extends DateHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(year, ??.??)', [table, column]);
}

View File

@@ -1,13 +1,7 @@
import { DateHelper } from '../types';
import { Knex } from 'knex';
import { HelperFn } from '../types';
export class HelperMySQL implements HelperFn {
private knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
export class DateHelperMySQL extends DateHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw('YEAR(??.??)', [table, column]);
}

View File

@@ -1,13 +1,7 @@
import { DateHelper } from '../types';
import { Knex } from 'knex';
import { HelperFn } from '../types';
export class HelperOracle implements HelperFn {
private knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
export class DateHelperOracle extends DateHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw("TO_CHAR(??.??, 'IYYY')", [table, column]);
}

View File

@@ -1,13 +1,7 @@
import { DateHelper } from '../types';
import { Knex } from 'knex';
import { HelperFn } from '../types';
export class HelperPostgres implements HelperFn {
private knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
export class DateHelperPostgres extends DateHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(YEAR FROM ??.??)', [table, column]);
}

View File

@@ -1,13 +1,7 @@
import { DateHelper } from '../types';
import { Knex } from 'knex';
import { HelperFn } from '../types';
export class HelperSQLite implements HelperFn {
private knex: Knex;
constructor(knex: Knex) {
this.knex = knex;
}
export class DateHelperSQLite extends DateHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw("strftime('%Y', ??.??)", [table, column]);
}
@@ -39,4 +33,9 @@ export class HelperSQLite implements HelperFn {
second(table: string, column: string): Knex.Raw {
return this.knex.raw("strftime('%S', ??.??)", [table, column]);
}
parse(date: string): string {
const newDate = new Date(date);
return (newDate.getTime() - newDate.getTimezoneOffset() * 60 * 1000).toString();
}
}

View File

@@ -0,0 +1,6 @@
export { DateHelperPostgres as postgres } from './dialects/postgres';
export { DateHelperPostgres as redshift } from './dialects/postgres';
export { DateHelperOracle as oracle } from './dialects/oracle';
export { DateHelperSQLite as sqlite } from './dialects/sqlite';
export { DateHelperMySQL as mysql } from './dialects/mysql';
export { DateHelperMSSQL as mssql } from './dialects/mssql';

View File

@@ -0,0 +1,16 @@
import { DatabaseHelper } from '../types';
import { Knex } from 'knex';
export abstract class DateHelper extends DatabaseHelper {
abstract year(table: string, column: string): Knex.Raw;
abstract month(table: string, column: string): Knex.Raw;
abstract week(table: string, column: string): Knex.Raw;
abstract day(table: string, column: string): Knex.Raw;
abstract weekday(table: string, column: string): Knex.Raw;
abstract hour(table: string, column: string): Knex.Raw;
abstract minute(table: string, column: string): Knex.Raw;
abstract second(table: string, column: string): Knex.Raw;
parse(date: string): string {
return date;
}
}

View File

@@ -1,193 +0,0 @@
import { Field, RawField } from '@directus/shared/types';
import { Knex } from 'knex';
import { stringify as geojsonToWKT, GeoJSONGeometry } from 'wellknown';
import { getDatabaseClient } from '..';
import getDatabase from '..';
let geometryHelper: KnexSpatial | undefined;
export function getGeometryHelper(database?: Knex): KnexSpatial {
if (!geometryHelper) {
database = database ?? getDatabase();
const client = getDatabaseClient(database);
const constructor = {
mysql: KnexSpatial_MySQL,
sqlite: KnexSpatial_SQLite,
postgres: KnexSpatial_PG,
redshift: KnexSpatial_Redshift,
mssql: KnexSpatial_MSSQL,
oracle: KnexSpatial_Oracle,
}[client];
if (!constructor) {
throw new Error(`Geometry helper not implemented on ${client}.`);
}
geometryHelper = new constructor(database);
}
return geometryHelper!;
}
abstract class KnexSpatial {
constructor(protected knex: Knex) {}
supported(): boolean | Promise<boolean> {
return true;
}
isTrue(expression: Knex.Raw) {
return expression;
}
isFalse(expression: Knex.Raw) {
return expression.wrap('NOT ', '');
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
const type = field.type.split('.')[1] ?? 'geometry';
return table.specificType(field.field, type);
}
asText(table: string, column: string): Knex.Raw {
return this.knex.raw('st_astext(??.??) as ??', [table, column, column]);
}
asGeoJSON?(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('st_geomfromtext(?, 4326)', text);
}
fromGeoJSON(geojson: GeoJSONGeometry): Knex.Raw {
return this.fromText(geojsonToWKT(geojson));
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('st_intersects(??, ?)', [key, geometry]);
}
intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isTrue(this._intersects(key, geojson));
}
nintersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isFalse(this._intersects(key, geojson));
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('st_intersects(??, ?)', [key, geometry]);
}
intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isTrue(this._intersects_bbox(key, geojson));
}
nintersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isFalse(this._intersects_bbox(key, geojson));
}
collect(table: string, column: string): Knex.Raw {
return this.knex.raw('st_astext(st_collect(??.??))', [table, column]);
}
}
class KnexSpatial_SQLite extends KnexSpatial {
async supported() {
const res = await this.knex.select('name').from('pragma_function_list').where({ name: 'spatialite_version' });
return res.length > 0;
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('asgeojson(??.??) as ??', [table, column, column]);
}
}
class KnexSpatial_PG extends KnexSpatial {
async supported() {
const res = await this.knex.select('oid').from('pg_proc').where({ proname: 'postgis_version' });
return res.length > 0;
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
const type = field.type.split('.')[1] ?? 'geometry';
return table.specificType(field.field, `geometry(${type}, 4326)`);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('?? && ?', [key, geometry]);
}
}
class KnexSpatial_MySQL extends KnexSpatial {
collect(table: string, column: string): Knex.Raw {
return this.knex.raw(
`concat('geometrycollection(', group_concat(? separator ', '), ')'`,
this.asText(table, column)
);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('st_geomfromtext(?)', text);
}
}
class KnexSpatial_Redshift extends KnexSpatial {
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'geometry');
}
}
class KnexSpatial_MSSQL extends KnexSpatial {
isTrue(expression: Knex.Raw) {
return expression.wrap(``, ` = 1`);
}
isFalse(expression: Knex.Raw) {
return expression.wrap(``, ` = 0`);
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'geometry');
}
asText(table: string, column: string): Knex.Raw {
return this.knex.raw('??.??.STAsText() as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('geometry::STGeomFromText(?, 4326)', text);
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('??.STIntersects(?)', [key, geometry]);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('??.STEnvelope().STIntersects(?.STEnvelope())', [key, geometry]);
}
collect(table: string, column: string): Knex.Raw {
return this.knex.raw('geometry::CollectionAggregate(??.??).STAsText()', [table, column]);
}
asGeoJSON: undefined;
}
class KnexSpatial_Oracle extends KnexSpatial {
isTrue(expression: Knex.Raw) {
return expression.wrap(``, ` = 'TRUE'`);
}
isFalse(expression: Knex.Raw) {
return expression.wrap(``, ` = 'FALSE'`);
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'sdo_geometry');
}
asText(table: string, column: string): Knex.Raw {
return this.knex.raw('sdo_util.to_wktgeometry(??.??) as ??', [table, column, column]);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('sdo_util.to_geojson(??.??) as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('sdo_geometry(?, 4326)', text);
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw(`sdo_overlapbdyintersect(??, ?)`, [key, geometry]);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw(`sdo_overlapbdyintersect(sdo_geom.sdo_mbr(??), sdo_geom.sdo_mbr(?))`, [key, geometry]);
}
collect(table: string, column: string): Knex.Raw {
return this.knex.raw(`concat('geometrycollection(', listagg(?, ', '), ')'`, this.asText(table, column));
}
}

View File

@@ -0,0 +1,36 @@
import { GeometryHelper } from '../types';
import { Field, RawField } from '@directus/shared/types';
import { GeoJSONGeometry } from 'wellknown';
import { Knex } from 'knex';
export class GeometryHelperMSSQL extends GeometryHelper {
isTrue(expression: Knex.Raw) {
return expression.wrap(``, ` = 1`);
}
isFalse(expression: Knex.Raw) {
return expression.wrap(``, ` = 0`);
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'geometry');
}
asText(table: string, column: string): Knex.Raw {
return this.knex.raw('??.??.STAsText() as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('geometry::STGeomFromText(?, 4326)', text);
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('??.STIntersects(?)', [key, geometry]);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('??.STEnvelope().STIntersects(?.STEnvelope())', [key, geometry]);
}
collect(table: string, column: string): Knex.Raw {
return this.knex.raw('geometry::CollectionAggregate(??.??).STAsText()', [table, column]);
}
}

View File

@@ -0,0 +1,17 @@
import { GeometryHelper } from '../types';
import { Knex } from 'knex';
export class GeometryHelperMySQL extends GeometryHelper {
collect(table: string, column: string): Knex.Raw {
return this.knex.raw(
`concat('geometrycollection(', group_concat(? separator ', '), ')'`,
this.asText(table, column)
);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('st_geomfromtext(?)', text);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}
}

View File

@@ -0,0 +1,39 @@
import { GeometryHelper } from '../types';
import { Field, RawField } from '@directus/shared/types';
import { GeoJSONGeometry } from 'wellknown';
import { Knex } from 'knex';
export class GeometryHelperOracle extends GeometryHelper {
isTrue(expression: Knex.Raw) {
return expression.wrap(``, ` = 'TRUE'`);
}
isFalse(expression: Knex.Raw) {
return expression.wrap(``, ` = 'FALSE'`);
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'sdo_geometry');
}
asText(table: string, column: string): Knex.Raw {
return this.knex.raw('sdo_util.to_wktgeometry(??.??) as ??', [table, column, column]);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('sdo_util.to_geojson(??.??) as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('sdo_geometry(?, 4326)', text);
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw(`sdo_overlapbdyintersect(??, ?)`, [key, geometry]);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw(`sdo_overlapbdyintersect(sdo_geom.sdo_mbr(??), sdo_geom.sdo_mbr(?))`, [key, geometry]);
}
collect(table: string, column: string): Knex.Raw {
return this.knex.raw(`concat('geometrycollection(', listagg(?, ', '), ')'`, this.asText(table, column));
}
}

View File

@@ -0,0 +1,22 @@
import { GeometryHelper } from '../types';
import { Field, RawField } from '@directus/shared/types';
import { GeoJSONGeometry } from 'wellknown';
import { Knex } from 'knex';
export class GeometryHelperPostgres extends GeometryHelper {
async supported() {
const res = await this.knex.select('oid').from('pg_proc').where({ proname: 'postgis_version' });
return res.length > 0;
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
const type = field.type.split('.')[1] ?? 'geometry';
return table.specificType(field.field, `geometry(${type}, 4326)`);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('?? && ?', [key, geometry]);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}
}

View File

@@ -0,0 +1,15 @@
import { GeometryHelper } from '../types';
import { Field, RawField } from '@directus/shared/types';
import { Knex } from 'knex';
export class GeometryHelperRedshift extends GeometryHelper {
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'geometry');
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}
}

View File

@@ -0,0 +1,12 @@
import { GeometryHelper } from '../types';
import { Knex } from 'knex';
export class GeometryHelperSQLite extends GeometryHelper {
async supported() {
const res = await this.knex.select('name').from('pragma_function_list').where({ name: 'spatialite_version' });
return res.length > 0;
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('asgeojson(??.??) as ??', [table, column, column]);
}
}

View File

@@ -0,0 +1,6 @@
export { GeometryHelperPostgres as postgres } from './dialects/postgres';
export { GeometryHelperRedshift as redshift } from './dialects/redshift';
export { GeometryHelperOracle as oracle } from './dialects/oracle';
export { GeometryHelperSQLite as sqlite } from './dialects/sqlite';
export { GeometryHelperMySQL as mysql } from './dialects/mysql';
export { GeometryHelperMSSQL as mssql } from './dialects/mssql';

View File

@@ -0,0 +1,52 @@
import { stringify as geojsonToWKT, GeoJSONGeometry } from 'wellknown';
import { Field, RawField } from '@directus/shared/types';
import { DatabaseHelper } from '../types';
import { Knex } from 'knex';
export abstract class GeometryHelper extends DatabaseHelper {
supported(): boolean | Promise<boolean> {
return true;
}
isTrue(expression: Knex.Raw) {
return expression;
}
isFalse(expression: Knex.Raw) {
return expression.wrap('NOT ', '');
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
const type = field.type.split('.')[1] ?? 'geometry';
return table.specificType(field.field, type);
}
asText(table: string, column: string): Knex.Raw {
return this.knex.raw('st_astext(??.??) as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
return this.knex.raw('st_geomfromtext(?, 4326)', text);
}
fromGeoJSON(geojson: GeoJSONGeometry): Knex.Raw {
return this.fromText(geojsonToWKT(geojson));
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('st_intersects(??, ?)', [key, geometry]);
}
intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isTrue(this._intersects(key, geojson));
}
nintersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isFalse(this._intersects(key, geojson));
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('st_intersects(??, ?)', [key, geometry]);
}
intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isTrue(this._intersects_bbox(key, geojson));
}
nintersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isFalse(this._intersects_bbox(key, geojson));
}
collect(table: string, column: string): Knex.Raw {
return this.knex.raw('st_astext(st_collect(??.??))', [table, column]);
}
}

View File

@@ -0,0 +1,15 @@
import { getDatabaseClient } from '..';
import { Knex } from 'knex';
import * as dateHelpers from './date';
import * as geometryHelpers from './geometry';
export function getHelpers(database: Knex) {
const client = getDatabaseClient(database);
return {
date: new dateHelpers[client](database),
st: new geometryHelpers[client](database),
};
}
export type Helpers = ReturnType<typeof getHelpers>;

View File

@@ -0,0 +1,5 @@
import { Knex } from 'knex';
export abstract class DatabaseHelper {
constructor(protected knex: Knex) {}
}

View File

@@ -9,7 +9,7 @@ import fse from 'fs-extra';
import path from 'path';
import { merge } from 'lodash';
import { promisify } from 'util';
import { getGeometryHelper } from './helpers/geometry';
import { getHelpers } from './helpers';
let database: Knex | null = null;
let inspector: ReturnType<typeof SchemaInspector> | null = null;
@@ -221,11 +221,11 @@ export async function validateMigrations(): Promise<boolean> {
*/
export async function validateDatabaseExtensions(): Promise<void> {
const database = getDatabase();
const databaseClient = getDatabaseClient(database);
const geometryHelper = getGeometryHelper(database);
const geometrySupport = await geometryHelper.supported();
const client = getDatabaseClient(database);
const helpers = getHelpers(database);
const geometrySupport = await helpers.st.supported();
if (!geometrySupport) {
switch (databaseClient) {
switch (client) {
case 'postgres':
logger.warn(`PostGIS isn't installed. Geometry type support will be limited.`);
break;
@@ -233,7 +233,7 @@ export async function validateDatabaseExtensions(): Promise<void> {
logger.warn(`Spatialite isn't installed. Geometry type support will be limited.`);
break;
default:
logger.warn(`Geometry type not supported on ${databaseClient}`);
logger.warn(`Geometry type not supported on ${client}`);
}
}
}

View File

@@ -9,8 +9,8 @@ import { getColumn } from '../utils/get-column';
import { stripFunction } from '../utils/strip-function';
import { toArray } from '@directus/shared/utils';
import { Query } from '@directus/shared/types';
import getDatabase from './index';
import { getGeometryHelper } from '../database/helpers/geometry';
import getDatabase from '.';
import { getHelpers } from '../database/helpers';
type RunASTOptions = {
/**
@@ -174,7 +174,7 @@ async function parseCurrentLevel(
}
function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string) {
const helper = getGeometryHelper();
const helpers = getHelpers(knex);
return function (fieldNode: FieldNode | M2ONode): Knex.Raw<string> {
let field;
@@ -192,7 +192,7 @@ function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string
}
if (field.type.startsWith('geometry')) {
return helper.asText(table, field.field);
return helpers.st.asText(table, field.field);
}
return getColumn(knex, table, fieldNode.name, alias);

View File

@@ -4,7 +4,7 @@ import { Knex } from 'knex';
import { isObject } from 'lodash';
import path from 'path';
import { Type, Field } from '@directus/shared/types';
import { getGeometryHelper } from '../helpers/geometry';
import { getHelpers } from '../helpers';
type TableSeed = {
table: string;
@@ -27,6 +27,7 @@ type TableSeed = {
};
export default async function runSeed(database: Knex): Promise<void> {
const helpers = getHelpers(database);
const exists = await database.schema.hasTable('directus_collections');
if (exists) {
@@ -57,8 +58,7 @@ export default async function runSeed(database: Knex): Promise<void> {
} else if (columnInfo.type === 'hash') {
column = tableBuilder.string(columnName, 255);
} else if (columnInfo.type?.startsWith('geometry')) {
const helper = getGeometryHelper();
column = helper.createColumn(tableBuilder, { field: columnName, type: columnInfo.type } as Field);
column = helpers.st.createColumn(tableBuilder, { field: columnName, type: columnInfo.type } as Field);
} else {
// @ts-ignore
column = tableBuilder[columnInfo.type!](columnName);

View File

@@ -19,11 +19,12 @@ import getLocalType from '../utils/get-local-type';
import { toArray } from '@directus/shared/utils';
import { isEqual, isNil } from 'lodash';
import { RelationsService } from './relations';
import { getGeometryHelper } from '../database/helpers/geometry';
import { getHelpers, Helpers } from '../database/helpers';
import Keyv from 'keyv';
export class FieldsService {
knex: Knex;
helpers: Helpers;
accountability: Accountability | null;
itemsService: ItemsService;
payloadService: PayloadService;
@@ -34,6 +35,7 @@ export class FieldsService {
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || getDatabase();
this.helpers = getHelpers(this.knex);
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
this.accountability = options.accountability || null;
this.itemsService = new ItemsService('directus_fields', options);
@@ -468,8 +470,7 @@ export class FieldsService {
} else if (field.type === 'timestamp') {
column = table.timestamp(field.field, { useTz: true });
} else if (field.type.startsWith('geometry')) {
const helper = getGeometryHelper();
column = helper.createColumn(table, field);
column = this.helpers.st.createColumn(table, field);
} else {
// @ts-ignore
column = table[field.type](field.field);

View File

@@ -10,7 +10,7 @@ import { Accountability, Query } from '@directus/shared/types';
import { toArray } from '@directus/shared/utils';
import { ItemsService } from './items';
import { unflatten } from 'flat';
import { getGeometryHelper } from '../database/helpers/geometry';
import { getHelpers, Helpers } from '../database/helpers';
import { parse as wktToGeoJSON } from 'wellknown';
import { generateHash } from '../utils/generate-hash';
@@ -33,12 +33,14 @@ type Transformers = {
export class PayloadService {
accountability: Accountability | null;
knex: Knex;
helpers: Helpers;
collection: string;
schema: SchemaOverview;
constructor(collection: string, options: AbstractServiceOptions) {
this.accountability = options.accountability || null;
this.knex = options.knex || getDatabase();
this.helpers = getHelpers(this.knex);
this.collection = collection;
this.schema = options.schema;
@@ -225,12 +227,10 @@ export class PayloadService {
* to check if the value is a raw instance before stringifying it in the next step.
*/
processGeometries<T extends Partial<Record<string, any>>[]>(payloads: T, action: Action): T {
const helper = getGeometryHelper();
const process =
action == 'read'
? (value: any) => (typeof value === 'string' ? wktToGeoJSON(value) : value)
: (value: any) => helper.fromGeoJSON(typeof value == 'string' ? JSON.parse(value) : value);
: (value: any) => this.helpers.st.fromGeoJSON(typeof value == 'string' ? JSON.parse(value) : value);
const fieldsInCollection = Object.entries(this.schema.collections[this.collection].fields);
const geometryColumns = fieldsInCollection.filter(([_, field]) => field.type.startsWith('geometry'));

View File

@@ -8,8 +8,7 @@ import { Aggregate, Filter, LogicalFilterAND, Query } from '@directus/shared/typ
import { applyFunctionToColumnName } from './apply-function-to-column-name';
import { getColumn } from './get-column';
import { getRelationType } from './get-relation-type';
import { getGeometryHelper } from '../database/helpers/geometry';
import { getDateHelper } from '../database/helpers/date';
import { getHelpers } from '../database/helpers';
const generateAlias = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
@@ -144,6 +143,7 @@ export function applyFilter(
collection: string,
subQuery = false
) {
const helpers = getHelpers(knex);
const relations: Relation[] = schema.relations;
const aliasMap: Record<string, string> = {};
@@ -371,8 +371,6 @@ export function applyFilter(
});
}
const dateHelper = getDateHelper();
const [collection, field] = key.split('.');
if (collection in schema.collections && field in schema.collections[collection].fields) {
@@ -380,9 +378,9 @@ export function applyFilter(
if (['date', 'dateTime', 'time', 'timestamp'].includes(type)) {
if (Array.isArray(compareValue)) {
compareValue = compareValue.map((val) => dateHelper.parseDate(val));
compareValue = compareValue.map((val) => helpers.date.parse(val));
} else {
compareValue = dateHelper.parseDate(compareValue);
compareValue = helpers.date.parse(compareValue);
}
}
}
@@ -479,21 +477,19 @@ export function applyFilter(
dbQuery[logical].whereNotBetween(selectionRaw, value);
}
const geometryHelper = getGeometryHelper();
if (operator == '_intersects') {
dbQuery[logical].whereRaw(geometryHelper.intersects(key, compareValue));
dbQuery[logical].whereRaw(helpers.st.intersects(key, compareValue));
}
if (operator == '_nintersects') {
dbQuery[logical].whereRaw(geometryHelper.nintersects(key, compareValue));
dbQuery[logical].whereRaw(helpers.st.nintersects(key, compareValue));
}
if (operator == '_intersects_bbox') {
dbQuery[logical].whereRaw(geometryHelper.intersects_bbox(key, compareValue));
dbQuery[logical].whereRaw(helpers.st.intersects_bbox(key, compareValue));
}
if (operator == '_nintersects_bbox') {
dbQuery[logical].whereRaw(geometryHelper.nintersects_bbox(key, compareValue));
dbQuery[logical].whereRaw(helpers.st.nintersects_bbox(key, compareValue));
}
}

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import { FunctionsHelper } from '../database/functions';
import { getHelpers } from '../database/helpers';
import { REGEX_BETWEEN_PARENS } from '@directus/shared/constants';
import { applyFunctionToColumnName } from './apply-function-to-column-name';
@@ -19,14 +19,14 @@ export function getColumn(
column: string,
alias: string | false = applyFunctionToColumnName(column)
): Knex.Raw {
const fn = FunctionsHelper(knex);
const { date: fn } = getHelpers(knex);
if (column.includes('(') && column.includes(')')) {
const functionName = column.split('(')[0];
const columnName = column.match(REGEX_BETWEEN_PARENS)![1];
if (functionName in fn) {
const result = fn[functionName as keyof typeof fn](table, columnName);
const result = fn[functionName as keyof typeof fn](table, columnName) as Knex.Raw;
if (alias) {
return knex.raw(result + ' AS ??', [alias]);