mirror of
https://github.com/directus/directus.git
synced 2026-02-04 15:45:07 -05:00
Improve helpers structure (#10052)
* Improve helpers structure * Added DatabaseHelper base class * Refactor index.ts
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
6
api/src/database/helpers/date/index.ts
Normal file
6
api/src/database/helpers/date/index.ts
Normal 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';
|
||||
16
api/src/database/helpers/date/types.ts
Normal file
16
api/src/database/helpers/date/types.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
36
api/src/database/helpers/geometry/dialects/mssql.ts
Normal file
36
api/src/database/helpers/geometry/dialects/mssql.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
17
api/src/database/helpers/geometry/dialects/mysql.ts
Normal file
17
api/src/database/helpers/geometry/dialects/mysql.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
39
api/src/database/helpers/geometry/dialects/oracle.ts
Normal file
39
api/src/database/helpers/geometry/dialects/oracle.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
22
api/src/database/helpers/geometry/dialects/postgres.ts
Normal file
22
api/src/database/helpers/geometry/dialects/postgres.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
15
api/src/database/helpers/geometry/dialects/redshift.ts
Normal file
15
api/src/database/helpers/geometry/dialects/redshift.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
12
api/src/database/helpers/geometry/dialects/sqlite.ts
Normal file
12
api/src/database/helpers/geometry/dialects/sqlite.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
6
api/src/database/helpers/geometry/index.ts
Normal file
6
api/src/database/helpers/geometry/index.ts
Normal 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';
|
||||
52
api/src/database/helpers/geometry/types.ts
Normal file
52
api/src/database/helpers/geometry/types.ts
Normal 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]);
|
||||
}
|
||||
}
|
||||
15
api/src/database/helpers/index.ts
Normal file
15
api/src/database/helpers/index.ts
Normal 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>;
|
||||
5
api/src/database/helpers/types.ts
Normal file
5
api/src/database/helpers/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export abstract class DatabaseHelper {
|
||||
constructor(protected knex: Knex) {}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user