Help us shape the future of Go! Take the 2026 Go Developer Survey

packet

package
v1.26.6 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package packet provides functionality for the TDTP framework.

Index

Constants

View Source
const (
	MaxDictEntries     = 256
	MaxDictShortLength = 16
	MaxDictFullLength  = 4096
)

Dictionary limits — keep small enough that a malicious or buggy producer cannot make a parser allocate unbounded memory.

View Source
const (
	SpecNullMarker   = "[NULL]"
	SpecNaNMarker    = "NaN"
	SpecInfMarker    = "INF"
	SpecNegInfMarker = "-INF"
	SpecNoDateMarker = "0000-00-00" // canonical "no date" / zero-date marker
)

Canonical SpecialValues markers per TDTP spec v1.3.1. These are fixed by the specification and must not be changed.

View Source
const DefaultMaxMessageSize = 3_800_000

DefaultMaxMessageSize — размер по умолчанию (3.8MB оценка → ~1.9MB реального XML). Внутренняя оценка намеренно вдвое больше реального XML, т.к. размер строк считается в UTF-16 единицах (MSMQ/COM-совместимость).

View Source
const EncryptionAlgoAESGCM = "aes-256-gcm"

EncryptionAlgoAESGCM is the only algorithm value v1.5 currently writes to the encryption="..." attribute on QueryContext/Schema/Data.

View Source
const InReplyToDirectExport = "DirectExport"

InReplyToDirectExport - зарезервированное значение для response-пакетов, сгенерированных командой --export без входящего request (автономный экспорт).

View Source
const LayoutColumns = "columns"

LayoutColumns — значение Data.Layout для колоночной раскладки.

View Source
const MaxSchemaBytes = 200 * 1024

MaxSchemaBytes — предел размера сериализованной Schema, включая Dictionary.

Schema копируется в каждую часть многотомного пакета, поэтому её размер умножается на число частей. Обычная схема — это описания полей, и до этого предела ей далеко: даже таблица на 1600 колонок укладывается заметно ниже. Реально упереться можно только крупным Dictionary. Автопостроения словаря в фреймворке пока нет — словарь либо приходит от внешнего продюсера через библиотечный API, либо содержит одну служебную запись (@MRC с адресом Mercury). Когда построение появится, оно должно укладываться в этот же предел.

View Source
const MaxSchemaBytesRead = 1024 * 1024

MaxSchemaBytesRead — жёсткий предел размера Schema при чтении.

Отдельная величина от MaxSchemaBytes (предел генерации, 200KB), и намеренно выше него. Предел на записи — правило формата: он касается только пакетов, которые мы создаём. Предел на чтении отвергал бы уже существующие данные, поэтому он не правило формата, а защита от патологического входа: чужой пакет с гигантской схемой иначе будет разобран и размещён в памяти целиком. Между 200KB и этим порогом пакет читается, но с предупреждением.

Variables

View Source
var ErrVersionWithoutIntegrity = errors.New("carries no xxh3 integrity hashes")

ErrVersionWithoutIntegrity marks a packet that declares v1.4 or later but carries no xxh3 hashes.

Functions

func AppendMSSQLDatetime

func AppendMSSQLDatetime(dst []byte, b []byte) []byte

AppendMSSQLDatetime форматирует MSSQL DATETIME в dst без аллокаций памяти.

ВНИМАНИЕ: границы не проверяются. b должен быть длиной ровно 8 байт, а поле дней — давать год 0001..9999. На битых байтах (год за 9999) put4 уходит за пределы digitsTable и функция паникует. Вход обязан быть проверен вызывающим кодом — либо используйте FormatMSSQLDatetime, который проверяет длину.

func AppendMSSQLDatetime2

func AppendMSSQLDatetime2(dst []byte, b []byte, scale int) []byte

AppendMSSQLDatetime2 форматирует MSSQL DATETIME2 в dst без аллокаций памяти.

ВНИМАНИЕ: границы не проверяются — см. предупреждение у AppendMSSQLDatetime. scale обязан быть 0..7, len(b) — timeLen(scale)+3.

func AppendTimestampRFC3339Nano

func AppendTimestampRFC3339Nano(dst []byte, t time.Time) []byte

AppendTimestampRFC3339Nano дописывает t в dst в том же виде, что и t.UTC().Format(time.RFC3339Nano). Для года вне 0001..9999 отдаёт работу stdlib.

func ApplyCompact

func ApplyCompact(pkt *DataPacket, fixedFieldNames []string, tail bool) error

ApplyCompact применяет compact-формат к пакету: помечает поля fixedFieldNames как fixed в схеме, стрипает _ prefix из имён, перекодирует строки в compact-формат, устанавливает версию 1.3.1.

Поле, в котором встречается пустое значение, fixed не становится — см. ApplyCompactReport.

func ApplyCompactReport

func ApplyCompactReport(pkt *DataPacket, fixedFieldNames []string, tail bool) (dropped []string, err error)

ApplyCompactReport — ApplyCompact, который возвращает имена полей, исключённых из fixed из-за пустых значений.

Пустая позиция fixed поля в compact означает «значение как в строке выше», и отличить от неё настоящую пустую строку формат не может. Без исключения `Sales, "", Sales` уезжало как `Sales|…`, `|…`, `|…` и читалось как три Sales — молча, с совпадающим хешем, потому что хеш считается по compact строкам. А в tail-строке пустое fixed поле давало пакет, который наш же ExpandCompactRows отвергает с CompactTailError.

Менять формат ради этого нельзя: любой уже выпущенный читатель прочёл бы новый способ записи "" как пропуск. Исключение поля из fixed стоит только сжатия этого поля, а такой пакет читают все версии.

NULL сюда не попадает: к этому моменту DetectAndApply уже заменил его маркером. Пустое значение — это настоящая пустая строка TEXT колонки.

func AutoFixedFields

func AutoFixedFields(schema Schema) []string

AutoFixedFields возвращает имена полей схемы, у которых имя начинается с "_". Используется для auto-detect fixed полей при compact-экспорте.

func BigintDecimal

func BigintDecimal(f Field) bool

BigintDecimal reports a DECIMAL field with scale 0 carrying the "bigint" subtype: a 64-bit integer column that travels as DECIMAL.

The case it exists for is Oracle NUMBER(19,0) — what Oracle creates for a TDTP INTEGER. Reading it back as INTEGER would be wrong for a native NUMBER(19,0) holding a value above int64 (it parses as int64 downstream); reading it as plain DECIMAL loses the fact that it is an integer, so a round trip turned BIGINT into NUMERIC(19,…) elsewhere. DECIMAL keeps every value exact; the subtype tells an importer it may create BIGINT. "bigint" is the subtype PostgreSQL and MSSQL already use for INTEGER fields.

func BigintValue

func BigintValue(v string) any

BigintValue converts a BigintDecimal value for a driver: the exact int64 when it fits, otherwise the decimal text unchanged — so a value above int64 makes a BIGINT column refuse the row loudly instead of being rounded. Never through float64, which keeps ~16 digits (9007199254740993 would become …992). Empty is NULL.

func BuildEscapeMask

func BuildEscapeMask(schema Schema) []bool

