Skip to content

Commit b7513b6

Browse files
committed
Fix decoding box[] type
1 parent b81cd17 commit b7513b6

File tree

3 files changed

+41
-26
lines changed

3 files changed

+41
-26
lines changed

modules/module-postgres/src/types/registry.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,21 @@ class CustomTypeValue extends CustomSqliteValue {
7575
return this.cache.lookupType(this.oid);
7676
}
7777

78-
toSqliteValue(context: CompatibilityContext): SqliteValue {
78+
private decodeToDatabaseInputValue(context: CompatibilityContext): DatabaseInputValue {
7979
if (context.isEnabled(CompatibilityOption.customTypes)) {
8080
try {
81-
const rawValue = this.cache.decodeWithCustomTypes(this.rawValue, this.oid);
82-
const value = toSyncRulesValue(rawValue);
83-
return applyValueContext(value, context);
81+
return this.cache.decodeWithCustomTypes(this.rawValue, this.oid);
8482
} catch (_e) {
8583
return this.rawValue;
8684
}
85+
} else {
86+
return pgwire.PgType.decode(this.rawValue, this.oid);
8787
}
88+
}
8889

89-
return this.rawValue;
90+
toSqliteValue(context: CompatibilityContext): SqliteValue {
91+
const value = toSyncRulesValue(this.decodeToDatabaseInputValue(context));
92+
return applyValueContext(value, context);
9093
}
9194

9295
get sqliteType(): SqliteValueType {
@@ -146,20 +149,25 @@ export class CustomTypeRegistry {
146149
oid: builtin,
147150
sqliteType: () => sqliteType
148151
});
152+
}
153+
}
149154

150-
const arrayVariant = pgwire.PgType.getArrayType(builtin);
151-
if (arrayVariant != null) {
152-
// NOTE: We could use builtin for this, since PgType.decode can decode arrays. Especially in the presence of
153-
// nested arrays (or arrays in compounds) though, we prefer to keep a common decoder state across everything
154-
// (since it's otherwise hard to decode inner separators properly). So, this ships its own array decoder.
155-
this.byOid.set(arrayVariant, {
156-
type: 'array',
157-
innerId: builtin,
158-
sqliteType: () => sqliteType,
159-
// We assume builtin arrays use commas as a separator (the default)
160-
separatorCharCode: pgwire.CHAR_CODE_COMMA
161-
});
162-
}
155+
for (const [arrayId, innerId] of pgwire.ARRAY_TO_ELEM_OID.entries()) {
156+
// We can just use the default decoder, except for box[] because those use a different delimiter. We don't fix
157+
// this in PgType._decodeArray for backwards-compatibility.
158+
if (innerId == 603) {
159+
this.byOid.set(arrayId, {
160+
type: 'array',
161+
innerId,
162+
sqliteType: () => 'text', // these get encoded as JSON arrays
163+
separatorCharCode: 0x3b // ";"
164+
});
165+
} else {
166+
this.byOid.set(arrayId, {
167+
type: 'builtin',
168+
oid: arrayId,
169+
sqliteType: () => 'text' // these get encoded as JSON arrays
170+
});
163171
}
164172
}
165173
}
@@ -245,7 +253,10 @@ export class CustomTypeRegistry {
245253
case 'unknown':
246254
return true;
247255
case 'array':
248-
return this.isParsedWithoutCustomTypesSupport(this.lookupType(type.innerId));
256+
return (
257+
type.separatorCharCode == pgwire.CHAR_CODE_COMMA &&
258+
this.isParsedWithoutCustomTypesSupport(this.lookupType(type.innerId))
259+
);
249260
default:
250261
return false;
251262
}

modules/module-postgres/test/src/pg_test.test.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { describe, expect, test } from 'vitest';
1111
import { clearTestDb, connectPgPool, connectPgWire, TEST_URI } from './util.js';
1212
import { WalStream } from '@module/replication/WalStream.js';
13-
import { PostgresTypeCache } from '@module/types/custom.js';
13+
import { PostgresTypeCache } from '@module/types/cache.js';
1414

1515
describe('pg data types', () => {
1616
async function setupTable(db: pgwire.PgClient) {
@@ -483,7 +483,8 @@ INSERT INTO test_data(id, time, timestamp, timestamptz) VALUES (1, '17:42:01.12'
483483
id serial primary key,
484484
rating rating_value,
485485
composite composite,
486-
nested_composite nested_composite
486+
nested_composite nested_composite,
487+
boxes box[]
487488
);`);
488489

489490
const slotName = 'test_slot';
@@ -500,11 +501,12 @@ INSERT INTO test_data(id, time, timestamp, timestamptz) VALUES (1, '17:42:01.12'
500501

501502
await db.query(`
502503
INSERT INTO test_custom
503-
(rating, composite, nested_composite)
504+
(rating, composite, nested_composite, boxes)
504505
VALUES (
505506
1,
506507
(ARRAY[2,3], 'bar'),
507-
(TRUE, (ARRAY[2,3], 'bar'))
508+
(TRUE, (ARRAY[2,3], 'bar')),
509+
ARRAY[box(point '(1,2)', point '(3,4)'), box(point '(5, 6)', point '(7,8)')]
508510
);
509511
`);
510512

@@ -524,14 +526,16 @@ INSERT INTO test_data(id, time, timestamp, timestamptz) VALUES (1, '17:42:01.12'
524526
expect(oldFormat).toMatchObject({
525527
rating: '1',
526528
composite: '("{2,3}",bar)',
527-
nested_composite: '(t,"(""{2,3}"",bar)")'
529+
nested_composite: '(t,"(""{2,3}"",bar)")',
530+
boxes: '["(3","4)","(1","2);(7","8)","(5","6)"]'
528531
});
529532

530533
const newFormat = applyRowContext(transformed, new CompatibilityContext(CompatibilityEdition.SYNC_STREAMS));
531534
expect(newFormat).toMatchObject({
532535
rating: 1,
533536
composite: '{"foo":[2.0,3.0],"bar":"bar"}',
534-
nested_composite: '{"a":1,"b":{"foo":[2.0,3.0],"bar":"bar"}}'
537+
nested_composite: '{"a":1,"b":{"foo":[2.0,3.0],"bar":"bar"}}',
538+
boxes: JSON.stringify(['(3,4),(1,2)', '(7,8),(5,6)'])
535539
});
536540
} finally {
537541
await db.end();

packages/jpgwire/src/pgwire_types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export enum PgTypeOid {
3737

3838
// Generate using:
3939
// select '[' || typarray || ', ' || oid || '], // ' || typname from pg_catalog.pg_type WHERE typarray != 0;
40-
const ARRAY_TO_ELEM_OID = new Map<number, number>([
40+
export const ARRAY_TO_ELEM_OID = new Map<number, number>([
4141
[1000, 16], // bool
4242
[1001, 17], // bytea
4343
[1002, 18], // char

0 commit comments

Comments
 (0)