init elowdb go-port commit

This commit is contained in:
41 changed files with 7273 additions and 0 deletions

354
CUSTOM_JSON.md Normal file
View File

@@ -0,0 +1,354 @@
# Настраиваемые функции сериализации JSON в LineDb
## Обзор
LineDb поддерживает настраиваемые функции сериализации и десериализации JSON. По умолчанию используется библиотека [go-json](https://github.com/goccy/go-json), но вы можете указать любые функции сериализации, соответствующие определенным сигнатурам.
## Функции по умолчанию
```go
// Функции сериализации по умолчанию (используют go-json)
func defaultJSONMarshal(v any) ([]byte, error) {
return json.Marshal(v)
}
func defaultJSONUnmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
```
## Настройка в опциях коллекции
Вы можете указать пользовательские функции сериализации в `JSONLFileOptions`:
```go
type JSONLFileOptions struct {
CollectionName string `json:"collectionName,omitempty"`
AllocSize int `json:"allocSize,omitempty"`
IndexedFields []string `json:"indexedFields,omitempty"`
EncryptKeyForLineDb string `json:"encryptKeyForLineDb,omitempty"`
SkipInvalidLines bool `json:"skipInvalidLines,omitempty"`
DecryptKey string `json:"decryptKey,omitempty"`
ConvertStringIdToNumber bool `json:"convertStringIdToNumber,omitempty"`
// Функции сериализации и десериализации JSON
JSONMarshal func(any) ([]byte, error) `json:"-"`
JSONUnmarshal func([]byte, any) error `json:"-"`
}
```
## Примеры использования
### 1. Использование стандартного encoding/json
```go
import "encoding/json"
// Создаем функции сериализации с использованием стандартного encoding/json
standardJSONMarshal := func(v any) ([]byte, error) {
return json.Marshal(v)
}
standardJSONUnmarshal := func(data []byte, v any) error {
return json.Unmarshal(data, v)
}
// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "users",
JSONMarshal: standardJSONMarshal,
JSONUnmarshal: standardJSONUnmarshal,
},
},
}
```
### 2. Кастомные функции с метаданными
```go
import "encoding/json"
import "time"
// Создаем кастомные функции сериализации с дополнительной логикой
customJSONMarshal := func(v any) ([]byte, error) {
// Добавляем метаданные к сериализации
data := map[string]any{
"data": v,
"timestamp": time.Now().Unix(),
"version": "1.0",
}
return json.Marshal(data)
}
customJSONUnmarshal := func(data []byte, v any) error {
// Извлекаем данные из кастомного формата
var wrapper map[string]any
if err := json.Unmarshal(data, &wrapper); err != nil {
return err
}
// Извлекаем основную часть данных
if dataField, exists := wrapper["data"]; exists {
// Сериализуем обратно в JSON и десериализуем в целевой тип
dataBytes, err := json.Marshal(dataField)
if err != nil {
return err
}
return json.Unmarshal(dataBytes, v)
}
// Если нет поля data, пытаемся десериализовать как обычный JSON
return json.Unmarshal(data, v)
}
// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "users",
JSONMarshal: customJSONMarshal,
JSONUnmarshal: customJSONUnmarshal,
},
},
}
```
### 3. Функции с шифрованием
```go
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io"
)
var encryptionKey = []byte("your-32-byte-encryption-key-here")
func encryptedJSONMarshal(v any) ([]byte, error) {
// Сначала сериализуем в JSON
jsonData, err := json.Marshal(v)
if err != nil {
return nil, err
}
// Создаем AES cipher
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return nil, err
}
// Создаем GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Создаем nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Шифруем данные
ciphertext := gcm.Seal(nonce, nonce, jsonData, nil)
// Кодируем в base64
return []byte(base64.StdEncoding.EncodeToString(ciphertext)), nil
}
func encryptedJSONUnmarshal(data []byte, v any) error {
// Декодируем из base64
ciphertext, err := base64.StdEncoding.DecodeString(string(data))
if err != nil {
return err
}
// Создаем AES cipher
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return err
}
// Создаем GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
// Извлекаем nonce
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
// Расшифровываем данные
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return err
}
// Десериализуем JSON
return json.Unmarshal(plaintext, v)
}
// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "secure_users",
JSONMarshal: encryptedJSONMarshal,
JSONUnmarshal: encryptedJSONUnmarshal,
},
},
}
```
### 4. ID всегда первое поле
```go
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
func idFirstJSONMarshal(v any) ([]byte, error) {
if data, ok := v.(map[string]any); ok {
var parts []string
// Сначала добавляем id если он есть
if id, exists := data["id"]; exists {
idBytes, err := json.Marshal(id)
if err != nil {
return nil, err
}
parts = append(parts, fmt.Sprintf(`"id":%s`, string(idBytes)))
}
// Затем добавляем все остальные поля в алфавитном порядке
var keys []string
for key := range data {
if key != "id" {
keys = append(keys, key)
}
}
sort.Strings(keys)
for _, key := range keys {
valueBytes, err := json.Marshal(data[key])
if err != nil {
return nil, err
}
parts = append(parts, fmt.Sprintf(`"%s":%s`, key, string(valueBytes)))
}
jsonStr := "{" + strings.Join(parts, ",") + "}"
return []byte(jsonStr), nil
}
// Если это не map, используем стандартную сериализацию
return json.Marshal(v)
}
func idFirstJSONUnmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "users",
JSONMarshal: idFirstJSONMarshal,
JSONUnmarshal: idFirstJSONUnmarshal,
},
},
}
```
## Требования к функциям
### JSONMarshal
- **Сигнатура**: `func(any) ([]byte, error)`
- **Назначение**: Сериализует данные в JSON
- **Возвращает**: Байты JSON и ошибку (если есть)
### JSONUnmarshal
- **Сигнатура**: `func([]byte, any) error`
- **Назначение**: Десериализует данные из JSON
- **Возвращает**: Ошибку (если есть)
## Совместимость
При использовании кастомных функций сериализации важно обеспечить совместимость:
1. **Обратная совместимость**: Убедитесь, что новые функции могут читать данные, записанные старыми функциями
2. **Версионирование**: Рассмотрите добавление версии в метаданные для будущих изменений
3. **Тестирование**: Всегда тестируйте функции сериализации с реальными данными
## Примеры файлов
### Стандартный JSON (go-json по умолчанию)
```json
{"id":1,"username":"user1","email":"user1@example.com"}
```
### Стандартный JSON (encoding/json)
```json
{"id":2,"username":"user2","email":"user2@example.com"}
```
### Кастомный JSON с метаданными
```json
{
"data": {"id":3,"username":"user3","email":"user3@example.com"},
"timestamp": 1754969076,
"version": "1.0"
}
```
### JSON с ID первым полем
```json
{"id":1,"createdAt":1754969435,"email":"user@example.com","isActive":true,"role":"user","username":"user1"}
```
## Запуск примера
```bash
cd examples/custom-json
go run main.go
```
Этот пример демонстрирует:
- Использование go-json (по умолчанию)
- Использование стандартного encoding/json
- Кастомные функции сериализации с метаданными
- Функции сериализации с ID первым полем
## Тестирование
```bash
cd examples/custom-json
go test -v
```
Тесты проверяют:
- Корректность сериализации/десериализации
- Работу с JSONLFile
- Обработку ошибок