From ac6bbfa44efec40a5fb99387321c5f40cbcdc259 Mon Sep 17 00:00:00 2001 From: aaaaaajie Date: Mon, 14 Apr 2025 19:33:53 +0800 Subject: [PATCH] feat: support GEOGRAPHY --- packages/core/database/src/collection.ts | 2 +- .../database/src/magic-attribute-model.ts | 2 +- .../src/server/__tests__/dumper.test.ts | 2 +- .../src/server/field-value-writer.ts | 14 +++++++---- .../plugin-map/src/server/fields/circle.ts | 19 ++++++++++++++- .../src/server/fields/lineString.ts | 21 +++++++++++++++- .../plugin-map/src/server/fields/point.ts | 20 +++++++++++++++- .../plugin-map/src/server/fields/polygon.ts | 24 ++++++++++++++++--- .../plugin-map/src/server/helpers/index.ts | 4 ++++ 9 files changed, 95 insertions(+), 13 deletions(-) diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index 9e803d9e3d..0874095ea0 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -923,7 +923,7 @@ export class Collection< public getTableNameWithSchemaAsString() { const tableName = this.model.tableName; - if (this.collectionSchema() && (this.db.inDialect('postgres') || this.db.inDialect('mssql'))) { + if (this.collectionSchema() && this.db.inDialect('postgres', 'mssql')) { return `${this.collectionSchema()}.${tableName}`; } diff --git a/packages/core/database/src/magic-attribute-model.ts b/packages/core/database/src/magic-attribute-model.ts index 265abd74f4..46f0e1181a 100644 --- a/packages/core/database/src/magic-attribute-model.ts +++ b/packages/core/database/src/magic-attribute-model.ts @@ -282,7 +282,7 @@ export class MagicAttributeModel extends Model { } async save(options?: SaveOptions) { - if (!options.hooks) { + if (!options?.hooks) { this.db.emit('magicAttributeModel.beforeSave', this, options); } diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts index 2dc78f594f..482d88f735 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts @@ -531,7 +531,7 @@ describe('dumper', () => { await db.getRepository('collections').create({ values: { name: 'tests', - sql: `select count(*) as count + sql: `select 1 as id,count(*) as count from ${userCollection.getTableNameWithSchemaAsString()}`, fields: [ { diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts index fcd8bfefd0..381b92e3d7 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts @@ -13,12 +13,13 @@ import moment from 'moment/moment'; type WriterFunc = (val: any, database: Database) => any; -const getMapFieldWriter = (field: Field) => { +const getMapFieldWriter = (field: Field, database: Database) => { return (val) => { const mockObj = { setDataValue: (name, newVal) => { val = newVal; }, + database, }; field.options.set.call(mockObj, val); @@ -31,9 +32,14 @@ export class FieldValueWriter { static write(field: Field, val, database) { if (val === null) return val; - - if (field.type == 'point' || field.type == 'lineString' || field.type == 'circle' || field.type === 'polygon') { - return getMapFieldWriter(field)(lodash.isString(val) ? JSON.parse(val) : val); + const getGeographyType = () => { + if (field.rawDataType.key?.toLowerCase() === DataTypes.GEOGRAPHY.key.toLowerCase()) { + return field.rawDataType.type; + } + }; + const geographyType = getGeographyType(); + if (geographyType) { + return getMapFieldWriter(field, database)(lodash.isString(val) ? JSON.parse(val) : val); } const fieldType = field.typeToString(); diff --git a/packages/plugins/@nocobase/plugin-map/src/server/fields/circle.ts b/packages/plugins/@nocobase/plugin-map/src/server/fields/circle.ts index 125f1d2248..434911145d 100644 --- a/packages/plugins/@nocobase/plugin-map/src/server/fields/circle.ts +++ b/packages/plugins/@nocobase/plugin-map/src/server/fields/circle.ts @@ -8,7 +8,8 @@ */ import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; -import { isPg, toValue } from '../helpers'; +import { isMssql, isPg, toValue } from '../helpers'; +import _ from 'lodash'; class Circle extends DataTypes.ABSTRACT { key = 'Circle'; @@ -34,6 +35,9 @@ export class CircleField extends Field { if (!value?.length) value = null; else if (isPg(context)) { value = value.join(','); + } else if (isMssql(context)) { + const [lat, lng] = value; + value = this.database?.sequelize.literal(`geography::Point(${lat}, ${lng}, 4326)`); } this.setDataValue(name, value); }, @@ -46,10 +50,23 @@ export class CircleField extends Field { get dataType() { if (isPg(this.context)) { return Circle; + } else if (isMssql(this.context)) { + return DataTypes.STRING; } else { return DataTypes.JSON; } } + + get rawDataType() { + return DataTypes.GEOGRAPHY; + } + + setter(value, options) { + if (isMssql(this.context) && _.isObjectLike(value)) { + return JSON.stringify(value); + } + return value; + } } export interface CircleFieldOptions extends BaseColumnFieldOptions { diff --git a/packages/plugins/@nocobase/plugin-map/src/server/fields/lineString.ts b/packages/plugins/@nocobase/plugin-map/src/server/fields/lineString.ts index ea208709bf..5b66d62bb8 100644 --- a/packages/plugins/@nocobase/plugin-map/src/server/fields/lineString.ts +++ b/packages/plugins/@nocobase/plugin-map/src/server/fields/lineString.ts @@ -8,7 +8,8 @@ */ import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; -import { isMysql, isPg, joinComma, toValue } from '../helpers'; +import { isMssql, isMysql, isPg, joinComma, toValue } from '../helpers'; +import _ from 'lodash'; class LineString extends DataTypes.ABSTRACT { key = 'Path'; @@ -38,6 +39,10 @@ export class LineStringField extends Field { type: 'LineString', coordinates: value, }; + } else if (isMssql(context)) { + const [lat, lng] = value; + const coordStr = `${lng} ${lat}`; + value = this.database?.sequelize.literal(`geography::STLineFromText('LINESTRING(${coordStr})', 4326)`); } this.setDataValue(name, value); }, @@ -51,12 +56,26 @@ export class LineStringField extends Field { if (isPg(this.context)) { return LineString; } + if (isMssql(this.context)) { + return DataTypes.STRING; + } if (isMysql(this.context)) { return DataTypes.GEOMETRY('LINESTRING'); } else { return DataTypes.JSON; } } + + get rawDataType() { + return DataTypes.GEOGRAPHY; + } + + setter(value, options) { + if (isMssql(this.context) && _.isObjectLike(value)) { + return JSON.stringify(value); + } + return value; + } } export interface LineStringOptions extends BaseColumnFieldOptions { diff --git a/packages/plugins/@nocobase/plugin-map/src/server/fields/point.ts b/packages/plugins/@nocobase/plugin-map/src/server/fields/point.ts index fb17b39929..35fd86fa16 100644 --- a/packages/plugins/@nocobase/plugin-map/src/server/fields/point.ts +++ b/packages/plugins/@nocobase/plugin-map/src/server/fields/point.ts @@ -8,7 +8,8 @@ */ import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; -import { isMysql, isPg, joinComma, toValue } from '../helpers'; +import { isMssql, isMysql, isPg, joinComma, toValue } from '../helpers'; +import _ from 'lodash'; class Point extends DataTypes.ABSTRACT { key = 'Point'; @@ -36,6 +37,9 @@ export class PointField extends Field { if (!value?.length) value = null; else if (isPg(context)) { value = joinComma(value); + } else if (isMssql(context)) { + const [lat, lng] = value; + value = this.database?.sequelize.literal(`geography::Point(${lat}, ${lng}, 4326)`); } else if (isMysql(context)) { value = { type: 'Point', @@ -54,12 +58,26 @@ export class PointField extends Field { if (isPg(this.context)) { return Point; } + if (isMssql(this.context)) { + return DataTypes.STRING; + } if (isMysql(this.context)) { return DataTypes.GEOMETRY('POINT'); } else { return DataTypes.JSON; } } + + get rawDataType() { + return DataTypes.GEOGRAPHY; + } + + setter(value, options) { + if (isMssql(this.context) && _.isObjectLike(value)) { + return JSON.stringify(value); + } + return value; + } } export interface PointFieldOptions extends BaseColumnFieldOptions { diff --git a/packages/plugins/@nocobase/plugin-map/src/server/fields/polygon.ts b/packages/plugins/@nocobase/plugin-map/src/server/fields/polygon.ts index 4212e4eb7e..088b1a0abe 100644 --- a/packages/plugins/@nocobase/plugin-map/src/server/fields/polygon.ts +++ b/packages/plugins/@nocobase/plugin-map/src/server/fields/polygon.ts @@ -8,7 +8,8 @@ */ import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; -import { isMysql, isPg, joinComma, toValue } from '../helpers'; +import { isMssql, isMysql, isPg, joinComma, toValue } from '../helpers'; +import _ from 'lodash'; class Polygon extends DataTypes.ABSTRACT { key = 'Polygon'; @@ -38,6 +39,10 @@ export class PolygonField extends Field { type: 'Polygon', coordinates: [value.concat([value[0]])], }; + } else if (isMssql(context)) { + const [lat, lng] = value; + const coordStr = `${lng} ${lat}`; + value = this.database?.sequelize.literal(`geography::STPolyFromText('POLYGON((${coordStr}))', 4326)`); } this.setDataValue(name, value); }, @@ -52,9 +57,22 @@ export class PolygonField extends Field { return Polygon; } else if (isMysql(this.context)) { return DataTypes.GEOMETRY('POLYGON'); - } else { - return DataTypes.JSON; } + if (isMssql(this.context)) { + return DataTypes.STRING; + } + return DataTypes.JSON; + } + + get rawDataType() { + return DataTypes.GEOGRAPHY; + } + + setter(value, options) { + if (isMssql(this.context) && _.isObjectLike(value)) { + return JSON.stringify(value); + } + return value; } } diff --git a/packages/plugins/@nocobase/plugin-map/src/server/helpers/index.ts b/packages/plugins/@nocobase/plugin-map/src/server/helpers/index.ts index 889fe26490..f2d2f0201c 100644 --- a/packages/plugins/@nocobase/plugin-map/src/server/helpers/index.ts +++ b/packages/plugins/@nocobase/plugin-map/src/server/helpers/index.ts @@ -32,3 +32,7 @@ export const isSqlite = (ctx) => { export const isMysql = (ctx) => { return getDialect(ctx) === 'mysql'; }; + +export const isMssql = (ctx) => { + return getDialect(ctx) === 'mssql'; +};