Работа с tuples¶
Структура данных tuple и особенности работы с Octopus.
Что такое tuple¶
Tuple — это упорядоченный массив полей в Octopus/Tarantool:
Порядок полей¶
Критично
Порядок полей в декларации должен точно соответствовать порядку в tuple!
// Tuple в БД: [id, email, name, status]
type FieldsUser struct {
Id int64 `ar:"primary_key"` // Поле 0
Email string `ar:"size:256"` // Поле 1
Name string `ar:"size:256"` // Поле 2
Status string `ar:"size:32"` // Поле 3
}
Несоответствие порядка приведёт к неправильной интерпретации данных!
Порядок индексов¶
Порядок индексов также критичен:
type IndexesUser struct {
// Должен соответствовать конфигурации Octopus
Id bool `ar:"fields:Id;primary_key"` // Индекс 0
Email bool `ar:"fields:Email;unique"` // Индекс 1
Status bool `ar:"fields:Status"` // Индекс 2
}
Дополнительные поля (extraFields)¶
Когда появляются¶
Если tuple содержит больше полей, чем объявлено:
// Объявлено 3 поля
type FieldsUser struct {
Id int64 `ar:"primary_key"`
Email string `ar:"size:256"`
Name string `ar:"size:256"`
}
// Tuple в БД: [id, email, name, legacy_field, another_field]
// legacy_field и another_field попадут в extraFields
Доступ к extraFields¶
user, _ := user.SelectById(ctx, 123)
// extraFields содержит "лишние" поля
for i, field := range user.ExtraFields {
fmt.Printf("Extra field %d: %v\n", i, field)
}
Сохранение при Update¶
При Update() extraFields сохраняются:
При Replace() extraFields теряются:
RepairTuple триггер¶
Назначение¶
Вызывается при проблемах с десериализацией:
- Неверное число полей
- Неверный формат поля
- Повреждённые данные
Объявление¶
type TriggersUser struct {
RepairTuple bool `ar:"pkg:github.com/myapp/repair;func:RepairUserTuple"`
}
Реализация¶
package repair
import "github.com/Educentr/go-activerecord/v3/pkg/octopus"
func RepairUserTuple(tuple *octopus.TupleData) error {
expectedFields := 4
// Добавляем недостающие поля
for len(tuple.Fields) < expectedFields {
tuple.Fields = append(tuple.Fields, []byte(""))
}
// Проверяем формат поля 0 (Id)
if len(tuple.Fields[0]) != 8 {
// Исправляем или логируем
return fmt.Errorf("invalid Id format")
}
return nil
}
Флаг Repaired¶
После восстановления:
user, _ := user.SelectById(ctx, 123)
if user.Repaired {
// Данные были восстановлены
// Update() будет работать как Replace()
log.Println("User data was repaired")
}
Сериализация типов¶
Числовые типы¶
| Go тип | Размер в tuple |
|---|---|
int8, uint8 |
1 байт |
int16, uint16 |
2 байта |
int32, uint32 |
4 байта |
int64, uint64 |
8 байт |
Строки¶
Строки хранятся как есть, размер определяется тегом size:
Сериализаторы¶
Для сложных типов используйте сериализаторы:
type FieldsUser struct {
Settings string `ar:"serializer:Json;size:2048"`
}
type SerializersUser struct {
Settings map[string]interface{} `ar:""`
}
Пример миграции схемы¶
Добавление поля¶
- Добавьте поле в декларацию:
type FieldsUser struct {
Id int64 `ar:"primary_key"`
Email string `ar:"size:256"`
Name string `ar:"size:256"`
Status string `ar:"size:32"` // Новое поле
}
- Добавьте триггер для старых записей:
type TriggersUser struct {
RepairTuple bool `ar:"pkg:github.com/myapp/repair;func:RepairUserTuple"`
}
func RepairUserTuple(tuple *octopus.TupleData) error {
if len(tuple.Fields) < 4 {
// Добавляем Status по умолчанию
tuple.Fields = append(tuple.Fields, []byte("active"))
}
return nil
}
- Перегенерируйте код:
Best Practices¶
1. Всегда объявляйте триггер¶
2. Логируйте восстановления¶
func RepairTuple(tuple *octopus.TupleData) error {
if len(tuple.Fields) < expected {
log.Printf("Repairing tuple with %d fields", len(tuple.Fields))
}
// ...
}
3. Не удаляйте поля¶
При удалении поля из декларации старые данные станут extraFields. Лучше пометить поле как deprecated.
4. Тестируйте миграции¶
func TestRepairTuple(t *testing.T) {
tuple := &octopus.TupleData{
Fields: [][]byte{
{0, 0, 0, 0, 0, 0, 0, 1}, // Id
[]byte("email"), // Email
// Name отсутствует
},
}
err := RepairTuple(tuple)
require.NoError(t, err)
require.Len(t, tuple.Fields, 3)
}
Следующие шаги¶
- Хранимые процедуры — вызов Lua процедур
- Триггеры — декларация триггеров