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

base

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: 21 Imported by: 0

README

Package base — shared helpers for database adapters

Overview

base holds the reusable pieces every database adapter needs, so the SQLite, PostgreSQL, MS SQL Server and MySQL adapters do not each carry their own copy.

The problem

Before base existed:

  • about 800 lines of duplicated export code across four adapters
  • about 600 lines of duplicated import code across four adapters
  • about 300 lines of duplicated type conversion
  • 1700 lines of duplication in total — a third of the adapters' code

The solution

base centralises the shared logic into reusable components:

pkg/adapters/base/
├── export_helper.go      - shared export logic
├── import_helper.go      - shared import logic
├── type_converter.go     - type conversion for every adapter
├── sql_adapter.go        - SQL dialect differences
└── doc.go                - package documentation

The components

1. ExportHelper

Shared logic for exporting data into TDTP packets.

Methods:

  • ExportTable() — export a whole table
  • ExportTableWithQuery() — export with TDTQL filtering, pushed down into SQL
  • ExportTableIncremental() — incremental synchronisation

Interfaces:

type SchemaReader interface {
    GetTableSchema(ctx context.Context, tableName string) (packet.Schema, error)
}

type DataReader interface {
    ReadAllRows(ctx context.Context, tableName string, schema packet.Schema) ([][]string, error)
    ReadRowsWithSQL(ctx context.Context, sql string, schema packet.Schema) ([][]string, error)
    GetRowCount(ctx context.Context, tableName string) (int64, error)
}

type SQLAdapter interface {
    AdaptSQL(standardSQL string, tableName string, schema packet.Schema, query *packet.Query) string
}
2. ImportHelper

Shared logic for importing TDTP packets into a database.

Methods:

  • ImportPacket() — import one packet
  • ImportPackets() — import several packets atomically
  • temporary tables, for an atomic replacement

Interfaces:

type TableManager interface {
    TableExists(ctx context.Context, tableName string) (bool, error)
    CreateTable(ctx context.Context, tableName string, schema packet.Schema) error
    DropTable(ctx context.Context, tableName string) error
    RenameTable(ctx context.Context, oldName, newName string) error
}

type DataInserter interface {
    InsertRows(ctx context.Context, tableName string, schema packet.Schema,
               rows []packet.Row, strategy adapters.ImportStrategy) error
}
3. UniversalTypeConverter

Type conversion between a database's types and TDTP's.

Methods:

  • ConvertValueToTDTP() — database value to TDTP
  • DBValueToString() — database value to a string, respecting that database's quirks
  • TypedValueToSQL() — TDTP value to a SQL parameter for a prepared statement

Database-specific types handled:

  • PostgreSQL: UUID, JSONB, INET, ARRAY, NUMERIC
  • MS SQL Server: UNIQUEIDENTIFIER, TIMESTAMP/ROWVERSION, NVARCHAR
4. SQLAdapter

Papers over SQL dialect differences.

Implementations:

  • StandardSQLAdapter — SQLite, PostgreSQL, MySQL (LIMIT/OFFSET)
  • MSSQLAdapter — MS SQL Server (OFFSET/FETCH)

Usage

Step 1: implement the interfaces in your adapter
package sqlite

import (
    "github.com/ruslano69/tdtp-framework/pkg/adapters/base"
)

type Adapter struct {
    db           *sql.DB
    exportHelper *base.ExportHelper
    importHelper *base.ImportHelper
    converter    *base.UniversalTypeConverter
}

// SchemaReader
func (a *Adapter) GetTableSchema(ctx context.Context, tableName string) (packet.Schema, error) {
    // The SQLite-specific part
}

// DataReader
func (a *Adapter) ReadAllRows(ctx context.Context, tableName string, schema packet.Schema) ([][]string, error) {
    // The SQLite-specific part
}

func (a *Adapter) ReadRowsWithSQL(ctx context.Context, sql string, schema packet.Schema) ([][]string, error) {
    // The SQLite-specific part
}

func (a *Adapter) GetRowCount(ctx context.Context, tableName string) (int64, error) {
    // The SQLite-specific part
}

// TableManager
func (a *Adapter) TableExists(ctx context.Context, tableName string) (bool, error) { ... }
func (a *Adapter) CreateTable(ctx context.Context, tableName string, schema packet.Schema) error { ... }
func (a *Adapter) DropTable(ctx context.Context, tableName string) error { ... }
func (a *Adapter) RenameTable(ctx context.Context, oldName, newName string) error { ... }