BuildEscapeMask отдаёт маску экранирования по схеме: false для типов, которые экранировать не нужно. Экспортируется для тех, кто раскладывает данные по колонкам вне пакета (например, сжатый путь экспорта).

func BumpVersion

func BumpVersion(pkt *DataPacket, minVersion string)

BumpVersion raises pkt.Version to at least minVersion, numerically compared — never lowers it.

Every producer-side feature stamps the version its feature introduced (compression → 1.2, compact → 1.3.1, integrity → 1.4, encryption → 1.5), so a packet's version is the max of the features it uses. Direct assignment (pkt.Version = "1.4") breaks this the moment steps combine — e.g. integrity stamping 1.4 and a later compact step overwriting it with 1.3.1 — which is how compressed packets shipped version="1.0" for years.

An unparsable current version is left untouched: same convention as NeedsRowCountCheck, unknown counts as newer, and clobbering it would destroy information.

func CheckDeclaredIntegrity

func CheckDeclaredIntegrity(pkt *DataPacket) error

CheckDeclaredIntegrity is the version↔contents rule every reader shares: a packet declaring v1.4 or later must carry xxh3 hashes, because those hashes are what v1.4 is — it has no other feature. Such a packet without them is not a lenient old file (unlike a compressed packet declaring 1.0, which the protocol simply never re-stamped); its version and contents contradict each other.

VerifyIntegrity alone cannot say this: it returns nil for an unstamped packet, and callers read that silence as "verified". Before this rule was shared, one relabelled packet got three answers — import refused it (pipeline.VerifyAndPrepare), validate called it VALID, and --test printed "Integrity check passed".

Checks only presence; pair it with VerifyIntegrity for the values. Nil for pre-v1.4 versions.

func CompareProtocolVersions

func CompareProtocolVersions(a, b string) (int, bool)

CompareProtocolVersions сравнивает две строки версий.

ok=false, если хотя бы одна из них не разбирается; результат в этом случае смысла не имеет и использовать его нельзя.

func ContractDictionary

func ContractDictionary(entries []DictEntry, cell string) string

ContractDictionary returns the short token for a full value, or the input unchanged if no entry matches. Use this when serialising a domain object back into a TDTP row to enable size reduction.

func DecryptSections

func DecryptSections(pkt *DataPacket, key []byte) error

