feat: support GEOGRAPHY

This commit is contained in:
aaaaaajie 2025-04-14 19:33:53 +08:00
parent d49d841bd8
commit ac6bbfa44e
9 changed files with 95 additions and 13 deletions

View File

@ -923,7 +923,7 @@ export class Collection<
public getTableNameWithSchemaAsString() { public getTableNameWithSchemaAsString() {
const tableName = this.model.tableName; 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}`; return `${this.collectionSchema()}.${tableName}`;
} }

View File

@ -282,7 +282,7 @@ export class MagicAttributeModel extends Model {
} }
async save(options?: SaveOptions<any>) { async save(options?: SaveOptions<any>) {
if (!options.hooks) { if (!options?.hooks) {
this.db.emit('magicAttributeModel.beforeSave', this, options); this.db.emit('magicAttributeModel.beforeSave', this, options);
} }

View File

@ -531,7 +531,7 @@ describe('dumper', () => {
await db.getRepository('collections').create({ await db.getRepository('collections').create({
values: { values: {
name: 'tests', name: 'tests',
sql: `select count(*) as count sql: `select 1 as id,count(*) as count
from ${userCollection.getTableNameWithSchemaAsString()}`, from ${userCollection.getTableNameWithSchemaAsString()}`,
fields: [ fields: [
{ {

View File

@ -13,12 +13,13 @@ import moment from 'moment/moment';
type WriterFunc = (val: any, database: Database) => any; type WriterFunc = (val: any, database: Database) => any;
const getMapFieldWriter = (field: Field) => { const getMapFieldWriter = (field: Field, database: Database) => {
return (val) => { return (val) => {
const mockObj = { const mockObj = {
setDataValue: (name, newVal) => { setDataValue: (name, newVal) => {
val = newVal; val = newVal;
}, },
database,
}; };
field.options.set.call(mockObj, val); field.options.set.call(mockObj, val);
@ -31,9 +32,14 @@ export class FieldValueWriter {
static write(field: Field, val, database) { static write(field: Field, val, database) {
if (val === null) return val; if (val === null) return val;
const getGeographyType = () => {
if (field.type == 'point' || field.type == 'lineString' || field.type == 'circle' || field.type === 'polygon') { if (field.rawDataType.key?.toLowerCase() === DataTypes.GEOGRAPHY.key.toLowerCase()) {
return getMapFieldWriter(field)(lodash.isString(val) ? JSON.parse(val) : val); return field.rawDataType.type;
}
};
const geographyType = getGeographyType();
if (geographyType) {
return getMapFieldWriter(field, database)(lodash.isString(val) ? JSON.parse(val) : val);
} }
const fieldType = field.typeToString(); const fieldType = field.typeToString();

View File

@ -8,7 +8,8 @@
*/ */
import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; 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 { class Circle extends DataTypes.ABSTRACT {
key = 'Circle'; key = 'Circle';
@ -34,6 +35,9 @@ export class CircleField extends Field {
if (!value?.length) value = null; if (!value?.length) value = null;
else if (isPg(context)) { else if (isPg(context)) {
value = value.join(','); 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); this.setDataValue(name, value);
}, },
@ -46,10 +50,23 @@ export class CircleField extends Field {
get dataType() { get dataType() {
if (isPg(this.context)) { if (isPg(this.context)) {
return Circle; return Circle;
} else if (isMssql(this.context)) {
return DataTypes.STRING;
} else { } else {
return DataTypes.JSON; 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 { export interface CircleFieldOptions extends BaseColumnFieldOptions {

View File

@ -8,7 +8,8 @@
*/ */
import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; 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 { class LineString extends DataTypes.ABSTRACT {
key = 'Path'; key = 'Path';
@ -38,6 +39,10 @@ export class LineStringField extends Field {
type: 'LineString', type: 'LineString',
coordinates: value, 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); this.setDataValue(name, value);
}, },
@ -51,12 +56,26 @@ export class LineStringField extends Field {
if (isPg(this.context)) { if (isPg(this.context)) {
return LineString; return LineString;
} }
if (isMssql(this.context)) {
return DataTypes.STRING;
}
if (isMysql(this.context)) { if (isMysql(this.context)) {
return DataTypes.GEOMETRY('LINESTRING'); return DataTypes.GEOMETRY('LINESTRING');
} else { } else {
return DataTypes.JSON; 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 { export interface LineStringOptions extends BaseColumnFieldOptions {

View File

@ -8,7 +8,8 @@
*/ */
import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; 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 { class Point extends DataTypes.ABSTRACT {
key = 'Point'; key = 'Point';
@ -36,6 +37,9 @@ export class PointField extends Field {
if (!value?.length) value = null; if (!value?.length) value = null;
else if (isPg(context)) { else if (isPg(context)) {
value = joinComma(value); 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)) { } else if (isMysql(context)) {
value = { value = {
type: 'Point', type: 'Point',
@ -54,12 +58,26 @@ export class PointField extends Field {
if (isPg(this.context)) { if (isPg(this.context)) {
return Point; return Point;
} }
if (isMssql(this.context)) {
return DataTypes.STRING;
}
if (isMysql(this.context)) { if (isMysql(this.context)) {
return DataTypes.GEOMETRY('POINT'); return DataTypes.GEOMETRY('POINT');
} else { } else {
return DataTypes.JSON; 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 { export interface PointFieldOptions extends BaseColumnFieldOptions {

View File

@ -8,7 +8,8 @@
*/ */
import { BaseColumnFieldOptions, DataTypes, Field, FieldContext } from '@nocobase/database'; 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 { class Polygon extends DataTypes.ABSTRACT {
key = 'Polygon'; key = 'Polygon';
@ -38,6 +39,10 @@ export class PolygonField extends Field {
type: 'Polygon', type: 'Polygon',
coordinates: [value.concat([value[0]])], 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); this.setDataValue(name, value);
}, },
@ -52,9 +57,22 @@ export class PolygonField extends Field {
return Polygon; return Polygon;
} else if (isMysql(this.context)) { } else if (isMysql(this.context)) {
return DataTypes.GEOMETRY('POLYGON'); 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;
} }
} }

View File

@ -32,3 +32,7 @@ export const isSqlite = (ctx) => {
export const isMysql = (ctx) => { export const isMysql = (ctx) => {
return getDialect(ctx) === 'mysql'; return getDialect(ctx) === 'mysql';
}; };
export const isMssql = (ctx) => {
return getDialect(ctx) === 'mssql';
};