Implementation Plan: Custom Codec Registry (Interface-Based Design) #148
Closed
SeanTAllen
started this conversation in
Research
Replies: 1 comment
-
|
Implemented in #150 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
This is the implementation plan for making the codec registry user-extensible. It builds on the design work from #147 and #146, and represents the output of an ensemble planning session with five different attention foci (API design, testing, correctness, security, completeness) that got synthesized and reviewed.
The big discovery during planning: the design in #147 assumed all 13
FieldDataTypesmembers would structurally conform to the newFieldDatainterface viaStringable. Turns outArray[U8] valdoesn't have astring()method. It's notStringable. So we need wrapper types, which makes this a bigger breaking change than the discussion anticipated.The Core Idea
Separate result types from parameter types. The decode path gets an open interface (
FieldData) that custom codecs can return their own types through. The encode path stays closed (FieldDataTypesunion). This asymmetry is deliberate — parameters need to map to known PostgreSQL OIDs for the wire protocol, but results just need to be readable.New Types
FieldDatainterface (postgres/field_data.pony)Open result type for decoded column values. 12 of the 13 built-in types already conform structurally via
Stringable. The 13th —Array[U8] val— gets wrapped (see below). Custom codecs return their own types implementing this interface.FieldDataEquatableinterface (same file)Opt-in equality for custom
FieldDatatypes inField.eq(). Built-in types use explicit match arms as they do today. Custom types implement this to participate in field equality comparisons.Bytea(postgres/bytea.pony)class val Bytea is (FieldData & Equatable[Bytea])— wrapsArray[U8] valfor PostgreSQLbyteacolumns (OID 17). Publiclet data: Array[U8] valfield.eq()does byte-by-byte comparison.string()returns hex representation.RawBytes(postgres/raw_bytes.pony)class val RawBytes is (FieldData & Equatable[RawBytes])— wrapsArray[U8] valfor unknown binary-format OIDs where no codec is registered. Same API shape asByteabut semantically distinct:Byteameans "this is known binary data from a bytea column,"RawBytesmeans "we don't know what this OID is and here are the raw bytes."Changes to Existing Types
Codec.decode()(postgres/codec.pony)Return type changes from
FieldDataTypes ?toFieldData ?. Theencodemethod staysFieldDataTypes. Docstring explains the asymmetry.Built-in codecs (
_binary_codecs.pony,_text_codecs.pony,_text_passthrough_binary_codec.pony)Mechanical: each
decodereturn type becomesFieldData. Bodies unchanged except_ByteaBinaryCodec.decode()returnsBytea(data)instead of rawdata, and_ByteaTextCodec.decode()returnsBytea(decoded_bytes)instead of the raw array. Allencodesignatures unchanged.CodecRegistry(postgres/codec_registry.pony)decode()return type:FieldDataTypes→FieldDatadata→RawBytes(data)String.from_array(data)already conforms)Delegates to the existing
_with_codecconstructor. Supports chaining:CodecRegistry.with_codec(600, A).with_codec(790, B).Field(postgres/field.pony)let value: FieldData(wasFieldDataTypes). Theeq()match gets two new arms replacingArray[U8] val:Plus a
FieldDataEquatablearm beforeelsefor custom types:_RowsBuilder(postgres/rows.pony)let field_value: FieldData(wasFieldDataTypes).Threading
CodecRegistryThrough the State MachineThe registry needs to flow from
Session.create()down to_SessionLoggedInwhere it's used. This follows the existing threading pattern for_notifyand_database_connect_info.Session.create()gets an optional parameter withCodecRegistryas default:Call site:
Session(server_info, db_info, notify where registry = my_registry)The registry threads through the full chain:
_SessionUnopened→_ConnectableState→_SessionSSLNegotiating/_SessionConnected→_AuthenticableState→_SessionSCRAMAuthenticating→_SessionLoggedIn. Each state class gets alet _codec_registry: CodecRegistryfield and constructor param. The_ConnectableStateand_AuthenticableStatetraits get an abstractfun codec_registry(): CodecRegistrymethod that concrete states implement._SessionLoggedInchanges from creating its ownCodecRegistryinternally to receiving one via constructor param.Tests
Updated existing tests
_test_codecs.pony:assert_is[FieldDataTypes]→assert_is[FieldData](~6 occurrences). Bytea codec tests match onByteaand use.datafor byte-level assertions._TestCodecRegistryDecodeUnknownBinarymatchesRawBytesinstead ofArray[U8] val._test_equality.pony:Array[U8] valfield values →Byteain example-based tests. Generator renamed_FieldDataTypesGen→_FieldDataGenwith outputGenerator[FieldData]. Property test type params updated throughout._test_query.pony:_ByteaQueryReceivermatchesByteainstead ofArray[U8] val, uses.datafor assertions._test_session.pony:_TestByteaResultDecodingreceiver matchesByteainstead ofArray[U8] val.New tests
Wrapper type tests:
_TestByteaString(hex output),_TestByteaEquality(equal, different lengths, empty),_TestRawBytesString,_TestRawBytesEquality.Custom codec tests:
_TestCodecRegistryWithCodecBinary,_TestCodecRegistryWithCodecText,_TestCodecRegistryWithCodecOverride,_TestCodecRegistryWithCodecChaining,_TestCodecRegistryWithCodecPreservesBuiltins,_TestRowsBuilderWithCustomCodec. Test helpers include a_TestPoint valclass implementingFieldData+FieldDataEquatableand a_TestPointCodecprimitive.Custom equality tests:
_TestFieldEqualityCustomType,_TestFieldInequalityCustomType,_TestFieldEqualityCustomVsBuiltin,_TestFieldEqualityCustomWithoutEquatable,_TestFieldCustomEqualityReflexiveProperty.Examples
Seven existing examples updated:
bytea,crud,named-prepared-query,prepared-query,query,ssl-query,ssl-preferred-query. Change| let v: Array[U8] val =>to| let v: Bytea =>and usev.datafor byte access. Copy-in/copy-out examples unchanged (they useArray[U8] valfor COPY wire data, not field values).New
custom-codecexample: customPointclass implementingFieldData+FieldDataEquatable, binary codec for PostgreSQLpoint(OID 600), registry viaCodecRegistry.with_codec(600, PointBinaryCodec), passed toSessionwithwhere registry = ..., matching onPointin results.Documentation and Release Notes
postgres/postgres.pony): new "Custom Codecs" section, updated "Working with Results" withByteainstead ofArray[U8] val.release-notes/next-release.mdupdated toByteaArray[U8] valtoBytea, (2) unknown binary OIDs change fromArray[U8] valtoRawBytesCLAUDE.mdupdated with new types and changed APIsexamples/README.mdupdated with custom-codec entryClassification
This is a
changed(breaking) change. The PR gets thechangelog - changedlabel. Two breaking paths for users: matching onArray[U8] valin result fields needs to becomeBytea(orRawBytesfor unknown binary OIDs).Beta Was this translation helpful? Give feedback.
All reactions