Skip to content

Commit

Permalink
[pg] Add implementation of geography type for postgis exension
Browse files Browse the repository at this point in the history
  • Loading branch information
robinsimonklein committed Sep 28, 2024
1 parent c8359a1 commit b62b3f1
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 32 deletions.
1 change: 1 addition & 0 deletions drizzle-orm/src/pg-core/columns/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './macaddr8.ts';
export * from './numeric.ts';
export * from './point.ts';
export * from './postgis_extension/geometry.ts';
export * from './postgis_extension/geography.ts';
export * from './real.ts';
export * from './serial.ts';
export * from './smallint.ts';
Expand Down
116 changes: 116 additions & 0 deletions drizzle-orm/src/pg-core/columns/postgis_extension/geography.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts';
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyPgTable } from '~/pg-core/table.ts';

import type { Equal } from '~/utils.ts';
import { PgColumn, PgColumnBuilder } from '../common.ts';
import { parseEWKB } from './utils.ts';

export type PgGeographyBuilderInitial<TName extends string> = PgGeographyBuilder<{
name: TName;
dataType: 'array';
columnType: 'PgGeography';
data: [number, number];
driverParam: string;
enumValues: undefined;
generated: undefined;
}>;

export class PgGeographyBuilder<T extends ColumnBuilderBaseConfig<'array', 'PgGeography'>> extends PgColumnBuilder<T> {
static readonly [entityKind]: string = 'PgGeographyBuilder';

constructor(name: T['name']) {
super(name, 'array', 'PgGeography');
}

/** @internal */
override build<TTableName extends string>(
table: AnyPgTable<{ name: TTableName }>,
): PgGeography<MakeColumnConfig<T, TTableName>> {
return new PgGeography<MakeColumnConfig<T, TTableName>>(
table,
this.config as ColumnBuilderRuntimeConfig<any, any>,
);
}
}

export class PgGeography<T extends ColumnBaseConfig<'array', 'PgGeography'>> extends PgColumn<T> {
static readonly [entityKind]: string = 'PgGeography';

getSQLType(): string {
return 'geography(point)';
}

override mapFromDriverValue(value: string): [number, number] {
return parseEWKB(value);
}

override mapToDriverValue(value: [number, number]): string {
return `point(${value[0]} ${value[1]})`;
}
}

export type PgGeographyObjectBuilderInitial<TName extends string> = PgGeographyObjectBuilder<{
name: TName;
dataType: 'json';
columnType: 'PgGeographyObject';
data: { lon: number; lat: number };
driverParam: string;
enumValues: undefined;
generated: undefined;
}>;

export class PgGeographyObjectBuilder<T extends ColumnBuilderBaseConfig<'json', 'PgGeographyObject'>>
extends PgColumnBuilder<T>
{
static readonly [entityKind]: string = 'PgGeographyObjectBuilder';

constructor(name: T['name']) {
super(name, 'json', 'PgGeographyObject');
}

/** @internal */
override build<TTableName extends string>(
table: AnyPgTable<{ name: TTableName }>,
): PgGeographyObject<MakeColumnConfig<T, TTableName>> {
return new PgGeographyObject<MakeColumnConfig<T, TTableName>>(
table,
this.config as ColumnBuilderRuntimeConfig<any, any>,
);
}
}

export class PgGeographyObject<T extends ColumnBaseConfig<'json', 'PgGeographyObject'>> extends PgColumn<T> {
static readonly [entityKind]: string = 'PgGeographyObject';

getSQLType(): string {
return 'geography(point)';
}

override mapFromDriverValue(value: string): { lon: number; lat: number } {
const parsed = parseEWKB(value);
return { lon: parsed[0], lat: parsed[1] };
}

override mapToDriverValue(value: { lon: number; lat: number }): string {
return `point(${value.lon} ${value.lat})`;
}
}

interface PgGeographyConfig<T extends 'tuple' | 'json' = 'tuple' | 'json'> {
mode?: T;
type?: 'point' | (string & {});
}

export function geography<TName extends string, TMode extends PgGeographyConfig['mode'] & {}>(
name: TName,
config?: PgGeographyConfig<TMode>,
): Equal<TMode, 'json'> extends true ? PgGeographyObjectBuilderInitial<TName>
: PgGeographyBuilderInitial<TName>;
export function geography(name: string, config?: PgGeographyConfig) {
if (!config?.mode || config.mode === 'tuple') {
return new PgGeographyBuilder(name);
}
return new PgGeographyObjectBuilder(name);
}
42 changes: 26 additions & 16 deletions integration-tests/tests/extensions/postgis/pg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Docker from 'dockerode';
import { sql } from 'drizzle-orm';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { drizzle } from 'drizzle-orm/node-postgres';
import { bigserial, geometry, line, pgTable, point } from 'drizzle-orm/pg-core';
import { bigserial, geometry, geography, line, pgTable, point } from 'drizzle-orm/pg-core';
import getPort from 'get-port';
import pg from 'pg';
import { v4 as uuid } from 'uuid';
Expand Down Expand Up @@ -85,9 +85,11 @@ const items = pgTable('items', {
pointObj: point('point_xy', { mode: 'xy' }),
line: line('line'),
lineObj: line('line_abc', { mode: 'abc' }),
geo: geometry('geo', { type: 'point' }),
geoObj: geometry('geo_obj', { type: 'point', mode: 'xy' }),
geoSrid: geometry('geo_options', { type: 'point', mode: 'xy', srid: 4000 }),
geometry: geometry('geometry', { type: 'point' }),
geometryObj: geometry('geometry_obj', { type: 'point', mode: 'xy' }),
geometrySrid: geometry('geometry_options', { type: 'point', mode: 'xy', srid: 4000 }),
geography: geometry('geography', { type: 'point' }),
geographyObj: geography('geography_obj', { type: 'point', mode: 'json' }),
});

