protoname

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package protoname is the protobuf identifier escaping shared by every descriptor emitter in the tree, and the DISPLAY policy that decides when an escaped name may safely be shown as the SQL identifier it came from.

It is a LEAF package on purpose. The escaping is needed by the DDL-time emitter (pkg/relational/core/metadata), by pkg/recordlayer itself, and by the plan-time emitter that synthesises a descriptor for a COMPUTED record (pkg/recordlayer/query/plan/cascades/values) — and that last one cannot import pkg/recordlayer, which already imports it. One escaping, one implementation: a second copy is a second chance to diverge from Java on bytes that are wire.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckValidProtoBufCompliantName

func CheckValidProtoBufCompliantName(name string) error

CheckValidProtoBufCompliantName validates that name is a legal protobuf identifier ([A-Za-z_][A-Za-z0-9_]*). Mirrors ProtoUtils.checkValidProtoBufCompliantName, including Java's exact message wording ("it not" verbatim).

func DecodeOnceIfReversible

func DecodeOnceIfReversible(stored string) string

DecodeOnceIfReversible returns the SQL identifier for a stored name, but only when the decoded spelling provably re-encodes to what was stored.

Record-layer metadata does not only come from the SQL layer — RecordMetaDataBuilder.SetRecords copies protobuf identifiers verbatim — so a record type may legally be named __0Order having never been escaped from anything. Decoding that yields __Order, which re-encodes to __Order and NOT to __0Order, so the name shown would resolve to nothing. The round trip is the provenance test and it needs no extra bookkeeping.

It does NOT prove the decoded spelling is safe to OFFER: it says the second step of a two-step lookup lands on the right entry, and nothing about the first. Use SafeDecoderOver when other names are printed alongside.

func SafeDecoderOver

func SafeDecoderOver(decoded, verbatim []string) func(string) string

SafeDecoderOver decides ONE decoding policy for a whole rendered output, from every name that output will print.

Per-value decisions are not enough and the split is easy to miss: decide separately for two lists and a colliding pair straddles them, each list is individually correct, and the output still shows one label for two different stored types.

`decoded` are names this output will render through the returned function. `verbatim` are names it prints unchanged — synthetic record types, which Java stores exactly as the caller passed them and which must never be decoded. They take part in the decision without being subject to it: a decoded name that equals one of them is the same one-label-two-things hazard.

Returns DecodeOnceIfReversible when unambiguous, identity otherwise — all-or-nothing, because under a collision every stored name is already a correct answer and a selective rewrite creates second-order collisions.

func ToProtoBufCompliantName

func ToProtoBufCompliantName(name string) (string, error)

ToProtoBufCompliantName escapes a user identifier into a protobuf-compliant name. Mirrors ProtoUtils.toProtoBufCompliantName (ProtoUtils.java:51-66): a leading "__" is preserved verbatim with only the remainder escaped, and names starting with ".", "$", "__0", "__1" or "__2" are rejected because the escaping could not be reversed.

func ToUserIdentifier

func ToUserIdentifier(protoIdentifier string) string

ToUserIdentifier reverses ToProtoBufCompliantName. Mirrors ProtoUtils.toUserIdentifier: replacements applied in the exact inverse order ("__2" -> ".", "__1" -> "$", "__0" -> "__").

NEITHER DIRECTION IS INJECTIVE, and a caller that assumes either one is will bind the wrong field. Both facts are load-bearing and neither is obvious from the three substitutions above:

  • ENCODING collides: `___1__2foo` decodes to `_$.foo`, so a DIFFERENT SQL name (`___1.FOO`) encodes to something that case-folds onto it. Matching a SQL name against storage by ENCODING the SQL name therefore accepts fields the identifier does not name. Decode the storage names instead — that is the direction every consumer already uses to answer "what is this column called".
  • DECODING collides too, which is easy to miss once the encode direction has been rejected for the same reason: `__0_` and `___0` both decode to `___`, so `foo__0_bar` and `foo___0bar` BOTH answer to the SQL name `foo___bar`. A descriptor can hold two fields with one SQL spelling.

So a lookup keyed on decoded names must handle COLLISIONS rather than take the first hit: which of two candidates wins is a property of the descriptor's field order and not of the query. This is not hypothetical — five successive defects in one lookup traced to this single unwritten fact, each fix correct about the coordinate it addressed and silent about the next.

The DISPLAY side of the package answers the same fact differently and on purpose: SafeDecoderOver goes all-or-nothing across a whole output, because under a collision every stored spelling is already a correct label and rewriting some of them invents new ones. A LOOKUP cannot take that route — it has to resolve one name — so it declines instead. Same fact, two consumers, two right answers; neither one is the general policy.

A DRAFT OF THIS PARAGRAPH SAID DDL CANNOT PRODUCE SUCH A PAIR. It can:

CREATE TABLE coll (id BIGINT, "___" BIGINT, "___0" BIGINT, PRIMARY KEY (id))

Both names begin `__`, so both pass through ToProtoBufCompliantName UNCHANGED — and `___0` then decodes to `___`, because the decode scan sees the `__0` starting at index 1. Two distinct, legal, non-duplicate SQL columns; one decoded spelling; a row type that cannot be built. Reading even an UNRELATED column of that table fails.

JAVA FAILS ON IT TOO, but with a DIFFERENT BLAST RADIUS, and that difference is the defect. Measured on a live JVM:

SELECT id FROM an_unrelated_table   Java ANSWERS      Go fails
SELECT id FROM coll                 Java fails        Go fails

Java's failure is TABLE-LOCAL. Go's takes down every query in the schema, including tables sharing nothing with the colliding one. A draft here read "both engines fail" as upstream-faithful; that came from a probe whose SETUP inserted into the colliding table, so Java was failing on the INSERT and every later query inherited it.

The escaped names are WIRE, so the encoding cannot change — reproducing Java means failing on `coll` and ANSWERING on everything else. Pinned in conformance/dotted_and_recursive_seed_java_probe_test.go.

Go's failure is also a PANIC, recovered at the driver boundary into XX000. A library that panics where it could return an error is design principle 4.

Both halves — the panic and the schema-wide scope — are RFC-238 §5 criterion (8), with the commands that make them checkable; §7b narrates how they were measured. They are TWO mechanisms, not one. The colliding table's own read fails table-locally, where it should, at the scan leaf that builds a row type from just the table the query names. The schema-wide part is elsewhere: buildMatchCandidates builds a positional type for every record type in the metadata that has a usable primary key and a descriptor, so ONE unbuildable table aborts the candidate set for all of them, and removing the panic does not change that. Java is table-local because MetaDataPlanContext.forRootReference narrows to the record types the QUERY names before building anything — not because it tolerates a bad table.

Types

type InvalidNameError

type InvalidNameError struct {
	Message string
}

InvalidNameError mirrors Java's ProtoUtils.InvalidNameException: the identifier cannot be turned into (or is not) a protobuf-compliant name.

func (*InvalidNameError) Error

func (e *InvalidNameError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL