Documentation
¶
Overview ¶
Package storage — переносимое реляционное ядро на database/sql с двумя диалектами: sqlite (modernc.org/sqlite) и postgres (pgx/v5 stdlib).
Бизнес-правил не содержит: только переносимый DML, диалектные различия (DDL, открытие, пулы) и версия схемы (docs/plan/01-architecture.md §2.3).
Index ¶
- Constants
- func MaskDSN(dsn string) string
- func TableNames() []string
- type AnalogRow
- type AttrCond
- type AttrRow
- type BacklinkRow
- type Cond
- type Config
- type DB
- func (d *DB) Begin(ctx context.Context) (*Tx, error)
- func (d *DB) CatalogRevision(ctx context.Context) (int64, error)
- func (d *DB) CheckSchema(ctx context.Context) error
- func (d *DB) Close() error
- func (d *DB) CountDevices(ctx context.Context, kind domain.Kind) (int, error)
- func (d *DB) CountDevicesByKind(ctx context.Context) (map[string]int, error)
- func (d *DB) CountKinds(ctx context.Context) (int, error)
- func (d *DB) CountManufacturers(ctx context.Context) (int, error)
- func (d *DB) DialectName() string
- func (d *DB) EnsureCreated(ctx context.Context) (bool, error)
- func (d *DB) FindDevice(ctx context.Context, kind domain.Kind, designation string) (*DeviceRow, error)
- func (d *DB) FindDeviceAnyKind(ctx context.Context, designation string) (*DeviceRow, error)
- func (d *DB) FindDevicesByIDs(ctx context.Context, ids []int64) ([]*DeviceRow, error)
- func (d *DB) ListDeviceIDsByKind(ctx context.Context, kind domain.Kind) ([]int64, error)
- func (d *DB) LoadAttributesByIDs(ctx context.Context, ids []int64) (map[int64][]AttrRow, error)
- func (d *DB) LoadBacklinksByIDs(ctx context.Context, ids []int64) (map[int64][]BacklinkRow, error)
- func (d *DB) LoadFields(ctx context.Context, deviceID int64) ([]domain.Field, error)
- func (d *DB) LoadFieldsByIDs(ctx context.Context, ids []int64) (map[int64][]domain.Field, error)
- func (d *DB) LoadManufacturers(ctx context.Context, deviceID int64) ([]string, error)
- func (d *DB) LoadManufacturersByIDs(ctx context.Context, ids []int64) (map[int64][]string, error)
- func (d *DB) LoadOutgoingAnalogsByIDs(ctx context.Context, ids []int64) (map[int64][]AnalogRow, error)
- func (d *DB) LoadSnapshot(ctx context.Context) (*catalog.Snapshot, error)
- func (d *DB) LoadValuesByIDs(ctx context.Context, ids []int64) (map[int64][]ValueRow, error)
- func (d *DB) LoadVariantsByIDs(ctx context.Context, ids []int64) (map[int64][]VariantRow, error)
- func (d *DB) Revisions(ctx context.Context) (catalog, data int64, err error)
- func (d *DB) Search(ctx context.Context, r SearchRequest) ([]SearchItemRow, int, error)
- func (d *DB) SuggestPrefix(ctx context.Context, prefix string, kind string, limit int) ([]SearchItemRow, error)
- type DeviceRow
- type Dialect
- type FieldCond
- type FilterOp
- type ParamCond
- type ParamValue
- type PoolConfig
- type SearchItemRow
- type SearchRequest
- type SortKey
- type Tx
- func (t *Tx) BumpCatalogRevision(ctx context.Context) error
- func (t *Tx) BumpDataRevision(ctx context.Context) error
- func (t *Tx) Commit() error
- func (t *Tx) DeleteDesignationFields(ctx context.Context, deviceID int64, names []string) error
- func (t *Tx) DeleteDevice(ctx context.Context, deviceID int64) error
- func (t *Tx) DeleteDeviceAnyKindByKey(ctx context.Context, designation string) (int64, error)
- func (t *Tx) DeleteDeviceByKey(ctx context.Context, kind domain.Kind, designation string) (int64, error)
- func (t *Tx) DeleteTypeGroupValues(ctx context.Context, deviceID int64, groupCode string) error
- func (t *Tx) DeleteVariants(ctx context.Context, deviceID int64) error
- func (t *Tx) FindDevice(ctx context.Context, kind domain.Kind, designation string) (*DeviceRow, error)
- func (t *Tx) InsertDesignationFields(ctx context.Context, deviceID int64, fields []domain.Field) error
- func (t *Tx) InsertDevice(ctx context.Context, kind domain.Kind, system domain.System, ...) (int64, bool, error)
- func (t *Tx) InsertIfAbsent(ctx context.Context, query string, args map[string]any) (bool, error)
- func (t *Tx) InsertIfAbsentReturningID(ctx context.Context, query string, args map[string]any) (int64, bool, error)
- func (t *Tx) InsertReturningID(ctx context.Context, query string, args map[string]any) (int64, error)
- func (t *Tx) InsertValues(ctx context.Context, deviceID int64, variantID *int64, values []ParamValue) error
- func (t *Tx) InsertVariant(ctx context.Context, deviceID int64, label string, sortOrder int) (int64, error)
- func (t *Tx) LoadAttributes(ctx context.Context, deviceID int64) ([]AttrRow, error)
- func (t *Tx) LoadFields(ctx context.Context, deviceID int64) ([]domain.Field, error)
- func (t *Tx) LoadManufacturers(ctx context.Context, deviceID int64) ([]string, error)
- func (t *Tx) LoadOutgoingAnalogs(ctx context.Context, deviceID int64) ([]AnalogRow, error)
- func (t *Tx) LoadValues(ctx context.Context, deviceID int64) ([]ValueRow, error)
- func (t *Tx) LoadVariants(ctx context.Context, deviceID int64) ([]VariantRow, error)
- func (t *Tx) ReplaceAnalogs(ctx context.Context, deviceID int64, targets []AnalogRow) error
- func (t *Tx) ReplaceAttributes(ctx context.Context, deviceID int64, rows []AttrRow) error
- func (t *Tx) ReplaceManufacturers(ctx context.Context, deviceID int64, names []string) error
- func (t *Tx) Rollback() error
- func (t *Tx) WriteCatalog(ctx context.Context, in catalog.Input) error
- type ValueRow
- type VariantRow
Constants ¶
const SchemaVersion = 5
SchemaVersion — версия схемы модуля (docs/plan/02-database.md §5): любое изменение DDL ниже — с инкрементом этой константы; несовпадение с schema_meta.schema_version — отказ в работе (schema_version_mismatch), миграций нет (D5). Версия 2 — каталожные таблицы без текстовых колонок (D9: отображаемые строки — бандлы internal/i18n; коды единиц — латиница). Версия 3 — словарь материалов полупроводников (коды вместо русских названий в device_designation_fields.text_value). Версия 4 — словарь подклассов и способов подстройки (коды полей subclass/adjustment), классификационные поля секции fields и каталожный словарь категорий. Версия 5 — postgres-диалект переписывает INTEGER в BIGINT и REAL в DOUBLE PRECISION (диапазон и точность sqlite: int64 и float64; текст переносимого DDL не менялся).
Variables ¶
This section is empty.
Functions ¶
func MaskDSN ¶
MaskDSN скрывает секрет в DSN для безопасного отображения (вывод CLI, отчёты QA, сообщения тестов): в URL-форме маскируется пароль userinfo, в ключевой форме — значения всех ключей password (конфликт ключей у pgx решается поздним значением — маскируются все). DSN без опознаваемого пароля возвращается без изменений; не разбираемая URL-форма маскируется целиком — безопаснее потерять наглядность, чем показать секрет.
func TableNames ¶
func TableNames() []string
TableNames — имена каталожных таблиц модуля, выведенные из DDL (ddlStatements): единый источник для очистки одноразовых баз — qa/runner использует напрямую, internal/testutil — копией, синхронизированной пин-тестом (импорт storage в testutil запрещён: тесты storage импортируют testutil). DROP — с CASCADE, порядок не важен.
Types ¶
type AnalogRow ¶
AnalogRow — исходящая ссылка-аналог: цель с обозначением и системой (направленность — D8: строка принадлежит владельцу device_id).
type BacklinkRow ¶
type BacklinkRow struct {
SourceID int64
Kind domain.Kind
Designation string
System domain.System
Note string
}
BacklinkRow — встречная ссылка: запись, указавшая устройство аналогом.
type Config ¶
type Config struct {
Dialect string // "sqlite" | "postgres"
DSN string // путь файла sqlite / DSN postgres
Pool PoolConfig
}
Config — конфигурация открытия хранилища (docs/plan/01-architecture.md §2.3): диалект, DSN/путь и параметры пулов соединений.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB — открытое хранилище. Правило (docs/plan/01-architecture.md §2.3): весь DML — только внутри явных транзакций Begin (соединение записи); вне транзакций — только SELECT из пула чтения. Бизнес-правил не содержит.
func Open ¶
Open открывает хранилище по конфигурации; схему не проверяет и не создаёт (это делают CheckSchema/EnsureCreated).
func (*DB) CatalogRevision ¶
CatalogRevision возвращает catalog_revision — проверка кэша каталога на каждом обращении к сервисам (единственный SELECT schema_meta).
func (*DB) CheckSchema ¶
CheckSchema читает schema_meta.schema_version и сверяет с версией модуля (docs/plan/02-database.md §5.2): отсутствие таблицы — database_not_initialized (не инициализирована, чужая или повреждённая база), несовпадение версии — schema_version_mismatch; продолжение работы запрещено.
func (*DB) CountDevices ¶
CountDevices возвращает число записей (класса, если задан).
func (*DB) CountDevicesByKind ¶
CountDevicesByKind — числа записей по классам одним GROUP BY-запросом (агрегат /stats); классы без записей в результате отсутствуют.
func (*DB) CountKinds ¶
CountKinds возвращает число строк kinds (признак пустого каталога для сидирования при EnsureCreated).
func (*DB) CountManufacturers ¶
CountManufacturers возвращает число строк manufacturers (контроль чистки сирот в тестах и диагностике).
func (*DB) DialectName ¶
DialectName возвращает код диалекта хранилища.
func (*DB) EnsureCreated ¶
EnsureCreated выполняет полный DDL (CREATE … IF NOT EXISTS) и записывает schema_version, catalog_revision и data_revision, если их ещё нет (docs/plan/02-database.md §5). Гонка-безопасна: вставка меты подавляет конфликт уникальности атомарно (ON CONFLICT DO NOTHING) — конкурентное создание той же меты ошибки не даёт. Возвращает true, если база создана этим вызовом (мета была вставлена).
func (*DB) FindDevice ¶
func (*DB) FindDeviceAnyKind ¶
FindDeviceAnyKind ищет запись по обозначению без фильтра классом (разрешение аналогов, find).
func (*DB) FindDevicesByIDs ¶
FindDevicesByIDs — записи по списку стабильных id (параллельно удалённые в результате отсутствуют; порядок строк — по id).
func (*DB) ListDeviceIDsByKind ¶
ListDeviceIDsByKind перечисляет id записей класса в порядке (designation) — единый снимок одного запроса: последовательный обход страниц по OFFSET на живой базе даёт дубли и пропуски (экспорт).
func (*DB) LoadAttributesByIDs ¶
LoadAttributesByIDs — значения атрибутов по списку записей.
func (*DB) LoadBacklinksByIDs ¶
LoadBacklinksByIDs — встречные ссылки («кто указал запись аналогом») по списку записей, индекс по analog_device_id (docs/plan/02-database.md §3).
func (*DB) LoadFields ¶
LoadFields — поля разбора обозначения (порядок алфавитный — стабильно для карточки и сравнения состояний).
func (*DB) LoadFieldsByIDs ¶
LoadFieldsByIDs — поля разбора обозначений по списку записей.
func (*DB) LoadManufacturers ¶
func (*DB) LoadManufacturersByIDs ¶
LoadManufacturersByIDs — производители по списку записей.
func (*DB) LoadOutgoingAnalogsByIDs ¶
func (d *DB) LoadOutgoingAnalogsByIDs(ctx context.Context, ids []int64) (map[int64][]AnalogRow, error)
LoadOutgoingAnalogsByIDs — исходящие ссылки-аналоги по списку записей.
func (*DB) LoadSnapshot ¶
LoadSnapshot загружает полный снимок каталога одной транзакцией чтения из пула чтения (транспорт каталога — docs/plan/01-architecture.md §2.2; реализация catalog.Store). Все SELECT и чтение catalog_revision идут из одного снапшота СУБД: конкурентный импорт каталога не может смешать в снимке старые и новые определения, а Revision соответствует загруженным строкам. Уровень REPEATABLE READ переносим: pgx задаёт его явно, sqlite-драйвер игнорирует — WAL-транзакция чтения и так держит стабильный снапшот на всё время транзакции. Порядок строк детерминирован (ORDER BY), чтобы снимки одной базы были сравнимы.
func (*DB) LoadValuesByIDs ¶
LoadValuesByIDs — все значения параметров (тип в целом и исполнения) с условиями по списку записей; порядок строк внутри записи — как у LoadValues.
func (*DB) LoadVariantsByIDs ¶
LoadVariantsByIDs — исполнения по списку записей.
func (*DB) Revisions ¶
Revisions возвращает текущие счётчики catalog_revision и data_revision (для ETag REST — docs/plan/02-database.md §5.5).
func (*DB) Search ¶
func (d *DB) Search(ctx context.Context, r SearchRequest) ([]SearchItemRow, int, error)
Search выполняет поиск записей: WHERE — общие условия, ORDER BY — сортировка с финальным (kind, designation), LIMIT/OFFSET — пагинация. Возвращает страницу и общее число записей под фильтрами.
func (*DB) SuggestPrefix ¶
func (d *DB) SuggestPrefix(ctx context.Context, prefix string, kind string, limit int) ([]SearchItemRow, error)
SuggestPrefix — автодополнение по префиксу обозначения (каноническому); kind — необязательный фильтр классом.
type Dialect ¶
type Dialect interface {
// Name возвращает код диалекта ("sqlite" | "postgres").
Name() string
// Open открывает пулы соединений по конфигурации.
Open(ctx context.Context, cfg Config) (*pools, error)
// RewriteDDL подставляет в переносимый оператор DDL определения
// диалекта: авто-PK колонок id (sqlite: INTEGER PRIMARY KEY
// AUTOINCREMENT; postgres: IDENTITY) и точный целочисленный тип
// (postgres: INTEGER → BIGINT — паритет диапазона int64 со sqlite).
RewriteDDL(stmt string) string
// InsertReturningID выполняет INSERT внутри транзакции и возвращает
// сгенерированный id (sqlite: last_insert_rowid() того же соединения;
// postgres: INSERT … RETURNING id).
InsertReturningID(ctx context.Context, tx *sql.Tx, query string, args []any) (int64, error)
// InsertIfAbsentReturningID выполняет INSERT внутри транзакции,
// атомарно подавляя конфликт уникальности (ON CONFLICT DO NOTHING),
// и возвращает сгенерированный id. inserted=false — строка уже есть:
// либо существовала, либо вставлена конкурирующей транзакцией
// (postgres: писатели параллельны — общий пул соединений); id в этом
// случае возвращает SELECT вызывающего кода. Ошибки уникальности
// не возникает — транзакция остаётся рабочей в обоих диалектах.
InsertIfAbsentReturningID(ctx context.Context, tx *sql.Tx, query string, args []any) (id int64, inserted bool, err error)
}
Dialect — диалектозависимая часть хранилища (docs/plan/02-database.md §4): открытие и пулы, определение авто-PK в DDL, получение id после вставки. Всё прочее — переносимый DML на database/sql: текст с @имя-плейсхолдерами переписывается в позиционные $n (positional).
type FieldCond ¶
FieldCond — фильтр по полю разбора обозначения: текстовое значение (Text) либо числовое со сравнением (Num + Op).
type ParamCond ¶
type ParamCond struct {
Code string
Min, Max, Exact float64
HasMin, HasMax bool
HasExact, HasText bool
Text string
}
ParamCond — фильтр по параметру: нижняя/верхняя граница, точное значение либо текст/enum. Вариантные параметры применяются в пределах одного исполнения (EXISTS по device_variants) — перекрёстные сочетания значений разных исполнений не сопоставляются (docs/plan/04-module-functionality.md §1).
type ParamValue ¶
type ParamValue struct {
Parameter string
Exact *float64
Min *float64
Max *float64
Text *string
Conditions []Cond
}
ParamValue — значение параметра с условиями (строка parameter_values вместе с parameter_value_conditions).
type PoolConfig ¶
type PoolConfig struct {
MaxOpen int
MaxIdle int
ConnMaxLifetime int64 // наносекунды; 0 — без ограничения
BusyTimeout int64 // миллисекунды (sqlite); 0 — 5000
}
PoolConfig — параметры пулов соединений. Для postgres — единый пул (MaxOpen/MaxIdle/ConnMaxLifetime); для sqlite — размер пула чтения (запись всегда идёт через одно выделенное соединение) и busy_timeout.
type SearchItemRow ¶
type SearchItemRow struct {
ID int64
Kind string
System string
Designation string
Fields []anyField
}
SearchItemRow — строка результата поиска.
type SearchRequest ¶
type SearchRequest struct {
Kind string
System string
Query string // подстрока обозначения (каноническая, без масок)
Fields []FieldCond
Attrs []AttrCond
Params []ParamCond
Sorts []SortKey
Limit int
Offset int
}
SearchRequest — параметры поиска без бизнес-правил: готовые условия (валидация применимости — сервисный слой).
type SortKey ¶
SortKey — ключ сортировки: "designation", "id" либо код поля разбора (Numeric — числовая сортировка поля, иначе текстовая).
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx — транзакция записи: Commit фиксирует, откат — Rollback (присутствует в defer вызывающего). Все DML-методы хранилища принимают *Tx.
func (*Tx) BumpCatalogRevision ¶
BumpCatalogRevision инкрементирует catalog_revision (транзакция меняет каталог).
func (*Tx) BumpDataRevision ¶
BumpDataRevision инкрементирует data_revision (транзакция меняет устройства).
func (*Tx) DeleteDesignationFields ¶
DeleteDesignationFields удаляет именованные поля разбора записи (замена набора явных классификационных полей секцией fields; поля грамматики парсера не затрагиваются).
func (*Tx) DeleteDevice ¶
DeleteDevice удаляет запись (каскад FK по всем дочерним таблицам, включая обе стороны device_analogs) и чистит сирот производителей.
func (*Tx) DeleteDeviceAnyKindByKey ¶
DeleteDeviceAnyKindByKey удаляет запись по обозначению без фильтра классом — детерминированно первая по kind_code, как FindDeviceAnyKind (подзапрос переносим: sqlite и postgres).
func (*Tx) DeleteDeviceByKey ¶
func (t *Tx) DeleteDeviceByKey(ctx context.Context, kind domain.Kind, designation string) (int64, error)
DeleteDeviceByKey удаляет запись по ключу (kind_code, designation) в текущей транзакции и чистит сирот производителей; возвращает число удалённых строк (0 — записи нет). Поиск и удаление атомарны в одном операторе: конкурентное удаление того же ключа даёт ноль строк — без «пустого» успеха и лишнего инкремента ревизии (TOCTOU отдельного поиска и удаления).
func (*Tx) DeleteTypeGroupValues ¶
DeleteTypeGroupValues удаляет данные типа в целом (variant_id NULL) по группе каталога — замена секции группы.
func (*Tx) DeleteVariants ¶
DeleteVariants удаляет исполнения записи вместе с их значениями (каскад FK parameter_values.variant_id).
func (*Tx) FindDevice ¶
func (*Tx) InsertDesignationFields ¶
func (t *Tx) InsertDesignationFields(ctx context.Context, deviceID int64, fields []domain.Field) error
InsertDesignationFields записывает поля разбора обозначения; поля, уже существующие у записи (параллельный писатель), не затрагиваются — конфликт уникальности (device_id, field) подавляется атомарно. Вставка многострочная: число операторов не зависит от числа полей.
func (*Tx) InsertDevice ¶
func (t *Tx) InsertDevice(ctx context.Context, kind domain.Kind, system domain.System, designation string) (int64, bool, error)
InsertDevice вставляет запись, если её нет, и возвращает id вместе с признаком вставки. Вставка атомарно подавляет конфликт уникальности (kind_code, designation): параллельный писатель (postgres — общий пул соединений; sqlite-писатель один) успел вставить то же обозначение — вставки не происходит, id перечитывается по ключу.
func (*Tx) InsertIfAbsent ¶
InsertIfAbsent — вставка с атомарным подавлением конфликта уникальности для таблиц без суррогатного id (составной PK: связи производителей, атрибуты, аналоги): inserted=false — строка уже есть (вставлена конкурирующей транзакцией). ON CONFLICT DO NOTHING и RowsAffected переносимы, RETURNING не используется.
func (*Tx) InsertIfAbsentReturningID ¶
func (t *Tx) InsertIfAbsentReturningID(ctx context.Context, query string, args map[string]any) (int64, bool, error)
InsertIfAbsentReturningID — вставка с атомарным подавлением конфликта уникальности и возвратом сгенерированного id (метод диалекта — docs/plan/02-database.md §4); для таблиц с суррогатным id.
func (*Tx) InsertReturningID ¶
func (t *Tx) InsertReturningID(ctx context.Context, query string, args map[string]any) (int64, error)
InsertReturningID — вставка с возвратом сгенерированного id (метод диалекта; сигнатура зафиксирована планом — docs/plan/02-database.md §4).
func (*Tx) InsertValues ¶
func (t *Tx) InsertValues(ctx context.Context, deviceID int64, variantID *int64, values []ParamValue) error
InsertValues вставляет значения параметров в контексте типа (variantID = nil) либо исполнения; порядок среза — sort_order. Значения без условий идут многострочной вставкой (id строк не нужен), значения с условиями — построчно (id нужен для условий), условия одного значения — одной многострочной вставкой.
func (*Tx) InsertVariant ¶
func (t *Tx) InsertVariant(ctx context.Context, deviceID int64, label string, sortOrder int) (int64, error)
InsertVariant вставляет исполнение и возвращает его id.
func (*Tx) LoadAttributes ¶
func (*Tx) LoadFields ¶
func (*Tx) LoadManufacturers ¶
func (*Tx) LoadOutgoingAnalogs ¶
func (*Tx) LoadValues ¶
loadValues — все значения параметров устройства (тип в целом и исполнения) с условиями; порядок детерминирован: сначала данные типа, затем значения вариантов по (variant_id, sort_order, id); условия — по коду.
func (*Tx) LoadVariants ¶
func (*Tx) ReplaceAnalogs ¶
ReplaceAnalogs заменяет исходящие ссылки-аналоги целиком (встречные ссылки других записей не затрагиваются — D8); ссылки, вставленные параллельным писателем в той же записи и не видимые DELETE этого вызова, сохраняются.
func (*Tx) ReplaceAttributes ¶
ReplaceAttributes заменяет значения атрибутов целиком (секция attributes); значения, вставленные параллельным писателем в той же записи и не видимые DELETE этого вызова, сохраняются.
func (*Tx) ReplaceManufacturers ¶
ReplaceManufacturers заменяет список производителей целиком: связи пересоздаются, недостающие строки manufacturers создаются, производители без единой связи удаляются (чистка сирот). Создание строки производителя атомарно подавляет конфликт уникальности имени: параллельный писатель успел вставить то же имя — id перечитывается по имени.
func (*Tx) WriteCatalog ¶
WriteCatalog применяет строки каталога upsert'ом по коду внутри транзакции записи (docs/plan/02-database.md §5.4): вставка новой строки либо обновление полей существующей; enum-значения, наборы условий и применимость к классам замещаются целиком; удаления строк каталога нет — только деактивация (is_active = 0) самим входом. Валидацию метасхемы выполняет сервисный слой до записи; порядок таблиц учитывает FK.
type ValueRow ¶
type ValueRow struct {
VariantID *int64
Value ParamValue
}
ValueRow — значение в контексте устройства: данные типа (VariantID = nil) либо исполнения (VariantID = id варианта).
type VariantRow ¶
VariantRow — строка device_variants.