From 856c2ae3135ccfd49d84abb230599e37acb54e9a Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Sun, 17 May 2026 07:56:20 -0400 Subject: [PATCH] fix(postgres): inferType returns ::text[] for empty/all-null arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously `inferType([])`, `inferType([null])`, and `inferType([null, null])` fell through the array branch (which couldn't determine an inner type) into the generic object branch and returned `::jsonb` / `::jsonb[]`. Both shapes are incompatible with `= ANY ($N::jsonb)` on CockroachDB: ERROR: unsupported comparison operator: _id = ANY $2::JSONB: op ANY requires array, tuple or subquery on right side The bug is reachable from the GitHub integration's sync loop. When a workspace has the App installed but no repositories linked, `pod-github`'s performSync submits a $in query of shape `[null]` for the `repository` field; the postgres adapter then emits SQL the database refuses, and the github pod crash-loops indefinitely on that workspace. (Stock Postgres is more lenient than CockroachDB and may silently accept the ::jsonb cast, masking the bug there.) Fix the array branch in inferType to skip null/undefined when sampling the inner type, and fall back to `::text[]` (a real SQL array type) when no inner type can be inferred. `$in`-on-Ref callers already expect that shape, and ANY accepts it on both Postgres and CockroachDB. Existing tests in utils.spec.ts had explicit `// BUG:` comments acknowledging these three cases — flipped to assert the fixed behavior, and added a `[null]` case (the exact array shape that crashed the github pod). Signed-off-by: Don Kendall --- .../postgres/src/__tests__/utils.spec.ts | 19 +++++++++++-------- .../server/packages/postgres/src/utils.ts | 13 ++++++++++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/foundations/server/packages/postgres/src/__tests__/utils.spec.ts b/foundations/server/packages/postgres/src/__tests__/utils.spec.ts index 1940cc8d7ab..11aea7d4dad 100644 --- a/foundations/server/packages/postgres/src/__tests__/utils.spec.ts +++ b/foundations/server/packages/postgres/src/__tests__/utils.spec.ts @@ -39,20 +39,23 @@ describe('utils - inferType', () => { expect(inferType([1, 2, 3])).toBe('::numeric[]') }) - it('should handle empty arrays', () => { - // BUG: Empty arrays are treated as objects and return '::jsonb' - // Expected behavior would be to return '' or handle specially - expect(inferType([])).toBe('::jsonb') + it('should handle empty arrays as text arrays', () => { + // Empty arrays fall back to '::text[]' so `= ANY` stays valid on CockroachDB. + expect(inferType([])).toBe('::text[]') }) it('should handle arrays with null first element', () => { expect(inferType([null, 'text'])).toBe('::text[]') }) - it('should handle arrays with all null elements', () => { - // BUG: Arrays with only null elements return '::jsonb[]' - // Expected: Should probably return '' or handle as empty array - expect(inferType([null, null])).toBe('::jsonb[]') + it('should handle single-null arrays as text arrays', () => { + // [null] is what the github integration submits when a workspace has the + // App installed but no repositories enabled; must not infer ::jsonb. + expect(inferType([null])).toBe('::text[]') + }) + + it('should handle arrays with all null elements as text arrays', () => { + expect(inferType([null, null])).toBe('::text[]') }) it('should infer Date type as text', () => { diff --git a/foundations/server/packages/postgres/src/utils.ts b/foundations/server/packages/postgres/src/utils.ts index 469904e3a93..ca8cb497639 100644 --- a/foundations/server/packages/postgres/src/utils.ts +++ b/foundations/server/packages/postgres/src/utils.ts @@ -255,10 +255,17 @@ export function inferType (val: any): string { return '::boolean' } if (Array.isArray(val)) { - const type = inferType(val[0] ?? val[1]) - if (type !== '') { - return type + '[]' + const sample = val.find((v) => v !== null && v !== undefined) + if (sample !== undefined) { + const inner = inferType(sample) + if (inner !== '') { + return inner + '[]' + } } + // Empty arrays and all-null arrays: caller is almost always running + // $in on a Ref/string column. ::jsonb[] would be incompatible with + // CockroachDB's `= ANY`, so fall back to a real array type. + return '::text[]' } if (typeof val === 'object') { if (val instanceof Date) {