// DataInserter
func (a *Adapter) InsertRows(ctx context.Context, tableName string, schema packet.Schema,
                             rows []packet.Row, strategy adapters.ImportStrategy) error { ... }
Step 2: wire up the helpers
func NewAdapter(dsn string) (*Adapter, error) {
    db, err := sql.Open("sqlite", dsn)
    if err != nil {
        return nil, err
    }

    a := &Adapter{db: db}

    // The converter
    a.converter = base.NewUniversalTypeConverter()

    // The export helper — SQLite needs no SQL adapter
    a.exportHelper = base.NewExportHelper(a, a, a.converter, nil)

    // The import helper, with temporary tables
    a.importHelper = base.NewImportHelper(a, a, a, true)

    return a, nil
}
Step 3: delegate the Adapter interface methods
// Delegate the export
func (a *Adapter) ExportTable(ctx context.Context, tableName string) ([]*packet.DataPacket, error) {
    return a.exportHelper.ExportTable(ctx, tableName)
}

func (a *Adapter) ExportTableWithQuery(ctx context.Context, tableName string,
                                       query *packet.Query, sender, recipient string) ([]*packet.DataPacket, error) {
    return a.exportHelper.ExportTableWithQuery(ctx, tableName, query, sender, recipient)
}

// Delegate the import
func (a *Adapter) ImportPacket(ctx context.Context, pkt *packet.DataPacket,
                               strategy adapters.ImportStrategy) error {
    return a.importHelper.ImportPacket(ctx, pkt, strategy)
}

func (a *Adapter) ImportPackets(ctx context.Context, packets []*packet.DataPacket,
                                strategy adapters.ImportStrategy) error {
    return a.importHelper.ImportPackets(ctx, packets, strategy)
}

Examples per database

SQLite
a.exportHelper = base.NewExportHelper(a, a, a.converter, nil) // nil = no SQL adaptation needed
a.importHelper = base.NewImportHelper(a, a, a, true)          // true = use temp tables
PostgreSQL
sqlAdapter := base.NewStandardSQLAdapter("postgres", a.schema+".", "\"")
a.exportHelper = base.NewExportHelper(a, a, a.converter, sqlAdapter)
a.importHelper = base.NewImportHelper(a, a, a, true)
MS SQL Server
sqlAdapter := base.NewMSSQLAdapter(a.schema) // "dbo" by default
a.exportHelper = base.NewExportHelper(a, a, a.converter, sqlAdapter)
a.importHelper = base.NewImportHelper(a, a, a, true)
MySQL
sqlAdapter := base.NewStandardSQLAdapter("mysql", "", "`")
a.exportHelper = base.NewExportHelper(a, a, a.converter, sqlAdapter)
a.importHelper = base.NewImportHelper(a, a, a, true)

What it bought

Measure Before After Change
Lines in one adapter about 1000 about 300 -70%
Duplicated lines about 1700 0 -100%
Total across the adapters about 4500 lines about 2800 -38%
Time to add an adapter about 2 days about 4 hours -75%

Why it is worth it

  • No duplication — the shared logic lives in one place
  • Easier maintenance — one change reaches every adapter
  • Consistency — every adapter behaves the same way
  • A new adapter is quick — you write only what is specific to it
  • Testable — the helpers can be tested on their own
  • Compatible — works with ETL, streaming and compression

Compatibility

It works with:

  • pkg/core/packet — generating and parsing TDTP packets
  • pkg/core/schema — the type system
  • pkg/core/tdtql — the query language and its SQL pushdown
  • pkg/etl — ETL pipelines
  • every existing adapter

Testing

Write unit tests against the helpers:

func TestExportHelper_ExportTable(t *testing.T) {
    // Mock SchemaReader, DataReader
    // Test ExportTable()
}

func TestImportHelper_ImportPacket(t *testing.T) {
    // Mock TableManager, DataInserter
    // Test ImportPacket()
}

func TestUniversalTypeConverter_ConvertValueToTDTP(t *testing.T) {
    // Test conversion across the types
}

Migrating an existing adapter

See MIGRATION_EXAMPLE.md for the SQLite adapter's migration, worked through step by step.

