Files
elowdb-go/tests/linedb_test.go
2026-03-04 10:10:57 +06:00

475 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package tests
import (
"os"
"testing"
"time"
"linedb/pkg/linedb"
)
func TestLineDbBasic(t *testing.T) {
// Очищаем тестовую папку
os.RemoveAll("./testdata")
// Создаем опции инициализации
initOptions := &linedb.LineDbInitOptions{
CacheSize: 100,
CacheTTL: time.Minute,
DBFolder: "./testdata",
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "test",
AllocSize: 256,
},
},
}
// Создаем базу данных
db := linedb.NewLineDb(nil)
defer db.Close()
// Инициализируем базу данных
if err := db.Init(false, initOptions); err != nil {
t.Fatalf("Failed to init database: %v", err)
}
// Тест вставки
testData := map[string]any{
"name": "test",
"value": 123,
}
if err := db.Insert(testData, "test", linedb.LineDbAdapterOptions{}); err != nil {
t.Fatalf("Failed to insert data: %v", err)
}
// Тест чтения
allData, err := db.Read("test", linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to read data: %v", err)
}
if len(allData) != 1 {
t.Fatalf("Expected 1 record, got %d", len(allData))
}
// Тест фильтрации
filter := map[string]any{"name": "test"}
filteredData, err := db.ReadByFilter(filter, "test", linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to filter data: %v", err)
}
if len(filteredData) != 1 {
t.Fatalf("Expected 1 filtered record, got %d", len(filteredData))
}
// Тест обновления
updateData := map[string]any{"value": 456}
updateFilter := map[string]any{"name": "test"}
updatedData, err := db.Update(updateData, "test", updateFilter, linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to update data: %v", err)
}
if len(updatedData) != 1 {
t.Fatalf("Expected 1 updated record, got %d", len(updatedData))
}
// Тест удаления
deleteFilter := map[string]any{"name": "test"}
deletedData, err := db.Delete(deleteFilter, "test", linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to delete data: %v", err)
}
if len(deletedData) != 1 {
t.Fatalf("Expected 1 deleted record, got %d", len(deletedData))
}
// Проверяем что данных больше нет
remainingData, err := db.Read("test", linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to read remaining data: %v", err)
}
if len(remainingData) != 0 {
t.Fatalf("Expected 0 remaining records, got %d", len(remainingData))
}
}
func TestLineDbPartitioning(t *testing.T) {
// Очищаем тестовую папку
os.RemoveAll("./testdata")
// Создаем опции инициализации с партиционированием
initOptions := &linedb.LineDbInitOptions{
CacheSize: 100,
CacheTTL: time.Minute,
DBFolder: "./testdata",
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "orders",
AllocSize: 256,
},
},
Partitions: []linedb.PartitionCollection{
{
CollectionName: "orders",
PartIDFn: func(item any) string {
if itemMap, ok := item.(map[string]any); ok {
if userId, exists := itemMap["userId"]; exists {
return toString(userId)
}
}
return "default"
},
},
},
}
// Создаем базу данных
db := linedb.NewLineDb(nil)
defer db.Close()
// Инициализируем базу данных
if err := db.Init(false, initOptions); err != nil {
t.Fatalf("Failed to init database: %v", err)
}
// Создаем заказы для разных пользователей
orders := []any{
map[string]any{
"userId": 1,
"item": "laptop",
"price": 999.99,
},
map[string]any{
"userId": 1,
"item": "mouse",
"price": 29.99,
},
map[string]any{
"userId": 2,
"item": "keyboard",
"price": 89.99,
},
}
// Вставляем заказы
if err := db.Insert(orders, "orders", linedb.LineDbAdapterOptions{}); err != nil {
t.Fatalf("Failed to insert orders: %v", err)
}
// Читаем все заказы
allOrders, err := db.Read("orders", linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to read orders: %v", err)
}
if len(allOrders) != 3 {
t.Fatalf("Expected 3 orders, got %d", len(allOrders))
}
// Фильтруем заказы пользователя 1
user1Filter := map[string]any{"userId": 1}
user1Orders, err := db.ReadByFilter(user1Filter, "orders", linedb.LineDbAdapterOptions{})
if err != nil {
t.Fatalf("Failed to filter user 1 orders: %v", err)
}
if len(user1Orders) != 2 {
t.Fatalf("Expected 2 orders for user 1, got %d", len(user1Orders))
}
}
// toString конвертирует значение в строку (для партиционирования)
func toString(value any) string {
switch v := value.(type) {
case string:
return v
case int:
return string(rune(v))
case int64:
return string(rune(v))
case float64:
return string(rune(int(v)))
default:
return ""
}
}
// setupUniqueCollection создаёт БД с коллекцией, у которой заданы UniqueFields
func setupUniqueCollection(t *testing.T, uniqueFields []string) (*linedb.LineDb, func()) {
t.Helper()
os.RemoveAll("./testdata")
initOptions := &linedb.LineDbInitOptions{
CacheSize: 100,
CacheTTL: time.Minute,
DBFolder: "./testdata",
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "users",
AllocSize: 256,
UniqueFields: uniqueFields,
},
},
}
db := linedb.NewLineDb(nil)
if err := db.Init(false, initOptions); err != nil {
t.Fatalf("Failed to init database: %v", err)
}
return db, func() { db.Close(); os.RemoveAll("./testdata") }
}
// TestLineDbUniqueFieldsNonEmpty проверяет, что дубликаты непустых значений в уникальном поле запрещены
func TestLineDbUniqueFieldsNonEmpty(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 1 failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "bob", "email": "bob@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 2 failed: %v", err)
}
// Дубликат email — ожидаем ошибку
err := db.Insert(map[string]any{"name": "alice2", "email": "alice@test.com"}, "users", opts)
if err == nil {
t.Fatal("Expected unique constraint violation for duplicate email")
}
if err.Error() == "" {
t.Fatalf("Expected meaningful error, got: %v", err)
}
all, _ := db.Read("users", opts)
if len(all) != 2 {
t.Fatalf("Expected 2 records, got %d", len(all))
}
}
// TestLineDbUniqueFieldsEmptyValues проверяет, что пустые значения (nil, "") в уникальном поле допускают несколько записей
func TestLineDbUniqueFieldsEmptyValues(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
// Несколько записей с отсутствующим полем email — должно быть разрешено
if err := db.Insert(map[string]any{"name": "a"}, "users", opts); err != nil {
t.Fatalf("Insert with nil email failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "b"}, "users", opts); err != nil {
t.Fatalf("Second insert with nil email failed: %v", err)
}
// Несколько записей с nil в email — должно быть разрешено
if err := db.Insert(map[string]any{"name": "a", "email": nil}, "users", opts); err != nil {
t.Fatalf("Insert with nil email failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "b", "email": nil}, "users", opts); err != nil {
t.Fatalf("Second insert with nil email failed: %v", err)
}
// Несколько записей с пустой строкой
if err := db.Insert(map[string]any{"name": "c", "email": ""}, "users", opts); err != nil {
t.Fatalf("Insert with empty email failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "d", "email": ""}, "users", opts); err != nil {
t.Fatalf("Second insert with empty email failed: %v", err)
}
all, err := db.Read("users", opts)
if err != nil {
t.Fatalf("Read failed: %v", err)
}
if len(all) != 6 {
t.Fatalf("Expected 6 records (2 with nil email + 2 with empty string + 2 without email), got %d", len(all))
}
}
// TestLineDbUniqueFieldsSkipCheckExistingForWrite проверяет режим SkipCheckExistingForWrite — уникальность не проверяется
func TestLineDbUniqueFieldsSkipCheckExistingForWrite(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{SkipCheckExistingForWrite: true}
if err := db.Insert(map[string]any{"name": "a", "email": "same@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 1 failed: %v", err)
}
// С тем же email, но с пропуском проверки — должно пройти
if err := db.Insert(map[string]any{"name": "b", "email": "same@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 2 with same email (skip check) failed: %v", err)
}
all, _ := db.Read("users", opts)
if len(all) != 2 {
t.Fatalf("Expected 2 records with SkipCheckExistingForWrite, got %d", len(all))
}
}
// TestLineDbUniqueFieldsUpdateConflict проверяет, что Update не может установить значение, уже занятое другой записью
func TestLineDbUniqueFieldsUpdateConflict(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert alice failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "bob", "email": "bob@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert bob failed: %v", err)
}
// Пытаемся обновить bob на email alice
_, err := db.Update(
map[string]any{"email": "alice@test.com"},
"users",
map[string]any{"name": "bob"},
opts,
)
if err == nil {
t.Fatal("Expected unique constraint violation when updating to existing email")
}
}
// TestLineDbUniqueFieldsUpdateSameValue проверяет, что Update на ту же запись (тот же unique-значение) допустим
func TestLineDbUniqueFieldsUpdateSameValue(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert failed: %v", err)
}
// Обновляем имя, оставляя email тем же — допустимо
updated, err := db.Update(
map[string]any{"name": "alice_updated"},
"users",
map[string]any{"email": "alice@test.com"},
opts,
)
if err != nil {
t.Fatalf("Update same record failed: %v", err)
}
if len(updated) != 1 {
t.Fatalf("Expected 1 updated record, got %d", len(updated))
}
}
// TestLineDbUniqueFieldsMultipleFields проверяет несколько уникальных полей
func TestLineDbUniqueFieldsMultipleFields(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email", "username"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
if err := db.Insert(map[string]any{"name": "a", "email": "a@test.com", "username": "user_a"}, "users", opts); err != nil {
t.Fatalf("Insert 1 failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "b", "email": "b@test.com", "username": "user_b"}, "users", opts); err != nil {
t.Fatalf("Insert 2 failed: %v", err)
}
// Дубликат email
err := db.Insert(map[string]any{"name": "c", "email": "a@test.com", "username": "user_c"}, "users", opts)
if err == nil {
t.Fatal("Expected unique constraint violation for duplicate email")
}
// Дубликат username
err = db.Insert(map[string]any{"name": "d", "email": "d@test.com", "username": "user_a"}, "users", opts)
if err == nil {
t.Fatal("Expected unique constraint violation for duplicate username")
}
all, _ := db.Read("users", opts)
if len(all) != 2 {
t.Fatalf("Expected 2 records, got %d", len(all))
}
}
// TestLineDbUniqueFieldsBatchInsertDuplicate проверяет, что дубликат внутри одного batch Insert даёт ошибку
func TestLineDbUniqueFieldsBatchInsertDuplicate(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
batch := []any{
map[string]any{"name": "a", "email": "x@test.com"},
map[string]any{"name": "b", "email": "x@test.com"},
}
err := db.Insert(batch, "users", opts)
if err == nil {
t.Fatal("Expected unique constraint violation in batch insert")
}
all, _ := db.Read("users", opts)
if len(all) != 0 {
t.Fatalf("Expected 0 records (batch should rollback or not persist), got %d", len(all))
}
}
// TestLineDbUniqueFieldsUpdateToEmpty проверяет, что Update может установить пустое значение в unique-поле
func TestLineDbUniqueFieldsUpdateToEmpty(t *testing.T) {
db, cleanup := setupUniqueCollection(t, []string{"email"})
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
if err := db.Insert(map[string]any{"name": "a", "email": "a@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "b", "email": "b@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 2 failed: %v", err)
}
// Обновляем одну запись: email -> ""
updated, err := db.Update(
map[string]any{"email": ""},
"users",
map[string]any{"email": "a@test.com"},
opts,
)
if err != nil {
t.Fatalf("Update to empty email failed: %v", err)
}
if len(updated) != 1 {
t.Fatalf("Expected 1 updated record, got %d", len(updated))
}
all, _ := db.Read("users", opts)
if len(all) != 2 {
t.Fatalf("Expected 2 records, got %d", len(all))
}
}
// TestLineDbUniqueFieldsNoUniqueFields проверяет, что без UniqueFields дубликаты разрешены
func TestLineDbUniqueFieldsNoUniqueFields(t *testing.T) {
db, cleanup := setupUniqueCollection(t, nil)
defer cleanup()
opts := linedb.LineDbAdapterOptions{}
if err := db.Insert(map[string]any{"name": "a", "email": "dup@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 1 failed: %v", err)
}
if err := db.Insert(map[string]any{"name": "b", "email": "dup@test.com"}, "users", opts); err != nil {
t.Fatalf("Insert 2 (duplicate email, no unique) failed: %v", err)
}
all, _ := db.Read("users", opts)
if len(all) != 2 {
t.Fatalf("Expected 2 records when no unique fields, got %d", len(all))
}
}