Documentation
¶
Overview ¶
Package oracle proxies Oracle Net (TNS) so that a spec can assert what a service asked its database, without the service being changed and without this package pretending to be Oracle.
It observes and relays. Bytes travel to a real Oracle in both directions untouched; only the client-to-server direction is read, and only far enough to recover the statement. That is why the channel is a few hundred lines rather than a reimplementation of a database: nothing here synthesises a result set, negotiates a data type, or answers a handshake. The cost is that RETURNS cannot work on this channel - a response this package did not author is one it cannot replace - and that is refused when a spec is parsed rather than ignored while it runs.
The framing below was measured against a real Oracle 23ai, and the captured packets in testdata are what the tests read. Two details would have been wrong if taken from the protocol's reputation instead, and both are noted where they are implemented.
Index ¶
Constants ¶
const ( TypeConnect byte = 1 TypeAccept byte = 2 TypeRefuse byte = 4 TypeRedirect byte = 5 TypeData byte = 6 TypeResend byte = 11 TypeMarker byte = 12 )
Packet types, of which only Data can carry a statement.
const HeaderLen = 8
HeaderLen is the fixed TNS header: length, checksum, type, flags, header checksum.
const MaxPacketLen = 0xFFFF
MaxPacketLen is the largest packet this package frames, and it is a property of the framing rather than a policy: a wide length is only recognised when the header's first two bytes are zero, so the size it can express stops at 64 KiB. A session negotiated with a large SDU could exceed that, and such a packet is refused rather than misread - its high bytes would be non-zero, the narrow reading would name a length far too small, and PacketLen returns ErrImplausibleLength. Loud, and in the safe direction.
Variables ¶
var ( // ErrShortHeader means fewer than HeaderLen bytes were available. ErrShortHeader = errors.New("oracle: packet shorter than a TNS header") // ErrImplausibleLength means the length field named a size this package // will not treat as a packet. ErrImplausibleLength = errors.New("oracle: implausible TNS packet length") )
Functions ¶
func PacketLen ¶
PacketLen reads the packet length from a TNS header.
Oracle uses two framings on one connection and the captured packets show both. A CONNECT is sent before any version has been agreed, so its length is the 16-bit field the protocol has always had, with a checksum in the next two bytes. Once the session negotiates, DATA packets put a 32-bit length across all four bytes and leave the checksum zero.
The two are told apart by the high half rather than by tracking connection state, which would make framing depend on having seen the handshake - no use to a proxy that attaches to a connection already in progress. A packet is at least HeaderLen bytes, so a 16-bit length never leaves its first two bytes zero; a 32-bit length is only plausible below the maximum below, so its high two bytes are always zero. The halves therefore cannot both be right, and which one is is decidable from the header alone.
func Statement ¶
Statement returns the SQL text carried by a packet, and whether one was found. A packet with no statement in it is the normal case rather than an error: Oracle carries a great deal over one connection that is not a statement.
A statement is stored as a length followed by its bytes. Below 254 the length is a single byte and every client agrees on that. At or above it, 0xFE introduces a wide length - and the clients do NOT agree on what follows it:
sqlplus fe 15 01 00 00 four bytes LITTLE-endian (277)
ODP.NET fe 02 01 15 marshalled: one byte saying how many follow,
then that many BIG-endian (277)
Both are read, because both were captured from the client that sends them, and the .NET driver is the one a service under test actually uses. Reading only the first form is silent rather than loud: no length agrees, the packet contributes nothing, and an expectation written against a statement past 254 bytes is reported as never called.
The search is for a length that agrees with what follows it, which is a strong constraint: the length must be matched by exactly that many printable bytes, and those bytes must open with a SQL verb. Anything else is not a statement as far as this package is concerned - which is also why two competing readings of the same bytes are safe to try in turn.
Types ¶
type Proxy ¶
type Proxy struct {
// contains filtered or unexported fields
}
Proxy sits between a service and a real Oracle, relaying both directions untouched and reading the client's statements as they pass.
It does not answer for Oracle. Every byte the service receives came from the database, so a statement's result is whatever the database really returned - which is why this channel supports the assertions about what was asked (ACCESSING_TABLES, VERIFY_OPERATION, VERIFY_WHERE_COLUMNS, VERIFY_WHERE, VERIFY_WRITTEN_VALUES, EXPECT_NOT) and not RETURNS.
func NewProxy ¶
func NewProxy(addr, upstreamAddr string, reg *registry.MockRegistry) *Proxy
NewProxy builds a proxy that listens on addr and relays to upstreamAddr.
func (*Proxy) SetDatabaseName ¶
SetDatabaseName scopes this proxy's expectations, so that a spec naming a database is only satisfied by the proxy serving it.