Skip to content

Commit b841573

Browse files
authored
Merge pull request #864 from constructive-io/devin/1774048366-postgis-enhancements
feat(graphile-postgis): implement all missing PostGIS features
2 parents f67b6f3 + 4631f9e commit b841573

13 files changed

Lines changed: 1629 additions & 10 deletions
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { PostgisAggregatePlugin } from '../src/plugins/aggregate-functions';
2+
3+
interface MockExtensionInfo {
4+
schemaName: string;
5+
geometryCodec: { name: string };
6+
geographyCodec: { name: string } | null;
7+
}
8+
9+
interface MockBuild {
10+
pgGISExtensionInfo: MockExtensionInfo | undefined;
11+
extend?(base: Record<string, unknown>, extra: Record<string, unknown>, reason: string): Record<string, unknown>;
12+
}
13+
14+
function createMockBuild(schemaName = 'public', hasPostgis = true): MockBuild {
15+
return {
16+
pgGISExtensionInfo: hasPostgis
17+
? {
18+
schemaName,
19+
geometryCodec: { name: 'geometry' },
20+
geographyCodec: null
21+
}
22+
: undefined,
23+
extend(base: Record<string, unknown>, extra: Record<string, unknown>, _reason: string) {
24+
return { ...base, ...extra };
25+
}
26+
};
27+
}
28+
29+
describe('PostgisAggregatePlugin', () => {
30+
describe('plugin metadata', () => {
31+
it('should have correct name', () => {
32+
expect(PostgisAggregatePlugin.name).toBe('PostgisAggregatePlugin');
33+
});
34+
35+
it('should have correct version', () => {
36+
expect(PostgisAggregatePlugin.version).toBe('1.0.0');
37+
});
38+
39+
it('should declare after dependencies', () => {
40+
expect(PostgisAggregatePlugin.after).toContain('PostgisRegisterTypesPlugin');
41+
expect(PostgisAggregatePlugin.after).toContain('PostgisExtensionDetectionPlugin');
42+
});
43+
});
44+
45+
describe('build hook', () => {
46+
const buildHook = (PostgisAggregatePlugin as any).schema.hooks.build;
47+
48+
it('should return build unchanged when PostGIS is not available', () => {
49+
const build = createMockBuild('public', false);
50+
const result = buildHook(build);
51+
expect(result).toBe(build);
52+
expect(result.pgGISAggregateFunctions).toBeUndefined();
53+
});
54+
55+
it('should add pgGISAggregateFunctions to build when PostGIS is available', () => {
56+
const build = createMockBuild();
57+
const result = buildHook(build);
58+
expect(result.pgGISAggregateFunctions).toBeDefined();
59+
});
60+
61+
it('should define stExtent aggregate function', () => {
62+
const result = buildHook(createMockBuild());
63+
const { stExtent } = result.pgGISAggregateFunctions;
64+
expect(stExtent).toBeDefined();
65+
expect(stExtent.sqlFunction).toBe('public.st_extent');
66+
expect(stExtent.returnsGeometry).toBe(true);
67+
expect(stExtent.description).toContain('Bounding box');
68+
});
69+
70+
it('should define stUnion aggregate function', () => {
71+
const result = buildHook(createMockBuild());
72+
const { stUnion } = result.pgGISAggregateFunctions;
73+
expect(stUnion).toBeDefined();
74+
expect(stUnion.sqlFunction).toBe('public.st_union');
75+
expect(stUnion.returnsGeometry).toBe(true);
76+
});
77+
78+
it('should define stCollect aggregate function', () => {
79+
const result = buildHook(createMockBuild());
80+
const { stCollect } = result.pgGISAggregateFunctions;
81+
expect(stCollect).toBeDefined();
82+
expect(stCollect.sqlFunction).toBe('public.st_collect');
83+
expect(stCollect.returnsGeometry).toBe(true);
84+
});
85+
86+
it('should define stConvexHull aggregate function with requiresCollect flag', () => {
87+
const result = buildHook(createMockBuild());
88+
const { stConvexHull } = result.pgGISAggregateFunctions;
89+
expect(stConvexHull).toBeDefined();
90+
expect(stConvexHull.sqlFunction).toBe('public.st_convexhull');
91+
expect(stConvexHull.returnsGeometry).toBe(true);
92+
expect(stConvexHull.requiresCollect).toBe(true);
93+
});
94+
95+
it('should use the correct schema name', () => {
96+
const result = buildHook(createMockBuild('postgis_ext'));
97+
expect(result.pgGISAggregateFunctions.stExtent.sqlFunction).toBe('postgis_ext.st_extent');
98+
expect(result.pgGISAggregateFunctions.stUnion.sqlFunction).toBe('postgis_ext.st_union');
99+
expect(result.pgGISAggregateFunctions.stCollect.sqlFunction).toBe('postgis_ext.st_collect');
100+
expect(result.pgGISAggregateFunctions.stConvexHull.sqlFunction).toBe('postgis_ext.st_convexhull');
101+
});
102+
103+
it('should define exactly 4 aggregate functions', () => {
104+
const result = buildHook(createMockBuild());
105+
expect(Object.keys(result.pgGISAggregateFunctions)).toHaveLength(4);
106+
});
107+
});
108+
});

graphile/graphile-postgis/__tests__/connection-filter-operators.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,12 @@ function runFactory(options: {
8686
describe('PostGIS operator factory (createPostgisOperatorFactory)', () => {
8787
describe('preset', () => {
8888
it('declares the factory in connectionFilterOperatorFactories', () => {
89-
const factories = GraphilePostgisPreset.schema?.connectionFilterOperatorFactories;
89+
const schema = GraphilePostgisPreset.schema as Record<string, unknown> | undefined;
90+
const factories = schema?.connectionFilterOperatorFactories as unknown[] | undefined;
9091
expect(factories).toBeDefined();
91-
expect(factories).toHaveLength(1);
92+
expect(factories).toHaveLength(2);
9293
expect(typeof factories![0]).toBe('function');
94+
expect(typeof factories![1]).toBe('function');
9395
});
9496
});
9597

graphile/graphile-postgis/__tests__/index.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ describe('graphile-postgis exports', () => {
1111
expect(postgisExports.PostgisExtensionDetectionPlugin).toBeDefined();
1212
expect(postgisExports.PostgisRegisterTypesPlugin).toBeDefined();
1313
expect(postgisExports.PostgisGeometryFieldsPlugin).toBeDefined();
14+
expect(postgisExports.PostgisMeasurementFieldsPlugin).toBeDefined();
15+
expect(postgisExports.PostgisTransformationFieldsPlugin).toBeDefined();
16+
expect(postgisExports.PostgisAggregatePlugin).toBeDefined();
1417
});
1518

1619
it('should export constants', () => {
@@ -36,6 +39,9 @@ describe('graphile-postgis exports', () => {
3639
'PostgisExtensionDetectionPlugin',
3740
'PostgisRegisterTypesPlugin',
3841
'PostgisGeometryFieldsPlugin',
42+
'PostgisMeasurementFieldsPlugin',
43+
'PostgisTransformationFieldsPlugin',
44+
'PostgisAggregatePlugin',
3945
// Constants
4046
'GisSubtype',
4147
'SUBTYPE_STRING_BY_SUBTYPE',
@@ -45,8 +51,9 @@ describe('graphile-postgis exports', () => {
4551
'getGISTypeDetails',
4652
'getGISTypeModifier',
4753
'getGISTypeName',
48-
// Connection filter operator factory
49-
'createPostgisOperatorFactory'
54+
// Connection filter operator factories
55+
'createPostgisOperatorFactory',
56+
'createWithinDistanceOperatorFactory'
5057
];
5158

5259
const actualExports = Object.keys(postgisExports);

0 commit comments

Comments
 (0)