You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用pg-mem测试TypeORM的PostGIS geography列触发报错求助

解决pg-mem兼容TypeORM Geography(Point,4326)类型的问题

问题背景

在TypeORM实体中定义了PostGIS地理类型列:

@Index({ spatial: true })
@Column({
  type: 'geography',
  spatialFeatureType: 'Point',
  srid: 4326,
})
geolocation!: Point;

使用pg-mem做单元测试时,触发SQL解析报错:

CREATE TABLE "user" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "geolocation" geography(Point,4326), ...(other columns)

Unexpected word token: "point". Instead, I was expecting to see one of the following:

  • A "int" token

此前尝试在PostGIS扩展和public模式下注册多种固定字符串的等效类型,但均未匹配成功。

核心原因

pg-mem的SQL解析器无法识别带参数的geography(Point,4326)类型格式,且固定字符串的等效类型注册无法覆盖动态参数场景(比如大小写差异、空格变化等)。

正确解决方案

通过函数式匹配注册等效类型,覆盖所有geography(...)格式的类型定义,同时确保PostGIS扩展正确加载:

import { DataType, newDb } from 'pg-mem';
import { Point } from 'typeorm';

// 创建pg-mem实例
const db = newDb();

// 注册PostGIS扩展,处理所有geography类型
db.registerExtension('postgis', (schema) => {
  // 匹配基础geography类型
  schema.registerEquivalentType({
    name: 'geography',
    equivalentTo: DataType.point,
    isValid: (val) => val instanceof Point || val === null || val === undefined,
  });

  // 关键:匹配所有带参数的geography(...)类型(比如geography(Point,4326))
  schema.registerEquivalentType({
    name: (typeName: string) => typeName.toLowerCase().startsWith('geography('),
    equivalentTo: DataType.point,
    isValid: (val) => val instanceof Point || val === null || val === undefined,
  });
});

// 加载PostGIS扩展到public模式
db.public.none('CREATE EXTENSION postgis');

// 后续连接TypeORM到pg-mem实例即可

补充说明

  1. 函数式匹配typeName.toLowerCase().startsWith('geography(')可以忽略大小写和参数差异,覆盖所有地理类型的变体;
  2. isValid判断精准匹配TypeORM的Point类型,避免非法值写入;
  3. 必须在创建表之前执行扩展注册和加载操作,确保pg-mem解析SQL时能识别地理类型。

替代方案(自定义类型解析)

如果需要更精准的序列化/解析逻辑,可以直接注册自定义类型:

db.public.registerType({
  name: 'geography',
  oid: 10150, // 自定义唯一OID,避免冲突
  parse: (input: string) => {
    // 解析WKT格式为TypeORM Point对象
    const wktMatch = input.match(/^\s*POINT\s*\((\S+)\s+(\S+)\)\s*$/i);
    if (wktMatch) {
      return new Point(parseFloat(wktMatch[1]), parseFloat(wktMatch[2]));
    }
    return null;
  },
  serialize: (point: Point) => {
    // 将TypeORM Point序列化为WKT格式
    return `POINT(${point.x} ${point.y})`;
  },
});

// 同时匹配带参数的地理类型
db.public.registerEquivalentType({
  name: (name) => name.toLowerCase().includes('geography'),
  equivalentTo: DataType.point,
});

内容的提问来源于stack exchange,提问作者MaxM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 20:43:17