Where to look next

If something here is unclear:


Version: 1.0 Created: 2025-12-25 Author: Claude Code, as part of the refactoring initiative

Documentation

Overview

Package base предоставляет общие хелперы и утилиты для всех адаптеров БД

Этот пакет устраняет дублирование кода между адаптерами (SQLite, PostgreSQL, MS SQL Server, MySQL) путем вынесения общей логики экспорта, импорта и конвертации типов в переиспользуемые компоненты.

Основные компоненты

ExportHelper - общая логика экспорта данных в TDTP пакеты:

  • ExportTable() - экспорт всей таблицы
  • ExportTableWithQuery() - экспорт с TDTQL фильтрацией и SQL оптимизацией
  • ExportTableIncremental() - инкрементальная синхронизация

ImportHelper - общая логика импорта TDTP пакетов в БД:

  • ImportPacket() - импорт одного пакета
  • ImportPackets() - импорт нескольких пакетов атомарно
  • Поддержка временных таблиц для атомарной замены

UniversalTypeConverter - универсальная конвертация типов данных:

  • ConvertValueToTDTP() - БД → TDTP формат
  • DBValueToString() - значение БД → строка (с учетом специфики СУБД)
  • TypedValueToSQL() - TDTP → SQL значение для PreparedStatement
  • Поддержка PostgreSQL-специфичных типов (UUID, JSONB, NUMERIC)
  • Поддержка MS SQL-специфичных типов (UNIQUEIDENTIFIER, TIMESTAMP/ROWVERSION)

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

Для создания адаптера необходимо:

1. Реализовать интерфейсы для ExportHelper:

  • SchemaReader (GetTableSchema)
  • DataReader (ReadAllRows, ReadRowsWithSQL, GetRowCount)
  • SQLAdapter (AdaptSQL) - опционально, для адаптации SQL под СУБД

2. Реализовать интерфейсы для ImportHelper:

  • TableManager (TableExists, CreateTable, DropTable, RenameTable)
  • DataInserter (InsertRows)
  • TransactionManager (BeginTx)

3. Создать хелперы в адаптере:

type Adapter struct {
    db           *sql.DB
    exportHelper *base.ExportHelper
    importHelper *base.ImportHelper
    converter    *base.UniversalTypeConverter
}

func (a *Adapter) initHelpers() {
    a.converter = base.NewUniversalTypeConverter()
    a.exportHelper = base.NewExportHelper(a, a, a.converter, nil)
    a.importHelper = base.NewImportHelper(a, a, a, true) // useTemporaryTables = true
}

4. Делегировать методы интерфейса Adapter хелперам:

func (a *Adapter) ExportTable(ctx context.Context, tableName string) ([]*packet.DataPacket, error) {
    return a.exportHelper.ExportTable(ctx, tableName)
}

func (a *Adapter) ImportPacket(ctx context.Context, pkt *packet.DataPacket, strategy adapters.ImportStrategy) error {
    return a.importHelper.ImportPacket(ctx, pkt, strategy)
}

Эффект от использования

Использование base пакета позволяет:

  • Сократить код адаптеров на ~60-70% (с ~1000 строк до ~300 строк)
  • Устранить дублирование логики экспорта (~800 строк дублированного кода)
  • Устранить дублирование логики импорта (~600 строк дублированного кода)
  • Устранить дублирование конвертации типов (~300 строк дублированного кода)
  • Упростить поддержку и добавление новых адаптеров
  • Обеспечить консистентность поведения между адаптерами

Совместимость

Пакет совместим с:

  • pkg/core/packet - генерация и парсинг TDTP пакетов
  • pkg/core/schema - система типов данных
  • pkg/core/tdtql - язык запросов и SQL оптимизация
  • pkg/etl - ETL конвейеры (использует packet.Generator)
  • Все существующие адаптеры (SQLite, PostgreSQL, MS SQL Server, MySQL)

Архитектурные принципы

