Documentation
¶
Overview ¶
Package packet provides functionality for the TDTP framework.
Index ¶
- Constants
- Variables
- func AppendMSSQLDatetime(dst []byte, b []byte) []byte
- func AppendMSSQLDatetime2(dst []byte, b []byte, scale int) []byte
- func AppendTimestampRFC3339Nano(dst []byte, t time.Time) []byte
- func ApplyCompact(pkt *DataPacket, fixedFieldNames []string, tail bool) error
- func ApplyCompactReport(pkt *DataPacket, fixedFieldNames []string, tail bool) (dropped []string, err error)
- func AutoFixedFields(schema Schema) []string
- func BigintDecimal(f Field) bool
- func BigintValue(v string) any
- func BuildEscapeMask(schema Schema) []bool
- func BumpVersion(pkt *DataPacket, minVersion string)
- func CheckDeclaredIntegrity(pkt *DataPacket) error
- func CompareProtocolVersions(a, b string) (int, bool)
- func ContractDictionary(entries []DictEntry, cell string) string
- func DecryptSections(pkt *DataPacket, key []byte) error
- func Downgrade(pkt *DataPacket)
- func EncryptSections(pkt *DataPacket, key []byte) error
- func EnsureColumnar(pkt *DataPacket)
- func ExpandColumnarRows(pkt *DataPacket) error
- func ExpandCompactRows(packet *DataPacket) error
- func ExpandDictionary(entries []DictEntry, cell string) string
- func ExtractKeyFields(schema Schema) []string
- func FormatMSSQLDatetime(b []byte) (string, error)
- func FormatMSSQLDatetime2(b []byte, scale int) (string, error)
- func FormatTimestampFast(t time.Time) string
- func GenerateUUID() string
- func GetFieldIndices(schema Schema, fieldNames []string) []int
- func HasIntegrity(pkt *DataPacket) bool
- func IsDateFieldType(fieldType string) bool
- func IsDictToken(s string) bool
- func IsEncrypted(pkt *DataPacket) bool
- func IsRawSpecialForm(fieldType, value string) bool
- func JoinRowEscaped(fields []string) string
- func NeedsRowCountCheck(version string) bool
- func NullMarkerOf(f Field) string
- func PacketSizeBudget(mb int) int
- func ParseRows(rows []Row, parser *Parser) [][]string
- func ResolveFixedFields(schema Schema, explicit []string) []string
- func SchemaEquals(a, b Schema) bool
- func UpdatePartTotalParts(packets []*DataPacket, totalParts int)
- func ValidateDictionary(entries []DictEntry) error
- func VerifyIntegrity(pkt *DataPacket) error
- func VerifyRowCount(pkt *DataPacket) error
- type AlarmDetails
- type CompactTailError
- type CompressionOptions
- type Data
- type DataPacket
- type DictEntry
- type DictExpander
- type Dictionary
- type ExecutionResults
- type Field
- type Filter
- type FilterStat
- type FilterStatistics
- type Filters
- type Generator
- func (g *Generator) DisableCompression()
- func (g *Generator) EnableCompression()
- func (g *Generator) GenerateAlarm(tableName string, severity, code, message string, affectedRecords int, ...) (*DataPacket, error)
- func (g *Generator) GenerateError(packageUUID, pipeline, errorCode, errorMessage string) (*DataPacket, error)
- func (g *Generator) GenerateReference(tableName string, schema Schema, rows [][]string) ([]*DataPacket, error)
- func (g *Generator) GenerateRequest(tableName string, query *Query, sender, recipient string) (*DataPacket, error)
- func (g *Generator) GenerateResponse(tableName string, inReplyTo string, schema Schema, rows [][]string, ...) ([]*DataPacket, error)
- func (g *Generator) SetColumnarLayout(on bool)
- func (g *Generator) SetCompression(opts CompressionOptions)
- func (g *Generator) SetCompressionLevel(level int)
- func (g *Generator) SetMaxMessageSize(size int)
- func (g *Generator) SetSkipSpecialValues(skip bool)
- func (g *Generator) ToXML(packet *DataPacket, _ bool) ([]byte, error)
- func (g *Generator) WriteToFile(packet *DataPacket, filename string) error
- func (g *Generator) WriteToFileFast(packet *DataPacket, filename string) error
- func (g *Generator) WriteToWriter(packet *DataPacket, w io.Writer) error
- type Header
- type IntegrityResult
- type LogicalGroup
- type MarkerValue
- type MessageType
- type OrStat
- type OrderBy
- type OrderField
- type Parser
- func (p *Parser) DecompressData(ctx context.Context, packet *DataPacket, ...) error
- func (p *Parser) ExpandCompactRows(packet *DataPacket) error
- func (p *Parser) GetCompressionAlgorithm(packet *DataPacket) string
- func (p *Parser) GetRowValues(row Row) []string
- func (p *Parser) GetRowValuesInto(row Row, dst []string) []string
- func (p *Parser) IsCompressed(packet *DataPacket) bool
- func (p *Parser) Parse(r io.Reader) (*DataPacket, error)
- func (p *Parser) ParseBytes(data []byte) (*DataPacket, error)
- func (p *Parser) ParseBytesWithDecompression(data []byte, ...) (*DataPacket, error)
- func (p *Parser) ParseFile(filename string) (*DataPacket, error)
- func (p *Parser) ParseWithDecompression(r io.Reader, ...) (*DataPacket, error)
- type PartResult
- type PipelineContext
- type PipelineInfo
- type PipelineVar
- type ProtocolVersion
- type Query
- type QueryContext
- type Row
- type Schema
- type SpecialValues
- type StreamingGenerator
- func (sg *StreamingGenerator) GeneratePartsStream(ctx context.Context, rowsChan <-chan []string, schema Schema, tableName string, ...) (<-chan *PartResult, <-chan *StreamingSummary)
- func (sg *StreamingGenerator) GeneratePartsStreamWithSender(ctx context.Context, rowsChan <-chan []string, schema Schema, tableName string, ...) (<-chan *PartResult, <-chan *StreamingSummary)
- func (sg *StreamingGenerator) SetPartSize(size int)
- type StreamingSummary
Constants ¶
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.
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.
const DefaultMaxMessageSize = 3_800_000
DefaultMaxMessageSize — размер по умолчанию (3.8MB оценка → ~1.9MB реального XML). Внутренняя оценка намеренно вдвое больше реального XML, т.к. размер строк считается в UTF-16 единицах (MSMQ/COM-совместимость).
const EncryptionAlgoAESGCM = "aes-256-gcm"
EncryptionAlgoAESGCM is the only algorithm value v1.5 currently writes to the encryption="..." attribute on QueryContext/Schema/Data.
const InReplyToDirectExport = "DirectExport"
InReplyToDirectExport - зарезервированное значение для response-пакетов, сгенерированных командой --export без входящего request (автономный экспорт).
const LayoutColumns = "columns"
LayoutColumns — значение Data.Layout для колоночной раскладки.
const MaxSchemaBytes = 200 * 1024
MaxSchemaBytes — предел размера сериализованной Schema, включая Dictionary.
Schema копируется в каждую часть многотомного пакета, поэтому её размер умножается на число частей. Обычная схема — это описания полей, и до этого предела ей далеко: даже таблица на 1600 колонок укладывается заметно ниже. Реально упереться можно только крупным Dictionary. Автопостроения словаря в фреймворке пока нет — словарь либо приходит от внешнего продюсера через библиотечный API, либо содержит одну служебную запись (@MRC с адресом Mercury). Когда построение появится, оно должно укладываться в этот же предел.
const MaxSchemaBytesRead = 1024 * 1024
MaxSchemaBytesRead — жёсткий предел размера Schema при чтении.
Отдельная величина от MaxSchemaBytes (предел генерации, 200KB), и намеренно выше него. Предел на записи — правило формата: он касается только пакетов, которые мы создаём. Предел на чтении отвергал бы уже существующие данные, поэтому он не правило формата, а защита от патологического входа: чужой пакет с гигантской схемой иначе будет разобран и размещён в памяти целиком. Между 200KB и этим порогом пакет читается, но с предупреждением.
Variables ¶
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 ¶
AppendMSSQLDatetime форматирует MSSQL DATETIME в dst без аллокаций памяти.
ВНИМАНИЕ: границы не проверяются. b должен быть длиной ровно 8 байт, а поле дней — давать год 0001..9999. На битых байтах (год за 9999) put4 уходит за пределы digitsTable и функция паникует. Вход обязан быть проверен вызывающим кодом — либо используйте FormatMSSQLDatetime, который проверяет длину.
func AppendMSSQLDatetime2 ¶
AppendMSSQLDatetime2 форматирует MSSQL DATETIME2 в dst без аллокаций памяти.
ВНИМАНИЕ: границы не проверяются — см. предупреждение у AppendMSSQLDatetime. scale обязан быть 0..7, len(b) — timeLen(scale)+3.
func AppendTimestampRFC3339Nano ¶
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 ¶
AutoFixedFields возвращает имена полей схемы, у которых имя начинается с "_". Используется для auto-detect fixed полей при compact-экспорте.
func BigintDecimal ¶
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 ¶
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 ¶
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 ¶
CompareProtocolVersions сравнивает две строки версий.
ok=false, если хотя бы одна из них не разбирается; результат в этом случае смысла не имеет и использовать его нельзя.
func ContractDictionary ¶
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 ¶
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 ¶
ExtractKeyFields извлекает ключевые поля из схемы
func FormatMSSQLDatetime ¶
FormatMSSQLDatetime возвращает строку ISO-8601 для 8-байтового DATETIME.
func FormatMSSQLDatetime2 ¶
FormatMSSQLDatetime2 возвращает строку ISO-8601 для DATETIME2(scale).
func FormatTimestampFast ¶
FormatTimestampFast — замена schema.FormatTimestamp с тем же результатом.
func GenerateUUID ¶
func GenerateUUID() string
GenerateUUID создает UUID v4 (публичная обёртка для использования вне пакета).
func GetFieldIndices ¶
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 ¶
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 ¶
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 ¶
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 ¶
JoinRowEscaped объединяет поля строки через | с TDTP-экранированием. Используется везде где []string → pipe-строка (компрессия, Python-байндинги).
func NeedsRowCountCheck ¶
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 ¶
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 ¶
PacketSizeBudget переводит N мегабайт реального XML в бюджет для SetMaxMessageSize — те же единицы, что estimateRowSize (вдвое больше UTF-8 байта). Общая для CLI (--packet-size) и pipeline (output.tdtp.packet_size_mb); не путать с packet_kb в YAML — там множителя нет намеренно, и выравнивать их нельзя (см. CLAUDE.md).
func ResolveFixedFields ¶
ResolveFixedFields возвращает финальный список fixed полей. Если explicit непустой — возвращает его. Иначе auto-detect по _ prefix.
func SchemaEquals ¶
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 ¶
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 ¶
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 ¶
RowsToColumnarData раскладывает строки по колонкам: каждый Row результата — одна колонка целиком, значения разделены '|'.
Экранирование берётся то же, что у построчного пути (writeEscaped через mask), и это не выбор из удобства. Два писателя в пакете уже дают разные байты на значении с переводом строки; третья схема добавила бы третий вариант тех же данных. Колоночная раскладка наследует каноническую.
func RowsToCompactData ¶
RowsToCompactData преобразует [][]string в Data с compact-форматом.
Для полей с Fixed=true значение пишется только при первом появлении или при смене значения (новая группа). В остальных строках группы на этой позиции записывается пустая строка (пропуск).
Если tail=true — последняя строка записывается с явными значениями всех fixed полей (carry-снэпшот). Это позволяет:
- читать данные с конца пакета / потока без полного прохода с начала;
- валидировать консистентность carry-forward при декодировании;
- передавать carry-состояние следующему чанку через атрибут carry="...".
Если в схеме нет ни одного fixed поля — возвращает обычный Data (compact=false).
Предусловие: в fixed поле нет пустых значений. Пустая позиция в compact — это «бери значение сверху», и настоящее "" декодер молча заменит значением предыдущей группы. ApplyCompact это предусловие обеспечивает сам: поле с "" он из fixed исключает.
func RowsToData ¶
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 (*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 ¶
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 ¶
SetCompressionLevel устанавливает уровень сжатия (1-19)
func (*Generator) SetMaxMessageSize ¶
SetMaxMessageSize устанавливает максимальный размер сообщения
func (*Generator) SetSkipSpecialValues ¶
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 ¶
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 ¶
OrderField для множественной сортировки
type Parser ¶
type Parser struct{}
Parser отвечает за парсинг TDTP пакетов
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 ¶
GetRowValues разбивает строку данных на значения полей. Обрабатывает экранирование: \| → |, \\ → \, \n → newline.
Ёмкость результата считается точно — `strings.Count` по разделителю. Это один проход с векторной инструкцией, и он дешевле любого промаха оценки: заниженная ёмкость стоит пересборки среза с копированием (на строке из сотни коротких полей это 1.75× ко времени), завышенная — лишней памяти (на четырёх длинных полях доходило до 22×).
func (*Parser) GetRowValuesInto ¶
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 парсит пакет из байтов и автоматически распаковывает
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 ¶
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 запрос
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 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 ¶
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 содержит итоговую информацию о потоковом экспорте