beforeEach(async () => {
Expand All @@ -99,9 +101,11 @@ beforeEach(async () => {
"point_xy" point,
"line" line,
"line_abc" line,
"geo" geometry(point),
"geo_obj" geometry(point),
"geo_options" geometry(point,4000)
"geometry" geometry(point),
"geometry_obj" geometry(point),
"geometry_options" geometry(point,4000),
"geography" geography(point),
"geography_obj" geography(point)
);
`);
});
Expand All @@ -112,9 +116,11 @@ test('insert + select', async () => {
pointObj: { x: 1, y: 2 },
line: [1, 2, 3],
lineObj: { a: 1, b: 2, c: 3 },
geo: [1, 2],
geoObj: { x: 1, y: 2 },
geoSrid: { x: 1, y: 2 },
geometry: [1, 2],
geometryObj: { x: 1, y: 2 },
geometrySrid: { x: 1, y: 2 },
geography: [1, 2],
geographyObj: { lon: 1, lat: 2 },
}]).returning();

const response = await db.select().from(items);
Expand All @@ -125,9 +131,11 @@ test('insert + select', async () => {
pointObj: { x: 1, y: 2 },
line: [1, 2, 3],
lineObj: { a: 1, b: 2, c: 3 },
geo: [1, 2],
geoObj: { x: 1, y: 2 },
geoSrid: { x: 1, y: 2 },
geometry: [1, 2],
geometryObj: { x: 1, y: 2 },
geometrySrid: { x: 1, y: 2 },
geography: [1, 2],
geographyObj: { lon: 1, lat: 2 },
}]);

expect(response).toStrictEqual([{
Expand All @@ -136,8 +144,10 @@ test('insert + select', async () => {
pointObj: { x: 1, y: 2 },
line: [1, 2, 3],
lineObj: { a: 1, b: 2, c: 3 },
geo: [1, 2],
geoObj: { x: 1, y: 2 },
geoSrid: { x: 1, y: 2 },
geometry: [1, 2],
geometryObj: { x: 1, y: 2 },
geometrySrid: { x: 1, y: 2 },
geography: [1, 2],
geographyObj: { lon: 1, lat: 2 },
}]);
});
42 changes: 26 additions & 16 deletions integration-tests/tests/extensions/postgis/postgres.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Docker from 'dockerode';
import { sql } from 'drizzle-orm';
import { bigserial, geometry, line, pgTable, point } from 'drizzle-orm/pg-core';
import {bigserial, geometry, geography, line, pgTable, point} from 'drizzle-orm/pg-core';
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import getPort from 'get-port';
import postgres, { type Sql } from 'postgres';
Expand Down Expand Up @@ -87,9 +87,11 @@ const items = pgTable('items', {
pointObj: point('point_xy', { mode: 'xy' }),
line: line('line'),
lineObj: line('line_abc', { mode: 'abc' }),
geo: geometry('geo', { type: 'point' }),
geoObj: geometry('geo_obj', { type: 'point', mode: 'xy' }),
geoSrid: geometry('geo_options', { type: 'point', mode: 'xy', srid: 4000 }),
geometry: geometry('geometry', { type: 'point' }),
geometryObj: geometry('geometry_obj', { type: 'point', mode: 'xy' }),
geometrySrid: geometry('geometry_options', { type: 'point', mode: 'xy', srid: 4000 }),
geography: geometry('geography', { type: 'point' }),
geographyObj: geography('geography_obj', { type: 'point', mode: 'json' }),
});

beforeEach(async () => {
Expand All @@ -101,9 +103,11 @@ beforeEach(async () => {
"point_xy" point,
"line" line,
"line_abc" line,
"geo" geometry(point),
"geo_obj" geometry(point),
"geo_options" geometry(point,4000)
"geometry" geometry(point),
"geometry_obj" geometry(point),
"geometry_options" geometry(point,4000),
"geography" geography(point),
"geography_obj" geography(point)
);
`);
});
Expand All @@ -114,9 +118,11 @@ test('insert + select', async () => {
pointObj: { x: 1, y: 2 },
line: [1, 2, 3],
lineObj: { a: 1, b: 2, c: 3 },
geo: [1, 2],
geoObj: { x: 1, y: 2 },
geoSrid: { x: 1, y: 2 },
geometry: [1, 2],
geometryObj: { x: 1, y: 2 },
geometrySrid: { x: 1, y: 2 },
geography: [1, 2],
geographyObj: { lon: 1, lat: 2 },
}]).returning();

const response = await db.select().from(items);
Expand All @@ -127,9 +133,11 @@ test('insert + select', async () => {
pointObj: { x: 1, y: 2 },
line: [1, 2, 3],
lineObj: { a: 1, b: 2, c: 3 },
geo: [1, 2],
geoObj: { x: 1, y: 2 },
geoSrid: { x: 1, y: 2 },
geometry: [1, 2],
geometryObj: { x: 1, y: 2 },
geometrySrid: { x: 1, y: 2 },
geography: [1, 2],
geographyObj: { lon: 1, lat: 2 },
}]);

expect(response).toStrictEqual([{
Expand All @@ -138,8 +146,10 @@ test('insert + select', async () => {
pointObj: { x: 1, y: 2 },
line: [1, 2, 3],
lineObj: { a: 1, b: 2, c: 3 },
geo: [1, 2],
geoObj: { x: 1, y: 2 },
geoSrid: { x: 1, y: 2 },
geometry: [1, 2],
geometryObj: { x: 1, y: 2 },
geometrySrid: { x: 1, y: 2 },
geography: [1, 2],
geographyObj: { lon: 1, lat: 2 },
}]);
});

0 comments on commit b62b3f1

Please sign in to comment.