- Dependency Inversion: хелперы зависят от интерфейсов, не от конкретных реализаций - Single Responsibility: каждый хелпер отвечает за свою область (export/import/conversion) - Open/Closed: легко расширяется новыми адаптерами без изменения базового кода - DRY (Don't Repeat Yourself): устранение дублирования кода

Index

Constants

View Source
const NullSentinel = "\x00"

NullSentinel — внутренний маркер DB NULL, сохраняющий информацию через pipeline конвертации. Заменяется на SpecialValues.Null.Marker (или "") в DetectAndApply перед записью в TDTP. Не попадает в финальный TDTP файл.

Variables

This section is empty.

Functions

func ConvertRowToSQLValues

func ConvertRowToSQLValues(
	rowValues []string,
	pkgSchema packet.Schema,
	converter *UniversalTypeConverter,
	dbType string,
) ([]any, error)

ConvertRowToSQLValues конвертирует строку TDTP в SQL значения для PreparedStatement Общая утилита для всех адаптеров

func FilterSchemaByFields

func FilterSchemaByFields(full packet.Schema, fields []string) (packet.Schema, []int, error)

FilterSchemaByFields возвращает схему только с запрошенными полями и их индексы в исходной полной схеме. Возвращает ошибку если поле не найдено. Экспортируется для использования в адаптерах (например, mssql).

func GenerateTempTableName

func GenerateTempTableName(baseName string) string

GenerateTempTableName генерирует имя временной таблицы Формат: {table_name}_tmp_{timestamp}

func ParseRowValues

func ParseRowValues(row packet.Row) []string

ParseRowValues парсит строку TDTP в массив значений. Общая утилита для всех адаптеров.

func ProjectRows

func ProjectRows(rows [][]string, indices []int) [][]string

ProjectRows возвращает только колонки по указанным индексам из каждой строки. Экспортируется для использования в адаптерах (например, mssql).

func ScanSQLRows

func ScanSQLRows(rows *sql.Rows, schema packet.Schema, converter *UniversalTypeConverter, dbType string) ([][]string, error)

ScanSQLRows scans database/sql rows into [][]string using the provided converter. dbType must match the converter's dbType parameter (e.g. "mssql", "sqlite", "mysql"). This eliminates the duplicated scanRows pattern across sql-based adapters.

Every column is scanned into `any`, including SQLite DATE/DATETIME/TIMESTAMP. There used to be a special case that bound those to *string, on the theory that it skipped modernc.parseTime. It did not: modernc keys its time parsing off the column's declared type — exactly the types the special case matched — so the driver still produced a time.Time and database/sql then formatted it back into the string with RFC3339Nano. The path cost an extra format and allocation per cell, left normalizeSQLiteDateTime dead (its input never had the space separator it looked for), carried the driver's own zone into a packet whose schema declares UTC, and — because a string cannot hold NULL — aborted the whole export with "converting NULL to string is unsupported" on the first NULL date.

func StreamSQLRows

func StreamSQLRows(ctx context.Context, rows *sql.Rows, sch packet.Schema,
	converter *UniversalTypeConverter, dbType string, out chan<- []string) error

StreamSQLRows перекладывает sql.Rows в канал, преобразуя ячейки тем же cellToTDTP, что и построчный ScanSQLRows.

Общий cellToTDTP здесь не для экономии строк: потоковый и обычный экспорт обязаны давать одинаковые байты, иначе выбор режима менял бы данные — и менял бы xxh3, который по ним считается.

Types

type ArenaBlock

type ArenaBlock struct {
	Names   []string
	Columns []*ColumnArena
	Rows    int
}

ArenaBlock — таблица, разложенная по аренам.

func ScanSQLArena

func ScanSQLArena(rows *sql.Rows, sch packet.Schema, converter *UniversalTypeConverter, dbType string, rowHint int) (*ArenaBlock, error)

ScanSQLArena читает sql.Rows в поколоночные арены.

Отличие от ScanSQLColumns в том, что здесь нет строки на ячейку. Колоночный сканер снял срез на строку, но оставил по string на значение — и именно они, а не срезы, составляли основную массу аллокаций. Здесь значение дописывается в буфер колонки прямо из того, что отдал драйвер.

Быстрый путь берётся не всегда; там, где он не берётся, вызывается обычный cellToTDTP и его результат копируется в буфер. Так что выигрыш зависит от типов колонок, а корректность — нет.

type ColumnArena

type ColumnArena struct {
	Buf     []byte
	Offsets []int32
}

ColumnArena — колонка целиком в одном буфере.

Buf хранит значения подряд, каждое завершается '\n'; Offsets[i] — начало значения i, длина Offsets равна числу строк плюс один. Границы задаются смещениями, а не разделителем, — '\n' лежит там для кодека, которому границы записей заметно помогают.

ВНИМАНИЕ: значение может содержать свой '\n'. Смещения от этого не страдают, а вот один Buf как поток — да. Экранирование это забота формата (её делает writeEscaped на записи), и колоночному формату придётся либо экранировать, либо передавать Offsets рядом с данными. Прототип границы не размечает.

func (*ColumnArena) At

func (c *ColumnArena) At(i int) []byte

At отдаёт значение без копирования — срез внутрь Buf, без завершающего '\n'.

func (*ColumnArena) Len

func (c *ColumnArena) Len() int

Len возвращает число значений.

func (*ColumnArena) String

func (c *ColumnArena) String(i int) string

String отдаёт значение строкой, аллоцируя. Для сверки и для потребителей, которым нужна строка; в самом колоночном пути звать незачем.

type ColumnBlock

type ColumnBlock struct {
	Names  []string
	Values [][]string
	Rows   int
}

ColumnBlock — таблица, разложенная по колонкам: Values[c][r] это ячейка строки r в колонке c. Names идёт параллельно Values и повторяет порядок schema.Fields.

ПРОТОТИП. Ничем в рабочем пути ещё не используется; смысл в том, чтобы померить, что даёт колоночная раскладка, не переписывая формат.

func ScanSQLColumns

func ScanSQLColumns(rows *sql.Rows, schema packet.Schema, converter *UniversalTypeConverter, dbType string, hint int) (*ColumnBlock, error)

ScanSQLColumns читает sql.Rows сразу по колонкам.

Отличие от ScanSQLRows не в порядке байт, а в том, что аллоцируется. Строчный сканер делает по []string на строку — сто тысяч строк это сто тысяч мелких срезов, каждый со своим заголовком и своей долей работы для сборщика мусора. Здесь срезов ровно столько, сколько колонок, и каждый выделен на всю высоту таблицы разом.

Ячейки проходят через тот же cellToTDTP, что и строчный путь, поэтому значения совпадают байт в байт — на это есть тест.

func (*ColumnBlock) ToRows

func (b *ColumnBlock) ToRows() [][]string

ToRows разворачивает блок обратно в строчную матрицу.

Нужен только для сверки с существующим путём и для тех потребителей, что ещё умеют лишь строки; в самом колоночном экспорте вызывать его незачем — он ровно та аллокация на строку, ради устранения которой всё и затевалось.

type DataInserter

type DataInserter interface {
	// InsertRows вставляет строки данных с использованием стратегии
	InsertRows(ctx context.Context, tableName string, schema packet.Schema, rows []packet.Row, strategy adapters.ImportStrategy) error
}

DataInserter предоставляет методы для вставки данных

type DataReader

type DataReader interface {
	// ReadAllRows читает все строки из таблицы
	ReadAllRows(ctx context.Context, tableName string, schema packet.Schema) ([][]string, error)

	// ReadRowsWithSQL выполняет SQL запрос и возвращает строки
	ReadRowsWithSQL(ctx context.Context, sql string, schema packet.Schema) ([][]string, error)

	// GetRowCount возвращает количество строк в таблице
	GetRowCount(ctx context.Context, tableName string) (int64, error)
}

DataReader предоставляет методы для чтения данных из таблицы

type ExportHelper

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

ExportHelper содержит общую логику экспорта для всех адаптеров Устраняет дублирование кода между SQLite, PostgreSQL, MS SQL Server, MySQL

func NewExportHelper

func NewExportHelper(
	schemaReader SchemaReader,
	dataReader DataReader,
	valueConverter ValueConverter,
	sqlAdapter SQLAdapter,
) *ExportHelper

NewExportHelper создает новый ExportHelper

func (*ExportHelper) ExportTable

func (h *ExportHelper) ExportTable(ctx context.Context, tableName string) ([]*packet.DataPacket, error)

ExportTable экспортирует всю таблицу в TDTP reference пакеты Общая реализация для всех адаптеров

func (*ExportHelper) ExportTableIncremental

func (h *ExportHelper) ExportTableIncremental(
	ctx context.Context,
	tableName string,
	incrementalConfig adapters.IncrementalConfig,
	buildIncrementalSQL func(tableName string, config adapters.IncrementalConfig) (string, []any),
	executeIncrementalQuery func(ctx context.Context, sql string, args []any, schema packet.Schema) ([][]string, string, error),
) ([]*packet.DataPacket, string, error)

ExportTableIncremental экспортирует только измененные записи с момента последней синхронизации Общая реализация для адаптеров поддерживающих инкрементальную синхронизацию

func (*ExportHelper) ExportTableWithQuery

func (h *ExportHelper) ExportTableWithQuery(
	ctx context.Context,
	tableName string,
	query *packet.Query,
	sender, recipient string,
) ([]*packet.DataPacket, error)

ExportTableWithQuery экспортирует таблицу с фильтрацией через TDTQL Общая реализация с SQL оптимизацией для всех адаптеров

func (*ExportHelper) SetColumnarLayout

func (h *ExportHelper) SetColumnarLayout(on bool)

SetColumnarLayout включает колоночную раскладку Data (--columnar).

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

func (*ExportHelper) SetMaxFallbackRows

func (h *ExportHelper) SetMaxFallbackRows(n int64)

SetMaxFallbackRows задаёт лимит строк для in-memory fallback при провале SQL pushdown. При 0 — без лимита (текущее поведение). При > 0 — если таблица больше лимита, возвращается ошибка вместо чтения всей таблицы в RAM. Защищает прод-БД от 17 GB сканов.

func (*ExportHelper) SetMaxMessageSize

func (h *ExportHelper) SetMaxMessageSize(size int)

SetMaxMessageSize задаёт максимальный размер одного TDTP пакета в байтах. Используется адаптерами для передачи настройки --packet-size из CLI.

func (*ExportHelper) SetSkipSpecialValues

func (h *ExportHelper) SetSkipSpecialValues(skip bool)

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

type FieldNameRenderer

type FieldNameRenderer interface {
	FieldNameRenderer(schema packet.Schema) func(name string) string
}

FieldNameRenderer is optional on an SQLAdapter: it renders field references for the pushdown query instead of the generator's ANSI default (simple names bare). Oracle needs it — a bare `name` is folded to NAME, so a column created as "name" (every table the adapter imports from another engine) failed with ORA-00904 and every filtered export fell back to reading the whole table into memory.

type ImportHelper

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

ImportHelper содержит общую логику импорта для всех адаптеров Устраняет дублирование кода между адаптерами

func NewImportHelper

func NewImportHelper(
	tableManager TableManager,
	dataInserter DataInserter,
	transactionManager TransactionManager,
	useTemporaryTables bool,
) *ImportHelper

NewImportHelper создает новый ImportHelper

func (*ImportHelper) ImportPacket

func (h *ImportHelper) ImportPacket(ctx context.Context, pkt *packet.DataPacket, strategy adapters.ImportStrategy) error

ImportPacket импортирует один TDTP пакет в БД StrategyCopy (и useTemporaryTables=true): атомарная замена через temp-таблицу. StrategyReplace/Ignore/Fail: прямой UPSERT в существующую таблицу. Общая реализация для всех адаптеров

func (*ImportHelper) ImportPackets

func (h *ImportHelper) ImportPackets(ctx context.Context, packets []*packet.DataPacket, strategy adapters.ImportStrategy) error

ImportPackets импортирует несколько пакетов атомарно (в одной транзакции) Общая реализация для всех адаптеров

type MSSQLAdapter

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

MSSQLAdapter реализует SQLAdapter для MS SQL Server Использует синтаксис OFFSET/FETCH вместо LIMIT

func NewMSSQLAdapter

func NewMSSQLAdapter(schemaName string) *MSSQLAdapter

NewMSSQLAdapter создает MSSQLAdapter

func (*MSSQLAdapter) AdaptSQL

func (a *MSSQLAdapter) AdaptSQL(standardSQL, tableName string, schema packet.Schema, query *packet.Query) string

AdaptSQL адаптирует стандартный SQL под MS SQL Server синтаксис Основные изменения: 1. LIMIT N → FETCH NEXT N ROWS ONLY 2. OFFSET N → OFFSET N ROWS 3. Добавляет ORDER BY если нужен для OFFSET/FETCH (обязательно с 2012+) 4. Квалифицирует имена таблиц: schema.[table] (поддержка "schema.table" формата) 5. Квалифицирует имена полей: [field]

func (*MSSQLAdapter) QuoteIdentifier

func (a *MSSQLAdapter) QuoteIdentifier(identifier string) string

QuoteIdentifier квотирует идентификатор для SQL Server

type ParseFailure

type ParseFailure struct {
	Field    string
	Type     string
	Count    int
	Sample   string // the first offending value, cut to sampleLen runes
	Err      string // the first error
	Replaced bool   // the value was substituted (JSON → "{}"/"[]"), not passed through
}

ParseFailure counts the values of one field that the converter could not handle as the field's declared type, and so wrote to the packet as they came (or, for an unserialisable JSON value, replaced).

This used to be one log line per cell and nothing else: an export with a wrong (p,s) on a DECIMAL column, or a date column holding text, finished successfully, and the only trace was a log nobody read. That is how the MySQL export declaring every DECIMAL as (18,2) went unnoticed — the values still matched on a round trip, because they were passed through raw.

func (ParseFailure) String

func (f ParseFailure) String() string

String renders the failure as one warning line, the same for every caller.

type ParseFailureReporter

type ParseFailureReporter interface {
	// ParseFailures returns the failures recorded since the previous call,
	// in the order the fields first failed, and clears the tally.
	ParseFailures() []ParseFailure
}

ParseFailureReporter is implemented by adapters whose converter keeps a ParseFailure tally. A caller exporting through an adapter asks it once the export is done, and reports what it gets.

type PostgreSQLSchemaAdapter

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

PostgreSQLSchemaAdapter реализует SQLAdapter для PostgreSQL с non-public схемой. Квалифицирует имя таблицы: "schema"."table". Используется когда schema != "public" (т.к. SQLGenerator генерирует только имя таблицы).

func NewPostgreSQLSchemaAdapter

func NewPostgreSQLSchemaAdapter(schema string) *PostgreSQLSchemaAdapter

NewPostgreSQLSchemaAdapter создает PostgreSQLSchemaAdapter для указанной схемы.

func (*PostgreSQLSchemaAdapter) AdaptSQL

func (a *PostgreSQLSchemaAdapter) AdaptSQL(standardSQL, tableName string, schema packet.Schema, query *packet.Query) string

AdaptSQL квалифицирует имя таблицы в FROM clause добавляя schema prefix с quoted identifiers.

type RowPostProcessor

type RowPostProcessor interface {
	PostProcessRows(ctx context.Context, schema packet.Schema, rows [][]string) (packet.Schema, [][]string)
}

RowPostProcessor — опциональный интерфейс для постобработки строк после чтения. Адаптеры реализуют его когда нужна специфичная фильтрация столбцов (например, MSSQL фильтрует read-only поля: identity, computed, timestamp). ExportHelper проверяет DataReader на реализацию этого интерфейса и вызывает PostProcessRows перед сборкой пакетов.

type SQLAdapter

type SQLAdapter interface {
	// AdaptSQL адаптирует стандартный SQL под синтаксис СУБД
	AdaptSQL(standardSQL string, tableName string, schema packet.Schema, query *packet.Query) string
}

SQLAdapter предоставляет методы для адаптации SQL под конкретную СУБД

type SchemaReader

type SchemaReader interface {
	GetTableSchema(ctx context.Context, tableName string) (packet.Schema, error)
}

SchemaReader предоставляет методы для чтения схемы таблицы

type StandardSQLAdapter

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

StandardSQLAdapter реализует SQLAdapter для стандартного SQL (SQLite, PostgreSQL, MySQL) Использует синтаксис LIMIT/OFFSET

func NewStandardSQLAdapter

func NewStandardSQLAdapter(dbType, schemaPrefix, quoteChar string) *StandardSQLAdapter

NewStandardSQLAdapter создает StandardSQLAdapter

func (*StandardSQLAdapter) AdaptSQL

func (a *StandardSQLAdapter) AdaptSQL(standardSQL, tableName string, schema packet.Schema, query *packet.Query) string

AdaptSQL адаптирует стандартный SQL (не требуется для SQLite/PostgreSQL/MySQL с LIMIT/OFFSET)

type StreamingDataReader

type StreamingDataReader interface {
	ReadAllRowsStream(ctx context.Context, tableName string, schema packet.Schema) (packet.Schema, <-chan []string, <-chan error, error)
}

StreamingDataReader — необязательное расширение DataReader: отдаёт строки потоком, не собирая их в память.

Необязательное намеренно. DataReader реализуют пять адаптеров плюс моки в тестах; добавление метода в сам интерфейс сломало бы всех разом ради того, что пока умеет один. Вызывающий проверяет приведением типа и откатывается на ReadAllRows, если потока нет.

Канал строк закрывается по исчерпании данных. Ошибки приходят в отдельный канал: читать его нужно ПОСЛЕ того, как канал строк закрыт, иначе ошибку, случившуюся в середине чтения, легко принять за нормальный конец.

Возвращает схему, которую РЕАЛЬНО будет отдавать, а не ту, что получил. Они расходятся: MSSQL выкидывает read-only колонки (identity, computed, rowversion), и обычный путь делает это хуком PostProcessRows уже после чтения. Потоку постфактум фильтровать нечего — строки уже ушли потребителю, — поэтому схема согласовывается заранее, и вызывающий обязан строить пакеты по возвращённой, а не по переданной.

type TableManager

type TableManager interface {
	// TableExists проверяет существование таблицы
	TableExists(ctx context.Context, tableName string) (bool, error)

	// CreateTable создает таблицу по TDTP схеме
	CreateTable(ctx context.Context, tableName string, schema packet.Schema) error

	// DropTable удаляет таблицу
	DropTable(ctx context.Context, tableName string) error

	// RenameTable переименовывает таблицу
	RenameTable(ctx context.Context, oldName, newName string) error
}

TableManager предоставляет методы для управления таблицами

type TransactionManager

type TransactionManager interface {
	// BeginTx начинает транзакцию
	BeginTx(ctx context.Context) (adapters.Tx, error)
}

TransactionManager предоставляет методы для работы с транзакциями

type UniversalTypeConverter

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

UniversalTypeConverter - универсальный конвертер типов для всех адаптеров Устраняет дублирование кода конвертации между адаптерами

func NewUniversalTypeConverter

func NewUniversalTypeConverter() *UniversalTypeConverter

NewUniversalTypeConverter создает новый UniversalTypeConverter

func (*UniversalTypeConverter) ConvertValueToTDTP

func (c *UniversalTypeConverter) ConvertValueToTDTP(field packet.Field, value string) string

ConvertValueToTDTP конвертирует значение из БД в TDTP формат Общая реализация (вместо 4 копий в адаптерах)

func (*UniversalTypeConverter) DBValueToString

func (c *UniversalTypeConverter) DBValueToString(value any, field packet.Field, dbType string) string

DBValueToString конвертирует значение БД в строку для последующей обработки Общий метод с поддержкой специфичных типов для разных СУБД

func (*UniversalTypeConverter) SetNoDateSentinels

func (c *UniversalTypeConverter) SetNoDateSentinels(dates []string)

SetNoDateSentinels configures date strings that should be treated as "no date" / zero-date. Used for MSSQL conventions like "1900-01-01" or "1753-01-01". At export time: if the date matches a sentinel → encoded as SpecNoDateMarker ("0000-00-00").

func (*UniversalTypeConverter) TakeParseFailures

func (c *UniversalTypeConverter) TakeParseFailures() []ParseFailure

TakeParseFailures returns and clears the failures recorded by ConvertValueToTDTP and the JSON branches of DBValueToString.

func (*UniversalTypeConverter) TypedValueToSQL

func (c *UniversalTypeConverter) TypedValueToSQL(tv schema.TypedValue, dbType string) any

TypedValueToSQL конвертирует TypedValue в значение для SQL Общая реализация для PreparedStatement parameters

type ValueConverter

type ValueConverter interface {
	// ConvertValueToTDTP конвертирует значение из БД в TDTP формат
	ConvertValueToTDTP(field packet.Field, value string) string
}

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

type ValueRenderer

type ValueRenderer interface {
	ValueRenderer(schema packet.Schema) func(field, value string) (string, bool)
}

ValueRenderer is optional on an SQLAdapter: it renders comparison values with knowledge of the column type (tdtql.SQLGenerator.Value). Oracle uses it for ANSI DATE/TIMESTAMP literals, which do not depend on the session's NLS settings the way a plain '1995-01-01' does.

Jump to

Keyboard shortcuts

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