DecryptSections reverses EncryptSections in place: for each of QueryContext/Schema/Data that carries a non-empty Encryption attribute, decrypts Encrypted (or Data's single opaque Row) with key and restores the structured fields. Sections without an Encryption attribute are left untouched (a v1.5-versioned packet may still carry a plain section if the producer chose not to encrypt everything — not the default, but not forbidden either).

key must come from one RetrieveKey call for this packet's Header.MessageID (burn-on-read — call once, reuse the returned key for every section and every part of a multi-part message).

func Downgrade

func Downgrade(pkt *DataPacket)

Downgrade converts a v1.4 packet with a Dictionary into a v1.3.1 compatible packet by expanding all token cells inline and removing the Dictionary. The original packet is modified in place. No-op if the packet has no Dictionary or is already v1.3.1.

func EncryptSections

func EncryptSections(pkt *DataPacket, key []byte) error

EncryptSections turns pkt into a TDTP v1.5 packet: QueryContext, Schema, and Data content are each replaced with opaque ciphertext, Header stays untouched. Sets pkt.Version = "1.5".

key must be the 32-byte AES-256 key returned by one BindKey call for this packet's Header.MessageID — the same key encrypts every section (and, for multi-part packets, every part), each with its own random nonce. See docs/tdtp-protocol-schema.md → "v1.5" for the full design.

Order matters and is fixed: call this AFTER ComputeIntegrity (xxh3 must be computed over plaintext) and AFTER compression (Data.Rows already reflects Compression, if any) — never before either.

Calls pkt.MaterializeRows() itself, unconditionally, as the very first step. This is not optional: GenerateReference leaves rows sitting in the unexported rawRows field until something moves them onto Data.Rows, and if that never happens here, writePacketTo's rawRows fast-path silently writes the ORIGINAL PLAINTEXT rows into <Data> — right alongside the encryption="aes-256-gcm" attribute this function still sets on Data, producing a packet that LOOKS encrypted but leaks every row in the clear. Every caller of this function must be able to rely on that guarantee rather than remembering to materialize first — a single forgotten call site anywhere in the codebase would be a silent plaintext leak, not a build error.

func EnsureColumnar

func EnsureColumnar(pkt *DataPacket)

EnsureColumnar приводит Data к колоночной раскладке, если она ещё не такая.

Идемпотентна намеренно. Раскладку выставляют из трёх мест — три точки записи генератора и шаг сжатия в команде экспорта, — и какие из них отработают, зависит от пути: GenerateReference оставляет строки в rawRows и раскладку делает писатель, GenerateResponse строит Data сразу, а сжатие перекладывает само между хешем и кодеком. Без защиты от повторного вызова путь с запросом и сжатием транспонировал бы дважды, приняв колонки за строки.

Отказывается работать, когда строки непрозрачны: у сжатого или зашифрованного пакета перекладывать нечего.

func ExpandColumnarRows

func ExpandColumnarRows(pkt *DataPacket) error

ExpandColumnarRows разворачивает колоночную раскладку обратно в построчную.

Устроен как ExpandCompactRows: правит Data.Rows на месте и снимает свой флаг, не трогая Compression, Checksum и XXH3 — они описывают уже случившееся и после разворота остаются верными.

Зовётся после распаковки: у сжатого пакета строки ещё лежат в блобе.

func ExpandCompactRows

func ExpandCompactRows(packet *DataPacket) error

ExpandCompactRows разворачивает compact-формат данных в полный формат.

В compact-формате (Data.Compact == true) fixed поля пишутся только в первой строке группы. В последующих строках группы на позициях fixed полей стоят пустые строки. При смене значения fixed поля (новая группа) значение снова пишется явно.

Алгоритм carry-forward:

  • Если Data.Carry непустой — парсится как начальное carry-состояние чанка, что позволяет декодировать произвольный чанк независимо от предыдущих.
  • Для каждого fixed поля хранится последнее явно записанное значение.
  • Если в текущей строке позиция fixed поля пустая — подставляется carry-значение.
  • Если непустая — обновляет carry и используется как есть.

Если Data.Tail == true — последняя строка должна содержать все fixed поля явно (является carry-снэпшотом). Нарушение этого инварианта возвращает ошибку.

После вызова Data.Compact устанавливается в false (данные уже развёрнуты). Если Data.Compact == false — функция ничего не делает.

func ExpandDictionary

func ExpandDictionary(entries []DictEntry, cell string) string

ExpandDictionary returns the full string for a token, or the input unchanged if it is not a token or not registered. Safe to call on arbitrary cell values — substrings inside mixed text pass through untouched because they fail the whole-cell match.

func ExtractKeyFields

func ExtractKeyFields(schema Schema) []string

ExtractKeyFields извлекает ключевые поля из схемы

func FormatMSSQLDatetime

func FormatMSSQLDatetime(b []byte) (string, error)

FormatMSSQLDatetime возвращает строку ISO-8601 для 8-байтового DATETIME.

func FormatMSSQLDatetime2

func FormatMSSQLDatetime2(b []byte, scale int) (string, error)

FormatMSSQLDatetime2 возвращает строку ISO-8601 для DATETIME2(scale).

func FormatTimestampFast

func FormatTimestampFast(t time.Time) string

FormatTimestampFast — замена schema.FormatTimestamp с тем же результатом.

func GenerateUUID

func GenerateUUID() string

GenerateUUID создает UUID v4 (публичная обёртка для использования вне пакета).

func GetFieldIndices

func GetFieldIndices(schema Schema, fieldNames []string) []int

GetFieldIndices возвращает индексы полей по их именам

func HasIntegrity

func HasIntegrity(pkt *DataPacket) bool

HasIntegrity reports whether the packet carries XXH3 integrity hashes. Fast pre-flight check: reads only the root attribute — no rows needed.

func IsDateFieldType

func IsDateFieldType(fieldType string) bool

IsDateFieldType reports whether a TDTP field type is a date/time type. Exported because adapters need the same list when they decode SpecialValues markers, and three private copies of it had already started to drift.

func IsDictToken

func IsDictToken(s string) bool

IsDictToken reports whether s is a syntactically valid dictionary token. Does not check whether the token exists in any dictionary.

func IsEncrypted

func IsEncrypted(pkt *DataPacket) bool

IsEncrypted reports whether any section of pkt still carries opaque v1.5 ciphertext (QueryContext/Schema/Data.Encryption non-empty). Callers that only understand plaintext/compressed packets — e.g. libtdtp's read path, which has no xZMercury key access — must check this before treating Schema.Fields or Data.Rows as real data: an encrypted packet parses as valid XML (Header stays plain) but Schema.Fields is empty and Data.Rows holds one opaque blob, not real rows.

func IsRawSpecialForm

func IsRawSpecialForm(fieldType, value string) bool

IsRawSpecialForm reports whether value is one of the raw driver spellings of a special value ("Infinity", "-Inf", "NaN") that DetectAndApply will later turn into a canonical marker.

Adapters use it to leave such a value alone on the way into the packet. Without the check the ParseValue→FormatValue round trip in ConvertValueToTDTP tries to read "Infinity" as a date, fails, and writes a line to the log for every affected cell — while returning the value unchanged anyway.

func JoinRowEscaped

func JoinRowEscaped(fields []string) string

JoinRowEscaped объединяет поля строки через | с TDTP-экранированием. Используется везде где []string → pipe-строка (компрессия, Python-байндинги).

func NeedsRowCountCheck

func NeedsRowCountCheck(version string) bool

NeedsRowCountCheck reports whether a packet of the given version predates the integrity hashes, and therefore must have its RecordsInPart registered with Mercury rather than verified by an xxh3 stamp.

The name is historical and was, for a while, taken literally: this predicate used to gate the RecordsInPart-vs-row-count comparison, on the reasoning that "from v1.4 packets carry XXH3-128 hashes that guarantee integrity end to end, making the counter redundant". That reasoning does not survive reading computeHashes: the hashes cover the Schema and the row values, and the header is in neither. The result was that from v1.4 up, no one checked the counter — a packet declaring 999 rows while carrying 3 parsed cleanly and verified.

The row count is now checked by VerifyRowCount, at every version, wherever the rows are actually readable. This predicate keeps only its second job, described below, which was always the real one.

This same predicate also gates pkg/pipeline's VerifyAndPrepare pre-flight (via the inverse: !NeedsRowCountCheck(version) → run the Mercury pre-flight) — meaning it doubles as "does this packet's version REQUIRE a registered xxh3 hash to be importable." That's a real assumption, not just a naming coincidence: any producer of a version >= "1.4" packet (this includes v1.5 encryption, whose packets always carry Version="1.5") MUST have called ComputeIntegrity + RegisterHash, or the consumer-side gate blocks it with HASH_NOT_REGISTERED — found live via a v1.5 packet that skipped this. See pkg/pipeline/produce.go's ComputeAndRegisterIntegrity, which every v1.5 encryption call site now calls for exactly this reason.

Сравнение версий — числовое, а не строковое (см. ParseProtocolVersion). Раньше здесь стояло `version <= "1.3.1"`, и на нынешнем наборе версий это давало верный ответ по совпадению порядка символов. На двузначном компоненте совпадение кончается: "1.10" строкой меньше "1.3.1", то есть версия 1.10 перестала бы требовать зарегистрированный хеш — и молча, потому что пакет при этом продолжает импортироваться.

Неразбираемая версия считается НОВЕЕ любой известной: предикат отвечает false, пакет обязан нести целостность. Так закрыт обход через мусор в поле версии; ровно так же вело себя и строковое сравнение ("abc" > "1.3.1"), но там это выходило случайно.

Пустая строка — единственное исключение, она считается легаси. Это тоже повторяет прежнее поведение ("" < "1.3.1"), и на практике сюда не доходит: parser.go отвергает пакет без версии раньше, а NewDataPacket проставляет "1.0".

func NullMarkerOf

func NullMarkerOf(f Field) string

NullMarkerOf returns the NULL marker a reader should apply to field f: the one it declares, or "[NULL]" when it declares none — packets from other producers and older writers mark NULL that way without a declaration. Comparing against the constant instead would read a column whose marker was moved to "[NULL1]" (see pickNullMarker) as NULL wherever it holds the text "[NULL]".

func PacketSizeBudget

func PacketSizeBudget(mb int) int

PacketSizeBudget переводит N мегабайт реального XML в бюджет для SetMaxMessageSize — те же единицы, что estimateRowSize (вдвое больше UTF-8 байта). Общая для CLI (--packet-size) и pipeline (output.tdtp.packet_size_mb); не путать с packet_kb в YAML — там множителя нет намеренно, и выравнивать их нельзя (см. CLAUDE.md).

func ParseRows

func ParseRows(rows []Row, parser *Parser) [][]string

ParseRows парсит TDTP строки в [][]string

func ResolveFixedFields

func ResolveFixedFields(schema Schema, explicit []string) []string

ResolveFixedFields возвращает финальный список fixed полей. Если explicit непустой — возвращает его. Иначе auto-detect по _ prefix.

func SchemaEquals

func SchemaEquals(a, b Schema) bool

SchemaEquals reports whether two schemas are structurally identical: same number of fields, same names and types in the same order.

func UpdatePartTotalParts

func UpdatePartTotalParts(packets []*DataPacket, totalParts int)

UpdatePartTotalParts обновляет TotalParts во всех частях (для batch post-processing) Используется когда нужно обновить файлы после завершения streaming экспорта

func ValidateDictionary

func ValidateDictionary(entries []DictEntry) error

ValidateDictionary checks that a dictionary satisfies the protocol invariants: entry count, short/full length bounds, token syntax for shorts, and uniqueness of both shorts and fulls. Returns the first violation as a descriptive error.

func VerifyIntegrity

func VerifyIntegrity(pkt *DataPacket) error

VerifyIntegrity checks that a parsed packet's XXH3 hashes match the payload. Returns nil if:

  • The packet carries no XXH3 hashes (not a v1.4 integrity packet — silently OK)
  • All three hashes match the recomputed values

Returns a descriptive error naming which hash failed.

func VerifyRowCount

func VerifyRowCount(pkt *DataPacket) error

VerifyRowCount сверяет RecordsInPart с фактическим числом строк.

Проверка НЕ зависит от версии пакета, и это исправление прежнего поведения. Раньше её гейтил NeedsRowCountCheck — то есть для v1.4 и выше она молчала, с обоснованием «целостность гарантируется XXH3, счётчик избыточен». Обоснование неверно: computeHashes накрывает Schema и значения строк, заголовок в хеш не входит вовсе. Пакет v1.4, заявляющий 999 строк при трёх реальных, разбирался без замечаний и проходил VerifyIntegrity.

Счётчик читают как авторитетный (etl/processor.go складывает из него TotalRowsLoaded, libtdtp отдаёт как число строк), так что расхождение — это неверная цифра у потребителя, а не косметика.

Зовётся там, где строки уже в построчном виде: на разборе несжатого пакета и сразу после распаковки. На сжатом или зашифрованном пакете возвращает nil — сверять нечего, строки ещё в блобе.

Types

type AlarmDetails

type AlarmDetails struct {
	Severity string `xml:"Severity"`
	Code     string `xml:"Code"`
	Message  string `xml:"Message"`
	// ServerMode — режим xZMercury-сервера, выпустившего или сжёгшего ключ.
	// "dev"  → аварийная замена при отказе Redis-кластера (ложная тревога).
	// "prod" → штатный прод; KEY_BURNED_BY_OTHER в prod — сигнал расследования.
	// Пусто → ошибка не связана с ключом или сервер не передал режим.
	ServerMode      string `xml:"ServerMode,omitempty"`
	AffectedRecords int    `xml:"AffectedRecords,omitempty"`
}

AlarmDetails содержит информацию о тревоге

type CompactTailError

type CompactTailError struct {
	FieldIndex int
	FieldName  string
}

CompactTailError возникает когда tail-строка содержит пустое fixed поле.

func (*CompactTailError) Error

func (e *CompactTailError) Error() string

type CompressionOptions

type CompressionOptions struct {
	Enabled   bool   // Включить сжатие
	Level     int    // Уровень сжатия: 1 (fastest) - 19 (best), по умолчанию 3
	MinSize   int    // Минимальный размер данных для сжатия (bytes), по умолчанию 1024
	Algorithm string // Алгоритм сжатия: "zstd" (пока только он поддерживается)
}

CompressionOptions содержит настройки сжатия данных

func DefaultCompressionOptions

func DefaultCompressionOptions() CompressionOptions

DefaultCompressionOptions возвращает настройки сжатия по умолчанию

type Data

type Data struct {
	Compression string `xml:"compression,attr,omitempty"` // Алгоритм сжатия: "zstd" или пусто
	Checksum    string `xml:"checksum,attr,omitempty"`    // XXH3-64 хеш сжатых данных (hex); backward compat
	XXH3        string `xml:"xxh3,attr,omitempty"`        // v1.4: xxh3_128 of raw row values (before compression)
	Compact     bool   `xml:"compact,attr,omitempty"`     // v1.3.1: compact format (пропуски для fixed полей)
	Tail        bool   `xml:"tail,attr,omitempty"`        // v1.3.1: последняя строка явно повторяет все fixed-поля — для потокового восстановления и валидации
	Carry       string `xml:"carry,attr,omitempty"`       // v1.3.1: начальное carry-состояние чанка (pipe-разделённые значения полей); позволяет декодировать чанки независимо друг от друга
	// Encryption (since TDTP v1.5): "aes-256-gcm" if Rows holds exactly one
	// opaque Row whose Value is base64(nonce||ciphertext) of the entire
	// pre-encryption <R>...</R> fragment (whatever Compression already
	// produced — one compressed row, or the plain N rows joined). Always
	// check Encryption before treating Rows as plaintext row values.
	Encryption string `xml:"encryption,attr,omitempty"`

	// Layout (опционально): "columns" означает, что каждый <R> хранит не строку
	// таблицы, а КОЛОНКУ целиком — все её значения, разделённые '|'. Пусто —
	// обычная построчная раскладка.
	//
	// Смысл в сжатии: в построчной раскладке значения одной колонки лежат
	// друг от друга на длину записи, и кодек не видит их рядом. Собранные
	// подряд, они дают около 19% на zstd 3.
	//
	// Атрибут обязателен именно потому, что меняет смысл <R>: читатель без
	// него разрежет колонки как строки и получит мусор, не заметив этого.
	// Поэтому раскладка включается только явным флагом генератора, а
	// умолчание — построчная.
	Layout string `xml:"layout,attr,omitempty"`

	Rows []Row `xml:"R"`
}

Data содержит табличные данные

func RowsToColumnarData

func RowsToColumnarData(rows [][]string, nFields int, mask []bool) Data

RowsToColumnarData раскладывает строки по колонкам: каждый Row результата — одна колонка целиком, значения разделены '|'.

Экранирование берётся то же, что у построчного пути (writeEscaped через mask), и это не выбор из удобства. Два писателя в пакете уже дают разные байты на значении с переводом строки; третья схема добавила бы третий вариант тех же данных. Колоночная раскладка наследует каноническую.

func RowsToCompactData

func RowsToCompactData(rows [][]string, schema Schema, tail bool) Data

RowsToCompactData преобразует [][]string в Data с compact-форматом.

Для полей с Fixed=true значение пишется только при первом появлении или при смене значения (новая группа). В остальных строках группы на этой позиции записывается пустая строка (пропуск).

Если tail=true — последняя строка записывается с явными значениями всех fixed полей (carry-снэпшот). Это позволяет:

  • читать данные с конца пакета / потока без полного прохода с начала;
  • валидировать консистентность carry-forward при декодировании;
  • передавать carry-состояние следующему чанку через атрибут carry="...".

Если в схеме нет ни одного fixed поля — возвращает обычный Data (compact=false).

Предусловие: в fixed поле нет пустых значений. Пустая позиция в compact — это «бери значение сверху», и настоящее "" декодер молча заменит значением предыдущей группы. ApplyCompact это предусловие обеспечивает сам: поле с "" он из fixed исключает.

func RowsToData

func RowsToData(rows [][]string) Data

RowsToData преобразует [][]string в Data (публичная функция) Правильно экранирует специальные символы (|, \)

type DataPacket

type DataPacket struct {
	Protocol        string           `xml:"protocol,attr"`
	Version         string           `xml:"version,attr"`
	XXH3            string           `xml:"xxh3,attr,omitempty"` // v1.4: packet fingerprint = xxh3_128(schema_xxh3|data_xxh3)
	Header          Header           `xml:"Header"`
	Query           *Query           `xml:"Query,omitempty"`
	QueryContext    *QueryContext    `xml:"QueryContext,omitempty"`
	PipelineContext *PipelineContext `xml:"PipelineContext,omitempty"`
	Schema          Schema           `xml:"Schema"`
	Data            Data             `xml:"Data"`
	AlarmDetails    *AlarmDetails    `xml:"AlarmDetails,omitempty"`
	// contains filtered or unexported fields
}

DataPacket представляет корневой элемент TDTP сообщения

func NewDataPacket

func NewDataPacket(msgType MessageType, tableName string) *DataPacket

NewDataPacket создает новый пакет с базовыми настройками

func NewErrorPacket

func NewErrorPacket(code, message, table, inReplyTo, serverMode string) *DataPacket

NewErrorPacket creates a minimal valid TDTP error packet (Type="error") that can be written to a file and imported by any TDTP consumer.

This is the "receipt" mechanism: when an operation fails (e.g. key-not-found on decrypt), the caller writes this packet to the output path instead of producing nothing. The receiving side imports it as a normal TDTP packet and sees the structured error rather than a missing file / silent failure.

Fields:

  • code — machine-readable error code (e.g. "KEY_BURNED_BY_OTHER")
  • message — human-readable description
  • table — table name the operation was attempted on (may be empty)
  • inReplyTo — original MessageID of the request (may be empty)
  • serverMode — xZMercury mode ("dev"/"prod") from burn marker; empty if not applicable. "dev" = dev-failover burn (expected during Redis outage, not a theft alert). "prod" = prod burn by unknown party (requires investigation).

func (*DataPacket) GetRows

func (p *DataPacket) GetRows() [][]string

GetRows извлекает все данные из пакета в виде [][]string Правильно обрабатывает экранирование специальных символов.

Это общий узел для сжатых и несжатых пакетов: DecompressData приводит Data.Rows к той же форме (pipe-joined строки), что и обычный разбор, поэтому дальше оба случая идут здесь и стоят одинаково.

Строки независимы друг от друга, поэтому на больших пакетах разбор идёт параллельно. Результат пишется по индексу в заранее выделенный слайс, так что порядок детерминирован и не зависит от планировщика.

func (*DataPacket) MaterializeRows

func (p *DataPacket) MaterializeRows()

MaterializeRows обеспечивает что Data.Rows заполнены из rawRows. Вызывается перед передачей пакета в функции, работающие напрямую с Data.Rows (импорт, сжатие).

func (*DataPacket) SetRows

func (p *DataPacket) SetRows(rows [][]string)

SetRows устанавливает данные в пакет из [][]string Правильно экранирует специальные символы

Сбрасывает rawRows, и это не деталь реализации. GenerateReference оставляет строки в неэкспортируемом rawRows, а writePacketTo предпочитает именно их — так что без сброса "установить строки" не устанавливало бы ничего: пакет уехал бы с прежними значениями, а вызывающий считал бы, что заменил их. Ровно так терялась цепочка pre-export: маскировщик отрабатывал, результат ложился в Data.Rows, а в XML уходили незамаскированные rawRows.

type DictEntry

type DictEntry struct {
	Short string `xml:"short,attr" json:"short"`
	Full  string `xml:"full,attr"  json:"full"`
}

DictEntry — одна запись словаря: короткий токен (например "@W3") и его полное значение (URI, описание, etc.). Применяется только когда ячейка данных РАВНА токену целиком (whole-cell match по regex ^@[A-Za-z][A-Za-z0-9_]*$). Substring внутри текста не трогается.

type DictExpander

type DictExpander struct {
	// contains filtered or unexported fields
}

DictExpander is a precompiled, read-only expander built once from a Dictionary and reused across all rows of a packet. The zero value (empty intern map) is safe: ExpandRow returns the input unchanged.

Usage:

exp := NewDictExpander(pkt.Schema.Dictionary)
for _, row := range pkt.Data.Rows {
    expanded := exp.ExpandRow(row.Value)
}

func NewDictExpander

func NewDictExpander(d *Dictionary) *DictExpander

NewDictExpander builds a DictExpander from a packet Dictionary. Nil or empty Dictionary produces a zero-cost no-op expander.

func (*DictExpander) ExpandRow

func (e *DictExpander) ExpandRow(s string) string

ExpandRow expands all whole-cell dictionary tokens in a pipe-delimited TDTP row value. Cells that are not tokens pass through without allocation.

Fast paths (zero allocation):

  • No dictionary loaded → return s unchanged
  • No '@' byte in s → return s unchanged
  • Tokens found but no expansion matched → return s unchanged

type Dictionary

type Dictionary struct {
	Entries []DictEntry `xml:"Entry" json:"entries"`
}

Dictionary — обёртка над []DictEntry, чтобы encoding/xml корректно опускал секцию когда нет записей (`omitempty` для slice работает только на содержимом, не на контейнерном теге).

type ExecutionResults

type ExecutionResults struct {
	TotalRecordsInTable int  `xml:"TotalRecordsInTable"`
	RecordsAfterFilters int  `xml:"RecordsAfterFilters"`
	RecordsReturned     int  `xml:"RecordsReturned"`
	MoreDataAvailable   bool `xml:"MoreDataAvailable"`
	NextOffset          int  `xml:"NextOffset,omitempty"`
}

ExecutionResults содержит результаты выполнения

type Field

type Field struct {
	Name          string         `xml:"name,attr"                        json:"name"`
	Type          string         `xml:"type,attr"                        json:"type"`
	Length        int            `xml:"length,attr,omitempty"            json:"length"`
	Precision     int            `xml:"precision,attr,omitempty"         json:"precision"`
	Scale         int            `xml:"scale,attr,omitempty"             json:"scale"`
	Key           bool           `xml:"key,attr,omitempty"               json:"key"`
	Timezone      string         `xml:"timezone,attr,omitempty"          json:"timezone,omitempty"`
	Subtype       string         `xml:"subtype,attr,omitempty"           json:"subtype,omitempty"`
	ReadOnly      bool           `xml:"readonly,attr,omitempty"          json:"readonly,omitempty"`       // Read-only поля (timestamp, computed)
	Fixed         bool           `xml:"fixed,attr,omitempty"             json:"fixed,omitempty"`          // v1.3.1: значение не меняется в пределах пакета
	SpecialValues *SpecialValues `xml:"SpecialValues,omitempty"          json:"special_values,omitempty"` // v1.3.1: маркеры специальных значений

	// OriginalName is set by the sanitizer when Name is transformed into a safe
	// SQL identifier. It is never serialized (xml:"-", json:"-") and carries the
	// original field name for storage as a database column comment.
	OriginalName string `xml:"-" json:"-"`
}

Field описывает одно поле таблицы

type Filter

type Filter struct {
	Field    string `xml:"field,attr"`
	Operator string `xml:"operator,attr"`
	Value    string `xml:"value,attr"`
	Value2   string `xml:"value2,attr,omitempty"` // для between
}

Filter представляет одно условие фильтрации

type FilterStat

type FilterStat struct {
	Field          string `xml:"field,attr"`
	Operator       string `xml:"operator,attr"`
	Value          string `xml:"value,attr"`
	RecordsMatched int    `xml:"recordsMatched,attr"`
}

FilterStat статистика одного фильтра

type FilterStatistics

type FilterStatistics struct {
	Filters []FilterStat `xml:"Filter,omitempty"`
	Or      []OrStat     `xml:"Or,omitempty"`
}

FilterStatistics содержит статистику по фильтрам

type Filters

type Filters struct {
	And *LogicalGroup `xml:"And,omitempty"`
	Or  *LogicalGroup `xml:"Or,omitempty"`
}

Filters содержит дерево условий фильтрации

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator отвечает за генерацию TDTP пакетов

func NewGenerator

func NewGenerator() *Generator

NewGenerator создает новый генератор

func (*Generator) DisableCompression

func (g *Generator) DisableCompression()

DisableCompression выключает сжатие

func (*Generator) EnableCompression

func (g *Generator) EnableCompression()

EnableCompression включает сжатие с уровнем по умолчанию

func (*Generator) GenerateAlarm

func (g *Generator) GenerateAlarm(
	tableName string,
	severity, code, message string,
	affectedRecords int,
	schema Schema,
	rows [][]string,
) (*DataPacket, error)

GenerateAlarm создает alarm пакет

func (*Generator) GenerateError

func (g *Generator) GenerateError(packageUUID, pipeline, errorCode, errorMessage string) (*DataPacket, error)

GenerateError создает error пакет для записи в таблицу tdtp_errors. Используется когда pipeline не может завершиться штатно (например, xZMercury недоступен). В отличие от alarm, error — стандартный DataPacket с Schema+Data, совместимый с любым consumer.

func (*Generator) GenerateReference

func (g *Generator) GenerateReference(tableName string, schema Schema, rows [][]string) ([]*DataPacket, error)

GenerateReference создает reference пакет (полный справочник)

func (*Generator) GenerateRequest

func (g *Generator) GenerateRequest(tableName string, query *Query, sender, recipient string) (*DataPacket, error)

GenerateRequest создает request пакет с запросом

func (*Generator) GenerateResponse

func (g *Generator) GenerateResponse(
	tableName string,
	inReplyTo string,
	schema Schema,
	rows [][]string,
	queryContext *QueryContext,
	sender, recipient string,
) ([]*DataPacket, error)

GenerateResponse создает response пакет с результатами

func (*Generator) SetColumnarLayout

func (g *Generator) SetColumnarLayout(on bool)

SetSkipSpecialValues отключает DetectAndApply для максимальной скорости экспорта. Используется с флагом --fast: NULL/NaN/Inf не получат canonical markers в схеме. Применять только когда источник гарантированно не содержит спецзначений. SetColumnarLayout включает колоночную раскладку Data.

По умолчанию выключена, и это осознанно: атрибут layout меняет смысл <R>, поэтому читатель, ничего о нём не знающий, разберёт такой пакет неверно и молча. Включать только там, где известно, что принимающая сторона умеет разворачивать (см. ExpandColumnarRows).

func (*Generator) SetCompression

func (g *Generator) SetCompression(opts CompressionOptions)

SetCompression устанавливает настройки сжатия

func (*Generator) SetCompressionLevel

func (g *Generator) SetCompressionLevel(level int)

SetCompressionLevel устанавливает уровень сжатия (1-19)

func (*Generator) SetMaxMessageSize

func (g *Generator) SetMaxMessageSize(size int)

SetMaxMessageSize устанавливает максимальный размер сообщения

func (*Generator) SetSkipSpecialValues

func (g *Generator) SetSkipSpecialValues(skip bool)

func (*Generator) ToXML

func (g *Generator) ToXML(packet *DataPacket, _ bool) ([]byte, error)

ToXML сериализует пакет в XML. indent игнорируется — ручной writer всегда даёт компактный XML (корректный и быстрый). Прежний xml.MarshalIndent на Data-секции занимал ~229ms на 100k строк; ручной writer делает то же за ~15ms. rawRows (если установлены) записываются напрямую без RowsToData.

func (*Generator) WriteToFile

func (g *Generator) WriteToFile(packet *DataPacket, filename string) error

WriteToFile записывает пакет прямо в файл без промежуточного []byte.

func (*Generator) WriteToFileFast

func (g *Generator) WriteToFileFast(packet *DataPacket, filename string) error

WriteToFileFast записывает пакет прямо в файл без промежуточного []byte. Используется вместо WriteToFile для экспорта в файлы.

func (*Generator) WriteToWriter

func (g *Generator) WriteToWriter(packet *DataPacket, w io.Writer) error

WriteToWriter записывает пакет в writer.

type Header struct {
	Type          MessageType `xml:"Type"`
	TableName     string      `xml:"TableName"`
	MessageID     string      `xml:"MessageID"`
	InReplyTo     string      `xml:"InReplyTo,omitempty"`
	PartNumber    int         `xml:"PartNumber,omitempty"`
	TotalParts    int         `xml:"TotalParts,omitempty"`
	RecordsInPart int         `xml:"RecordsInPart,omitempty"`
	Timestamp     time.Time   `xml:"Timestamp"`
	Sender        string      `xml:"Sender,omitempty"`
	Recipient     string      `xml:"Recipient,omitempty"`
}

Header содержит метаданные сообщения

type IntegrityResult

type IntegrityResult struct {
	SchemaXXH3 string // xxh3_128 of canonical Schema bytes (without xxh3 attr)
	DataXXH3   string // xxh3_128 of raw row values (before compression)
	PacketXXH3 string // xxh3_128(SchemaXXH3 + "|" + DataXXH3) — packet fingerprint
}

IntegrityResult carries the three XXH3-128 hashes computed for a packet.

The three-level model:

SchemaXXH3  = xxh3_128(canonical Schema XML)        — "паспорт данных"
DataXXH3    = xxh3_128(raw row values)               — "данные"
PacketXXH3  = xxh3_128(SchemaXXH3 + "|" + DataXXH3) — "odtp fingerprint"

All values are 32-char lowercase hex strings (128 bits = 16 bytes).

func ComputeIntegrity

func ComputeIntegrity(pkt *DataPacket) (*IntegrityResult, error)

ComputeIntegrity calculates the three-level XXH3 integrity hashes for pkt and stamps them onto the packet in place:

pkt.Schema.XXH3  ← SchemaXXH3
pkt.Data.XXH3    ← DataXXH3
pkt.XXH3         ← PacketXXH3 (the "odtp" fingerprint)

Call after rows are materialized and BEFORE compression/serialization. The existing pkt.Data.Checksum (xxh3_64 of compressed blob) is unaffected.

type LogicalGroup

type LogicalGroup struct {
	Filters []Filter       `xml:"Filter,omitempty"`
	And     []LogicalGroup `xml:"And,omitempty"`
	Or      []LogicalGroup `xml:"Or,omitempty"`
}

LogicalGroup представляет логическую группу условий

type MarkerValue

type MarkerValue struct {
	Marker string `xml:"marker,attr" json:"marker"`
}

MarkerValue содержит строковый маркер специального значения

type MessageType

type MessageType string

MessageType определяет тип TDTP сообщения

const (
	TypeReference MessageType = "reference"
	TypeRequest   MessageType = "request"
	TypeResponse  MessageType = "response"
	TypeAlarm     MessageType = "alarm"
	TypeError     MessageType = "error"
)

TDTP message type constants.

type OrStat

type OrStat struct {
	RecordsMatched int          `xml:"recordsMatched,attr"`
	Filters        []FilterStat `xml:"Filter,omitempty"`
}

OrStat статистика OR группы

type OrderBy

type OrderBy struct {
	Field     string       `xml:"field,attr,omitempty"`
	Direction string       `xml:"direction,attr,omitempty"`
	Fields    []OrderField `xml:"Field,omitempty"` // множественная сортировка
}

OrderBy определяет сортировку

type OrderField

type OrderField struct {
	Name      string `xml:"name,attr"`
	Direction string `xml:"direction,attr"`
}

OrderField для множественной сортировки

type Parser

type Parser struct{}

Parser отвечает за парсинг TDTP пакетов

func NewParser

func NewParser() *Parser

NewParser создает новый парсер

func (*Parser) DecompressData

func (p *Parser) DecompressData(ctx context.Context, packet *DataPacket, decompressor func(ctx context.Context, compressed string, algo string) ([]string, error)) error

DecompressData распаковывает сжатые данные в пакете decompressor - функция распаковки, принимает сжатую строку и algo ("zstd", "kanzi" и т.д.) Если данные не сжаты, возвращает их как есть

func (*Parser) ExpandCompactRows

func (p *Parser) ExpandCompactRows(packet *DataPacket) error

ExpandCompactRows разворачивает compact-формат в пакете. Это удобная обёртка над одноимённой функцией пакета. Если Data.Compact == false — ничего не делает.

func (*Parser) GetCompressionAlgorithm

func (p *Parser) GetCompressionAlgorithm(packet *DataPacket) string

GetCompressionAlgorithm возвращает алгоритм сжатия данных

func (*Parser) GetRowValues

func (p *Parser) GetRowValues(row Row) []string

GetRowValues разбивает строку данных на значения полей. Обрабатывает экранирование: \| → |, \\ → \, \n → newline.

Ёмкость результата считается точно — `strings.Count` по разделителю. Это один проход с векторной инструкцией, и он дешевле любого промаха оценки: заниженная ёмкость стоит пересборки среза с копированием (на строке из сотни коротких полей это 1.75× ко времени), завышенная — лишней памяти (на четырёх длинных полях доходило до 22×).

func (*Parser) GetRowValuesInto

func (p *Parser) GetRowValuesInto(row Row, dst []string) []string

GetRowValuesInto разбирает строку в переданный срез, переиспользуя его память: dst усекается до нуля и заполняется заново.

Нужен там, где значения потребляются и выбрасываются внутри итерации — тогда разбор строки обходится без единой аллокации (около 100 нс против 300 нс с выделением среза на каждую строку).

ВНИМАНИЕ: результат ссылается на память dst, поэтому переиспользовать буфер можно только если предыдущий результат больше не нужен. Для DataPacket.GetRows это не годится — он удерживает срез каждой строки, и общий буфер сделал бы их всех алиасами одного массива. Передайте nil, чтобы получить свежий срез.

func (*Parser) IsCompressed

func (p *Parser) IsCompressed(packet *DataPacket) bool

IsCompressed проверяет, сжаты ли данные в пакете

func (*Parser) Parse

func (p *Parser) Parse(r io.Reader) (*DataPacket, error)

Parse парсит TDTP пакет из reader.

Вход до maxBufferedParse вычитывается целиком, чтобы разбор шёл тем же гибридным путём, что и ParseBytes — потоковый xml.Decoder гонял бы каждую строку через reflection. Для обычных пакетов это почти не стоит памяти: оба вызывающих с io.Reader (cmd/tdtp-svg, pkg/etl/importer) и так держат байты в памяти и лишь оборачивают их в bytes.Reader.

Если вход больше порога, дочитывание идёт потоковым декодером, а уже прочитанный кусок подставляется обратно через io.MultiReader — перематывать reader не требуется.

func (*Parser) ParseBytes

func (p *Parser) ParseBytes(data []byte) (*DataPacket, error)

ParseBytes парсит TDTP пакет из байтового массива.

Сначала пробуется ручной разбор Data-секции (parser_fast.go) — зеркало writePacketTo: reflection остаётся только на мелких секциях, тысячи <R> сканируются вручную. Если форма пакета отличается от ожидаемой, tryFastParse возвращает ok=false и разбор идёт обычным xml.Unmarshal — включая выдачу ошибки на действительно битом XML.

func (*Parser) ParseBytesWithDecompression

func (p *Parser) ParseBytesWithDecompression(data []byte, decompressor func(ctx context.Context, compressed string, algo string) ([]string, error)) (*DataPacket, error)

ParseBytesWithDecompression парсит пакет из байтов и автоматически распаковывает

func (*Parser) ParseFile

func (p *Parser) ParseFile(filename string) (*DataPacket, error)

ParseFile парсит TDTP пакет из файла

func (*Parser) ParseWithDecompression

func (p *Parser) ParseWithDecompression(r io.Reader, decompressor func(ctx context.Context, compressed string, algo string) ([]string, error)) (*DataPacket, error)

ParseWithDecompression парсит пакет и автоматически распаковывает сжатые данные

type PartResult

type PartResult struct {
	Packet    *DataPacket // Сгенерированный пакет
	PartNum   int         // Номер части (1-based)
	RowsCount int         // Количество строк в этой части
	Error     error       // Ошибка генерации (если есть)
}

PartResult представляет результат генерации одной части

type PipelineContext

type PipelineContext struct {
	Pipeline  PipelineInfo  `xml:"Pipeline"`
	Variables []PipelineVar `xml:"Variables>Var,omitempty"`
}

PipelineContext содержит метаданные pipeline, встроенные в пакет при экспорте (v1.4). Позволяет получателю проверить параметры источника через --expect-var.

type PipelineInfo

type PipelineInfo struct {
	Name    string `xml:"name,attr"`
	Version string `xml:"version,attr,omitempty"`
}

PipelineInfo описывает pipeline-источник: имя и версию конфига.

type PipelineVar

type PipelineVar struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

PipelineVar — одна переменная pipeline, использованная при экспорте.

type ProtocolVersion

type ProtocolVersion struct {
	// contains filtered or unexported fields
}

ProtocolVersion — разобранная версия протокола TDTP ("1.0", "1.3.1", "1.4").

Компоненты сравниваются как числа, а не как символы. До появления этого типа версии сравнивались строкой (`version <= "1.3.1"`), и на нынешнем наборе значений это давало верный ответ по совпадению: '0', '2', '3' идут в таблице символов до '4'. Совпадение кончается на двузначном компоненте — "1.10" строкой меньше "1.3.1", потому что '1' < '3', то есть версия 1.10 молча считалась бы ДОревней 1.3.1 и переставала требовать зарегистрированный хеш. Отказ тихий: пакет импортируется, просто без проверки, которую его версия обязана требовать.

func ParseProtocolVersion

func ParseProtocolVersion(s string) (ProtocolVersion, bool)

ParseProtocolVersion разбирает строку версии.

ok=false означает, что строка не является версией: пустая, с нечисловым компонентом, с пустым компонентом ("1..2") или с отрицательным числом. Решение, что делать с таким значением, принимает вызывающий — см. NeedsRowCountCheck.

func (ProtocolVersion) Compare

func (v ProtocolVersion) Compare(other ProtocolVersion) int

Compare возвращает -1, 0 или +1.

Отсутствующие компоненты считаются нулями, поэтому "1.4" и "1.4.0" равны, а "1.3" меньше "1.3.1".

type Query

type Query struct {
	Language string   `xml:"language,attr"`
	Version  string   `xml:"version,attr"`
	Fields   []string `xml:"Fields>Field,omitempty"` // column projection: nil/empty = SELECT *
	Filters  *Filters `xml:"Filters,omitempty"`
	OrderBy  *OrderBy `xml:"OrderBy,omitempty"`
	Limit    int      `xml:"Limit,omitempty"`
	Offset   int      `xml:"Offset,omitempty"`
}

Query представляет TDTQL запрос

func NewQuery

func NewQuery() *Query

NewQuery создает новый TDTQL запрос

type QueryContext

type QueryContext struct {
	OriginalQuery    Query             `xml:"OriginalQuery"`
	ExecutionResults ExecutionResults  `xml:"ExecutionResults"`
	FilterStatistics *FilterStatistics `xml:"FilterStatistics,omitempty"`
	Encryption       string            `xml:"encryption,attr,omitempty"` // v1.5: "aes-256-gcm" if Encrypted holds ciphertext
	Encrypted        string            `xml:",chardata"`                 // v1.5: base64(nonce||ciphertext) when Encryption != ""
}

QueryContext содержит контекст выполнения запроса (в response)

Encryption/Encrypted (since TDTP v1.5) — when Encryption is non-empty (currently only "aes-256-gcm"), OriginalQuery/ExecutionResults/ FilterStatistics are NOT populated — the entire section content is opaque ciphertext in Encrypted instead. Always check Encryption before reading the other fields; see docs/tdtp-protocol-schema.md → "v1.5".

type Row

type Row struct {
	Value string `xml:",chardata"`
}

Row представляет одну строку данных

type Schema

type Schema struct {
	Fields     []Field     `xml:"Field"                    json:"fields"`
	Dictionary *Dictionary `xml:"Dictionary,omitempty"     json:"dictionary,omitempty"`
	XXH3       string      `xml:"xxh3,attr,omitempty"      json:"xxh3,omitempty"`        // v1.4: xxh3_128 of Schema content
	Encryption string      `xml:"encryption,attr,omitempty" json:"encryption,omitempty"` // v1.5: "aes-256-gcm" if Encrypted holds ciphertext
	Encrypted  string      `xml:",chardata"                 json:"encrypted,omitempty"`  // v1.5: base64(nonce||ciphertext) when Encryption != ""
}

Schema описывает структуру таблицы.

Dictionary (since TDTP v1.4) — опциональная таблица сокращений для громоздких повторяющихся значений (XML namespace URI, CRS-коды, ontology prefixes и т.п.). Хранится как opaque metadata: core НЕ разворачивает токены автоматически, это делает domain-консьюмер. См. ExpandDictionary / ContractDictionary в этом пакете.

XXH3 (since TDTP v1.4) — xxh3_128 fingerprint of the canonical Schema bytes (excluding the xxh3 attribute itself). Computed by ComputeIntegrity.

Encryption/Encrypted (since TDTP v1.5) — when Encryption is non-empty (currently only "aes-256-gcm"), Fields/Dictionary are NOT populated — the entire section content is opaque ciphertext in Encrypted instead. Always check Encryption before reading Fields; see docs/tdtp-protocol-schema.md → "v1.5" for the wire format and why.

func DetectAndApply

func DetectAndApply(rows [][]string, sch Schema) ([][]string, Schema)

DetectAndApply scans all rows, detects which SpecialValues actually appear per field, updates Schema.Fields accordingly (only for fields where specials were found), and re-encodes the affected cell values using canonical markers.

Rules for float/decimal columns:

  • nullSentinel (DB NULL) → SpecialValues.Null = "[NULL]"
  • "NaN" → SpecialValues.NaN = "NaN"
  • "Inf"/"+Inf"/"Infinity" → SpecialValues.Infinity = "INF"
  • "-Inf"/"-Infinity" → SpecialValues.NegInfinity = "-INF"

Rules for date/datetime/timestamp columns:

  • nullSentinel → SpecialValues.Null = "[NULL]"
  • "0000-00-00" → SpecialValues.NoDate = "0000-00-00"
  • "Infinity"/"+Inf" etc → SpecialValues.Infinity = "INF" (PostgreSQL date infinity)
  • "-Infinity"/"-Inf" etc → SpecialValues.NegInfinity = "-INF"

The returned rows and schema are safe to use for packet generation. If no specials are found the function returns the inputs unchanged.

type SpecialValues

type SpecialValues struct {
	Null        *MarkerValue `xml:"Null,omitempty"        json:"null,omitempty"`
	Infinity    *MarkerValue `xml:"Infinity,omitempty"    json:"infinity,omitempty"`
	NegInfinity *MarkerValue `xml:"NegInfinity,omitempty" json:"neg_infinity,omitempty"`
	NaN         *MarkerValue `xml:"NaN,omitempty"         json:"nan,omitempty"`
	NoDate      *MarkerValue `xml:"NoDate,omitempty"      json:"no_date,omitempty"`
}

SpecialValues содержит маркеры специальных значений для поля (v1.3.1)

type StreamingGenerator

type StreamingGenerator struct {
	*Generator
	// contains filtered or unexported fields
}

StreamingGenerator генерирует TDTP пакеты в потоковом режиме В отличие от обычного Generator, не требует загрузки всех данных в память

func NewStreamingGenerator

func NewStreamingGenerator() *StreamingGenerator

NewStreamingGenerator создает новый потоковый генератор

func (*StreamingGenerator) GeneratePartsStream

func (sg *StreamingGenerator) GeneratePartsStream(
	ctx context.Context,
	rowsChan <-chan []string,
	schema Schema,
	tableName string,
	msgType MessageType,
) (<-chan *PartResult, <-chan *StreamingSummary)

GeneratePartsStream генерирует части по мере чтения данных из канала

Ключевые особенности: - Не требует загрузки всех данных в память - TotalParts = 0 в каждой части (unknown до конца экспорта) - Части генерируются по мере поступления данных - В конце возвращается StreamingSummary с фактическими значениями

Использование:

rowsChan := make(chan []string)
go func() {
    // Читаем данные из БД и отправляем в канал
    for rows.Next() {
        rowsChan <- scanRow()
    }
    close(rowsChan)
}()

partsChan, summaryChan := sg.GeneratePartsStream(ctx, rowsChan, schema, "users", TypeReference)
for part := range partsChan {
    if part.Error != nil {
        log.Error(part.Error)
        continue
    }
    broker.Send(ctx, part.Packet)
}
summary := <-summaryChan

func (*StreamingGenerator) GeneratePartsStreamWithSender

func (sg *StreamingGenerator) GeneratePartsStreamWithSender(
	ctx context.Context,
	rowsChan <-chan []string,
	schema Schema,
	tableName string,
	msgType MessageType,
	inReplyTo string,
	sender string,
	recipient string,
) (<-chan *PartResult, <-chan *StreamingSummary)

GeneratePartsStreamWithSender генерирует части с указанием sender/recipient Используется для Response пакетов

func (*StreamingGenerator) SetPartSize

func (sg *StreamingGenerator) SetPartSize(size int)

SetPartSize устанавливает максимальный размер части в байтах

type StreamingSummary

type StreamingSummary struct {
	MessageIDBase string // Базовый MessageID для всех частей
	TotalParts    int    // Фактическое количество частей
	TotalRows     int    // Общее количество строк
}

StreamingSummary содержит итоговую информацию о потоковом экспорте

Jump to

Keyboard shortcuts

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