Files
2026-04-07 15:04:38 +06:00

723 lines
21 KiB
Go
Raw Permalink 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 main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"direct-dev.ru/gitea/GiteaAdmin/elowdb-go/pkg/linedb"
)
// User представляет пользователя
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
IsActive bool `json:"isActive"`
Role string `json:"role"`
CreatedAt int64 `json:"createdAt"`
LastLogin *int64 `json:"lastLogin,omitempty"`
}
// Product представляет продукт
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Category string `json:"category"`
InStock bool `json:"inStock"`
SellerID int `json:"sellerId"`
CreatedAt int64 `json:"createdAt"`
}
// Order представляет заказ
type Order struct {
ID int `json:"id"`
UserID int `json:"userId"`
ProductID int `json:"productId"`
Quantity int `json:"quantity"`
TotalPrice float64 `json:"totalPrice"`
Status string `json:"status"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// OrderItem представляет элемент заказа
type OrderItem struct {
ID int `json:"id"`
OrderID int `json:"orderId"`
ProductID int `json:"productId"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}
// APIResponse представляет ответ API
type APIResponse struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}
var Database *linedb.LineDb
var server *http.Server
// parseTestNumbers парсит номера тестов из аргументов командной строки
func parseTestNumbers() []int {
if len(os.Args) < 2 {
return []int{}
}
// Получаем аргумент после имени программы
arg := os.Args[1]
// Проверяем, что это не флаг помощи
if arg == "-h" || arg == "--help" || arg == "help" {
printUsage()
return []int{}
}
// Разбиваем по запятой
parts := strings.Split(arg, ",")
var numbers []int
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
num, err := strconv.Atoi(part)
if err != nil {
fmt.Printf("Ошибка: '%s' не является числом\n", part)
printUsage()
return []int{}
}
// Проверяем диапазон
if num < 1 || num > 4 {
fmt.Printf("Ошибка: номер теста %d должен быть от 1 до 4\n", num)
printUsage()
return []int{}
}
numbers = append(numbers, num)
}
return numbers
}
// printUsage выводит справку по использованию
func printUsage() {
fmt.Println("\nИспользование:")
fmt.Println(" go run main.go # Запустить все тесты")
fmt.Println(" go run main.go 1 # Запустить только тест 1")
fmt.Println(" go run main.go 1,3 # Запустить тесты 1 и 3")
fmt.Println(" go run main.go 2,3,4 # Запустить тесты 2, 3 и 4")
fmt.Println(" go run main.go help # Показать эту справку")
fmt.Println("\nДоступные тесты:")
fmt.Println(" 1 - Базовые CRUD операции")
fmt.Println(" 2 - Сложные запросы и фильтрация")
fmt.Println(" 3 - Работа с несколькими коллекциями")
fmt.Println(" 4 - Тест производительности")
fmt.Println(" 5 - HTTP API сервер")
fmt.Println(" 6 - API endpoints")
os.Exit(0)
}
// runAllTests запускает все тесты
func runAllTests() {
testBasicCRUDOperations()
testComplexQueries()
testMultipleCollections()
testPerformance()
testHTTPServer()
testAPIEndpoints()
}
// runSelectedTests запускает только выбранные тесты
func runSelectedTests(numbers []int) {
for _, num := range numbers {
switch num {
case 1:
fmt.Println("1. Базовые CRUD операции")
testBasicCRUDOperations()
case 2:
fmt.Println("2. Сложные запросы и фильтрация")
testComplexQueries()
case 3:
fmt.Println("3. Работа с несколькими коллекциями")
testMultipleCollections()
case 4:
fmt.Println("4. Тест производительности")
testPerformance()
case 5:
fmt.Println("5. HTTP API сервер")
testHTTPServer()
case 6:
fmt.Println("6. API endpoints")
testAPIEndpoints()
}
}
}
func testBasicCRUDOperations() {
// Создаем пользователя
user := map[string]any{
"id": 1,
"username": "testuser",
"email": "test@example.com",
"isActive": true,
"role": "user",
"createdAt": time.Now().Unix(),
}
// Вставка
if err := Database.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert user: %v", err)
return
}
fmt.Printf("\tПользователь создан: %s\n", user["username"])
// Чтение
result, err := Database.Read("users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read users: %v", err)
return
}
fmt.Printf("\tПользователей в базе: %d\n", len(result))
// Обновление
updateData := map[string]any{"role": "admin"}
updated, err := Database.Update(updateData, "users", map[string]any{"id": 1}, linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to update user: %v", err)
return
}
fmt.Printf(" Обновлено пользователей: %d\n", len(updated))
// Удаление
deleted, err := Database.Delete(map[string]any{"id": 1}, "users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to delete user: %v", err)
return
}
fmt.Printf(" Удалено пользователей: %d\n", len(deleted))
// Проверяем, что пользователь удален
result, err = Database.Read("users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read users after delete: %v", err)
return
}
fmt.Printf(" Пользователей после удаления: %d\n", len(result))
}
func testComplexQueries() {
// Создаем несколько пользователей
users := []any{
map[string]any{
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"isActive": true,
"role": "user",
"createdAt": time.Now().Unix(),
},
map[string]any{
"id": 2,
"username": "jane_smith",
"email": "jane@example.com",
"isActive": true,
"role": "admin",
"createdAt": time.Now().Unix(),
},
map[string]any{
"id": 3,
"username": "bob_wilson",
"email": "bob@example.com",
"isActive": false,
"role": "user",
"createdAt": time.Now().Unix(),
},
}
if err := Database.Insert(users, "users", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert users: %v", err)
return
}
// Фильтрация по роли
admins, err := Database.ReadByFilter(map[string]any{"role": "admin"}, "users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to filter admins: %v", err)
return
}
fmt.Printf(" Администраторов: %d\n", len(admins))
// Фильтрация по активности
activeUsers, err := Database.ReadByFilter(map[string]any{"isActive": true}, "users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to filter active users: %v", err)
return
}
fmt.Printf(" Активных пользователей: %d\n", len(activeUsers))
// Строковая фильтрация
emailFilter, err := Database.ReadByFilter("email == 'john@example.com'", "users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to filter by email: %v", err)
return
}
fmt.Printf(" Пользователей с email john@example.com: %d\n", len(emailFilter))
// Частичное совпадение
partialMatch, err := Database.ReadByFilter(
map[string]any{"username": "john"},
"users",
linedb.LineDbAdapterOptions{StrictCompare: false},
)
if err != nil {
log.Printf("Failed to partial match: %v", err)
return
}
fmt.Printf(" Пользователей с именем содержащим 'john': %d\n", len(partialMatch))
}
func testMultipleCollections() {
// Создаем пользователя
user := map[string]any{
"id": 1,
"username": "customer1",
"email": "customer1@example.com",
"isActive": true,
"role": "customer",
"createdAt": time.Now().Unix(),
}
// Создаем продукты
products := []any{
map[string]any{
"id": 1,
"name": "Laptop",
"price": 999.99,
"category": "Electronics",
"inStock": true,
"sellerId": 1,
"createdAt": time.Now().Unix(),
},
map[string]any{
"id": 2,
"name": "Mouse",
"price": 29.99,
"category": "Electronics",
"inStock": true,
"sellerId": 1,
"createdAt": time.Now().Unix(),
},
}
// Создаем заказ
order := map[string]any{
"id": 1,
"userId": 1,
"productId": 1,
"quantity": 2,
"totalPrice": 1999.98,
"status": "pending",
"createdAt": time.Now().Unix(),
"updatedAt": time.Now().Unix(),
}
// Создаем элементы заказа
orderItems := []any{
map[string]any{
"id": 1,
"orderId": 1,
"productId": 1,
"quantity": 2,
"price": 999.99,
},
}
// Вставляем данные во все коллекции
if err := Database.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert user: %v", err)
return
}
if err := Database.Insert(products, "products", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert products: %v", err)
return
}
if err := Database.Insert(order, "orders", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert order: %v", err)
return
}
if err := Database.Insert(orderItems, "orderItems", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert order items: %v", err)
return
}
// Читаем данные из всех коллекций
users, err := Database.Read("users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read users: %v", err)
return
}
productsData, err := Database.Read("products", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read products: %v", err)
return
}
orders, err := Database.Read("orders", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read orders: %v", err)
return
}
orderItemsData, err := Database.Read("orderItems", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read order items: %v", err)
return
}
fmt.Printf(" Пользователей: %d\n", len(users))
fmt.Printf(" Продуктов: %d\n", len(productsData))
fmt.Printf(" Заказов: %d\n", len(orders))
fmt.Printf(" Элементов заказов: %d\n", len(orderItemsData))
// Сложный запрос: найти все заказы пользователя
userOrders, err := Database.ReadByFilter(map[string]any{"userId": 1}, "orders", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to get user orders: %v", err)
return
}
fmt.Printf(" Заказов пользователя 1: %d\n", len(userOrders))
}
func testPerformance() {
// Тест производительности массовой вставки
recordCount := 500
fmt.Printf(" Создание %d записей для теста производительности...\n", recordCount)
// Создаем пользователей
users := make([]any, recordCount)
for i := 1; i <= recordCount; i++ {
users[i-1] = map[string]any{
"id": i,
"username": fmt.Sprintf("user%d", i),
"email": fmt.Sprintf("user%d@example.com", i),
"isActive": i%2 == 0, // чередуем активных и неактивных
"role": "user",
"createdAt": time.Now().Unix(),
}
}
// Измеряем время вставки
startTime := time.Now()
if err := Database.Insert(users, "users", linedb.LineDbAdapterOptions{}); err != nil {
log.Printf("Failed to insert users: %v", err)
return
}
endTime := time.Now()
duration := endTime.Sub(startTime)
fmt.Printf(" Вставлено пользователей: %d\n", recordCount)
fmt.Printf(" Время выполнения: %v\n", duration)
fmt.Printf(" Скорость: %.2f записей/сек\n", float64(recordCount)/duration.Seconds())
// Тест производительности чтения
startTime = time.Now()
result, err := Database.Read("users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to read users: %v", err)
return
}
endTime = time.Now()
readDuration := endTime.Sub(startTime)
fmt.Printf(" Прочитано пользователей: %d\n", len(result))
fmt.Printf(" Время чтения: %v\n", readDuration)
fmt.Printf(" Скорость чтения: %.2f записей/сек\n", float64(len(result))/readDuration.Seconds())
// Тест производительности фильтрации
startTime = time.Now()
activeUsers, err := Database.ReadByFilter(map[string]any{"isActive": true}, "users", linedb.LineDbAdapterOptions{})
if err != nil {
log.Printf("Failed to filter active users: %v", err)
return
}
endTime = time.Now()
filterDuration := endTime.Sub(startTime)
fmt.Printf(" Активных пользователей: %d\n", len(activeUsers))
fmt.Printf(" Время фильтрации: %v\n", filterDuration)
}
func testHTTPServer() {
// Настраиваем HTTP сервер
mux := http.NewServeMux()
// Обработчики API
mux.HandleFunc("/api/users", handleUsers)
mux.HandleFunc("/api/products", handleProducts)
mux.HandleFunc("/api/orders", handleOrders)
server = &http.Server{
Addr: ":3001",
Handler: mux,
}
fmt.Printf(" Запуск HTTP сервера на порту 3001...\n")
fmt.Printf(" Доступные эндпоинты:\n")
fmt.Printf(" - GET /api/users - получить всех пользователей\n")
fmt.Printf(" - POST /api/users - создать пользователя\n")
fmt.Printf(" - GET /api/products - получить все продукты\n")
fmt.Printf(" - POST /api/products - создать продукт\n")
fmt.Printf(" - GET /api/orders - получить все заказы\n")
fmt.Printf(" - POST /api/orders - создать заказ\n")
// Запускаем сервер в горутине
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("HTTP server error: %v", err)
}
}()
// Даем серверу время на запуск
time.Sleep(1 * time.Second)
// Тестируем API
fmt.Printf(" Тестирование API...\n")
testAPIEndpoints()
// Останавливаем сервер
if err := server.Close(); err != nil {
log.Printf("Failed to close server: %v", err)
}
}
func handleUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
users, err := Database.Read("users", linedb.LineDbAdapterOptions{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(APIResponse{Success: true, Data: users})
case "POST":
var user map[string]any
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := Database.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(APIResponse{Success: true, Message: "User created successfully"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func handleProducts(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
products, err := Database.Read("products", linedb.LineDbAdapterOptions{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(APIResponse{Success: true, Data: products})
case "POST":
var product map[string]any
if err := json.NewDecoder(r.Body).Decode(&product); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := Database.Insert(product, "products", linedb.LineDbAdapterOptions{}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(APIResponse{Success: true, Message: "Product created successfully"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func handleOrders(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
orders, err := Database.Read("orders", linedb.LineDbAdapterOptions{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(APIResponse{Success: true, Data: orders})
case "POST":
var order map[string]any
if err := json.NewDecoder(r.Body).Decode(&order); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := Database.Insert(order, "orders", linedb.LineDbAdapterOptions{}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(APIResponse{Success: true, Message: "Order created successfully"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func testAPIEndpoints() {
// Тестируем GET /api/users
resp, err := http.Get("http://localhost:3001/api/users")
if err != nil {
log.Printf("Failed to test GET /api/users: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
fmt.Printf(" ✓ GET /api/users работает\n")
} else {
fmt.Printf(" ✗ GET /api/users вернул статус %d\n", resp.StatusCode)
}
// Тестируем POST /api/users
userData := map[string]any{
"id": 999,
"username": "apitest",
"email": "apitest@example.com",
"isActive": true,
"role": "user",
"createdAt": time.Now().Unix(),
}
jsonData, _ := json.Marshal(userData)
resp, err = http.Post("http://localhost:3001/api/users", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
log.Printf("Failed to test POST /api/users: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
fmt.Printf(" ✓ POST /api/users работает\n")
} else {
fmt.Printf(" ✗ POST /api/users вернул статус %d\n", resp.StatusCode)
}
}
func main() {
// Очищаем тестовую папку
os.RemoveAll("./data/test-linedb-integration")
// Создаем опции инициализации
initOptions := &linedb.LineDbInitOptions{
CacheSize: 1000,
CacheTTL: 10 * time.Second,
DBFolder: "./data/test-linedb-integration",
Collections: []linedb.JSONLFileOptions{
{
CollectionName: "users",
AllocSize: 256,
IndexedFields: []string{"id", "username", "email"},
},
{
CollectionName: "products",
AllocSize: 256,
IndexedFields: []string{"id", "name", "category"},
},
{
CollectionName: "orders",
AllocSize: 256,
IndexedFields: []string{"id", "userId", "status"},
},
{
CollectionName: "orderItems",
AllocSize: 256,
IndexedFields: []string{"id", "orderId", "productId"},
},
},
Partitions: []linedb.PartitionCollection{
{
CollectionName: "orders",
PartIDFnStr: "userId",
},
},
}
// Создаем базу данных
Database = linedb.NewLineDb(nil)
// Инициализируем базу данных
if err := Database.Init(true, initOptions); err != nil {
log.Fatalf("Failed to init database: %v", err)
}
defer Database.Close()
fmt.Println("=== LineDb Integration Tests Demo ===")
numbers := parseTestNumbers()
if len(numbers) > 0 {
runSelectedTests(numbers)
} else {
runAllTests()
}
// // Тест 1: Базовые CRUD операции
// fmt.Println("1. Базовые CRUD операции")
// testBasicCRUDOperations()
// // Тест 2: Сложные запросы и фильтрация
// fmt.Println("\n2. Сложные запросы и фильтрация")
// testComplexQueries()
// // Тест 3: Работа с несколькими коллекциями
// fmt.Println("\n3. Работа с несколькими коллекциями")
// testMultipleCollections()
// // Тест 4: Производительность
// fmt.Println("\n4. Тест производительности")
// testPerformance()
// // Тест 5: HTTP API сервер
// fmt.Println("\n5. HTTP API сервер")
// testHTTPServer()
fmt.Println("\n=== Все тесты завершены ===")
}